skillsmgr 0.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/index.js ADDED
@@ -0,0 +1,1638 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command as Command8 } from "commander";
5
+
6
+ // src/commands/setup.ts
7
+ import { Command } from "commander";
8
+ import { join as join3 } from "path";
9
+ import { fileURLToPath } from "url";
10
+ import { dirname as dirname2 } from "path";
11
+
12
+ // src/constants.ts
13
+ import { homedir } from "os";
14
+ import { join } from "path";
15
+ var SKILLS_MANAGER_DIR = join(homedir(), ".skills-manager");
16
+ var METADATA_FILENAME = ".skillsmgr.json";
17
+ var SKILL_SOURCES = ["official", "community", "custom"];
18
+ var SUPPORTED_TOOLS = [
19
+ "claude-code",
20
+ "cursor",
21
+ "windsurf",
22
+ "cline",
23
+ "roo-code",
24
+ "kilo-code",
25
+ "antigravity"
26
+ ];
27
+ var ANTHROPIC_SKILLS_REPO = "https://github.com/anthropics/skills";
28
+
29
+ // src/utils/fs.ts
30
+ import {
31
+ existsSync,
32
+ mkdirSync,
33
+ copyFileSync,
34
+ symlinkSync,
35
+ lstatSync,
36
+ readFileSync,
37
+ readdirSync,
38
+ unlinkSync,
39
+ writeFileSync,
40
+ rmSync
41
+ } from "fs";
42
+ import { dirname, join as join2 } from "path";
43
+ function ensureDir(dir) {
44
+ if (!existsSync(dir)) {
45
+ mkdirSync(dir, { recursive: true });
46
+ }
47
+ }
48
+ function copyFile(src, dest) {
49
+ ensureDir(dirname(dest));
50
+ copyFileSync(src, dest);
51
+ }
52
+ function isSymlink(path) {
53
+ try {
54
+ return lstatSync(path).isSymbolicLink();
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+ function readFileContent(path) {
60
+ return readFileSync(path, "utf-8");
61
+ }
62
+ function writeFile(path, content) {
63
+ ensureDir(dirname(path));
64
+ writeFileSync(path, content, "utf-8");
65
+ }
66
+ function fileExists(path) {
67
+ return existsSync(path);
68
+ }
69
+ function removeDir(dir) {
70
+ if (existsSync(dir)) {
71
+ rmSync(dir, { recursive: true, force: true });
72
+ }
73
+ }
74
+ function getDirectoriesInDir(dir) {
75
+ if (!existsSync(dir)) {
76
+ return [];
77
+ }
78
+ const entries = readdirSync(dir, { withFileTypes: true });
79
+ return entries.filter((e) => e.isDirectory()).map((e) => ({
80
+ name: e.name,
81
+ path: join2(dir, e.name)
82
+ })).sort((a, b) => a.name.localeCompare(b.name));
83
+ }
84
+ function copyDir(src, dest) {
85
+ ensureDir(dest);
86
+ const entries = readdirSync(src, { withFileTypes: true });
87
+ for (const entry of entries) {
88
+ const srcPath = join2(src, entry.name);
89
+ const destPath = join2(dest, entry.name);
90
+ if (entry.isDirectory()) {
91
+ copyDir(srcPath, destPath);
92
+ } else {
93
+ copyFile(srcPath, destPath);
94
+ }
95
+ }
96
+ }
97
+ function linkDir(src, dest) {
98
+ ensureDir(dirname(dest));
99
+ if (existsSync(dest)) {
100
+ unlinkSync(dest);
101
+ }
102
+ symlinkSync(src, dest);
103
+ }
104
+
105
+ // src/commands/setup.ts
106
+ var __filename2 = fileURLToPath(import.meta.url);
107
+ var __dirname2 = dirname2(__filename2);
108
+ async function executeSetup() {
109
+ console.log(`Creating ${SKILLS_MANAGER_DIR}...`);
110
+ for (const source of SKILL_SOURCES) {
111
+ const dir = join3(SKILLS_MANAGER_DIR, source);
112
+ ensureDir(dir);
113
+ console.log(`\u2713 Created ${source}/`);
114
+ }
115
+ const templateDir = join3(__dirname2, "templates", "example-skill");
116
+ const targetDir = join3(SKILLS_MANAGER_DIR, "custom", "example-skill");
117
+ if (!fileExists(targetDir)) {
118
+ copyDir(templateDir, targetDir);
119
+ console.log("\u2713 Created custom/example-skill/SKILL.md");
120
+ } else {
121
+ console.log("\xB7 custom/example-skill already exists, skipping");
122
+ }
123
+ console.log("\nSetup complete!\n");
124
+ console.log("Next steps:");
125
+ console.log(" skillsmgr install anthropic # Download official Anthropic skills");
126
+ console.log(" skillsmgr list # View available skills");
127
+ console.log(" skillsmgr init # Deploy skills to your project");
128
+ }
129
+ var setupCommand = new Command("setup").description("Initialize ~/.skills-manager/ directory structure").action(async () => {
130
+ await executeSetup();
131
+ });
132
+
133
+ // src/commands/install.ts
134
+ import { Command as Command2 } from "commander";
135
+ import { join as join6 } from "path";
136
+
137
+ // src/services/git.ts
138
+ import { execSync } from "child_process";
139
+ import { join as join4, basename } from "path";
140
+ import { existsSync as existsSync2 } from "fs";
141
+ var GitService = class {
142
+ /**
143
+ * Clone a git repository to the skills manager directory
144
+ * @param url Git URL or 'anthropic' shorthand
145
+ * @param isCustom Whether to install to custom/ instead of community/
146
+ * @returns Path where repo was cloned
147
+ */
148
+ clone(url, isCustom = false) {
149
+ const actualUrl = url === "anthropic" ? ANTHROPIC_SKILLS_REPO : url;
150
+ const repoName = this.extractRepoName(actualUrl);
151
+ let targetDir;
152
+ if (url === "anthropic") {
153
+ targetDir = join4(SKILLS_MANAGER_DIR, "official", "anthropic");
154
+ } else if (isCustom) {
155
+ targetDir = join4(SKILLS_MANAGER_DIR, "custom", repoName);
156
+ } else {
157
+ targetDir = join4(SKILLS_MANAGER_DIR, "community", repoName);
158
+ }
159
+ if (existsSync2(targetDir)) {
160
+ console.log(`Updating existing ${repoName}...`);
161
+ execSync("git pull", { cwd: targetDir, stdio: "inherit" });
162
+ return targetDir;
163
+ }
164
+ ensureDir(join4(targetDir, ".."));
165
+ console.log(`Cloning ${repoName}...`);
166
+ execSync(`git clone --depth 1 "${actualUrl}" "${targetDir}"`, {
167
+ stdio: "inherit"
168
+ });
169
+ return targetDir;
170
+ }
171
+ /**
172
+ * Clone a specific skill from a repository
173
+ * @param url Git URL with path to specific skill (e.g., https://github.com/org/repo/tree/main/skill-name)
174
+ * @param isCustom Whether to install to custom/
175
+ * @returns Path where skill was cloned
176
+ */
177
+ cloneSpecificSkill(url, isCustom = false) {
178
+ const match = url.match(
179
+ /github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/
180
+ );
181
+ if (!match) {
182
+ return null;
183
+ }
184
+ const [, owner, repo, branch, skillPath] = match;
185
+ const skillName = basename(skillPath);
186
+ const repoUrl = `https://github.com/${owner}/${repo}`;
187
+ const isAnthropic = owner === "anthropics" && repo === "skills";
188
+ let targetDir;
189
+ if (isAnthropic) {
190
+ targetDir = join4(SKILLS_MANAGER_DIR, "official", "anthropic");
191
+ } else if (isCustom) {
192
+ targetDir = join4(SKILLS_MANAGER_DIR, "custom", repo);
193
+ } else {
194
+ targetDir = join4(SKILLS_MANAGER_DIR, "community", repo);
195
+ }
196
+ const skillTargetDir = join4(targetDir, skillName);
197
+ ensureDir(targetDir);
198
+ if (!existsSync2(join4(targetDir, ".git"))) {
199
+ execSync(`git init`, { cwd: targetDir, stdio: "pipe" });
200
+ execSync(`git remote add origin "${repoUrl}"`, {
201
+ cwd: targetDir,
202
+ stdio: "pipe"
203
+ });
204
+ }
205
+ execSync(`git config core.sparseCheckout true`, {
206
+ cwd: targetDir,
207
+ stdio: "pipe"
208
+ });
209
+ execSync(`echo "${skillPath}" >> .git/info/sparse-checkout`, {
210
+ cwd: targetDir,
211
+ stdio: "pipe"
212
+ });
213
+ execSync(`git pull --depth 1 origin ${branch}`, {
214
+ cwd: targetDir,
215
+ stdio: "inherit"
216
+ });
217
+ return skillTargetDir;
218
+ }
219
+ extractRepoName(url) {
220
+ const match = url.match(/\/([^/]+?)(\.git)?$/);
221
+ return match ? match[1] : "unknown";
222
+ }
223
+ /**
224
+ * Check if a URL points to a specific skill (vs a repository)
225
+ */
226
+ isSpecificSkillUrl(url) {
227
+ return url.includes("/tree/");
228
+ }
229
+ };
230
+
231
+ // src/services/github.ts
232
+ import { join as join5 } from "path";
233
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
234
+ var GitHubService = class {
235
+ baseApiUrl = "https://api.github.com";
236
+ defaultBranchCache = /* @__PURE__ */ new Map();
237
+ /**
238
+ * Get the default branch for a repository
239
+ */
240
+ async getDefaultBranch(owner, repo) {
241
+ const cacheKey = `${owner}/${repo}`;
242
+ if (this.defaultBranchCache.has(cacheKey)) {
243
+ return this.defaultBranchCache.get(cacheKey);
244
+ }
245
+ const url = `${this.baseApiUrl}/repos/${owner}/${repo}`;
246
+ const response = await fetch(url, {
247
+ headers: this.getHeaders()
248
+ });
249
+ if (!response.ok) {
250
+ return "main";
251
+ }
252
+ const data = await response.json();
253
+ const branch = data.default_branch || "main";
254
+ this.defaultBranchCache.set(cacheKey, branch);
255
+ return branch;
256
+ }
257
+ /**
258
+ * List skills in a GitHub repository's skills directory
259
+ */
260
+ async listSkills(owner, repo, skillsPath = "skills") {
261
+ const url = `${this.baseApiUrl}/repos/${owner}/${repo}/contents/${skillsPath}`;
262
+ const response = await fetch(url, {
263
+ headers: this.getHeaders()
264
+ });
265
+ if (!response.ok) {
266
+ throw new Error(`Failed to list skills: ${response.statusText}`);
267
+ }
268
+ const contents = await response.json();
269
+ return contents.filter((item) => item.type === "dir").map((item) => ({ name: item.name, path: item.path }));
270
+ }
271
+ /**
272
+ * Download a specific skill directory from GitHub
273
+ */
274
+ async downloadSkill(owner, repo, skillPath, targetDir) {
275
+ ensureDir(targetDir);
276
+ await this.downloadDirectory(owner, repo, skillPath, targetDir);
277
+ }
278
+ /**
279
+ * Download all files in a directory recursively
280
+ */
281
+ async downloadDirectory(owner, repo, path, targetDir) {
282
+ const url = `${this.baseApiUrl}/repos/${owner}/${repo}/contents/${path}`;
283
+ const response = await fetch(url, {
284
+ headers: this.getHeaders()
285
+ });
286
+ if (!response.ok) {
287
+ throw new Error(`Failed to download directory: ${response.statusText}`);
288
+ }
289
+ const contents = await response.json();
290
+ for (const item of contents) {
291
+ const localPath = join5(targetDir, item.name);
292
+ if (item.type === "file" && item.download_url) {
293
+ await this.downloadFile(item.download_url, localPath);
294
+ } else if (item.type === "dir") {
295
+ mkdirSync2(localPath, { recursive: true });
296
+ await this.downloadDirectory(owner, repo, item.path, localPath);
297
+ }
298
+ }
299
+ }
300
+ /**
301
+ * Download a single file
302
+ */
303
+ async downloadFile(url, localPath) {
304
+ const response = await fetch(url);
305
+ if (!response.ok) {
306
+ throw new Error(`Failed to download file: ${response.statusText}`);
307
+ }
308
+ const content = await response.text();
309
+ writeFileSync2(localPath, content, "utf-8");
310
+ }
311
+ /**
312
+ * Get the target directory for a skill based on source
313
+ */
314
+ getTargetDir(owner, repo, skillName, isCustom = false) {
315
+ const isAnthropic = owner === "anthropics" && repo === "skills";
316
+ let baseDir;
317
+ if (isAnthropic) {
318
+ baseDir = join5(SKILLS_MANAGER_DIR, "official", "anthropic");
319
+ } else if (isCustom) {
320
+ baseDir = join5(SKILLS_MANAGER_DIR, "custom", repo);
321
+ } else {
322
+ baseDir = join5(SKILLS_MANAGER_DIR, "community", repo);
323
+ }
324
+ return join5(baseDir, skillName);
325
+ }
326
+ /**
327
+ * Parse a GitHub URL to extract owner, repo, and optional path
328
+ */
329
+ parseGitHubUrl(url) {
330
+ const treeMatch = url.match(
331
+ /github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)(?:\/(.+))?/
332
+ );
333
+ if (treeMatch) {
334
+ return {
335
+ owner: treeMatch[1],
336
+ repo: treeMatch[2],
337
+ branch: treeMatch[3],
338
+ path: treeMatch[4]
339
+ };
340
+ }
341
+ const basicMatch = url.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
342
+ if (basicMatch) {
343
+ return {
344
+ owner: basicMatch[1],
345
+ repo: basicMatch[2]
346
+ };
347
+ }
348
+ return null;
349
+ }
350
+ getHeaders() {
351
+ const headers = {
352
+ Accept: "application/vnd.github.v3+json",
353
+ "User-Agent": "skillsmgr"
354
+ };
355
+ const token = process.env.GITHUB_TOKEN;
356
+ if (token) {
357
+ headers["Authorization"] = `token ${token}`;
358
+ }
359
+ return headers;
360
+ }
361
+ };
362
+
363
+ // src/utils/prompts.ts
364
+ import inquirer from "inquirer";
365
+
366
+ // src/tools/configs.ts
367
+ var TOOL_CONFIGS = {
368
+ "claude-code": {
369
+ name: "claude-code",
370
+ displayName: "Claude Code",
371
+ skillsDir: ".claude/skills",
372
+ supportsLink: true,
373
+ supportsModeSpecific: false
374
+ },
375
+ "cursor": {
376
+ name: "cursor",
377
+ displayName: "Cursor",
378
+ skillsDir: ".cursor/skills",
379
+ supportsLink: true,
380
+ supportsModeSpecific: false
381
+ },
382
+ "windsurf": {
383
+ name: "windsurf",
384
+ displayName: "Windsurf",
385
+ skillsDir: ".windsurf/skills",
386
+ supportsLink: true,
387
+ supportsModeSpecific: false
388
+ },
389
+ "cline": {
390
+ name: "cline",
391
+ displayName: "Cline",
392
+ skillsDir: ".cline/skills",
393
+ supportsLink: true,
394
+ supportsModeSpecific: false
395
+ },
396
+ "roo-code": {
397
+ name: "roo-code",
398
+ displayName: "Roo Code",
399
+ skillsDir: ".roo/skills",
400
+ supportsLink: true,
401
+ supportsModeSpecific: true,
402
+ modePattern: "skills-{mode}",
403
+ availableModes: ["code", "architect"]
404
+ },
405
+ "kilo-code": {
406
+ name: "kilo-code",
407
+ displayName: "Kilo Code",
408
+ skillsDir: ".kilocode/skills",
409
+ supportsLink: true,
410
+ supportsModeSpecific: true,
411
+ modePattern: "skills-{mode}",
412
+ availableModes: ["code", "architect"]
413
+ },
414
+ "antigravity": {
415
+ name: "antigravity",
416
+ displayName: "Antigravity",
417
+ skillsDir: ".agent/skills",
418
+ supportsLink: true,
419
+ supportsModeSpecific: false
420
+ }
421
+ };
422
+ function getTargetDir(config, mode) {
423
+ if (config.supportsModeSpecific && mode && mode !== "all" && config.modePattern) {
424
+ const baseDir = config.skillsDir.split("/").slice(0, -1).join("/");
425
+ return `${baseDir}/${config.modePattern.replace("{mode}", mode)}`;
426
+ }
427
+ return config.skillsDir;
428
+ }
429
+
430
+ // src/utils/interactive-select.ts
431
+ import * as readline from "readline";
432
+ function wrapText(text, maxWidth) {
433
+ const words = text.split(" ");
434
+ const lines = [];
435
+ let currentLine = "";
436
+ for (const word of words) {
437
+ if (currentLine.length === 0) {
438
+ currentLine = word;
439
+ } else if (currentLine.length + 1 + word.length <= maxWidth) {
440
+ currentLine += " " + word;
441
+ } else {
442
+ lines.push(currentLine);
443
+ currentLine = word;
444
+ }
445
+ }
446
+ if (currentLine.length > 0) {
447
+ lines.push(currentLine);
448
+ }
449
+ return lines;
450
+ }
451
+ function buildDisplayItems(choices, searchQuery) {
452
+ const displayItems = [];
453
+ const filteredIndices = [];
454
+ let currentGroup;
455
+ choices.forEach((choice, index) => {
456
+ if (searchQuery && !choice.name.toLowerCase().includes(searchQuery.toLowerCase())) {
457
+ return;
458
+ }
459
+ filteredIndices.push(index);
460
+ if (choice.group && choice.group !== currentGroup) {
461
+ currentGroup = choice.group;
462
+ displayItems.push({ type: "separator", text: `\u2500\u2500 ${choice.group} \u2500\u2500` });
463
+ }
464
+ displayItems.push({ type: "choice", choiceIndex: index });
465
+ });
466
+ return { displayItems, filteredIndices };
467
+ }
468
+ async function interactiveCheckbox(options) {
469
+ const { message, choices, pageSize = 15, searchThreshold = 20 } = options;
470
+ const enableSearch = choices.length > searchThreshold;
471
+ const selected = new Set(
472
+ choices.map((c, i) => c.checked ? i : -1).filter((i) => i >= 0)
473
+ );
474
+ let searchQuery = "";
475
+ let { displayItems, filteredIndices } = buildDisplayItems(choices, searchQuery);
476
+ let cursor = displayItems.findIndex((item) => item.type === "choice");
477
+ if (cursor === -1) cursor = 0;
478
+ let scrollOffset = 0;
479
+ let lastRenderedLines = 0;
480
+ return new Promise((resolve) => {
481
+ const rl = readline.createInterface({
482
+ input: process.stdin,
483
+ output: process.stdout
484
+ });
485
+ if (process.stdin.isTTY) {
486
+ process.stdin.setRawMode(true);
487
+ }
488
+ readline.emitKeypressEvents(process.stdin, rl);
489
+ const render = (isInitial = false) => {
490
+ if (!isInitial && lastRenderedLines > 0) {
491
+ process.stdout.write(`\x1B[${lastRenderedLines}A\x1B[J`);
492
+ }
493
+ const lines = [];
494
+ lines.push(`? ${message}`);
495
+ if (enableSearch) {
496
+ const searchDisplay = searchQuery || "";
497
+ lines.push(` \x1B[33m\u{1F50D} Search:\x1B[0m ${searchDisplay}\x1B[2m\u2502\x1B[0m \x1B[2m(${filteredIndices.length}/${choices.length} skills)\x1B[0m`);
498
+ }
499
+ const visibleStart = scrollOffset;
500
+ const visibleEnd = Math.min(scrollOffset + pageSize, displayItems.length);
501
+ if (scrollOffset > 0) {
502
+ lines.push(" \x1B[2m\u2191 more above\x1B[0m");
503
+ }
504
+ if (displayItems.length === 0) {
505
+ lines.push(" \x1B[2mNo matching skills found\x1B[0m");
506
+ }
507
+ for (let i = visibleStart; i < visibleEnd; i++) {
508
+ const item = displayItems[i];
509
+ if (item.type === "separator") {
510
+ lines.push(` \x1B[33m${item.text}\x1B[0m`);
511
+ } else {
512
+ const choice = choices[item.choiceIndex];
513
+ const isSelected = selected.has(item.choiceIndex);
514
+ const isCursor = i === cursor;
515
+ const checkbox = isSelected ? "\x1B[32m\u25C9\x1B[0m" : "\u25EF";
516
+ const prefix = isCursor ? "\x1B[36m\u276F\x1B[0m" : " ";
517
+ const highlight = isCursor ? "\x1B[36m" : "";
518
+ const reset = "\x1B[0m";
519
+ const suffix = choice.suffix ? ` \x1B[33m${choice.suffix}\x1B[0m` : "";
520
+ lines.push(
521
+ `${prefix} ${checkbox} ${highlight}${choice.name}${reset}${suffix}`
522
+ );
523
+ if (isCursor && choice.description) {
524
+ const maxWidth = process.stdout.columns ? process.stdout.columns - 6 : 74;
525
+ const descLines = wrapText(choice.description, maxWidth);
526
+ for (const descLine of descLines) {
527
+ lines.push(` \x1B[2m${descLine}\x1B[0m`);
528
+ }
529
+ }
530
+ }
531
+ }
532
+ if (visibleEnd < displayItems.length) {
533
+ lines.push(" \x1B[2m\u2193 more below\x1B[0m");
534
+ }
535
+ if (enableSearch) {
536
+ lines.push(
537
+ "\x1B[2m(Type to search, \u2191\u2193 move, space select, ctrl+a toggle filtered, enter confirm)\x1B[0m"
538
+ );
539
+ } else {
540
+ lines.push(
541
+ "\x1B[2m(\u2191\u2193 move, space select, ctrl+a toggle all, enter confirm)\x1B[0m"
542
+ );
543
+ }
544
+ console.log(lines.join("\n"));
545
+ lastRenderedLines = lines.length;
546
+ };
547
+ render(true);
548
+ const cleanup = () => {
549
+ if (process.stdin.isTTY) {
550
+ process.stdin.setRawMode(false);
551
+ }
552
+ process.stdin.removeListener("keypress", handleKeypress);
553
+ rl.close();
554
+ };
555
+ const findPrevChoice = (from) => {
556
+ for (let i = from - 1; i >= 0; i--) {
557
+ if (displayItems[i].type === "choice") return i;
558
+ }
559
+ return from;
560
+ };
561
+ const findNextChoice = (from) => {
562
+ for (let i = from + 1; i < displayItems.length; i++) {
563
+ if (displayItems[i].type === "choice") return i;
564
+ }
565
+ return from;
566
+ };
567
+ const updateSearch = (newQuery) => {
568
+ searchQuery = newQuery;
569
+ const result = buildDisplayItems(choices, searchQuery);
570
+ displayItems = result.displayItems;
571
+ filteredIndices = result.filteredIndices;
572
+ cursor = displayItems.findIndex((item) => item.type === "choice");
573
+ if (cursor === -1) cursor = 0;
574
+ scrollOffset = 0;
575
+ };
576
+ const handleKeypress = (str, key) => {
577
+ if (!key) return;
578
+ if (enableSearch && str && str.length === 1 && !key.ctrl && !key.meta && key.name !== "space") {
579
+ if (/^[a-zA-Z0-9\-_.]$/.test(str)) {
580
+ updateSearch(searchQuery + str);
581
+ render();
582
+ return;
583
+ }
584
+ }
585
+ if (key.name === "up") {
586
+ cursor = findPrevChoice(cursor);
587
+ if (cursor < scrollOffset) {
588
+ scrollOffset = cursor;
589
+ }
590
+ render();
591
+ } else if (key.name === "down") {
592
+ cursor = findNextChoice(cursor);
593
+ if (cursor >= scrollOffset + pageSize) {
594
+ scrollOffset = cursor - pageSize + 1;
595
+ }
596
+ render();
597
+ } else if (key.name === "space") {
598
+ const item = displayItems[cursor];
599
+ if (item && item.type === "choice") {
600
+ const choiceIndex = item.choiceIndex;
601
+ if (selected.has(choiceIndex)) {
602
+ selected.delete(choiceIndex);
603
+ } else {
604
+ selected.add(choiceIndex);
605
+ }
606
+ render();
607
+ }
608
+ } else if (key.name === "a" && key.ctrl) {
609
+ const indicesToToggle = enableSearch && searchQuery ? filteredIndices : choices.map((_, i) => i);
610
+ const allSelected = indicesToToggle.every((i) => selected.has(i));
611
+ if (allSelected) {
612
+ indicesToToggle.forEach((i) => selected.delete(i));
613
+ } else {
614
+ indicesToToggle.forEach((i) => selected.add(i));
615
+ }
616
+ render();
617
+ } else if (key.name === "return") {
618
+ cleanup();
619
+ process.stdout.write(`\x1B[${lastRenderedLines}A\x1B[J`);
620
+ const selectedNames = Array.from(selected).sort((a, b) => a - b).map((i) => choices[i].name);
621
+ if (selectedNames.length === 0) {
622
+ console.log(`? ${message} \x1B[2mNone selected\x1B[0m`);
623
+ } else if (selectedNames.length <= 3) {
624
+ console.log(
625
+ `? ${message} \x1B[36m${selectedNames.join(", ")}\x1B[0m`
626
+ );
627
+ } else {
628
+ console.log(
629
+ `? ${message} \x1B[36m${selectedNames.length} skills selected\x1B[0m`
630
+ );
631
+ }
632
+ resolve(
633
+ Array.from(selected).sort((a, b) => a - b).map((i) => choices[i].value)
634
+ );
635
+ } else if (key.name === "c" && key.ctrl) {
636
+ cleanup();
637
+ console.log("\nCancelled.");
638
+ process.exit(0);
639
+ } else if (key.name === "backspace") {
640
+ if (enableSearch && searchQuery.length > 0) {
641
+ updateSearch(searchQuery.slice(0, -1));
642
+ render();
643
+ }
644
+ }
645
+ };
646
+ process.stdin.on("keypress", handleKeypress);
647
+ });
648
+ }
649
+
650
+ // src/utils/prompts.ts
651
+ function handlePromptError(error) {
652
+ if (error && typeof error === "object" && "name" in error) {
653
+ if (error.name === "ExitPromptError") {
654
+ console.log("\nCancelled.");
655
+ process.exit(0);
656
+ }
657
+ }
658
+ throw error;
659
+ }
660
+ async function promptTools(configuredTools) {
661
+ const choices = SUPPORTED_TOOLS.map((tool) => {
662
+ const config = TOOL_CONFIGS[tool];
663
+ const isConfigured = configuredTools?.includes(tool);
664
+ return {
665
+ name: isConfigured ? `${config.displayName} [configured]` : config.displayName,
666
+ value: tool,
667
+ checked: isConfigured
668
+ };
669
+ });
670
+ try {
671
+ const { tools } = await inquirer.prompt([
672
+ {
673
+ type: "checkbox",
674
+ name: "tools",
675
+ message: "Select target tools:",
676
+ choices,
677
+ validate: (answer) => {
678
+ if (answer.length === 0) {
679
+ return "You must select at least one tool.";
680
+ }
681
+ return true;
682
+ }
683
+ }
684
+ ]);
685
+ return tools;
686
+ } catch (error) {
687
+ handlePromptError(error);
688
+ }
689
+ }
690
+ async function promptMode(toolName, modes) {
691
+ const config = TOOL_CONFIGS[toolName];
692
+ const choices = [
693
+ { name: `All modes (${config.skillsDir}/)`, value: "all" },
694
+ ...modes.map((mode) => ({
695
+ name: `${mode.charAt(0).toUpperCase() + mode.slice(1)} mode only (${config.skillsDir.replace("skills", `skills-${mode}`)}/)`,
696
+ value: mode
697
+ }))
698
+ ];
699
+ try {
700
+ const { mode } = await inquirer.prompt([
701
+ {
702
+ type: "list",
703
+ name: "mode",
704
+ message: `Select target mode for ${config.displayName}:`,
705
+ choices
706
+ }
707
+ ]);
708
+ return mode;
709
+ } catch (error) {
710
+ handlePromptError(error);
711
+ }
712
+ }
713
+ async function promptSkills(skills, deployedSkillNames = []) {
714
+ const grouped = {};
715
+ for (const skill of skills) {
716
+ if (!grouped[skill.source]) {
717
+ grouped[skill.source] = [];
718
+ }
719
+ grouped[skill.source].push(skill);
720
+ }
721
+ const choices = [];
722
+ for (const [source, sourceSkills] of Object.entries(grouped)) {
723
+ for (const skill of sourceSkills) {
724
+ const isDeployed = deployedSkillNames.includes(skill.name);
725
+ choices.push({
726
+ name: skill.name,
727
+ description: skill.description,
728
+ value: skill.name,
729
+ checked: isDeployed,
730
+ group: source,
731
+ suffix: isDeployed ? "[deployed]" : void 0
732
+ });
733
+ }
734
+ }
735
+ return interactiveCheckbox({
736
+ message: "Select skills to deploy:",
737
+ choices,
738
+ pageSize: 15
739
+ });
740
+ }
741
+ async function promptSkillsToInstall(skills) {
742
+ const choices = skills.map((skill) => ({
743
+ name: skill.name,
744
+ description: skill.description,
745
+ value: skill.name
746
+ }));
747
+ return interactiveCheckbox({
748
+ message: "Select skills to install:",
749
+ choices,
750
+ pageSize: 15
751
+ });
752
+ }
753
+ async function promptSelect(message, choices) {
754
+ try {
755
+ const { selected } = await inquirer.prompt([
756
+ {
757
+ type: "list",
758
+ name: "selected",
759
+ message,
760
+ choices
761
+ }
762
+ ]);
763
+ return selected;
764
+ } catch (error) {
765
+ handlePromptError(error);
766
+ }
767
+ }
768
+ async function promptSyncAction(filename) {
769
+ try {
770
+ const { action } = await inquirer.prompt([
771
+ {
772
+ type: "list",
773
+ name: "action",
774
+ message: `${filename}: source changed`,
775
+ choices: [
776
+ { name: "Overwrite", value: "overwrite" },
777
+ { name: "Skip", value: "skip" },
778
+ { name: "Show diff", value: "diff" }
779
+ ]
780
+ }
781
+ ]);
782
+ return action;
783
+ } catch (error) {
784
+ handlePromptError(error);
785
+ }
786
+ }
787
+ async function promptOrphanAction(skillName) {
788
+ try {
789
+ const { action } = await inquirer.prompt([
790
+ {
791
+ type: "list",
792
+ name: "action",
793
+ message: `${skillName}: source no longer exists`,
794
+ choices: [
795
+ { name: "Remove", value: "remove" },
796
+ { name: "Keep", value: "keep" }
797
+ ]
798
+ }
799
+ ]);
800
+ return action;
801
+ } catch (error) {
802
+ handlePromptError(error);
803
+ }
804
+ }
805
+
806
+ // src/utils/progress.ts
807
+ var ProgressBar = class {
808
+ current = 0;
809
+ total;
810
+ label;
811
+ barWidth = 30;
812
+ constructor(total, label = "Progress") {
813
+ this.total = total;
814
+ this.label = label;
815
+ }
816
+ /**
817
+ * Start the progress bar
818
+ */
819
+ start() {
820
+ this.current = 0;
821
+ this.render();
822
+ }
823
+ /**
824
+ * Increment progress by 1
825
+ */
826
+ tick() {
827
+ this.current++;
828
+ this.render();
829
+ }
830
+ /**
831
+ * Update to specific value
832
+ */
833
+ update(value) {
834
+ this.current = value;
835
+ this.render();
836
+ }
837
+ /**
838
+ * Complete the progress bar
839
+ */
840
+ complete() {
841
+ this.current = this.total;
842
+ this.render();
843
+ console.log("");
844
+ }
845
+ /**
846
+ * Render the progress bar
847
+ */
848
+ render() {
849
+ const percent = Math.min(100, Math.round(this.current / this.total * 100));
850
+ const filled = Math.round(this.current / this.total * this.barWidth);
851
+ const empty = this.barWidth - filled;
852
+ const bar = "\x1B[32m" + "\u2588".repeat(filled) + "\x1B[0m" + "\u2591".repeat(empty);
853
+ const status = `${this.current}/${this.total}`;
854
+ process.stdout.write(`\r\x1B[K${this.label} ${bar} ${percent}% (${status})`);
855
+ }
856
+ };
857
+
858
+ // src/commands/install.ts
859
+ function parseSkillDescription(content) {
860
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
861
+ if (!frontmatterMatch) {
862
+ return "";
863
+ }
864
+ const descMatch = frontmatterMatch[1].match(/^description:\s*(.+)$/m);
865
+ return descMatch ? descMatch[1].trim() : "";
866
+ }
867
+ async function installFromAnthropic(options) {
868
+ const githubService = new GitHubService();
869
+ const owner = "anthropics";
870
+ const repo = "skills";
871
+ console.log("Fetching available skills from anthropic/skills...");
872
+ const skillsList = await githubService.listSkills(owner, repo, "skills");
873
+ if (skillsList.length === 0) {
874
+ console.log("No skills found in repository");
875
+ return;
876
+ }
877
+ const defaultBranch = await githubService.getDefaultBranch(owner, repo);
878
+ const skills = [];
879
+ const progress = new ProgressBar(skillsList.length, "Fetching skill info");
880
+ progress.start();
881
+ for (const skill of skillsList) {
882
+ try {
883
+ const response = await fetch(
884
+ `https://raw.githubusercontent.com/${owner}/${repo}/${defaultBranch}/${skill.path}/SKILL.md`
885
+ );
886
+ if (response.ok) {
887
+ const content = await response.text();
888
+ const description = parseSkillDescription(content);
889
+ skills.push({
890
+ name: skill.name,
891
+ description,
892
+ path: skill.path
893
+ });
894
+ } else {
895
+ skills.push({ name: skill.name, description: "", path: skill.path });
896
+ }
897
+ } catch {
898
+ skills.push({ name: skill.name, description: "", path: skill.path });
899
+ }
900
+ progress.tick();
901
+ }
902
+ progress.complete();
903
+ console.log(`Found ${skills.length} skills.
904
+ `);
905
+ let selectedSkills = skills;
906
+ if (!options.all) {
907
+ const selectedNames = await promptSkillsToInstall(skills);
908
+ if (selectedNames.length === 0) {
909
+ console.log("No skills selected");
910
+ return;
911
+ }
912
+ selectedSkills = skills.filter((s) => selectedNames.includes(s.name));
913
+ }
914
+ console.log(`
915
+ Downloading ${selectedSkills.length} skills...`);
916
+ const targetBase = join6(SKILLS_MANAGER_DIR, "official", "anthropic");
917
+ for (const skill of selectedSkills) {
918
+ const targetDir = join6(targetBase, skill.name);
919
+ process.stdout.write(` ${skill.name}...`);
920
+ await githubService.downloadSkill(owner, repo, skill.path, targetDir);
921
+ console.log(" \u2713");
922
+ }
923
+ console.log(`
924
+ \u2713 Installed ${selectedSkills.length} skills to ${targetBase}`);
925
+ }
926
+ async function installFromGitHubUrl(url, options) {
927
+ const githubService = new GitHubService();
928
+ const parsed = githubService.parseGitHubUrl(url);
929
+ if (!parsed) {
930
+ return false;
931
+ }
932
+ const { owner, repo, path } = parsed;
933
+ const isAnthropic = owner === "anthropics" && repo === "skills";
934
+ if (path) {
935
+ const skillName = path.split("/").pop() || path;
936
+ const targetDir = githubService.getTargetDir(owner, repo, skillName, options.custom);
937
+ console.log(`Downloading ${skillName}...`);
938
+ await githubService.downloadSkill(owner, repo, path, targetDir);
939
+ console.log(`\u2713 Installed ${skillName} to ${targetDir}`);
940
+ return true;
941
+ }
942
+ console.log(`Fetching available skills from ${owner}/${repo}...`);
943
+ let skillsList = [];
944
+ const skillsPaths = ["skills", ".", "src/skills"];
945
+ for (const skillsPath of skillsPaths) {
946
+ try {
947
+ skillsList = await githubService.listSkills(owner, repo, skillsPath);
948
+ if (skillsList.length > 0) break;
949
+ } catch {
950
+ continue;
951
+ }
952
+ }
953
+ if (skillsList.length === 0) {
954
+ return false;
955
+ }
956
+ const defaultBranch = await githubService.getDefaultBranch(owner, repo);
957
+ const skills = [];
958
+ const progress = new ProgressBar(skillsList.length, "Fetching skill info");
959
+ progress.start();
960
+ for (const skill of skillsList) {
961
+ try {
962
+ const response = await fetch(
963
+ `https://raw.githubusercontent.com/${owner}/${repo}/${defaultBranch}/${skill.path}/SKILL.md`
964
+ );
965
+ if (response.ok) {
966
+ const content = await response.text();
967
+ const description = parseSkillDescription(content);
968
+ skills.push({ name: skill.name, description, path: skill.path });
969
+ }
970
+ } catch {
971
+ }
972
+ progress.tick();
973
+ }
974
+ progress.complete();
975
+ if (skills.length === 0) {
976
+ return false;
977
+ }
978
+ console.log(`Found ${skills.length} skills.
979
+ `);
980
+ let selectedSkills = skills;
981
+ if (!options.all) {
982
+ const selectedNames = await promptSkillsToInstall(skills);
983
+ if (selectedNames.length === 0) {
984
+ console.log("No skills selected");
985
+ return true;
986
+ }
987
+ selectedSkills = skills.filter((s) => selectedNames.includes(s.name));
988
+ }
989
+ console.log(`
990
+ Downloading ${selectedSkills.length} skills...`);
991
+ let targetBase;
992
+ if (isAnthropic) {
993
+ targetBase = join6(SKILLS_MANAGER_DIR, "official", "anthropic");
994
+ } else if (options.custom) {
995
+ targetBase = join6(SKILLS_MANAGER_DIR, "custom", repo);
996
+ } else {
997
+ targetBase = join6(SKILLS_MANAGER_DIR, "community", repo);
998
+ }
999
+ for (const skill of selectedSkills) {
1000
+ const targetDir = join6(targetBase, skill.name);
1001
+ process.stdout.write(` ${skill.name}...`);
1002
+ await githubService.downloadSkill(owner, repo, skill.path, targetDir);
1003
+ console.log(" \u2713");
1004
+ }
1005
+ console.log(`
1006
+ \u2713 Installed ${selectedSkills.length} skills to ${targetBase}`);
1007
+ return true;
1008
+ }
1009
+ async function installViaGitClone(source, options) {
1010
+ const gitService = new GitService();
1011
+ if (gitService.isSpecificSkillUrl(source)) {
1012
+ const skillPath = gitService.cloneSpecificSkill(source, options.custom || false);
1013
+ if (skillPath) {
1014
+ console.log(`\u2713 Installed skill to ${skillPath}`);
1015
+ } else {
1016
+ console.log("Failed to parse skill URL");
1017
+ process.exit(1);
1018
+ }
1019
+ return;
1020
+ }
1021
+ const repoPath = gitService.clone(source, options.custom || false);
1022
+ let skillsRoot = repoPath;
1023
+ if (source === "anthropic") {
1024
+ const skillsSubdir = join6(repoPath, "skills");
1025
+ if (fileExists(skillsSubdir)) {
1026
+ skillsRoot = skillsSubdir;
1027
+ }
1028
+ }
1029
+ const skillDirs = getDirectoriesInDir(skillsRoot);
1030
+ const skills = [];
1031
+ for (const dir of skillDirs) {
1032
+ const skillMdPath = join6(dir.path, "SKILL.md");
1033
+ if (fileExists(skillMdPath)) {
1034
+ const content = readFileContent(skillMdPath);
1035
+ const description = parseSkillDescription(content);
1036
+ skills.push({
1037
+ name: dir.name,
1038
+ description,
1039
+ path: dir.path
1040
+ });
1041
+ }
1042
+ }
1043
+ if (skills.length === 0) {
1044
+ console.log("No skills found in repository");
1045
+ return;
1046
+ }
1047
+ console.log(`Found ${skills.length} skills.
1048
+ `);
1049
+ if (options.all) {
1050
+ console.log(`\u2713 Installed ${skills.length} skills to ${repoPath}`);
1051
+ return;
1052
+ }
1053
+ const selectedNames = await promptSkillsToInstall(skills);
1054
+ if (selectedNames.length === 0) {
1055
+ console.log("No skills selected");
1056
+ removeDir(repoPath);
1057
+ return;
1058
+ }
1059
+ const unselectedSkills = skills.filter((s) => !selectedNames.includes(s.name));
1060
+ for (const skill of unselectedSkills) {
1061
+ removeDir(skill.path);
1062
+ }
1063
+ console.log(`
1064
+ \u2713 Installed ${selectedNames.length} skills to ${repoPath}`);
1065
+ }
1066
+ async function executeInstall(source, options) {
1067
+ if (!fileExists(SKILLS_MANAGER_DIR)) {
1068
+ console.log("Skills manager not set up. Run: skillsmgr setup");
1069
+ process.exit(1);
1070
+ }
1071
+ try {
1072
+ if (source === "anthropic") {
1073
+ await installFromAnthropic(options);
1074
+ return;
1075
+ }
1076
+ if (source.includes("github.com")) {
1077
+ const success = await installFromGitHubUrl(source, options);
1078
+ if (success) return;
1079
+ console.log("GitHub API failed, falling back to git clone...");
1080
+ }
1081
+ await installViaGitClone(source, options);
1082
+ } catch (error) {
1083
+ if (error instanceof Error) {
1084
+ console.error(`Error: ${error.message}`);
1085
+ }
1086
+ process.exit(1);
1087
+ }
1088
+ }
1089
+ var installCommand = new Command2("install").description("Download skills from a repository").argument("<source>", 'Repository URL or "anthropic" for official skills').option("--all", "Install all skills without prompting").option("--custom", "Install to custom/ instead of community/").action(async (source, options) => {
1090
+ await executeInstall(source, options);
1091
+ });
1092
+
1093
+ // src/commands/list.ts
1094
+ import { Command as Command3 } from "commander";
1095
+
1096
+ // src/services/skills.ts
1097
+ import { join as join7 } from "path";
1098
+ var SkillsService = class {
1099
+ constructor(skillsDir) {
1100
+ this.skillsDir = skillsDir;
1101
+ }
1102
+ getAllSkills() {
1103
+ const skills = [];
1104
+ for (const source of SKILL_SOURCES) {
1105
+ const sourceDir = join7(this.skillsDir, source);
1106
+ const sourceSkills = this.getSkillsFromSource(sourceDir, source);
1107
+ skills.push(...sourceSkills);
1108
+ }
1109
+ return skills;
1110
+ }
1111
+ getSkillsBySource(source) {
1112
+ const sourceDir = join7(this.skillsDir, source);
1113
+ return this.getSkillsFromSource(sourceDir, source);
1114
+ }
1115
+ getSkillByName(name) {
1116
+ const allSkills = this.getAllSkills();
1117
+ return allSkills.find((s) => s.name === name);
1118
+ }
1119
+ getSkillsByNames(names) {
1120
+ const allSkills = this.getAllSkills();
1121
+ return names.map((name) => allSkills.find((s) => s.name === name)).filter((s) => s !== void 0);
1122
+ }
1123
+ findSkillsByName(name) {
1124
+ const allSkills = this.getAllSkills();
1125
+ return allSkills.filter((s) => s.name === name);
1126
+ }
1127
+ getSkillsFromSource(sourceDir, sourcePrefix) {
1128
+ const skills = [];
1129
+ if (!fileExists(sourceDir)) {
1130
+ return skills;
1131
+ }
1132
+ if (sourcePrefix === "custom") {
1133
+ const skillDirs = getDirectoriesInDir(sourceDir);
1134
+ for (const skillDir of skillDirs) {
1135
+ const skill = this.loadSkill(skillDir.path, sourcePrefix);
1136
+ if (skill) {
1137
+ skills.push(skill);
1138
+ }
1139
+ }
1140
+ } else {
1141
+ const repoDirs = getDirectoriesInDir(sourceDir);
1142
+ for (const repoDir of repoDirs) {
1143
+ const skillsSubdir = join7(repoDir.path, "skills");
1144
+ const searchDir = fileExists(skillsSubdir) ? skillsSubdir : repoDir.path;
1145
+ const skillDirs = getDirectoriesInDir(searchDir);
1146
+ for (const skillDir of skillDirs) {
1147
+ const source = `${sourcePrefix}/${repoDir.name}`;
1148
+ const skill = this.loadSkill(skillDir.path, source);
1149
+ if (skill) {
1150
+ skills.push(skill);
1151
+ }
1152
+ }
1153
+ }
1154
+ }
1155
+ return skills;
1156
+ }
1157
+ loadSkill(skillPath, source) {
1158
+ const skillMdPath = join7(skillPath, "SKILL.md");
1159
+ if (!fileExists(skillMdPath)) {
1160
+ return void 0;
1161
+ }
1162
+ const content = readFileContent(skillMdPath);
1163
+ const { name, description } = this.parseSkillMd(content);
1164
+ return {
1165
+ name: name || skillPath.split("/").pop() || "",
1166
+ description: description || "",
1167
+ path: skillPath,
1168
+ source
1169
+ };
1170
+ }
1171
+ parseSkillMd(content) {
1172
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
1173
+ if (!frontmatterMatch) {
1174
+ return { name: "", description: "" };
1175
+ }
1176
+ const frontmatter = frontmatterMatch[1];
1177
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
1178
+ const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
1179
+ return {
1180
+ name: nameMatch ? nameMatch[1].trim() : "",
1181
+ description: descMatch ? descMatch[1].trim() : ""
1182
+ };
1183
+ }
1184
+ };
1185
+
1186
+ // src/services/metadata.ts
1187
+ import { join as join8 } from "path";
1188
+ var MetadataService = class {
1189
+ constructor(projectDir) {
1190
+ this.projectDir = projectDir;
1191
+ this.metadataPath = join8(projectDir, METADATA_FILENAME);
1192
+ }
1193
+ metadataPath;
1194
+ load() {
1195
+ if (!fileExists(this.metadataPath)) {
1196
+ return { version: "1.0", tools: {} };
1197
+ }
1198
+ const content = readFileContent(this.metadataPath);
1199
+ return JSON.parse(content);
1200
+ }
1201
+ save(metadata) {
1202
+ writeFile(this.metadataPath, JSON.stringify(metadata, null, 2));
1203
+ }
1204
+ addDeployment(toolName, targetDir, mode, skills) {
1205
+ const metadata = this.load();
1206
+ metadata.tools[toolName] = {
1207
+ targetDir,
1208
+ mode,
1209
+ deployedAt: (/* @__PURE__ */ new Date()).toISOString(),
1210
+ skills
1211
+ };
1212
+ this.save(metadata);
1213
+ }
1214
+ updateDeployment(toolName, skills) {
1215
+ const metadata = this.load();
1216
+ if (metadata.tools[toolName]) {
1217
+ metadata.tools[toolName].skills = skills;
1218
+ metadata.tools[toolName].deployedAt = (/* @__PURE__ */ new Date()).toISOString();
1219
+ }
1220
+ this.save(metadata);
1221
+ }
1222
+ removeDeployment(toolName) {
1223
+ const metadata = this.load();
1224
+ delete metadata.tools[toolName];
1225
+ this.save(metadata);
1226
+ }
1227
+ getDeployedSkills(toolName) {
1228
+ const metadata = this.load();
1229
+ return metadata.tools[toolName]?.skills || [];
1230
+ }
1231
+ getToolDeployment(toolName) {
1232
+ const metadata = this.load();
1233
+ return metadata.tools[toolName];
1234
+ }
1235
+ getConfiguredTools() {
1236
+ const metadata = this.load();
1237
+ return Object.keys(metadata.tools);
1238
+ }
1239
+ hasMetadata() {
1240
+ return fileExists(this.metadataPath);
1241
+ }
1242
+ };
1243
+
1244
+ // src/commands/list.ts
1245
+ async function executeList(options) {
1246
+ if (options.deployed) {
1247
+ await listDeployed();
1248
+ } else {
1249
+ await listAvailable();
1250
+ }
1251
+ }
1252
+ async function listAvailable() {
1253
+ if (!fileExists(SKILLS_MANAGER_DIR)) {
1254
+ console.log("Skills manager not set up. Run: skillsmgr setup");
1255
+ process.exit(1);
1256
+ }
1257
+ const service = new SkillsService(SKILLS_MANAGER_DIR);
1258
+ const skills = service.getAllSkills();
1259
+ if (skills.length === 0) {
1260
+ console.log("No skills found in ~/.skills-manager/");
1261
+ console.log("\nRun: skillsmgr install anthropic");
1262
+ return;
1263
+ }
1264
+ console.log("Available skills in ~/.skills-manager/:\n");
1265
+ const grouped = {};
1266
+ for (const skill of skills) {
1267
+ if (!grouped[skill.source]) {
1268
+ grouped[skill.source] = [];
1269
+ }
1270
+ grouped[skill.source].push(skill);
1271
+ }
1272
+ for (const [source, sourceSkills] of Object.entries(grouped)) {
1273
+ console.log(`\u2500\u2500 ${source} (${sourceSkills.length} skill${sourceSkills.length > 1 ? "s" : ""}) \u2500\u2500`);
1274
+ for (const skill of sourceSkills) {
1275
+ console.log(` ${skill.name.padEnd(20)} ${skill.description}`);
1276
+ }
1277
+ console.log();
1278
+ }
1279
+ }
1280
+ async function listDeployed() {
1281
+ const metadataService = new MetadataService(process.cwd());
1282
+ if (!metadataService.hasMetadata()) {
1283
+ console.log("No skills deployed in current project.");
1284
+ console.log("\nRun: skillsmgr init");
1285
+ return;
1286
+ }
1287
+ console.log("Deployed skills in current project:\n");
1288
+ const configuredTools = metadataService.getConfiguredTools();
1289
+ for (const toolName of configuredTools) {
1290
+ const deployment = metadataService.getToolDeployment(toolName);
1291
+ if (!deployment) continue;
1292
+ const config = TOOL_CONFIGS[toolName];
1293
+ const displayName = config?.displayName || toolName;
1294
+ console.log(`${displayName} (${deployment.targetDir}/):`);
1295
+ for (const skill of deployment.skills) {
1296
+ const modeStr = skill.deployMode === "link" ? "link" : "copy";
1297
+ console.log(` \u25C9 ${skill.name.padEnd(16)} (${modeStr}) \u2190 ${skill.source}`);
1298
+ }
1299
+ console.log();
1300
+ }
1301
+ }
1302
+ var listCommand = new Command3("list").description("List available or deployed skills").option("--deployed", "List deployed skills in current project").action(async (options) => {
1303
+ await executeList(options);
1304
+ });
1305
+
1306
+ // src/commands/init.ts
1307
+ import { Command as Command4 } from "commander";
1308
+
1309
+ // src/services/deployer.ts
1310
+ import { join as join9 } from "path";
1311
+ import { existsSync as existsSync3, rmSync as rmSync2 } from "fs";
1312
+ var Deployer = class {
1313
+ constructor(projectDir) {
1314
+ this.projectDir = projectDir;
1315
+ }
1316
+ deploySkill(skill, toolConfig, mode, targetMode) {
1317
+ const targetDir = getTargetDir(toolConfig, targetMode);
1318
+ const fullTargetDir = join9(this.projectDir, targetDir);
1319
+ ensureDir(fullTargetDir);
1320
+ const skillTargetPath = join9(fullTargetDir, skill.name);
1321
+ if (mode === "link") {
1322
+ linkDir(skill.path, skillTargetPath);
1323
+ } else {
1324
+ copyDir(skill.path, skillTargetPath);
1325
+ }
1326
+ }
1327
+ deploySkills(skills, toolConfig, mode, targetMode) {
1328
+ for (const skill of skills) {
1329
+ this.deploySkill(skill, toolConfig, mode, targetMode);
1330
+ }
1331
+ }
1332
+ removeSkill(skillName, toolConfig, targetMode) {
1333
+ const targetDir = getTargetDir(toolConfig, targetMode);
1334
+ const skillPath = join9(this.projectDir, targetDir, skillName);
1335
+ if (existsSync3(skillPath)) {
1336
+ rmSync2(skillPath, { recursive: true, force: true });
1337
+ }
1338
+ }
1339
+ getDeployedSkillPath(skillName, toolConfig, targetMode) {
1340
+ const targetDir = getTargetDir(toolConfig, targetMode);
1341
+ return join9(this.projectDir, targetDir, skillName);
1342
+ }
1343
+ isSkillDeployed(skillName, toolConfig, targetMode) {
1344
+ const skillPath = this.getDeployedSkillPath(skillName, toolConfig, targetMode);
1345
+ return existsSync3(skillPath);
1346
+ }
1347
+ };
1348
+
1349
+ // src/commands/init.ts
1350
+ async function executeInit(options) {
1351
+ if (!fileExists(SKILLS_MANAGER_DIR)) {
1352
+ console.log("Skills manager not set up. Run: skillsmgr setup");
1353
+ process.exit(1);
1354
+ }
1355
+ const skillsService = new SkillsService(SKILLS_MANAGER_DIR);
1356
+ const metadataService = new MetadataService(process.cwd());
1357
+ const deployer = new Deployer(process.cwd());
1358
+ const allSkills = skillsService.getAllSkills();
1359
+ if (allSkills.length === 0) {
1360
+ console.log("No skills found. Run: skillsmgr install anthropic");
1361
+ process.exit(1);
1362
+ }
1363
+ const configuredTools = metadataService.getConfiguredTools();
1364
+ const selectedTools = await promptTools(configuredTools);
1365
+ const toolModes = {};
1366
+ for (const toolName of selectedTools) {
1367
+ const config = TOOL_CONFIGS[toolName];
1368
+ if (config.supportsModeSpecific && config.availableModes) {
1369
+ const mode = await promptMode(toolName, config.availableModes);
1370
+ toolModes[toolName] = mode;
1371
+ } else {
1372
+ toolModes[toolName] = "all";
1373
+ }
1374
+ }
1375
+ const deployedSkillNames = /* @__PURE__ */ new Set();
1376
+ for (const toolName of selectedTools) {
1377
+ const deployed = metadataService.getDeployedSkills(toolName);
1378
+ deployed.forEach((s) => deployedSkillNames.add(s.name));
1379
+ }
1380
+ const selectedSkillNames = await promptSkills(
1381
+ allSkills,
1382
+ Array.from(deployedSkillNames)
1383
+ );
1384
+ if (selectedSkillNames.length === 0) {
1385
+ console.log("No skills selected");
1386
+ return;
1387
+ }
1388
+ const selectedSkills = skillsService.getSkillsByNames(selectedSkillNames);
1389
+ const deployMode = options.copy ? "copy" : "link";
1390
+ console.log("\nDeploying skills...\n");
1391
+ for (const toolName of selectedTools) {
1392
+ const config = TOOL_CONFIGS[toolName];
1393
+ const mode = toolModes[toolName];
1394
+ const targetDir = getTargetDir(config, mode);
1395
+ console.log(`${config.displayName}:`);
1396
+ const previouslyDeployed = metadataService.getDeployedSkills(toolName);
1397
+ const previousNames = new Set(previouslyDeployed.map((s) => s.name));
1398
+ const toAdd = selectedSkills.filter((s) => !previousNames.has(s.name));
1399
+ const toKeep = selectedSkills.filter((s) => previousNames.has(s.name));
1400
+ const toRemove = previouslyDeployed.filter(
1401
+ (s) => !selectedSkillNames.includes(s.name)
1402
+ );
1403
+ for (const skill of toRemove) {
1404
+ deployer.removeSkill(skill.name, config, mode);
1405
+ console.log(` \u2717 ${skill.name} (removed)`);
1406
+ }
1407
+ for (const skill of toKeep) {
1408
+ console.log(` \xB7 ${skill.name} (unchanged)`);
1409
+ }
1410
+ for (const skill of toAdd) {
1411
+ deployer.deploySkill(skill, config, deployMode, mode);
1412
+ console.log(` \u2713 ${skill.name} (${deployMode === "link" ? "linked" : "copied"})`);
1413
+ }
1414
+ const newDeployedSkills = selectedSkills.map((skill) => ({
1415
+ name: skill.name,
1416
+ source: skill.source,
1417
+ deployMode
1418
+ }));
1419
+ metadataService.addDeployment(toolName, targetDir, mode, newDeployedSkills);
1420
+ console.log();
1421
+ }
1422
+ console.log(
1423
+ `Done! Deployed ${selectedSkillNames.length} skills to ${selectedTools.length} tool${selectedTools.length > 1 ? "s" : ""}.`
1424
+ );
1425
+ }
1426
+ var initCommand = new Command4("init").description("Deploy skills to current project").option("--copy", "Copy files instead of creating symlinks").action(async (options) => {
1427
+ await executeInit(options);
1428
+ });
1429
+
1430
+ // src/commands/add.ts
1431
+ import { Command as Command5 } from "commander";
1432
+ async function executeAdd(skillName, options) {
1433
+ if (!fileExists(SKILLS_MANAGER_DIR)) {
1434
+ console.log("Skills manager not set up. Run: skillsmgr setup");
1435
+ process.exit(1);
1436
+ }
1437
+ const skillsService = new SkillsService(SKILLS_MANAGER_DIR);
1438
+ const metadataService = new MetadataService(process.cwd());
1439
+ const deployer = new Deployer(process.cwd());
1440
+ const matchingSkills = skillsService.findSkillsByName(skillName);
1441
+ if (matchingSkills.length === 0) {
1442
+ console.log(`Skill '${skillName}' not found`);
1443
+ process.exit(1);
1444
+ }
1445
+ let skill = matchingSkills[0];
1446
+ if (matchingSkills.length > 1) {
1447
+ console.log(`Multiple skills found with name '${skillName}':`);
1448
+ const choices = matchingSkills.map((s, i) => ({
1449
+ name: `${i + 1}. ${s.source}/${s.name}`,
1450
+ value: s.source
1451
+ }));
1452
+ const selectedSource = await promptSelect("Select skill:", choices);
1453
+ skill = matchingSkills.find((s) => s.source === selectedSource);
1454
+ }
1455
+ let targetTools;
1456
+ if (options.tool) {
1457
+ if (!TOOL_CONFIGS[options.tool]) {
1458
+ console.log(`Unknown tool: ${options.tool}`);
1459
+ process.exit(1);
1460
+ }
1461
+ targetTools = [options.tool];
1462
+ } else {
1463
+ targetTools = metadataService.getConfiguredTools();
1464
+ if (targetTools.length === 0) {
1465
+ console.log("No tools configured. Run: skillsmgr init");
1466
+ process.exit(1);
1467
+ }
1468
+ }
1469
+ const deployMode = options.copy ? "copy" : "link";
1470
+ console.log(`Adding ${skillName} to configured tools...`);
1471
+ for (const toolName of targetTools) {
1472
+ const config = TOOL_CONFIGS[toolName];
1473
+ const deployment = metadataService.getToolDeployment(toolName);
1474
+ const mode = deployment?.mode || "all";
1475
+ deployer.deploySkill(skill, config, deployMode, mode);
1476
+ const existingSkills = metadataService.getDeployedSkills(toolName);
1477
+ const alreadyExists = existingSkills.some((s) => s.name === skill.name);
1478
+ if (!alreadyExists) {
1479
+ const newSkill = {
1480
+ name: skill.name,
1481
+ source: skill.source,
1482
+ deployMode
1483
+ };
1484
+ metadataService.updateDeployment(toolName, [...existingSkills, newSkill]);
1485
+ }
1486
+ console.log(
1487
+ ` \u2713 ${config.displayName} (${deployMode === "link" ? "linked" : "copied"})`
1488
+ );
1489
+ }
1490
+ }
1491
+ var addCommand = new Command5("add").description("Add a skill to the project").argument("<skill>", "Skill name to add").option("--tool <tool>", "Add to specific tool only").option("--copy", "Copy files instead of creating symlinks").action(async (skill, options) => {
1492
+ await executeAdd(skill, options);
1493
+ });
1494
+
1495
+ // src/commands/remove.ts
1496
+ import { Command as Command6 } from "commander";
1497
+ async function executeRemove(skillName, options) {
1498
+ const metadataService = new MetadataService(process.cwd());
1499
+ const deployer = new Deployer(process.cwd());
1500
+ if (!metadataService.hasMetadata()) {
1501
+ console.log("No skills deployed in current project.");
1502
+ process.exit(1);
1503
+ }
1504
+ let targetTools;
1505
+ if (options.tool) {
1506
+ if (!TOOL_CONFIGS[options.tool]) {
1507
+ console.log(`Unknown tool: ${options.tool}`);
1508
+ process.exit(1);
1509
+ }
1510
+ targetTools = [options.tool];
1511
+ } else {
1512
+ targetTools = metadataService.getConfiguredTools();
1513
+ }
1514
+ console.log(`Removing ${skillName}...`);
1515
+ let removed = false;
1516
+ for (const toolName of targetTools) {
1517
+ const config = TOOL_CONFIGS[toolName];
1518
+ const deployment = metadataService.getToolDeployment(toolName);
1519
+ if (!deployment) continue;
1520
+ const existingSkills = deployment.skills;
1521
+ const skillToRemove = existingSkills.find((s) => s.name === skillName);
1522
+ if (!skillToRemove) continue;
1523
+ deployer.removeSkill(skillName, config, deployment.mode);
1524
+ const remainingSkills = existingSkills.filter((s) => s.name !== skillName);
1525
+ metadataService.updateDeployment(toolName, remainingSkills);
1526
+ console.log(` \u2713 Removed from ${config.displayName}`);
1527
+ removed = true;
1528
+ }
1529
+ if (!removed) {
1530
+ console.log(`Skill '${skillName}' not found in any configured tool`);
1531
+ }
1532
+ }
1533
+ var removeCommand = new Command6("remove").description("Remove a skill from the project").argument("<skill>", "Skill name to remove").option("--tool <tool>", "Remove from specific tool only").action(async (skill, options) => {
1534
+ await executeRemove(skill, options);
1535
+ });
1536
+
1537
+ // src/commands/sync.ts
1538
+ import { Command as Command7 } from "commander";
1539
+ import { join as join10 } from "path";
1540
+ async function executeSync() {
1541
+ const metadataService = new MetadataService(process.cwd());
1542
+ const deployer = new Deployer(process.cwd());
1543
+ if (!metadataService.hasMetadata()) {
1544
+ console.log("No skills deployed in current project.");
1545
+ process.exit(1);
1546
+ }
1547
+ console.log("Checking deployed skills...\n");
1548
+ const configuredTools = metadataService.getConfiguredTools();
1549
+ let updatedCount = 0;
1550
+ let removedCount = 0;
1551
+ let untrackedCount = 0;
1552
+ for (const toolName of configuredTools) {
1553
+ const config = TOOL_CONFIGS[toolName];
1554
+ const deployment = metadataService.getToolDeployment(toolName);
1555
+ if (!deployment) continue;
1556
+ console.log(`${config.displayName} (${deployment.targetDir}/):`);
1557
+ for (const skill of deployment.skills) {
1558
+ const deployedPath = join10(process.cwd(), deployment.targetDir, skill.name);
1559
+ const sourcePath = join10(SKILLS_MANAGER_DIR, skill.source, skill.name);
1560
+ if (!fileExists(sourcePath)) {
1561
+ console.log(` \u2717 ${skill.name}: orphaned (source deleted)`);
1562
+ const action = await promptOrphanAction(skill.name);
1563
+ if (action === "remove") {
1564
+ deployer.removeSkill(skill.name, config, deployment.mode);
1565
+ const remaining = deployment.skills.filter((s) => s.name !== skill.name);
1566
+ metadataService.updateDeployment(toolName, remaining);
1567
+ console.log(` \u2713 Removed ${skill.name}`);
1568
+ removedCount++;
1569
+ }
1570
+ continue;
1571
+ }
1572
+ if (isSymlink(deployedPath)) {
1573
+ console.log(` \u2713 ${skill.name}: up to date (link)`);
1574
+ continue;
1575
+ }
1576
+ if (skill.deployMode === "copy") {
1577
+ const sourceSkillMd = join10(sourcePath, "SKILL.md");
1578
+ const deployedSkillMd = join10(deployedPath, "SKILL.md");
1579
+ if (fileExists(sourceSkillMd) && fileExists(deployedSkillMd)) {
1580
+ const sourceContent = readFileContent(sourceSkillMd);
1581
+ const deployedContent = readFileContent(deployedSkillMd);
1582
+ if (sourceContent !== deployedContent) {
1583
+ console.log(` \u26A0 ${skill.name}: source changed (copy)`);
1584
+ const action = await promptSyncAction(skill.name);
1585
+ if (action === "diff") {
1586
+ console.log("\n--- local");
1587
+ console.log(deployedContent.slice(0, 500));
1588
+ console.log("\n+++ source");
1589
+ console.log(sourceContent.slice(0, 500));
1590
+ console.log();
1591
+ const finalAction = await promptSyncAction(skill.name);
1592
+ if (finalAction === "overwrite") {
1593
+ deployer.deploySkill(
1594
+ { name: skill.name, description: "", path: sourcePath, source: skill.source },
1595
+ config,
1596
+ "copy",
1597
+ deployment.mode
1598
+ );
1599
+ console.log(` \u2713 Updated ${skill.name}`);
1600
+ updatedCount++;
1601
+ }
1602
+ } else if (action === "overwrite") {
1603
+ deployer.deploySkill(
1604
+ { name: skill.name, description: "", path: sourcePath, source: skill.source },
1605
+ config,
1606
+ "copy",
1607
+ deployment.mode
1608
+ );
1609
+ console.log(` \u2713 Updated ${skill.name}`);
1610
+ updatedCount++;
1611
+ }
1612
+ } else {
1613
+ console.log(` \u2713 ${skill.name}: up to date (copy)`);
1614
+ }
1615
+ }
1616
+ }
1617
+ }
1618
+ console.log();
1619
+ }
1620
+ console.log(
1621
+ `Sync complete: ${updatedCount} updated, ${removedCount} removed, ${untrackedCount} untracked`
1622
+ );
1623
+ }
1624
+ var syncCommand = new Command7("sync").description("Sync and verify deployed skills").action(async () => {
1625
+ await executeSync();
1626
+ });
1627
+
1628
+ // src/index.ts
1629
+ var program = new Command8();
1630
+ program.name("skillsmgr").description("Unified skills manager for AI coding tools").version("0.1.0");
1631
+ program.addCommand(setupCommand);
1632
+ program.addCommand(installCommand);
1633
+ program.addCommand(listCommand);
1634
+ program.addCommand(initCommand);
1635
+ program.addCommand(addCommand);
1636
+ program.addCommand(removeCommand);
1637
+ program.addCommand(syncCommand);
1638
+ program.parse();