skill-master 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/cli.js ADDED
@@ -0,0 +1,1368 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/installer.ts
4
+ import { join as join6 } from "path";
5
+ import { existsSync as existsSync6 } from "fs";
6
+
7
+ // src/core/git-source.ts
8
+ import { execFile } from "child_process";
9
+ import { promisify } from "util";
10
+ import { existsSync as existsSync2 } from "fs";
11
+
12
+ // src/utils/fs-helpers.ts
13
+ import { mkdir, cp, readFile, writeFile, rename, symlink, lstat, rm } from "fs/promises";
14
+ import { existsSync } from "fs";
15
+ import { dirname, join } from "path";
16
+ import { tmpdir } from "os";
17
+ import { randomBytes } from "crypto";
18
+ async function ensureDir(dir) {
19
+ await mkdir(dir, { recursive: true });
20
+ }
21
+ async function copyDir(src, dest) {
22
+ await ensureDir(dest);
23
+ await cp(src, dest, { recursive: true, force: true });
24
+ }
25
+ async function removePath(target) {
26
+ if (existsSync(target)) {
27
+ await rm(target, { recursive: true, force: true });
28
+ }
29
+ }
30
+ async function atomicWriteJson(filePath, data) {
31
+ await ensureDir(dirname(filePath));
32
+ const tmpPath = filePath + ".tmp";
33
+ await writeFile(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
34
+ await rename(tmpPath, filePath);
35
+ }
36
+ async function readJsonSafe(filePath) {
37
+ try {
38
+ const content = await readFile(filePath, "utf-8");
39
+ return JSON.parse(content);
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+ async function readTextSafe(filePath) {
45
+ try {
46
+ return await readFile(filePath, "utf-8");
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+ async function writeText(filePath, content) {
52
+ await ensureDir(dirname(filePath));
53
+ await writeFile(filePath, content, "utf-8");
54
+ }
55
+ async function symlinkOrCopy(target, linkPath, forceCopy = false) {
56
+ await ensureDir(dirname(linkPath));
57
+ await removePath(linkPath);
58
+ if (forceCopy) {
59
+ await copyDir(target, linkPath);
60
+ return "copy";
61
+ }
62
+ try {
63
+ await symlink(target, linkPath, "dir");
64
+ return "symlink";
65
+ } catch {
66
+ await copyDir(target, linkPath);
67
+ return "copy";
68
+ }
69
+ }
70
+ async function isSymlink(path) {
71
+ try {
72
+ const stat = await lstat(path);
73
+ return stat.isSymbolicLink();
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+ function createTempDir() {
79
+ const id = randomBytes(8).toString("hex");
80
+ return join(tmpdir(), `skill-master-${id}`);
81
+ }
82
+
83
+ // src/utils/errors.ts
84
+ var SkillManagerError = class extends Error {
85
+ constructor(message) {
86
+ super(message);
87
+ this.name = "SkillManagerError";
88
+ }
89
+ };
90
+ var SkillNotFoundError = class extends SkillManagerError {
91
+ constructor(skillName) {
92
+ super(`Skill "${skillName}" not found`);
93
+ this.name = "SkillNotFoundError";
94
+ }
95
+ };
96
+ var RegistryCorruptError = class extends SkillManagerError {
97
+ constructor(detail) {
98
+ super(`Registry is corrupted${detail ? ": " + detail : ""}`);
99
+ this.name = "RegistryCorruptError";
100
+ }
101
+ };
102
+ var GitCloneError = class extends SkillManagerError {
103
+ constructor(url, detail) {
104
+ super(`Failed to clone "${url}"${detail ? ": " + detail : ""}`);
105
+ this.name = "GitCloneError";
106
+ }
107
+ };
108
+ var SkillParseError = class extends SkillManagerError {
109
+ constructor(detail) {
110
+ super(`Failed to parse SKILL.md: ${detail}`);
111
+ this.name = "SkillParseError";
112
+ }
113
+ };
114
+
115
+ // src/utils/logger.ts
116
+ import chalk from "chalk";
117
+ var PREFIX = chalk.blue("skill-master");
118
+ function info(msg) {
119
+ console.log(`${PREFIX} ${chalk.cyan("info")} ${msg}`);
120
+ }
121
+ function success(msg) {
122
+ console.log(`${PREFIX} ${chalk.green("\u2714")} ${msg}`);
123
+ }
124
+ function warn(msg) {
125
+ console.log(`${PREFIX} ${chalk.yellow("\u26A0")} ${msg}`);
126
+ }
127
+ function error(msg) {
128
+ console.error(`${PREFIX} ${chalk.red("\u2716")} ${msg}`);
129
+ }
130
+ function debug(msg) {
131
+ if (process.env.DEBUG) {
132
+ console.log(`${PREFIX} ${chalk.gray("debug")} ${msg}`);
133
+ }
134
+ }
135
+ function step(num, total, msg) {
136
+ const counter = chalk.gray(`[${num}/${total}]`);
137
+ console.log(`${PREFIX} ${counter} ${msg}`);
138
+ }
139
+ function blank() {
140
+ console.log();
141
+ }
142
+ function kv(key, value) {
143
+ console.log(` ${chalk.gray(key + ":")} ${value}`);
144
+ }
145
+ function tableHeader(...cols) {
146
+ console.log(chalk.bold(cols.map((c) => c.padEnd(20)).join("")));
147
+ console.log(chalk.gray("\u2500".repeat(cols.length * 20)));
148
+ }
149
+ function tableRow(...cols) {
150
+ console.log(cols.map((c) => c.padEnd(20)).join(""));
151
+ }
152
+
153
+ // src/core/git-source.ts
154
+ var execFileAsync = promisify(execFile);
155
+ function isGitUrl(source) {
156
+ return source.startsWith("https://") || source.startsWith("http://") || source.startsWith("git@") || source.startsWith("git://") || source.includes("github.com/") || source.includes("gitlab.com/");
157
+ }
158
+ function normalizeGitUrl(source) {
159
+ if (source.startsWith("https://") || source.startsWith("http://") || source.startsWith("git@") || source.startsWith("git://")) {
160
+ if (source.startsWith("https://") && !source.endsWith(".git")) {
161
+ return source + ".git";
162
+ }
163
+ return source;
164
+ }
165
+ if (/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(source)) {
166
+ return `https://github.com/${source}.git`;
167
+ }
168
+ return source;
169
+ }
170
+ function parseGitUrl(url) {
171
+ const treeMatch = url.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/(.+)/);
172
+ if (treeMatch) {
173
+ return {
174
+ owner: treeMatch[1],
175
+ repo: treeMatch[2].replace(/\.git$/, ""),
176
+ branch: treeMatch[3]
177
+ };
178
+ }
179
+ const match = url.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
180
+ if (match) {
181
+ return { owner: match[1], repo: match[2] };
182
+ }
183
+ throw new GitCloneError(url, "Unable to parse GitHub URL");
184
+ }
185
+ async function cloneRepo(url, branch) {
186
+ const normalizedUrl = normalizeGitUrl(url);
187
+ const tempDir = createTempDir();
188
+ await ensureDir(tempDir);
189
+ const args = ["clone", "--depth", "1"];
190
+ if (branch) {
191
+ args.push("--branch", branch);
192
+ }
193
+ args.push(normalizedUrl, tempDir);
194
+ debug(`Cloning ${normalizedUrl} to ${tempDir}`);
195
+ try {
196
+ await execFileAsync("git", args, { timeout: 6e4 });
197
+ } catch (err) {
198
+ const msg = err instanceof Error ? err.message : String(err);
199
+ throw new GitCloneError(url, msg);
200
+ }
201
+ return tempDir;
202
+ }
203
+
204
+ // src/core/skill-parser.ts
205
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
206
+ import { readdir } from "fs/promises";
207
+ import { join as join2 } from "path";
208
+ import { existsSync as existsSync3 } from "fs";
209
+ var TOOL_CAPABILITY_MAP = {
210
+ "Bash": "shell",
211
+ "Read": "read_file",
212
+ "Write": "write_file",
213
+ "Edit": "edit_file",
214
+ "Glob": "find_file",
215
+ "Grep": "search_content",
216
+ "Task": "sub_task",
217
+ "WebFetch": "web_fetch",
218
+ "WebSearch": "web_search"
219
+ };
220
+ function parseSkillMd(content) {
221
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
222
+ if (!match) {
223
+ throw new SkillParseError("No valid frontmatter block found");
224
+ }
225
+ const rawFrontmatter = match[1];
226
+ const body = match[2];
227
+ let frontmatter;
228
+ try {
229
+ frontmatter = parseYaml(rawFrontmatter);
230
+ } catch (err) {
231
+ throw new SkillParseError(`YAML parse error: ${err.message}`);
232
+ }
233
+ validateFrontmatter(frontmatter);
234
+ return { frontmatter, body, rawFrontmatter };
235
+ }
236
+ function validateFrontmatter(fm) {
237
+ if (!fm.name || typeof fm.name !== "string") {
238
+ throw new SkillParseError('Missing or invalid "name" field');
239
+ }
240
+ if (!fm.version || typeof fm.version !== "string") {
241
+ throw new SkillParseError('Missing or invalid "version" field');
242
+ }
243
+ if (!Array.isArray(fm["allowed-tools"]) || fm["allowed-tools"].length === 0) {
244
+ throw new SkillParseError('Missing or empty "allowed-tools" field');
245
+ }
246
+ }
247
+ function inferCapabilities(allowedTools) {
248
+ const caps = /* @__PURE__ */ new Set();
249
+ for (const tool of allowedTools) {
250
+ const cap = TOOL_CAPABILITY_MAP[tool];
251
+ if (cap) {
252
+ caps.add(cap);
253
+ }
254
+ }
255
+ return [...caps];
256
+ }
257
+ async function findSkillDirectory(dir) {
258
+ if (existsSync3(join2(dir, "SKILL.md"))) {
259
+ return dir;
260
+ }
261
+ const skillsRoot = join2(dir, ".claude", "skills");
262
+ if (!existsSync3(skillsRoot)) {
263
+ return null;
264
+ }
265
+ try {
266
+ const entries = await readdir(skillsRoot, { withFileTypes: true });
267
+ for (const entry of entries) {
268
+ if (entry.isDirectory()) {
269
+ const skillMdPath = join2(skillsRoot, entry.name, "SKILL.md");
270
+ if (existsSync3(skillMdPath)) {
271
+ return join2(skillsRoot, entry.name);
272
+ }
273
+ }
274
+ }
275
+ } catch {
276
+ return null;
277
+ }
278
+ return null;
279
+ }
280
+ async function readSkillMd(dir) {
281
+ const content = await readTextSafe(join2(dir, "SKILL.md"));
282
+ if (!content) return null;
283
+ return parseSkillMd(content);
284
+ }
285
+ function extractEnvKeys(envExampleContent) {
286
+ const keys = [];
287
+ for (const line of envExampleContent.split("\n")) {
288
+ const trimmed = line.trim();
289
+ if (trimmed && !trimmed.startsWith("#")) {
290
+ const match = trimmed.match(/^([A-Z_][A-Z0-9_]*)=/);
291
+ if (match) {
292
+ keys.push(match[1]);
293
+ }
294
+ }
295
+ }
296
+ return keys;
297
+ }
298
+
299
+ // src/core/env-manager.ts
300
+ import { join as join4 } from "path";
301
+
302
+ // src/utils/paths.ts
303
+ import { homedir } from "os";
304
+ import { join as join3 } from "path";
305
+ var AGENTS_HOME = join3(homedir(), ".agents");
306
+ var CONFIG_DIR = join3(AGENTS_HOME, "config");
307
+ var SKILLS_DIR = join3(AGENTS_HOME, "skills");
308
+ var REGISTRY_PATH = join3(AGENTS_HOME, "registry.json");
309
+ function getSkillCanonicalPath(name) {
310
+ return join3(SKILLS_DIR, name);
311
+ }
312
+ function getSkillConfigPath(name) {
313
+ return join3(CONFIG_DIR, name);
314
+ }
315
+ var AGENT_SKILL_DIRS = {
316
+ "claude-code": ".claude/skills",
317
+ "opencode": ".opencode/skills",
318
+ "cursor": ".cursor/skills",
319
+ "cline": ".cline/skills",
320
+ "windsurf": ".windsurf/skills"
321
+ };
322
+ function getAgentSkillPath(cwd, agent, name) {
323
+ return join3(cwd, AGENT_SKILL_DIRS[agent], name);
324
+ }
325
+
326
+ // src/core/env-manager.ts
327
+ function parseEnvFile(content) {
328
+ const result = {};
329
+ for (const line of content.split("\n")) {
330
+ const trimmed = line.trim();
331
+ if (!trimmed || trimmed.startsWith("#")) continue;
332
+ const eqIndex = trimmed.indexOf("=");
333
+ if (eqIndex === -1) continue;
334
+ const key = trimmed.slice(0, eqIndex).trim();
335
+ let value = trimmed.slice(eqIndex + 1).trim();
336
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
337
+ value = value.slice(1, -1);
338
+ }
339
+ result[key] = value;
340
+ }
341
+ return result;
342
+ }
343
+ function serializeEnv(data) {
344
+ return Object.entries(data).map(([key, value]) => `${key}=${value}`).join("\n") + "\n";
345
+ }
346
+ async function backupEnv(skillName, agentSkillDir) {
347
+ const locations = [
348
+ join4(getSkillConfigPath(skillName), ".env"),
349
+ ...agentSkillDir ? [join4(agentSkillDir, ".env")] : [],
350
+ join4(getSkillCanonicalPath(skillName), ".env")
351
+ ];
352
+ for (const loc of locations) {
353
+ const content = await readTextSafe(loc);
354
+ if (content) {
355
+ const data = parseEnvFile(content);
356
+ if (Object.keys(data).length > 0) {
357
+ debug(`Backed up .env from ${loc}`);
358
+ return data;
359
+ }
360
+ }
361
+ }
362
+ return null;
363
+ }
364
+ async function restoreEnv(skillName, envData, skillDir) {
365
+ const exampleContent = await readTextSafe(join4(skillDir, ".env.example"));
366
+ let finalContent;
367
+ if (exampleContent) {
368
+ finalContent = mergeEnv(envData, exampleContent);
369
+ } else {
370
+ finalContent = serializeEnv(envData);
371
+ }
372
+ const configEnvPath = join4(getSkillConfigPath(skillName), ".env");
373
+ await writeText(configEnvPath, finalContent);
374
+ const skillEnvPath = join4(skillDir, ".env");
375
+ await writeText(skillEnvPath, finalContent);
376
+ debug(`Restored .env to ${configEnvPath} and ${skillEnvPath}`);
377
+ }
378
+ function mergeEnv(existing, exampleContent) {
379
+ const lines = [];
380
+ const usedKeys = /* @__PURE__ */ new Set();
381
+ for (const [key, value] of Object.entries(existing)) {
382
+ lines.push(`${key}=${value}`);
383
+ usedKeys.add(key);
384
+ }
385
+ const exampleKeys = parseEnvFile(exampleContent);
386
+ const newKeys = Object.keys(exampleKeys).filter((k) => !usedKeys.has(k));
387
+ if (newKeys.length > 0) {
388
+ lines.push("");
389
+ lines.push("# New keys added by skill update (please configure):");
390
+ for (const key of newKeys) {
391
+ lines.push(`# ${key}=`);
392
+ }
393
+ }
394
+ return lines.join("\n") + "\n";
395
+ }
396
+ async function getEnvStatus(skillName, requiredKeys) {
397
+ if (requiredKeys.length === 0) return "configured";
398
+ const configEnvPath = join4(getSkillConfigPath(skillName), ".env");
399
+ const content = await readTextSafe(configEnvPath);
400
+ if (!content) return "missing";
401
+ const data = parseEnvFile(content);
402
+ const configuredKeys = Object.entries(data).filter(([, v]) => v && !v.includes("your_") && !v.includes("_here")).map(([k]) => k);
403
+ const allConfigured = requiredKeys.every((k) => configuredKeys.includes(k));
404
+ const someConfigured = requiredKeys.some((k) => configuredKeys.includes(k));
405
+ if (allConfigured) return "configured";
406
+ if (someConfigured) return "partial";
407
+ return "missing";
408
+ }
409
+ async function setEnvValue(skillName, key, value, skillDir) {
410
+ const configEnvPath = join4(getSkillConfigPath(skillName), ".env");
411
+ const content = await readTextSafe(configEnvPath);
412
+ const data = content ? parseEnvFile(content) : {};
413
+ data[key] = value;
414
+ const newContent = serializeEnv(data);
415
+ await writeText(configEnvPath, newContent);
416
+ if (skillDir) {
417
+ await writeText(join4(skillDir, ".env"), newContent);
418
+ }
419
+ }
420
+ function getEnvEditPath(skillName) {
421
+ return join4(getSkillConfigPath(skillName), ".env");
422
+ }
423
+
424
+ // src/core/registry.ts
425
+ import { existsSync as existsSync4 } from "fs";
426
+ function createEmptyRegistry() {
427
+ return { version: 1, skills: {} };
428
+ }
429
+ function validateRegistry(data) {
430
+ if (!data || typeof data !== "object") return false;
431
+ const reg = data;
432
+ return reg.version === 1 && typeof reg.skills === "object" && reg.skills !== null;
433
+ }
434
+ async function readRegistry() {
435
+ if (!existsSync4(REGISTRY_PATH)) {
436
+ return createEmptyRegistry();
437
+ }
438
+ const data = await readJsonSafe(REGISTRY_PATH);
439
+ if (!data) {
440
+ throw new RegistryCorruptError("Failed to parse registry.json");
441
+ }
442
+ if (!validateRegistry(data)) {
443
+ throw new RegistryCorruptError("Invalid registry structure");
444
+ }
445
+ return data;
446
+ }
447
+ async function updateRegistry(skillName, entry) {
448
+ const registry = await readRegistry();
449
+ registry.skills[skillName] = entry;
450
+ await atomicWriteJson(REGISTRY_PATH, registry);
451
+ }
452
+ async function removeFromRegistry(skillName) {
453
+ const registry = await readRegistry();
454
+ delete registry.skills[skillName];
455
+ await atomicWriteJson(REGISTRY_PATH, registry);
456
+ }
457
+ async function listRegistry() {
458
+ const registry = await readRegistry();
459
+ return registry.skills;
460
+ }
461
+ async function getRegistryEntry(skillName) {
462
+ const registry = await readRegistry();
463
+ return registry.skills[skillName] ?? null;
464
+ }
465
+
466
+ // src/platform/detector.ts
467
+ import { existsSync as existsSync5 } from "fs";
468
+ import { join as join5 } from "path";
469
+ import { homedir as homedir2 } from "os";
470
+ var PLATFORM_MARKERS = [
471
+ { dir: ".claude", platform: "claude-code" },
472
+ { dir: ".cursor", platform: "cursor" },
473
+ { dir: ".windsurf", platform: "windsurf" },
474
+ { dir: ".cline", platform: "cline" }
475
+ ];
476
+ function detectPlatform(cwd) {
477
+ for (const { dir, platform } of PLATFORM_MARKERS) {
478
+ if (existsSync5(join5(cwd, dir))) {
479
+ return platform;
480
+ }
481
+ }
482
+ if (existsSync5(join5(homedir2(), ".config", "opencode"))) {
483
+ return "opencode";
484
+ }
485
+ return "claude-code";
486
+ }
487
+
488
+ // src/core/installer.ts
489
+ var TOTAL_STEPS = 9;
490
+ async function installSkill(options) {
491
+ const { source, cwd, copy = false, force = false } = options;
492
+ step(1, TOTAL_STEPS, "Fetching skill source...");
493
+ let sourceDir;
494
+ if (source.type === "git") {
495
+ sourceDir = await cloneRepo(source.url, source.branch);
496
+ } else if (source.type === "local") {
497
+ sourceDir = source.path;
498
+ if (!existsSync6(sourceDir)) {
499
+ throw new SkillNotFoundError(sourceDir);
500
+ }
501
+ } else {
502
+ throw new SkillParseError("Invalid source type");
503
+ }
504
+ step(2, TOTAL_STEPS, "Locating SKILL.md...");
505
+ const skillDir = await findSkillDirectory(sourceDir);
506
+ if (!skillDir) {
507
+ throw new SkillNotFoundError(`No SKILL.md found in ${sourceDir}`);
508
+ }
509
+ step(3, TOTAL_STEPS, "Parsing SKILL.md...");
510
+ const parsed = await readSkillMd(skillDir);
511
+ if (!parsed) {
512
+ throw new SkillParseError("Failed to read SKILL.md");
513
+ }
514
+ const skillName = parsed.frontmatter.name;
515
+ info(`Found skill: ${skillName} v${parsed.frontmatter.version}`);
516
+ step(4, TOTAL_STEPS, "Detecting agent platform...");
517
+ const agent = options.agent ?? detectPlatform(cwd);
518
+ info(`Target platform: ${agent}`);
519
+ step(5, TOTAL_STEPS, "Backing up .env...");
520
+ const agentSkillDir = getAgentSkillPath(cwd, agent, skillName);
521
+ const envBackup = await backupEnv(skillName, agentSkillDir);
522
+ if (envBackup) {
523
+ success(`Backed up ${Object.keys(envBackup).length} env key(s)`);
524
+ } else {
525
+ info("No existing .env found");
526
+ }
527
+ step(6, TOTAL_STEPS, "Installing to canonical path...");
528
+ const canonicalPath = getSkillCanonicalPath(skillName);
529
+ if (existsSync6(canonicalPath) && !force) {
530
+ info("Replacing existing installation");
531
+ }
532
+ await removePath(canonicalPath);
533
+ await copyDir(skillDir, canonicalPath);
534
+ success(`Installed to ${canonicalPath}`);
535
+ step(7, TOTAL_STEPS, "Restoring .env...");
536
+ if (envBackup) {
537
+ await restoreEnv(skillName, envBackup, canonicalPath);
538
+ success(".env restored successfully");
539
+ } else {
540
+ const examplePath = join6(canonicalPath, ".env.example");
541
+ if (existsSync6(examplePath)) {
542
+ warn("Found .env.example \u2014 run `skill-master env edit " + skillName + "` to configure");
543
+ }
544
+ }
545
+ step(8, TOTAL_STEPS, `Linking to ${agent} skills directory...`);
546
+ const agentPath = getAgentSkillPath(cwd, agent, skillName);
547
+ const linkType = await symlinkOrCopy(canonicalPath, agentPath, copy);
548
+ success(`${linkType === "symlink" ? "Symlinked" : "Copied"} to ${agentPath}`);
549
+ step(9, TOTAL_STEPS, "Updating registry...");
550
+ const capabilities = parsed.frontmatter.capabilities ?? inferCapabilities(parsed.frontmatter["allowed-tools"]);
551
+ const envExampleContent = await readTextSafe(join6(canonicalPath, ".env.example"));
552
+ const envKeys = envExampleContent ? extractEnvKeys(envExampleContent) : [];
553
+ const now = (/* @__PURE__ */ new Date()).toISOString();
554
+ const entry = {
555
+ source: source.type === "git" ? source.url : source.path,
556
+ version: parsed.frontmatter.version,
557
+ installed_at: now,
558
+ updated_at: now,
559
+ agent,
560
+ env_keys: envKeys,
561
+ capabilities,
562
+ canonical_path: canonicalPath,
563
+ agent_path: agentPath
564
+ };
565
+ await updateRegistry(skillName, entry);
566
+ blank();
567
+ success(`Skill "${skillName}" installed successfully!`);
568
+ }
569
+
570
+ // src/commands/add.ts
571
+ function parseAddFlags(args) {
572
+ const flags = {
573
+ global: false,
574
+ agent: [],
575
+ skill: [],
576
+ yes: false,
577
+ list: false,
578
+ all: false,
579
+ fullDepth: false,
580
+ copy: false,
581
+ force: false
582
+ };
583
+ let source = null;
584
+ let i = 0;
585
+ while (i < args.length) {
586
+ const arg = args[i];
587
+ if (arg.startsWith("--") && arg.includes("=")) {
588
+ const eqIdx = arg.indexOf("=");
589
+ const key = arg.slice(2, eqIdx);
590
+ const val = arg.slice(eqIdx + 1);
591
+ switch (key) {
592
+ case "agent":
593
+ flags.agent.push(val);
594
+ break;
595
+ case "skill":
596
+ flags.skill.push(val);
597
+ break;
598
+ default:
599
+ break;
600
+ }
601
+ i++;
602
+ continue;
603
+ }
604
+ switch (arg) {
605
+ case "-g":
606
+ case "--global":
607
+ flags.global = true;
608
+ i++;
609
+ break;
610
+ case "-a":
611
+ case "--agent":
612
+ i++;
613
+ while (i < args.length && !args[i].startsWith("-")) {
614
+ flags.agent.push(args[i]);
615
+ i++;
616
+ }
617
+ break;
618
+ case "-s":
619
+ case "--skill":
620
+ i++;
621
+ while (i < args.length && !args[i].startsWith("-")) {
622
+ flags.skill.push(args[i]);
623
+ i++;
624
+ }
625
+ break;
626
+ case "-y":
627
+ case "--yes":
628
+ flags.yes = true;
629
+ i++;
630
+ break;
631
+ case "-l":
632
+ case "--list":
633
+ flags.list = true;
634
+ i++;
635
+ break;
636
+ case "--all":
637
+ flags.all = true;
638
+ i++;
639
+ break;
640
+ case "--full-depth":
641
+ flags.fullDepth = true;
642
+ i++;
643
+ break;
644
+ case "--copy":
645
+ flags.copy = true;
646
+ i++;
647
+ break;
648
+ case "--force":
649
+ flags.force = true;
650
+ i++;
651
+ break;
652
+ default:
653
+ if (!arg.startsWith("-") && source === null) {
654
+ source = arg;
655
+ }
656
+ i++;
657
+ break;
658
+ }
659
+ }
660
+ if (flags.all) {
661
+ if (flags.skill.length === 0) flags.skill.push("*");
662
+ if (flags.agent.length === 0) flags.agent.push("*");
663
+ flags.yes = true;
664
+ }
665
+ return { source, flags };
666
+ }
667
+ async function add(args) {
668
+ if (args.length === 0) {
669
+ error("Usage: skill-master add <source> [options]");
670
+ console.log("");
671
+ console.log("Options:");
672
+ console.log(" -g, --global Install globally (~/.agents/)");
673
+ console.log(" -a, --agent <agents> Target agents (space-separated)");
674
+ console.log(" -s, --skill <skills> Select skills (space-separated)");
675
+ console.log(" -y, --yes Skip confirmations");
676
+ console.log(" -l, --list List available skills without installing");
677
+ console.log(" --all Install all skills to all agents");
678
+ console.log(" --full-depth Search all subdirectories");
679
+ console.log(" --copy Copy instead of symlink");
680
+ console.log(" --force Force reinstall");
681
+ process.exit(1);
682
+ }
683
+ const { source, flags } = parseAddFlags(args);
684
+ if (!source) {
685
+ error("No source specified. Provide a GitHub URL, owner/repo, or local path.");
686
+ process.exit(1);
687
+ }
688
+ const skillSource = isGitUrl(source) ? { type: "git", url: source } : { type: "local", path: source };
689
+ const cwd = process.cwd();
690
+ const agents = flags.agent.length > 0 ? flags.agent : [void 0];
691
+ try {
692
+ for (const agent of agents) {
693
+ await installSkill({
694
+ source: skillSource,
695
+ agent,
696
+ cwd,
697
+ copy: flags.copy,
698
+ force: flags.force,
699
+ yes: flags.yes
700
+ });
701
+ }
702
+ } catch (err) {
703
+ error(err.message);
704
+ process.exit(1);
705
+ }
706
+ }
707
+
708
+ // src/commands/update.ts
709
+ async function update(args) {
710
+ if (args.length === 0) {
711
+ error("Usage: skill-master update <skill-name> [--force]");
712
+ process.exit(1);
713
+ }
714
+ const skillName = args[0];
715
+ const force = args.includes("--force");
716
+ try {
717
+ const entry = await getRegistryEntry(skillName);
718
+ if (!entry) {
719
+ throw new SkillNotFoundError(skillName);
720
+ }
721
+ info(`Updating skill: ${skillName}`);
722
+ info(`Source: ${entry.source}`);
723
+ const source = isGitUrl(entry.source) ? { type: "git", url: entry.source } : { type: "local", path: entry.source };
724
+ await installSkill({
725
+ source,
726
+ agent: entry.agent,
727
+ cwd: process.cwd(),
728
+ force: true
729
+ });
730
+ success(`Skill "${skillName}" updated successfully!`);
731
+ } catch (err) {
732
+ error(err.message);
733
+ process.exit(1);
734
+ }
735
+ }
736
+
737
+ // src/commands/remove.ts
738
+ function parseRemoveFlags(args) {
739
+ const flags = {
740
+ global: false,
741
+ agent: [],
742
+ skill: [],
743
+ yes: false,
744
+ all: false,
745
+ purge: false
746
+ };
747
+ const names = [];
748
+ let i = 0;
749
+ while (i < args.length) {
750
+ const arg = args[i];
751
+ if (arg.startsWith("--") && arg.includes("=")) {
752
+ const eqIdx = arg.indexOf("=");
753
+ const key = arg.slice(2, eqIdx);
754
+ const val = arg.slice(eqIdx + 1);
755
+ switch (key) {
756
+ case "agent":
757
+ flags.agent.push(val);
758
+ break;
759
+ case "skill":
760
+ flags.skill.push(val);
761
+ break;
762
+ default:
763
+ break;
764
+ }
765
+ i++;
766
+ continue;
767
+ }
768
+ switch (arg) {
769
+ case "-g":
770
+ case "--global":
771
+ flags.global = true;
772
+ i++;
773
+ break;
774
+ case "-a":
775
+ case "--agent":
776
+ i++;
777
+ while (i < args.length && !args[i].startsWith("-")) {
778
+ flags.agent.push(args[i]);
779
+ i++;
780
+ }
781
+ break;
782
+ case "-s":
783
+ case "--skill":
784
+ i++;
785
+ while (i < args.length && !args[i].startsWith("-")) {
786
+ flags.skill.push(args[i]);
787
+ i++;
788
+ }
789
+ break;
790
+ case "-y":
791
+ case "--yes":
792
+ flags.yes = true;
793
+ i++;
794
+ break;
795
+ case "--all":
796
+ flags.all = true;
797
+ i++;
798
+ break;
799
+ case "--purge":
800
+ flags.purge = true;
801
+ i++;
802
+ break;
803
+ default:
804
+ if (!arg.startsWith("-")) {
805
+ names.push(arg);
806
+ }
807
+ i++;
808
+ break;
809
+ }
810
+ }
811
+ if (flags.all) {
812
+ flags.yes = true;
813
+ }
814
+ return { names, flags };
815
+ }
816
+ async function remove(args) {
817
+ if (args.length === 0) {
818
+ error("Usage: skill-master remove [skills...] [options]");
819
+ console.log("");
820
+ console.log("Options:");
821
+ console.log(" -g, --global Remove from global (~/.agents/)");
822
+ console.log(" -a, --agent <agents> Target agents (space-separated)");
823
+ console.log(" -s, --skill <skills> Select skills (space-separated)");
824
+ console.log(" -y, --yes Skip confirmations");
825
+ console.log(" --all Remove all skills");
826
+ console.log(" --purge Also remove config data");
827
+ process.exit(1);
828
+ }
829
+ const { names, flags } = parseRemoveFlags(args);
830
+ let skillNames;
831
+ if (flags.all) {
832
+ const registry = await listRegistry();
833
+ skillNames = Object.keys(registry);
834
+ } else if (flags.skill.length > 0) {
835
+ skillNames = flags.skill;
836
+ } else {
837
+ skillNames = names;
838
+ }
839
+ if (skillNames.length === 0) {
840
+ error("No skills specified. Provide skill names or use --all.");
841
+ process.exit(1);
842
+ }
843
+ try {
844
+ for (const skillName of skillNames) {
845
+ const entry = await getRegistryEntry(skillName);
846
+ if (!entry) {
847
+ throw new SkillNotFoundError(skillName);
848
+ }
849
+ info(`Removing skill: ${skillName}`);
850
+ await removePath(entry.agent_path);
851
+ success(`Removed from ${entry.agent_path}`);
852
+ await removePath(entry.canonical_path);
853
+ success(`Removed from ${entry.canonical_path}`);
854
+ if (flags.purge) {
855
+ await removePath(getSkillConfigPath(skillName));
856
+ success("Purged config directory");
857
+ }
858
+ await removeFromRegistry(skillName);
859
+ success(`Skill "${skillName}" removed successfully!`);
860
+ }
861
+ } catch (err) {
862
+ error(err.message);
863
+ process.exit(1);
864
+ }
865
+ }
866
+
867
+ // src/commands/env.ts
868
+ import { spawn } from "child_process";
869
+ async function env(args) {
870
+ const subcommand = args[0];
871
+ if (!subcommand || subcommand === "list") {
872
+ await envList();
873
+ } else if (subcommand === "set") {
874
+ await envSet(args.slice(1));
875
+ } else if (subcommand === "edit") {
876
+ await envEdit(args.slice(1));
877
+ } else {
878
+ error("Usage: skill-master env <list|set|edit>");
879
+ process.exit(1);
880
+ }
881
+ }
882
+ async function envList() {
883
+ const skills = await listRegistry();
884
+ const entries = Object.entries(skills);
885
+ if (entries.length === 0) {
886
+ info("No skills installed");
887
+ return;
888
+ }
889
+ blank();
890
+ tableHeader("Skill", "Status", "Keys");
891
+ for (const [name, entry] of entries) {
892
+ const status = await getEnvStatus(name, entry.env_keys);
893
+ const statusIcon = status === "configured" ? "\u2713" : status === "partial" ? "\u26A0" : "\u2717";
894
+ tableRow(name, `${statusIcon} ${status}`, entry.env_keys.join(", "));
895
+ }
896
+ blank();
897
+ }
898
+ async function envSet(args) {
899
+ if (args.length < 2) {
900
+ error("Usage: skill-master env set <skill> KEY=VALUE");
901
+ process.exit(1);
902
+ }
903
+ const skillName = args[0];
904
+ const [key, value] = args[1].split("=");
905
+ if (!key || !value) {
906
+ error("Invalid format. Use: KEY=VALUE");
907
+ process.exit(1);
908
+ }
909
+ try {
910
+ const skillDir = getSkillCanonicalPath(skillName);
911
+ await setEnvValue(skillName, key, value, skillDir);
912
+ success(`Set ${key} for ${skillName}`);
913
+ } catch (err) {
914
+ error(err.message);
915
+ process.exit(1);
916
+ }
917
+ }
918
+ async function envEdit(args) {
919
+ if (args.length === 0) {
920
+ error("Usage: skill-master env edit <skill>");
921
+ process.exit(1);
922
+ }
923
+ const skillName = args[0];
924
+ const envPath = getEnvEditPath(skillName);
925
+ const editor = process.env.EDITOR || "vi";
926
+ info(`Opening ${envPath} with ${editor}...`);
927
+ const child = spawn(editor, [envPath], {
928
+ stdio: "inherit",
929
+ shell: true
930
+ });
931
+ child.on("exit", (code) => {
932
+ if (code === 0) {
933
+ success("Saved");
934
+ } else {
935
+ error("Editor exited with error");
936
+ process.exit(1);
937
+ }
938
+ });
939
+ }
940
+
941
+ // src/commands/list.ts
942
+ function parseListFlags(args) {
943
+ const flags = {
944
+ global: false,
945
+ agent: []
946
+ };
947
+ let i = 0;
948
+ while (i < args.length) {
949
+ const arg = args[i];
950
+ if (arg.startsWith("--") && arg.includes("=")) {
951
+ const eqIdx = arg.indexOf("=");
952
+ const key = arg.slice(2, eqIdx);
953
+ const val = arg.slice(eqIdx + 1);
954
+ if (key === "agent") flags.agent.push(val);
955
+ i++;
956
+ continue;
957
+ }
958
+ switch (arg) {
959
+ case "-g":
960
+ case "--global":
961
+ flags.global = true;
962
+ i++;
963
+ break;
964
+ case "-a":
965
+ case "--agent":
966
+ i++;
967
+ while (i < args.length && !args[i].startsWith("-")) {
968
+ flags.agent.push(args[i]);
969
+ i++;
970
+ }
971
+ break;
972
+ default:
973
+ i++;
974
+ break;
975
+ }
976
+ }
977
+ return flags;
978
+ }
979
+ async function list(args = []) {
980
+ const flags = parseListFlags(args);
981
+ const skills = await listRegistry();
982
+ let entries = Object.entries(skills);
983
+ if (flags.agent.length > 0) {
984
+ entries = entries.filter(([, entry]) => flags.agent.includes(entry.agent));
985
+ }
986
+ if (entries.length === 0) {
987
+ info("No skills installed");
988
+ return;
989
+ }
990
+ blank();
991
+ tableHeader("Skill", "Version", "Platform", "Installed");
992
+ for (const [name, entry] of entries) {
993
+ const date = new Date(entry.installed_at).toLocaleDateString();
994
+ tableRow(name, entry.version, entry.agent, date);
995
+ }
996
+ blank();
997
+ }
998
+
999
+ // src/commands/info.ts
1000
+ async function info2(args) {
1001
+ if (args.length === 0) {
1002
+ error("Usage: skill-master info <skill-name>");
1003
+ process.exit(1);
1004
+ }
1005
+ const skillName = args[0];
1006
+ try {
1007
+ const entry = await getRegistryEntry(skillName);
1008
+ if (!entry) {
1009
+ throw new SkillNotFoundError(skillName);
1010
+ }
1011
+ const envStatus = await getEnvStatus(skillName, entry.env_keys);
1012
+ blank();
1013
+ info(`Skill: ${skillName}`);
1014
+ kv("Version", entry.version);
1015
+ kv("Platform", entry.agent);
1016
+ kv("Source", entry.source);
1017
+ kv("Installed", new Date(entry.installed_at).toLocaleString());
1018
+ kv("Updated", new Date(entry.updated_at).toLocaleString());
1019
+ kv("Canonical Path", entry.canonical_path);
1020
+ kv("Agent Path", entry.agent_path);
1021
+ kv("Capabilities", entry.capabilities.join(", "));
1022
+ kv("Env Keys", entry.env_keys.join(", ") || "none");
1023
+ kv("Env Status", envStatus);
1024
+ blank();
1025
+ } catch (err) {
1026
+ error(err.message);
1027
+ process.exit(1);
1028
+ }
1029
+ }
1030
+
1031
+ // src/commands/doctor.ts
1032
+ import { existsSync as existsSync7 } from "fs";
1033
+ async function doctor() {
1034
+ blank();
1035
+ info("Running diagnostics...");
1036
+ blank();
1037
+ let issues = 0;
1038
+ info("Checking directory structure...");
1039
+ const dirs = [AGENTS_HOME, CONFIG_DIR, SKILLS_DIR];
1040
+ for (const dir of dirs) {
1041
+ if (existsSync7(dir)) {
1042
+ success(`\u2713 ${dir}`);
1043
+ } else {
1044
+ warn(`\u2717 ${dir} (missing)`);
1045
+ issues++;
1046
+ }
1047
+ }
1048
+ blank();
1049
+ info("Checking registry...");
1050
+ if (existsSync7(REGISTRY_PATH)) {
1051
+ success(`\u2713 ${REGISTRY_PATH}`);
1052
+ try {
1053
+ const skills = await listRegistry();
1054
+ info(` Found ${Object.keys(skills).length} skill(s)`);
1055
+ } catch (err) {
1056
+ error(`\u2717 Registry corrupted: ${err.message}`);
1057
+ issues++;
1058
+ }
1059
+ } else {
1060
+ info(` No registry found (will be created on first install)`);
1061
+ }
1062
+ blank();
1063
+ info("Checking installed skills...");
1064
+ try {
1065
+ const skills = await listRegistry();
1066
+ for (const [name, entry] of Object.entries(skills)) {
1067
+ info(`Skill: ${name}`);
1068
+ if (existsSync7(entry.canonical_path)) {
1069
+ success(` \u2713 Canonical path exists`);
1070
+ } else {
1071
+ error(` \u2717 Canonical path missing: ${entry.canonical_path}`);
1072
+ issues++;
1073
+ }
1074
+ if (existsSync7(entry.agent_path)) {
1075
+ const isLink = await isSymlink(entry.agent_path);
1076
+ success(` \u2713 Agent path exists (${isLink ? "symlink" : "copy"})`);
1077
+ } else {
1078
+ error(` \u2717 Agent path missing: ${entry.agent_path}`);
1079
+ issues++;
1080
+ }
1081
+ const envStatus = await getEnvStatus(name, entry.env_keys);
1082
+ if (envStatus === "configured") {
1083
+ success(` \u2713 Environment configured`);
1084
+ } else if (envStatus === "partial") {
1085
+ warn(` \u26A0 Environment partially configured`);
1086
+ } else if (entry.env_keys.length > 0) {
1087
+ warn(` \u26A0 Environment not configured`);
1088
+ }
1089
+ }
1090
+ } catch (err) {
1091
+ error(`Failed to check skills: ${err.message}`);
1092
+ issues++;
1093
+ }
1094
+ blank();
1095
+ if (issues === 0) {
1096
+ success("All checks passed!");
1097
+ } else {
1098
+ warn(`Found ${issues} issue(s)`);
1099
+ }
1100
+ blank();
1101
+ }
1102
+
1103
+ // src/commands/find.ts
1104
+ async function find(args) {
1105
+ const query = args.filter((a) => !a.startsWith("-")).join(" ").trim();
1106
+ if (!query) {
1107
+ console.log("Usage: skill-master find <query>");
1108
+ console.log("");
1109
+ console.log("Search for skills in the online registry.");
1110
+ console.log("");
1111
+ console.log("Examples:");
1112
+ console.log(" skill-master find git");
1113
+ console.log(' skill-master find "code review"');
1114
+ process.exit(0);
1115
+ }
1116
+ info(`Searching for "${query}"...`);
1117
+ try {
1118
+ const url = `https://skills.sh/api/search?q=${encodeURIComponent(query)}`;
1119
+ const response = await fetch(url, {
1120
+ headers: { "Accept": "application/json" },
1121
+ signal: AbortSignal.timeout(1e4)
1122
+ });
1123
+ if (!response.ok) {
1124
+ error(`Search API returned ${response.status}: ${response.statusText}`);
1125
+ process.exit(1);
1126
+ }
1127
+ const data = await response.json();
1128
+ if (!Array.isArray(data) || data.length === 0) {
1129
+ info("No skills found matching your query.");
1130
+ return;
1131
+ }
1132
+ blank();
1133
+ tableHeader("Name", "Source", "Installs");
1134
+ for (const item of data) {
1135
+ tableRow(
1136
+ item.name ?? "\u2014",
1137
+ item.source ?? "\u2014",
1138
+ String(item.installs ?? 0)
1139
+ );
1140
+ }
1141
+ blank();
1142
+ } catch (err) {
1143
+ if (err.name === "TimeoutError") {
1144
+ error("Search request timed out. Please try again.");
1145
+ } else {
1146
+ error(`Search failed: ${err.message}`);
1147
+ }
1148
+ process.exit(1);
1149
+ }
1150
+ }
1151
+
1152
+ // src/commands/init.ts
1153
+ import { existsSync as existsSync8 } from "fs";
1154
+ import { join as join7, basename } from "path";
1155
+ var SKILL_MD_TEMPLATE = `---
1156
+ name: {{NAME}}
1157
+ version: 0.1.0
1158
+ author: ""
1159
+ description: ""
1160
+ allowed-tools:
1161
+ - Read
1162
+ - Edit
1163
+ - Write
1164
+ - Bash
1165
+ - Glob
1166
+ - Grep
1167
+ user-invocable: true
1168
+ ---
1169
+
1170
+ # {{NAME}}
1171
+
1172
+ <!-- Describe what this skill does -->
1173
+ `;
1174
+ async function init(args) {
1175
+ const nameArg = args.filter((a) => !a.startsWith("-"))[0];
1176
+ const cwd = process.cwd();
1177
+ let targetDir;
1178
+ let skillName;
1179
+ if (nameArg) {
1180
+ targetDir = join7(cwd, nameArg);
1181
+ skillName = nameArg;
1182
+ } else {
1183
+ targetDir = cwd;
1184
+ skillName = basename(cwd);
1185
+ }
1186
+ const skillMdPath = join7(targetDir, "SKILL.md");
1187
+ if (existsSync8(skillMdPath)) {
1188
+ error(`SKILL.md already exists at ${skillMdPath}`);
1189
+ process.exit(1);
1190
+ }
1191
+ await ensureDir(targetDir);
1192
+ const content = SKILL_MD_TEMPLATE.replace(/\{\{NAME\}\}/g, skillName);
1193
+ await writeText(skillMdPath, content);
1194
+ success(`Created ${skillMdPath}`);
1195
+ info(`Edit the file to configure your skill.`);
1196
+ }
1197
+
1198
+ // src/commands/check.ts
1199
+ import { execFile as execFile2 } from "child_process";
1200
+ import { promisify as promisify2 } from "util";
1201
+ var execFileAsync2 = promisify2(execFile2);
1202
+ async function check(_args) {
1203
+ const skills = await listRegistry();
1204
+ const entries = Object.entries(skills);
1205
+ if (entries.length === 0) {
1206
+ info("No skills installed");
1207
+ return;
1208
+ }
1209
+ info("Checking for updates...");
1210
+ blank();
1211
+ let updatable = 0;
1212
+ for (const [name, entry] of entries) {
1213
+ if (!isGitUrl(entry.source)) {
1214
+ info(`${name}: local source \u2014 skipped`);
1215
+ continue;
1216
+ }
1217
+ try {
1218
+ const remoteHead = await getRemoteHead(entry.source);
1219
+ if (!remoteHead) {
1220
+ warn(`${name}: unable to query remote`);
1221
+ continue;
1222
+ }
1223
+ const isUpToDate = entry.version === remoteHead.slice(0, entry.version.length);
1224
+ if (isUpToDate) {
1225
+ success(`${name}: up to date (${entry.version})`);
1226
+ } else {
1227
+ warn(`${name}: update available (${entry.version} \u2192 ${remoteHead.slice(0, 7)})`);
1228
+ updatable++;
1229
+ }
1230
+ } catch {
1231
+ warn(`${name}: failed to check remote`);
1232
+ }
1233
+ }
1234
+ blank();
1235
+ if (updatable === 0) {
1236
+ success("All skills are up to date!");
1237
+ } else {
1238
+ info(`${updatable} skill(s) can be updated. Run "skill-master update <name>" to update.`);
1239
+ }
1240
+ }
1241
+ async function getRemoteHead(source) {
1242
+ try {
1243
+ const { owner, repo } = parseGitUrl(source);
1244
+ const url = `https://github.com/${owner}/${repo}.git`;
1245
+ const { stdout } = await execFileAsync2("git", ["ls-remote", url, "HEAD"], {
1246
+ timeout: 15e3
1247
+ });
1248
+ const match = stdout.trim().split(/\s+/)[0];
1249
+ return match || null;
1250
+ } catch {
1251
+ return null;
1252
+ }
1253
+ }
1254
+
1255
+ // src/cli.ts
1256
+ var VERSION = "0.1.0";
1257
+ var HELP = `
1258
+ skill-master v${VERSION}
1259
+
1260
+ Usage:
1261
+ skill-master add <source> [options] Install skills (aliases: install, a, i)
1262
+ skill-master remove [skills...] [opts] Remove skills (aliases: rm, r)
1263
+ skill-master list [options] List installed skills (alias: ls)
1264
+ skill-master find [query] Search for skills (aliases: search, f, s)
1265
+ skill-master update [skill] Update skills (alias: upgrade)
1266
+ skill-master init [name] Create a new skill template
1267
+ skill-master check Check for skill updates
1268
+ skill-master env <list|set|edit> Manage environment variables
1269
+ skill-master info <skill-name> Show skill details
1270
+ skill-master doctor Run diagnostics
1271
+
1272
+ Add Options:
1273
+ -g, --global Install globally (~/.agents/)
1274
+ -a, --agent <agents> Target agents (space-separated)
1275
+ -s, --skill <skills> Select skills (space-separated)
1276
+ -y, --yes Skip confirmations
1277
+ -l, --list List available skills without installing
1278
+ --all Install all skills to all agents
1279
+ --full-depth Search all subdirectories
1280
+ --copy Copy instead of symlink
1281
+ --force Force reinstall
1282
+
1283
+ Examples:
1284
+ skill-master add owner/repo
1285
+ skill-master add https://github.com/user/skill -a claude-code cursor -y
1286
+ skill-master add ./local-skill --agent=cursor --copy
1287
+ skill-master remove my-skill --purge
1288
+ skill-master find "code review"
1289
+ skill-master init my-new-skill
1290
+ skill-master check
1291
+ `;
1292
+ async function main() {
1293
+ const args = process.argv.slice(2);
1294
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
1295
+ console.log(HELP);
1296
+ process.exit(0);
1297
+ }
1298
+ if (args[0] === "--version" || args[0] === "-v") {
1299
+ console.log(VERSION);
1300
+ process.exit(0);
1301
+ }
1302
+ const command = args[0];
1303
+ const commandArgs = args.slice(1);
1304
+ try {
1305
+ switch (command) {
1306
+ // add (primary) with aliases: install, a, i
1307
+ case "add":
1308
+ case "a":
1309
+ case "install":
1310
+ case "i":
1311
+ await add(commandArgs);
1312
+ break;
1313
+ // remove with aliases: rm, r
1314
+ case "remove":
1315
+ case "rm":
1316
+ case "r":
1317
+ await remove(commandArgs);
1318
+ break;
1319
+ // list with alias: ls
1320
+ case "list":
1321
+ case "ls":
1322
+ await list(commandArgs);
1323
+ break;
1324
+ // find with aliases: search, f, s
1325
+ case "find":
1326
+ case "search":
1327
+ case "f":
1328
+ case "s":
1329
+ await find(commandArgs);
1330
+ break;
1331
+ // update with alias: upgrade
1332
+ case "update":
1333
+ case "upgrade":
1334
+ await update(commandArgs);
1335
+ break;
1336
+ // init
1337
+ case "init":
1338
+ await init(commandArgs);
1339
+ break;
1340
+ // check
1341
+ case "check":
1342
+ await check(commandArgs);
1343
+ break;
1344
+ // env, info, doctor — skill-master extensions
1345
+ case "env":
1346
+ await env(commandArgs);
1347
+ break;
1348
+ case "info":
1349
+ await info2(commandArgs);
1350
+ break;
1351
+ case "doctor":
1352
+ await doctor();
1353
+ break;
1354
+ default:
1355
+ error(`Unknown command: ${command}`);
1356
+ console.log(HELP);
1357
+ process.exit(1);
1358
+ }
1359
+ } catch (err) {
1360
+ error(err.message);
1361
+ if (process.env.DEBUG) {
1362
+ console.error(err);
1363
+ }
1364
+ process.exit(1);
1365
+ }
1366
+ }
1367
+ main();
1368
+ //# sourceMappingURL=cli.js.map