1
+ import path from 'path' ;
2
+ import * as vscode from 'vscode' ;
3
+
4
+ interface RegisteredPrompt extends vscode . QuickPickItem {
5
+ tag : string ;
6
+ ref : string ; // Either a local path or a git ref: `<provider>:<owner>/<repo>?path=<path>`
7
+ mcp : boolean ;
8
+ }
9
+
10
+ export const showRegistryInput = async ( context : vscode . ExtensionContext ) => {
11
+ let registry = context . globalState . get ( 'registry' ) as { [ key : string ] : RegisteredPrompt } || { } ;
12
+ if ( Object . keys ( registry ) . length === 0 ) {
13
+ registry = {
14
+ 'placeholder' : {
15
+ label : 'Placeholder' ,
16
+ ref : 'github:docker/labs-ai-tools-for-devs?path=prompts/npm-project.md' ,
17
+ mcp : false ,
18
+ tag : 'placeholder'
19
+ } ,
20
+ 'placeholder2' : {
21
+ label : 'Placeholder2' ,
22
+ ref : 'github:docker/labs-ai-tools-for-devs?path=prompts/examples/explain_dockerfile.md' ,
23
+ mcp : true ,
24
+ tag : 'placeholder2'
25
+ } ,
26
+ 'placeholder3' : {
27
+ label : 'Placeholder3' ,
28
+ ref : 'github:docker/labs-ai-tools-for-devs?path=prompts/examples/curl.md' ,
29
+ mcp : false ,
30
+ tag : 'placeholder3'
31
+ }
32
+ ,
33
+ 'placeholder4' : {
34
+ label : 'Placeholder4' ,
35
+ ref : '~/Dev/labs-ai-tools-for-devs/prompts/examples/curl.md' ,
36
+ mcp : false ,
37
+ tag : 'placeholder4'
38
+ }
39
+ } ;
40
+ await context . globalState . update ( 'registry' , registry ) ;
41
+ }
42
+ const refreshItems = async ( ) => {
43
+ input . items = Object . values ( await context . globalState . get ( 'registry' ) as { [ key : string ] : RegisteredPrompt } ) . map ( registeredPrompt => ( {
44
+ ...registeredPrompt , buttons :
45
+ [
46
+ {
47
+ tooltip : 'Run' ,
48
+ iconPath : new vscode . ThemeIcon ( 'run' )
49
+ } ,
50
+ {
51
+ tooltip : 'Open' ,
52
+ text : 'Open' ,
53
+ iconPath : new vscode . ThemeIcon ( 'open-preview' )
54
+ }
55
+ ] ,
56
+ detail : registeredPrompt . mcp ? 'MCP' : 'Not MCP'
57
+ } ) ) ;
58
+ }
59
+ const input = vscode . window . createQuickPick < RegisteredPrompt > ( ) ;
60
+ input . canSelectMany = true ;
61
+ input . matchOnDescription = true ;
62
+ input . matchOnDetail = true ;
63
+ input . placeholder = 'Start typing to search or paste a prompt reference to register' ;
64
+ refreshItems ( ) ;
65
+ input . onDidChangeSelection ( ( selection ) => {
66
+ if ( selection . length > 0 && input . buttons . find ( button => button . tooltip === 'Delete all' ) === undefined ) {
67
+ input . buttons = [
68
+ {
69
+ tooltip : 'Delete all' ,
70
+ iconPath : new vscode . ThemeIcon ( 'trash' )
71
+ } ,
72
+ {
73
+ tooltip : 'Register selection as MCP servers' ,
74
+ iconPath : new vscode . ThemeIcon ( 'eye' )
75
+ } ,
76
+ {
77
+ tooltip : 'Unregister selection as MCP servers' ,
78
+ iconPath : new vscode . ThemeIcon ( 'eye-closed' )
79
+ }
80
+ ] ;
81
+ }
82
+ if ( selection . length === 0 && ! input . buttons . find ( button => button . tooltip === 'Delete all' ) ) {
83
+ input . buttons = [ ] ;
84
+ }
85
+ } ) ;
86
+ input . onDidChangeValue ( ( value ) => {
87
+ try {
88
+ const uri = convertRefInputToURI ( value ) ;
89
+ input . items = [ ...input . items , { label : `Register ${ uri . path } ` , ref : uri . toString ( ) , mcp : false , tag : uri . path , detail : uri . toString ( ) } ] ;
90
+ } catch ( e ) {
91
+ // Input is not a valid reference
92
+ }
93
+ } ) ;
94
+ input . onDidAccept ( ( ) => {
95
+ input . hide ( ) ;
96
+ if ( input . selectedItems . length === 0 ) {
97
+
98
+ }
99
+
100
+ } ) ;
101
+ input . onDidTriggerButton ( async ( button ) => {
102
+ if ( button . tooltip === 'Delete all' ) {
103
+ const reg = context . globalState . get ( 'registry' ) as { [ key : string ] : RegisteredPrompt } ;
104
+ let deleted = 0 ;
105
+ for ( const item of input . selectedItems ) {
106
+ delete reg [ item . tag ] ;
107
+ deleted ++ ;
108
+ }
109
+ vscode . window . showInformationMessage ( `Deleted ${ deleted } prompts` ) ;
110
+ await context . globalState . update ( 'registry' , reg ) ;
111
+ await refreshItems ( ) ;
112
+ }
113
+ if ( button . tooltip === 'Register selection as MCP servers' ) {
114
+ const reg = context . globalState . get ( 'registry' ) as { [ key : string ] : RegisteredPrompt } ;
115
+ let registered = 0 ;
116
+ for ( const item of input . selectedItems ) {
117
+ reg [ item . tag ] . mcp = true ;
118
+ registered ++ ;
119
+ }
120
+ vscode . window . showInformationMessage ( `Registered ${ registered } prompts as MCP servers` ) ;
121
+ await context . globalState . update ( 'registry' , reg ) ;
122
+ await refreshItems ( ) ;
123
+ }
124
+ if ( button . tooltip === 'Unregister selection as MCP servers' ) {
125
+ const reg = context . globalState . get ( 'registry' ) as { [ key : string ] : RegisteredPrompt } ;
126
+ let unregistered = 0 ;
127
+ for ( const item of input . selectedItems ) {
128
+ reg [ item . tag ] . mcp = false ;
129
+ unregistered ++ ;
130
+ }
131
+ vscode . window . showInformationMessage ( `Unregistered ${ unregistered } prompts as MCP servers` ) ;
132
+ await context . globalState . update ( 'registry' , reg ) ;
133
+ await refreshItems ( ) ;
134
+ }
135
+ } ) ;
136
+ input . show ( ) ;
137
+ }
138
+
139
+ export const convertRefInputToURI = ( ref : string ) : vscode . Uri => {
140
+ // Ref is a git ref
141
+ if ( ref . match ( / ^ .* : .* \/ $ / ) ) {
142
+ let [ provider , rest ] = ref . split ( ':' ) ;
143
+ let [ owner , rest2 ] = rest . split ( '/' ) ;
144
+ let [ repo , path ] = rest2 . split ( '?path=' ) ;
145
+ return vscode . Uri . parse ( `https://${ provider } .com/${ owner } /${ repo } /blob/main/${ path } ` ) ;
146
+ }
147
+ // Local path
148
+ try {
149
+ return vscode . Uri . file
50E0
( ref ) ;
150
+ } catch ( e ) {
151
+ throw new Error ( `Invalid reference: ${ ref } ` ) ;
152
+ }
153
+ }
154
+
155
+ export const registerPrompt = ( context : vscode . ExtensionContext , ref : string , tag : string ) => {
156
+ const registry = context . globalState . get ( 'registry' ) as { [ key : string ] : RegisteredPrompt } ;
157
+ registry [ tag ] = { label : tag , ref : ref . toString ( ) , mcp : false , tag } ;
158
+ context . globalState . update ( 'registry' , registry ) ;
159
+ }
160
+
161
+ export const registerOpenPrompt = async ( context : vscode . ExtensionContext ) => {
162
+ // If no open markdown editor, return
163
+ const editor = vscode . window . activeTextEditor ;
164
+ if ( ! editor || editor . document . languageId !== 'markdown' ) {
165
+ return ;
166
+ }
167
+ // If open markdown editor, get the tag from the file name
168
+ const markdownPath = editor . document . uri . fsPath ;
169
+ const tag = path . basename ( markdownPath ) ;
170
+ const registry = await context . globalState . get ( 'registry' ) as { [ key : string ] : RegisteredPrompt } ;
171
+ if ( registry [ tag ] ) {
172
+ if ( registry [ tag ] . ref === markdownPath ) {
173
+ const option = await vscode . window . showErrorMessage ( `You have already registered this prompt.` , 'Open' , 'Unregister' ) ;
174
+ if ( option === 'Open' ) {
175
+ return vscode . commands . executeCommand ( 'vscode.open' , vscode . Uri . parse ( registry [ tag ] . ref ) ) ;
176
+ }
177
+ if ( option === 'Unregister' ) {
178
+ delete registry [ tag ] ;
179
+ await context . globalState . update ( 'registry' , registry ) ;
180
+ return vscode . window . showInformationMessage ( `Unregistered prompt ${ tag } ` ) ;
181
+ }
182
+ }
183
+ else {
184
+ return vscode . window . showErrorMessage ( `Prompt ${ tag } already registered at ${ registry [ tag ] . ref } ` ) ;
185
+ }
186
+
187
+ }
188
+ registerPrompt ( context , markdownPath , tag ) ;
189
+ return vscode . window . showInformationMessage ( `Registered prompt ${ tag } ` ) ;
190
+ }
191
+
192
+ /**
193
+ * TODO Q's:
194
+ * Default MCP servers?
195
+ * Update to new version migration?
196
+ * Two commands or one command? Registry vs MCP
197
+ * Local ref == path?
198
+ * Ref conversions?
199
+ * Language + Copy --> MCP debugging
200
+ * Connecting to anthropic config?
201
+ *
202
+ */
0 commit comments