@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/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();