Skip to content

Commit dc96cf2

Browse files
njb90claude
andcommitted
feat: add PlatformId types, installForUniversal/Windsurf, detectActivePlatforms
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c58e3df commit dc96cf2

1 file changed

Lines changed: 106 additions & 9 deletions

File tree

src/aiToolsService.ts

Lines changed: 106 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
2035
type 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

5881
const 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

300375
export 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+
340437
export async function readConfiguredMcpServerKeys(
341438
rootUri: vscode.Uri,
342439
mcpFilePath: string,

0 commit comments

Comments
 (0)