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 ( 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