agent-skills-cli 1.0.8 → 1.0.9

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 (142) hide show
  1. package/README.md +18 -17
  2. package/dist/cli/agents.d.ts +10 -0
  3. package/dist/cli/agents.d.ts.map +1 -0
  4. package/dist/cli/agents.js +270 -0
  5. package/dist/cli/agents.js.map +1 -0
  6. package/dist/cli/commands/audit.d.ts +11 -0
  7. package/dist/cli/commands/audit.d.ts.map +1 -0
  8. package/dist/cli/commands/audit.js +168 -0
  9. package/dist/cli/commands/audit.js.map +1 -0
  10. package/dist/cli/commands/blueprint.d.ts +11 -0
  11. package/dist/cli/commands/blueprint.d.ts.map +1 -0
  12. package/dist/cli/commands/blueprint.js +210 -0
  13. package/dist/cli/commands/blueprint.js.map +1 -0
  14. package/dist/cli/commands/bootstrap.d.ts +11 -0
  15. package/dist/cli/commands/bootstrap.d.ts.map +1 -0
  16. package/dist/cli/commands/bootstrap.js +267 -0
  17. package/dist/cli/commands/bootstrap.js.map +1 -0
  18. package/dist/cli/commands/capture.d.ts +11 -0
  19. package/dist/cli/commands/capture.d.ts.map +1 -0
  20. package/dist/cli/commands/capture.js +109 -0
  21. package/dist/cli/commands/capture.js.map +1 -0
  22. package/dist/cli/commands/ci.d.ts +11 -0
  23. package/dist/cli/commands/ci.d.ts.map +1 -0
  24. package/dist/cli/commands/ci.js +144 -0
  25. package/dist/cli/commands/ci.js.map +1 -0
  26. package/dist/cli/commands/collab.d.ts +11 -0
  27. package/dist/cli/commands/collab.d.ts.map +1 -0
  28. package/dist/cli/commands/collab.js +196 -0
  29. package/dist/cli/commands/collab.js.map +1 -0
  30. package/dist/cli/commands/convert.d.ts +11 -0
  31. package/dist/cli/commands/convert.d.ts.map +1 -0
  32. package/dist/cli/commands/convert.js +219 -0
  33. package/dist/cli/commands/convert.js.map +1 -0
  34. package/dist/cli/commands/craft.d.ts +18 -0
  35. package/dist/cli/commands/craft.d.ts.map +1 -0
  36. package/dist/cli/commands/craft.js +205 -0
  37. package/dist/cli/commands/craft.js.map +1 -0
  38. package/dist/cli/commands/export.d.ts +9 -0
  39. package/dist/cli/commands/export.d.ts.map +1 -0
  40. package/dist/cli/commands/export.js +103 -0
  41. package/dist/cli/commands/export.js.map +1 -0
  42. package/dist/cli/commands/forge.d.ts +11 -0
  43. package/dist/cli/commands/forge.d.ts.map +1 -0
  44. package/dist/cli/commands/forge.js +152 -0
  45. package/dist/cli/commands/forge.js.map +1 -0
  46. package/dist/cli/commands/grid.d.ts +11 -0
  47. package/dist/cli/commands/grid.d.ts.map +1 -0
  48. package/dist/cli/commands/grid.js +217 -0
  49. package/dist/cli/commands/grid.js.map +1 -0
  50. package/dist/cli/commands/insight.d.ts +7 -0
  51. package/dist/cli/commands/insight.d.ts.map +1 -0
  52. package/dist/cli/commands/insight.js +71 -0
  53. package/dist/cli/commands/insight.js.map +1 -0
  54. package/dist/cli/commands/install.d.ts +6 -0
  55. package/dist/cli/commands/install.d.ts.map +1 -0
  56. package/dist/cli/commands/install.js +359 -0
  57. package/dist/cli/commands/install.js.map +1 -0
  58. package/dist/cli/commands/interactive.d.ts +7 -0
  59. package/dist/cli/commands/interactive.d.ts.map +1 -0
  60. package/dist/cli/commands/interactive.js +535 -0
  61. package/dist/cli/commands/interactive.js.map +1 -0
  62. package/dist/cli/commands/list.d.ts +6 -0
  63. package/dist/cli/commands/list.d.ts.map +1 -0
  64. package/dist/cli/commands/list.js +77 -0
  65. package/dist/cli/commands/list.js.map +1 -0
  66. package/dist/cli/commands/lockspec.d.ts +11 -0
  67. package/dist/cli/commands/lockspec.d.ts.map +1 -0
  68. package/dist/cli/commands/lockspec.js +179 -0
  69. package/dist/cli/commands/lockspec.js.map +1 -0
  70. package/dist/cli/commands/marketplace.d.ts +7 -0
  71. package/dist/cli/commands/marketplace.d.ts.map +1 -0
  72. package/dist/cli/commands/marketplace.js +417 -0
  73. package/dist/cli/commands/marketplace.js.map +1 -0
  74. package/dist/cli/commands/method.d.ts +7 -0
  75. package/dist/cli/commands/method.d.ts.map +1 -0
  76. package/dist/cli/commands/method.js +140 -0
  77. package/dist/cli/commands/method.js.map +1 -0
  78. package/dist/cli/commands/mine.d.ts +11 -0
  79. package/dist/cli/commands/mine.d.ts.map +1 -0
  80. package/dist/cli/commands/mine.js +254 -0
  81. package/dist/cli/commands/mine.js.map +1 -0
  82. package/dist/cli/commands/recall.d.ts +11 -0
  83. package/dist/cli/commands/recall.d.ts.map +1 -0
  84. package/dist/cli/commands/recall.js +201 -0
  85. package/dist/cli/commands/recall.js.map +1 -0
  86. package/dist/cli/commands/rule.d.ts +11 -0
  87. package/dist/cli/commands/rule.d.ts.map +1 -0
  88. package/dist/cli/commands/rule.js +230 -0
  89. package/dist/cli/commands/rule.js.map +1 -0
  90. package/dist/cli/commands/search.d.ts +6 -0
  91. package/dist/cli/commands/search.d.ts.map +1 -0
  92. package/dist/cli/commands/search.js +173 -0
  93. package/dist/cli/commands/search.js.map +1 -0
  94. package/dist/cli/commands/show.d.ts +6 -0
  95. package/dist/cli/commands/show.d.ts.map +1 -0
  96. package/dist/cli/commands/show.js +150 -0
  97. package/dist/cli/commands/show.js.map +1 -0
  98. package/dist/cli/commands/submit.d.ts +15 -0
  99. package/dist/cli/commands/submit.d.ts.map +1 -0
  100. package/dist/cli/commands/submit.js +151 -0
  101. package/dist/cli/commands/submit.js.map +1 -0
  102. package/dist/cli/commands/suggest.d.ts +11 -0
  103. package/dist/cli/commands/suggest.d.ts.map +1 -0
  104. package/dist/cli/commands/suggest.js +164 -0
  105. package/dist/cli/commands/suggest.js.map +1 -0
  106. package/dist/cli/commands/track.d.ts +11 -0
  107. package/dist/cli/commands/track.d.ts.map +1 -0
  108. package/dist/cli/commands/track.js +199 -0
  109. package/dist/cli/commands/track.js.map +1 -0
  110. package/dist/cli/commands/trigger.d.ts +11 -0
  111. package/dist/cli/commands/trigger.d.ts.map +1 -0
  112. package/dist/cli/commands/trigger.js +157 -0
  113. package/dist/cli/commands/trigger.js.map +1 -0
  114. package/dist/cli/commands/utils-commands.d.ts +9 -0
  115. package/dist/cli/commands/utils-commands.d.ts.map +1 -0
  116. package/dist/cli/commands/utils-commands.js +389 -0
  117. package/dist/cli/commands/utils-commands.js.map +1 -0
  118. package/dist/cli/commands/validate.d.ts +6 -0
  119. package/dist/cli/commands/validate.d.ts.map +1 -0
  120. package/dist/cli/commands/validate.js +40 -0
  121. package/dist/cli/commands/validate.js.map +1 -0
  122. package/dist/cli/index.d.ts +3 -0
  123. package/dist/cli/index.d.ts.map +1 -1
  124. package/dist/cli/index.js +84 -2743
  125. package/dist/cli/index.js.map +1 -1
  126. package/dist/core/audit.d.ts +24 -0
  127. package/dist/core/audit.d.ts.map +1 -0
  128. package/dist/core/audit.js +195 -0
  129. package/dist/core/audit.js.map +1 -0
  130. package/dist/core/index.d.ts +6 -0
  131. package/dist/core/index.d.ts.map +1 -1
  132. package/dist/core/index.js +6 -0
  133. package/dist/core/index.js.map +1 -1
  134. package/dist/core/scanner-rules.d.ts +58 -0
  135. package/dist/core/scanner-rules.d.ts.map +1 -0
  136. package/dist/core/scanner-rules.js +335 -0
  137. package/dist/core/scanner-rules.js.map +1 -0
  138. package/dist/core/suggest.d.ts +51 -0
  139. package/dist/core/suggest.d.ts.map +1 -0
  140. package/dist/core/suggest.js +241 -0
  141. package/dist/core/suggest.js.map +1 -0
  142. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -2,289 +2,55 @@
2
2
  /**
3
3
  * Agent Skills CLI
4
4
  * Universal CLI for managing Agent Skills across Cursor, Claude Code, GitHub Copilot, OpenAI Codex
5
+ *
6
+ * This file is the thin orchestrator — all command logic lives in ./commands/*.ts
7
+ * and shared config lives in ./agents.ts.
5
8
  */
6
9
  import { Command } from 'commander';
7
10
  import chalk from 'chalk';
8
11
  import inquirer from 'inquirer';
9
12
  import ora from 'ora';
10
13
  import * as p from '@clack/prompts';
11
- import { discoverSkills, loadSkill, validateMetadata, validateBody, formatValidationResult, generateSkillsPromptXML, generateFullSkillsContext, listSkillResources, listMarketplaceSkills, installSkill, uninstallSkill, searchSkills, getInstalledSkills, listMarketplaces, addMarketplace, checkUpdates, installFromGitHubUrl, getSkillByScoped, getSkillBaseUrl, fetchAssetManifest, getAssetUrl, fetchAsset, fetchSkillsForCLI,
12
- // Telemetry
13
- setVersion, trackSearch } from '../core/index.js';
14
- import { homedir } from 'os';
14
+ import { listMarketplaceSkills, fetchSkillsForCLI, } from '../core/index.js';
15
+ import { setVersion } from '../core/telemetry.js';
16
+ import { AGENTS } from './agents.js';
17
+ // ─── Modular command imports ────────────────────────────────────────────────
18
+ import { registerListCommand } from './commands/list.js';
19
+ import { registerValidateCommand } from './commands/validate.js';
20
+ import { registerShowCommand } from './commands/show.js';
21
+ import { registerMarketplaceCommands } from './commands/marketplace.js';
22
+ import { registerSearchInstallCommand } from './commands/search.js';
23
+ import { registerInstallCommand } from './commands/install.js';
24
+ import { registerExportCommand } from './commands/export.js';
25
+ import { registerDoctorCommand, registerCheckCommand, registerUpdateCommand, registerExecCommand } from './commands/utils-commands.js';
26
+ import { registerInteractiveCommands } from './commands/interactive.js';
27
+ // ─── Already-extracted command imports ──────────────────────────────────────
15
28
  import { registerRemoveCommand } from './commands/remove.js';
16
- import { fzfSearch } from './fzf-search.js';
17
- import { addSkillToLock, createLockEntry, getCanonicalPath } from '../core/index.js';
18
- // Centralized agent configuration with project and global paths
19
- const home = homedir();
20
- const AGENTS = {
21
- 'cursor': {
22
- name: 'cursor',
23
- displayName: 'Cursor',
24
- projectDir: '.cursor/skills',
25
- globalDir: `${home}/.cursor/skills`,
26
- },
27
- 'claude': {
28
- name: 'claude',
29
- displayName: 'Claude Code',
30
- projectDir: '.claude/skills',
31
- globalDir: `${home}/.claude/skills`,
32
- },
33
- 'copilot': {
34
- name: 'copilot',
35
- displayName: 'GitHub Copilot',
36
- projectDir: '.github/skills',
37
- globalDir: `${home}/.github/skills`,
38
- },
39
- 'codex': {
40
- name: 'codex',
41
- displayName: 'Codex',
42
- projectDir: '.codex/skills',
43
- globalDir: `${home}/.codex/skills`,
44
- },
45
- 'antigravity': {
46
- name: 'antigravity',
47
- displayName: 'Antigravity',
48
- projectDir: '.agent/skills',
49
- globalDir: `${home}/.gemini/antigravity/skills`,
50
- },
51
- // New agents from add-skill
52
- 'opencode': {
53
- name: 'opencode',
54
- displayName: 'OpenCode',
55
- projectDir: '.opencode/skill',
56
- globalDir: `${home}/.config/opencode/skill`,
57
- },
58
- 'amp': {
59
- name: 'amp',
60
- displayName: 'Amp',
61
- projectDir: '.agents/skills',
62
- globalDir: `${home}/.config/agents/skills`,
63
- },
64
- 'kilo': {
65
- name: 'kilo',
66
- displayName: 'Kilo Code',
67
- projectDir: '.kilocode/skills',
68
- globalDir: `${home}/.kilocode/skills`,
69
- },
70
- 'roo': {
71
- name: 'roo',
72
- displayName: 'Roo Code',
73
- projectDir: '.roo/skills',
74
- globalDir: `${home}/.roo/skills`,
75
- },
76
- 'goose': {
77
- name: 'goose',
78
- displayName: 'Goose',
79
- projectDir: '.goose/skills',
80
- globalDir: `${home}/.config/goose/skills`,
81
- },
82
- // New agents from vercel-labs/skills (19 additional)
83
- 'cline': {
84
- name: 'cline',
85
- displayName: 'Cline',
86
- projectDir: '.cline/skills',
87
- globalDir: `${home}/.cline/skills`,
88
- },
89
- 'codebuddy': {
90
- name: 'codebuddy',
91
- displayName: 'CodeBuddy',
92
- projectDir: '.codebuddy/skills',
93
- globalDir: `${home}/.codebuddy/skills`,
94
- },
95
- 'command-code': {
96
- name: 'command-code',
97
- displayName: 'Command Code',
98
- projectDir: '.commandcode/skills',
99
- globalDir: `${home}/.commandcode/skills`,
100
- },
101
- 'continue': {
102
- name: 'continue',
103
- displayName: 'Continue',
104
- projectDir: '.continue/skills',
105
- globalDir: `${home}/.continue/skills`,
106
- },
107
- 'crush': {
108
- name: 'crush',
109
- displayName: 'Crush',
110
- projectDir: '.crush/skills',
111
- globalDir: `${home}/.config/crush/skills`,
112
- },
113
- 'clawdbot': {
114
- name: 'clawdbot',
115
- displayName: 'Clawdbot',
116
- projectDir: 'skills',
117
- globalDir: `${home}/.clawdbot/skills`,
118
- },
119
- 'droid': {
120
- name: 'droid',
121
- displayName: 'Droid',
122
- projectDir: '.factory/skills',
123
- globalDir: `${home}/.factory/skills`,
124
- },
125
- 'gemini-cli': {
126
- name: 'gemini-cli',
127
- displayName: 'Gemini CLI',
128
- projectDir: '.gemini/skills',
129
- globalDir: `${home}/.gemini/skills`,
130
- },
131
- 'kiro-cli': {
132
- name: 'kiro-cli',
133
- displayName: 'Kiro CLI',
134
- projectDir: '.kiro/skills',
135
- globalDir: `${home}/.kiro/skills`,
136
- },
137
- 'mcpjam': {
138
- name: 'mcpjam',
139
- displayName: 'MCPJam',
140
- projectDir: '.mcpjam/skills',
141
- globalDir: `${home}/.mcpjam/skills`,
142
- },
143
- 'mux': {
144
- name: 'mux',
145
- displayName: 'Mux',
146
- projectDir: '.mux/skills',
147
- globalDir: `${home}/.mux/skills`,
148
- },
149
- 'openhands': {
150
- name: 'openhands',
151
- displayName: 'OpenHands',
152
- projectDir: '.openhands/skills',
153
- globalDir: `${home}/.openhands/skills`,
154
- },
155
- 'pi': {
156
- name: 'pi',
157
- displayName: 'Pi',
158
- projectDir: '.pi/skills',
159
- globalDir: `${home}/.pi/agent/skills`,
160
- },
161
- 'qoder': {
162
- name: 'qoder',
163
- displayName: 'Qoder',
164
- projectDir: '.qoder/skills',
165
- globalDir: `${home}/.qoder/skills`,
166
- },
167
- 'qwen-code': {
168
- name: 'qwen-code',
169
- displayName: 'Qwen Code',
170
- projectDir: '.qwen/skills',
171
- globalDir: `${home}/.qwen/skills`,
172
- },
173
- 'trae': {
174
- name: 'trae',
175
- displayName: 'Trae',
176
- projectDir: '.trae/skills',
177
- globalDir: `${home}/.trae/skills`,
178
- },
179
- 'windsurf': {
180
- name: 'windsurf',
181
- displayName: 'Windsurf',
182
- projectDir: '.windsurf/skills',
183
- globalDir: `${home}/.codeium/windsurf/skills`,
184
- },
185
- 'zencoder': {
186
- name: 'zencoder',
187
- displayName: 'Zencoder',
188
- projectDir: '.zencoder/skills',
189
- globalDir: `${home}/.zencoder/skills`,
190
- },
191
- 'neovate': {
192
- name: 'neovate',
193
- displayName: 'Neovate',
194
- projectDir: '.neovate/skills',
195
- globalDir: `${home}/.neovate/skills`,
196
- },
197
- // Additional agents from Vercel Skills (13 more = 42 total)
198
- 'ara': {
199
- name: 'ara',
200
- displayName: 'Ara',
201
- projectDir: '.ara/skills',
202
- globalDir: `${home}/.ara/skills`,
203
- },
204
- 'aide': {
205
- name: 'aide',
206
- displayName: 'Aide',
207
- projectDir: '.aide/skills',
208
- globalDir: `${home}/.aide/skills`,
209
- },
210
- 'alex': {
211
- name: 'alex',
212
- displayName: 'Alex',
213
- projectDir: '.alex/skills',
214
- globalDir: `${home}/.alex/skills`,
215
- },
216
- 'bb': {
217
- name: 'bb',
218
- displayName: 'BB',
219
- projectDir: '.bb/skills',
220
- globalDir: `${home}/.bb/skills`,
221
- },
222
- 'codestory': {
223
- name: 'codestory',
224
- displayName: 'CodeStory',
225
- projectDir: '.codestory/skills',
226
- globalDir: `${home}/.codestory/skills`,
227
- },
228
- 'helix-ai': {
229
- name: 'helix-ai',
230
- displayName: 'Helix AI',
231
- projectDir: '.helix/skills',
232
- globalDir: `${home}/.helix/skills`,
233
- },
234
- 'meekia': {
235
- name: 'meekia',
236
- displayName: 'Meekia',
237
- projectDir: '.meekia/skills',
238
- globalDir: `${home}/.meekia/skills`,
239
- },
240
- 'pear-ai': {
241
- name: 'pear-ai',
242
- displayName: 'Pear AI',
243
- projectDir: '.pearai/skills',
244
- globalDir: `${home}/.pearai/skills`,
245
- },
246
- 'adal': {
247
- name: 'adal',
248
- displayName: 'Adal',
249
- projectDir: '.adal/skills',
250
- globalDir: `${home}/.adal/skills`,
251
- },
252
- 'pochi': {
253
- name: 'pochi',
254
- displayName: 'Pochi',
255
- projectDir: '.pochi/skills',
256
- globalDir: `${home}/.pochi/skills`,
257
- },
258
- 'sourcegraph-cody': {
259
- name: 'sourcegraph-cody',
260
- displayName: 'Sourcegraph Cody',
261
- projectDir: '.sourcegraph/skills',
262
- globalDir: `${home}/.sourcegraph/skills`,
263
- },
264
- 'void-ai': {
265
- name: 'void-ai',
266
- displayName: 'Void AI',
267
- projectDir: '.void/skills',
268
- globalDir: `${home}/.void/skills`,
269
- },
270
- 'zed': {
271
- name: 'zed',
272
- displayName: 'Zed',
273
- projectDir: '.zed/skills',
274
- globalDir: `${home}/.zed/skills`,
275
- },
276
- };
277
- // Helper to get install path
278
- function getInstallPath(agent, global) {
279
- const config = AGENTS[agent];
280
- if (!config)
281
- return `.${agent}/skills`;
282
- return global ? config.globalDir : config.projectDir;
283
- }
29
+ import { registerSuggestCommand } from './commands/suggest.js';
30
+ import { registerAuditCommand } from './commands/audit.js';
31
+ import { registerCraftCommand } from './commands/craft.js';
32
+ import { registerSubmitCommand } from './commands/submit.js';
33
+ import { registerBootstrapCommand } from './commands/bootstrap.js';
34
+ import { registerConvertCommand } from './commands/convert.js';
35
+ import { registerCollabCommand } from './commands/collab.js';
36
+ import { registerLockspecCommand } from './commands/lockspec.js';
37
+ import { registerForgeCommand } from './commands/forge.js';
38
+ import { registerMineCommand } from './commands/mine.js';
39
+ import { registerRecallCommand } from './commands/recall.js';
40
+ import { registerGridCommand } from './commands/grid.js';
41
+ import { registerCaptureCommand } from './commands/capture.js';
42
+ import { registerTriggerCommand } from './commands/trigger.js';
43
+ import { registerRuleCommand } from './commands/rule.js';
44
+ import { registerBlueprintCommand } from './commands/blueprint.js';
45
+ import { registerCiCommand } from './commands/ci.js';
46
+ import { registerTrackCommand } from './commands/track.js';
47
+ import { registerInsightCommand } from './commands/insight.js';
48
+ import { registerMethodCommand } from './commands/method.js';
49
+ // ─── Program setup ─────────────────────────────────────────────────────────
284
50
  const program = new Command();
285
51
  // Initialize telemetry with CLI version
286
52
  setVersion('1.0.8');
287
- // Main flow when running `skills` - go straight to install
53
+ // ─── Main interactive flow (`skills` with no subcommand) ────────────────────
288
54
  async function showMainMenu() {
289
55
  console.log('');
290
56
  p.intro(chalk.bgCyan.black(' Agent Skills CLI '));
@@ -308,21 +74,18 @@ async function showMainMenu() {
308
74
  p.log.warn('No agents selected. Exiting.');
309
75
  return;
310
76
  }
311
- // Cast to string array for use throughout the function
312
77
  const selectedAgents = agents;
313
- // Step 2: Fetch skills from our database (primary), SkillsMP as fallback
78
+ // Step 2: Fetch skills from our database
314
79
  const spinner = ora('Fetching skills from marketplace...').start();
315
80
  let marketplaceSkills = [];
316
81
  let total = 0;
317
82
  try {
318
- // Try our database first
319
83
  const result = await fetchSkillsForCLI({ limit: 100, sortBy: 'stars' });
320
84
  marketplaceSkills = result.skills;
321
85
  total = result.total;
322
86
  spinner.succeed(`Found ${total.toLocaleString()} skills (showing top 100 by stars)`);
323
87
  }
324
88
  catch (err) {
325
- // Fallback to GitHub sources if our API is down
326
89
  spinner.text = 'Falling back to GitHub sources...';
327
90
  marketplaceSkills = await listMarketplaceSkills();
328
91
  total = marketplaceSkills.length;
@@ -356,10 +119,8 @@ async function showMainMenu() {
356
119
  console.log(chalk.yellow('\nNo skills selected. Exiting.\n'));
357
120
  return;
358
121
  }
359
- // Step 4: Install skills directly to platform directories (like working install command)
122
+ // Step 4: Install skills directly to platform directories
360
123
  console.log('');
361
- // Use centralized AGENTS config for platform directories
362
- // Import dependencies once
363
124
  const { getSkillByScoped } = await import('../core/skillsdb.js');
364
125
  const { mkdir, cp, rm } = await import('fs/promises');
365
126
  const { join } = await import('path');
@@ -367,21 +128,17 @@ async function showMainMenu() {
367
128
  const { exec } = await import('child_process');
368
129
  const { promisify } = await import('util');
369
130
  const execAsync = promisify(exec);
370
- // Function to install a single skill
371
- async function installSkillToplatforms(skill) {
131
+ async function installSkillToPlatforms(skill) {
372
132
  try {
373
- // Fetch skill from database using scopedName
374
133
  const dbSkill = await getSkillByScoped(skill.scopedName || skill.name);
375
134
  if (!dbSkill) {
376
135
  return { success: false, name: skill.name, error: 'Skill not found' };
377
136
  }
378
- // Handle both camelCase (from API) and snake_case field names
379
137
  const githubUrl = dbSkill.github_url || dbSkill.githubUrl;
380
138
  const scopedName = dbSkill.scoped_name || dbSkill.scopedName || skill.scopedName;
381
139
  if (!githubUrl) {
382
140
  return { success: false, name: skill.name, error: 'No GitHub URL found' };
383
141
  }
384
- // Parse GitHub URL
385
142
  const urlMatch = githubUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/);
386
143
  if (!urlMatch) {
387
144
  return { success: false, name: skill.name, error: 'Invalid GitHub URL' };
@@ -389,12 +146,10 @@ async function showMainMenu() {
389
146
  const [, owner, repo] = urlMatch;
390
147
  const branch = dbSkill.branch || 'main';
391
148
  const skillPath = (dbSkill.path || '').replace(/\/SKILL\.md$/i, '');
392
- // Download to temp directory
393
149
  const tempDir = join(tmpdir(), `skill-${Date.now()}-${Math.random().toString(36).slice(2)}`);
394
150
  await mkdir(tempDir, { recursive: true });
395
151
  try {
396
152
  await execAsync(`git clone --depth 1 --branch ${branch} https://github.com/${owner}/${repo}.git .`, { cwd: tempDir });
397
- // Install to each platform
398
153
  for (const platform of selectedAgents) {
399
154
  const agentConfig = AGENTS[platform];
400
155
  if (!agentConfig)
@@ -402,14 +157,12 @@ async function showMainMenu() {
402
157
  const targetDir = agentConfig.projectDir;
403
158
  const skillDir = join(process.cwd(), targetDir, dbSkill.name);
404
159
  await mkdir(skillDir, { recursive: true });
405
- // Copy skill files
406
160
  const sourceDir = skillPath ? join(tempDir, skillPath) : tempDir;
407
161
  await cp(sourceDir, skillDir, { recursive: true });
408
162
  }
409
163
  return { success: true, name: dbSkill.name, scopedName };
410
164
  }
411
165
  finally {
412
- // Cleanup temp directory
413
166
  await rm(tempDir, { recursive: true, force: true }).catch(() => { });
414
167
  }
415
168
  }
@@ -417,13 +170,10 @@ async function showMainMenu() {
417
170
  return { success: false, name: skill.name, error: err.message || String(err) };
418
171
  }
419
172
  }
420
- // Show what we're downloading
421
173
  console.log(chalk.bold(`šŸ“¦ Installing ${selectedSkills.length} skill(s) in parallel...\n`));
422
174
  const downloadSpinner = ora(`Downloading ${selectedSkills.length} skills...`).start();
423
- // Install all skills in parallel
424
- const results = await Promise.all(selectedSkills.map((skill) => installSkillToplatforms(skill)));
175
+ const results = await Promise.all(selectedSkills.map((skill) => installSkillToPlatforms(skill)));
425
176
  downloadSpinner.succeed(`Downloaded ${results.filter(r => r.success).length}/${selectedSkills.length} skills`);
426
- // Show results
427
177
  console.log('');
428
178
  for (const result of results) {
429
179
  if (result.success) {
@@ -442,2461 +192,52 @@ async function showMainMenu() {
442
192
  }
443
193
  console.log('');
444
194
  }
445
- async function interactiveInstall() {
446
- // Step 1: Select target agent(s)
447
- const { agents } = await inquirer.prompt([
448
- {
449
- type: 'checkbox',
450
- name: 'agents',
451
- message: 'Which AI agents will you use these skills with?',
452
- choices: [
453
- { name: 'Cursor', value: 'cursor', checked: true },
454
- { name: 'Claude Code', value: 'claude', checked: true },
455
- { name: 'GitHub Copilot', value: 'copilot', checked: true },
456
- { name: 'OpenAI Codex', value: 'codex', checked: false }
457
- ]
458
- }
459
- ]);
460
- if (agents.length === 0) {
461
- console.log(chalk.yellow('No agents selected.'));
462
- return;
463
- }
464
- // Step 2: Fetch and select skills
465
- const spinner = ora('Fetching skills from marketplace...').start();
466
- const skills = await listMarketplaceSkills();
467
- spinner.stop();
468
- if (skills.length === 0) {
469
- console.log(chalk.yellow('No skills found.'));
470
- return;
471
- }
472
- const choices = skills.map(skill => ({
473
- name: `${skill.name} - ${skill.description?.slice(0, 45) || 'No description'}...`,
474
- value: skill.name,
475
- short: skill.name
476
- }));
477
- const { selectedSkills } = await inquirer.prompt([
478
- {
479
- type: 'checkbox',
480
- name: 'selectedSkills',
481
- message: 'Select skills to install (Space to select):',
482
- choices,
483
- pageSize: 12
484
- }
485
- ]);
486
- if (selectedSkills.length === 0) {
487
- console.log(chalk.yellow('No skills selected.'));
488
- return;
489
- }
490
- // Step 3: Install skills
491
- console.log('');
492
- for (const skillName of selectedSkills) {
493
- const installSpinner = ora(`Installing ${skillName}...`).start();
494
- try {
495
- await installSkill(skillName);
496
- installSpinner.succeed(`Installed: ${skillName}`);
497
- }
498
- catch (err) {
499
- installSpinner.fail(`Failed: ${skillName}`);
500
- }
501
- }
502
- // Step 4: Export to selected agents
503
- console.log('');
504
- const exportSpinner = ora('Exporting to selected agents...').start();
505
- const allSkills = await discoverSkills();
506
- const { mkdir, writeFile, appendFile } = await import('fs/promises');
507
- const { join } = await import('path');
508
- const { existsSync } = await import('fs');
509
- const fs = { mkdir, writeFile, appendFile, join, existsSync };
510
- exportSpinner.stop();
511
- for (const agent of agents) {
512
- const agentSpinner = ora(`Exporting to ${agent}...`).start();
513
- await exportToAgent(agent, allSkills, '.', fs);
514
- agentSpinner.succeed(`Exported to ${agent}`);
515
- }
516
- console.log(chalk.bold.green('\n✨ Done! Skills installed and exported.\n'));
517
- }
518
- async function interactiveExport() {
519
- const skills = await discoverSkills();
520
- if (skills.length === 0) {
521
- console.log(chalk.yellow('No skills found to export.'));
522
- return;
523
- }
524
- const { agents } = await inquirer.prompt([
525
- {
526
- type: 'checkbox',
527
- name: 'agents',
528
- message: 'Select target AI agents:',
529
- choices: [
530
- { name: 'Cursor (.cursor/skills/)', value: 'cursor', checked: true },
531
- { name: 'Claude Code (.claude/skills/)', value: 'claude', checked: true },
532
- { name: 'GitHub Copilot (.github/skills/)', value: 'copilot', checked: true },
533
- { name: 'OpenAI Codex (.codex/skills/)', value: 'codex', checked: false },
534
- { name: 'Antigravity (.agent/workflows/)', value: 'antigravity', checked: true }
535
- ]
536
- }
537
- ]);
538
- if (agents.length === 0) {
539
- console.log(chalk.yellow('No agents selected.'));
540
- return;
541
- }
542
- const { mkdir, writeFile, appendFile } = await import('fs/promises');
543
- const { join } = await import('path');
544
- const { existsSync } = await import('fs');
545
- const fs = { mkdir, writeFile, appendFile, join, existsSync };
546
- console.log('');
547
- for (const agent of agents) {
548
- const spinner = ora(`Exporting to ${agent}...`).start();
549
- await exportToAgent(agent, skills, '.', fs);
550
- spinner.succeed();
551
- }
552
- console.log(chalk.bold.green('\nāœ“ Export complete!\n'));
553
- }
195
+ // ─── Root command ───────────────────────────────────────────────────────────
554
196
  program
555
197
  .name('skills')
556
198
  .description('Agent Skills CLI - Manage skills for Cursor, Claude Code, GitHub Copilot, OpenAI Codex')
557
199
  .version('1.0.0')
558
200
  .action(showMainMenu);
559
- // List command
560
- program
561
- .command('list')
562
- .description('List all discovered skills')
563
- .option('-p, --paths <paths...>', 'Custom search paths')
564
- .option('-v, --verbose', 'Show detailed information')
565
- .option('--json', 'Output as JSON')
566
- .option('--table', 'Output as ASCII table')
567
- .option('-q, --quiet', 'Output names only (for scripting)')
568
- .action(async (options) => {
569
- try {
570
- const config = options.paths ? { searchPaths: options.paths } : {};
571
- const skills = await discoverSkills(config);
572
- if (skills.length === 0) {
573
- if (options.json) {
574
- console.log(JSON.stringify({ skills: [], count: 0 }));
575
- }
576
- else if (!options.quiet) {
577
- console.log(chalk.yellow('No skills found.'));
578
- console.log(chalk.gray('Skills are searched in:'));
579
- console.log(chalk.gray(' - ~/.antigravity/skills/'));
580
- console.log(chalk.gray(' - .antigravity/skills/'));
581
- console.log(chalk.gray(' - ./skills/'));
582
- }
583
- return;
584
- }
585
- // JSON output
586
- if (options.json) {
587
- console.log(JSON.stringify({
588
- skills: skills.map(s => ({
589
- name: s.name,
590
- description: s.description,
591
- path: s.path
592
- })),
593
- count: skills.length
594
- }, null, 2));
595
- return;
596
- }
597
- // Quiet output (names only)
598
- if (options.quiet) {
599
- skills.forEach(s => console.log(s.name));
600
- return;
601
- }
602
- // Table output
603
- if (options.table) {
604
- const maxName = Math.max(...skills.map(s => s.name.length), 4);
605
- const maxDesc = Math.min(Math.max(...skills.map(s => (s.description || '').length), 11), 50);
606
- console.log('');
607
- console.log(chalk.bold('Name'.padEnd(maxName + 2) + 'Description'));
608
- console.log('─'.repeat(maxName + 2 + maxDesc));
609
- for (const skill of skills) {
610
- const desc = (skill.description || '').slice(0, 50);
611
- console.log(chalk.cyan(skill.name.padEnd(maxName + 2)) + chalk.gray(desc));
612
- }
613
- console.log('');
614
- return;
615
- }
616
- // Default output
617
- console.log(chalk.bold(`\nFound ${skills.length} skill(s):\n`));
618
- for (const skill of skills) {
619
- console.log(chalk.cyan(` ${skill.name}`));
620
- if (options.verbose) {
621
- console.log(chalk.gray(` ${skill.description}`));
622
- console.log(chalk.gray(` Path: ${skill.path}`));
623
- }
624
- }
625
- console.log('');
626
- }
627
- catch (error) {
628
- console.error(chalk.red('Error listing skills:'), error);
629
- process.exit(1);
630
- }
631
- });
632
- // Validate command
633
- program
634
- .command('validate <path>')
635
- .description('Validate a skill against the Agent Skills specification')
636
- .action(async (path) => {
637
- try {
638
- const skill = await loadSkill(path);
639
- if (!skill) {
640
- console.error(chalk.red(`Skill not found at: ${path}`));
641
- process.exit(1);
642
- }
643
- console.log(chalk.bold(`\nValidating: ${skill.metadata.name}\n`));
644
- // Validate metadata
645
- const metadataResult = validateMetadata(skill.metadata);
646
- console.log(chalk.underline('Metadata:'));
647
- console.log(formatValidationResult(metadataResult));
648
- // Validate body
649
- const bodyResult = validateBody(skill.body);
650
- console.log(chalk.underline('\nBody Content:'));
651
- console.log(formatValidationResult(bodyResult));
652
- // Overall result
653
- const isValid = metadataResult.valid && bodyResult.valid;
654
- console.log('\n' + '─'.repeat(40));
655
- if (isValid) {
656
- console.log(chalk.green.bold('āœ“ Skill is valid'));
657
- }
658
- else {
659
- console.log(chalk.red.bold('āœ— Skill has validation errors'));
660
- process.exit(1);
661
- }
662
- }
663
- catch (error) {
664
- console.error(chalk.red('Error validating skill:'), error);
665
- process.exit(1);
666
- }
667
- });
668
- // Show command
669
- program
670
- .command('show <name>')
671
- .description('Show detailed information about a skill')
672
- .action(async (name) => {
673
- try {
674
- const skills = await discoverSkills();
675
- const skillRef = skills.find(s => s.name === name);
676
- if (!skillRef) {
677
- console.error(chalk.red(`Skill not found: ${name}`));
678
- console.log(chalk.gray('Available skills:'), skills.map(s => s.name).join(', ') || 'none');
679
- process.exit(1);
680
- }
681
- const skill = await loadSkill(skillRef.path);
682
- if (!skill) {
683
- console.error(chalk.red(`Could not load skill: ${name}`));
684
- process.exit(1);
685
- }
686
- console.log(chalk.bold(`\n${skill.metadata.name}`));
687
- console.log('─'.repeat(40));
688
- console.log(chalk.cyan('Description:'), skill.metadata.description);
689
- console.log(chalk.cyan('Path:'), skill.path);
690
- if (skill.metadata.license) {
691
- console.log(chalk.cyan('License:'), skill.metadata.license);
692
- }
693
- if (skill.metadata.compatibility) {
694
- console.log(chalk.cyan('Compatibility:'), skill.metadata.compatibility);
695
- }
696
- // List resources
697
- const resources = await listSkillResources(skill.path);
698
- if (resources.scripts.length > 0) {
699
- console.log(chalk.cyan('\nScripts:'));
700
- resources.scripts.forEach(s => console.log(chalk.gray(` - ${s}`)));
701
- }
702
- if (resources.references.length > 0) {
703
- console.log(chalk.cyan('\nReferences:'));
704
- resources.references.forEach(r => console.log(chalk.gray(` - ${r}`)));
705
- }
706
- if (resources.assets.length > 0) {
707
- console.log(chalk.cyan('\nAssets:'));
708
- resources.assets.forEach(a => console.log(chalk.gray(` - ${a}`)));
709
- }
710
- // Body preview
711
- const bodyLines = skill.body.split('\n').slice(0, 10);
712
- console.log(chalk.cyan('\nInstructions (preview):'));
713
- console.log(chalk.gray(bodyLines.join('\n')));
714
- if (skill.body.split('\n').length > 10) {
715
- console.log(chalk.gray('...'));
716
- }
717
- console.log('');
718
- }
719
- catch (error) {
720
- console.error(chalk.red('Error showing skill:'), error);
721
- process.exit(1);
722
- }
723
- });
724
- // Prompt command - generate system prompt XML
725
- program
726
- .command('prompt')
727
- .description('Generate system prompt XML for discovered skills')
728
- .option('-f, --full', 'Include full skill system instructions')
729
- .action(async (options) => {
730
- try {
731
- const skills = await discoverSkills();
732
- if (skills.length === 0) {
733
- console.log(chalk.yellow('No skills found.'));
734
- return;
735
- }
736
- if (options.full) {
737
- const context = generateFullSkillsContext(skills);
738
- console.log(context);
739
- }
740
- else {
741
- const { xml, skillCount, estimatedTokens } = generateSkillsPromptXML(skills);
742
- console.log(xml);
743
- console.log(chalk.gray(`\n# ${skillCount} skills, ~${estimatedTokens} tokens`));
744
- }
745
- }
746
- catch (error) {
747
- console.error(chalk.red('Error generating prompt:'), error);
748
- process.exit(1);
749
- }
750
- });
751
- // Init command - create a new skill
752
- program
753
- .command('init <name>')
754
- .description('Create a new skill from template')
755
- .option('-d, --directory <dir>', 'Directory to create skill in', './skills')
756
- .action(async (name, options) => {
757
- try {
758
- const { mkdir, writeFile } = await import('fs/promises');
759
- const { join } = await import('path');
760
- const skillDir = join(options.directory, name);
761
- // Create directories
762
- await mkdir(join(skillDir, 'scripts'), { recursive: true });
763
- await mkdir(join(skillDir, 'references'), { recursive: true });
764
- await mkdir(join(skillDir, 'assets'), { recursive: true });
765
- // Create SKILL.md
766
- const skillMd = `---
767
- name: ${name}
768
- description: Brief description of what this skill does and when to use it.
769
- license: MIT
770
- metadata:
771
- author: your-name
772
- version: "1.0"
773
- ---
774
-
775
- # ${name.split('-').map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')}
776
-
777
- ## When to use this skill
778
-
779
- Use this skill when the user needs to...
780
-
781
- ## Instructions
782
-
783
- 1. First step
784
- 2. Second step
785
- 3. Third step
786
-
787
- ## Examples
788
-
789
- ### Example 1
790
-
791
- \`\`\`
792
- Example input or command
793
- \`\`\`
794
-
795
- ## Best practices
796
-
797
- - Best practice 1
798
- - Best practice 2
799
- `;
800
- await writeFile(join(skillDir, 'SKILL.md'), skillMd);
801
- console.log(chalk.green(`āœ“ Created skill: ${name}`));
802
- console.log(chalk.gray(` Path: ${skillDir}`));
803
- console.log(chalk.gray('\nNext steps:'));
804
- console.log(chalk.gray(' 1. Edit SKILL.md with your instructions'));
805
- console.log(chalk.gray(' 2. Add scripts to scripts/'));
806
- console.log(chalk.gray(' 3. Run: skills validate ' + skillDir));
807
- }
808
- catch (error) {
809
- console.error(chalk.red('Error creating skill:'), error);
810
- process.exit(1);
811
- }
812
- });
813
- // ============================================
814
- // ASSETS COMMAND - On-demand asset fetching
815
- // ============================================
816
- program
817
- .command('assets <skill-name>')
818
- .description('List and fetch assets for a skill on-demand from GitHub')
819
- .option('-l, --list', 'List available assets')
820
- .option('-m, --manifest', 'Show asset manifest if available')
821
- .option('-g, --get <path>', 'Fetch and display specific asset content')
822
- .option('--json', 'Output in JSON format')
823
- .action(async (skillName, options) => {
824
- try {
825
- const spinner = ora('Fetching skill info...').start();
826
- // Fetch skill from database
827
- const skill = await getSkillByScoped(skillName);
828
- if (!skill) {
829
- spinner.fail(`Skill not found: ${skillName}`);
830
- process.exit(1);
831
- }
832
- if (!skill.raw_url) {
833
- spinner.fail('Skill has no raw_url - cannot fetch assets');
834
- process.exit(1);
835
- }
836
- const baseUrl = getSkillBaseUrl(skill.raw_url);
837
- spinner.succeed(`Found skill: ${skill.scoped_name || skill.name}`);
838
- if (options.manifest) {
839
- // Show asset manifest
840
- const manifestSpinner = ora('Fetching manifest...').start();
841
- const manifest = await fetchAssetManifest(baseUrl);
842
- if (!manifest) {
843
- manifestSpinner.fail('No asset manifest found (index.jsonl)');
844
- return;
845
- }
846
- manifestSpinner.succeed(`Found ${manifest.length} components`);
847
- if (options.json) {
848
- console.log(JSON.stringify(manifest, null, 2));
849
- }
850
- else {
851
- console.log('');
852
- // Group by category
853
- const byCategory = new Map();
854
- for (const entry of manifest) {
855
- const cat = entry.category || 'other';
856
- if (!byCategory.has(cat))
857
- byCategory.set(cat, []);
858
- byCategory.get(cat).push(entry);
859
- }
860
- for (const [category, entries] of byCategory) {
861
- console.log(chalk.bold.cyan(`\n${category}:`));
862
- for (const entry of entries.slice(0, 5)) {
863
- console.log(chalk.white(` ${entry.id}`));
864
- if (entry.name)
865
- console.log(chalk.gray(` ${entry.name}`));
866
- }
867
- if (entries.length > 5) {
868
- console.log(chalk.gray(` ... and ${entries.length - 5} more`));
869
- }
870
- }
871
- }
872
- }
873
- else if (options.get) {
874
- // Fetch specific asset
875
- const assetPath = options.get;
876
- const assetUrl = getAssetUrl(baseUrl, assetPath);
877
- const fetchSpinner = ora(`Fetching ${assetPath}...`).start();
878
- const content = await fetchAsset(assetUrl);
879
- if (!content) {
880
- fetchSpinner.fail(`Asset not found: ${assetPath}`);
881
- process.exit(1);
882
- }
883
- fetchSpinner.succeed(`Fetched ${content.length} chars`);
884
- console.log('');
885
- console.log(content);
886
- }
887
- else {
888
- // Default: show info about assets
889
- console.log(chalk.gray(`\nBase URL: ${baseUrl}`));
890
- console.log('');
891
- console.log(chalk.bold('Usage:'));
892
- console.log(chalk.gray(` skills assets "${skillName}" --manifest`));
893
- console.log(chalk.gray(' Show component manifest'));
894
- console.log('');
895
- console.log(chalk.gray(` skills assets "${skillName}" --get "assets/code/v3/html/buttons/primary.html"`));
896
- console.log(chalk.gray(' Fetch specific asset content'));
897
- }
898
- }
899
- catch (error) {
900
- console.error(chalk.red('Error:'), error);
901
- process.exit(1);
902
- }
903
- });
904
- // ============================================
905
- // MARKETPLACE COMMANDS
906
- // ============================================
907
- // Market list - list skills from SkillsMP (40k+ skills)
908
- program
909
- .command('market-list')
910
- .alias('ml')
911
- .description('List skills from SkillsMP marketplace (40k+ skills)')
912
- .option('-l, --limit <number>', 'Number of skills to show', '50')
913
- .option('-p, --page <number>', 'Page number', '1')
914
- .option('--legacy', 'Use legacy GitHub sources instead of SkillsMP')
915
- .action(async (options) => {
916
- try {
917
- if (options.legacy) {
918
- // Legacy mode: fetch from configured GitHub sources
919
- console.log(chalk.bold('\nFetching skills from GitHub sources...\n'));
920
- const skills = await listMarketplaceSkills();
921
- if (skills.length === 0) {
922
- console.log(chalk.yellow('No skills found.'));
923
- return;
924
- }
925
- const bySource = new Map();
926
- for (const skill of skills) {
927
- const sourceId = skill.source.id;
928
- if (!bySource.has(sourceId)) {
929
- bySource.set(sourceId, []);
930
- }
931
- bySource.get(sourceId).push(skill);
932
- }
933
- for (const [sourceId, sourceSkills] of bySource) {
934
- const source = sourceSkills[0].source;
935
- console.log(chalk.bold.cyan(`\nšŸ“¦ ${source.name}`));
936
- console.log(chalk.gray(` ${source.owner}/${source.repo}`));
937
- if (source.verified) {
938
- console.log(chalk.green(' āœ“ Verified'));
939
- }
940
- console.log('');
941
- for (const skill of sourceSkills) {
942
- console.log(chalk.white(` ${skill.name}`));
943
- if (skill.description) {
944
- const desc = skill.description.length > 60
945
- ? skill.description.slice(0, 60) + '...'
946
- : skill.description;
947
- console.log(chalk.gray(` ${desc}`));
948
- }
949
- }
950
- }
951
- console.log(chalk.gray(`\nTotal: ${skills.length} skills from ${bySource.size} sources`));
952
- }
953
- else {
954
- // Database mode (primary): fetch from our API
955
- console.log(chalk.bold('\n🌐 Skills Marketplace\n'));
956
- const limit = parseInt(options.limit) || 50;
957
- const page = parseInt(options.page) || 1;
958
- let result;
959
- try {
960
- result = await fetchSkillsForCLI({ limit, page, sortBy: 'stars' });
961
- }
962
- catch {
963
- console.log(chalk.gray('Falling back to GitHub sources...'));
964
- const skills = await listMarketplaceSkills();
965
- result = { skills: skills.slice(0, limit), total: skills.length, hasNext: false };
966
- }
967
- console.log(chalk.gray(`Showing ${result.skills.length} of ${result.total.toLocaleString()} skills (page ${page})\n`));
968
- for (const skill of result.skills) {
969
- const stars = skill.stars ? chalk.yellow(`⭐${skill.stars.toLocaleString()}`) : '';
970
- console.log(chalk.white(` ${skill.name} ${stars}`));
971
- if (skill.description) {
972
- const desc = skill.description.length > 55
973
- ? skill.description.slice(0, 55) + '...'
974
- : skill.description;
975
- console.log(chalk.gray(` ${desc}`));
976
- }
977
- console.log(chalk.dim(` by ${skill.author || 'unknown'}`));
978
- }
979
- console.log(chalk.gray(`\nTotal: ${result.total.toLocaleString()} skills`));
980
- if (result.hasNext) {
981
- console.log(chalk.gray(`Next page: skills market-list --page ${page + 1}`));
982
- }
983
- }
984
- console.log(chalk.gray('\nUse: skills (interactive) to install\n'));
985
- }
986
- catch (error) {
987
- console.error(chalk.red('Error:'), error);
988
- process.exit(1);
989
- }
990
- });
991
- // Market search - search skills
992
- program
993
- .command('market-search <query>')
994
- .alias('ms')
995
- .description('Search skills in the marketplace')
996
- .option('-l, --limit <number>', 'Number of results', '20')
997
- .action(async (query, options) => {
998
- try {
999
- console.log(chalk.bold(`\nšŸ” Searching for "${query}"...\n`));
1000
- const limit = parseInt(options.limit) || 20;
1001
- let result = null;
1002
- // Try database first, fallback to GitHub
1003
- try {
1004
- result = await fetchSkillsForCLI({ search: query, limit, sortBy: 'stars' });
1005
- }
1006
- catch {
1007
- // Fallback to GitHub-based search
1008
- console.log(chalk.gray('Falling back to GitHub sources...'));
1009
- const skills = await searchSkills(query);
1010
- result = { skills: skills.slice(0, limit), total: skills.length };
1011
- }
1012
- if (!result || result.skills.length === 0) {
1013
- console.log(chalk.yellow(`No skills found matching "${query}"`));
1014
- return;
1015
- }
1016
- console.log(chalk.gray(`Found ${result.total.toLocaleString()} skills (showing top ${result.skills.length}):\n`));
1017
- for (const skill of result.skills) {
1018
- const stars = skill.stars ? chalk.yellow(`⭐${skill.stars.toLocaleString()}`) : '';
1019
- console.log(chalk.cyan(` ${skill.name} ${stars}`));
1020
- console.log(chalk.gray(` ${skill.description?.slice(0, 70)}${(skill.description?.length || 0) > 70 ? '...' : ''}`));
1021
- console.log(chalk.dim(` by ${skill.author || 'unknown'}`));
1022
- console.log('');
1023
- }
1024
- console.log(chalk.gray('Use: skills (interactive) to install\n'));
1025
- }
1026
- catch (error) {
1027
- console.error(chalk.red('Error searching skills:'), error);
1028
- process.exit(1);
1029
- }
1030
- });
1031
- // ============================================
1032
- // SEARCH COMMAND - Main user-facing search
1033
- // ============================================
1034
- program
1035
- .command('search [query...]')
1036
- .alias('s')
1037
- .description('Search and install skills from marketplace (67K+ skills)')
1038
- .option('-l, --limit <n>', 'Maximum results to show', '20')
1039
- .option('-s, --sort <by>', 'Sort by: stars, recent, name', 'stars')
1040
- .option('-i, --interactive', 'Launch interactive FZF-style search')
1041
- .option('--json', 'Output as JSON for scripting (no interactive prompt)')
1042
- .action(async (queryParts, options) => {
1043
- try {
1044
- // Interactive FZF mode
1045
- if (options.interactive || (queryParts.length === 0 && !options.json)) {
1046
- const selected = await fzfSearch(queryParts.join(' '));
1047
- if (selected) {
1048
- console.log(chalk.bold(`\nšŸ“¦ Selected: ${selected.name}\n`));
1049
- console.log(chalk.gray(`Scoped name: ${selected.scopedName}`));
1050
- console.log(chalk.gray(`Author: ${selected.author}`));
1051
- console.log(chalk.gray(`Stars: ${selected.stars.toLocaleString()}`));
1052
- if (selected.description) {
1053
- console.log(chalk.gray(`Description: ${selected.description}`));
1054
- }
1055
- console.log('');
1056
- // Ask if user wants to install
1057
- const { install } = await inquirer.prompt([{
1058
- type: 'confirm',
1059
- name: 'install',
1060
- message: `Install ${selected.name}?`,
1061
- default: true
1062
- }]);
1063
- if (install) {
1064
- const { execSync } = await import('child_process');
1065
- execSync(`"${process.argv[0]}" "${process.argv[1]}" install "${selected.scopedName}"`, { stdio: 'inherit' });
1066
- }
1067
- }
1068
- else {
1069
- console.log(chalk.yellow('\nSearch cancelled.\n'));
1070
- }
1071
- return;
1072
- }
1073
- const query = queryParts.join(' ');
1074
- if (!query) {
1075
- console.log(chalk.yellow('Please provide a search query or use -i for interactive mode.'));
1076
- return;
1077
- }
1078
- const limit = parseInt(options.limit) || 20;
1079
- if (!options.json) {
1080
- console.log(chalk.bold(`\nšŸ” Searching for "${query}"...\n`));
1081
- }
1082
- // Fetch results from database
1083
- let result;
1084
- try {
1085
- result = await fetchSkillsForCLI({
1086
- search: query,
1087
- limit,
1088
- sortBy: options.sort
1089
- });
1090
- }
1091
- catch {
1092
- // Fallback to GitHub sources
1093
- if (!options.json) {
1094
- console.log(chalk.gray('Falling back to GitHub sources...'));
1095
- }
1096
- const skills = await searchSkills(query);
1097
- result = { skills: skills.slice(0, limit), total: skills.length };
1098
- }
1099
- // Track search telemetry
1100
- trackSearch(query, result.total);
1101
- if (result.skills.length === 0) {
1102
- if (options.json) {
1103
- console.log(JSON.stringify({ skills: [], total: 0, query }));
1104
- }
1105
- else {
1106
- console.log(chalk.yellow(`No skills found matching "${query}"`));
1107
- }
1108
- return;
1109
- }
1110
- // JSON output (non-interactive)
1111
- if (options.json) {
1112
- console.log(JSON.stringify({
1113
- skills: result.skills.map(s => ({
1114
- name: s.name,
1115
- author: s.author,
1116
- scopedName: s.scopedName || `${s.author}/${s.name}`,
1117
- description: s.description,
1118
- stars: s.stars || 0,
1119
- githubUrl: s.githubUrl
1120
- })),
1121
- total: result.total,
1122
- query
1123
- }, null, 2));
1124
- return;
1125
- }
1126
- // Display results summary
1127
- console.log(chalk.gray(`Found ${result.total.toLocaleString()} skills. Select to install:\n`));
1128
- // Interactive install - always show selection prompt
1129
- const choices = result.skills.map((skill) => ({
1130
- name: `${skill.name} ${skill.stars ? chalk.yellow(`⭐${skill.stars.toLocaleString()}`) : ''} ${chalk.dim(`@${skill.author || 'unknown'}`)}`,
1131
- value: {
1132
- name: skill.name,
1133
- scopedName: skill.scopedName || `${skill.author}/${skill.name}`,
1134
- githubUrl: skill.githubUrl || ''
1135
- },
1136
- short: skill.name
1137
- }));
1138
- const { selectedSkills } = await inquirer.prompt([
1139
- {
1140
- type: 'checkbox',
1141
- name: 'selectedSkills',
1142
- message: 'Select skills (Space to select, Enter to confirm):',
1143
- choices,
1144
- pageSize: 15,
1145
- loop: false
1146
- }
1147
- ]);
1148
- if (selectedSkills.length === 0) {
1149
- console.log(chalk.yellow('\nNo skills selected.\n'));
1150
- return;
1151
- }
1152
- // Select platforms
1153
- const agentChoices = Object.entries(AGENTS).map(([key, config]) => ({
1154
- name: config.displayName,
1155
- value: key,
1156
- checked: ['cursor', 'claude', 'copilot', 'antigravity'].includes(key)
1157
- }));
1158
- const { platforms } = await inquirer.prompt([
1159
- {
1160
- type: 'checkbox',
1161
- name: 'platforms',
1162
- message: 'Install to which platforms?',
1163
- choices: agentChoices,
1164
- pageSize: 10
1165
- }
1166
- ]);
1167
- if (platforms.length === 0) {
1168
- console.log(chalk.yellow('\nNo platforms selected.\n'));
1169
- return;
1170
- }
1171
- // Install skills
1172
- const { mkdir, cp, rm } = await import('fs/promises');
1173
- const { join } = await import('path');
1174
- const { tmpdir } = await import('os');
1175
- const { exec } = await import('child_process');
1176
- const { promisify } = await import('util');
1177
- const execAsync = promisify(exec);
1178
- console.log(chalk.bold(`\nšŸ“¦ Installing ${selectedSkills.length} skill(s)...\n`));
1179
- for (const skill of selectedSkills) {
1180
- const installSpinner = ora(`Installing ${skill.name}...`).start();
1181
- try {
1182
- // Fetch skill details
1183
- const { getSkillByScoped } = await import('../core/skillsdb.js');
1184
- const dbSkill = await getSkillByScoped(skill.scopedName);
1185
- if (!dbSkill) {
1186
- installSpinner.fail(`${skill.name}: Not found`);
1187
- continue;
1188
- }
1189
- const githubUrl = dbSkill.github_url || dbSkill.githubUrl;
1190
- if (!githubUrl) {
1191
- installSpinner.fail(`${skill.name}: No GitHub URL`);
1192
- continue;
1193
- }
1194
- // Parse GitHub URL
1195
- const urlMatch = githubUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/);
1196
- if (!urlMatch) {
1197
- installSpinner.fail(`${skill.name}: Invalid GitHub URL`);
1198
- continue;
1199
- }
1200
- const [, owner, repo] = urlMatch;
1201
- const branch = dbSkill.branch || 'main';
1202
- const skillPath = (dbSkill.path || '').replace(/\/SKILL\.md$/i, '');
1203
- // Clone to temp
1204
- const tempDir = join(tmpdir(), `skill-${Date.now()}-${Math.random().toString(36).slice(2)}`);
1205
- await mkdir(tempDir, { recursive: true });
1206
- try {
1207
- await execAsync(`git clone --depth 1 --branch ${branch} https://github.com/${owner}/${repo}.git .`, { cwd: tempDir });
1208
- // Copy to each platform
1209
- for (const platform of platforms) {
1210
- const agentConfig = AGENTS[platform];
1211
- if (!agentConfig)
1212
- continue;
1213
- const targetDir = agentConfig.projectDir;
1214
- const skillDir = join(process.cwd(), targetDir, dbSkill.name);
1215
- await mkdir(skillDir, { recursive: true });
1216
- const sourceDir = skillPath ? join(tempDir, skillPath) : tempDir;
1217
- await cp(sourceDir, skillDir, { recursive: true });
1218
- }
1219
- installSpinner.succeed(`${skill.name} → ${platforms.join(', ')}`);
1220
- }
1221
- finally {
1222
- await rm(tempDir, { recursive: true, force: true }).catch(() => { });
1223
- }
1224
- }
1225
- catch (err) {
1226
- installSpinner.fail(`${skill.name}: ${err.message || err}`);
1227
- }
1228
- }
1229
- console.log(chalk.bold.green(`\n✨ Installation complete!\n`));
1230
- }
1231
- catch (error) {
1232
- console.error(chalk.red('Error searching:'), error);
1233
- process.exit(1);
1234
- }
1235
- });
1236
- // Install - Install a skill by scoped name (e.g., @author/skill or author/skill)
1237
- program
1238
- .command('install <scoped-name> [platforms...]')
1239
- .alias('i')
1240
- .description('Install a skill by @author/name or just name')
1241
- .option('-g, --global', 'Install skill globally (user-level) instead of project-level')
1242
- .option('-l, --list', 'Show skill details without installing')
1243
- .option('-p, --platform <platforms>', 'Target platforms (comma-separated): cursor,claude,copilot,codex,antigravity,opencode,amp,kilo,roo,goose')
1244
- .option('-t, --target <platforms>', 'Target platforms (alias for --platform)')
1245
- .option('--all', 'Install to all platforms')
1246
- .action(async (scopedName, platformsArg, options) => {
1247
- try {
1248
- const { parseScopedName, getSkillByScoped, fetchFromDB } = await import('../core/skillsdb.js');
1249
- const { mkdir, writeFile, cp } = await import('fs/promises');
1250
- const { existsSync } = await import('fs');
1251
- const { join } = await import('path');
1252
- const { tmpdir, homedir } = await import('os');
1253
- const { exec } = await import('child_process');
1254
- const { promisify } = await import('util');
1255
- const execAsync = promisify(exec);
1256
- const { author, name } = parseScopedName(scopedName);
1257
- console.log(chalk.bold(`\nšŸ“¦ Searching for "${scopedName}"...\n`));
1258
- // Try our database first
1259
- let skill;
1260
- try {
1261
- skill = await getSkillByScoped(scopedName);
1262
- }
1263
- catch {
1264
- // Fallback to GitHub sources if our API is down
1265
- console.log(chalk.gray('Falling back to GitHub sources...'));
1266
- const skills = await searchSkills(name);
1267
- skill = skills.find((s) => s.name.toLowerCase() === name.toLowerCase() &&
1268
- (!author || s.author?.toLowerCase() === author.toLowerCase())) || skills[0];
1269
- }
1270
- if (!skill) {
1271
- console.log(chalk.yellow(`No skill found matching "${scopedName}"`));
1272
- console.log(chalk.gray('Try: skills market-search <query> to find skills\n'));
1273
- return;
1274
- }
1275
- const githubUrl = skill.github_url || skill.githubUrl;
1276
- if (!githubUrl) {
1277
- console.log(chalk.red('Could not find GitHub URL for this skill'));
1278
- return;
1279
- }
1280
- console.log(chalk.gray(`Found: ${skill.name} by ${skill.author}`));
1281
- console.log(chalk.gray(`Stars: ${skill.stars?.toLocaleString() || 0}`));
1282
- console.log(chalk.gray(`URL: ${githubUrl}`));
1283
- if (skill.description) {
1284
- console.log(chalk.gray(`Description: ${skill.description}`));
1285
- }
1286
- console.log('');
1287
- // If --list flag, just show details and exit
1288
- if (options.list) {
1289
- console.log(chalk.cyan('Use without --list to install this skill.\n'));
1290
- return;
1291
- }
1292
- // Determine target platforms (priority: --all > positional args > -t/-p > auto-detect)
1293
- let platforms = [];
1294
- if (options.all) {
1295
- platforms = Object.keys(AGENTS);
1296
- }
1297
- else if (platformsArg && platformsArg.length > 0) {
1298
- // Positional arguments like: skills install @author/skill claude cursor
1299
- platforms = platformsArg.map((p) => p.trim().toLowerCase());
1300
- }
1301
- else if (options.target) {
1302
- // -t or --target option
1303
- platforms = options.target.split(',').map((p) => p.trim().toLowerCase());
1304
- }
1305
- else if (options.platform) {
1306
- // -p or --platform option
1307
- platforms = options.platform.split(',').map((p) => p.trim().toLowerCase());
1308
- }
1309
- else {
1310
- // Auto-detect platforms in current directory
1311
- const cwd = process.cwd();
1312
- if (existsSync(join(cwd, '.cursor')))
1313
- platforms.push('cursor');
1314
- if (existsSync(join(cwd, '.claude')))
1315
- platforms.push('claude');
1316
- if (existsSync(join(cwd, '.github')))
1317
- platforms.push('copilot');
1318
- if (existsSync(join(cwd, '.codex')))
1319
- platforms.push('codex');
1320
- if (existsSync(join(cwd, '.agent')))
1321
- platforms.push('antigravity');
1322
- // If none detected, prompt user
1323
- if (platforms.length === 0) {
1324
- const { selectedPlatforms } = await inquirer.prompt([{
1325
- type: 'checkbox',
1326
- name: 'selectedPlatforms',
1327
- message: 'Select target platforms:',
1328
- choices: [
1329
- { name: 'Cursor', value: 'cursor', checked: true },
1330
- { name: 'Claude Code', value: 'claude', checked: true },
1331
- { name: 'GitHub Copilot', value: 'copilot' },
1332
- { name: 'OpenAI Codex', value: 'codex' },
1333
- { name: 'Antigravity', value: 'antigravity' }
1334
- ]
1335
- }]);
1336
- platforms = selectedPlatforms;
1337
- }
1338
- }
1339
- if (platforms.length === 0) {
1340
- console.log(chalk.yellow('No platforms selected. Exiting.'));
1341
- return;
1342
- }
1343
- console.log(chalk.gray(`Installing to: ${platforms.join(', ')}${options.global ? ' (global)' : ''}\n`));
1344
- // Use centralized AGENTS config with global support
1345
- const isGlobal = !!options.global;
1346
- // Download skill to temp directory
1347
- const tempDir = join(tmpdir(), `skill-${Date.now()}`);
1348
- await mkdir(tempDir, { recursive: true });
1349
- try {
1350
- // Clone skill from GitHub
1351
- const spinner = ora(`Downloading ${skill.name}...`).start();
1352
- // Parse GitHub URL to get repo info
1353
- const urlMatch = githubUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/);
1354
- if (!urlMatch) {
1355
- spinner.fail('Invalid GitHub URL');
1356
- return;
1357
- }
1358
- const [, owner, repo] = urlMatch;
1359
- const branch = skill.branch || 'main';
1360
- const skillPath = skill.path?.replace(/\/SKILL\.md$/i, '') || '';
1361
- // Clone repo
1362
- await execAsync(`git clone --depth 1 --branch ${branch} https://github.com/${owner}/${repo}.git .`, { cwd: tempDir });
1363
- spinner.succeed(`Downloaded ${skill.name}`);
1364
- // Install to each platform
1365
- for (const platform of platforms) {
1366
- const platformSpinner = ora(`Installing to ${platform}...`).start();
1367
- const agentConfig = AGENTS[platform];
1368
- if (!agentConfig) {
1369
- platformSpinner.fail(`Unknown platform: ${platform}`);
1370
- continue;
1371
- }
1372
- const targetDir = isGlobal ? agentConfig.globalDir : agentConfig.projectDir;
1373
- const skillDir = isGlobal ? join(targetDir, skill.name) : join(process.cwd(), targetDir, skill.name);
1374
- await mkdir(skillDir, { recursive: true });
1375
- // Copy skill files
1376
- const sourceDir = skillPath ? join(tempDir, skillPath) : tempDir;
1377
- if (platform === 'antigravity') {
1378
- // Antigravity uses .agent/skills/<skill-name>/
1379
- // Copy all files including subdirectories (references, scripts, etc.)
1380
- await cp(sourceDir, skillDir, { recursive: true });
1381
- // Also create a flat .md file for quick access if SKILL.md exists
1382
- const skillMdPath = join(sourceDir, 'SKILL.md');
1383
- if (existsSync(skillMdPath)) {
1384
- const { readFile } = await import('fs/promises');
1385
- const content = await readFile(skillMdPath, 'utf-8');
1386
- const flatMdDir = isGlobal ? targetDir : join(process.cwd(), targetDir);
1387
- await writeFile(join(flatMdDir, `${skill.name}.md`), content);
1388
- }
1389
- }
1390
- else {
1391
- // Other platforms use folder structure
1392
- await cp(sourceDir, skillDir, { recursive: true });
1393
- }
1394
- platformSpinner.succeed(`Installed to ${targetDir}/${skill.name}`);
1395
- }
1396
- }
1397
- finally {
1398
- // Cleanup temp directory
1399
- const { rm } = await import('fs/promises');
1400
- await rm(tempDir, { recursive: true, force: true }).catch(() => { });
1401
- }
1402
- // Track installation in lock file
1403
- const canonicalPath = getCanonicalPath(skill.name, { global: isGlobal, cwd: process.cwd() });
1404
- const lockEntry = createLockEntry({
1405
- name: skill.name,
1406
- scopedName: `@${skill.author}/${skill.name}`,
1407
- source: githubUrl,
1408
- sourceType: 'database',
1409
- agents: platforms,
1410
- canonicalPath,
1411
- isGlobal,
1412
- projectDir: isGlobal ? undefined : process.cwd()
1413
- });
1414
- await addSkillToLock(lockEntry);
1415
- console.log(chalk.bold.green(`\n✨ Successfully installed: ${skill.name}`));
1416
- console.log(chalk.gray(` Scoped name: @${skill.author}/${skill.name}`));
1417
- console.log(chalk.gray(` Platforms: ${platforms.join(', ')}\n`));
1418
- }
1419
- catch (error) {
1420
- console.error(chalk.red('Error installing skill:'), error.message || error);
1421
- process.exit(1);
1422
- }
1423
- });
1424
- // Add - Install skills directly from Git repository URLs
1425
- program
1426
- .command('add <source>')
1427
- .description('Install skills from a Git repo (e.g., owner/repo, owner/repo@skill-name)')
1428
- .option('-g, --global', 'Install skill globally (user-level) instead of project-level')
1429
- .option('-l, --list', 'List available skills in the repository without installing')
1430
- .option('-s, --skill <skills...>', 'Specify skill names to install')
1431
- .option('-a, --agent <agents...>', 'Specify agents to install to')
1432
- .option('-y, --yes', 'Skip confirmation prompts')
1433
- .action(async (source, options) => {
1434
- try {
1435
- const { mkdir, cp, rm, readdir, readFile } = await import('fs/promises');
1436
- const { existsSync, statSync } = await import('fs');
1437
- const { join, basename, dirname } = await import('path');
1438
- const { tmpdir } = await import('os');
1439
- const { exec } = await import('child_process');
1440
- const { promisify } = await import('util');
1441
- const execAsync = promisify(exec);
1442
- // Parse source URL with @skill-name support
1443
- function parseSource(input) {
1444
- // Check for @skill-name suffix: owner/repo@skill-name
1445
- let targetSkill;
1446
- const atMatch = input.match(/^(.+)@([a-zA-Z0-9_-]+)$/);
1447
- if (atMatch && !input.includes('github.com') && !input.includes('gitlab.com')) {
1448
- input = atMatch[1];
1449
- targetSkill = atMatch[2];
1450
- }
1451
- // GitHub URL with path: github.com/owner/repo/tree/branch/path
1452
- const githubTreeMatch = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
1453
- if (githubTreeMatch) {
1454
- const [, owner, repo, , subpath] = githubTreeMatch;
1455
- return { type: 'github', url: `https://github.com/${owner}/${repo}.git`, subpath, targetSkill };
1456
- }
1457
- // GitHub URL: github.com/owner/repo
1458
- const githubRepoMatch = input.match(/github\.com\/([^/]+)\/([^/]+)/);
1459
- if (githubRepoMatch) {
1460
- const [, owner, repo] = githubRepoMatch;
1461
- const cleanRepo = repo.replace(/\.git$/, '');
1462
- return { type: 'github', url: `https://github.com/${owner}/${cleanRepo}.git`, targetSkill };
1463
- }
1464
- // GitLab URL: gitlab.com/owner/repo
1465
- const gitlabMatch = input.match(/gitlab\.com\/([^/]+)\/([^/]+)/);
1466
- if (gitlabMatch) {
1467
- const [, owner, repo] = gitlabMatch;
1468
- const cleanRepo = repo.replace(/\.git$/, '');
1469
- return { type: 'gitlab', url: `https://gitlab.com/${owner}/${cleanRepo}.git`, targetSkill };
1470
- }
1471
- // GitHub shorthand: owner/repo or owner/repo/path
1472
- const shorthandMatch = input.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
1473
- if (shorthandMatch && !input.includes(':')) {
1474
- const [, owner, repo, subpath] = shorthandMatch;
1475
- return { type: 'github', url: `https://github.com/${owner}/${repo}.git`, subpath, targetSkill };
1476
- }
1477
- // Fallback: treat as direct git URL
1478
- return { type: 'git', url: input, targetSkill };
1479
- }
1480
- // Discover skills in a directory
1481
- async function discoverSkillsInDir(dir, subpath) {
1482
- const skills = [];
1483
- const searchPath = subpath ? join(dir, subpath) : dir;
1484
- const searchDirs = [
1485
- searchPath,
1486
- join(searchPath, 'skills'),
1487
- join(searchPath, '.claude/skills'),
1488
- join(searchPath, '.cursor/skills'),
1489
- join(searchPath, '.agent/skills'),
1490
- join(searchPath, '.codex/skills'),
1491
- join(searchPath, '.opencode/skill'),
1492
- ];
1493
- for (const searchDir of searchDirs) {
1494
- if (!existsSync(searchDir))
1495
- continue;
1496
- try {
1497
- const entries = await readdir(searchDir, { withFileTypes: true });
1498
- for (const entry of entries) {
1499
- if (entry.isDirectory()) {
1500
- const skillMdPath = join(searchDir, entry.name, 'SKILL.md');
1501
- if (existsSync(skillMdPath)) {
1502
- try {
1503
- const content = await readFile(skillMdPath, 'utf-8');
1504
- const nameMatch = content.match(/^name:\s*(.+)$/m);
1505
- const descMatch = content.match(/^description:\s*(.+)$/m);
1506
- skills.push({
1507
- name: nameMatch ? nameMatch[1].trim() : entry.name,
1508
- description: descMatch ? descMatch[1].trim() : '',
1509
- path: join(searchDir, entry.name),
1510
- });
1511
- }
1512
- catch { }
1513
- }
1514
- }
1515
- }
1516
- }
1517
- catch { }
1518
- }
1519
- return skills;
1520
- }
1521
- console.log(chalk.bold('\nšŸ“¦ add-skill\n'));
1522
- const parsed = parseSource(source);
1523
- console.log(chalk.gray(`Source: ${parsed.url}${parsed.subpath ? ` (${parsed.subpath})` : ''}`));
1524
- // Clone repository
1525
- const tempDir = join(tmpdir(), `add-skill-${Date.now()}`);
1526
- await mkdir(tempDir, { recursive: true });
1527
- const spinner = ora('Cloning repository...').start();
1528
- try {
1529
- await execAsync(`git clone --depth 1 ${parsed.url} .`, { cwd: tempDir });
1530
- spinner.succeed('Repository cloned');
1531
- }
1532
- catch (err) {
1533
- spinner.fail('Failed to clone repository');
1534
- console.error(chalk.red(err.message || err));
1535
- await rm(tempDir, { recursive: true, force: true }).catch(() => { });
1536
- return;
1537
- }
1538
- // Discover skills
1539
- const discoverSpinner = ora('Discovering skills...').start();
1540
- const skills = await discoverSkillsInDir(tempDir, parsed.subpath);
1541
- if (skills.length === 0) {
1542
- discoverSpinner.fail('No skills found');
1543
- console.log(chalk.yellow('\nNo valid skills found. Skills require a SKILL.md with name and description.'));
1544
- await rm(tempDir, { recursive: true, force: true }).catch(() => { });
1545
- return;
1546
- }
1547
- discoverSpinner.succeed(`Found ${skills.length} skill${skills.length > 1 ? 's' : ''}`);
1548
- // If --list, just show skills and exit
1549
- if (options.list) {
1550
- console.log(chalk.bold('\nAvailable Skills:'));
1551
- for (const skill of skills) {
1552
- console.log(chalk.cyan(` ${skill.name}`));
1553
- if (skill.description) {
1554
- console.log(chalk.gray(` ${skill.description}`));
1555
- }
1556
- }
1557
- console.log(chalk.gray('\nUse --skill <name> to install specific skills\n'));
1558
- await rm(tempDir, { recursive: true, force: true }).catch(() => { });
1559
- return;
1560
- }
1561
- // Select skills to install
1562
- let selectedSkills = skills;
1563
- // If @skill-name was specified in the source URL, auto-select that skill
1564
- if (parsed.targetSkill) {
1565
- selectedSkills = skills.filter(s => s.name.toLowerCase() === parsed.targetSkill.toLowerCase());
1566
- if (selectedSkills.length === 0) {
1567
- console.log(chalk.yellow(`Skill "${parsed.targetSkill}" not found in repository.`));
1568
- console.log(chalk.gray('Available skills:'));
1569
- for (const skill of skills) {
1570
- console.log(chalk.cyan(` ${skill.name}`));
1571
- }
1572
- await rm(tempDir, { recursive: true, force: true }).catch(() => { });
1573
- return;
1574
- }
1575
- console.log(chalk.gray(`Auto-selected: ${parsed.targetSkill}`));
1576
- }
1577
- else if (options.skill && options.skill.length > 0) {
1578
- selectedSkills = skills.filter(s => options.skill.some((name) => s.name.toLowerCase() === name.toLowerCase()));
1579
- if (selectedSkills.length === 0) {
1580
- console.log(chalk.yellow(`No matching skills found for: ${options.skill.join(', ')}`));
1581
- await rm(tempDir, { recursive: true, force: true }).catch(() => { });
1582
- return;
1583
- }
1584
- }
1585
- else if (!options.yes && skills.length > 1) {
1586
- // Interactive selection
1587
- const { selected } = await inquirer.prompt([{
1588
- type: 'checkbox',
1589
- name: 'selected',
1590
- message: 'Select skills to install:',
1591
- choices: skills.map(s => ({ name: `${s.name}${s.description ? ` - ${s.description.slice(0, 50)}` : ''}`, value: s, checked: true })),
1592
- }]);
1593
- selectedSkills = selected;
1594
- }
1595
- if (selectedSkills.length === 0) {
1596
- console.log(chalk.yellow('No skills selected.'));
1597
- await rm(tempDir, { recursive: true, force: true }).catch(() => { });
1598
- return;
1599
- }
1600
- // Select agents
1601
- let targetAgents = [];
1602
- if (options.agent && options.agent.length > 0) {
1603
- targetAgents = options.agent;
1604
- }
1605
- else if (options.yes) {
1606
- targetAgents = Object.keys(AGENTS);
1607
- }
1608
- else {
1609
- const { agents } = await inquirer.prompt([{
1610
- type: 'checkbox',
1611
- name: 'agents',
1612
- message: 'Select agents to install to:',
1613
- choices: Object.entries(AGENTS).map(([key, config]) => ({
1614
- name: config.displayName,
1615
- value: key,
1616
- checked: ['cursor', 'claude', 'antigravity'].includes(key),
1617
- })),
1618
- }]);
1619
- targetAgents = agents;
1620
- }
1621
- if (targetAgents.length === 0) {
1622
- console.log(chalk.yellow('No agents selected.'));
1623
- await rm(tempDir, { recursive: true, force: true }).catch(() => { });
1624
- return;
1625
- }
1626
- const isGlobal = !!options.global;
1627
- // Install skills
1628
- console.log(chalk.bold('\nInstalling...\n'));
1629
- for (const skill of selectedSkills) {
1630
- for (const agent of targetAgents) {
1631
- const agentConfig = AGENTS[agent];
1632
- if (!agentConfig)
1633
- continue;
1634
- const targetDir = isGlobal ? agentConfig.globalDir : agentConfig.projectDir;
1635
- const skillDir = isGlobal ? join(targetDir, skill.name) : join(process.cwd(), targetDir, skill.name);
1636
- await mkdir(skillDir, { recursive: true });
1637
- await cp(skill.path, skillDir, { recursive: true });
1638
- console.log(chalk.green(`āœ” ${skill.name} → ${agentConfig.displayName}`));
1639
- console.log(chalk.gray(` ${isGlobal ? skillDir : targetDir + '/' + skill.name}`));
1640
- }
1641
- // Track in lock file
1642
- const canonicalPath = getCanonicalPath(skill.name, { global: isGlobal, cwd: process.cwd() });
1643
- const lockEntry = createLockEntry({
1644
- name: skill.name,
1645
- scopedName: skill.name, // From repo, no author info
1646
- source: parsed.url,
1647
- sourceType: parsed.type === 'github' ? 'github' : parsed.type === 'gitlab' ? 'gitlab' : 'local',
1648
- agents: targetAgents,
1649
- canonicalPath,
1650
- isGlobal,
1651
- projectDir: isGlobal ? undefined : process.cwd()
1652
- });
1653
- await addSkillToLock(lockEntry);
1654
- }
1655
- console.log(chalk.bold.green(`\n✨ Successfully installed ${selectedSkills.length} skill(s)\n`));
1656
- // Cleanup
1657
- await rm(tempDir, { recursive: true, force: true }).catch(() => { });
1658
- }
1659
- catch (error) {
1660
- console.error(chalk.red('Error:'), error.message || error);
1661
- process.exit(1);
1662
- }
1663
- });
1664
- // Alias for backward compatibility
1665
- program
1666
- .command('market-install <name>')
1667
- .alias('mi')
1668
- .description('Install a skill (alias for: skills install)')
1669
- .action(async (name) => {
1670
- console.log(chalk.gray('Tip: Use `skills install <id-or-name>` directly\n'));
1671
- const { execSync } = await import('child_process');
1672
- try {
1673
- execSync(`"${process.argv[0]}" "${process.argv[1]}" install "${name}"`, { stdio: 'inherit' });
1674
- }
1675
- catch { }
1676
- });
1677
- // Install from URL - install directly from GitHub or SkillsMP URL
1678
- program
1679
- .command('install-url <url>')
1680
- .alias('iu')
1681
- .description('Install a skill from GitHub URL or SkillsMP page URL')
1682
- .action(async (url) => {
1683
- try {
1684
- let githubUrl = url;
1685
- // Convert SkillsMP URL to GitHub URL
1686
- // Format: https://skillsmp.com/skills/<id>
1687
- if (url.includes('skillsmp.com/skills/')) {
1688
- console.log(chalk.bold(`\nšŸ“¦ Fetching skill info from SkillsMP...`));
1689
- // Extract skill ID from URL
1690
- const skillId = url.split('/skills/').pop()?.replace(/\/$/, '');
1691
- // Fetch skill details from API
1692
- const response = await fetch(`https://skillsmp.com/api/skills/${skillId}`);
1693
- if (!response.ok) {
1694
- throw new Error('Could not find skill on SkillsMP');
1695
- }
1696
- const data = await response.json();
1697
- githubUrl = data.skill.githubUrl;
1698
- console.log(chalk.gray(`Found: ${data.skill.name} by ${data.skill.author}\n`));
1699
- }
1700
- // Validate GitHub URL
1701
- if (!githubUrl.includes('github.com')) {
1702
- console.log(chalk.red('Invalid URL. Please provide a GitHub URL or SkillsMP skill page URL.'));
1703
- return;
1704
- }
1705
- console.log(chalk.gray(`Installing from: ${githubUrl}\n`));
1706
- const homedir = (await import('os')).homedir();
1707
- const skillsDir = `${homedir}/.antigravity/skills`;
1708
- const installed = await installFromGitHubUrl(githubUrl, skillsDir);
1709
- console.log(chalk.green(`āœ“ Successfully installed: ${installed.name}`));
1710
- console.log(chalk.gray(` Path: ${installed.path}`));
1711
- console.log('');
1712
- }
1713
- catch (error) {
1714
- console.error(chalk.red('Error installing skill:'), error.message || error);
1715
- process.exit(1);
1716
- }
1717
- });
1718
- // Market uninstall - remove an installed skill
1719
- program
1720
- .command('market-uninstall <name>')
1721
- .alias('mu')
1722
- .description('Uninstall a marketplace-installed skill')
1723
- .action(async (name) => {
1724
- try {
1725
- await uninstallSkill(name);
1726
- console.log(chalk.green(`āœ“ Uninstalled: ${name}`));
1727
- }
1728
- catch (error) {
1729
- console.error(chalk.red('Error uninstalling skill:'), error);
1730
- process.exit(1);
1731
- }
1732
- });
1733
- // Market installed - show installed marketplace skills
1734
- program
1735
- .command('market-installed')
1736
- .alias('mind')
1737
- .description('List skills installed from marketplaces')
1738
- .action(async () => {
1739
- try {
1740
- const installed = await getInstalledSkills();
1741
- if (installed.length === 0) {
1742
- console.log(chalk.yellow('\nNo marketplace skills installed.'));
1743
- console.log(chalk.gray('Use: skills market-install <name> to install\n'));
1744
- return;
1745
- }
1746
- console.log(chalk.bold(`\nInstalled marketplace skills:\n`));
1747
- for (const skill of installed) {
1748
- console.log(chalk.cyan(` ${skill.name}`));
1749
- console.log(chalk.gray(` Path: ${skill.localPath}`));
1750
- if (skill.source) {
1751
- console.log(chalk.gray(` Source: ${skill.source.name}`));
1752
- }
1753
- if (skill.version) {
1754
- console.log(chalk.gray(` Version: ${skill.version}`));
1755
- }
1756
- console.log(chalk.gray(` Installed: ${skill.installedAt}`));
1757
- console.log('');
1758
- }
1759
- }
1760
- catch (error) {
1761
- console.error(chalk.red('Error listing installed skills:'), error);
1762
- process.exit(1);
1763
- }
1764
- });
1765
- // Market sources - list marketplace sources
1766
- program
1767
- .command('market-sources')
1768
- .description('List registered marketplace sources')
1769
- .action(async () => {
1770
- try {
1771
- // Show SkillsMP as primary
1772
- console.log(chalk.bold('\n🌐 Primary Marketplace:\n'));
1773
- console.log(chalk.cyan(` SkillsMP`) + chalk.green(' āœ“'));
1774
- console.log(chalk.gray(` URL: https://skillsmp.com`));
1775
- console.log(chalk.gray(` Skills: 40,000+`));
1776
- console.log(chalk.gray(` The largest Agent Skills marketplace`));
1777
- console.log('');
1778
- // Show legacy sources
1779
- const sources = await listMarketplaces();
1780
- if (sources.length > 0) {
1781
- console.log(chalk.bold('Legacy GitHub Sources:\n'));
1782
- for (const source of sources) {
1783
- const verified = source.verified ? chalk.green(' āœ“') : '';
1784
- console.log(chalk.cyan(` ${source.name}${verified}`));
1785
- console.log(chalk.gray(` ID: ${source.id}`));
1786
- console.log(chalk.gray(` Repo: ${source.owner}/${source.repo}`));
1787
- if (source.description) {
1788
- console.log(chalk.gray(` ${source.description}`));
1789
- }
1790
- console.log('');
1791
- }
1792
- }
1793
- }
1794
- catch (error) {
1795
- console.error(chalk.red('Error listing sources:'), error);
1796
- process.exit(1);
1797
- }
1798
- });
1799
- // Market add-source - add a new marketplace
1800
- program
1801
- .command('market-add-source')
1802
- .description('Add a custom marketplace source')
1803
- .requiredOption('--id <id>', 'Unique identifier')
1804
- .requiredOption('--name <name>', 'Display name')
1805
- .requiredOption('--owner <owner>', 'GitHub owner')
1806
- .requiredOption('--repo <repo>', 'GitHub repository')
1807
- .option('--branch <branch>', 'Branch name', 'main')
1808
- .option('--path <path>', 'Path to skills directory', 'skills')
1809
- .action(async (options) => {
1810
- try {
1811
- await addMarketplace({
1812
- id: options.id,
1813
- name: options.name,
1814
- owner: options.owner,
1815
- repo: options.repo,
1816
- branch: options.branch,
1817
- skillsPath: options.path,
1818
- verified: false
1819
- });
1820
- console.log(chalk.green(`āœ“ Added marketplace: ${options.name}`));
1821
- }
1822
- catch (error) {
1823
- console.error(chalk.red('Error adding marketplace:'), error);
1824
- process.exit(1);
1825
- }
1826
- });
1827
- // Market update-check - check for updates
1828
- program
1829
- .command('market-update-check')
1830
- .alias('muc')
1831
- .description('Check for updates to installed skills')
1832
- .action(async () => {
1833
- try {
1834
- console.log(chalk.bold('\nChecking for updates...\n'));
1835
- const updates = await checkUpdates();
1836
- if (updates.length === 0) {
1837
- console.log(chalk.yellow('No installed marketplace skills to check.'));
1838
- return;
1839
- }
1840
- const hasUpdates = updates.filter(u => u.hasUpdate);
1841
- if (hasUpdates.length === 0) {
1842
- console.log(chalk.green('All skills are up to date! āœ“'));
1843
- }
1844
- else {
1845
- console.log(chalk.yellow(`${hasUpdates.length} skill(s) have updates available:\n`));
1846
- for (const update of hasUpdates) {
1847
- console.log(chalk.cyan(` ${update.skill.name}`));
1848
- console.log(chalk.gray(` Current: ${update.currentVersion || 'unknown'}`));
1849
- console.log(chalk.green(` Latest: ${update.latestVersion}`));
1850
- console.log('');
1851
- }
1852
- console.log(chalk.gray('To update, uninstall and reinstall the skill.'));
1853
- }
1854
- }
1855
- catch (error) {
1856
- console.error(chalk.red('Error checking updates:'), error);
1857
- process.exit(1);
1858
- }
1859
- });
1860
- // ============================================
1861
- // WORKFLOW SYNC COMMAND
1862
- // ============================================
1863
- // Sync - copy skills to .agent/workflows for Antigravity auto-discovery
1864
- program
1865
- .command('sync')
1866
- .description('Sync skills to .agent/workflows/ for Antigravity auto-discovery')
1867
- .option('-d, --directory <dir>', 'Target project directory', '.')
1868
- .option('-a, --all', 'Sync all discovered skills')
1869
- .option('-n, --name <name>', 'Sync a specific skill by name')
1870
- .action(async (options) => {
1871
- try {
1872
- const { mkdir, writeFile, readFile, cp } = await import('fs/promises');
1873
- const { join } = await import('path');
1874
- const { existsSync } = await import('fs');
1875
- const workflowsDir = join(options.directory, '.agent', 'workflows');
1876
- await mkdir(workflowsDir, { recursive: true });
1877
- const skills = await discoverSkills();
1878
- if (skills.length === 0) {
1879
- console.log(chalk.yellow('No skills found to sync.'));
1880
- return;
1881
- }
1882
- // Filter skills if specific name provided
1883
- const toSync = options.name
1884
- ? skills.filter(s => s.name === options.name)
1885
- : options.all
1886
- ? skills
1887
- : skills; // Default: sync all
1888
- if (toSync.length === 0) {
1889
- console.log(chalk.yellow(`Skill not found: ${options.name}`));
1890
- return;
1891
- }
1892
- console.log(chalk.bold(`\nSyncing ${toSync.length} skill(s) to ${workflowsDir}...\n`));
1893
- for (const skillRef of toSync) {
1894
- try {
1895
- const skill = await loadSkill(skillRef.path);
1896
- if (!skill)
1897
- continue;
1898
- // Create workflow file from skill
1899
- const workflowContent = `---
1900
- description: ${skill.metadata.description.slice(0, 100)}
1901
- ---
1902
-
1903
- ${skill.body}
1904
- `;
1905
- const workflowPath = join(workflowsDir, `${skill.metadata.name}.md`);
1906
- await writeFile(workflowPath, workflowContent);
1907
- console.log(chalk.green(` āœ“ ${skill.metadata.name}`));
1908
- console.log(chalk.gray(` → ${workflowPath}`));
1909
- }
1910
- catch (err) {
1911
- console.log(chalk.red(` āœ— ${skillRef.name}: ${err}`));
1912
- }
1913
- }
1914
- console.log(chalk.bold.green(`\nāœ“ Skills synced to .agent/workflows/`));
1915
- console.log(chalk.gray(`\nNow you can use: "/${toSync.map(s => s.name).join('", "/')}"`));
1916
- console.log(chalk.gray('Or just say: "Use the [skill-name] skill to..."'));
1917
- }
1918
- catch (error) {
1919
- console.error(chalk.red('Error syncing skills:'), error);
1920
- process.exit(1);
1921
- }
1922
- });
1923
- // Export - convert skills to different AI agent formats
1924
- program
1925
- .command('export')
1926
- .description('Export skills to different AI agent formats (Copilot, Cursor, Claude, Codex)')
1927
- .option('-t, --target <agent>', 'Target agent: copilot, cursor, claude, codex, antigravity, all', 'all')
1928
- .option('-d, --directory <dir>', 'Project directory', '.')
1929
- .option('-n, --name <name>', 'Export specific skill only')
1930
- .action(async (options) => {
1931
- try {
1932
- const { mkdir, writeFile, appendFile } = await import('fs/promises');
1933
- const { join } = await import('path');
1934
- const { existsSync } = await import('fs');
1935
- const skills = await discoverSkills();
1936
- const toExport = options.name
1937
- ? skills.filter(s => s.name === options.name)
1938
- : skills;
1939
- if (toExport.length === 0) {
1940
- console.log(chalk.yellow('No skills found to export.'));
1941
- return;
1942
- }
1943
- const targets = options.target === 'all'
1944
- ? ['copilot', 'cursor', 'claude', 'codex', 'antigravity']
1945
- : [options.target];
1946
- console.log(chalk.bold(`\nExporting ${toExport.length} skill(s) to: ${targets.join(', ')}\n`));
1947
- for (const target of targets) {
1948
- await exportToAgent(target, toExport, options.directory, { mkdir, writeFile, appendFile, join, existsSync });
1949
- }
1950
- console.log(chalk.bold.green('\nāœ“ Export complete!'));
1951
- console.log(chalk.gray('\nGenerated files:'));
1952
- if (targets.includes('copilot') || targets.includes('all')) {
1953
- console.log(chalk.gray(' - .github/copilot-instructions.md'));
1954
- }
1955
- if (targets.includes('cursor') || targets.includes('all')) {
1956
- console.log(chalk.gray(' - .cursor/rules/<skill>/RULE.md'));
1957
- }
1958
- if (targets.includes('claude') || targets.includes('all')) {
1959
- console.log(chalk.gray(' - CLAUDE.md'));
1960
- }
1961
- if (targets.includes('codex') || targets.includes('all')) {
1962
- console.log(chalk.gray(' - AGENTS.md'));
1963
- }
1964
- if (targets.includes('antigravity') || targets.includes('all')) {
1965
- console.log(chalk.gray(' - .agent/workflows/<skill>.md'));
1966
- }
1967
- }
1968
- catch (error) {
1969
- console.error(chalk.red('Error exporting skills:'), error);
1970
- process.exit(1);
1971
- }
1972
- });
1973
- async function exportToAgent(target, skillRefs, projectDir, fs) {
1974
- const loadedSkills = [];
1975
- for (const ref of skillRefs) {
1976
- const skill = await loadSkill(ref.path);
1977
- if (skill)
1978
- loadedSkills.push(skill);
1979
- }
1980
- switch (target) {
1981
- case 'copilot':
1982
- await exportToCopilot(loadedSkills, projectDir, fs);
1983
- break;
1984
- case 'cursor':
1985
- await exportToCursor(loadedSkills, projectDir, fs);
1986
- break;
1987
- case 'claude':
1988
- await exportToClaude(loadedSkills, projectDir, fs);
1989
- break;
1990
- case 'codex':
1991
- await exportToCodex(loadedSkills, projectDir, fs);
1992
- break;
1993
- case 'antigravity':
1994
- await exportToAntigravity(loadedSkills, projectDir, fs);
1995
- break;
1996
- }
1997
- }
1998
- async function exportToCopilot(skills, projectDir, fs) {
1999
- // GitHub Copilot now uses Agent Skills standard: .github/skills/<name>/SKILL.md
2000
- // Also supports .claude/skills/ for compatibility
2001
- const copilotDir = fs.join(projectDir, '.github', 'skills');
2002
- await fs.mkdir(copilotDir, { recursive: true });
2003
- for (const skill of skills) {
2004
- const skillDir = fs.join(copilotDir, skill.metadata.name);
2005
- await fs.mkdir(skillDir, { recursive: true });
2006
- // Create SKILL.md in Agent Skills format
2007
- const content = `---
2008
- name: ${skill.metadata.name}
2009
- description: ${skill.metadata.description}
2010
- ---
2011
-
2012
- ${skill.body}
2013
- `;
2014
- await fs.writeFile(fs.join(skillDir, 'SKILL.md'), content);
2015
- }
2016
- console.log(chalk.green(` āœ“ GitHub Copilot: .github/skills/<skill>/SKILL.md`));
2017
- }
2018
- async function exportToCursor(skills, projectDir, fs) {
2019
- // Cursor now uses Agent Skills standard: .cursor/skills/<name>/SKILL.md
2020
- const cursorDir = fs.join(projectDir, '.cursor', 'skills');
2021
- await fs.mkdir(cursorDir, { recursive: true });
2022
- for (const skill of skills) {
2023
- const skillDir = fs.join(cursorDir, skill.metadata.name);
2024
- await fs.mkdir(skillDir, { recursive: true });
2025
- // Create SKILL.md in Agent Skills format
2026
- const content = `---
2027
- name: ${skill.metadata.name}
2028
- description: ${skill.metadata.description}
2029
- ---
2030
-
2031
- ${skill.body}
2032
- `;
2033
- await fs.writeFile(fs.join(skillDir, 'SKILL.md'), content);
2034
- }
2035
- console.log(chalk.green(` āœ“ Cursor: .cursor/skills/<skill>/SKILL.md`));
2036
- }
2037
- async function exportToClaude(skills, projectDir, fs) {
2038
- // Claude Code now uses Agent Skills standard: .claude/skills/<name>/SKILL.md
2039
- const claudeDir = fs.join(projectDir, '.claude', 'skills');
2040
- await fs.mkdir(claudeDir, { recursive: true });
2041
- for (const skill of skills) {
2042
- const skillDir = fs.join(claudeDir, skill.metadata.name);
2043
- await fs.mkdir(skillDir, { recursive: true });
2044
- // Create SKILL.md in Agent Skills format
2045
- const content = `---
2046
- name: ${skill.metadata.name}
2047
- description: ${skill.metadata.description}
2048
- ---
2049
-
2050
- ${skill.body}
2051
- `;
2052
- await fs.writeFile(fs.join(skillDir, 'SKILL.md'), content);
2053
- }
2054
- console.log(chalk.green(` āœ“ Claude Code: .claude/skills/<skill>/SKILL.md`));
2055
- }
2056
- async function exportToCodex(skills, projectDir, fs) {
2057
- // OpenAI Codex uses Agent Skills standard: .codex/skills/<name>/SKILL.md
2058
- const codexDir = fs.join(projectDir, '.codex', 'skills');
2059
- await fs.mkdir(codexDir, { recursive: true });
2060
- for (const skill of skills) {
2061
- const skillDir = fs.join(codexDir, skill.metadata.name);
2062
- await fs.mkdir(skillDir, { recursive: true });
2063
- // Create SKILL.md in Agent Skills format
2064
- const content = `---
2065
- name: ${skill.metadata.name}
2066
- description: ${skill.metadata.description}
2067
- ---
2068
-
2069
- ${skill.body}
2070
- `;
2071
- await fs.writeFile(fs.join(skillDir, 'SKILL.md'), content);
2072
- }
2073
- console.log(chalk.green(` āœ“ OpenAI Codex: .codex/skills/<skill>/SKILL.md`));
2074
- }
2075
- async function exportToAntigravity(skills, projectDir, fs) {
2076
- const workflowsDir = fs.join(projectDir, '.agent', 'workflows');
2077
- await fs.mkdir(workflowsDir, { recursive: true });
2078
- for (const skill of skills) {
2079
- const content = `---
2080
- description: ${skill.metadata.description.slice(0, 100)}
2081
- ---
2082
-
2083
- ${skill.body}
2084
- `;
2085
- await fs.writeFile(fs.join(workflowsDir, `${skill.metadata.name}.md`), content);
2086
- }
2087
- console.log(chalk.green(` āœ“ Antigravity: .agent/workflows/<skill>.md`));
2088
- }
2089
- // ============================================
2090
- // INTERACTIVE COMMANDS
2091
- // ============================================
2092
- // Interactive install wizard - select skills with arrow keys
2093
- program
2094
- .command('install-wizard')
2095
- .alias('iw')
2096
- .description('Interactive skill installation wizard (legacy)')
2097
- .action(async () => {
2098
- try {
2099
- const spinner = ora('Fetching skills from marketplaces...').start();
2100
- const skills = await listMarketplaceSkills();
2101
- spinner.stop();
2102
- if (skills.length === 0) {
2103
- console.log(chalk.yellow('No skills found in marketplaces.'));
2104
- return;
2105
- }
2106
- const choices = skills.map(skill => ({
2107
- name: `${skill.name} - ${skill.description?.slice(0, 50) || 'No description'}...`,
2108
- value: skill.name,
2109
- short: skill.name
2110
- }));
2111
- const { selectedSkills } = await inquirer.prompt([
2112
- {
2113
- type: 'checkbox',
2114
- name: 'selectedSkills',
2115
- message: 'Select skills to install (Space to select, Enter to confirm):',
2116
- choices,
2117
- pageSize: 15
2118
- }
2119
- ]);
2120
- if (selectedSkills.length === 0) {
2121
- console.log(chalk.yellow('No skills selected.'));
2122
- return;
2123
- }
2124
- for (const skillName of selectedSkills) {
2125
- const installSpinner = ora(`Installing ${skillName}...`).start();
2126
- try {
2127
- const result = await installSkill(skillName);
2128
- installSpinner.succeed(`Installed: ${skillName}`);
2129
- }
2130
- catch (err) {
2131
- installSpinner.fail(`Failed to install ${skillName}: ${err}`);
2132
- }
2133
- }
2134
- console.log(chalk.bold.green('\nāœ“ Installation complete!'));
2135
- console.log(chalk.gray('Run "skills export" to export to your AI agent.'));
2136
- }
2137
- catch (error) {
2138
- console.error(chalk.red('Error:'), error);
2139
- process.exit(1);
2140
- }
2141
- });
2142
- // Interactive export - select target agents
2143
- program
2144
- .command('export-interactive')
2145
- .alias('ei')
2146
- .description('Interactive export with agent selection menu')
2147
- .action(async () => {
2148
- try {
2149
- const skills = await discoverSkills();
2150
- if (skills.length === 0) {
2151
- console.log(chalk.yellow('No skills found to export.'));
2152
- return;
2153
- }
2154
- const { agents } = await inquirer.prompt([
2155
- {
2156
- type: 'checkbox',
2157
- name: 'agents',
2158
- message: 'Select target AI agents:',
2159
- choices: [
2160
- { name: 'GitHub Copilot (.github/skills/)', value: 'copilot', checked: true },
2161
- { name: 'Cursor (.cursor/skills/)', value: 'cursor', checked: true },
2162
- { name: 'Claude Code (.claude/skills/)', value: 'claude', checked: true },
2163
- { name: 'OpenAI Codex (.codex/skills/)', value: 'codex', checked: true },
2164
- { name: 'Antigravity (.agent/workflows/)', value: 'antigravity', checked: true }
2165
- ]
2166
- }
2167
- ]);
2168
- if (agents.length === 0) {
2169
- console.log(chalk.yellow('No agents selected.'));
2170
- return;
2171
- }
2172
- const { mkdir, writeFile, appendFile } = await import('fs/promises');
2173
- const { join } = await import('path');
2174
- const { existsSync } = await import('fs');
2175
- console.log(chalk.bold(`\nExporting ${skills.length} skill(s) to: ${agents.join(', ')}\n`));
2176
- for (const target of agents) {
2177
- const spinner = ora(`Exporting to ${target}...`).start();
2178
- await exportToAgent(target, skills, '.', { mkdir, writeFile, appendFile, join, existsSync });
2179
- spinner.succeed();
2180
- }
2181
- console.log(chalk.bold.green('\nāœ“ Export complete!'));
2182
- }
2183
- catch (error) {
2184
- console.error(chalk.red('Error:'), error);
2185
- process.exit(1);
2186
- }
2187
- });
2188
- // Quick setup wizard
2189
- program
2190
- .command('setup')
2191
- .description('Interactive setup wizard - install skills and export to your agents')
2192
- .action(async () => {
2193
- console.log(chalk.bold.cyan('\nšŸš€ Agent Skills Setup Wizard\n'));
2194
- // Step 1: Choose what to do
2195
- const { action } = await inquirer.prompt([
2196
- {
2197
- type: 'list',
2198
- name: 'action',
2199
- message: 'What would you like to do?',
2200
- choices: [
2201
- { name: 'šŸ“¦ Install skills from marketplace', value: 'install' },
2202
- { name: 'šŸ“¤ Export installed skills to AI agents', value: 'export' },
2203
- { name: 'šŸ”„ Both - Install and export', value: 'both' }
2204
- ]
2205
- }
2206
- ]);
2207
- if (action === 'install' || action === 'both') {
2208
- const spinner = ora('Fetching skills from marketplaces...').start();
2209
- const skills = await listMarketplaceSkills();
2210
- spinner.stop();
2211
- if (skills.length > 0) {
2212
- const choices = skills.slice(0, 20).map(skill => ({
2213
- name: `${skill.name} - ${skill.description?.slice(0, 40) || ''}...`,
2214
- value: skill.name
2215
- }));
2216
- const { selectedSkills } = await inquirer.prompt([
2217
- {
2218
- type: 'checkbox',
2219
- name: 'selectedSkills',
2220
- message: 'Select skills to install:',
2221
- choices,
2222
- pageSize: 10
2223
- }
2224
- ]);
2225
- for (const skillName of selectedSkills) {
2226
- const installSpinner = ora(`Installing ${skillName}...`).start();
2227
- try {
2228
- await installSkill(skillName);
2229
- installSpinner.succeed(`Installed: ${skillName}`);
2230
- }
2231
- catch (err) {
2232
- installSpinner.fail(`Failed: ${skillName}`);
2233
- }
2234
- }
2235
- }
2236
- }
2237
- if (action === 'export' || action === 'both') {
2238
- const { agents } = await inquirer.prompt([
2239
- {
2240
- type: 'checkbox',
2241
- name: 'agents',
2242
- message: 'Which AI agents do you use?',
2243
- choices: [
2244
- { name: 'Cursor', value: 'cursor', checked: true },
2245
- { name: 'Claude Code', value: 'claude', checked: true },
2246
- { name: 'GitHub Copilot', value: 'copilot', checked: true },
2247
- { name: 'OpenAI Codex', value: 'codex', checked: false }
2248
- ]
2249
- }
2250
- ]);
2251
- const skills = await discoverSkills();
2252
- const { mkdir, writeFile, appendFile } = await import('fs/promises');
2253
- const { join } = await import('path');
2254
- const { existsSync } = await import('fs');
2255
- for (const target of agents) {
2256
- const spinner = ora(`Exporting to ${target}...`).start();
2257
- await exportToAgent(target, skills, '.', { mkdir, writeFile, appendFile, join, existsSync });
2258
- spinner.succeed();
2259
- }
2260
- }
2261
- console.log(chalk.bold.green('\n✨ Setup complete!'));
2262
- console.log(chalk.gray('Your skills are now ready to use in your AI agents.\n'));
2263
- });
2264
- // ============================================
2265
- // PHASE 1: LEVERAGE EXISTING CODE
2266
- // ============================================
2267
- // Run - Execute a skill's script
2268
- program
2269
- .command('run <skill-name> <script>')
2270
- .description('Execute a script from an installed skill')
2271
- .option('-a, --args <args...>', 'Arguments to pass to the script')
2272
- .option('--timeout <ms>', 'Timeout in milliseconds', '30000')
2273
- .action(async (skillName, script, options) => {
2274
- try {
2275
- const { executeScript, listScripts } = await import('../core/executor.js');
2276
- const { homedir } = await import('os');
2277
- const { join } = await import('path');
2278
- const { existsSync } = await import('fs');
2279
- // Find the skill path
2280
- const skillsDir = join(homedir(), '.antigravity', 'skills');
2281
- const skillPath = join(skillsDir, skillName);
2282
- if (!existsSync(skillPath)) {
2283
- console.error(chalk.red(`Skill not found: ${skillName}`));
2284
- console.log(chalk.gray(`Expected at: ${skillPath}`));
2285
- console.log(chalk.gray('\nInstall with: skills install <skill-name>'));
2286
- process.exit(1);
2287
- }
2288
- // List available scripts if asked
2289
- const scripts = await listScripts(skillPath);
2290
- if (scripts.length === 0) {
2291
- console.log(chalk.yellow(`No scripts found in ${skillName}`));
2292
- console.log(chalk.gray('Skills can have scripts in the scripts/ directory.'));
2293
- return;
2294
- }
2295
- if (!scripts.includes(script)) {
2296
- console.log(chalk.red(`Script not found: ${script}`));
2297
- console.log(chalk.cyan('\nAvailable scripts:'));
2298
- scripts.forEach(s => console.log(chalk.gray(` - ${s}`)));
2299
- return;
2300
- }
2301
- const spinner = ora(`Running ${script}...`).start();
2302
- const result = await executeScript(skillPath, script, options.args || [], { timeout: parseInt(options.timeout) });
2303
- if (result.success) {
2304
- spinner.succeed(`Completed in ${result.executionTime}ms`);
2305
- if (result.stdout) {
2306
- console.log(chalk.gray('\nOutput:'));
2307
- console.log(result.stdout);
2308
- }
2309
- }
2310
- else {
2311
- spinner.fail(`Failed (exit code: ${result.exitCode})`);
2312
- if (result.stderr) {
2313
- console.error(chalk.red(result.stderr));
2314
- }
2315
- process.exit(1);
2316
- }
2317
- }
2318
- catch (error) {
2319
- console.error(chalk.red('Error running script:'), error);
2320
- process.exit(1);
2321
- }
2322
- });
2323
- // Context - Generate LLM system prompt context
2324
- program
2325
- .command('context')
2326
- .description('Generate system prompt context for AI agents')
2327
- .option('-f, --format <format>', 'Output format: xml, json, markdown', 'xml')
2328
- .option('-s, --skills <skills...>', 'Specific skills to include (default: all installed)')
2329
- .option('-o, --output <file>', 'Write to file instead of stdout')
2330
- .action(async (options) => {
2331
- try {
2332
- const { generateSkillsPromptXML, generateFullSkillsContext } = await import('../core/injector.js');
2333
- const { discoverSkills } = await import('../core/loader.js');
2334
- const allSkills = await discoverSkills();
2335
- // Filter if specific skills requested
2336
- let skills = allSkills;
2337
- if (options.skills && options.skills.length > 0) {
2338
- skills = allSkills.filter(s => options.skills.some((name) => s.name.toLowerCase().includes(name.toLowerCase())));
2339
- }
2340
- if (skills.length === 0) {
2341
- console.error(chalk.yellow('No skills found.'));
2342
- console.log(chalk.gray('Install skills with: skills install <name>'));
2343
- return;
2344
- }
2345
- let output = '';
2346
- if (options.format === 'xml') {
2347
- const result = generateSkillsPromptXML(skills);
2348
- output = result.xml;
2349
- if (!options.output) {
2350
- console.log(chalk.gray(`\n# ${result.skillCount} skills, ~${result.estimatedTokens} tokens\n`));
2351
- }
2352
- }
2353
- else if (options.format === 'json') {
2354
- output = JSON.stringify({
2355
- skills: skills.map(s => ({
2356
- name: s.name,
2357
- description: s.description,
2358
- path: s.path
2359
- })),
2360
- count: skills.length
2361
- }, null, 2);
2362
- }
2363
- else if (options.format === 'markdown') {
2364
- output = generateFullSkillsContext(skills);
2365
- }
2366
- if (options.output) {
2367
- const { writeFile } = await import('fs/promises');
2368
- await writeFile(options.output, output);
2369
- console.log(chalk.green(`āœ“ Written to ${options.output}`));
2370
- }
2371
- else {
2372
- console.log(output);
2373
- }
2374
- }
2375
- catch (error) {
2376
- console.error(chalk.red('Error generating context:'), error);
2377
- process.exit(1);
2378
- }
2379
- });
2380
- // Preview - Open skill in browser
2381
- program
2382
- .command('preview <skill-name>')
2383
- .description('Open skill detail page in browser')
2384
- .option('--url-only', 'Just print the URL without opening')
2385
- .action(async (skillName, options) => {
2386
- try {
2387
- // Parse scoped name
2388
- const clean = skillName.replace(/^@/, '');
2389
- const url = `https://skills.karanjot.dev/marketplace/${clean}`;
2390
- if (options.urlOnly) {
2391
- console.log(url);
2392
- }
2393
- else {
2394
- const { exec } = await import('child_process');
2395
- const { promisify } = await import('util');
2396
- const execAsync = promisify(exec);
2397
- // Cross-platform open
2398
- const cmd = process.platform === 'darwin' ? 'open' :
2399
- process.platform === 'win32' ? 'start' : 'xdg-open';
2400
- await execAsync(`${cmd} "${url}"`);
2401
- console.log(chalk.green(`āœ“ Opened: ${url}`));
2402
- }
2403
- }
2404
- catch (error) {
2405
- console.error(chalk.red('Error opening preview:'), error);
2406
- process.exit(1);
2407
- }
2408
- });
2409
- // Scripts - List scripts in an installed skill
2410
- program
2411
- .command('scripts <skill-name>')
2412
- .description('List available scripts in an installed skill')
2413
- .action(async (skillName) => {
2414
- try {
2415
- const { listScripts, isScriptSafe } = await import('../core/executor.js');
2416
- const { homedir } = await import('os');
2417
- const { join } = await import('path');
2418
- const { existsSync } = await import('fs');
2419
- const { readFile } = await import('fs/promises');
2420
- const skillsDir = join(homedir(), '.antigravity', 'skills');
2421
- const skillPath = join(skillsDir, skillName);
2422
- if (!existsSync(skillPath)) {
2423
- console.error(chalk.red(`Skill not found: ${skillName}`));
2424
- return;
2425
- }
2426
- const scripts = await listScripts(skillPath);
2427
- if (scripts.length === 0) {
2428
- console.log(chalk.yellow('No scripts found in this skill.'));
2429
- return;
2430
- }
2431
- console.log(chalk.bold(`\nšŸ“œ Scripts in ${skillName}:\n`));
2432
- for (const script of scripts) {
2433
- const scriptPath = join(skillPath, 'scripts', script);
2434
- try {
2435
- const content = await readFile(scriptPath, 'utf-8');
2436
- const safety = isScriptSafe(content);
2437
- const safetyIcon = safety.safe ? chalk.green('āœ“') : chalk.yellow('⚠');
2438
- console.log(` ${safetyIcon} ${chalk.cyan(script)}`);
2439
- if (!safety.safe) {
2440
- safety.warnings.forEach(w => console.log(chalk.gray(` Warning: ${w}`)));
2441
- }
2442
- }
2443
- catch {
2444
- console.log(` ${chalk.gray('?')} ${script}`);
2445
- }
2446
- }
2447
- console.log(chalk.gray(`\nRun with: skills run ${skillName} <script>\n`));
2448
- }
2449
- catch (error) {
2450
- console.error(chalk.red('Error listing scripts:'), error);
2451
- process.exit(1);
2452
- }
2453
- });
2454
- // Shell completions
2455
- program
2456
- .command('completion <shell>')
2457
- .description('Generate shell completion script (bash, zsh, fish)')
2458
- .action((shell) => {
2459
- const commands = [
2460
- 'list', 'validate', 'show', 'prompt', 'init', 'assets',
2461
- 'install', 'uninstall', 'search', 'run', 'context',
2462
- 'preview', 'scripts', 'market-list', 'market-search',
2463
- 'market-install', 'market-uninstall', 'market-installed',
2464
- 'market-sources', 'setup', 'completion'
2465
- ];
2466
- if (shell === 'bash') {
2467
- console.log(`# Bash completion for skills CLI
2468
- # Add to ~/.bashrc: eval "$(skills completion bash)"
2469
-
2470
- _skills_completions() {
2471
- local cur="\${COMP_WORDS[COMP_CWORD]}"
2472
- local commands="${commands.join(' ')}"
2473
-
2474
- if [ \${COMP_CWORD} -eq 1 ]; then
2475
- COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
2476
- fi
2477
- }
2478
-
2479
- complete -F _skills_completions skills`);
2480
- }
2481
- else if (shell === 'zsh') {
2482
- console.log(`# Zsh completion for skills CLI
2483
- # Add to ~/.zshrc: eval "$(skills completion zsh)"
2484
-
2485
- _skills() {
2486
- local commands=(
2487
- ${commands.map(c => `'${c}:${c} command'`).join('\n ')}
2488
- )
2489
-
2490
- _arguments '1: :->command' && return
2491
-
2492
- case $state in
2493
- command)
2494
- _describe 'command' commands
2495
- ;;
2496
- esac
2497
- }
2498
-
2499
- compdef _skills skills`);
2500
- }
2501
- else if (shell === 'fish') {
2502
- console.log(`# Fish completion for skills CLI
2503
- # Save to ~/.config/fish/completions/skills.fish
2504
-
2505
- ${commands.map(c => `complete -c skills -f -n "__fish_use_subcommand" -a "${c}" -d "${c} command"`).join('\n')}`);
2506
- }
2507
- else {
2508
- console.error(chalk.red(`Unknown shell: ${shell}`));
2509
- console.log(chalk.gray('Supported: bash, zsh, fish'));
2510
- process.exit(1);
2511
- }
2512
- });
2513
- // Info - Show installation status and paths
2514
- program
2515
- .command('info')
2516
- .description('Show skills installation status and paths')
2517
- .action(async () => {
2518
- try {
2519
- const { homedir } = await import('os');
2520
- const { join } = await import('path');
2521
- const { existsSync } = await import('fs');
2522
- const { readdir } = await import('fs/promises');
2523
- console.log(chalk.bold('\nšŸ“¦ Skills CLI Info\n'));
2524
- // Installation paths
2525
- const paths = [
2526
- { name: 'Global skills', path: join(homedir(), '.antigravity', 'skills') },
2527
- { name: 'Project skills', path: join(process.cwd(), '.antigravity', 'skills') },
2528
- { name: 'Legacy skills', path: join(process.cwd(), 'skills') },
2529
- { name: 'Config', path: join(homedir(), '.antigravity', 'marketplace.json') }
2530
- ];
2531
- console.log(chalk.cyan('šŸ“ Paths:'));
2532
- for (const { name, path } of paths) {
2533
- const exists = existsSync(path);
2534
- const icon = exists ? chalk.green('āœ“') : chalk.gray('ā—‹');
2535
- console.log(` ${icon} ${name}: ${chalk.gray(path)}`);
2536
- }
2537
- // Count installed skills
2538
- const skillsDir = join(homedir(), '.antigravity', 'skills');
2539
- let skillCount = 0;
2540
- if (existsSync(skillsDir)) {
2541
- const entries = await readdir(skillsDir, { withFileTypes: true });
2542
- skillCount = entries.filter(e => e.isDirectory()).length;
2543
- }
2544
- console.log(chalk.cyan('\nšŸ“Š Stats:'));
2545
- console.log(` Installed skills: ${chalk.bold(skillCount.toString())}`);
2546
- console.log(` Platform: ${chalk.gray(process.platform)}`);
2547
- console.log(` Node: ${chalk.gray(process.version)}`);
2548
- // Show agent directories
2549
- console.log(chalk.cyan('\nšŸ¤– Agent Directories:'));
2550
- const agents = [
2551
- { name: 'Cursor', path: '.cursor/skills' },
2552
- { name: 'Claude', path: '.claude/skills' },
2553
- { name: 'Copilot', path: '.github/skills' },
2554
- { name: 'Codex', path: '.codex/skills' },
2555
- { name: 'Antigravity', path: '.agent/workflows' }
2556
- ];
2557
- for (const { name, path } of agents) {
2558
- const fullPath = join(process.cwd(), path);
2559
- const exists = existsSync(fullPath);
2560
- const icon = exists ? chalk.green('āœ“') : chalk.gray('ā—‹');
2561
- console.log(` ${icon} ${name}: ${chalk.gray(path)}`);
2562
- }
2563
- console.log('');
2564
- }
2565
- catch (error) {
2566
- console.error(chalk.red('Error:'), error);
2567
- process.exit(1);
2568
- }
2569
- });
2570
- // (Old update command removed - see new lock file-based update command below)
2571
- // Doctor - Diagnose and fix common issues
2572
- program
2573
- .command('doctor')
2574
- .description('Diagnose and fix common skills installation issues')
2575
- .option('--fix', 'Attempt to fix issues automatically')
2576
- .action(async (options) => {
2577
- try {
2578
- const { homedir } = await import('os');
2579
- const { join } = await import('path');
2580
- const { existsSync } = await import('fs');
2581
- const { mkdir, readdir } = await import('fs/promises');
2582
- console.log(chalk.bold('\n🩺 Skills Doctor\n'));
2583
- const checks = [];
2584
- // Check 1: Skills directory exists
2585
- const skillsDir = join(homedir(), '.antigravity', 'skills');
2586
- if (existsSync(skillsDir)) {
2587
- checks.push({ name: 'Skills directory', status: 'pass', message: skillsDir });
2588
- }
2589
- else {
2590
- checks.push({
2591
- name: 'Skills directory',
2592
- status: 'warn',
2593
- message: 'Not created yet',
2594
- fix: async () => {
2595
- await mkdir(skillsDir, { recursive: true });
2596
- }
2597
- });
2598
- }
2599
- // Check 2: Config directory
2600
- const configDir = join(homedir(), '.antigravity');
2601
- if (existsSync(configDir)) {
2602
- checks.push({ name: 'Config directory', status: 'pass', message: configDir });
2603
- }
2604
- else {
2605
- checks.push({
2606
- name: 'Config directory',
2607
- status: 'warn',
2608
- message: 'Not created yet',
2609
- fix: async () => {
2610
- await mkdir(configDir, { recursive: true });
2611
- }
2612
- });
2613
- }
2614
- // Check 3: Node version
2615
- const nodeVersion = process.version;
2616
- const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
2617
- if (majorVersion >= 18) {
2618
- checks.push({ name: 'Node.js version', status: 'pass', message: nodeVersion });
2619
- }
2620
- else {
2621
- checks.push({ name: 'Node.js version', status: 'fail', message: `${nodeVersion} (requires >=18)` });
2622
- }
2623
- // Check 4: Git available
2624
- try {
2625
- const { execSync } = await import('child_process');
2626
- execSync('git --version', { stdio: 'pipe' });
2627
- checks.push({ name: 'Git installed', status: 'pass', message: 'Available' });
2628
- }
2629
- catch {
2630
- checks.push({ name: 'Git installed', status: 'warn', message: 'Not found (optional)' });
2631
- }
2632
- // Check 5: Network connectivity
2633
- try {
2634
- const response = await fetch('https://api.github.com/rate_limit', {
2635
- headers: { 'User-Agent': 'agent-skills-cli' }
2636
- });
2637
- if (response.ok) {
2638
- checks.push({ name: 'GitHub API', status: 'pass', message: 'Connected' });
2639
- }
2640
- else {
2641
- checks.push({ name: 'GitHub API', status: 'warn', message: `Status ${response.status}` });
2642
- }
2643
- }
2644
- catch {
2645
- checks.push({ name: 'GitHub API', status: 'fail', message: 'Cannot connect' });
2646
- }
2647
- // Check 6: Installed skills have valid SKILL.md
2648
- if (existsSync(skillsDir)) {
2649
- const entries = await readdir(skillsDir, { withFileTypes: true });
2650
- const skillCount = entries.filter(e => e.isDirectory()).length;
2651
- let validCount = 0;
2652
- for (const entry of entries) {
2653
- if (entry.isDirectory()) {
2654
- const skillMd = join(skillsDir, entry.name, 'SKILL.md');
2655
- if (existsSync(skillMd))
2656
- validCount++;
2657
- }
2658
- }
2659
- if (skillCount === 0) {
2660
- checks.push({ name: 'Installed skills', status: 'pass', message: 'None installed' });
2661
- }
2662
- else if (validCount === skillCount) {
2663
- checks.push({ name: 'Installed skills', status: 'pass', message: `${validCount}/${skillCount} valid` });
2664
- }
2665
- else {
2666
- checks.push({ name: 'Installed skills', status: 'warn', message: `${validCount}/${skillCount} valid` });
2667
- }
2668
- }
2669
- // Display results
2670
- let hasIssues = false;
2671
- for (const check of checks) {
2672
- const icon = check.status === 'pass' ? chalk.green('āœ“') :
2673
- check.status === 'warn' ? chalk.yellow('⚠') :
2674
- chalk.red('āœ—');
2675
- console.log(` ${icon} ${check.name}: ${chalk.gray(check.message)}`);
2676
- if (check.status !== 'pass')
2677
- hasIssues = true;
2678
- }
2679
- // Fix issues if requested
2680
- if (options.fix && hasIssues) {
2681
- console.log(chalk.cyan('\nšŸ”§ Attempting fixes...\n'));
2682
- for (const check of checks) {
2683
- if (check.fix && check.status !== 'pass') {
2684
- try {
2685
- await check.fix();
2686
- console.log(chalk.green(` āœ“ Fixed: ${check.name}`));
2687
- }
2688
- catch (err) {
2689
- console.log(chalk.red(` āœ— Could not fix: ${check.name}`));
2690
- }
2691
- }
2692
- }
2693
- }
2694
- if (!hasIssues) {
2695
- console.log(chalk.green('\nāœ“ All checks passed!\n'));
2696
- }
2697
- else if (!options.fix) {
2698
- console.log(chalk.gray('\nRun with --fix to attempt automatic fixes.\n'));
2699
- }
2700
- console.log('');
2701
- }
2702
- catch (error) {
2703
- console.error(chalk.red('Error running doctor:'), error);
2704
- process.exit(1);
2705
- }
2706
- });
2707
- // ============================================
2708
- // CHECK COMMAND - Check for skill updates
2709
- // ============================================
2710
- program
2711
- .command('check')
2712
- .description('Check installed skills and available updates')
2713
- .option('-a, --agent <agent>', 'Check specific agent only')
2714
- .option('-g, --global', 'Check globally installed skills only')
2715
- .option('--json', 'Output as JSON')
2716
- .action(async (options) => {
2717
- try {
2718
- const { listInstalledSkills, readLock } = await import('../core/index.js');
2719
- const spinner = ora('Checking installed skills...').start();
2720
- // Read from lock file
2721
- const lock = await readLock();
2722
- let skills = Object.values(lock.skills);
2723
- // Filter by global/project
2724
- if (options.global) {
2725
- skills = skills.filter(s => s.isGlobal);
2726
- }
2727
- else if (!options.global && !options.agent) {
2728
- // Show all (both global and project)
2729
- }
2730
- // Filter by agent
2731
- if (options.agent) {
2732
- skills = skills.filter(s => s.agents.includes(options.agent));
2733
- }
2734
- spinner.stop();
2735
- if (options.json) {
2736
- console.log(JSON.stringify({ skills, count: skills.length }, null, 2));
2737
- return;
2738
- }
2739
- if (skills.length === 0) {
2740
- console.log(chalk.yellow('\nšŸ“¦ No skills installed.'));
2741
- console.log(chalk.gray('Use `skills search` or `skills install` to add skills.\n'));
2742
- return;
2743
- }
2744
- console.log(chalk.bold(`\nšŸ“¦ Found ${skills.length} installed skill(s):\n`));
2745
- for (const skill of skills) {
2746
- const sourceLabel = skill.sourceType === 'database' ? '🌐 Database' :
2747
- skill.sourceType === 'github' ? 'šŸ™ GitHub' :
2748
- skill.sourceType === 'gitlab' ? '🦊 GitLab' : 'šŸ“ Local';
2749
- console.log(` ${chalk.cyan(skill.scopedName)} ${chalk.gray(`[${sourceLabel}]`)}`);
2750
- console.log(chalk.gray(` Agents: ${skill.agents.join(', ')}`));
2751
- console.log(chalk.gray(` Installed: ${new Date(skill.installedAt).toLocaleDateString()}`));
2752
- if (skill.version) {
2753
- console.log(chalk.gray(` Version: ${skill.version.slice(0, 7)}`));
2754
- }
2755
- console.log('');
2756
- }
2757
- console.log(chalk.gray('Tip: Run `skills update` to update all skills.'));
2758
- console.log(chalk.gray(' Run `skills remove` to uninstall skills.\n'));
2759
- }
2760
- catch (error) {
2761
- console.error(chalk.red('Error checking installed skills:'), error);
2762
- process.exit(1);
2763
- }
2764
- });
2765
- // Update - Re-download skills from their sources
2766
- program
2767
- .command('update [skill-names...]')
2768
- .description('Update installed skills from their sources')
2769
- .option('-a, --all', 'Update all installed skills')
2770
- .option('-g, --global', 'Only update globally installed skills')
2771
- .option('-y, --yes', 'Skip confirmation prompts')
2772
- .action(async (skillNames, options) => {
2773
- try {
2774
- const { readLock, removeSkillFromLock, addSkillToLock, createLockEntry } = await import('../core/index.js');
2775
- const { mkdir, cp, rm } = await import('fs/promises');
2776
- const { existsSync } = await import('fs');
2777
- const { join } = await import('path');
2778
- const { tmpdir } = await import('os');
2779
- const { exec } = await import('child_process');
2780
- const { promisify } = await import('util');
2781
- const execAsync = promisify(exec);
2782
- const lock = await readLock();
2783
- let skillsToUpdate = Object.values(lock.skills);
2784
- // Filter by global
2785
- if (options.global) {
2786
- skillsToUpdate = skillsToUpdate.filter(s => s.isGlobal);
2787
- }
2788
- // Filter by specific names
2789
- if (skillNames && skillNames.length > 0) {
2790
- skillsToUpdate = skillsToUpdate.filter(s => skillNames.some((n) => s.name.toLowerCase() === n.toLowerCase() ||
2791
- s.scopedName.toLowerCase() === n.toLowerCase()));
2792
- }
2793
- // If not --all and no specific skills, prompt for selection
2794
- if (!options.all && skillNames.length === 0 && skillsToUpdate.length > 0) {
2795
- const { selected } = await inquirer.prompt([{
2796
- type: 'checkbox',
2797
- name: 'selected',
2798
- message: 'Select skills to update:',
2799
- choices: skillsToUpdate.map(s => ({
2800
- name: `${s.scopedName} (${s.sourceType})`,
2801
- value: s,
2802
- checked: true
2803
- }))
2804
- }]);
2805
- skillsToUpdate = selected;
2806
- }
2807
- if (skillsToUpdate.length === 0) {
2808
- console.log(chalk.yellow('\nšŸ“¦ No skills to update.'));
2809
- console.log(chalk.gray('Use `skills install` to add skills first.\n'));
2810
- return;
2811
- }
2812
- // Filter to only updateable skills (github/gitlab)
2813
- const updateable = skillsToUpdate.filter(s => s.sourceType === 'github' || s.sourceType === 'gitlab' || s.sourceType === 'database');
2814
- if (updateable.length === 0) {
2815
- console.log(chalk.yellow('\nšŸ“¦ No remote skills to update.'));
2816
- console.log(chalk.gray('Local skills cannot be updated automatically.\n'));
2817
- return;
2818
- }
2819
- console.log(chalk.bold(`\nšŸ“¦ Updating ${updateable.length} skill(s)...\n`));
2820
- let successCount = 0;
2821
- let failCount = 0;
2822
- for (const skill of updateable) {
2823
- const spinner = ora(`Updating ${skill.scopedName}...`).start();
2824
- try {
2825
- // Clone to temp directory
2826
- const tempDir = join(tmpdir(), `skill-update-${Date.now()}`);
2827
- await mkdir(tempDir, { recursive: true });
2828
- // Parse GitHub/GitLab URL
2829
- const urlMatch = skill.source.match(/(github|gitlab)\.com\/([^/]+)\/([^/]+)/);
2830
- if (!urlMatch) {
2831
- spinner.fail(`${skill.scopedName}: Invalid source URL`);
2832
- failCount++;
2833
- continue;
2834
- }
2835
- // Clone the repo
2836
- await execAsync(`git clone --depth 1 ${skill.source} .`, { cwd: tempDir });
2837
- // Update each agent directory
2838
- for (const agent of skill.agents) {
2839
- const agentConfig = AGENTS[agent];
2840
- if (!agentConfig)
2841
- continue;
2842
- const targetDir = skill.isGlobal ? agentConfig.globalDir : agentConfig.projectDir;
2843
- const skillDir = skill.isGlobal
2844
- ? join(targetDir, skill.name)
2845
- : join(skill.projectDir || process.cwd(), targetDir, skill.name);
2846
- // Remove old version
2847
- if (existsSync(skillDir)) {
2848
- await rm(skillDir, { recursive: true, force: true });
2849
- }
2850
- // Copy new version
2851
- await mkdir(skillDir, { recursive: true });
2852
- await cp(tempDir, skillDir, { recursive: true });
2853
- }
2854
- // Get latest commit SHA for version tracking
2855
- let version;
2856
- try {
2857
- const { stdout } = await execAsync('git rev-parse HEAD', { cwd: tempDir });
2858
- version = stdout.trim();
2859
- }
2860
- catch { }
2861
- // Update lock file
2862
- await removeSkillFromLock(skill.scopedName);
2863
- const updatedEntry = createLockEntry({
2864
- name: skill.name,
2865
- scopedName: skill.scopedName,
2866
- source: skill.source,
2867
- sourceType: skill.sourceType,
2868
- version,
2869
- agents: skill.agents,
2870
- canonicalPath: skill.canonicalPath,
2871
- isGlobal: skill.isGlobal,
2872
- projectDir: skill.projectDir
2873
- });
2874
- await addSkillToLock(updatedEntry);
2875
- // Cleanup
2876
- await rm(tempDir, { recursive: true, force: true }).catch(() => { });
2877
- spinner.succeed(`${skill.scopedName}: Updated successfully`);
2878
- successCount++;
2879
- }
2880
- catch (err) {
2881
- spinner.fail(`${skill.scopedName}: ${err.message || err}`);
2882
- failCount++;
2883
- }
2884
- }
2885
- console.log('');
2886
- if (successCount > 0) {
2887
- console.log(chalk.bold.green(`✨ Updated ${successCount} skill(s)`));
2888
- }
2889
- if (failCount > 0) {
2890
- console.log(chalk.yellow(`⚠ ${failCount} skill(s) failed to update`));
2891
- }
2892
- console.log('');
2893
- }
2894
- catch (error) {
2895
- console.error(chalk.red('Error updating skills:'), error);
2896
- process.exit(1);
2897
- }
2898
- });
2899
- // Register the remove command
201
+ // ─── Register all command modules ───────────────────────────────────────────
202
+ // Group 1: Core commands
203
+ registerListCommand(program);
204
+ registerValidateCommand(program);
205
+ registerShowCommand(program); // show, prompt, init
206
+ // Group 2: Marketplace & assets
207
+ registerMarketplaceCommands(program); // assets, market-list/search/install/uninstall/installed/sources/add-source/update-check, install-url
208
+ // Group 3: Search & install
209
+ registerSearchInstallCommand(program); // search
210
+ registerInstallCommand(program); // install
211
+ // Group 4: Export
212
+ registerExportCommand(program);
213
+ // Group 5: Utilities
214
+ registerDoctorCommand(program);
215
+ registerCheckCommand(program);
216
+ registerUpdateCommand(program);
217
+ registerExecCommand(program);
218
+ // Group 6: Interactive wizards & misc
219
+ registerInteractiveCommands(program); // install-wizard, export-interactive, setup, run, context, preview, scripts, completion, info
220
+ // Group 7: Previously-extracted modular commands
2900
221
  registerRemoveCommand(program, AGENTS);
222
+ registerSuggestCommand(program);
223
+ registerAuditCommand(program);
224
+ registerCraftCommand(program);
225
+ registerSubmitCommand(program);
226
+ registerBootstrapCommand(program);
227
+ registerConvertCommand(program);
228
+ registerCollabCommand(program);
229
+ registerLockspecCommand(program);
230
+ registerForgeCommand(program);
231
+ registerMineCommand(program);
232
+ registerRecallCommand(program);
233
+ registerGridCommand(program);
234
+ registerCaptureCommand(program);
235
+ registerTriggerCommand(program);
236
+ registerRuleCommand(program);
237
+ registerBlueprintCommand(program);
238
+ registerCiCommand(program);
239
+ registerTrackCommand(program);
240
+ registerInsightCommand(program);
241
+ registerMethodCommand(program);
2901
242
  program.parse();
2902
243
  //# sourceMappingURL=index.js.map