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