@codemcp/skills 1.8.1 → 2.1.0
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/LICENSE +19 -0
- package/dist/api.js +23 -0
- package/dist/chunk-JSBRDJBE.js +30 -0
- package/dist/chunk-OAWSLH2D.js +1294 -0
- package/dist/chunk-US7NTYE7.js +5642 -0
- package/dist/cli.js +1883 -0
- package/dist/dist-TGUYKB34.js +41 -0
- package/package.json +15 -9
- package/bin/cli.mjs +0 -14
- package/dist/THIRD-PARTY-LICENSES.md +0 -476
- package/dist/_chunks/libs/@clack/core.mjs +0 -769
- package/dist/_chunks/libs/@clack/core.mjs.map +0 -1
- package/dist/_chunks/libs/@clack/prompts.mjs +0 -358
- package/dist/_chunks/libs/@clack/prompts.mjs.map +0 -1
- package/dist/_chunks/libs/@kwsites/file-exists.mjs +0 -857
- package/dist/_chunks/libs/@kwsites/file-exists.mjs.map +0 -1
- package/dist/_chunks/libs/@kwsites/promise-deferred.mjs +0 -54
- package/dist/_chunks/libs/@kwsites/promise-deferred.mjs.map +0 -1
- package/dist/_chunks/libs/esprima.mjs +0 -5340
- package/dist/_chunks/libs/esprima.mjs.map +0 -1
- package/dist/_chunks/libs/extend-shallow.mjs +0 -36
- package/dist/_chunks/libs/extend-shallow.mjs.map +0 -1
- package/dist/_chunks/libs/gray-matter.mjs +0 -2719
- package/dist/_chunks/libs/gray-matter.mjs.map +0 -1
- package/dist/_chunks/libs/simple-git.mjs +0 -3606
- package/dist/_chunks/libs/simple-git.mjs.map +0 -1
- package/dist/_chunks/libs/xdg-basedir.mjs +0 -16
- package/dist/_chunks/libs/xdg-basedir.mjs.map +0 -1
- package/dist/_chunks/rolldown-runtime.mjs +0 -24
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -5714
- package/dist/cli.mjs.map +0 -1
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1883 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
M,
|
|
4
|
+
Se,
|
|
5
|
+
Y,
|
|
6
|
+
agents,
|
|
7
|
+
detectInstalledAgents,
|
|
8
|
+
discoverSkills,
|
|
9
|
+
fe,
|
|
10
|
+
fetchSkillFolderHash,
|
|
11
|
+
getAllLockedSkills,
|
|
12
|
+
getCanonicalPath,
|
|
13
|
+
getCanonicalSkillsDir,
|
|
14
|
+
getGitHubToken,
|
|
15
|
+
getInstallPath,
|
|
16
|
+
getMCPCanonicalSkillsDir,
|
|
17
|
+
getSkillFromLock,
|
|
18
|
+
he,
|
|
19
|
+
initTelemetry,
|
|
20
|
+
isRepoPrivate,
|
|
21
|
+
listInstalledSkills,
|
|
22
|
+
pD,
|
|
23
|
+
parseAddOptions,
|
|
24
|
+
parseSyncOptions,
|
|
25
|
+
removeSkillFromLock,
|
|
26
|
+
require_picocolors,
|
|
27
|
+
runAdd,
|
|
28
|
+
runInstallFromLock,
|
|
29
|
+
runSync,
|
|
30
|
+
sanitizeName,
|
|
31
|
+
track,
|
|
32
|
+
ve,
|
|
33
|
+
xe,
|
|
34
|
+
ye
|
|
35
|
+
} from "./chunk-US7NTYE7.js";
|
|
36
|
+
import {
|
|
37
|
+
ConfigGeneratorRegistry,
|
|
38
|
+
GitHubCopilotGenerator,
|
|
39
|
+
KiroGenerator,
|
|
40
|
+
McpConfigAdapterRegistry,
|
|
41
|
+
OpenCodeAgentGenerator,
|
|
42
|
+
OpenCodeMcpGenerator,
|
|
43
|
+
VsCodeGenerator
|
|
44
|
+
} from "./chunk-OAWSLH2D.js";
|
|
45
|
+
import {
|
|
46
|
+
__toESM
|
|
47
|
+
} from "./chunk-JSBRDJBE.js";
|
|
48
|
+
|
|
49
|
+
// src/cli.ts
|
|
50
|
+
import { spawnSync } from "child_process";
|
|
51
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync } from "fs";
|
|
52
|
+
import { basename, join as join3, dirname as dirname2 } from "path";
|
|
53
|
+
import { homedir as homedir4 } from "os";
|
|
54
|
+
import { fileURLToPath } from "url";
|
|
55
|
+
|
|
56
|
+
// src/find.ts
|
|
57
|
+
import * as readline from "readline";
|
|
58
|
+
var RESET = "\x1B[0m";
|
|
59
|
+
var BOLD = "\x1B[1m";
|
|
60
|
+
var DIM = "\x1B[38;5;102m";
|
|
61
|
+
var TEXT = "\x1B[38;5;145m";
|
|
62
|
+
var CYAN = "\x1B[36m";
|
|
63
|
+
var SEARCH_API_BASE = process.env.SKILLS_API_URL || "https://skills.sh";
|
|
64
|
+
function formatInstalls(count) {
|
|
65
|
+
if (!count || count <= 0) return "";
|
|
66
|
+
if (count >= 1e6) return `${(count / 1e6).toFixed(1).replace(/\.0$/, "")}M installs`;
|
|
67
|
+
if (count >= 1e3) return `${(count / 1e3).toFixed(1).replace(/\.0$/, "")}K installs`;
|
|
68
|
+
return `${count} install${count === 1 ? "" : "s"}`;
|
|
69
|
+
}
|
|
70
|
+
async function searchSkillsAPI(query) {
|
|
71
|
+
try {
|
|
72
|
+
const url = `${SEARCH_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=10`;
|
|
73
|
+
const res = await fetch(url);
|
|
74
|
+
if (!res.ok) return [];
|
|
75
|
+
const data = await res.json();
|
|
76
|
+
return data.skills.map((skill) => ({
|
|
77
|
+
name: skill.name,
|
|
78
|
+
slug: skill.id,
|
|
79
|
+
source: skill.source || "",
|
|
80
|
+
installs: skill.installs
|
|
81
|
+
}));
|
|
82
|
+
} catch {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
87
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
88
|
+
var CLEAR_DOWN = "\x1B[J";
|
|
89
|
+
var MOVE_UP = (n) => `\x1B[${n}A`;
|
|
90
|
+
var MOVE_TO_COL = (n) => `\x1B[${n}G`;
|
|
91
|
+
async function runSearchPrompt(initialQuery = "") {
|
|
92
|
+
let results = [];
|
|
93
|
+
let selectedIndex = 0;
|
|
94
|
+
let query = initialQuery;
|
|
95
|
+
let loading = false;
|
|
96
|
+
let debounceTimer = null;
|
|
97
|
+
let lastRenderedLines = 0;
|
|
98
|
+
if (process.stdin.isTTY) {
|
|
99
|
+
process.stdin.setRawMode(true);
|
|
100
|
+
}
|
|
101
|
+
readline.emitKeypressEvents(process.stdin);
|
|
102
|
+
process.stdin.resume();
|
|
103
|
+
process.stdout.write(HIDE_CURSOR);
|
|
104
|
+
function render() {
|
|
105
|
+
if (lastRenderedLines > 0) {
|
|
106
|
+
process.stdout.write(MOVE_UP(lastRenderedLines) + MOVE_TO_COL(1));
|
|
107
|
+
}
|
|
108
|
+
process.stdout.write(CLEAR_DOWN);
|
|
109
|
+
const lines = [];
|
|
110
|
+
const cursor = `${BOLD}_${RESET}`;
|
|
111
|
+
lines.push(`${TEXT}Search skills:${RESET} ${query}${cursor}`);
|
|
112
|
+
lines.push("");
|
|
113
|
+
if (!query || query.length < 2) {
|
|
114
|
+
lines.push(`${DIM}Start typing to search (min 2 chars)${RESET}`);
|
|
115
|
+
} else if (results.length === 0 && loading) {
|
|
116
|
+
lines.push(`${DIM}Searching...${RESET}`);
|
|
117
|
+
} else if (results.length === 0) {
|
|
118
|
+
lines.push(`${DIM}No skills found${RESET}`);
|
|
119
|
+
} else {
|
|
120
|
+
const maxVisible = 8;
|
|
121
|
+
const visible = results.slice(0, maxVisible);
|
|
122
|
+
for (let i = 0; i < visible.length; i++) {
|
|
123
|
+
const skill = visible[i];
|
|
124
|
+
const isSelected = i === selectedIndex;
|
|
125
|
+
const arrow = isSelected ? `${BOLD}>${RESET}` : " ";
|
|
126
|
+
const name = isSelected ? `${BOLD}${skill.name}${RESET}` : `${TEXT}${skill.name}${RESET}`;
|
|
127
|
+
const source = skill.source ? ` ${DIM}${skill.source}${RESET}` : "";
|
|
128
|
+
const installs = formatInstalls(skill.installs);
|
|
129
|
+
const installsBadge = installs ? ` ${CYAN}${installs}${RESET}` : "";
|
|
130
|
+
const loadingIndicator = loading && i === 0 ? ` ${DIM}...${RESET}` : "";
|
|
131
|
+
lines.push(` ${arrow} ${name}${source}${installsBadge}${loadingIndicator}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
lines.push("");
|
|
135
|
+
lines.push(`${DIM}up/down navigate | enter select | esc cancel${RESET}`);
|
|
136
|
+
for (const line of lines) {
|
|
137
|
+
process.stdout.write(line + "\n");
|
|
138
|
+
}
|
|
139
|
+
lastRenderedLines = lines.length;
|
|
140
|
+
}
|
|
141
|
+
function triggerSearch(q) {
|
|
142
|
+
if (debounceTimer) {
|
|
143
|
+
clearTimeout(debounceTimer);
|
|
144
|
+
debounceTimer = null;
|
|
145
|
+
}
|
|
146
|
+
loading = false;
|
|
147
|
+
if (!q || q.length < 2) {
|
|
148
|
+
results = [];
|
|
149
|
+
selectedIndex = 0;
|
|
150
|
+
render();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
loading = true;
|
|
154
|
+
render();
|
|
155
|
+
const debounceMs = Math.max(150, 350 - q.length * 50);
|
|
156
|
+
debounceTimer = setTimeout(async () => {
|
|
157
|
+
try {
|
|
158
|
+
results = await searchSkillsAPI(q);
|
|
159
|
+
selectedIndex = 0;
|
|
160
|
+
} catch {
|
|
161
|
+
results = [];
|
|
162
|
+
} finally {
|
|
163
|
+
loading = false;
|
|
164
|
+
debounceTimer = null;
|
|
165
|
+
render();
|
|
166
|
+
}
|
|
167
|
+
}, debounceMs);
|
|
168
|
+
}
|
|
169
|
+
if (initialQuery) {
|
|
170
|
+
triggerSearch(initialQuery);
|
|
171
|
+
}
|
|
172
|
+
render();
|
|
173
|
+
return new Promise((resolve) => {
|
|
174
|
+
function cleanup() {
|
|
175
|
+
process.stdin.removeListener("keypress", handleKeypress);
|
|
176
|
+
if (process.stdin.isTTY) {
|
|
177
|
+
process.stdin.setRawMode(false);
|
|
178
|
+
}
|
|
179
|
+
process.stdout.write(SHOW_CURSOR);
|
|
180
|
+
process.stdin.pause();
|
|
181
|
+
}
|
|
182
|
+
function handleKeypress(_ch, key) {
|
|
183
|
+
if (!key) return;
|
|
184
|
+
if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
185
|
+
cleanup();
|
|
186
|
+
resolve(null);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (key.name === "return") {
|
|
190
|
+
cleanup();
|
|
191
|
+
resolve(results[selectedIndex] || null);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (key.name === "up") {
|
|
195
|
+
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
196
|
+
render();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (key.name === "down") {
|
|
200
|
+
selectedIndex = Math.min(Math.max(0, results.length - 1), selectedIndex + 1);
|
|
201
|
+
render();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (key.name === "backspace") {
|
|
205
|
+
if (query.length > 0) {
|
|
206
|
+
query = query.slice(0, -1);
|
|
207
|
+
triggerSearch(query);
|
|
208
|
+
}
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
|
|
212
|
+
const char = key.sequence;
|
|
213
|
+
if (char >= " " && char <= "~") {
|
|
214
|
+
query += char;
|
|
215
|
+
triggerSearch(query);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
process.stdin.on("keypress", handleKeypress);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function getOwnerRepoFromString(pkg) {
|
|
223
|
+
const atIndex = pkg.lastIndexOf("@");
|
|
224
|
+
const repoPath = atIndex > 0 ? pkg.slice(0, atIndex) : pkg;
|
|
225
|
+
const match = repoPath.match(/^([^/]+)\/([^/]+)$/);
|
|
226
|
+
if (match) {
|
|
227
|
+
return { owner: match[1], repo: match[2] };
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
async function isRepoPublic(owner, repo) {
|
|
232
|
+
const isPrivate = await isRepoPrivate(owner, repo);
|
|
233
|
+
return isPrivate === false;
|
|
234
|
+
}
|
|
235
|
+
async function runFind(args) {
|
|
236
|
+
const query = args.join(" ");
|
|
237
|
+
const isNonInteractive = !process.stdin.isTTY;
|
|
238
|
+
const agentTip = `${DIM}Tip: if running in a coding agent, follow these steps:${RESET}
|
|
239
|
+
${DIM} 1) npx @codemcp/skills find [query]${RESET}
|
|
240
|
+
${DIM} 2) npx @codemcp/skills add <owner/repo@skill>${RESET}`;
|
|
241
|
+
if (query) {
|
|
242
|
+
const results = await searchSkillsAPI(query);
|
|
243
|
+
track({
|
|
244
|
+
event: "find",
|
|
245
|
+
query,
|
|
246
|
+
resultCount: String(results.length)
|
|
247
|
+
});
|
|
248
|
+
if (results.length === 0) {
|
|
249
|
+
console.log(`${DIM}No skills found for "${query}"${RESET}`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
console.log(`${DIM}Install with${RESET} npx @codemcp/skills add <owner/repo@skill>`);
|
|
253
|
+
console.log();
|
|
254
|
+
for (const skill of results.slice(0, 6)) {
|
|
255
|
+
const pkg2 = skill.source || skill.slug;
|
|
256
|
+
const installs = formatInstalls(skill.installs);
|
|
257
|
+
console.log(
|
|
258
|
+
`${TEXT}${pkg2}@${skill.name}${RESET}${installs ? ` ${CYAN}${installs}${RESET}` : ""}`
|
|
259
|
+
);
|
|
260
|
+
console.log(`${DIM}\u2514 https://skills.sh/${skill.slug}${RESET}`);
|
|
261
|
+
console.log();
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (isNonInteractive) {
|
|
266
|
+
console.log(agentTip);
|
|
267
|
+
console.log();
|
|
268
|
+
}
|
|
269
|
+
const selected = await runSearchPrompt();
|
|
270
|
+
track({
|
|
271
|
+
event: "find",
|
|
272
|
+
query: "",
|
|
273
|
+
resultCount: selected ? "1" : "0",
|
|
274
|
+
interactive: "1"
|
|
275
|
+
});
|
|
276
|
+
if (!selected) {
|
|
277
|
+
console.log(`${DIM}Search cancelled${RESET}`);
|
|
278
|
+
console.log();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const pkg = selected.source || selected.slug;
|
|
282
|
+
const skillName = selected.name;
|
|
283
|
+
console.log();
|
|
284
|
+
console.log(`${TEXT}Installing ${BOLD}${skillName}${RESET} from ${DIM}${pkg}${RESET}...`);
|
|
285
|
+
console.log();
|
|
286
|
+
const { source, options } = parseAddOptions([pkg, "--skill", skillName]);
|
|
287
|
+
await runAdd(source, options);
|
|
288
|
+
console.log();
|
|
289
|
+
const info = getOwnerRepoFromString(pkg);
|
|
290
|
+
if (info && await isRepoPublic(info.owner, info.repo)) {
|
|
291
|
+
console.log(
|
|
292
|
+
`${DIM}View the skill at${RESET} ${TEXT}https://skills.sh/${selected.slug}${RESET}`
|
|
293
|
+
);
|
|
294
|
+
} else {
|
|
295
|
+
console.log(`${DIM}Discover more skills at${RESET} ${TEXT}https://skills.sh${RESET}`);
|
|
296
|
+
}
|
|
297
|
+
console.log();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/list.ts
|
|
301
|
+
import { homedir } from "os";
|
|
302
|
+
var RESET2 = "\x1B[0m";
|
|
303
|
+
var BOLD2 = "\x1B[1m";
|
|
304
|
+
var DIM2 = "\x1B[38;5;102m";
|
|
305
|
+
var CYAN2 = "\x1B[36m";
|
|
306
|
+
var YELLOW = "\x1B[33m";
|
|
307
|
+
function shortenPath(fullPath, cwd) {
|
|
308
|
+
const home = homedir();
|
|
309
|
+
if (fullPath.startsWith(home)) {
|
|
310
|
+
return fullPath.replace(home, "~");
|
|
311
|
+
}
|
|
312
|
+
if (fullPath.startsWith(cwd)) {
|
|
313
|
+
return "." + fullPath.slice(cwd.length);
|
|
314
|
+
}
|
|
315
|
+
return fullPath;
|
|
316
|
+
}
|
|
317
|
+
function formatList(items, maxShow = 5) {
|
|
318
|
+
if (items.length <= maxShow) {
|
|
319
|
+
return items.join(", ");
|
|
320
|
+
}
|
|
321
|
+
const shown = items.slice(0, maxShow);
|
|
322
|
+
const remaining = items.length - maxShow;
|
|
323
|
+
return `${shown.join(", ")} +${remaining} more`;
|
|
324
|
+
}
|
|
325
|
+
function parseListOptions(args) {
|
|
326
|
+
const options = {};
|
|
327
|
+
for (let i = 0; i < args.length; i++) {
|
|
328
|
+
const arg = args[i];
|
|
329
|
+
if (arg === "-g" || arg === "--global") {
|
|
330
|
+
options.global = true;
|
|
331
|
+
} else if (arg === "-a" || arg === "--agent") {
|
|
332
|
+
options.agent = options.agent || [];
|
|
333
|
+
while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
334
|
+
options.agent.push(args[++i]);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return options;
|
|
339
|
+
}
|
|
340
|
+
async function runList(args) {
|
|
341
|
+
const options = parseListOptions(args);
|
|
342
|
+
const scope = options.global === true ? true : false;
|
|
343
|
+
let agentFilter;
|
|
344
|
+
if (options.agent && options.agent.length > 0) {
|
|
345
|
+
const validAgents = Object.keys(agents);
|
|
346
|
+
const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
|
|
347
|
+
if (invalidAgents.length > 0) {
|
|
348
|
+
console.log(`${YELLOW}Invalid agents: ${invalidAgents.join(", ")}${RESET2}`);
|
|
349
|
+
console.log(`${DIM2}Valid agents: ${validAgents.join(", ")}${RESET2}`);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
agentFilter = options.agent;
|
|
353
|
+
}
|
|
354
|
+
const installedSkills = await listInstalledSkills({
|
|
355
|
+
global: scope,
|
|
356
|
+
agentFilter
|
|
357
|
+
});
|
|
358
|
+
const lockedSkills = await getAllLockedSkills();
|
|
359
|
+
const cwd = process.cwd();
|
|
360
|
+
const scopeLabel = scope ? "Global" : "Project";
|
|
361
|
+
if (installedSkills.length === 0) {
|
|
362
|
+
console.log(`${DIM2}No ${scopeLabel.toLowerCase()} skills found.${RESET2}`);
|
|
363
|
+
if (scope) {
|
|
364
|
+
console.log(`${DIM2}Try listing project skills without -g${RESET2}`);
|
|
365
|
+
} else {
|
|
366
|
+
console.log(`${DIM2}Try listing global skills with -g${RESET2}`);
|
|
367
|
+
}
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
function printSkill(skill, indent = false) {
|
|
371
|
+
const prefix = indent ? " " : "";
|
|
372
|
+
const shortPath = shortenPath(skill.canonicalPath, cwd);
|
|
373
|
+
const agentNames = skill.agents.map((a) => agents[a].displayName);
|
|
374
|
+
const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET2}`;
|
|
375
|
+
console.log(`${prefix}${CYAN2}${skill.name}${RESET2} ${DIM2}${shortPath}${RESET2}`);
|
|
376
|
+
console.log(`${prefix} ${DIM2}Agents:${RESET2} ${agentInfo}`);
|
|
377
|
+
}
|
|
378
|
+
console.log(`${BOLD2}${scopeLabel} Skills${RESET2}`);
|
|
379
|
+
console.log();
|
|
380
|
+
const groupedSkills = {};
|
|
381
|
+
const ungroupedSkills = [];
|
|
382
|
+
for (const skill of installedSkills) {
|
|
383
|
+
const lockEntry = lockedSkills[skill.name];
|
|
384
|
+
if (lockEntry?.pluginName) {
|
|
385
|
+
const group = lockEntry.pluginName;
|
|
386
|
+
if (!groupedSkills[group]) {
|
|
387
|
+
groupedSkills[group] = [];
|
|
388
|
+
}
|
|
389
|
+
groupedSkills[group].push(skill);
|
|
390
|
+
} else {
|
|
391
|
+
ungroupedSkills.push(skill);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const hasGroups = Object.keys(groupedSkills).length > 0;
|
|
395
|
+
if (hasGroups) {
|
|
396
|
+
const sortedGroups = Object.keys(groupedSkills).sort();
|
|
397
|
+
for (const group of sortedGroups) {
|
|
398
|
+
const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
399
|
+
console.log(`${BOLD2}${title}${RESET2}`);
|
|
400
|
+
const skills = groupedSkills[group];
|
|
401
|
+
if (skills) {
|
|
402
|
+
for (const skill of skills) {
|
|
403
|
+
printSkill(skill, true);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
console.log();
|
|
407
|
+
}
|
|
408
|
+
if (ungroupedSkills.length > 0) {
|
|
409
|
+
console.log(`${BOLD2}General${RESET2}`);
|
|
410
|
+
for (const skill of ungroupedSkills) {
|
|
411
|
+
printSkill(skill, true);
|
|
412
|
+
}
|
|
413
|
+
console.log();
|
|
414
|
+
}
|
|
415
|
+
} else {
|
|
416
|
+
for (const skill of installedSkills) {
|
|
417
|
+
printSkill(skill);
|
|
418
|
+
}
|
|
419
|
+
console.log();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// src/remove.ts
|
|
424
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
425
|
+
import { readdir, rm, lstat } from "fs/promises";
|
|
426
|
+
import { join } from "path";
|
|
427
|
+
async function removeCommand(skillNames, options) {
|
|
428
|
+
const isGlobal = options.global ?? false;
|
|
429
|
+
const cwd = process.cwd();
|
|
430
|
+
const spinner = Y();
|
|
431
|
+
spinner.start("Scanning for installed skills...");
|
|
432
|
+
const skillNamesSet = /* @__PURE__ */ new Set();
|
|
433
|
+
const scanDir = async (dir) => {
|
|
434
|
+
try {
|
|
435
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
436
|
+
for (const entry of entries) {
|
|
437
|
+
if (entry.isDirectory()) {
|
|
438
|
+
skillNamesSet.add(entry.name);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
} catch (err) {
|
|
442
|
+
if (err instanceof Error && err.code !== "ENOENT") {
|
|
443
|
+
M.warn(`Could not scan directory ${dir}: ${err.message}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
if (isGlobal) {
|
|
448
|
+
await scanDir(getCanonicalSkillsDir(true, cwd));
|
|
449
|
+
for (const agent of Object.values(agents)) {
|
|
450
|
+
if (agent.globalSkillsDir !== void 0) {
|
|
451
|
+
await scanDir(agent.globalSkillsDir);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
} else {
|
|
455
|
+
await scanDir(getCanonicalSkillsDir(false, cwd));
|
|
456
|
+
for (const agent of Object.values(agents)) {
|
|
457
|
+
await scanDir(join(cwd, agent.skillsDir));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const installedSkills = Array.from(skillNamesSet).sort();
|
|
461
|
+
spinner.stop(`Found ${installedSkills.length} unique installed skill(s)`);
|
|
462
|
+
if (installedSkills.length === 0) {
|
|
463
|
+
Se(import_picocolors.default.yellow("No skills found to remove."));
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (options.agent && options.agent.length > 0) {
|
|
467
|
+
const validAgents = Object.keys(agents);
|
|
468
|
+
const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
|
|
469
|
+
if (invalidAgents.length > 0) {
|
|
470
|
+
M.error(`Invalid agents: ${invalidAgents.join(", ")}`);
|
|
471
|
+
M.info(`Valid agents: ${validAgents.join(", ")}`);
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
let selectedSkills = [];
|
|
476
|
+
if (options.all) {
|
|
477
|
+
selectedSkills = installedSkills;
|
|
478
|
+
} else if (skillNames.length > 0) {
|
|
479
|
+
selectedSkills = installedSkills.filter(
|
|
480
|
+
(s) => skillNames.some((name) => name.toLowerCase() === s.toLowerCase())
|
|
481
|
+
);
|
|
482
|
+
if (selectedSkills.length === 0) {
|
|
483
|
+
M.error(`No matching skills found for: ${skillNames.join(", ")}`);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
const choices = installedSkills.map((s) => ({
|
|
488
|
+
value: s,
|
|
489
|
+
label: s
|
|
490
|
+
}));
|
|
491
|
+
const selected = await fe({
|
|
492
|
+
message: `Select skills to remove ${import_picocolors.default.dim("(space to toggle)")}`,
|
|
493
|
+
options: choices,
|
|
494
|
+
required: true
|
|
495
|
+
});
|
|
496
|
+
if (pD(selected)) {
|
|
497
|
+
xe("Removal cancelled");
|
|
498
|
+
process.exit(0);
|
|
499
|
+
}
|
|
500
|
+
selectedSkills = selected;
|
|
501
|
+
}
|
|
502
|
+
let targetAgents;
|
|
503
|
+
if (options.agent && options.agent.length > 0) {
|
|
504
|
+
targetAgents = options.agent;
|
|
505
|
+
} else {
|
|
506
|
+
targetAgents = Object.keys(agents);
|
|
507
|
+
spinner.stop(`Targeting ${targetAgents.length} potential agent(s)`);
|
|
508
|
+
}
|
|
509
|
+
if (!options.yes) {
|
|
510
|
+
console.log();
|
|
511
|
+
M.info("Skills to remove:");
|
|
512
|
+
for (const skill of selectedSkills) {
|
|
513
|
+
M.message(` ${import_picocolors.default.red("\u2022")} ${skill}`);
|
|
514
|
+
}
|
|
515
|
+
console.log();
|
|
516
|
+
const confirmed = await ye({
|
|
517
|
+
message: `Are you sure you want to uninstall ${selectedSkills.length} skill(s)?`
|
|
518
|
+
});
|
|
519
|
+
if (pD(confirmed) || !confirmed) {
|
|
520
|
+
xe("Removal cancelled");
|
|
521
|
+
process.exit(0);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
spinner.start("Removing skills...");
|
|
525
|
+
const results = [];
|
|
526
|
+
for (const skillName of selectedSkills) {
|
|
527
|
+
try {
|
|
528
|
+
const canonicalPath = getCanonicalPath(skillName, { global: isGlobal, cwd });
|
|
529
|
+
for (const agentKey of targetAgents) {
|
|
530
|
+
const agent = agents[agentKey];
|
|
531
|
+
const skillPath = getInstallPath(skillName, agentKey, { global: isGlobal, cwd });
|
|
532
|
+
const pathsToCleanup = /* @__PURE__ */ new Set([skillPath]);
|
|
533
|
+
const sanitizedName = sanitizeName(skillName);
|
|
534
|
+
if (isGlobal && agent.globalSkillsDir) {
|
|
535
|
+
pathsToCleanup.add(join(agent.globalSkillsDir, sanitizedName));
|
|
536
|
+
} else {
|
|
537
|
+
pathsToCleanup.add(join(cwd, agent.skillsDir, sanitizedName));
|
|
538
|
+
}
|
|
539
|
+
for (const pathToCleanup of pathsToCleanup) {
|
|
540
|
+
if (pathToCleanup === canonicalPath) {
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
const stats = await lstat(pathToCleanup).catch(() => null);
|
|
545
|
+
if (stats) {
|
|
546
|
+
await rm(pathToCleanup, { recursive: true, force: true });
|
|
547
|
+
}
|
|
548
|
+
} catch (err) {
|
|
549
|
+
M.warn(
|
|
550
|
+
`Could not remove skill from ${agent.displayName}: ${err instanceof Error ? err.message : String(err)}`
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const installedAgents = await detectInstalledAgents();
|
|
556
|
+
const remainingAgents = installedAgents.filter((a) => !targetAgents.includes(a));
|
|
557
|
+
let isStillUsed = false;
|
|
558
|
+
for (const agentKey of remainingAgents) {
|
|
559
|
+
const path = getInstallPath(skillName, agentKey, { global: isGlobal, cwd });
|
|
560
|
+
const exists = await lstat(path).catch(() => null);
|
|
561
|
+
if (exists) {
|
|
562
|
+
isStillUsed = true;
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (!isStillUsed) {
|
|
567
|
+
await rm(canonicalPath, { recursive: true, force: true });
|
|
568
|
+
}
|
|
569
|
+
const lockEntry = isGlobal ? await getSkillFromLock(skillName) : null;
|
|
570
|
+
const effectiveSource = lockEntry?.source || "local";
|
|
571
|
+
const effectiveSourceType = lockEntry?.sourceType || "local";
|
|
572
|
+
if (isGlobal) {
|
|
573
|
+
await removeSkillFromLock(skillName);
|
|
574
|
+
}
|
|
575
|
+
results.push({
|
|
576
|
+
skill: skillName,
|
|
577
|
+
success: true,
|
|
578
|
+
source: effectiveSource,
|
|
579
|
+
sourceType: effectiveSourceType
|
|
580
|
+
});
|
|
581
|
+
} catch (err) {
|
|
582
|
+
results.push({
|
|
583
|
+
skill: skillName,
|
|
584
|
+
success: false,
|
|
585
|
+
error: err instanceof Error ? err.message : String(err)
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
spinner.stop("Removal process complete");
|
|
590
|
+
const successful = results.filter((r) => r.success);
|
|
591
|
+
const failed = results.filter((r) => !r.success);
|
|
592
|
+
if (successful.length > 0) {
|
|
593
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
594
|
+
for (const r of successful) {
|
|
595
|
+
const source = r.source || "local";
|
|
596
|
+
const existing = bySource.get(source) || { skills: [] };
|
|
597
|
+
existing.skills.push(r.skill);
|
|
598
|
+
existing.sourceType = r.sourceType;
|
|
599
|
+
bySource.set(source, existing);
|
|
600
|
+
}
|
|
601
|
+
for (const [source, data] of bySource) {
|
|
602
|
+
track({
|
|
603
|
+
event: "remove",
|
|
604
|
+
source,
|
|
605
|
+
skills: data.skills.join(","),
|
|
606
|
+
agents: targetAgents.join(","),
|
|
607
|
+
...isGlobal && { global: "1" },
|
|
608
|
+
sourceType: data.sourceType
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (successful.length > 0) {
|
|
613
|
+
M.success(import_picocolors.default.green(`Successfully removed ${successful.length} skill(s)`));
|
|
614
|
+
}
|
|
615
|
+
if (failed.length > 0) {
|
|
616
|
+
M.error(import_picocolors.default.red(`Failed to remove ${failed.length} skill(s)`));
|
|
617
|
+
for (const r of failed) {
|
|
618
|
+
M.message(` ${import_picocolors.default.red("\u2717")} ${r.skill}: ${r.error}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
console.log();
|
|
622
|
+
Se(import_picocolors.default.green("Done!"));
|
|
623
|
+
}
|
|
624
|
+
function parseRemoveOptions(args) {
|
|
625
|
+
const options = {};
|
|
626
|
+
const skills = [];
|
|
627
|
+
for (let i = 0; i < args.length; i++) {
|
|
628
|
+
const arg = args[i];
|
|
629
|
+
if (arg === "-g" || arg === "--global") {
|
|
630
|
+
options.global = true;
|
|
631
|
+
} else if (arg === "-y" || arg === "--yes") {
|
|
632
|
+
options.yes = true;
|
|
633
|
+
} else if (arg === "--all") {
|
|
634
|
+
options.all = true;
|
|
635
|
+
} else if (arg === "-a" || arg === "--agent") {
|
|
636
|
+
options.agent = options.agent || [];
|
|
637
|
+
i++;
|
|
638
|
+
let nextArg = args[i];
|
|
639
|
+
while (i < args.length && nextArg && !nextArg.startsWith("-")) {
|
|
640
|
+
options.agent.push(nextArg);
|
|
641
|
+
i++;
|
|
642
|
+
nextArg = args[i];
|
|
643
|
+
}
|
|
644
|
+
i--;
|
|
645
|
+
} else if (arg && !arg.startsWith("-")) {
|
|
646
|
+
skills.push(arg);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return { skills, options };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/mcp.ts
|
|
653
|
+
import { homedir as homedir3 } from "os";
|
|
654
|
+
|
|
655
|
+
// src/mcp-configurator.ts
|
|
656
|
+
import { promises as fs } from "fs";
|
|
657
|
+
import { join as join2, dirname } from "path";
|
|
658
|
+
import { homedir as homedir2 } from "os";
|
|
659
|
+
var AGENT_TO_MCP_CLIENT = {
|
|
660
|
+
"claude-code": "claude-desktop",
|
|
661
|
+
claude: "claude-desktop",
|
|
662
|
+
cline: "cline",
|
|
663
|
+
cursor: "cursor",
|
|
664
|
+
"kiro-cli": "kiro",
|
|
665
|
+
kiro: "kiro",
|
|
666
|
+
junie: "junie",
|
|
667
|
+
opencode: "opencode",
|
|
668
|
+
zed: "zed",
|
|
669
|
+
continue: "continue",
|
|
670
|
+
"github-copilot": "github-copilot",
|
|
671
|
+
"mistral-vibe": "mistral-vibe",
|
|
672
|
+
windsurf: "windsurf",
|
|
673
|
+
codex: "codex",
|
|
674
|
+
"command-code": "command-code",
|
|
675
|
+
cortex: "cortex",
|
|
676
|
+
crush: "crush",
|
|
677
|
+
droid: "droid",
|
|
678
|
+
"gemini-cli": "gemini-cli",
|
|
679
|
+
goose: "goose",
|
|
680
|
+
"iflow-cli": "iflow-cli",
|
|
681
|
+
kilo: "kilo",
|
|
682
|
+
"kimi-cli": "kimi-cli",
|
|
683
|
+
kode: "kode",
|
|
684
|
+
mcpjam: "mcpjam",
|
|
685
|
+
mux: "mux",
|
|
686
|
+
neovate: "neovate",
|
|
687
|
+
openhands: "openhands",
|
|
688
|
+
pi: "pi",
|
|
689
|
+
qoder: "qoder",
|
|
690
|
+
"qwen-code": "qwen-code",
|
|
691
|
+
replit: "replit",
|
|
692
|
+
roo: "roo",
|
|
693
|
+
trae: "trae",
|
|
694
|
+
"trae-cn": "trae-cn",
|
|
695
|
+
zencoder: "zencoder",
|
|
696
|
+
pochi: "pochi",
|
|
697
|
+
adal: "adal",
|
|
698
|
+
universal: "universal",
|
|
699
|
+
amp: "amp",
|
|
700
|
+
antigravity: "antigravity",
|
|
701
|
+
augment: "augment",
|
|
702
|
+
openclaw: "openclaw",
|
|
703
|
+
codebuddy: "codebuddy"
|
|
704
|
+
};
|
|
705
|
+
function getAgentConfigPath(agentType, cwd, scope = "local") {
|
|
706
|
+
const mappedType = AGENT_TO_MCP_CLIENT[agentType] || agentType;
|
|
707
|
+
switch (mappedType) {
|
|
708
|
+
case "claude-desktop":
|
|
709
|
+
return join2(cwd, ".claude", "mcp.json");
|
|
710
|
+
case "cline":
|
|
711
|
+
return join2(cwd, ".cline", "mcp.json");
|
|
712
|
+
case "cursor":
|
|
713
|
+
return join2(cwd, ".cursor", "mcp.json");
|
|
714
|
+
case "kiro":
|
|
715
|
+
if (scope === "global") {
|
|
716
|
+
return join2(cwd, ".kiro", "agents", "default.json");
|
|
717
|
+
}
|
|
718
|
+
return join2(cwd, ".kiro", "mcp.json");
|
|
719
|
+
case "github-copilot":
|
|
720
|
+
return join2(cwd, ".vscode", "mcp.json");
|
|
721
|
+
case "junie":
|
|
722
|
+
return join2(cwd, ".junie", "mcp.json");
|
|
723
|
+
case "opencode":
|
|
724
|
+
if (scope === "global") {
|
|
725
|
+
return join2(cwd, ".config", "opencode", "opencode.json");
|
|
726
|
+
}
|
|
727
|
+
return join2(cwd, "opencode.json");
|
|
728
|
+
case "zed":
|
|
729
|
+
return join2(cwd, ".config", "zed", "settings.json");
|
|
730
|
+
case "continue":
|
|
731
|
+
return join2(cwd, ".continue", "config.json");
|
|
732
|
+
// For other agents, try to infer config path
|
|
733
|
+
default:
|
|
734
|
+
const sanitized = mappedType.replace(/[^a-z0-9-]/gi, "_").toLowerCase();
|
|
735
|
+
return join2(cwd, `.${sanitized}`, "mcp.json");
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
async function readAgentConfig(configPath, agentType) {
|
|
739
|
+
try {
|
|
740
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
741
|
+
const raw = JSON.parse(content);
|
|
742
|
+
if (agentType) {
|
|
743
|
+
const mappedType = AGENT_TO_MCP_CLIENT[agentType] || agentType;
|
|
744
|
+
const adapter = McpConfigAdapterRegistry.getAdapter(mappedType);
|
|
745
|
+
return adapter.toStandard(raw);
|
|
746
|
+
}
|
|
747
|
+
return raw;
|
|
748
|
+
} catch (error) {
|
|
749
|
+
if (error.code === "ENOENT") {
|
|
750
|
+
return { mcpServers: {} };
|
|
751
|
+
}
|
|
752
|
+
throw error;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
async function writeAgentConfig(configPath, config) {
|
|
756
|
+
const dir = dirname(configPath);
|
|
757
|
+
await fs.mkdir(dir, { recursive: true });
|
|
758
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
759
|
+
}
|
|
760
|
+
async function configureAgentMcp(agentType, cwd, scope = "local") {
|
|
761
|
+
if (!agentType || typeof agentType !== "string") {
|
|
762
|
+
throw new Error(`Invalid agent type: ${agentType}`);
|
|
763
|
+
}
|
|
764
|
+
if (!AGENT_TO_MCP_CLIENT[agentType] && !isValidMcpClientType(agentType)) {
|
|
765
|
+
throw new Error(`Unknown agent type: ${agentType}`);
|
|
766
|
+
}
|
|
767
|
+
const configPath = getAgentConfigPath(agentType, cwd, scope);
|
|
768
|
+
let config = await readAgentConfig(configPath, agentType);
|
|
769
|
+
if (!config.mcpServers) {
|
|
770
|
+
config.mcpServers = {};
|
|
771
|
+
}
|
|
772
|
+
const mcpServerConfig = {
|
|
773
|
+
command: "npx",
|
|
774
|
+
args: ["-y", "@codemcp/skills-server"]
|
|
775
|
+
};
|
|
776
|
+
config.mcpServers.agentskills = mcpServerConfig;
|
|
777
|
+
const mappedType = AGENT_TO_MCP_CLIENT[agentType] || agentType;
|
|
778
|
+
const adapter = McpConfigAdapterRegistry.getAdapter(mappedType);
|
|
779
|
+
let existingAgentConfig;
|
|
780
|
+
try {
|
|
781
|
+
const existingContent = await fs.readFile(configPath, "utf-8");
|
|
782
|
+
existingAgentConfig = JSON.parse(existingContent);
|
|
783
|
+
} catch {
|
|
784
|
+
existingAgentConfig = void 0;
|
|
785
|
+
}
|
|
786
|
+
const agentSpecificConfig = adapter.toClient(config, existingAgentConfig);
|
|
787
|
+
await writeAgentConfig(configPath, agentSpecificConfig);
|
|
788
|
+
}
|
|
789
|
+
function isValidMcpClientType(type) {
|
|
790
|
+
const validTypes = [
|
|
791
|
+
"claude-desktop",
|
|
792
|
+
"cline",
|
|
793
|
+
"cursor",
|
|
794
|
+
"kiro",
|
|
795
|
+
"junie",
|
|
796
|
+
"opencode",
|
|
797
|
+
"zed",
|
|
798
|
+
"continue",
|
|
799
|
+
"codium"
|
|
800
|
+
];
|
|
801
|
+
return validTypes.includes(type);
|
|
802
|
+
}
|
|
803
|
+
function buildConfigGeneratorRegistry() {
|
|
804
|
+
const registry = new ConfigGeneratorRegistry();
|
|
805
|
+
registry.register(new VsCodeGenerator());
|
|
806
|
+
registry.register(new KiroGenerator());
|
|
807
|
+
registry.register(new OpenCodeMcpGenerator());
|
|
808
|
+
return registry;
|
|
809
|
+
}
|
|
810
|
+
async function safeWrite(filePath, content) {
|
|
811
|
+
try {
|
|
812
|
+
const stat = await fs.stat(filePath);
|
|
813
|
+
if (stat.isDirectory()) {
|
|
814
|
+
throw new Error(
|
|
815
|
+
`Generator returned a directory path instead of a file path: ${filePath}. Generators must write to a specific named file and must never clear or overwrite directories.`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
} catch (e) {
|
|
819
|
+
if (e.code !== "ENOENT") throw e;
|
|
820
|
+
}
|
|
821
|
+
const dir = dirname(filePath);
|
|
822
|
+
await fs.mkdir(dir, { recursive: true });
|
|
823
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
824
|
+
}
|
|
825
|
+
async function writeGeneratedConfig(generatedConfig) {
|
|
826
|
+
if (generatedConfig.files) {
|
|
827
|
+
for (const file of generatedConfig.files) {
|
|
828
|
+
const content = typeof file.content === "string" ? file.content : JSON.stringify(file.content, null, 2);
|
|
829
|
+
await safeWrite(file.path, content);
|
|
830
|
+
}
|
|
831
|
+
} else {
|
|
832
|
+
const content = typeof generatedConfig.content === "string" ? generatedConfig.content : JSON.stringify(generatedConfig.content, null, 2);
|
|
833
|
+
await safeWrite(generatedConfig.filePath, content);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
async function generateSkillsMcpAgent(agentType, cwd, scope = "local", extraServers, includeAgentConfig = true) {
|
|
837
|
+
const registry = buildConfigGeneratorRegistry();
|
|
838
|
+
const skillsDir = scope === "global" ? homedir2() : cwd;
|
|
839
|
+
const baseConfig = {
|
|
840
|
+
id: "skills-mcp",
|
|
841
|
+
description: "Agent-skills MCP server with use_skill tool access",
|
|
842
|
+
mcp_servers: {
|
|
843
|
+
"agent-skills": {
|
|
844
|
+
type: "stdio",
|
|
845
|
+
command: "npx",
|
|
846
|
+
args: ["-y", "@codemcp/skills-server"],
|
|
847
|
+
tools: ["*"]
|
|
848
|
+
},
|
|
849
|
+
...extraServers
|
|
850
|
+
},
|
|
851
|
+
tools: { use_skill: true },
|
|
852
|
+
permissions: { use_skill: "allow" }
|
|
853
|
+
};
|
|
854
|
+
const generatorOptions = {
|
|
855
|
+
skillsDir,
|
|
856
|
+
agentId: "skills-mcp",
|
|
857
|
+
scope,
|
|
858
|
+
isGlobal: scope === "global",
|
|
859
|
+
includeAgentConfig
|
|
860
|
+
};
|
|
861
|
+
const generator = registry.getGenerator(agentType);
|
|
862
|
+
if (!generator) {
|
|
863
|
+
throw new Error(
|
|
864
|
+
`No config generator found for agent type: ${agentType}. Supported types: ${registry.getSupportedAgentTypes().join(", ")}`
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
await writeGeneratedConfig(await generator.generate(baseConfig, generatorOptions));
|
|
868
|
+
if (includeAgentConfig && generator instanceof VsCodeGenerator) {
|
|
869
|
+
const agentFileGenerator = new GitHubCopilotGenerator();
|
|
870
|
+
await writeGeneratedConfig(await agentFileGenerator.generate(baseConfig, generatorOptions));
|
|
871
|
+
}
|
|
872
|
+
if (includeAgentConfig && generator instanceof OpenCodeMcpGenerator) {
|
|
873
|
+
const agentFileGenerator = new OpenCodeAgentGenerator();
|
|
874
|
+
await writeGeneratedConfig(await agentFileGenerator.generate(baseConfig, generatorOptions));
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/mcp-skill-deps.ts
|
|
879
|
+
import { promises as fs2 } from "fs";
|
|
880
|
+
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
881
|
+
async function loadInstalledSkillMcpDeps(cwd, scope) {
|
|
882
|
+
const isGlobal = scope === "global";
|
|
883
|
+
const searchDirs = [
|
|
884
|
+
getCanonicalSkillsDir(isGlobal, cwd),
|
|
885
|
+
getMCPCanonicalSkillsDir(isGlobal, cwd)
|
|
886
|
+
];
|
|
887
|
+
const seen = /* @__PURE__ */ new Map();
|
|
888
|
+
const serverToolSets = /* @__PURE__ */ new Map();
|
|
889
|
+
for (const dir of searchDirs) {
|
|
890
|
+
try {
|
|
891
|
+
const skills = await discoverSkills(dir, void 0, { fullDepth: true });
|
|
892
|
+
for (const skill of skills) {
|
|
893
|
+
for (const dep of skill.requiresMcpServers ?? []) {
|
|
894
|
+
if (!seen.has(dep.name)) {
|
|
895
|
+
seen.set(dep.name, dep);
|
|
896
|
+
}
|
|
897
|
+
const skillAllowedTools = skill.allowedTools;
|
|
898
|
+
const serverName = dep.name;
|
|
899
|
+
if (!skillAllowedTools || skillAllowedTools.length === 0) {
|
|
900
|
+
serverToolSets.set(serverName, "wildcard");
|
|
901
|
+
} else if (serverToolSets.get(serverName) !== "wildcard") {
|
|
902
|
+
const prefix = `@${serverName}/`;
|
|
903
|
+
const serverTools = skillAllowedTools.filter((t) => t.startsWith(prefix)).map((t) => t.slice(prefix.length));
|
|
904
|
+
if (serverTools.length === 0) {
|
|
905
|
+
serverToolSets.set(serverName, "wildcard");
|
|
906
|
+
} else {
|
|
907
|
+
const existing = serverToolSets.get(serverName);
|
|
908
|
+
if (existing instanceof Set) {
|
|
909
|
+
for (const t of serverTools) existing.add(t);
|
|
910
|
+
} else {
|
|
911
|
+
serverToolSets.set(serverName, new Set(serverTools));
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
} catch {
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
const allowedToolsByServer = {};
|
|
921
|
+
for (const [serverName, toolsOrWildcard] of serverToolSets.entries()) {
|
|
922
|
+
if (toolsOrWildcard instanceof Set) {
|
|
923
|
+
allowedToolsByServer[serverName] = [...toolsOrWildcard];
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return { deps: [...seen.values()], allowedToolsByServer };
|
|
927
|
+
}
|
|
928
|
+
function substituteParam(value, params) {
|
|
929
|
+
return value.replace(
|
|
930
|
+
/\{\{([A-Za-z0-9_-]+)\}\}/g,
|
|
931
|
+
(_, key) => params[key] ?? `{{${key}}}`
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
function applyParams(dep, params) {
|
|
935
|
+
const result = {
|
|
936
|
+
command: dep.command
|
|
937
|
+
};
|
|
938
|
+
if (dep.args?.length) result.args = dep.args.map((a) => substituteParam(a, params));
|
|
939
|
+
if (dep.env && Object.keys(dep.env).length) {
|
|
940
|
+
result.env = Object.fromEntries(
|
|
941
|
+
Object.entries(dep.env).map(([k, v]) => [k, substituteParam(v, params)])
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
if (dep.cwd) result.cwd = dep.cwd;
|
|
945
|
+
return result;
|
|
946
|
+
}
|
|
947
|
+
async function resolveParameters(dep) {
|
|
948
|
+
const result = {};
|
|
949
|
+
if (!dep.parameters) return result;
|
|
950
|
+
for (const [paramName, spec] of Object.entries(dep.parameters)) {
|
|
951
|
+
let resolved = spec.default;
|
|
952
|
+
if (resolved?.startsWith("{{ENV:")) {
|
|
953
|
+
const m = resolved.match(/\{\{ENV:([A-Za-z0-9_]+)\}\}/);
|
|
954
|
+
if (m) resolved = process.env[m[1]] ?? void 0;
|
|
955
|
+
}
|
|
956
|
+
if (resolved !== void 0) {
|
|
957
|
+
result[paramName] = resolved;
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
if (!spec.required) continue;
|
|
961
|
+
const answer = await he({
|
|
962
|
+
message: `${import_picocolors2.default.cyan(dep.name)} needs ${import_picocolors2.default.bold(paramName)}: ${spec.description}`
|
|
963
|
+
});
|
|
964
|
+
if (pD(answer)) {
|
|
965
|
+
xe("MCP server configuration cancelled");
|
|
966
|
+
process.exit(0);
|
|
967
|
+
}
|
|
968
|
+
result[paramName] = answer;
|
|
969
|
+
}
|
|
970
|
+
return result;
|
|
971
|
+
}
|
|
972
|
+
async function configureSkillMcpDepsForAgents(deps, agentTypes, configCwd, scope, configMode = "agent-config", allowedToolsByServer = {}) {
|
|
973
|
+
if (deps.length === 0 || agentTypes.length === 0) return;
|
|
974
|
+
const resolvedConfigs = /* @__PURE__ */ new Map();
|
|
975
|
+
for (const dep of deps) {
|
|
976
|
+
const params = await resolveParameters(dep);
|
|
977
|
+
resolvedConfigs.set(dep.name, applyParams(dep, params));
|
|
978
|
+
}
|
|
979
|
+
const registry = buildConfigGeneratorRegistry();
|
|
980
|
+
let anyConfigured = false;
|
|
981
|
+
for (const agentType of agentTypes) {
|
|
982
|
+
const useAgentConfig = configMode === "agent-config" && registry.supports(agentType);
|
|
983
|
+
if (useAgentConfig) {
|
|
984
|
+
const missingServers = {};
|
|
985
|
+
for (const dep of deps) {
|
|
986
|
+
const resolved = resolvedConfigs.get(dep.name);
|
|
987
|
+
const restrictedTools = allowedToolsByServer[dep.name];
|
|
988
|
+
missingServers[dep.name] = {
|
|
989
|
+
command: resolved.command,
|
|
990
|
+
args: resolved.args,
|
|
991
|
+
env: resolved.env,
|
|
992
|
+
...resolved.cwd ? { cwd: resolved.cwd } : {},
|
|
993
|
+
// Only whitelist specific tools when the skill declares allowedTools
|
|
994
|
+
// for this server; otherwise leave undefined so generators use wildcard.
|
|
995
|
+
...restrictedTools ? { tools: restrictedTools } : {}
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
try {
|
|
999
|
+
await generateSkillsMcpAgent(agentType, configCwd, scope, missingServers);
|
|
1000
|
+
for (const dep of deps) {
|
|
1001
|
+
M.success(
|
|
1002
|
+
`${import_picocolors2.default.green("\u2713")} Added ${import_picocolors2.default.cyan(dep.name)} to ${import_picocolors2.default.dim(agents[agentType]?.displayName || agentType)}`
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
anyConfigured = true;
|
|
1006
|
+
} catch {
|
|
1007
|
+
M.warn(
|
|
1008
|
+
import_picocolors2.default.yellow(
|
|
1009
|
+
`Could not update agent config for ${agents[agentType]?.displayName || agentType} \u2014 add skill MCP servers manually`
|
|
1010
|
+
)
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
} else {
|
|
1014
|
+
const { McpConfigAdapterRegistry: McpConfigAdapterRegistry2 } = await import("./dist-TGUYKB34.js");
|
|
1015
|
+
const configPath = getAgentConfigPath(agentType, configCwd, scope);
|
|
1016
|
+
const config = await readAgentConfig(configPath, agentType);
|
|
1017
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
1018
|
+
let anyServerAdded = false;
|
|
1019
|
+
for (const dep of deps) {
|
|
1020
|
+
if (config.mcpServers[dep.name]) continue;
|
|
1021
|
+
config.mcpServers[dep.name] = resolvedConfigs.get(dep.name);
|
|
1022
|
+
anyServerAdded = true;
|
|
1023
|
+
M.success(
|
|
1024
|
+
`${import_picocolors2.default.green("\u2713")} Added ${import_picocolors2.default.cyan(dep.name)} to ${import_picocolors2.default.dim(agents[agentType]?.displayName || agentType)}`
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
if (anyServerAdded) {
|
|
1028
|
+
try {
|
|
1029
|
+
const { McpConfigAdapterRegistry: McpConfigAdapterRegistry3 } = await import("./dist-TGUYKB34.js");
|
|
1030
|
+
const adapter = McpConfigAdapterRegistry3.getAdapter(agentType);
|
|
1031
|
+
let existingAgentConfig;
|
|
1032
|
+
try {
|
|
1033
|
+
const existingContent = await fs2.readFile(configPath, "utf-8");
|
|
1034
|
+
existingAgentConfig = JSON.parse(existingContent);
|
|
1035
|
+
} catch {
|
|
1036
|
+
existingAgentConfig = void 0;
|
|
1037
|
+
}
|
|
1038
|
+
const agentSpecificConfig = adapter.toClient(config, existingAgentConfig);
|
|
1039
|
+
await writeAgentConfig(configPath, agentSpecificConfig);
|
|
1040
|
+
anyConfigured = true;
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
M.warn(
|
|
1043
|
+
import_picocolors2.default.yellow(
|
|
1044
|
+
`Could not update MCP config for ${agents[agentType]?.displayName || agentType} \u2014 add skills manually`
|
|
1045
|
+
)
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
if (anyConfigured) {
|
|
1052
|
+
console.log();
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// src/mcp.ts
|
|
1057
|
+
function parseMcpOptions(args) {
|
|
1058
|
+
const agentList = [];
|
|
1059
|
+
let scope = "local";
|
|
1060
|
+
let configMode;
|
|
1061
|
+
for (let i = 0; i < args.length; i++) {
|
|
1062
|
+
const arg = args[i];
|
|
1063
|
+
if (arg === "-a" || arg === "--agent") {
|
|
1064
|
+
i++;
|
|
1065
|
+
let nextArg = args[i];
|
|
1066
|
+
while (i < args.length && nextArg && !nextArg.startsWith("-")) {
|
|
1067
|
+
agentList.push(nextArg);
|
|
1068
|
+
i++;
|
|
1069
|
+
nextArg = args[i];
|
|
1070
|
+
}
|
|
1071
|
+
i--;
|
|
1072
|
+
} else if (arg === "-g" || arg === "--global") {
|
|
1073
|
+
scope = "global";
|
|
1074
|
+
} else if (arg === "--agent-config") {
|
|
1075
|
+
configMode = "agent-config";
|
|
1076
|
+
} else if (arg === "--mcp-json") {
|
|
1077
|
+
configMode = "mcp-json";
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
const mode = agentList.length > 0 ? "cli" : "tui";
|
|
1081
|
+
return {
|
|
1082
|
+
mode,
|
|
1083
|
+
agents: agentList,
|
|
1084
|
+
scope,
|
|
1085
|
+
configMode
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
async function runMcpSetup(options, cwd = process.cwd()) {
|
|
1089
|
+
const scope = options.scope || "local";
|
|
1090
|
+
const configCwd = scope === "global" ? homedir3() : cwd;
|
|
1091
|
+
if (options.mode === "tui") {
|
|
1092
|
+
await setupTuiMode(configCwd, scope, options.configMode);
|
|
1093
|
+
} else {
|
|
1094
|
+
await setupCliMode(options.agents, configCwd, scope, options.configMode);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
function isGeneratorBacked(agentType) {
|
|
1098
|
+
return buildConfigGeneratorRegistry().supports(agentType);
|
|
1099
|
+
}
|
|
1100
|
+
async function configureOneAgent(agentType, configCwd, scope, configMode) {
|
|
1101
|
+
try {
|
|
1102
|
+
if (configMode === "agent-config" && isGeneratorBacked(agentType)) {
|
|
1103
|
+
await generateSkillsMcpAgent(agentType, configCwd, scope);
|
|
1104
|
+
} else {
|
|
1105
|
+
await configureAgentMcp(agentType, configCwd, scope);
|
|
1106
|
+
}
|
|
1107
|
+
return agentType;
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
console.error(
|
|
1110
|
+
`\u2717 Failed to configure ${agents[agentType]?.displayName || agentType}:`,
|
|
1111
|
+
error.message
|
|
1112
|
+
);
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
function printAgentSummary(agentType, configMode, _scope) {
|
|
1117
|
+
const agent = agents[agentType];
|
|
1118
|
+
const displayName = agent?.displayName || agentType;
|
|
1119
|
+
const isVerified = agent?.agentConfigSupport?.verified ?? false;
|
|
1120
|
+
if (configMode === "agent-config" && isGeneratorBacked(agentType)) {
|
|
1121
|
+
const hint = agent?.agentConfigSupport?.activationHint;
|
|
1122
|
+
console.log(` \u2713 ${displayName} \u2014 agent config written`);
|
|
1123
|
+
if (hint) {
|
|
1124
|
+
const looksLikeCli = !hint.includes(" ") || hint.startsWith("kiro") || hint.startsWith("opencode");
|
|
1125
|
+
if (looksLikeCli) {
|
|
1126
|
+
console.log(` \u2192 To activate: ${hint}`);
|
|
1127
|
+
} else {
|
|
1128
|
+
console.log(` \u2192 ${hint}`);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
if (!isVerified) {
|
|
1132
|
+
console.log(
|
|
1133
|
+
` \u26A0\uFE0F MCP integration not yet verified. Please ensure ${displayName} picks up the MCP server.`
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
} else {
|
|
1137
|
+
console.log(` \u2713 ${displayName} \u2014 MCP server registered in mcp.json`);
|
|
1138
|
+
if (!isVerified) {
|
|
1139
|
+
console.log(
|
|
1140
|
+
` \u26A0\uFE0F MCP integration not yet verified. Please check that ${displayName} has loaded the MCP server.`
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
async function setupTuiMode(cwd, scope = "local", forcedConfigMode) {
|
|
1146
|
+
const installedAgents = await detectInstalledAgents();
|
|
1147
|
+
if (installedAgents.length === 0) {
|
|
1148
|
+
console.log("No supported agents detected. Please install an agent first.");
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
const selectedScope = await ve({
|
|
1152
|
+
message: "Where should MCP configs be stored?",
|
|
1153
|
+
options: [
|
|
1154
|
+
{ value: "local", label: "Local (Project directory) \u2014 shared via Git" },
|
|
1155
|
+
{ value: "global", label: "Global (Home directory) \u2014 personal settings only" }
|
|
1156
|
+
]
|
|
1157
|
+
});
|
|
1158
|
+
if (typeof selectedScope === "symbol") {
|
|
1159
|
+
xe("Operation cancelled");
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
scope = selectedScope;
|
|
1163
|
+
const configCwd = scope === "global" ? homedir3() : cwd;
|
|
1164
|
+
const selectedAgents = await fe({
|
|
1165
|
+
message: "Select agents to configure for MCP:",
|
|
1166
|
+
options: installedAgents.map((agentType) => {
|
|
1167
|
+
const agent = agents[agentType];
|
|
1168
|
+
const supportsAgentConfig = !!agent?.agentConfigSupport;
|
|
1169
|
+
return {
|
|
1170
|
+
value: agentType,
|
|
1171
|
+
label: `${agent?.displayName || agentType}${supportsAgentConfig ? " \u2726" : ""}`,
|
|
1172
|
+
hint: supportsAgentConfig ? "supports agent config" : void 0
|
|
1173
|
+
};
|
|
1174
|
+
})
|
|
1175
|
+
});
|
|
1176
|
+
if (typeof selectedAgents === "symbol") {
|
|
1177
|
+
xe("Operation cancelled");
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
if (!selectedAgents || selectedAgents.length === 0) {
|
|
1181
|
+
console.log("No agents selected.");
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
const agentConfigCapable = selectedAgents.filter(
|
|
1185
|
+
(a) => isGeneratorBacked(a)
|
|
1186
|
+
);
|
|
1187
|
+
let configMode = "mcp-json";
|
|
1188
|
+
if (forcedConfigMode) {
|
|
1189
|
+
configMode = forcedConfigMode;
|
|
1190
|
+
} else if (agentConfigCapable.length > 0) {
|
|
1191
|
+
const capableNames = agentConfigCapable.map((a) => agents[a]?.displayName || a).join(", ");
|
|
1192
|
+
const modeChoice = await ve({
|
|
1193
|
+
message: `How should skills-mcp be configured for ${capableNames}?`,
|
|
1194
|
+
options: [
|
|
1195
|
+
{
|
|
1196
|
+
value: "agent-config",
|
|
1197
|
+
label: 'Agent config \u2014 creates a named "skills-mcp" agent with usage instructions',
|
|
1198
|
+
hint: "Recommended: selectable by name; bundled prompt guides the agent"
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
value: "mcp-json",
|
|
1202
|
+
label: "MCP server only \u2014 registers servers in mcp.json, no dedicated agent",
|
|
1203
|
+
hint: "Simpler; MCP server is available in all conversations without a named agent"
|
|
1204
|
+
}
|
|
1205
|
+
]
|
|
1206
|
+
});
|
|
1207
|
+
if (typeof modeChoice === "symbol") {
|
|
1208
|
+
xe("Operation cancelled");
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
configMode = modeChoice;
|
|
1212
|
+
}
|
|
1213
|
+
console.log("");
|
|
1214
|
+
const configuredAgents = [];
|
|
1215
|
+
for (const agentType of selectedAgents) {
|
|
1216
|
+
const result = await configureOneAgent(agentType, configCwd, scope, configMode);
|
|
1217
|
+
if (result !== null) configuredAgents.push(agentType);
|
|
1218
|
+
}
|
|
1219
|
+
if (configuredAgents.length > 0) {
|
|
1220
|
+
const { deps: skillDeps, allowedToolsByServer } = await loadInstalledSkillMcpDeps(cwd, scope);
|
|
1221
|
+
await configureSkillMcpDepsForAgents(
|
|
1222
|
+
skillDeps,
|
|
1223
|
+
configuredAgents,
|
|
1224
|
+
configCwd,
|
|
1225
|
+
scope,
|
|
1226
|
+
configMode,
|
|
1227
|
+
allowedToolsByServer
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
const scopeLabel = scope === "global" ? "global (home directory)" : "local (project directory)";
|
|
1231
|
+
const failCount = selectedAgents.length - configuredAgents.length;
|
|
1232
|
+
console.log("");
|
|
1233
|
+
if (configuredAgents.length > 0) {
|
|
1234
|
+
console.log(`Configured ${configuredAgents.length} agent(s) in ${scopeLabel}:`);
|
|
1235
|
+
for (const agentType of configuredAgents) {
|
|
1236
|
+
printAgentSummary(agentType, configMode, scope);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (failCount > 0) {
|
|
1240
|
+
console.error(`
|
|
1241
|
+
\u2717 Failed to configure ${failCount} agent(s) in ${scopeLabel}`);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
async function setupCliMode(agentTypes, cwd, scope = "local", forcedConfigMode) {
|
|
1245
|
+
if (agentTypes.includes("*")) {
|
|
1246
|
+
const installedAgents = await detectInstalledAgents();
|
|
1247
|
+
agentTypes = installedAgents;
|
|
1248
|
+
}
|
|
1249
|
+
const configuredAgents = [];
|
|
1250
|
+
for (const agentType of agentTypes) {
|
|
1251
|
+
const configMode = forcedConfigMode ?? (isGeneratorBacked(agentType) ? "agent-config" : "mcp-json");
|
|
1252
|
+
const result = await configureOneAgent(agentType, cwd, scope, configMode);
|
|
1253
|
+
if (result !== null) configuredAgents.push(agentType);
|
|
1254
|
+
}
|
|
1255
|
+
if (configuredAgents.length > 0) {
|
|
1256
|
+
const { deps: skillDeps, allowedToolsByServer } = await loadInstalledSkillMcpDeps(cwd, scope);
|
|
1257
|
+
if (forcedConfigMode) {
|
|
1258
|
+
const generatorBacked = configuredAgents.filter((a) => isGeneratorBacked(a));
|
|
1259
|
+
const rawMcp = configuredAgents.filter((a) => !isGeneratorBacked(a));
|
|
1260
|
+
if (generatorBacked.length > 0) {
|
|
1261
|
+
await configureSkillMcpDepsForAgents(
|
|
1262
|
+
skillDeps,
|
|
1263
|
+
generatorBacked,
|
|
1264
|
+
cwd,
|
|
1265
|
+
scope,
|
|
1266
|
+
forcedConfigMode,
|
|
1267
|
+
allowedToolsByServer
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
if (rawMcp.length > 0) {
|
|
1271
|
+
await configureSkillMcpDepsForAgents(
|
|
1272
|
+
skillDeps,
|
|
1273
|
+
rawMcp,
|
|
1274
|
+
cwd,
|
|
1275
|
+
scope,
|
|
1276
|
+
"mcp-json",
|
|
1277
|
+
allowedToolsByServer
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
} else {
|
|
1281
|
+
const generatorBacked = configuredAgents.filter((a) => isGeneratorBacked(a));
|
|
1282
|
+
const rawMcp = configuredAgents.filter((a) => !isGeneratorBacked(a));
|
|
1283
|
+
if (generatorBacked.length > 0) {
|
|
1284
|
+
await configureSkillMcpDepsForAgents(
|
|
1285
|
+
skillDeps,
|
|
1286
|
+
generatorBacked,
|
|
1287
|
+
cwd,
|
|
1288
|
+
scope,
|
|
1289
|
+
"agent-config",
|
|
1290
|
+
allowedToolsByServer
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
if (rawMcp.length > 0) {
|
|
1294
|
+
await configureSkillMcpDepsForAgents(
|
|
1295
|
+
skillDeps,
|
|
1296
|
+
rawMcp,
|
|
1297
|
+
cwd,
|
|
1298
|
+
scope,
|
|
1299
|
+
"mcp-json",
|
|
1300
|
+
allowedToolsByServer
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
const failCount = agentTypes.length - configuredAgents.length;
|
|
1306
|
+
if (configuredAgents.length > 0) {
|
|
1307
|
+
console.log(`
|
|
1308
|
+
Configured ${configuredAgents.length} agent(s):`);
|
|
1309
|
+
for (const agentType of configuredAgents) {
|
|
1310
|
+
const configMode = forcedConfigMode ?? (isGeneratorBacked(agentType) ? "agent-config" : "mcp-json");
|
|
1311
|
+
printAgentSummary(agentType, configMode, scope);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
if (failCount > 0) {
|
|
1315
|
+
console.error(`
|
|
1316
|
+
\u2717 Failed to configure ${failCount} agent(s)`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// src/cli.ts
|
|
1321
|
+
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
1322
|
+
function getVersion() {
|
|
1323
|
+
try {
|
|
1324
|
+
const pkgPath = join3(__dirname, "..", "package.json");
|
|
1325
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1326
|
+
return pkg.version;
|
|
1327
|
+
} catch {
|
|
1328
|
+
return "0.0.0";
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
var VERSION = getVersion();
|
|
1332
|
+
initTelemetry(VERSION);
|
|
1333
|
+
var RESET3 = "\x1B[0m";
|
|
1334
|
+
var BOLD3 = "\x1B[1m";
|
|
1335
|
+
var DIM3 = "\x1B[38;5;102m";
|
|
1336
|
+
var TEXT2 = "\x1B[38;5;145m";
|
|
1337
|
+
var LOGO_LINES = [
|
|
1338
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
1339
|
+
"\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
|
|
1340
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
|
|
1341
|
+
"\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u255D ",
|
|
1342
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 ",
|
|
1343
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D "
|
|
1344
|
+
];
|
|
1345
|
+
var GRAYS = [
|
|
1346
|
+
"\x1B[38;5;250m",
|
|
1347
|
+
// lighter gray
|
|
1348
|
+
"\x1B[38;5;248m",
|
|
1349
|
+
"\x1B[38;5;245m",
|
|
1350
|
+
// mid gray
|
|
1351
|
+
"\x1B[38;5;243m",
|
|
1352
|
+
"\x1B[38;5;240m",
|
|
1353
|
+
"\x1B[38;5;238m"
|
|
1354
|
+
// darker gray
|
|
1355
|
+
];
|
|
1356
|
+
function showLogo() {
|
|
1357
|
+
console.log();
|
|
1358
|
+
LOGO_LINES.forEach((line, i) => {
|
|
1359
|
+
console.log(`${GRAYS[i]}${line}${RESET3}`);
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
function showBanner() {
|
|
1363
|
+
showLogo();
|
|
1364
|
+
console.log();
|
|
1365
|
+
console.log(`${DIM3}The open agent skills ecosystem${RESET3}`);
|
|
1366
|
+
console.log();
|
|
1367
|
+
console.log(
|
|
1368
|
+
` ${DIM3}$${RESET3} ${TEXT2}npx @codemcp/skills add ${DIM3}<package>${RESET3} ${DIM3}Add a new skill${RESET3}`
|
|
1369
|
+
);
|
|
1370
|
+
console.log(
|
|
1371
|
+
` ${DIM3}$${RESET3} ${TEXT2}npx @codemcp/skills remove${RESET3} ${DIM3}Remove installed skills${RESET3}`
|
|
1372
|
+
);
|
|
1373
|
+
console.log(
|
|
1374
|
+
` ${DIM3}$${RESET3} ${TEXT2}npx @codemcp/skills list${RESET3} ${DIM3}List installed skills${RESET3}`
|
|
1375
|
+
);
|
|
1376
|
+
console.log(
|
|
1377
|
+
` ${DIM3}$${RESET3} ${TEXT2}npx @codemcp/skills find ${DIM3}[query]${RESET3} ${DIM3}Search for skills${RESET3}`
|
|
1378
|
+
);
|
|
1379
|
+
console.log();
|
|
1380
|
+
console.log(
|
|
1381
|
+
` ${DIM3}$${RESET3} ${TEXT2}npx @codemcp/skills check${RESET3} ${DIM3}Check for updates${RESET3}`
|
|
1382
|
+
);
|
|
1383
|
+
console.log(
|
|
1384
|
+
` ${DIM3}$${RESET3} ${TEXT2}npx @codemcp/skills update${RESET3} ${DIM3}Update all skills${RESET3}`
|
|
1385
|
+
);
|
|
1386
|
+
console.log();
|
|
1387
|
+
console.log(
|
|
1388
|
+
` ${DIM3}$${RESET3} ${TEXT2}npx @codemcp/skills experimental_install${RESET3} ${DIM3}Restore from skills-lock.json${RESET3}`
|
|
1389
|
+
);
|
|
1390
|
+
console.log(
|
|
1391
|
+
` ${DIM3}$${RESET3} ${TEXT2}npx @codemcp/skills init ${DIM3}[name]${RESET3} ${DIM3}Create a new skill${RESET3}`
|
|
1392
|
+
);
|
|
1393
|
+
console.log(
|
|
1394
|
+
` ${DIM3}$${RESET3} ${TEXT2}npx @codemcp/skills experimental_sync${RESET3} ${DIM3}Sync skills from node_modules${RESET3}`
|
|
1395
|
+
);
|
|
1396
|
+
console.log();
|
|
1397
|
+
console.log(`${DIM3}try:${RESET3} npx @codemcp/skills add vercel-labs/agent-skills`);
|
|
1398
|
+
console.log();
|
|
1399
|
+
console.log(`Discover more skills at ${TEXT2}https://skills.sh/${RESET3}`);
|
|
1400
|
+
console.log();
|
|
1401
|
+
}
|
|
1402
|
+
function showHelp() {
|
|
1403
|
+
console.log(`
|
|
1404
|
+
${BOLD3}Usage:${RESET3} npx @codemcp/skills <command> [options]
|
|
1405
|
+
|
|
1406
|
+
${BOLD3}Manage Skills:${RESET3}
|
|
1407
|
+
add <package> Add a skill package (alias: a)
|
|
1408
|
+
e.g. vercel-labs/agent-skills
|
|
1409
|
+
https://github.com/vercel-labs/agent-skills
|
|
1410
|
+
remove [skills] Remove installed skills
|
|
1411
|
+
list, ls List installed skills
|
|
1412
|
+
find [query] Search for skills interactively
|
|
1413
|
+
|
|
1414
|
+
${BOLD3}Updates:${RESET3}
|
|
1415
|
+
check Check for available skill updates
|
|
1416
|
+
update Update all skills to latest versions
|
|
1417
|
+
|
|
1418
|
+
${BOLD3}Project:${RESET3}
|
|
1419
|
+
experimental_install Restore skills from skills-lock.json
|
|
1420
|
+
init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
|
|
1421
|
+
experimental_sync Sync skills from node_modules into agent directories
|
|
1422
|
+
|
|
1423
|
+
${BOLD3}Add Options:${RESET3}
|
|
1424
|
+
-g, --global Install skill globally (user-level) instead of project-level
|
|
1425
|
+
-a, --agent <agents> Specify agents to install to (use '*' for all agents)
|
|
1426
|
+
-s, --skill <skills> Specify skill names to install (use '*' for all skills)
|
|
1427
|
+
-l, --list List available skills in the repository without installing
|
|
1428
|
+
-y, --yes Skip confirmation prompts
|
|
1429
|
+
--copy Copy files instead of symlinking to agent directories
|
|
1430
|
+
--all Shorthand for --skill '*' --agent '*' -y
|
|
1431
|
+
--full-depth Search all subdirectories even when a root SKILL.md exists
|
|
1432
|
+
|
|
1433
|
+
${BOLD3}Remove Options:${RESET3}
|
|
1434
|
+
-g, --global Remove from global scope
|
|
1435
|
+
-a, --agent <agents> Remove from specific agents (use '*' for all agents)
|
|
1436
|
+
-s, --skill <skills> Specify skills to remove (use '*' for all skills)
|
|
1437
|
+
-y, --yes Skip confirmation prompts
|
|
1438
|
+
--all Shorthand for --skill '*' --agent '*' -y
|
|
1439
|
+
|
|
1440
|
+
${BOLD3}Experimental Sync Options:${RESET3}
|
|
1441
|
+
-a, --agent <agents> Specify agents to install to (use '*' for all agents)
|
|
1442
|
+
-y, --yes Skip confirmation prompts
|
|
1443
|
+
|
|
1444
|
+
${BOLD3}List Options:${RESET3}
|
|
1445
|
+
-g, --global List global skills (default: project)
|
|
1446
|
+
-a, --agent <agents> Filter by specific agents
|
|
1447
|
+
|
|
1448
|
+
${BOLD3}Options:${RESET3}
|
|
1449
|
+
--help, -h Show this help message
|
|
1450
|
+
--version, -v Show version number
|
|
1451
|
+
|
|
1452
|
+
${BOLD3}Examples:${RESET3}
|
|
1453
|
+
${DIM3}$${RESET3} npx @codemcp/skills add vercel-labs/agent-skills
|
|
1454
|
+
${DIM3}$${RESET3} npx @codemcp/skills add vercel-labs/agent-skills -g
|
|
1455
|
+
${DIM3}$${RESET3} npx @codemcp/skills add vercel-labs/agent-skills --agent claude-code cursor
|
|
1456
|
+
${DIM3}$${RESET3} npx @codemcp/skills add vercel-labs/agent-skills --skill pr-review commit
|
|
1457
|
+
${DIM3}$${RESET3} npx @codemcp/skills remove ${DIM3}# interactive remove${RESET3}
|
|
1458
|
+
${DIM3}$${RESET3} npx @codemcp/skills remove web-design ${DIM3}# remove by name${RESET3}
|
|
1459
|
+
${DIM3}$${RESET3} npx @codemcp/skills rm --global frontend-design
|
|
1460
|
+
${DIM3}$${RESET3} npx @codemcp/skills list ${DIM3}# list project skills${RESET3}
|
|
1461
|
+
${DIM3}$${RESET3} npx @codemcp/skills ls -g ${DIM3}# list global skills${RESET3}
|
|
1462
|
+
${DIM3}$${RESET3} npx @codemcp/skills ls -a claude-code ${DIM3}# filter by agent${RESET3}
|
|
1463
|
+
${DIM3}$${RESET3} npx @codemcp/skills find ${DIM3}# interactive search${RESET3}
|
|
1464
|
+
${DIM3}$${RESET3} npx @codemcp/skills find typescript ${DIM3}# search by keyword${RESET3}
|
|
1465
|
+
${DIM3}$${RESET3} npx @codemcp/skills check
|
|
1466
|
+
${DIM3}$${RESET3} npx @codemcp/skills update
|
|
1467
|
+
${DIM3}$${RESET3} npx @codemcp/skills experimental_install ${DIM3}# restore from skills-lock.json${RESET3}
|
|
1468
|
+
${DIM3}$${RESET3} npx @codemcp/skills init my-skill
|
|
1469
|
+
${DIM3}$${RESET3} npx @codemcp/skills experimental_sync ${DIM3}# sync from node_modules${RESET3}
|
|
1470
|
+
${DIM3}$${RESET3} npx @codemcp/skills experimental_sync -y ${DIM3}# sync without prompts${RESET3}
|
|
1471
|
+
|
|
1472
|
+
Discover more skills at ${TEXT2}https://skills.sh/${RESET3}
|
|
1473
|
+
`);
|
|
1474
|
+
}
|
|
1475
|
+
function showRemoveHelp() {
|
|
1476
|
+
console.log(`
|
|
1477
|
+
${BOLD3}Usage:${RESET3} npx @codemcp/skills remove [skills...] [options]
|
|
1478
|
+
|
|
1479
|
+
${BOLD3}Description:${RESET3}
|
|
1480
|
+
Remove installed skills from agents. If no skill names are provided,
|
|
1481
|
+
an interactive selection menu will be shown.
|
|
1482
|
+
|
|
1483
|
+
${BOLD3}Arguments:${RESET3}
|
|
1484
|
+
skills Optional skill names to remove (space-separated)
|
|
1485
|
+
|
|
1486
|
+
${BOLD3}Options:${RESET3}
|
|
1487
|
+
-g, --global Remove from global scope (~/) instead of project scope
|
|
1488
|
+
-a, --agent Remove from specific agents (use '*' for all agents)
|
|
1489
|
+
-s, --skill Specify skills to remove (use '*' for all skills)
|
|
1490
|
+
-y, --yes Skip confirmation prompts
|
|
1491
|
+
--all Shorthand for --skill '*' --agent '*' -y
|
|
1492
|
+
|
|
1493
|
+
${BOLD3}Examples:${RESET3}
|
|
1494
|
+
${DIM3}$${RESET3} npx @codemcp/skills remove ${DIM3}# interactive selection${RESET3}
|
|
1495
|
+
${DIM3}$${RESET3} npx @codemcp/skills remove my-skill ${DIM3}# remove specific skill${RESET3}
|
|
1496
|
+
${DIM3}$${RESET3} npx @codemcp/skills remove skill1 skill2 -y ${DIM3}# remove multiple skills${RESET3}
|
|
1497
|
+
${DIM3}$${RESET3} npx @codemcp/skills remove --global my-skill ${DIM3}# remove from global scope${RESET3}
|
|
1498
|
+
${DIM3}$${RESET3} npx @codemcp/skills rm --agent claude-code my-skill ${DIM3}# remove from specific agent${RESET3}
|
|
1499
|
+
${DIM3}$${RESET3} npx @codemcp/skills remove --all ${DIM3}# remove all skills${RESET3}
|
|
1500
|
+
${DIM3}$${RESET3} npx @codemcp/skills remove --skill '*' -a cursor ${DIM3}# remove all skills from cursor${RESET3}
|
|
1501
|
+
|
|
1502
|
+
Discover more skills at ${TEXT2}https://skills.sh/${RESET3}
|
|
1503
|
+
`);
|
|
1504
|
+
}
|
|
1505
|
+
function showMcpHelp() {
|
|
1506
|
+
console.log(`
|
|
1507
|
+
${BOLD3}Usage:${RESET3} npx @codemcp/skills mcp setup [options]
|
|
1508
|
+
|
|
1509
|
+
${BOLD3}Description:${RESET3}
|
|
1510
|
+
Configure MCP (Model Context Protocol) server for agent environments.
|
|
1511
|
+
Supports both interactive (TUI) and command-line (CLI) modes.
|
|
1512
|
+
|
|
1513
|
+
${BOLD3}Subcommands:${RESET3}
|
|
1514
|
+
setup Configure MCP server for agents (default if no subcommand)
|
|
1515
|
+
|
|
1516
|
+
${BOLD3}Options:${RESET3}
|
|
1517
|
+
-a, --agent Specify agents to configure (space-separated)
|
|
1518
|
+
Use '*' to configure all detected agents
|
|
1519
|
+
-g, --global Write configs to home directory instead of project
|
|
1520
|
+
--agent-config Create a named agent file with usage instructions
|
|
1521
|
+
(Kiro, GitHub Copilot, OpenCode only; default for those agents)
|
|
1522
|
+
--mcp-json Register MCP servers in mcp.json only, no agent file
|
|
1523
|
+
(works for all agents; skips the TUI mode-selection prompt)
|
|
1524
|
+
|
|
1525
|
+
${BOLD3}Modes:${RESET3}
|
|
1526
|
+
${DIM3}Interactive (TUI):${RESET3} npx @codemcp/skills mcp setup
|
|
1527
|
+
Guides through scope \u2192 agent selection \u2192 config type \u2192 summary
|
|
1528
|
+
|
|
1529
|
+
${DIM3}Command-line (CLI):${RESET3} npx @codemcp/skills mcp setup --agent <agents>
|
|
1530
|
+
Configures specified agents without interaction
|
|
1531
|
+
|
|
1532
|
+
${BOLD3}Examples:${RESET3}
|
|
1533
|
+
${DIM3}$${RESET3} npx @codemcp/skills mcp setup ${DIM3}# interactive${RESET3}
|
|
1534
|
+
${DIM3}$${RESET3} npx @codemcp/skills mcp setup --agent claude-code ${DIM3}# mcp.json for Claude${RESET3}
|
|
1535
|
+
${DIM3}$${RESET3} npx @codemcp/skills mcp setup --agent kiro-cli --agent-config ${DIM3}# Kiro agent file${RESET3}
|
|
1536
|
+
${DIM3}$${RESET3} npx @codemcp/skills mcp setup --agent kiro-cli --mcp-json ${DIM3}# Kiro mcp.json only${RESET3}
|
|
1537
|
+
${DIM3}$${RESET3} npx @codemcp/skills mcp setup --agent claude-code cline --mcp-json ${DIM3}# multiple, mcp.json${RESET3}
|
|
1538
|
+
${DIM3}$${RESET3} npx @codemcp/skills mcp setup --agent '*' ${DIM3}# all agents${RESET3}
|
|
1539
|
+
|
|
1540
|
+
${BOLD3}Supported Agents:${RESET3}
|
|
1541
|
+
claude-code, cline, cursor, kiro-cli, junie, opencode, and more
|
|
1542
|
+
Agents marked \u2726 in the TUI support a rich agent config file.
|
|
1543
|
+
|
|
1544
|
+
Discover more at ${TEXT2}https://skills.sh/${RESET3}
|
|
1545
|
+
`);
|
|
1546
|
+
}
|
|
1547
|
+
function runInit(args) {
|
|
1548
|
+
const cwd = process.cwd();
|
|
1549
|
+
const skillName = args[0] || basename(cwd);
|
|
1550
|
+
const hasName = args[0] !== void 0;
|
|
1551
|
+
const skillDir = hasName ? join3(cwd, skillName) : cwd;
|
|
1552
|
+
const skillFile = join3(skillDir, "SKILL.md");
|
|
1553
|
+
const displayPath = hasName ? `${skillName}/SKILL.md` : "SKILL.md";
|
|
1554
|
+
if (existsSync(skillFile)) {
|
|
1555
|
+
console.log(`${TEXT2}Skill already exists at ${DIM3}${displayPath}${RESET3}`);
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
if (hasName) {
|
|
1559
|
+
mkdirSync(skillDir, { recursive: true });
|
|
1560
|
+
}
|
|
1561
|
+
const skillContent = `---
|
|
1562
|
+
name: ${skillName}
|
|
1563
|
+
description: A brief description of what this skill does
|
|
1564
|
+
---
|
|
1565
|
+
|
|
1566
|
+
# ${skillName}
|
|
1567
|
+
|
|
1568
|
+
Instructions for the agent to follow when this skill is activated.
|
|
1569
|
+
|
|
1570
|
+
## When to use
|
|
1571
|
+
|
|
1572
|
+
Describe when this skill should be used.
|
|
1573
|
+
|
|
1574
|
+
## Instructions
|
|
1575
|
+
|
|
1576
|
+
1. First step
|
|
1577
|
+
2. Second step
|
|
1578
|
+
3. Additional steps as needed
|
|
1579
|
+
`;
|
|
1580
|
+
writeFileSync(skillFile, skillContent);
|
|
1581
|
+
console.log(`${TEXT2}Initialized skill: ${DIM3}${skillName}${RESET3}`);
|
|
1582
|
+
console.log();
|
|
1583
|
+
console.log(`${DIM3}Created:${RESET3}`);
|
|
1584
|
+
console.log(` ${displayPath}`);
|
|
1585
|
+
console.log();
|
|
1586
|
+
console.log(`${DIM3}Next steps:${RESET3}`);
|
|
1587
|
+
console.log(` 1. Edit ${TEXT2}${displayPath}${RESET3} to define your skill instructions`);
|
|
1588
|
+
console.log(
|
|
1589
|
+
` 2. Update the ${TEXT2}name${RESET3} and ${TEXT2}description${RESET3} in the frontmatter`
|
|
1590
|
+
);
|
|
1591
|
+
console.log();
|
|
1592
|
+
console.log(`${DIM3}Publishing:${RESET3}`);
|
|
1593
|
+
console.log(
|
|
1594
|
+
` ${DIM3}GitHub:${RESET3} Push to a repo, then ${TEXT2}npx @codemcp/skills add <owner>/<repo>${RESET3}`
|
|
1595
|
+
);
|
|
1596
|
+
console.log(
|
|
1597
|
+
` ${DIM3}URL:${RESET3} Host the file, then ${TEXT2}npx @codemcp/skills add https://example.com/${displayPath}${RESET3}`
|
|
1598
|
+
);
|
|
1599
|
+
console.log();
|
|
1600
|
+
console.log(`Browse existing skills for inspiration at ${TEXT2}https://skills.sh/${RESET3}`);
|
|
1601
|
+
console.log();
|
|
1602
|
+
}
|
|
1603
|
+
var AGENTS_DIR = ".agents";
|
|
1604
|
+
var LOCK_FILE = ".skill-lock.json";
|
|
1605
|
+
var CURRENT_LOCK_VERSION = 3;
|
|
1606
|
+
function getSkillLockPath() {
|
|
1607
|
+
return join3(homedir4(), AGENTS_DIR, LOCK_FILE);
|
|
1608
|
+
}
|
|
1609
|
+
function readSkillLock() {
|
|
1610
|
+
const lockPath = getSkillLockPath();
|
|
1611
|
+
try {
|
|
1612
|
+
const content = readFileSync(lockPath, "utf-8");
|
|
1613
|
+
const parsed = JSON.parse(content);
|
|
1614
|
+
if (typeof parsed.version !== "number" || !parsed.skills) {
|
|
1615
|
+
return { version: CURRENT_LOCK_VERSION, skills: {} };
|
|
1616
|
+
}
|
|
1617
|
+
if (parsed.version < CURRENT_LOCK_VERSION) {
|
|
1618
|
+
return { version: CURRENT_LOCK_VERSION, skills: {} };
|
|
1619
|
+
}
|
|
1620
|
+
return parsed;
|
|
1621
|
+
} catch {
|
|
1622
|
+
return { version: CURRENT_LOCK_VERSION, skills: {} };
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
async function runCheck(args = []) {
|
|
1626
|
+
console.log(`${TEXT2}Checking for skill updates...${RESET3}`);
|
|
1627
|
+
console.log();
|
|
1628
|
+
const lock = readSkillLock();
|
|
1629
|
+
const skillNames = Object.keys(lock.skills);
|
|
1630
|
+
if (skillNames.length === 0) {
|
|
1631
|
+
console.log(`${DIM3}No skills tracked in lock file.${RESET3}`);
|
|
1632
|
+
console.log(
|
|
1633
|
+
`${DIM3}Install skills with${RESET3} ${TEXT2}npx @codemcp/skills add <package>${RESET3}`
|
|
1634
|
+
);
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
const token = getGitHubToken();
|
|
1638
|
+
const skillsBySource = /* @__PURE__ */ new Map();
|
|
1639
|
+
let skippedCount = 0;
|
|
1640
|
+
for (const skillName of skillNames) {
|
|
1641
|
+
const entry = lock.skills[skillName];
|
|
1642
|
+
if (!entry) continue;
|
|
1643
|
+
if (entry.sourceType !== "github" || !entry.skillFolderHash || !entry.skillPath) {
|
|
1644
|
+
skippedCount++;
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1647
|
+
const existing = skillsBySource.get(entry.source) || [];
|
|
1648
|
+
existing.push({ name: skillName, entry });
|
|
1649
|
+
skillsBySource.set(entry.source, existing);
|
|
1650
|
+
}
|
|
1651
|
+
const totalSkills = skillNames.length - skippedCount;
|
|
1652
|
+
if (totalSkills === 0) {
|
|
1653
|
+
console.log(`${DIM3}No GitHub skills to check.${RESET3}`);
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
console.log(`${DIM3}Checking ${totalSkills} skill(s) for updates...${RESET3}`);
|
|
1657
|
+
const updates = [];
|
|
1658
|
+
const errors = [];
|
|
1659
|
+
for (const [source, skills] of skillsBySource) {
|
|
1660
|
+
for (const { name, entry } of skills) {
|
|
1661
|
+
try {
|
|
1662
|
+
const latestHash = await fetchSkillFolderHash(source, entry.skillPath, token);
|
|
1663
|
+
if (!latestHash) {
|
|
1664
|
+
errors.push({ name, source, error: "Could not fetch from GitHub" });
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1667
|
+
if (latestHash !== entry.skillFolderHash) {
|
|
1668
|
+
updates.push({ name, source });
|
|
1669
|
+
}
|
|
1670
|
+
} catch (err) {
|
|
1671
|
+
errors.push({
|
|
1672
|
+
name,
|
|
1673
|
+
source,
|
|
1674
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
console.log();
|
|
1680
|
+
if (updates.length === 0) {
|
|
1681
|
+
console.log(`${TEXT2}\u2713 All skills are up to date${RESET3}`);
|
|
1682
|
+
} else {
|
|
1683
|
+
console.log(`${TEXT2}${updates.length} update(s) available:${RESET3}`);
|
|
1684
|
+
console.log();
|
|
1685
|
+
for (const update of updates) {
|
|
1686
|
+
console.log(` ${TEXT2}\u2191${RESET3} ${update.name}`);
|
|
1687
|
+
console.log(` ${DIM3}source: ${update.source}${RESET3}`);
|
|
1688
|
+
}
|
|
1689
|
+
console.log();
|
|
1690
|
+
console.log(
|
|
1691
|
+
`${DIM3}Run${RESET3} ${TEXT2}npx @codemcp/skills update${RESET3} ${DIM3}to update all skills${RESET3}`
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
if (errors.length > 0) {
|
|
1695
|
+
console.log();
|
|
1696
|
+
console.log(`${DIM3}Could not check ${errors.length} skill(s) (may need reinstall)${RESET3}`);
|
|
1697
|
+
}
|
|
1698
|
+
track({
|
|
1699
|
+
event: "check",
|
|
1700
|
+
skillCount: String(totalSkills),
|
|
1701
|
+
updatesAvailable: String(updates.length)
|
|
1702
|
+
});
|
|
1703
|
+
console.log();
|
|
1704
|
+
}
|
|
1705
|
+
async function runUpdate() {
|
|
1706
|
+
console.log(`${TEXT2}Checking for skill updates...${RESET3}`);
|
|
1707
|
+
console.log();
|
|
1708
|
+
const lock = readSkillLock();
|
|
1709
|
+
const skillNames = Object.keys(lock.skills);
|
|
1710
|
+
if (skillNames.length === 0) {
|
|
1711
|
+
console.log(`${DIM3}No skills tracked in lock file.${RESET3}`);
|
|
1712
|
+
console.log(
|
|
1713
|
+
`${DIM3}Install skills with${RESET3} ${TEXT2}npx @codemcp/skills add <package>${RESET3}`
|
|
1714
|
+
);
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
const token = getGitHubToken();
|
|
1718
|
+
const updates = [];
|
|
1719
|
+
let checkedCount = 0;
|
|
1720
|
+
for (const skillName of skillNames) {
|
|
1721
|
+
const entry = lock.skills[skillName];
|
|
1722
|
+
if (!entry) continue;
|
|
1723
|
+
if (entry.sourceType !== "github" || !entry.skillFolderHash || !entry.skillPath) {
|
|
1724
|
+
continue;
|
|
1725
|
+
}
|
|
1726
|
+
checkedCount++;
|
|
1727
|
+
try {
|
|
1728
|
+
const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token);
|
|
1729
|
+
if (latestHash && latestHash !== entry.skillFolderHash) {
|
|
1730
|
+
updates.push({ name: skillName, source: entry.source, entry });
|
|
1731
|
+
}
|
|
1732
|
+
} catch {
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
if (checkedCount === 0) {
|
|
1736
|
+
console.log(`${DIM3}No skills to check.${RESET3}`);
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
if (updates.length === 0) {
|
|
1740
|
+
console.log(`${TEXT2}\u2713 All skills are up to date${RESET3}`);
|
|
1741
|
+
console.log();
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
console.log(`${TEXT2}Found ${updates.length} update(s)${RESET3}`);
|
|
1745
|
+
console.log();
|
|
1746
|
+
let successCount = 0;
|
|
1747
|
+
let failCount = 0;
|
|
1748
|
+
for (const update of updates) {
|
|
1749
|
+
console.log(`${TEXT2}Updating ${update.name}...${RESET3}`);
|
|
1750
|
+
let installUrl = update.entry.sourceUrl;
|
|
1751
|
+
if (update.entry.skillPath) {
|
|
1752
|
+
let skillFolder = update.entry.skillPath;
|
|
1753
|
+
if (skillFolder.endsWith("/SKILL.md")) {
|
|
1754
|
+
skillFolder = skillFolder.slice(0, -9);
|
|
1755
|
+
} else if (skillFolder.endsWith("SKILL.md")) {
|
|
1756
|
+
skillFolder = skillFolder.slice(0, -8);
|
|
1757
|
+
}
|
|
1758
|
+
if (skillFolder.endsWith("/")) {
|
|
1759
|
+
skillFolder = skillFolder.slice(0, -1);
|
|
1760
|
+
}
|
|
1761
|
+
installUrl = update.entry.sourceUrl.replace(/\.git$/, "").replace(/\/$/, "");
|
|
1762
|
+
installUrl = `${installUrl}/tree/main/${skillFolder}`;
|
|
1763
|
+
}
|
|
1764
|
+
const result = spawnSync("npx", ["-y", "@codemcp/skills", "add", installUrl, "-g", "-y"], {
|
|
1765
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
1766
|
+
});
|
|
1767
|
+
if (result.status === 0) {
|
|
1768
|
+
successCount++;
|
|
1769
|
+
console.log(` ${TEXT2}\u2713${RESET3} Updated ${update.name}`);
|
|
1770
|
+
} else {
|
|
1771
|
+
failCount++;
|
|
1772
|
+
console.log(` ${DIM3}\u2717 Failed to update ${update.name}${RESET3}`);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
console.log();
|
|
1776
|
+
if (successCount > 0) {
|
|
1777
|
+
console.log(`${TEXT2}\u2713 Updated ${successCount} skill(s)${RESET3}`);
|
|
1778
|
+
}
|
|
1779
|
+
if (failCount > 0) {
|
|
1780
|
+
console.log(`${DIM3}Failed to update ${failCount} skill(s)${RESET3}`);
|
|
1781
|
+
}
|
|
1782
|
+
track({
|
|
1783
|
+
event: "update",
|
|
1784
|
+
skillCount: String(updates.length),
|
|
1785
|
+
successCount: String(successCount),
|
|
1786
|
+
failCount: String(failCount)
|
|
1787
|
+
});
|
|
1788
|
+
console.log();
|
|
1789
|
+
}
|
|
1790
|
+
async function main() {
|
|
1791
|
+
const args = process.argv.slice(2);
|
|
1792
|
+
if (args.length === 0) {
|
|
1793
|
+
showBanner();
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
const command = args[0];
|
|
1797
|
+
const restArgs = args.slice(1);
|
|
1798
|
+
switch (command) {
|
|
1799
|
+
case "find":
|
|
1800
|
+
case "search":
|
|
1801
|
+
case "f":
|
|
1802
|
+
case "s":
|
|
1803
|
+
showLogo();
|
|
1804
|
+
console.log();
|
|
1805
|
+
await runFind(restArgs);
|
|
1806
|
+
break;
|
|
1807
|
+
case "init":
|
|
1808
|
+
showLogo();
|
|
1809
|
+
console.log();
|
|
1810
|
+
runInit(restArgs);
|
|
1811
|
+
break;
|
|
1812
|
+
case "experimental_install": {
|
|
1813
|
+
showLogo();
|
|
1814
|
+
await runInstallFromLock(restArgs);
|
|
1815
|
+
break;
|
|
1816
|
+
}
|
|
1817
|
+
case "i":
|
|
1818
|
+
case "install":
|
|
1819
|
+
case "a":
|
|
1820
|
+
case "add": {
|
|
1821
|
+
showLogo();
|
|
1822
|
+
const { source: addSource, options: addOpts } = parseAddOptions(restArgs);
|
|
1823
|
+
await runAdd(addSource, addOpts);
|
|
1824
|
+
break;
|
|
1825
|
+
}
|
|
1826
|
+
case "remove":
|
|
1827
|
+
case "rm":
|
|
1828
|
+
case "r":
|
|
1829
|
+
if (restArgs.includes("--help") || restArgs.includes("-h")) {
|
|
1830
|
+
showRemoveHelp();
|
|
1831
|
+
break;
|
|
1832
|
+
}
|
|
1833
|
+
const { skills, options: removeOptions } = parseRemoveOptions(restArgs);
|
|
1834
|
+
await removeCommand(skills, removeOptions);
|
|
1835
|
+
break;
|
|
1836
|
+
case "experimental_sync": {
|
|
1837
|
+
showLogo();
|
|
1838
|
+
const { options: syncOptions } = parseSyncOptions(restArgs);
|
|
1839
|
+
await runSync(restArgs, syncOptions);
|
|
1840
|
+
break;
|
|
1841
|
+
}
|
|
1842
|
+
case "list":
|
|
1843
|
+
case "ls":
|
|
1844
|
+
await runList(restArgs);
|
|
1845
|
+
break;
|
|
1846
|
+
case "check":
|
|
1847
|
+
runCheck(restArgs);
|
|
1848
|
+
break;
|
|
1849
|
+
case "update":
|
|
1850
|
+
case "upgrade":
|
|
1851
|
+
runUpdate();
|
|
1852
|
+
break;
|
|
1853
|
+
case "mcp": {
|
|
1854
|
+
if (!restArgs[0] || restArgs[0] === "--help" || restArgs[0] === "-h") {
|
|
1855
|
+
showMcpHelp();
|
|
1856
|
+
break;
|
|
1857
|
+
}
|
|
1858
|
+
const subcommand = restArgs[0];
|
|
1859
|
+
const mcpArgs = restArgs.slice(1);
|
|
1860
|
+
if (subcommand === "setup") {
|
|
1861
|
+
showLogo();
|
|
1862
|
+
const options = parseMcpOptions(mcpArgs);
|
|
1863
|
+
await runMcpSetup(options);
|
|
1864
|
+
} else {
|
|
1865
|
+
console.error(`Unknown mcp subcommand: ${subcommand}`);
|
|
1866
|
+
showMcpHelp();
|
|
1867
|
+
}
|
|
1868
|
+
break;
|
|
1869
|
+
}
|
|
1870
|
+
case "--help":
|
|
1871
|
+
case "-h":
|
|
1872
|
+
showHelp();
|
|
1873
|
+
break;
|
|
1874
|
+
case "--version":
|
|
1875
|
+
case "-v":
|
|
1876
|
+
console.log(VERSION);
|
|
1877
|
+
break;
|
|
1878
|
+
default:
|
|
1879
|
+
console.log(`Unknown command: ${command}`);
|
|
1880
|
+
console.log(`Run ${BOLD3}npx @codemcp/skills --help${RESET3} for usage.`);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
main();
|