@@ -17,6 +17,21 @@ export type SkillInfo = {
1717 dirName : string ;
1818} ;
1919
20+ export type PlatformId = 'universal' | 'claude-code' | 'vscode-copilot' | 'windsurf' ;
21+
22+ export type PlatformDef = {
23+ id : PlatformId ;
24+ label : string ;
25+ sublabel ?: string ;
26+ } ;
27+
28+ export const PLATFORMS : PlatformDef [ ] = [
29+ { id : 'universal' , label : 'Universal' , sublabel : 'Cursor, Codex, Amp, Warp + more' } ,
30+ { id : 'claude-code' , label : 'Claude Code' } ,
31+ { id : 'vscode-copilot' , label : 'VS Code (Copilot)' } ,
32+ { id : 'windsurf' , label : 'Windsurf' } ,
33+ ] ;
34+
2035type GitHubEntry = {
2136 name : string ;
2237 type : "file" | "dir" ;
@@ -53,6 +68,14 @@ export function getMcpFilePath(editor: EditorType): string {
5368 }
5469}
5570
71+ export function detectEditorPlatform ( ) : PlatformId {
72+ const editor = detectEditor ( ) ;
73+ if ( editor === 'windsurf' ) { return 'windsurf' ; }
74+ if ( editor === 'vscode' ) { return 'vscode-copilot' ; }
75+ if ( editor === 'cursor' || editor === 'antigravity' ) { return 'universal' ; }
76+ return 'claude-code' ; // claude-code, unknown
77+ }
78+
5679// ── GitHub API helpers ────────────────────────────────────────────────────────
5780
5881const SKILLS_BASE = "https://api.github.com/repos/cloudinary-devs/skills/contents" ;
@@ -295,20 +318,72 @@ export async function installForCopilot(
295318 }
296319}
297320
321+ export async function installForUniversal (
322+ rootUri : vscode . Uri ,
323+ skillName : string ,
324+ skillContent : string ,
325+ createdFiles : string [ ] ,
326+ errors : string [ ]
327+ ) : Promise < void > {
328+ const skillFile = vscode . Uri . joinPath ( rootUri , `.agents/skills/${ skillName } /SKILL.md` ) ;
329+ const written = await writeWithOverwriteCheck ( skillFile , skillContent , `${ skillName } /SKILL.md` ) ;
330+ if ( ! written ) { return ; }
331+ createdFiles . push ( `.agents/skills/${ skillName } /SKILL.md` ) ;
332+
333+ let refs : Array < { name : string ; content : string } > ;
334+ try { refs = await fetchReferenceFiles ( skillName ) ; } catch ( err : any ) {
335+ errors . push ( `${ skillName } references: ${ err . message } ` ) ; return ;
336+ }
337+ for ( const ref of refs ) {
338+ try {
339+ const refUri = vscode . Uri . joinPath ( rootUri , `.agents/skills/${ skillName } /references/${ ref . name } ` ) ;
340+ await ensureDir ( vscode . Uri . joinPath ( refUri , '..' ) ) ;
341+ await vscode . workspace . fs . writeFile ( refUri , Buffer . from ( ref . content , 'utf-8' ) ) ;
342+ createdFiles . push ( `.agents/skills/${ skillName } /references/${ ref . name } ` ) ;
343+ } catch ( err : any ) { errors . push ( `${ skillName } /references/${ ref . name } : ${ err . message } ` ) ; }
344+ }
345+ }
346+
347+ export async function installForWindsurf (
348+ rootUri : vscode . Uri ,
349+ skillName : string ,
350+ skillContent : string ,
351+ createdFiles : string [ ] ,
352+ errors : string [ ]
353+ ) : Promise < void > {
354+ const skillFile = vscode . Uri . joinPath ( rootUri , `.windsurf/skills/${ skillName } /SKILL.md` ) ;
355+ const written = await writeWithOverwriteCheck ( skillFile , skillContent , `${ skillName } /SKILL.md` ) ;
356+ if ( ! written ) { return ; }
357+ createdFiles . push ( `.windsurf/skills/${ skillName } /SKILL.md` ) ;
358+
359+ let refs : Array < { name : string ; content : string } > ;
360+ try { refs = await fetchReferenceFiles ( skillName ) ; } catch ( err : any ) {
361+ errors . push ( `${ skillName } references: ${ err . message } ` ) ; return ;
362+ }
363+ for ( const ref of refs ) {
364+ try {
365+ const refUri = vscode . Uri . joinPath ( rootUri , `.windsurf/skills/${ skillName } /references/${ ref . name } ` ) ;
366+ await ensureDir ( vscode . Uri . joinPath ( refUri , '..' ) ) ;
367+ await vscode . workspace . fs . writeFile ( refUri , Buffer . from ( ref . content , 'utf-8' ) ) ;
368+ createdFiles . push ( `.windsurf/skills/${ skillName } /references/${ ref . name } ` ) ;
369+ } catch ( err : any ) { errors . push ( `${ skillName } /references/${ ref . name } : ${ err . message } ` ) ; }
370+ }
371+ }
372+
298373// ── Status detection ──────────────────────────────────────────────────────────
299374
300375export async function readInstalledSkillDirNames (
301376 rootUri : vscode . Uri ,
302- ideTargetLabel : string ,
377+ platform : PlatformId ,
303378 skills : SkillInfo [ ]
304379) : Promise < Set < string > > {
305380 const installed = new Set < string > ( ) ;
306381
307- if ( ideTargetLabel === "VS Code (Copilot)" ) {
382+ if ( platform === 'vscode-copilot' ) {
308383 try {
309- const uri = vscode . Uri . joinPath ( rootUri , " .github/copilot-instructions.md" ) ;
384+ const uri = vscode . Uri . joinPath ( rootUri , ' .github/copilot-instructions.md' ) ;
310385 const bytes = await vscode . workspace . fs . readFile ( uri ) ;
311- const content = Buffer . from ( bytes ) . toString ( " utf-8" ) ;
386+ const content = Buffer . from ( bytes ) . toString ( ' utf-8' ) ;
312387 for ( const skill of skills ) {
313388 if ( content . includes ( `## ${ skill . name } ` ) ) {
314389 installed . add ( skill . dirName ) ;
@@ -320,14 +395,17 @@ export async function readInstalledSkillDirNames(
320395 return installed ;
321396 }
322397
398+ const pathPrefix =
399+ platform === 'claude-code' ? '.claude/skills' :
400+ platform === 'universal' ? '.agents/skills' :
401+ /* windsurf */ '.windsurf/skills' ;
402+
323403 await Promise . all (
324404 skills . map ( async ( skill ) => {
325405 try {
326- const checkPath =
327- ideTargetLabel === "Claude Code"
328- ? `.claude/skills/${ skill . dirName } /SKILL.md`
329- : `.cursor/rules/${ skill . dirName } .mdc` ;
330- await vscode . workspace . fs . stat ( vscode . Uri . joinPath ( rootUri , checkPath ) ) ;
406+ await vscode . workspace . fs . stat (
407+ vscode . Uri . joinPath ( rootUri , `${ pathPrefix } /${ skill . dirName } /SKILL.md` )
408+ ) ;
331409 installed . add ( skill . dirName ) ;
332410 } catch {
333411 // not installed
@@ -337,6 +415,25 @@ export async function readInstalledSkillDirNames(
337415 return installed ;
338416}
339417
418+ export async function detectActivePlatforms ( rootUri : vscode . Uri ) : Promise < PlatformId [ ] > {
419+ const checks : Array < { id : PlatformId ; path : string } > = [
420+ { id : 'universal' , path : '.agents/skills' } ,
421+ { id : 'claude-code' , path : '.claude/skills' } ,
422+ { id : 'vscode-copilot' , path : '.github/copilot-instructions.md' } ,
423+ { id : 'windsurf' , path : '.windsurf/skills' } ,
424+ ] ;
425+ const active = new Set < PlatformId > ( [ detectEditorPlatform ( ) ] ) ;
426+ await Promise . all (
427+ checks . map ( async ( { id, path } ) => {
428+ try {
429+ await vscode . workspace . fs . stat ( vscode . Uri . joinPath ( rootUri , path ) ) ;
430+ active . add ( id ) ;
431+ } catch { /* not present */ }
432+ } )
433+ ) ;
434+ return [ ...active ] ;
435+ }
436+
340437export async function readConfiguredMcpServerKeys (
341438 rootUri : vscode . Uri ,
342439 mcpFilePath : string ,
0 commit comments