@votruongdanh/ai-agent-skills 3.2.1 → 3.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.kiro/skills/_scripts/.ai-memory-template.md +55 -55
  2. package/.kiro/skills/_scripts/checklist.md +35 -34
  3. package/.kiro/skills/_scripts/memory-compact.md +142 -0
  4. package/.kiro/skills/_scripts/memory-dedupe.md +26 -26
  5. package/.kiro/skills/_scripts/pre-deploy.md +42 -42
  6. package/.kiro/skills/_scripts/verify-all.md +28 -28
  7. package/.kiro/skills/agents/SKILL.md +85 -85
  8. package/.kiro/skills/agents/agents/backend-specialist.md +29 -29
  9. package/.kiro/skills/agents/agents/database-architect.md +28 -28
  10. package/.kiro/skills/agents/agents/debugger.md +29 -29
  11. package/.kiro/skills/agents/agents/devops-engineer.md +30 -30
  12. package/.kiro/skills/agents/agents/documentation-writer.md +29 -29
  13. package/.kiro/skills/agents/agents/frontend-specialist.md +30 -30
  14. package/.kiro/skills/agents/agents/orchestrator.md +36 -36
  15. package/.kiro/skills/agents/agents/performance-optimizer.md +29 -29
  16. package/.kiro/skills/agents/agents/project-planner.md +30 -30
  17. package/.kiro/skills/agents/agents/security-auditor.md +30 -30
  18. package/.kiro/skills/agents/agents/test-engineer.md +29 -29
  19. package/.kiro/skills/brainstorm/SKILL.md +66 -66
  20. package/.kiro/skills/clean/SKILL.md +90 -90
  21. package/.kiro/skills/create/SKILL.md +63 -63
  22. package/.kiro/skills/debug/SKILL.md +71 -71
  23. package/.kiro/skills/deploy/SKILL.md +66 -66
  24. package/.kiro/skills/enhance/SKILL.md +58 -58
  25. package/.kiro/skills/explain/SKILL.md +69 -69
  26. package/.kiro/skills/instructions.md +95 -93
  27. package/.kiro/skills/integrate/SKILL.md +50 -50
  28. package/.kiro/skills/motion-ui/SKILL.md +492 -0
  29. package/.kiro/skills/orchestrate/SKILL.md +73 -73
  30. package/.kiro/skills/plan/SKILL.md +69 -69
  31. package/.kiro/skills/preview/SKILL.md +55 -55
  32. package/.kiro/skills/status/SKILL.md +57 -57
  33. package/.kiro/skills/test/SKILL.md +58 -58
  34. package/.kiro/skills/ui-ux-pro-max/SKILL.md +65 -65
  35. package/LICENSE +21 -21
  36. package/README.md +212 -208
  37. package/bin/cli.js +876 -876
  38. package/bin/install-skills.ps1 +30 -30
  39. package/index.js +23 -23
  40. package/lib/skill-bundle.js +741 -725
  41. package/package.json +62 -62
  42. package/scripts/render-targets.js +22 -22
package/bin/cli.js CHANGED
@@ -1,876 +1,876 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
- const os = require('os');
5
- const path = require('path');
6
- const https = require('https');
7
- const { execSync } = require('child_process');
8
- const readline = require('readline');
9
-
10
- const {
11
- findIDEContext,
12
- findProjectRootForIDE,
13
- getAvailableIDENames,
14
- getIDEDefinition,
15
- getInstalledVersion,
16
- getSkillCatalog,
17
- installBundle,
18
- listSkillNames,
19
- normalizeIDEName,
20
- parseFrontmatter,
21
- } = require('../lib/skill-bundle');
22
-
23
- const PACKAGE_NAME = '@votruongdanh/ai-agent-skills';
24
- const CURRENT_VERSION = require('../package.json').version;
25
-
26
- // ─── ANSI Colors ────────────────────────────────────────────────────────────
27
- const c = {
28
- reset: '\x1b[0m',
29
- bold: '\x1b[1m',
30
- dim: '\x1b[2m',
31
- green: '\x1b[32m',
32
- yellow: '\x1b[33m',
33
- blue: '\x1b[34m',
34
- magenta: '\x1b[35m',
35
- cyan: '\x1b[36m',
36
- red: '\x1b[31m',
37
- bgBlue: '\x1b[44m',
38
- bgGreen: '\x1b[42m',
39
- white: '\x1b[37m',
40
- };
41
-
42
- function badge(text) {
43
- return `${c.bgBlue}${c.white}${c.bold} ${text} ${c.reset}`;
44
- }
45
-
46
- function success(text) {
47
- return `${c.green}✔${c.reset} ${text}`;
48
- }
49
-
50
- function warn(text) {
51
- return `${c.yellow}⚠${c.reset} ${text}`;
52
- }
53
-
54
- function info(text) {
55
- return `${c.cyan}ℹ${c.reset} ${text}`;
56
- }
57
-
58
- function error(text) {
59
- return `${c.red}✖${c.reset} ${text}`;
60
- }
61
-
62
- function step(num, text) {
63
- return `${c.dim}[${num}]${c.reset} ${text}`;
64
- }
65
-
66
- // ─── Interactive Prompt Helpers ─────────────────────────────────────────────
67
- function createRL() {
68
- return readline.createInterface({ input: process.stdin, output: process.stdout });
69
- }
70
-
71
- function ask(rl, question) {
72
- return new Promise((resolve) => rl.question(question, resolve));
73
- }
74
-
75
- async function selectMenu(rl, title, options) {
76
- console.log(`\n${c.bold}${title}${c.reset}\n`);
77
- options.forEach((opt, i) => {
78
- const marker = opt.recommended ? ` ${c.green}← recommended${c.reset}` : '';
79
- const desc = opt.description ? ` ${c.dim}(${opt.description})${c.reset}` : '';
80
- console.log(` ${c.cyan}${i + 1}${c.reset}) ${c.bold}${opt.label}${c.reset}${desc}${marker}`);
81
- });
82
- console.log('');
83
-
84
- while (true) {
85
- const answer = await ask(rl, ` ${c.dim}Your choice (1-${options.length}):${c.reset} `);
86
- const num = parseInt(answer.trim(), 10);
87
- if (num >= 1 && num <= options.length) {
88
- return options[num - 1];
89
- }
90
- console.log(` ${c.red}Please enter a number between 1 and ${options.length}${c.reset}`);
91
- }
92
- }
93
-
94
- async function confirm(rl, question) {
95
- const answer = await ask(rl, ` ${question} ${c.dim}(y/n):${c.reset} `);
96
- return answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes';
97
- }
98
-
99
- function parseIDEFlag() {
100
- for (const arg of process.argv.slice(2)) {
101
- if (arg.startsWith('--ide=')) {
102
- return arg.split('=')[1];
103
- }
104
- }
105
- return null;
106
- }
107
-
108
- function parseArg(prefix) {
109
- for (const arg of process.argv.slice(2)) {
110
- if (arg.startsWith(`--${prefix}=`)) {
111
- return arg.split('=').slice(1).join('=');
112
- }
113
- }
114
- return null;
115
- }
116
-
117
- function hasFlag(flag) {
118
- return process.argv.slice(2).includes(flag);
119
- }
120
-
121
- function positionalArg(index) {
122
- const args = process.argv.slice(2).filter((a) => !a.startsWith('-'));
123
- return args[index] || null;
124
- }
125
-
126
- // ─── Update Check ───────────────────────────────────────────────────────────
127
- function checkForUpdates(callback) {
128
- https
129
- .get(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, (res) => {
130
- let data = '';
131
- res.on('data', (chunk) => { data += chunk; });
132
- res.on('end', () => {
133
- try {
134
- const latest = JSON.parse(data).version;
135
- if (latest !== CURRENT_VERSION) {
136
- console.log(warn(`Update available: ${c.yellow}${CURRENT_VERSION}${c.reset} → ${c.green}${latest}${c.reset}`));
137
- console.log(info(`Run: ${c.cyan}npx ${PACKAGE_NAME}@latest init${c.reset}\n`));
138
- }
139
- } catch (_) {}
140
- callback();
141
- });
142
- })
143
- .on('error', () => callback());
144
- }
145
-
146
- // ─── IDE Resolution ─────────────────────────────────────────────────────────
147
- function resolveInstallContext() {
148
- const cwd = process.cwd();
149
- const manualIDE = parseIDEFlag();
150
-
151
- if (manualIDE) {
152
- const normalized = normalizeIDEName(manualIDE);
153
- if (normalized) {
154
- const projectMatch = findProjectRootForIDE(cwd, normalized);
155
- const useDetectedProjectRoot = projectMatch && projectMatch.projectDir === cwd;
156
- return {
157
- ide: normalized,
158
- source: 'flag',
159
- baseDir: useDetectedProjectRoot ? projectMatch.projectDir : cwd,
160
- matchedPath: useDetectedProjectRoot ? projectMatch.matchedPath : null,
161
- };
162
- }
163
- console.log(error(`Unknown IDE: ${manualIDE}`));
164
- console.log(info(`Supported: ${getAvailableIDENames().join(', ')}\n`));
165
- }
166
-
167
- const detected = findIDEContext(cwd);
168
- return {
169
- ide: detected.ide,
170
- source: detected.source,
171
- baseDir: detected.projectDir || cwd,
172
- matchedPath: detected.matchedPath,
173
- };
174
- }
175
-
176
- // ─── Interactive Init ───────────────────────────────────────────────────────
177
- async function interactiveInit() {
178
- const rl = createRL();
179
-
180
- console.log(`\n${badge('AI Agent Skills')} v${CURRENT_VERSION}\n`);
181
-
182
- // Step 1: Choose IDE
183
- const ideFlag = parseIDEFlag();
184
- let selectedIDE;
185
- const supportedIDEOptions = [
186
- { label: 'Cursor', value: 'cursor', description: '.cursor/skills + .cursor/rules', recommended: true },
187
- { label: 'Kiro', value: 'kiro', description: '.kiro/skills' },
188
- { label: 'Antigravity', value: 'antigravity', description: '.agent/workflows' },
189
- { label: 'Codex', value: 'codex', description: '.agents/skills (+ AGENTS.md / memories)' },
190
- { label: 'VS Code', value: 'vscode', description: '.github/skills' },
191
- { label: 'GitHub Copilot', value: 'copilot', description: '.github/skills' },
192
- { label: 'All supported IDEs', value: 'all', description: 'Install for every supported IDE' },
193
- ];
194
-
195
- if (ideFlag) {
196
- if (String(ideFlag).trim().toLowerCase() === 'all') {
197
- selectedIDE = 'all';
198
- console.log(info(`IDE target: ${c.bold}All supported IDEs${c.reset} (from --ide flag)\n`));
199
- } else {
200
- selectedIDE = normalizeIDEName(ideFlag);
201
- }
202
-
203
- if (!selectedIDE || selectedIDE === 'generic') {
204
- console.log(error(`Unknown IDE: ${ideFlag}`));
205
- console.log(info(`Supported: cursor, kiro, antigravity, codex, vscode, copilot, all\n`));
206
- rl.close();
207
- process.exit(1);
208
- }
209
-
210
- if (selectedIDE !== 'all') {
211
- const def = getIDEDefinition(selectedIDE);
212
- console.log(info(`IDE: ${c.bold}${def.displayName}${c.reset} (from --ide flag)\n`));
213
- }
214
- } else {
215
- const choice = await selectMenu(rl, '🔧 Choose your IDE:', supportedIDEOptions);
216
- selectedIDE = choice.value;
217
- }
218
-
219
- // Step 2: Choose scope (project vs global)
220
- let scope = hasFlag('--global') ? 'global' : null;
221
-
222
- if (!scope) {
223
- const globalDescription =
224
- selectedIDE === 'all'
225
- ? 'Install to each IDE global directory'
226
- : `~/${getIDEDefinition(selectedIDE).projectRoot}`;
227
- const scopeOptions = [
228
- { label: 'This project only', value: 'project', description: process.cwd(), recommended: true },
229
- { label: 'Global (all projects)', value: 'global', description: globalDescription },
230
- ];
231
- const scopeChoice = await selectMenu(rl, '📂 Install scope:', scopeOptions);
232
- scope = scopeChoice.value;
233
- }
234
-
235
- rl.close();
236
-
237
- // Step 3: Install
238
- const cwd = process.cwd();
239
- const installTargets =
240
- selectedIDE === 'all'
241
- ? ['cursor', 'kiro', 'antigravity', 'codex', 'vscode', 'copilot']
242
- : [selectedIDE];
243
-
244
- console.log(`\n${step(1, 'Installing skills...')}`);
245
-
246
- const installResults = [];
247
- for (const ide of installTargets) {
248
- const ideDefinition = getIDEDefinition(ide);
249
- const baseDir = cwd;
250
- const rootDir =
251
- scope === 'global'
252
- ? ideDefinition.globalRoot
253
- : path.join(baseDir, ideDefinition.projectRoot);
254
- const installedVersion = getInstalledVersion(rootDir);
255
-
256
- const result = installBundle({
257
- baseDir,
258
- ide,
259
- scope,
260
- version: CURRENT_VERSION,
261
- includeCompatibilityAliases: true,
262
- });
263
-
264
- installResults.push({ result, installedVersion });
265
- }
266
-
267
- for (const { result, installedVersion } of installResults) {
268
- console.log(success(`${result.skillCount} skills installed for ${c.bold}${result.displayName}${c.reset}`));
269
- if (installedVersion) {
270
- console.log(info(`Updated: ${c.yellow}${installedVersion}${c.reset} → ${c.green}${CURRENT_VERSION}${c.reset}`));
271
- }
272
- }
273
-
274
- console.log(`\n${step(2, 'Targets created:')}`);
275
- for (const { result } of installResults) {
276
- for (const target of result.targets) {
277
- const rel = path.relative(cwd, target.targetDir) || '.';
278
- const tag = target.compatibility ? ` ${c.dim}(compat)${c.reset}` : '';
279
- console.log(` ${c.green}→${c.reset} ${rel}${tag}`);
280
- }
281
- }
282
-
283
- // Warnings
284
- const uniqueWarnings = dedupeWarnings(installResults.flatMap(({ result }) => result.warnings));
285
- if (uniqueWarnings.length) {
286
- console.log('');
287
- for (const w of uniqueWarnings) {
288
- console.log(warn(w.message));
289
- }
290
- }
291
-
292
- // Next steps
293
- const nextStepIDEName =
294
- selectedIDE === 'all'
295
- ? 'your IDE'
296
- : installResults[0].result.displayName;
297
- console.log(`\n${c.bold}${c.green}Done!${c.reset} Next steps:\n`);
298
- console.log(` 1. ${c.dim}Reopen${c.reset} ${c.bold}${nextStepIDEName}${c.reset}`);
299
- console.log(` 2. ${c.dim}Open agent chat and type${c.reset} ${c.cyan}/${c.reset} ${c.dim}to list skills${c.reset}`);
300
- console.log(` 3. ${c.dim}Try:${c.reset} ${c.cyan}/create${c.reset}, ${c.cyan}/debug${c.reset}, ${c.cyan}/explain${c.reset}, or ${c.cyan}/plan${c.reset}`);
301
- console.log('');
302
- }
303
-
304
- // ─── GitHub Skill Fetcher ───────────────────────────────────────────────────
305
- function httpsGet(url) {
306
- return new Promise((resolve, reject) => {
307
- const options = { headers: { 'User-Agent': 'ai-agent-skills-cli' } };
308
- https.get(url, options, (res) => {
309
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
310
- return httpsGet(res.headers.location).then(resolve, reject);
311
- }
312
- if (res.statusCode !== 200) {
313
- return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
314
- }
315
- let data = '';
316
- res.on('data', (chunk) => { data += chunk; });
317
- res.on('end', () => resolve(data));
318
- }).on('error', reject);
319
- });
320
- }
321
-
322
- async function fetchGitHubTree(owner, repo, branch) {
323
- const url = `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`;
324
- const data = JSON.parse(await httpsGet(url));
325
- return data.tree || [];
326
- }
327
-
328
- async function fetchGitHubFile(owner, repo, branch, filePath) {
329
- const url = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filePath}`;
330
- return httpsGet(url);
331
- }
332
-
333
- async function addSkillFromGitHub() {
334
- const source = positionalArg(1); // e.g. "owner/repo" or "owner/repo/skills/my-skill"
335
-
336
- if (!source) {
337
- console.log(error('Usage: ai-skills add <owner/repo> [--skill=name] [--ide=cursor]'));
338
- console.log(info('Example: ai-skills add anthropics/skills --skill=pdf-processing'));
339
- console.log(info('Example: ai-skills add anthropics/skills (lists all skills)'));
340
- process.exit(1);
341
- }
342
-
343
- const parts = source.split('/');
344
- if (parts.length < 2) {
345
- console.log(error('Format: owner/repo (e.g. anthropics/skills)'));
346
- process.exit(1);
347
- }
348
-
349
- const owner = parts[0];
350
- const repo = parts[1];
351
- const branch = parseArg('branch') || 'main';
352
- const skillFilter = parseArg('skill');
353
-
354
- console.log(`\n${badge('AI Agent Skills')} ${c.dim}— Add from GitHub${c.reset}\n`);
355
- console.log(info(`Fetching from ${c.cyan}${owner}/${repo}${c.reset} (${branch})...`));
356
-
357
- try {
358
- const tree = await fetchGitHubTree(owner, repo, branch);
359
-
360
- // Find all SKILL.md files
361
- const skillFiles = tree
362
- .filter((item) => item.path.endsWith('/SKILL.md') && item.type === 'blob')
363
- .map((item) => {
364
- const dir = path.dirname(item.path);
365
- const name = path.basename(dir);
366
- return { name, dir, skillMdPath: item.path };
367
- });
368
-
369
- if (!skillFiles.length) {
370
- console.log(error('No SKILL.md files found in this repository.'));
371
- process.exit(1);
372
- }
373
-
374
- console.log(success(`Found ${c.bold}${skillFiles.length}${c.reset} skills in repo\n`));
375
-
376
- // If --skill specified, filter
377
- let selectedSkills = skillFiles;
378
- if (skillFilter) {
379
- selectedSkills = skillFiles.filter((s) => s.name === skillFilter);
380
- if (!selectedSkills.length) {
381
- console.log(error(`Skill "${skillFilter}" not found. Available:`));
382
- skillFiles.forEach((s) => console.log(` ${c.cyan}→${c.reset} ${s.name} ${c.dim}(${s.dir})${c.reset}`));
383
- process.exit(1);
384
- }
385
- } else {
386
- // Interactive selection
387
- const rl = createRL();
388
-
389
- console.log(`${c.bold}Available skills:${c.reset}\n`);
390
- skillFiles.forEach((s, i) => {
391
- console.log(` ${c.cyan}${i + 1}${c.reset}) ${c.bold}${s.name}${c.reset} ${c.dim}(${s.dir})${c.reset}`);
392
- });
393
- console.log(` ${c.cyan}${skillFiles.length + 1}${c.reset}) ${c.bold}All skills${c.reset} ${c.dim}(install everything)${c.reset}`);
394
- console.log('');
395
-
396
- const answer = await ask(rl, ` ${c.dim}Choose (1-${skillFiles.length + 1}):${c.reset} `);
397
- const num = parseInt(answer.trim(), 10);
398
-
399
- if (num === skillFiles.length + 1) {
400
- // Install all
401
- } else if (num >= 1 && num <= skillFiles.length) {
402
- selectedSkills = [skillFiles[num - 1]];
403
- } else {
404
- console.log(error('Invalid choice'));
405
- rl.close();
406
- process.exit(1);
407
- }
408
-
409
- // Choose target IDE
410
- const ideFlag = parseIDEFlag();
411
- let targetIDE;
412
-
413
- if (ideFlag) {
414
- targetIDE = normalizeIDEName(ideFlag);
415
- } else {
416
- const ideOptions = [
417
- { label: 'Cursor', value: 'cursor', description: '.cursor/skills', recommended: true },
418
- { label: 'Kiro', value: 'kiro', description: '.kiro/skills' },
419
- { label: 'Antigravity', value: 'antigravity', description: '.agent/workflows' },
420
- { label: 'Codex', value: 'codex', description: '.agents/skills (+ AGENTS.md / memories)' },
421
- { label: 'VS Code', value: 'vscode', description: '.github/skills' },
422
- { label: 'GitHub Copilot', value: 'copilot', description: '.github/skills' },
423
- ];
424
- const ideChoice = await selectMenu(rl, '🔧 Install to:', ideOptions);
425
- targetIDE = ideChoice.value;
426
- }
427
-
428
- // Choose scope
429
- let scope = 'project';
430
- if (!hasFlag('--global')) {
431
- const scopeOptions = [
432
- { label: 'This project', value: 'project', recommended: true },
433
- { label: 'Global (~/.agents/skills)', value: 'global' },
434
- ];
435
- const scopeChoice = await selectMenu(rl, '📂 Scope:', scopeOptions);
436
- scope = scopeChoice.value;
437
- }
438
-
439
- rl.close();
440
-
441
- // Download and install
442
- console.log(`\n${step(1, 'Downloading skills...')}`);
443
-
444
- const targetBase = scope === 'global' ? os.homedir() : process.cwd();
445
- let targetDir;
446
- if (targetIDE === 'agents') {
447
- targetDir = path.join(targetBase, '.agents', 'skills');
448
- } else {
449
- const ideDef = getIDEDefinition(targetIDE);
450
- if (ideDef) {
451
- const target = ideDef.targets.find((t) => t.format === 'skill') || ideDef.targets[0];
452
- targetDir = scope === 'global'
453
- ? path.join(ideDef.globalRoot, target.relativeDir)
454
- : path.join(targetBase, ideDef.projectRoot, target.relativeDir);
455
- } else {
456
- targetDir = path.join(targetBase, '.agents', 'skills');
457
- }
458
- }
459
-
460
- let installed = 0;
461
- for (const skill of selectedSkills) {
462
- process.stdout.write(` ${c.cyan}↓${c.reset} ${skill.name}...`);
463
-
464
- // Fetch SKILL.md
465
- const content = await fetchGitHubFile(owner, repo, branch, skill.skillMdPath);
466
- const skillDir = path.join(targetDir, skill.name);
467
- fs.mkdirSync(skillDir, { recursive: true });
468
- fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content, 'utf8');
469
-
470
- // Fetch companion dirs (scripts/, references/, assets/)
471
- const companionDirs = ['scripts', 'references', 'assets'];
472
- for (const companion of companionDirs) {
473
- const prefix = `${skill.dir}/${companion}/`;
474
- const companionFiles = tree.filter((item) => item.path.startsWith(prefix) && item.type === 'blob');
475
- for (const file of companionFiles) {
476
- const relativePath = file.path.slice(skill.dir.length + 1);
477
- const destPath = path.join(skillDir, relativePath);
478
- fs.mkdirSync(path.dirname(destPath), { recursive: true });
479
- const fileContent = await fetchGitHubFile(owner, repo, branch, file.path);
480
- fs.writeFileSync(destPath, fileContent, 'utf8');
481
- }
482
- }
483
-
484
- installed++;
485
- console.log(` ${c.green}✔${c.reset}`);
486
- }
487
-
488
- console.log(`\n${success(`Installed ${c.bold}${installed}${c.reset} skill(s) to ${c.cyan}${path.relative(process.cwd(), targetDir) || targetDir}${c.reset}`)}`);
489
- console.log(`\n${c.bold}${c.green}Done!${c.reset} Skills are ready to use.\n`);
490
- return;
491
- }
492
- } catch (err) {
493
- console.log(error(`Failed: ${err.message}`));
494
- process.exit(1);
495
- }
496
- }
497
-
498
- function printResolution(context) {
499
- const ideDefinition = getIDEDefinition(context.ide);
500
-
501
- if (context.source === 'flag') {
502
- console.log(info(`Using IDE: ${c.bold}${ideDefinition.displayName}${c.reset} (from --ide flag)`));
503
- if (context.matchedPath) {
504
- console.log(info(`Workspace: ${c.dim}${context.baseDir}${c.reset} (found ${context.matchedPath})\n`));
505
- } else {
506
- console.log(info(`Workspace: ${c.dim}${context.baseDir}${c.reset}\n`));
507
- }
508
- return;
509
- }
510
-
511
- if (context.source === 'project') {
512
- console.log(info(`Auto-detected ${c.bold}${ideDefinition.displayName}${c.reset} from ${c.dim}${context.matchedPath}${c.reset}\n`));
513
- return;
514
- }
515
-
516
- if (context.source === 'global') {
517
- console.log(info(`Auto-detected ${c.bold}${ideDefinition.displayName}${c.reset} from global config ${c.dim}${context.matchedPath}${c.reset}\n`));
518
- return;
519
- }
520
-
521
- console.log(warn('No IDE marker found. Falling back to generic SKILL.md layout.'));
522
- console.log(info(`Tip: use ${c.cyan}--ide=cursor${c.reset}, ${c.cyan}--ide=kiro${c.reset}, ${c.cyan}--ide=antigravity${c.reset}, ${c.cyan}--ide=codex${c.reset}, ${c.cyan}--ide=vscode${c.reset}, or ${c.cyan}--ide=copilot${c.reset}\n`));
523
- }
524
-
525
- function dedupeWarnings(warnings) {
526
- const seen = new Set();
527
- const uniqueWarnings = [];
528
-
529
- for (const warning of warnings || []) {
530
- const key = `${warning.code || 'warning'}:${warning.message}`;
531
- if (seen.has(key)) {
532
- continue;
533
- }
534
-
535
- seen.add(key);
536
- uniqueWarnings.push(warning);
537
- }
538
-
539
- return uniqueWarnings;
540
- }
541
-
542
- function printWarnings(warnings) {
543
- const uniqueWarnings = dedupeWarnings(warnings);
544
- if (!uniqueWarnings.length) {
545
- return;
546
- }
547
-
548
- for (const warning of uniqueWarnings) {
549
- console.log(warn(warning.message));
550
- }
551
- console.log('');
552
- }
553
-
554
- function printInstallResult(result, previousVersion, scope) {
555
- const action = previousVersion ? 'Updated' : 'Installed';
556
-
557
- console.log(success(`${action} ${c.bold}${result.skillCount}${c.reset} skills for ${c.bold}${result.displayName}${c.reset}`));
558
- if (previousVersion) {
559
- console.log(info(`Version: ${c.yellow}${previousVersion}${c.reset} → ${c.green}${CURRENT_VERSION}${c.reset}`));
560
- }
561
-
562
- console.log(`\n${c.bold}Targets:${c.reset}`);
563
- for (const target of result.targets) {
564
- const relativePath = path.relative(process.cwd(), target.targetDir) || '.';
565
- const tag = target.compatibility ? ` ${c.dim}(compat)${c.reset}` : '';
566
- console.log(` ${c.green}→${c.reset} ${relativePath}${tag}`);
567
- }
568
- console.log('');
569
- printWarnings(result.warnings);
570
-
571
- console.log(`${c.bold}${c.green}Done!${c.reset} Next steps:\n`);
572
- console.log(` 1. ${c.dim}Reopen${c.reset} ${c.bold}${result.displayName}${c.reset}`);
573
- console.log(` 2. ${c.dim}Open agent chat and type${c.reset} ${c.cyan}/${c.reset} ${c.dim}to list skills${c.reset}`);
574
- console.log(` 3. ${c.dim}Try:${c.reset} ${c.cyan}/create${c.reset}, ${c.cyan}/debug${c.reset}, ${c.cyan}/explain${c.reset}, or ${c.cyan}/plan${c.reset}`);
575
- console.log('');
576
- }
577
-
578
- function install(scope) {
579
- try {
580
- console.log(`\n${badge('AI Agent Skills')} v${CURRENT_VERSION}\n`);
581
-
582
- const ideFlag = parseIDEFlag();
583
- if (!ideFlag) {
584
- console.log(error('Non-interactive install requires --ide=<name|all>.'));
585
- console.log(info('Supported: cursor, kiro, antigravity, codex, vscode, copilot, all\n'));
586
- process.exit(1);
587
- }
588
-
589
- const ideFlagNormalized = String(ideFlag).trim().toLowerCase();
590
- const installTargets =
591
- ideFlagNormalized === 'all'
592
- ? ['cursor', 'kiro', 'antigravity', 'codex', 'vscode', 'copilot']
593
- : null;
594
-
595
- if (!installTargets && !normalizeIDEName(ideFlag)) {
596
- console.log(error(`Unknown IDE: ${ideFlag}`));
597
- console.log(info(`Supported: cursor, kiro, antigravity, codex, vscode, copilot, all\n`));
598
- process.exit(1);
599
- }
600
-
601
- if (installTargets) {
602
- console.log(info(`Using IDE target: ${c.bold}All supported IDEs${c.reset} (from --ide flag)\n`));
603
- const baseDir = process.cwd();
604
-
605
- for (const targetIDE of installTargets) {
606
- const ideDefinition = getIDEDefinition(targetIDE);
607
- const rootDir =
608
- scope === 'global'
609
- ? ideDefinition.globalRoot
610
- : path.join(baseDir, ideDefinition.projectRoot);
611
- const installedVersion = getInstalledVersion(rootDir);
612
-
613
- console.log(step(1, `${installedVersion ? 'Updating' : 'Installing'} skills for ${c.bold}${ideDefinition.displayName}${c.reset}...\n`));
614
-
615
- const result = installBundle({
616
- baseDir,
617
- ide: targetIDE,
618
- scope,
619
- version: CURRENT_VERSION,
620
- includeCompatibilityAliases: true,
621
- });
622
-
623
- printInstallResult(result, installedVersion, scope);
624
- }
625
-
626
- return;
627
- }
628
-
629
- const context = resolveInstallContext();
630
- const ideDefinition = getIDEDefinition(context.ide);
631
- const baseDir = scope === 'global' ? process.cwd() : context.baseDir;
632
- const rootDir =
633
- scope === 'global'
634
- ? ideDefinition.globalRoot
635
- : path.join(baseDir, ideDefinition.projectRoot);
636
- const installedVersion = getInstalledVersion(rootDir);
637
-
638
- printResolution(context);
639
-
640
- console.log(step(1, `${installedVersion ? 'Updating' : 'Installing'} skills for ${c.bold}${ideDefinition.displayName}${c.reset}...\n`));
641
-
642
- const result = installBundle({
643
- baseDir,
644
- ide: context.ide,
645
- scope,
646
- version: CURRENT_VERSION,
647
- includeCompatibilityAliases: true,
648
- });
649
-
650
- printInstallResult(result, installedVersion, scope);
651
- } catch (err) {
652
- console.log(error(`Install failed: ${err.message}`));
653
- process.exit(1);
654
- }
655
- }
656
-
657
- function getTargetCompatibility(skill, ide) {
658
- const ideDefinition = getIDEDefinition(ide) || getIDEDefinition('generic');
659
-
660
- return ideDefinition.targets.map((target) => ({
661
- format: target.format,
662
- status: skill.compatibility[target.format].status,
663
- warnings: skill.compatibility[target.format].warnings,
664
- }));
665
- }
666
-
667
- function formatCompatibilitySummary(skill, ide) {
668
- return getTargetCompatibility(skill, ide)
669
- .map((target) => {
670
- const status = target.status === 'generated-with-warnings' ? 'generated+warnings' : target.status;
671
- return `${target.format}: ${status}`;
672
- })
673
- .join(', ');
674
- }
675
-
676
- function listSkills() {
677
- try {
678
- const context = resolveInstallContext();
679
- const catalogResult = getSkillCatalog({ includeDiagnostics: true });
680
- const jsonMode = hasFlag('--json');
681
- const renderedCatalog = catalogResult.skills.map((skill) => ({
682
- slug: skill.slug,
683
- name: skill.name,
684
- description: skill.description,
685
- sourceRoot: skill.sourceRoot,
686
- hasScripts: skill.hasScripts,
687
- hasReferences: skill.hasReferences,
688
- hasAssets: skill.hasAssets,
689
- compatibility: getTargetCompatibility(skill, context.ide),
690
- }));
691
-
692
- if (jsonMode) {
693
- console.log(
694
- JSON.stringify(
695
- {
696
- package: PACKAGE_NAME,
697
- version: CURRENT_VERSION,
698
- ide: context.ide,
699
- skills: renderedCatalog,
700
- diagnostics: dedupeWarnings(catalogResult.diagnostics),
701
- },
702
- null,
703
- 2
704
- )
705
- );
706
- return;
707
- }
708
-
709
- const ideDefinition = getIDEDefinition(context.ide);
710
- console.log(`\n${badge('AI Agent Skills')} ${c.dim}— Skill Catalog${c.reset}\n`);
711
- console.log(info(`IDE: ${c.bold}${ideDefinition.displayName}${c.reset}`));
712
- if (context.matchedPath) {
713
- console.log(info(`Detected from: ${c.dim}${context.matchedPath}${c.reset}`));
714
- }
715
- console.log('');
716
-
717
- console.log(`${c.bold}Available skills (${catalogResult.skills.length}):${c.reset}\n`);
718
- for (const skill of catalogResult.skills) {
719
- const compat = formatCompatibilitySummary(skill, context.ide);
720
- console.log(` ${c.cyan}/${skill.slug}${c.reset} ${c.dim}—${c.reset} ${skill.description}`);
721
- console.log(` ${c.dim}source: ${skill.sourceRoot} | ${compat}${c.reset}`);
722
- }
723
- console.log('');
724
- printWarnings(catalogResult.diagnostics);
725
- } catch (err) {
726
- console.log(error(`List failed: ${err.message}`));
727
- process.exit(1);
728
- }
729
- }
730
-
731
- function statusReport() {
732
- try {
733
- const context = resolveInstallContext();
734
- const ideDefinition = getIDEDefinition(context.ide);
735
- const rootDir = path.join(context.baseDir, ideDefinition.projectRoot);
736
- const installedVersion = getInstalledVersion(rootDir);
737
- const catalogResult = getSkillCatalog({ includeDiagnostics: true });
738
-
739
- console.log(`\n${badge('AI Agent Skills')} ${c.dim}— Status${c.reset}\n`);
740
-
741
- const rows = [
742
- ['Package', PACKAGE_NAME],
743
- ['Bundle version', CURRENT_VERSION],
744
- ['Installed version', installedVersion || `${c.yellow}not installed${c.reset}`],
745
- ['IDE', ideDefinition.displayName],
746
- ['Workspace', context.baseDir],
747
- ['Skills', `${catalogResult.skills.length}`],
748
- ];
749
-
750
- const agentSkill = catalogResult.skills.find((s) => s.slug === 'agents');
751
- if (agentSkill && agentSkill.agents) {
752
- rows.push(['Agent routing', `${c.green}enabled${c.reset}`]);
753
- }
754
-
755
- const needsUpdate = installedVersion && installedVersion !== CURRENT_VERSION;
756
- rows.push(['Update needed', needsUpdate ? `${c.yellow}yes${c.reset}` : `${c.green}no${c.reset}`]);
757
-
758
- for (const [label, value] of rows) {
759
- console.log(` ${c.dim}${label}:${c.reset} ${value}`);
760
- }
761
-
762
- if (catalogResult.diagnostics.length) {
763
- console.log('');
764
- printWarnings(catalogResult.diagnostics);
765
- }
766
-
767
- console.log('');
768
- } catch (err) {
769
- console.log(error(`Status failed: ${err.message}`));
770
- process.exit(1);
771
- }
772
- }
773
-
774
- function showHelp() {
775
- const skillNames = listSkillNames().map((name) => `${c.cyan}/${name}${c.reset}`).join(', ');
776
-
777
- console.log(`
778
- ${badge('AI Agent Skills')} v${CURRENT_VERSION}
779
-
780
- ${c.bold}Usage:${c.reset}
781
-
782
- ${c.green}npx ${PACKAGE_NAME} init${c.reset} ${c.dim}Interactive setup (choose IDE + scope)${c.reset}
783
- ${c.green}npx ${PACKAGE_NAME} init --ide=cursor${c.reset} ${c.dim}Non-interactive install for specific IDE${c.reset}
784
- ${c.green}npx ${PACKAGE_NAME} init --ide=all${c.reset} ${c.dim}Non-interactive install for all supported IDEs${c.reset}
785
- ${c.green}npx ${PACKAGE_NAME} init --ide=codex${c.reset} ${c.dim}Install skills for Codex-style workspace${c.reset}
786
- ${c.green}npx ${PACKAGE_NAME} init --ide=vscode${c.reset} ${c.dim}Install skills to VS Code/Copilot layout${c.reset}
787
- ${c.green}npx ${PACKAGE_NAME} global${c.reset} ${c.dim}Install globally for all projects${c.reset}
788
- ${c.green}npx ${PACKAGE_NAME} add owner/repo${c.reset} ${c.dim}Add skills from a GitHub repository${c.reset}
789
- ${c.green}npx ${PACKAGE_NAME} list${c.reset} ${c.dim}List all bundled skills${c.reset}
790
- ${c.green}npx ${PACKAGE_NAME} list --json${c.reset} ${c.dim}JSON output for scripts${c.reset}
791
- ${c.green}npx ${PACKAGE_NAME} status${c.reset} ${c.dim}Show install status & versions${c.reset}
792
- ${c.green}npx ${PACKAGE_NAME} help${c.reset} ${c.dim}Show this help${c.reset}
793
-
794
- ${c.bold}GitHub Add Examples:${c.reset}
795
-
796
- ${c.green}npx ${PACKAGE_NAME} add anthropics/skills${c.reset}
797
- ${c.green}npx ${PACKAGE_NAME} add anthropics/skills --skill=pdf-processing${c.reset}
798
- ${c.green}npx ${PACKAGE_NAME} add anthropics/skills --ide=cursor --branch=main${c.reset}
799
-
800
- ${c.bold}Supported IDEs:${c.reset}
801
-
802
- ${c.cyan}cursor${c.reset} ${c.dim}→ .cursor/skills + .cursor/rules${c.reset}
803
- ${c.cyan}kiro${c.reset} ${c.dim}→ .kiro/skills${c.reset}
804
- ${c.cyan}antigravity${c.reset} ${c.dim}→ .agent/workflows${c.reset}
805
- ${c.cyan}codex${c.reset} ${c.dim}→ .agents/skills (detected via AGENTS.md / memories)${c.reset}
806
- ${c.cyan}vscode${c.reset} ${c.dim}→ .github/skills${c.reset}
807
- ${c.cyan}copilot${c.reset} ${c.dim}→ .github/skills${c.reset}
808
- ${c.cyan}all${c.reset} ${c.dim}→ install to all IDE targets above${c.reset}
809
-
810
- ${c.bold}Available skills:${c.reset}
811
- ${skillNames}
812
-
813
- ${c.dim}Documentation: https://github.com/votruongdanh/ai-agent-skills${c.reset}
814
- `);
815
- }
816
-
817
- // ─── Main ───────────────────────────────────────────────────────────────────
818
- const command = process.argv[2];
819
-
820
- switch (command) {
821
- case 'init':
822
- if (hasFlag('--no-interactive')) {
823
- // Non-interactive: --ide flag or explicit opt-out
824
- checkForUpdates(() => install('project'));
825
- } else {
826
- // Interactive mode by default
827
- checkForUpdates(() => interactiveInit().catch((err) => {
828
- console.log(error(err.message));
829
- process.exit(1);
830
- }));
831
- }
832
- break;
833
- case 'global':
834
- checkForUpdates(() => install('global'));
835
- break;
836
- case 'add':
837
- checkForUpdates(() => addSkillFromGitHub().catch((err) => {
838
- console.log(error(err.message));
839
- process.exit(1);
840
- }));
841
- break;
842
- case 'list':
843
- listSkills();
844
- break;
845
- case 'status':
846
- statusReport();
847
- break;
848
- case 'update':
849
- if (hasFlag('--no-interactive')) {
850
- checkForUpdates(() => install('project'));
851
- } else {
852
- checkForUpdates(() => interactiveInit().catch((err) => {
853
- console.log(error(err.message));
854
- process.exit(1);
855
- }));
856
- }
857
- break;
858
- case 'help':
859
- case '--help':
860
- case '-h':
861
- showHelp();
862
- break;
863
- default:
864
- if (!command) {
865
- // No command = show interactive init
866
- checkForUpdates(() => interactiveInit().catch((err) => {
867
- console.log(error(err.message));
868
- process.exit(1);
869
- }));
870
- } else {
871
- console.log(error(`Unknown command: ${command}`));
872
- console.log(info(`Run ${c.cyan}npx ${PACKAGE_NAME} help${c.reset} for usage.\n`));
873
- showHelp();
874
- process.exit(1);
875
- }
876
- }
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const https = require('https');
7
+ const { execSync } = require('child_process');
8
+ const readline = require('readline');
9
+
10
+ const {
11
+ findIDEContext,
12
+ findProjectRootForIDE,
13
+ getAvailableIDENames,
14
+ getIDEDefinition,
15
+ getInstalledVersion,
16
+ getSkillCatalog,
17
+ installBundle,
18
+ listSkillNames,
19
+ normalizeIDEName,
20
+ parseFrontmatter,
21
+ } = require('../lib/skill-bundle');
22
+
23
+ const PACKAGE_NAME = '@votruongdanh/ai-agent-skills';
24
+ const CURRENT_VERSION = require('../package.json').version;
25
+
26
+ // ─── ANSI Colors ────────────────────────────────────────────────────────────
27
+ const c = {
28
+ reset: '\x1b[0m',
29
+ bold: '\x1b[1m',
30
+ dim: '\x1b[2m',
31
+ green: '\x1b[32m',
32
+ yellow: '\x1b[33m',
33
+ blue: '\x1b[34m',
34
+ magenta: '\x1b[35m',
35
+ cyan: '\x1b[36m',
36
+ red: '\x1b[31m',
37
+ bgBlue: '\x1b[44m',
38
+ bgGreen: '\x1b[42m',
39
+ white: '\x1b[37m',
40
+ };
41
+
42
+ function badge(text) {
43
+ return `${c.bgBlue}${c.white}${c.bold} ${text} ${c.reset}`;
44
+ }
45
+
46
+ function success(text) {
47
+ return `${c.green}✔${c.reset} ${text}`;
48
+ }
49
+
50
+ function warn(text) {
51
+ return `${c.yellow}⚠${c.reset} ${text}`;
52
+ }
53
+
54
+ function info(text) {
55
+ return `${c.cyan}ℹ${c.reset} ${text}`;
56
+ }
57
+
58
+ function error(text) {
59
+ return `${c.red}✖${c.reset} ${text}`;
60
+ }
61
+
62
+ function step(num, text) {
63
+ return `${c.dim}[${num}]${c.reset} ${text}`;
64
+ }
65
+
66
+ // ─── Interactive Prompt Helpers ─────────────────────────────────────────────
67
+ function createRL() {
68
+ return readline.createInterface({ input: process.stdin, output: process.stdout });
69
+ }
70
+
71
+ function ask(rl, question) {
72
+ return new Promise((resolve) => rl.question(question, resolve));
73
+ }
74
+
75
+ async function selectMenu(rl, title, options) {
76
+ console.log(`\n${c.bold}${title}${c.reset}\n`);
77
+ options.forEach((opt, i) => {
78
+ const marker = opt.recommended ? ` ${c.green}← recommended${c.reset}` : '';
79
+ const desc = opt.description ? ` ${c.dim}(${opt.description})${c.reset}` : '';
80
+ console.log(` ${c.cyan}${i + 1}${c.reset}) ${c.bold}${opt.label}${c.reset}${desc}${marker}`);
81
+ });
82
+ console.log('');
83
+
84
+ while (true) {
85
+ const answer = await ask(rl, ` ${c.dim}Your choice (1-${options.length}):${c.reset} `);
86
+ const num = parseInt(answer.trim(), 10);
87
+ if (num >= 1 && num <= options.length) {
88
+ return options[num - 1];
89
+ }
90
+ console.log(` ${c.red}Please enter a number between 1 and ${options.length}${c.reset}`);
91
+ }
92
+ }
93
+
94
+ async function confirm(rl, question) {
95
+ const answer = await ask(rl, ` ${question} ${c.dim}(y/n):${c.reset} `);
96
+ return answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes';
97
+ }
98
+
99
+ function parseIDEFlag() {
100
+ for (const arg of process.argv.slice(2)) {
101
+ if (arg.startsWith('--ide=')) {
102
+ return arg.split('=')[1];
103
+ }
104
+ }
105
+ return null;
106
+ }
107
+
108
+ function parseArg(prefix) {
109
+ for (const arg of process.argv.slice(2)) {
110
+ if (arg.startsWith(`--${prefix}=`)) {
111
+ return arg.split('=').slice(1).join('=');
112
+ }
113
+ }
114
+ return null;
115
+ }
116
+
117
+ function hasFlag(flag) {
118
+ return process.argv.slice(2).includes(flag);
119
+ }
120
+
121
+ function positionalArg(index) {
122
+ const args = process.argv.slice(2).filter((a) => !a.startsWith('-'));
123
+ return args[index] || null;
124
+ }
125
+
126
+ // ─── Update Check ───────────────────────────────────────────────────────────
127
+ function checkForUpdates(callback) {
128
+ https
129
+ .get(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, (res) => {
130
+ let data = '';
131
+ res.on('data', (chunk) => { data += chunk; });
132
+ res.on('end', () => {
133
+ try {
134
+ const latest = JSON.parse(data).version;
135
+ if (latest !== CURRENT_VERSION) {
136
+ console.log(warn(`Update available: ${c.yellow}${CURRENT_VERSION}${c.reset} → ${c.green}${latest}${c.reset}`));
137
+ console.log(info(`Run: ${c.cyan}npx ${PACKAGE_NAME}@latest init${c.reset}\n`));
138
+ }
139
+ } catch (_) {}
140
+ callback();
141
+ });
142
+ })
143
+ .on('error', () => callback());
144
+ }
145
+
146
+ // ─── IDE Resolution ─────────────────────────────────────────────────────────
147
+ function resolveInstallContext() {
148
+ const cwd = process.cwd();
149
+ const manualIDE = parseIDEFlag();
150
+
151
+ if (manualIDE) {
152
+ const normalized = normalizeIDEName(manualIDE);
153
+ if (normalized) {
154
+ const projectMatch = findProjectRootForIDE(cwd, normalized);
155
+ const useDetectedProjectRoot = projectMatch && projectMatch.projectDir === cwd;
156
+ return {
157
+ ide: normalized,
158
+ source: 'flag',
159
+ baseDir: useDetectedProjectRoot ? projectMatch.projectDir : cwd,
160
+ matchedPath: useDetectedProjectRoot ? projectMatch.matchedPath : null,
161
+ };
162
+ }
163
+ console.log(error(`Unknown IDE: ${manualIDE}`));
164
+ console.log(info(`Supported: ${getAvailableIDENames().join(', ')}\n`));
165
+ }
166
+
167
+ const detected = findIDEContext(cwd);
168
+ return {
169
+ ide: detected.ide,
170
+ source: detected.source,
171
+ baseDir: detected.projectDir || cwd,
172
+ matchedPath: detected.matchedPath,
173
+ };
174
+ }
175
+
176
+ // ─── Interactive Init ───────────────────────────────────────────────────────
177
+ async function interactiveInit() {
178
+ const rl = createRL();
179
+
180
+ console.log(`\n${badge('AI Agent Skills')} v${CURRENT_VERSION}\n`);
181
+
182
+ // Step 1: Choose IDE
183
+ const ideFlag = parseIDEFlag();
184
+ let selectedIDE;
185
+ const supportedIDEOptions = [
186
+ { label: 'Cursor', value: 'cursor', description: '.cursor/skills + .cursor/rules', recommended: true },
187
+ { label: 'Kiro', value: 'kiro', description: '.kiro/skills' },
188
+ { label: 'Antigravity', value: 'antigravity', description: '.agent/workflows' },
189
+ { label: 'Codex', value: 'codex', description: '.agents/skills (+ AGENTS.md / memories)' },
190
+ { label: 'VS Code', value: 'vscode', description: '.github/skills' },
191
+ { label: 'GitHub Copilot', value: 'copilot', description: '.github/skills' },
192
+ { label: 'All supported IDEs', value: 'all', description: 'Install for every supported IDE' },
193
+ ];
194
+
195
+ if (ideFlag) {
196
+ if (String(ideFlag).trim().toLowerCase() === 'all') {
197
+ selectedIDE = 'all';
198
+ console.log(info(`IDE target: ${c.bold}All supported IDEs${c.reset} (from --ide flag)\n`));
199
+ } else {
200
+ selectedIDE = normalizeIDEName(ideFlag);
201
+ }
202
+
203
+ if (!selectedIDE || selectedIDE === 'generic') {
204
+ console.log(error(`Unknown IDE: ${ideFlag}`));
205
+ console.log(info(`Supported: cursor, kiro, antigravity, codex, vscode, copilot, all\n`));
206
+ rl.close();
207
+ process.exit(1);
208
+ }
209
+
210
+ if (selectedIDE !== 'all') {
211
+ const def = getIDEDefinition(selectedIDE);
212
+ console.log(info(`IDE: ${c.bold}${def.displayName}${c.reset} (from --ide flag)\n`));
213
+ }
214
+ } else {
215
+ const choice = await selectMenu(rl, '🔧 Choose your IDE:', supportedIDEOptions);
216
+ selectedIDE = choice.value;
217
+ }
218
+
219
+ // Step 2: Choose scope (project vs global)
220
+ let scope = hasFlag('--global') ? 'global' : null;
221
+
222
+ if (!scope) {
223
+ const globalDescription =
224
+ selectedIDE === 'all'
225
+ ? 'Install to each IDE global directory'
226
+ : `~/${getIDEDefinition(selectedIDE).projectRoot}`;
227
+ const scopeOptions = [
228
+ { label: 'This project only', value: 'project', description: process.cwd(), recommended: true },
229
+ { label: 'Global (all projects)', value: 'global', description: globalDescription },
230
+ ];
231
+ const scopeChoice = await selectMenu(rl, '📂 Install scope:', scopeOptions);
232
+ scope = scopeChoice.value;
233
+ }
234
+
235
+ rl.close();
236
+
237
+ // Step 3: Install
238
+ const cwd = process.cwd();
239
+ const installTargets =
240
+ selectedIDE === 'all'
241
+ ? ['cursor', 'kiro', 'antigravity', 'codex', 'vscode', 'copilot']
242
+ : [selectedIDE];
243
+
244
+ console.log(`\n${step(1, 'Installing skills...')}`);
245
+
246
+ const installResults = [];
247
+ for (const ide of installTargets) {
248
+ const ideDefinition = getIDEDefinition(ide);
249
+ const baseDir = cwd;
250
+ const rootDir =
251
+ scope === 'global'
252
+ ? ideDefinition.globalRoot
253
+ : path.join(baseDir, ideDefinition.projectRoot);
254
+ const installedVersion = getInstalledVersion(rootDir);
255
+
256
+ const result = installBundle({
257
+ baseDir,
258
+ ide,
259
+ scope,
260
+ version: CURRENT_VERSION,
261
+ includeCompatibilityAliases: true,
262
+ });
263
+
264
+ installResults.push({ result, installedVersion });
265
+ }
266
+
267
+ for (const { result, installedVersion } of installResults) {
268
+ console.log(success(`${result.skillCount} skills installed for ${c.bold}${result.displayName}${c.reset}`));
269
+ if (installedVersion) {
270
+ console.log(info(`Updated: ${c.yellow}${installedVersion}${c.reset} → ${c.green}${CURRENT_VERSION}${c.reset}`));
271
+ }
272
+ }
273
+
274
+ console.log(`\n${step(2, 'Targets created:')}`);
275
+ for (const { result } of installResults) {
276
+ for (const target of result.targets) {
277
+ const rel = path.relative(cwd, target.targetDir) || '.';
278
+ const tag = target.compatibility ? ` ${c.dim}(compat)${c.reset}` : '';
279
+ console.log(` ${c.green}→${c.reset} ${rel}${tag}`);
280
+ }
281
+ }
282
+
283
+ // Warnings
284
+ const uniqueWarnings = dedupeWarnings(installResults.flatMap(({ result }) => result.warnings));
285
+ if (uniqueWarnings.length) {
286
+ console.log('');
287
+ for (const w of uniqueWarnings) {
288
+ console.log(warn(w.message));
289
+ }
290
+ }
291
+
292
+ // Next steps
293
+ const nextStepIDEName =
294
+ selectedIDE === 'all'
295
+ ? 'your IDE'
296
+ : installResults[0].result.displayName;
297
+ console.log(`\n${c.bold}${c.green}Done!${c.reset} Next steps:\n`);
298
+ console.log(` 1. ${c.dim}Reopen${c.reset} ${c.bold}${nextStepIDEName}${c.reset}`);
299
+ console.log(` 2. ${c.dim}Open agent chat and type${c.reset} ${c.cyan}/${c.reset} ${c.dim}to list skills${c.reset}`);
300
+ console.log(` 3. ${c.dim}Try:${c.reset} ${c.cyan}/create${c.reset}, ${c.cyan}/debug${c.reset}, ${c.cyan}/explain${c.reset}, or ${c.cyan}/plan${c.reset}`);
301
+ console.log('');
302
+ }
303
+
304
+ // ─── GitHub Skill Fetcher ───────────────────────────────────────────────────
305
+ function httpsGet(url) {
306
+ return new Promise((resolve, reject) => {
307
+ const options = { headers: { 'User-Agent': 'ai-agent-skills-cli' } };
308
+ https.get(url, options, (res) => {
309
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
310
+ return httpsGet(res.headers.location).then(resolve, reject);
311
+ }
312
+ if (res.statusCode !== 200) {
313
+ return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
314
+ }
315
+ let data = '';
316
+ res.on('data', (chunk) => { data += chunk; });
317
+ res.on('end', () => resolve(data));
318
+ }).on('error', reject);
319
+ });
320
+ }
321
+
322
+ async function fetchGitHubTree(owner, repo, branch) {
323
+ const url = `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`;
324
+ const data = JSON.parse(await httpsGet(url));
325
+ return data.tree || [];
326
+ }
327
+
328
+ async function fetchGitHubFile(owner, repo, branch, filePath) {
329
+ const url = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filePath}`;
330
+ return httpsGet(url);
331
+ }
332
+
333
+ async function addSkillFromGitHub() {
334
+ const source = positionalArg(1); // e.g. "owner/repo" or "owner/repo/skills/my-skill"
335
+
336
+ if (!source) {
337
+ console.log(error('Usage: ai-skills add <owner/repo> [--skill=name] [--ide=cursor]'));
338
+ console.log(info('Example: ai-skills add anthropics/skills --skill=pdf-processing'));
339
+ console.log(info('Example: ai-skills add anthropics/skills (lists all skills)'));
340
+ process.exit(1);
341
+ }
342
+
343
+ const parts = source.split('/');
344
+ if (parts.length < 2) {
345
+ console.log(error('Format: owner/repo (e.g. anthropics/skills)'));
346
+ process.exit(1);
347
+ }
348
+
349
+ const owner = parts[0];
350
+ const repo = parts[1];
351
+ const branch = parseArg('branch') || 'main';
352
+ const skillFilter = parseArg('skill');
353
+
354
+ console.log(`\n${badge('AI Agent Skills')} ${c.dim}— Add from GitHub${c.reset}\n`);
355
+ console.log(info(`Fetching from ${c.cyan}${owner}/${repo}${c.reset} (${branch})...`));
356
+
357
+ try {
358
+ const tree = await fetchGitHubTree(owner, repo, branch);
359
+
360
+ // Find all SKILL.md files
361
+ const skillFiles = tree
362
+ .filter((item) => item.path.endsWith('/SKILL.md') && item.type === 'blob')
363
+ .map((item) => {
364
+ const dir = path.dirname(item.path);
365
+ const name = path.basename(dir);
366
+ return { name, dir, skillMdPath: item.path };
367
+ });
368
+
369
+ if (!skillFiles.length) {
370
+ console.log(error('No SKILL.md files found in this repository.'));
371
+ process.exit(1);
372
+ }
373
+
374
+ console.log(success(`Found ${c.bold}${skillFiles.length}${c.reset} skills in repo\n`));
375
+
376
+ // If --skill specified, filter
377
+ let selectedSkills = skillFiles;
378
+ if (skillFilter) {
379
+ selectedSkills = skillFiles.filter((s) => s.name === skillFilter);
380
+ if (!selectedSkills.length) {
381
+ console.log(error(`Skill "${skillFilter}" not found. Available:`));
382
+ skillFiles.forEach((s) => console.log(` ${c.cyan}→${c.reset} ${s.name} ${c.dim}(${s.dir})${c.reset}`));
383
+ process.exit(1);
384
+ }
385
+ } else {
386
+ // Interactive selection
387
+ const rl = createRL();
388
+
389
+ console.log(`${c.bold}Available skills:${c.reset}\n`);
390
+ skillFiles.forEach((s, i) => {
391
+ console.log(` ${c.cyan}${i + 1}${c.reset}) ${c.bold}${s.name}${c.reset} ${c.dim}(${s.dir})${c.reset}`);
392
+ });
393
+ console.log(` ${c.cyan}${skillFiles.length + 1}${c.reset}) ${c.bold}All skills${c.reset} ${c.dim}(install everything)${c.reset}`);
394
+ console.log('');
395
+
396
+ const answer = await ask(rl, ` ${c.dim}Choose (1-${skillFiles.length + 1}):${c.reset} `);
397
+ const num = parseInt(answer.trim(), 10);
398
+
399
+ if (num === skillFiles.length + 1) {
400
+ // Install all
401
+ } else if (num >= 1 && num <= skillFiles.length) {
402
+ selectedSkills = [skillFiles[num - 1]];
403
+ } else {
404
+ console.log(error('Invalid choice'));
405
+ rl.close();
406
+ process.exit(1);
407
+ }
408
+
409
+ // Choose target IDE
410
+ const ideFlag = parseIDEFlag();
411
+ let targetIDE;
412
+
413
+ if (ideFlag) {
414
+ targetIDE = normalizeIDEName(ideFlag);
415
+ } else {
416
+ const ideOptions = [
417
+ { label: 'Cursor', value: 'cursor', description: '.cursor/skills', recommended: true },
418
+ { label: 'Kiro', value: 'kiro', description: '.kiro/skills' },
419
+ { label: 'Antigravity', value: 'antigravity', description: '.agent/workflows' },
420
+ { label: 'Codex', value: 'codex', description: '.agents/skills (+ AGENTS.md / memories)' },
421
+ { label: 'VS Code', value: 'vscode', description: '.github/skills' },
422
+ { label: 'GitHub Copilot', value: 'copilot', description: '.github/skills' },
423
+ ];
424
+ const ideChoice = await selectMenu(rl, '🔧 Install to:', ideOptions);
425
+ targetIDE = ideChoice.value;
426
+ }
427
+
428
+ // Choose scope
429
+ let scope = 'project';
430
+ if (!hasFlag('--global')) {
431
+ const scopeOptions = [
432
+ { label: 'This project', value: 'project', recommended: true },
433
+ { label: 'Global (~/.agents/skills)', value: 'global' },
434
+ ];
435
+ const scopeChoice = await selectMenu(rl, '📂 Scope:', scopeOptions);
436
+ scope = scopeChoice.value;
437
+ }
438
+
439
+ rl.close();
440
+
441
+ // Download and install
442
+ console.log(`\n${step(1, 'Downloading skills...')}`);
443
+
444
+ const targetBase = scope === 'global' ? os.homedir() : process.cwd();
445
+ let targetDir;
446
+ if (targetIDE === 'agents') {
447
+ targetDir = path.join(targetBase, '.agents', 'skills');
448
+ } else {
449
+ const ideDef = getIDEDefinition(targetIDE);
450
+ if (ideDef) {
451
+ const target = ideDef.targets.find((t) => t.format === 'skill') || ideDef.targets[0];
452
+ targetDir = scope === 'global'
453
+ ? path.join(ideDef.globalRoot, target.relativeDir)
454
+ : path.join(targetBase, ideDef.projectRoot, target.relativeDir);
455
+ } else {
456
+ targetDir = path.join(targetBase, '.agents', 'skills');
457
+ }
458
+ }
459
+
460
+ let installed = 0;
461
+ for (const skill of selectedSkills) {
462
+ process.stdout.write(` ${c.cyan}↓${c.reset} ${skill.name}...`);
463
+
464
+ // Fetch SKILL.md
465
+ const content = await fetchGitHubFile(owner, repo, branch, skill.skillMdPath);
466
+ const skillDir = path.join(targetDir, skill.name);
467
+ fs.mkdirSync(skillDir, { recursive: true });
468
+ fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content, 'utf8');
469
+
470
+ // Fetch companion dirs (scripts/, references/, assets/)
471
+ const companionDirs = ['scripts', 'references', 'assets'];
472
+ for (const companion of companionDirs) {
473
+ const prefix = `${skill.dir}/${companion}/`;
474
+ const companionFiles = tree.filter((item) => item.path.startsWith(prefix) && item.type === 'blob');
475
+ for (const file of companionFiles) {
476
+ const relativePath = file.path.slice(skill.dir.length + 1);
477
+ const destPath = path.join(skillDir, relativePath);
478
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
479
+ const fileContent = await fetchGitHubFile(owner, repo, branch, file.path);
480
+ fs.writeFileSync(destPath, fileContent, 'utf8');
481
+ }
482
+ }
483
+
484
+ installed++;
485
+ console.log(` ${c.green}✔${c.reset}`);
486
+ }
487
+
488
+ console.log(`\n${success(`Installed ${c.bold}${installed}${c.reset} skill(s) to ${c.cyan}${path.relative(process.cwd(), targetDir) || targetDir}${c.reset}`)}`);
489
+ console.log(`\n${c.bold}${c.green}Done!${c.reset} Skills are ready to use.\n`);
490
+ return;
491
+ }
492
+ } catch (err) {
493
+ console.log(error(`Failed: ${err.message}`));
494
+ process.exit(1);
495
+ }
496
+ }
497
+
498
+ function printResolution(context) {
499
+ const ideDefinition = getIDEDefinition(context.ide);
500
+
501
+ if (context.source === 'flag') {
502
+ console.log(info(`Using IDE: ${c.bold}${ideDefinition.displayName}${c.reset} (from --ide flag)`));
503
+ if (context.matchedPath) {
504
+ console.log(info(`Workspace: ${c.dim}${context.baseDir}${c.reset} (found ${context.matchedPath})\n`));
505
+ } else {
506
+ console.log(info(`Workspace: ${c.dim}${context.baseDir}${c.reset}\n`));
507
+ }
508
+ return;
509
+ }
510
+
511
+ if (context.source === 'project') {
512
+ console.log(info(`Auto-detected ${c.bold}${ideDefinition.displayName}${c.reset} from ${c.dim}${context.matchedPath}${c.reset}\n`));
513
+ return;
514
+ }
515
+
516
+ if (context.source === 'global') {
517
+ console.log(info(`Auto-detected ${c.bold}${ideDefinition.displayName}${c.reset} from global config ${c.dim}${context.matchedPath}${c.reset}\n`));
518
+ return;
519
+ }
520
+
521
+ console.log(warn('No IDE marker found. Falling back to generic SKILL.md layout.'));
522
+ console.log(info(`Tip: use ${c.cyan}--ide=cursor${c.reset}, ${c.cyan}--ide=kiro${c.reset}, ${c.cyan}--ide=antigravity${c.reset}, ${c.cyan}--ide=codex${c.reset}, ${c.cyan}--ide=vscode${c.reset}, or ${c.cyan}--ide=copilot${c.reset}\n`));
523
+ }
524
+
525
+ function dedupeWarnings(warnings) {
526
+ const seen = new Set();
527
+ const uniqueWarnings = [];
528
+
529
+ for (const warning of warnings || []) {
530
+ const key = `${warning.code || 'warning'}:${warning.message}`;
531
+ if (seen.has(key)) {
532
+ continue;
533
+ }
534
+
535
+ seen.add(key);
536
+ uniqueWarnings.push(warning);
537
+ }
538
+
539
+ return uniqueWarnings;
540
+ }
541
+
542
+ function printWarnings(warnings) {
543
+ const uniqueWarnings = dedupeWarnings(warnings);
544
+ if (!uniqueWarnings.length) {
545
+ return;
546
+ }
547
+
548
+ for (const warning of uniqueWarnings) {
549
+ console.log(warn(warning.message));
550
+ }
551
+ console.log('');
552
+ }
553
+
554
+ function printInstallResult(result, previousVersion, scope) {
555
+ const action = previousVersion ? 'Updated' : 'Installed';
556
+
557
+ console.log(success(`${action} ${c.bold}${result.skillCount}${c.reset} skills for ${c.bold}${result.displayName}${c.reset}`));
558
+ if (previousVersion) {
559
+ console.log(info(`Version: ${c.yellow}${previousVersion}${c.reset} → ${c.green}${CURRENT_VERSION}${c.reset}`));
560
+ }
561
+
562
+ console.log(`\n${c.bold}Targets:${c.reset}`);
563
+ for (const target of result.targets) {
564
+ const relativePath = path.relative(process.cwd(), target.targetDir) || '.';
565
+ const tag = target.compatibility ? ` ${c.dim}(compat)${c.reset}` : '';
566
+ console.log(` ${c.green}→${c.reset} ${relativePath}${tag}`);
567
+ }
568
+ console.log('');
569
+ printWarnings(result.warnings);
570
+
571
+ console.log(`${c.bold}${c.green}Done!${c.reset} Next steps:\n`);
572
+ console.log(` 1. ${c.dim}Reopen${c.reset} ${c.bold}${result.displayName}${c.reset}`);
573
+ console.log(` 2. ${c.dim}Open agent chat and type${c.reset} ${c.cyan}/${c.reset} ${c.dim}to list skills${c.reset}`);
574
+ console.log(` 3. ${c.dim}Try:${c.reset} ${c.cyan}/create${c.reset}, ${c.cyan}/debug${c.reset}, ${c.cyan}/explain${c.reset}, or ${c.cyan}/plan${c.reset}`);
575
+ console.log('');
576
+ }
577
+
578
+ function install(scope) {
579
+ try {
580
+ console.log(`\n${badge('AI Agent Skills')} v${CURRENT_VERSION}\n`);
581
+
582
+ const ideFlag = parseIDEFlag();
583
+ if (!ideFlag) {
584
+ console.log(error('Non-interactive install requires --ide=<name|all>.'));
585
+ console.log(info('Supported: cursor, kiro, antigravity, codex, vscode, copilot, all\n'));
586
+ process.exit(1);
587
+ }
588
+
589
+ const ideFlagNormalized = String(ideFlag).trim().toLowerCase();
590
+ const installTargets =
591
+ ideFlagNormalized === 'all'
592
+ ? ['cursor', 'kiro', 'antigravity', 'codex', 'vscode', 'copilot']
593
+ : null;
594
+
595
+ if (!installTargets && !normalizeIDEName(ideFlag)) {
596
+ console.log(error(`Unknown IDE: ${ideFlag}`));
597
+ console.log(info(`Supported: cursor, kiro, antigravity, codex, vscode, copilot, all\n`));
598
+ process.exit(1);
599
+ }
600
+
601
+ if (installTargets) {
602
+ console.log(info(`Using IDE target: ${c.bold}All supported IDEs${c.reset} (from --ide flag)\n`));
603
+ const baseDir = process.cwd();
604
+
605
+ for (const targetIDE of installTargets) {
606
+ const ideDefinition = getIDEDefinition(targetIDE);
607
+ const rootDir =
608
+ scope === 'global'
609
+ ? ideDefinition.globalRoot
610
+ : path.join(baseDir, ideDefinition.projectRoot);
611
+ const installedVersion = getInstalledVersion(rootDir);
612
+
613
+ console.log(step(1, `${installedVersion ? 'Updating' : 'Installing'} skills for ${c.bold}${ideDefinition.displayName}${c.reset}...\n`));
614
+
615
+ const result = installBundle({
616
+ baseDir,
617
+ ide: targetIDE,
618
+ scope,
619
+ version: CURRENT_VERSION,
620
+ includeCompatibilityAliases: true,
621
+ });
622
+
623
+ printInstallResult(result, installedVersion, scope);
624
+ }
625
+
626
+ return;
627
+ }
628
+
629
+ const context = resolveInstallContext();
630
+ const ideDefinition = getIDEDefinition(context.ide);
631
+ const baseDir = scope === 'global' ? process.cwd() : context.baseDir;
632
+ const rootDir =
633
+ scope === 'global'
634
+ ? ideDefinition.globalRoot
635
+ : path.join(baseDir, ideDefinition.projectRoot);
636
+ const installedVersion = getInstalledVersion(rootDir);
637
+
638
+ printResolution(context);
639
+
640
+ console.log(step(1, `${installedVersion ? 'Updating' : 'Installing'} skills for ${c.bold}${ideDefinition.displayName}${c.reset}...\n`));
641
+
642
+ const result = installBundle({
643
+ baseDir,
644
+ ide: context.ide,
645
+ scope,
646
+ version: CURRENT_VERSION,
647
+ includeCompatibilityAliases: true,
648
+ });
649
+
650
+ printInstallResult(result, installedVersion, scope);
651
+ } catch (err) {
652
+ console.log(error(`Install failed: ${err.message}`));
653
+ process.exit(1);
654
+ }
655
+ }
656
+
657
+ function getTargetCompatibility(skill, ide) {
658
+ const ideDefinition = getIDEDefinition(ide) || getIDEDefinition('generic');
659
+
660
+ return ideDefinition.targets.map((target) => ({
661
+ format: target.format,
662
+ status: skill.compatibility[target.format].status,
663
+ warnings: skill.compatibility[target.format].warnings,
664
+ }));
665
+ }
666
+
667
+ function formatCompatibilitySummary(skill, ide) {
668
+ return getTargetCompatibility(skill, ide)
669
+ .map((target) => {
670
+ const status = target.status === 'generated-with-warnings' ? 'generated+warnings' : target.status;
671
+ return `${target.format}: ${status}`;
672
+ })
673
+ .join(', ');
674
+ }
675
+
676
+ function listSkills() {
677
+ try {
678
+ const context = resolveInstallContext();
679
+ const catalogResult = getSkillCatalog({ includeDiagnostics: true });
680
+ const jsonMode = hasFlag('--json');
681
+ const renderedCatalog = catalogResult.skills.map((skill) => ({
682
+ slug: skill.slug,
683
+ name: skill.name,
684
+ description: skill.description,
685
+ sourceRoot: skill.sourceRoot,
686
+ hasScripts: skill.hasScripts,
687
+ hasReferences: skill.hasReferences,
688
+ hasAssets: skill.hasAssets,
689
+ compatibility: getTargetCompatibility(skill, context.ide),
690
+ }));
691
+
692
+ if (jsonMode) {
693
+ console.log(
694
+ JSON.stringify(
695
+ {
696
+ package: PACKAGE_NAME,
697
+ version: CURRENT_VERSION,
698
+ ide: context.ide,
699
+ skills: renderedCatalog,
700
+ diagnostics: dedupeWarnings(catalogResult.diagnostics),
701
+ },
702
+ null,
703
+ 2
704
+ )
705
+ );
706
+ return;
707
+ }
708
+
709
+ const ideDefinition = getIDEDefinition(context.ide);
710
+ console.log(`\n${badge('AI Agent Skills')} ${c.dim}— Skill Catalog${c.reset}\n`);
711
+ console.log(info(`IDE: ${c.bold}${ideDefinition.displayName}${c.reset}`));
712
+ if (context.matchedPath) {
713
+ console.log(info(`Detected from: ${c.dim}${context.matchedPath}${c.reset}`));
714
+ }
715
+ console.log('');
716
+
717
+ console.log(`${c.bold}Available skills (${catalogResult.skills.length}):${c.reset}\n`);
718
+ for (const skill of catalogResult.skills) {
719
+ const compat = formatCompatibilitySummary(skill, context.ide);
720
+ console.log(` ${c.cyan}/${skill.slug}${c.reset} ${c.dim}—${c.reset} ${skill.description}`);
721
+ console.log(` ${c.dim}source: ${skill.sourceRoot} | ${compat}${c.reset}`);
722
+ }
723
+ console.log('');
724
+ printWarnings(catalogResult.diagnostics);
725
+ } catch (err) {
726
+ console.log(error(`List failed: ${err.message}`));
727
+ process.exit(1);
728
+ }
729
+ }
730
+
731
+ function statusReport() {
732
+ try {
733
+ const context = resolveInstallContext();
734
+ const ideDefinition = getIDEDefinition(context.ide);
735
+ const rootDir = path.join(context.baseDir, ideDefinition.projectRoot);
736
+ const installedVersion = getInstalledVersion(rootDir);
737
+ const catalogResult = getSkillCatalog({ includeDiagnostics: true });
738
+
739
+ console.log(`\n${badge('AI Agent Skills')} ${c.dim}— Status${c.reset}\n`);
740
+
741
+ const rows = [
742
+ ['Package', PACKAGE_NAME],
743
+ ['Bundle version', CURRENT_VERSION],
744
+ ['Installed version', installedVersion || `${c.yellow}not installed${c.reset}`],
745
+ ['IDE', ideDefinition.displayName],
746
+ ['Workspace', context.baseDir],
747
+ ['Skills', `${catalogResult.skills.length}`],
748
+ ];
749
+
750
+ const agentSkill = catalogResult.skills.find((s) => s.slug === 'agents');
751
+ if (agentSkill && agentSkill.agents) {
752
+ rows.push(['Agent routing', `${c.green}enabled${c.reset}`]);
753
+ }
754
+
755
+ const needsUpdate = installedVersion && installedVersion !== CURRENT_VERSION;
756
+ rows.push(['Update needed', needsUpdate ? `${c.yellow}yes${c.reset}` : `${c.green}no${c.reset}`]);
757
+
758
+ for (const [label, value] of rows) {
759
+ console.log(` ${c.dim}${label}:${c.reset} ${value}`);
760
+ }
761
+
762
+ if (catalogResult.diagnostics.length) {
763
+ console.log('');
764
+ printWarnings(catalogResult.diagnostics);
765
+ }
766
+
767
+ console.log('');
768
+ } catch (err) {
769
+ console.log(error(`Status failed: ${err.message}`));
770
+ process.exit(1);
771
+ }
772
+ }
773
+
774
+ function showHelp() {
775
+ const skillNames = listSkillNames().map((name) => `${c.cyan}/${name}${c.reset}`).join(', ');
776
+
777
+ console.log(`
778
+ ${badge('AI Agent Skills')} v${CURRENT_VERSION}
779
+
780
+ ${c.bold}Usage:${c.reset}
781
+
782
+ ${c.green}npx ${PACKAGE_NAME} init${c.reset} ${c.dim}Interactive setup (choose IDE + scope)${c.reset}
783
+ ${c.green}npx ${PACKAGE_NAME} init --ide=cursor${c.reset} ${c.dim}Non-interactive install for specific IDE${c.reset}
784
+ ${c.green}npx ${PACKAGE_NAME} init --ide=all${c.reset} ${c.dim}Non-interactive install for all supported IDEs${c.reset}
785
+ ${c.green}npx ${PACKAGE_NAME} init --ide=codex${c.reset} ${c.dim}Install skills for Codex-style workspace${c.reset}
786
+ ${c.green}npx ${PACKAGE_NAME} init --ide=vscode${c.reset} ${c.dim}Install skills to VS Code/Copilot layout${c.reset}
787
+ ${c.green}npx ${PACKAGE_NAME} global${c.reset} ${c.dim}Install globally for all projects${c.reset}
788
+ ${c.green}npx ${PACKAGE_NAME} add owner/repo${c.reset} ${c.dim}Add skills from a GitHub repository${c.reset}
789
+ ${c.green}npx ${PACKAGE_NAME} list${c.reset} ${c.dim}List all bundled skills${c.reset}
790
+ ${c.green}npx ${PACKAGE_NAME} list --json${c.reset} ${c.dim}JSON output for scripts${c.reset}
791
+ ${c.green}npx ${PACKAGE_NAME} status${c.reset} ${c.dim}Show install status & versions${c.reset}
792
+ ${c.green}npx ${PACKAGE_NAME} help${c.reset} ${c.dim}Show this help${c.reset}
793
+
794
+ ${c.bold}GitHub Add Examples:${c.reset}
795
+
796
+ ${c.green}npx ${PACKAGE_NAME} add anthropics/skills${c.reset}
797
+ ${c.green}npx ${PACKAGE_NAME} add anthropics/skills --skill=pdf-processing${c.reset}
798
+ ${c.green}npx ${PACKAGE_NAME} add anthropics/skills --ide=cursor --branch=main${c.reset}
799
+
800
+ ${c.bold}Supported IDEs:${c.reset}
801
+
802
+ ${c.cyan}cursor${c.reset} ${c.dim}→ .cursor/skills + .cursor/rules${c.reset}
803
+ ${c.cyan}kiro${c.reset} ${c.dim}→ .kiro/skills${c.reset}
804
+ ${c.cyan}antigravity${c.reset} ${c.dim}→ .agent/workflows${c.reset}
805
+ ${c.cyan}codex${c.reset} ${c.dim}→ .agents/skills (detected via AGENTS.md / memories)${c.reset}
806
+ ${c.cyan}vscode${c.reset} ${c.dim}→ .github/skills${c.reset}
807
+ ${c.cyan}copilot${c.reset} ${c.dim}→ .github/skills${c.reset}
808
+ ${c.cyan}all${c.reset} ${c.dim}→ install to all IDE targets above${c.reset}
809
+
810
+ ${c.bold}Available skills:${c.reset}
811
+ ${skillNames}
812
+
813
+ ${c.dim}Documentation: https://github.com/votruongdanh/ai-agent-skills${c.reset}
814
+ `);
815
+ }
816
+
817
+ // ─── Main ───────────────────────────────────────────────────────────────────
818
+ const command = process.argv[2];
819
+
820
+ switch (command) {
821
+ case 'init':
822
+ if (hasFlag('--no-interactive')) {
823
+ // Non-interactive: --ide flag or explicit opt-out
824
+ checkForUpdates(() => install('project'));
825
+ } else {
826
+ // Interactive mode by default
827
+ checkForUpdates(() => interactiveInit().catch((err) => {
828
+ console.log(error(err.message));
829
+ process.exit(1);
830
+ }));
831
+ }
832
+ break;
833
+ case 'global':
834
+ checkForUpdates(() => install('global'));
835
+ break;
836
+ case 'add':
837
+ checkForUpdates(() => addSkillFromGitHub().catch((err) => {
838
+ console.log(error(err.message));
839
+ process.exit(1);
840
+ }));
841
+ break;
842
+ case 'list':
843
+ listSkills();
844
+ break;
845
+ case 'status':
846
+ statusReport();
847
+ break;
848
+ case 'update':
849
+ if (hasFlag('--no-interactive')) {
850
+ checkForUpdates(() => install('project'));
851
+ } else {
852
+ checkForUpdates(() => interactiveInit().catch((err) => {
853
+ console.log(error(err.message));
854
+ process.exit(1);
855
+ }));
856
+ }
857
+ break;
858
+ case 'help':
859
+ case '--help':
860
+ case '-h':
861
+ showHelp();
862
+ break;
863
+ default:
864
+ if (!command) {
865
+ // No command = show interactive init
866
+ checkForUpdates(() => interactiveInit().catch((err) => {
867
+ console.log(error(err.message));
868
+ process.exit(1);
869
+ }));
870
+ } else {
871
+ console.log(error(`Unknown command: ${command}`));
872
+ console.log(info(`Run ${c.cyan}npx ${PACKAGE_NAME} help${c.reset} for usage.\n`));
873
+ showHelp();
874
+ process.exit(1);
875
+ }
876
+ }