synskill 0.1.4

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.
Files changed (3) hide show
  1. package/README.md +69 -0
  2. package/bin/synskill.js +2259 -0
  3. package/package.json +33 -0
@@ -0,0 +1,2259 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * This file is generated. Do not edit directly.
4
+ * Edit the source entrypoint and run `npm run build` to rebuild.
5
+ */
6
+ "use strict";
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+
12
+ // src/args.js
13
+ var require_args = __commonJS({
14
+ "src/args.js"(exports2, module2) {
15
+ "use strict";
16
+ var KNOWN_COMMANDS = /* @__PURE__ */ new Set(["install", "add", "list", "update", "help", "version"]);
17
+ function parseArgs2(argv) {
18
+ let command = "";
19
+ const options = {
20
+ dest: "",
21
+ global: false,
22
+ projectPath: void 0,
23
+ skills: [],
24
+ all: false,
25
+ copy: false,
26
+ force: false,
27
+ noPull: false,
28
+ yes: false,
29
+ filter: "",
30
+ details: false,
31
+ repoUrl: ""
32
+ };
33
+ const args = [...argv];
34
+ if (args[0] && !args[0].startsWith("-")) {
35
+ const firstToken = args.shift();
36
+ if (KNOWN_COMMANDS.has(firstToken)) {
37
+ command = firstToken;
38
+ } else {
39
+ command = "install";
40
+ options.skills.push(firstToken);
41
+ }
42
+ }
43
+ while (args.length) {
44
+ const arg = args.shift();
45
+ switch (arg) {
46
+ case "--dest":
47
+ options.dest = args.shift() || "";
48
+ break;
49
+ case "--global":
50
+ options.global = true;
51
+ break;
52
+ case "--project": {
53
+ const next = args[0];
54
+ if (next && !next.startsWith("-")) {
55
+ options.projectPath = args.shift();
56
+ } else {
57
+ options.projectPath = "";
58
+ }
59
+ break;
60
+ }
61
+ case "--skills":
62
+ options.skills = splitList(args.shift() || "");
63
+ break;
64
+ case "--all":
65
+ options.all = true;
66
+ break;
67
+ case "--copy":
68
+ options.copy = true;
69
+ break;
70
+ case "--force":
71
+ options.force = true;
72
+ break;
73
+ case "--no-pull":
74
+ options.noPull = true;
75
+ break;
76
+ case "--yes":
77
+ case "-y":
78
+ options.yes = true;
79
+ break;
80
+ case "--filter":
81
+ case "--search":
82
+ options.filter = args.shift() || "";
83
+ break;
84
+ case "--details":
85
+ case "--full":
86
+ options.details = true;
87
+ break;
88
+ case "--repo-url":
89
+ options.repoUrl = args.shift() || "";
90
+ break;
91
+ case "--help":
92
+ case "-h":
93
+ command = "help";
94
+ break;
95
+ case "--version":
96
+ case "-v":
97
+ command = "version";
98
+ break;
99
+ default:
100
+ if (!arg.startsWith("-") && (command === "install" || command === "add" || command === "")) {
101
+ options.skills.push(arg);
102
+ break;
103
+ }
104
+ if (arg.startsWith("--skills=")) {
105
+ options.skills = splitList(arg.split("=").slice(1).join("="));
106
+ break;
107
+ }
108
+ if (arg.startsWith("--project=")) {
109
+ options.projectPath = arg.split("=").slice(1).join("=") || "";
110
+ break;
111
+ }
112
+ if (arg.startsWith("--filter=")) {
113
+ options.filter = arg.split("=").slice(1).join("=") || "";
114
+ break;
115
+ }
116
+ if (arg.startsWith("--repo-url=")) {
117
+ options.repoUrl = arg.split("=").slice(1).join("=") || "";
118
+ break;
119
+ }
120
+ break;
121
+ }
122
+ }
123
+ return { command, options };
124
+ }
125
+ function splitList(value) {
126
+ return String(value).replace(/[; ]/g, ",").split(",").map((entry) => entry.trim()).filter(Boolean);
127
+ }
128
+ module2.exports = {
129
+ KNOWN_COMMANDS,
130
+ parseArgs: parseArgs2,
131
+ splitList
132
+ };
133
+ }
134
+ });
135
+
136
+ // src/constants.js
137
+ var require_constants = __commonJS({
138
+ "src/constants.js"(exports2, module2) {
139
+ "use strict";
140
+ var os = require("os");
141
+ var path2 = require("path");
142
+ var CLI_NAME2 = "synskill";
143
+ var DEFAULT_BRANCH2 = "main";
144
+ var DEFAULT_REPO_URL2 = decodeObfuscatedString(
145
+ [20, 16, 26, 51, 9, 0, 24, 14, 88, 0, 10, 6, 28, 75, 28, 11, 9, 73, 25, 8, 22, 9, 95, 16, 22, 76, 27, 28, 29, 24, 30, 0, 14, 68, 31, 7, 68, 15, 13, 16, 70, 2, 26, 13],
146
+ "synskill-cache"
147
+ );
148
+ var PRIVATE_REPO_ENV_VAR2 = decodeObfuscatedString(
149
+ [32, 32, 32, 50, 59, 58, 41, 51, 126, 46, 39, 58, 63, 42, 49, 33, 46, 57, 35, 51, 120, 55, 34],
150
+ "synskill-env"
151
+ );
152
+ var CACHE_DIR_NAME = ".synskill";
153
+ var LEGACY_CACHE_DIR_NAMES = [
154
+ decodeObfuscatedString([93, 10, 23, 29, 10, 25, 31, 9, 0, 16, 10, 10, 4, 9, 0], "synskill-cache")
155
+ ];
156
+ var LEGACY_CACHE_METADATA_NAMES = [
157
+ decodeObfuscatedString(
158
+ [93, 10, 23, 29, 10, 25, 31, 9, 0, 30, 14, 29, 13, 31, 10, 67, 16, 10, 10, 4, 9, 3, 7, 22, 27, 15],
159
+ "synskill-meta"
160
+ )
161
+ ];
162
+ var LEGACY_REPO_PACKAGE_NAMES = [
163
+ decodeObfuscatedString([0, 0, 0, 18, 27, 26, 9, 65, 94, 27, 2, 11, 31, 10], "synskill-pkg")
164
+ ];
165
+ var LEGACY_REPO_ENTRY_FILES = [
166
+ decodeObfuscatedString([0, 0, 0, 18, 27, 26, 9, 65, 94, 14, 7, 24, 30, 10, 93, 19, 29], "synskill-entry")
167
+ ];
168
+ var CACHE_METADATA_FILE = ".synskill-cache.json";
169
+ var CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
170
+ var INSTALL_TARGETS2 = [
171
+ {
172
+ key: "agents",
173
+ name: "GitHub Copilot, Cursor, Codex, OpenCode",
174
+ folder: ".agents"
175
+ },
176
+ {
177
+ key: "claude",
178
+ name: "Claude Code",
179
+ folder: ".claude"
180
+ }
181
+ ];
182
+ function userHome() {
183
+ return process.env.USERPROFILE || os.homedir();
184
+ }
185
+ function getCacheDir(homeDir = userHome()) {
186
+ return path2.join(homeDir, CACHE_DIR_NAME);
187
+ }
188
+ function getCacheMetadataPath(cacheDir) {
189
+ return path2.join(cacheDir, CACHE_METADATA_FILE);
190
+ }
191
+ function decodeObfuscatedString(bytes, key) {
192
+ let value = "";
193
+ for (let index = 0; index < bytes.length; index += 1) {
194
+ value += String.fromCharCode(bytes[index] ^ key.charCodeAt(index % key.length));
195
+ }
196
+ return value;
197
+ }
198
+ module2.exports = {
199
+ CACHE_DIR_NAME,
200
+ CACHE_METADATA_FILE,
201
+ CACHE_TTL_MS,
202
+ CLI_NAME: CLI_NAME2,
203
+ DEFAULT_BRANCH: DEFAULT_BRANCH2,
204
+ DEFAULT_REPO_URL: DEFAULT_REPO_URL2,
205
+ INSTALL_TARGETS: INSTALL_TARGETS2,
206
+ LEGACY_CACHE_DIR_NAMES,
207
+ LEGACY_CACHE_METADATA_NAMES,
208
+ LEGACY_REPO_ENTRY_FILES,
209
+ LEGACY_REPO_PACKAGE_NAMES,
210
+ PRIVATE_REPO_ENV_VAR: PRIVATE_REPO_ENV_VAR2,
211
+ getCacheDir,
212
+ getCacheMetadataPath,
213
+ userHome
214
+ };
215
+ }
216
+ });
217
+
218
+ // src/git.js
219
+ var require_git = __commonJS({
220
+ "src/git.js"(exports2, module2) {
221
+ "use strict";
222
+ var { spawnSync } = require("child_process");
223
+ var path2 = require("path");
224
+ var { DEFAULT_REPO_URL: DEFAULT_REPO_URL2, PRIVATE_REPO_ENV_VAR: PRIVATE_REPO_ENV_VAR2 } = require_constants();
225
+ var inheritedGitConfigCache = /* @__PURE__ */ new Map();
226
+ var GitCommandError = class extends Error {
227
+ constructor(message, details = {}) {
228
+ super(message);
229
+ this.name = "GitCommandError";
230
+ this.code = details.code || "";
231
+ this.stderr = details.stderr || "";
232
+ this.stdout = details.stdout || "";
233
+ this.args = details.args || [];
234
+ this.repoUrl = details.repoUrl || "";
235
+ this.isAuthError = Boolean(details.isAuthError);
236
+ this.isMissingGit = Boolean(details.isMissingGit);
237
+ }
238
+ };
239
+ function runGit(args, options = {}) {
240
+ const commandArgs = [...getInheritedGitConfigArgs(options.gitConfigContextCwd), ...args];
241
+ const result = spawnSync("git", commandArgs, {
242
+ cwd: options.cwd || void 0,
243
+ encoding: "utf8",
244
+ env: {
245
+ ...process.env,
246
+ GIT_TERMINAL_PROMPT: "0",
247
+ GCM_INTERACTIVE: "Never"
248
+ }
249
+ });
250
+ if (result.error) {
251
+ const isMissingGit = result.error.code === "ENOENT";
252
+ throw new GitCommandError(
253
+ isMissingGit ? "Git is not installed or not available in PATH." : result.error.message,
254
+ {
255
+ code: result.error.code,
256
+ args: commandArgs,
257
+ isMissingGit
258
+ }
259
+ );
260
+ }
261
+ if (result.status !== 0) {
262
+ const stderr = (result.stderr || "").trim();
263
+ const stdout = (result.stdout || "").trim();
264
+ throw new GitCommandError(stderr || stdout || "git command failed", {
265
+ code: String(result.status),
266
+ stderr,
267
+ stdout,
268
+ args: commandArgs,
269
+ repoUrl: options.repoUrl,
270
+ isAuthError: isGitAuthError(stderr || stdout)
271
+ });
272
+ }
273
+ return {
274
+ stdout: (result.stdout || "").trim(),
275
+ stderr: (result.stderr || "").trim()
276
+ };
277
+ }
278
+ function getInheritedGitConfigArgs(contextCwd) {
279
+ return getInheritedGitConfigEntries(contextCwd).flatMap(({ key, value }) => ["-c", `${key}=${value}`]);
280
+ }
281
+ function getInheritedGitConfigEntries(contextCwd) {
282
+ if (!contextCwd) {
283
+ return [];
284
+ }
285
+ const normalizedCwd = path2.resolve(contextCwd);
286
+ if (inheritedGitConfigCache.has(normalizedCwd)) {
287
+ return inheritedGitConfigCache.get(normalizedCwd);
288
+ }
289
+ const result = spawnSync(
290
+ "git",
291
+ ["-C", normalizedCwd, "config", "--get-regexp", "^(core\\.sshcommand|url\\..*\\.insteadof)$"],
292
+ {
293
+ encoding: "utf8",
294
+ env: {
295
+ ...process.env,
296
+ GIT_TERMINAL_PROMPT: "0",
297
+ GCM_INTERACTIVE: "Never"
298
+ }
299
+ }
300
+ );
301
+ if (result.error || result.status !== 0) {
302
+ inheritedGitConfigCache.set(normalizedCwd, []);
303
+ return [];
304
+ }
305
+ const entries = [];
306
+ const seen = /* @__PURE__ */ new Set();
307
+ String(result.stdout || "").split(/\r?\n/).filter(Boolean).forEach((line) => {
308
+ const separatorIndex = line.search(/\s/);
309
+ if (separatorIndex <= 0) {
310
+ return;
311
+ }
312
+ const key = line.slice(0, separatorIndex).trim();
313
+ const value = normalizeInheritedConfigValue(key, line.slice(separatorIndex).trim());
314
+ if (!key || !value) {
315
+ return;
316
+ }
317
+ const entryKey = `${key}\0${value}`;
318
+ if (seen.has(entryKey)) {
319
+ return;
320
+ }
321
+ seen.add(entryKey);
322
+ entries.push({ key, value });
323
+ });
324
+ inheritedGitConfigCache.set(normalizedCwd, entries);
325
+ return entries;
326
+ }
327
+ function syncInheritedGitConfig(repoCwd, contextCwd) {
328
+ const entries = getInheritedGitConfigEntries(contextCwd);
329
+ if (!repoCwd || entries.length === 0) {
330
+ return;
331
+ }
332
+ const normalizedRepoCwd = path2.resolve(repoCwd);
333
+ for (const { key, value } of entries) {
334
+ if (key.endsWith(".insteadof")) {
335
+ const existingValues = getLocalGitConfigValues(normalizedRepoCwd, key);
336
+ if (existingValues.includes(value)) {
337
+ continue;
338
+ }
339
+ setLocalGitConfigValue(normalizedRepoCwd, key, value, { append: true });
340
+ continue;
341
+ }
342
+ setLocalGitConfigValue(normalizedRepoCwd, key, value);
343
+ }
344
+ }
345
+ function isGitAuthError(message) {
346
+ const text = String(message || "").toLowerCase();
347
+ return [
348
+ "permission denied",
349
+ "authentication failed",
350
+ "could not read username",
351
+ "could not read from remote repository",
352
+ "publickey",
353
+ "repository not found",
354
+ "access denied",
355
+ "not authorized"
356
+ ].some((fragment) => text.includes(fragment));
357
+ }
358
+ function normalizeInheritedConfigValue(key, value) {
359
+ if (!value) {
360
+ return "";
361
+ }
362
+ if (key === "core.sshcommand" && /\s/.test(value)) {
363
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
364
+ return value;
365
+ }
366
+ return `"${value}"`;
367
+ }
368
+ return value;
369
+ }
370
+ function getLocalGitConfigValues(repoCwd, key) {
371
+ const result = spawnSync("git", ["-C", repoCwd, "config", "--local", "--get-all", key], {
372
+ encoding: "utf8",
373
+ env: {
374
+ ...process.env,
375
+ GIT_TERMINAL_PROMPT: "0",
376
+ GCM_INTERACTIVE: "Never"
377
+ }
378
+ });
379
+ if (result.error || result.status !== 0) {
380
+ return [];
381
+ }
382
+ return String(result.stdout || "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
383
+ }
384
+ function setLocalGitConfigValue(repoCwd, key, value, options = {}) {
385
+ const args = ["-C", repoCwd, "config", "--local"];
386
+ if (options.append) {
387
+ args.push("--add");
388
+ }
389
+ args.push(key, value);
390
+ const result = spawnSync("git", args, {
391
+ encoding: "utf8",
392
+ env: {
393
+ ...process.env,
394
+ GIT_TERMINAL_PROMPT: "0",
395
+ GCM_INTERACTIVE: "Never"
396
+ }
397
+ });
398
+ if (result.error) {
399
+ const isMissingGit = result.error.code === "ENOENT";
400
+ throw new GitCommandError(
401
+ isMissingGit ? "Git is not installed or not available in PATH." : result.error.message,
402
+ {
403
+ code: result.error.code,
404
+ args,
405
+ isMissingGit
406
+ }
407
+ );
408
+ }
409
+ if (result.status !== 0) {
410
+ const stderr = (result.stderr || "").trim();
411
+ const stdout = (result.stdout || "").trim();
412
+ throw new GitCommandError(stderr || stdout || "git config failed", {
413
+ code: String(result.status),
414
+ stderr,
415
+ stdout,
416
+ args
417
+ });
418
+ }
419
+ }
420
+ function formatGitAccessError2(repoUrl, error) {
421
+ const repoLabel = repoUrl && repoUrl !== DEFAULT_REPO_URL2 ? repoUrl : "the private repository";
422
+ if (!(error instanceof GitCommandError)) {
423
+ return `Failed to access ${repoLabel}: ${error.message || String(error)}`;
424
+ }
425
+ if (error.isMissingGit) {
426
+ return "Git is required for synskill. Install Git and make sure it is available in PATH.";
427
+ }
428
+ if (error.isAuthError) {
429
+ if (repoUrl && repoUrl !== DEFAULT_REPO_URL2) {
430
+ return [
431
+ `Failed to access ${repoLabel}.`,
432
+ "Git could not fetch the external skill source.",
433
+ `Verify the repository is reachable with: git ls-remote ${repoUrl}`,
434
+ "If the repository is private, configure the required Git credentials before retrying."
435
+ ].join("\n");
436
+ }
437
+ return [
438
+ `Failed to access ${repoLabel}.`,
439
+ "Git authentication did not succeed.",
440
+ `Use a repo URL you can access via --repo-url or ${PRIVATE_REPO_ENV_VAR2}.`,
441
+ "For SSH, verify your key with: ssh -T git@bitbucket.org",
442
+ "For HTTPS, configure a credential helper or app password before retrying."
443
+ ].join("\n");
444
+ }
445
+ return `Failed to access ${repoLabel}:
446
+ ${error.message}`;
447
+ }
448
+ module2.exports = {
449
+ GitCommandError,
450
+ formatGitAccessError: formatGitAccessError2,
451
+ getInheritedGitConfigArgs,
452
+ runGit,
453
+ syncInheritedGitConfig
454
+ };
455
+ }
456
+ });
457
+
458
+ // src/installer.js
459
+ var require_installer = __commonJS({
460
+ "src/installer.js"(exports2, module2) {
461
+ "use strict";
462
+ var fs = require("fs");
463
+ var path2 = require("path");
464
+ var { INSTALL_TARGETS: INSTALL_TARGETS2, userHome } = require_constants();
465
+ function buildInstallPlan2(options = {}, targetKeys = [], scope, mode = "symlink") {
466
+ if (options.dest) {
467
+ return {
468
+ mode: "copy",
469
+ scope: "dest",
470
+ targets: [
471
+ {
472
+ key: "dest",
473
+ baseDir: absPath(options.dest)
474
+ }
475
+ ]
476
+ };
477
+ }
478
+ const selectedTargets = targetKeys.length ? INSTALL_TARGETS2.filter((target) => targetKeys.includes(target.key)) : INSTALL_TARGETS2;
479
+ const resolvedScope = scope || (options.global ? "global" : options.projectPath !== void 0 ? "project" : "global");
480
+ const baseDir = resolvedScope === "global" ? userHome() : absPath(options.projectPath || process.cwd());
481
+ return {
482
+ mode,
483
+ scope: resolvedScope,
484
+ baseDir,
485
+ targets: selectedTargets.map((target) => ({
486
+ key: target.key,
487
+ baseDir: path2.join(baseDir, target.folder, "skills")
488
+ }))
489
+ };
490
+ }
491
+ function hasExistingSkills2(plan, skills) {
492
+ for (const skill of skills) {
493
+ for (const target of plan.targets) {
494
+ if (fs.existsSync(path2.join(target.baseDir, skill.name))) {
495
+ return true;
496
+ }
497
+ }
498
+ }
499
+ return false;
500
+ }
501
+ async function installSkills2(skills, plan, force) {
502
+ const results = [];
503
+ for (const skill of skills) {
504
+ const sourceDir = path2.resolve(skill.path);
505
+ const useSymlink = plan.mode === "symlink" && !skill.external;
506
+ for (const target of plan.targets) {
507
+ const targetDir = path2.join(target.baseDir, skill.name);
508
+ if (fs.existsSync(targetDir)) {
509
+ if (force) {
510
+ await fs.promises.rm(targetDir, { recursive: true, force: true });
511
+ } else {
512
+ results.push({
513
+ skill: skill.name,
514
+ path: targetDir,
515
+ skipped: true,
516
+ mode: plan.mode
517
+ });
518
+ continue;
519
+ }
520
+ }
521
+ if (!useSymlink) {
522
+ await copyDir(skill.path, targetDir);
523
+ results.push({
524
+ skill: skill.name,
525
+ path: targetDir,
526
+ skipped: false,
527
+ mode: "copy"
528
+ });
529
+ continue;
530
+ }
531
+ const symlinkCreated = await createSymlink(sourceDir, targetDir);
532
+ if (!symlinkCreated) {
533
+ await copyDir(skill.path, targetDir);
534
+ results.push({
535
+ skill: skill.name,
536
+ path: targetDir,
537
+ skipped: false,
538
+ mode: "copy",
539
+ symlinkFailed: true,
540
+ sourcePath: sourceDir
541
+ });
542
+ continue;
543
+ }
544
+ results.push({
545
+ skill: skill.name,
546
+ path: targetDir,
547
+ skipped: false,
548
+ mode: "symlink",
549
+ sourcePath: sourceDir
550
+ });
551
+ }
552
+ }
553
+ return results;
554
+ }
555
+ async function createSymlink(target, linkPath) {
556
+ try {
557
+ const linkDir = path2.dirname(linkPath);
558
+ await fs.promises.mkdir(linkDir, { recursive: true });
559
+ if (fs.existsSync(linkPath)) {
560
+ await fs.promises.rm(linkPath, { recursive: true, force: true });
561
+ }
562
+ const linkTarget = process.platform === "win32" ? path2.resolve(target) : path2.relative(linkDir, target);
563
+ const linkType = process.platform === "win32" ? "junction" : "dir";
564
+ await fs.promises.symlink(linkTarget, linkPath, linkType);
565
+ return true;
566
+ } catch {
567
+ return false;
568
+ }
569
+ }
570
+ async function copyDir(source, destination) {
571
+ if (fs.existsSync(destination)) {
572
+ await fs.promises.rm(destination, { recursive: true, force: true });
573
+ }
574
+ await fs.promises.mkdir(destination, { recursive: true });
575
+ const entries = await fs.promises.readdir(source, { withFileTypes: true });
576
+ for (const entry of entries) {
577
+ const sourcePath = path2.join(source, entry.name);
578
+ const destinationPath = path2.join(destination, entry.name);
579
+ if (entry.isDirectory()) {
580
+ await copyDir(sourcePath, destinationPath);
581
+ } else {
582
+ await fs.promises.copyFile(sourcePath, destinationPath);
583
+ }
584
+ }
585
+ }
586
+ function absPath(value) {
587
+ if (!value) {
588
+ throw new Error("Path is empty.");
589
+ }
590
+ return path2.isAbsolute(value) ? value : path2.resolve(process.cwd(), value);
591
+ }
592
+ module2.exports = {
593
+ absPath,
594
+ buildInstallPlan: buildInstallPlan2,
595
+ createSymlink,
596
+ copyDir,
597
+ hasExistingSkills: hasExistingSkills2,
598
+ installSkills: installSkills2
599
+ };
600
+ }
601
+ });
602
+
603
+ // src/external-skills.js
604
+ var require_external_skills = __commonJS({
605
+ "src/external-skills.js"(exports2, module2) {
606
+ "use strict";
607
+ var fs = require("fs");
608
+ var os = require("os");
609
+ var path2 = require("path");
610
+ var { runGit } = require_git();
611
+ var EXTERNAL_SKILLS = [
612
+ {
613
+ name: "skill-creator",
614
+ description: "Guided skill authoring flow from anthropics/skills. Use when creating a new skill or updating an existing skill that extends Codex with reusable workflows, scripts, references, or assets.",
615
+ shortDescription: "Create or update skills from the upstream Anthropic skills repo.",
616
+ repository: "anthropics/skills",
617
+ external: {
618
+ repoUrl: "https://github.com/anthropics/skills.git",
619
+ ref: "main",
620
+ path: "skills/skill-creator",
621
+ url: "https://github.com/anthropics/skills/tree/main/skills/skill-creator"
622
+ }
623
+ }
624
+ ];
625
+ function cloneExternalSkill(skill) {
626
+ return {
627
+ ...skill,
628
+ external: { ...skill.external },
629
+ path: ""
630
+ };
631
+ }
632
+ function getExternalSkills() {
633
+ return EXTERNAL_SKILLS.map(cloneExternalSkill);
634
+ }
635
+ function getExplicitExternalSkills2(names = []) {
636
+ const requested = new Set((names || []).map((name) => String(name).toLowerCase()));
637
+ return EXTERNAL_SKILLS.filter((skill) => requested.has(skill.name.toLowerCase())).map(cloneExternalSkill);
638
+ }
639
+ function isExplicitExternalSkillName(name) {
640
+ const normalized = String(name || "").toLowerCase();
641
+ return EXTERNAL_SKILLS.some((skill) => skill.name.toLowerCase() === normalized);
642
+ }
643
+ async function materializeSkillSources2(skills, deps = {}) {
644
+ const externalSkills = skills.filter((skill) => skill && skill.external);
645
+ if (!externalSkills.length) {
646
+ return {
647
+ skills,
648
+ cleanup: async () => {
649
+ }
650
+ };
651
+ }
652
+ const tempBaseDir = deps.tempBaseDir || fs.mkdtempSync(path2.join(deps.tempParentDir || os.tmpdir(), "synskill-external-"));
653
+ const groupedSkills = groupByRepo(externalSkills);
654
+ const preparedByName = /* @__PURE__ */ new Map();
655
+ try {
656
+ for (let index = 0; index < groupedSkills.length; index += 1) {
657
+ const group = groupedSkills[index];
658
+ const checkoutDir = path2.join(tempBaseDir, `repo-${index}`);
659
+ checkoutRepoGroup(group, checkoutDir, deps);
660
+ for (const skill of group.skills) {
661
+ const skillPath = path2.join(checkoutDir, ...skill.external.path.split("/"));
662
+ if (!fs.existsSync(path2.join(skillPath, "SKILL.md"))) {
663
+ throw new Error(
664
+ `External skill "${skill.name}" was not found at ${skill.external.url || skill.external.path}.`
665
+ );
666
+ }
667
+ preparedByName.set(skill.name.toLowerCase(), {
668
+ ...skill,
669
+ path: skillPath
670
+ });
671
+ }
672
+ }
673
+ return {
674
+ skills: skills.map((skill) => preparedByName.get(skill.name.toLowerCase()) || skill),
675
+ cleanup: async () => {
676
+ fs.rmSync(tempBaseDir, { recursive: true, force: true });
677
+ }
678
+ };
679
+ } catch (error) {
680
+ fs.rmSync(tempBaseDir, { recursive: true, force: true });
681
+ throw error;
682
+ }
683
+ }
684
+ function groupByRepo(skills) {
685
+ const groups = /* @__PURE__ */ new Map();
686
+ for (const skill of skills) {
687
+ const key = `${skill.external.repoUrl}#${skill.external.ref || "main"}`;
688
+ if (!groups.has(key)) {
689
+ groups.set(key, {
690
+ repoUrl: skill.external.repoUrl,
691
+ ref: skill.external.ref || "main",
692
+ paths: /* @__PURE__ */ new Set(),
693
+ skills: []
694
+ });
695
+ }
696
+ const group = groups.get(key);
697
+ group.paths.add(skill.external.path);
698
+ group.skills.push(skill);
699
+ }
700
+ return Array.from(groups.values());
701
+ }
702
+ function checkoutRepoGroup(group, checkoutDir, deps = {}) {
703
+ const runGitCommand = deps.runGitCommand || runGit;
704
+ const repoUrl = group.repoUrl;
705
+ const ref = group.ref || "main";
706
+ const sparsePaths = Array.from(group.paths);
707
+ fs.rmSync(checkoutDir, { recursive: true, force: true });
708
+ fs.mkdirSync(path2.dirname(checkoutDir), { recursive: true });
709
+ try {
710
+ runGitCommand(
711
+ ["clone", "--depth", "1", "--filter=blob:none", "--sparse", "--branch", ref, repoUrl, checkoutDir],
712
+ { repoUrl }
713
+ );
714
+ runGitCommand(["sparse-checkout", "set", "--cone", ...sparsePaths], {
715
+ cwd: checkoutDir,
716
+ repoUrl
717
+ });
718
+ return;
719
+ } catch {
720
+ fs.rmSync(checkoutDir, { recursive: true, force: true });
721
+ }
722
+ runGitCommand(["clone", "--depth", "1", "--branch", ref, repoUrl, checkoutDir], { repoUrl });
723
+ }
724
+ module2.exports = {
725
+ EXTERNAL_SKILLS,
726
+ getExternalSkills,
727
+ getExplicitExternalSkills: getExplicitExternalSkills2,
728
+ isExplicitExternalSkillName,
729
+ materializeSkillSources: materializeSkillSources2
730
+ };
731
+ }
732
+ });
733
+
734
+ // src/skill-catalog.js
735
+ var require_skill_catalog = __commonJS({
736
+ "src/skill-catalog.js"(exports2, module2) {
737
+ "use strict";
738
+ var fs = require("fs");
739
+ var path2 = require("path");
740
+ var { isExplicitExternalSkillName } = require_external_skills();
741
+ function findSkills2(repoRoot) {
742
+ const fromJson = loadSkillsJson(repoRoot);
743
+ if (fromJson.length) {
744
+ return fromJson;
745
+ }
746
+ const skillsRoot = path2.join(repoRoot, "skills");
747
+ const base = dirExists(skillsRoot) ? skillsRoot : repoRoot;
748
+ const entries = fs.readdirSync(base, { withFileTypes: true });
749
+ const skills = entries.filter((entry) => entry.isDirectory() && fs.existsSync(path2.join(base, entry.name, "SKILL.md"))).filter((entry) => !isExplicitExternalSkillName(entry.name)).map((entry) => {
750
+ const skillPath = path2.join(base, entry.name);
751
+ const skillFile = path2.join(skillPath, "SKILL.md");
752
+ const { description } = readFrontmatter(skillFile);
753
+ return {
754
+ name: entry.name,
755
+ path: skillPath,
756
+ description,
757
+ shortDescription: buildShortDescription(description),
758
+ repository: "any"
759
+ };
760
+ }).sort((a, b) => a.name.localeCompare(b.name));
761
+ if (!skills.length) {
762
+ throw new Error("No skills found in the private skills repository.");
763
+ }
764
+ return skills;
765
+ }
766
+ function loadSkillsJson(repoRoot) {
767
+ const skillsFile = path2.join(repoRoot, "skills.json");
768
+ if (!fs.existsSync(skillsFile)) {
769
+ return [];
770
+ }
771
+ try {
772
+ const raw = fs.readFileSync(skillsFile, "utf8");
773
+ const data = JSON.parse(raw);
774
+ if (!Array.isArray(data)) {
775
+ return [];
776
+ }
777
+ return data.filter((skill) => skill && typeof skill.name === "string").map((skill) => ({
778
+ name: skill.name,
779
+ description: skill.description || "",
780
+ shortDescription: skill.shortDescription || buildShortDescription(skill.description),
781
+ repository: skill.repository || "any",
782
+ path: skill.path ? path2.join(repoRoot, skill.path) : path2.join(repoRoot, "skills", skill.name)
783
+ })).filter((skill) => !isExplicitExternalSkillName(skill.name)).filter((skill) => fs.existsSync(skill.path)).sort((a, b) => a.name.localeCompare(b.name));
784
+ } catch {
785
+ return [];
786
+ }
787
+ }
788
+ function applyListFilter2(skills, options = {}) {
789
+ const query = String(options.filter || "").trim().toLowerCase();
790
+ if (!query) {
791
+ return skills;
792
+ }
793
+ return skills.filter(
794
+ (skill) => [skill.name, skill.description, skill.shortDescription, skill.repository].filter(Boolean).some((field) => String(field).toLowerCase().includes(query))
795
+ );
796
+ }
797
+ function splitRepositoryField(repository) {
798
+ const values = Array.isArray(repository) ? repository : String(repository || "any").split(",");
799
+ const seen = /* @__PURE__ */ new Set();
800
+ const result = [];
801
+ values.forEach((value) => {
802
+ const normalized = String(value || "").trim();
803
+ if (!normalized) {
804
+ return;
805
+ }
806
+ const key = normalized.toLowerCase();
807
+ if (seen.has(key)) {
808
+ return;
809
+ }
810
+ seen.add(key);
811
+ result.push(normalized);
812
+ });
813
+ return result.length ? result : ["any"];
814
+ }
815
+ function filterSkillsByName2(skills, names) {
816
+ const nameSet = new Set((names || []).map((name) => String(name).toLowerCase()));
817
+ const selected = skills.filter((skill) => nameSet.has(skill.name.toLowerCase()));
818
+ if (!selected.length) {
819
+ throw new Error(`No matching skills selected: ${(names || []).join(", ")}`);
820
+ }
821
+ return selected;
822
+ }
823
+ function buildShortDescription(description) {
824
+ if (!description) {
825
+ return "";
826
+ }
827
+ let base = String(description).replace(/\s+/g, " ").trim();
828
+ const cutTokens = [" Use when ", " Use after ", " Use for ", " Use if ", " Use to "];
829
+ for (const token of cutTokens) {
830
+ const index = base.indexOf(token);
831
+ if (index > 0) {
832
+ base = base.slice(0, index).trim();
833
+ break;
834
+ }
835
+ }
836
+ base = base.replace(/\s*\([^)]*\)\s*/g, " ").replace(/\s+/g, " ").trim();
837
+ const trimTokens = [" including ", " such as ", " e.g. ", " e.g., ", " for example "];
838
+ for (const token of trimTokens) {
839
+ const index = base.toLowerCase().indexOf(token.trim());
840
+ if (index > 0) {
841
+ base = base.slice(0, index).trim();
842
+ break;
843
+ }
844
+ }
845
+ if (base.includes(". ")) {
846
+ base = base.split(". ")[0].trim();
847
+ }
848
+ if (base.length > 90) {
849
+ const commaCut = base.split(",")[0].trim();
850
+ if (commaCut.length >= 40) {
851
+ base = commaCut;
852
+ }
853
+ }
854
+ if (base.length > 90) {
855
+ const andCut = base.split(" and ")[0].trim();
856
+ if (andCut.length >= 40) {
857
+ base = andCut;
858
+ }
859
+ }
860
+ if (base.length > 90) {
861
+ base = `${base.slice(0, 87).trimEnd()}...`;
862
+ }
863
+ return base;
864
+ }
865
+ function readFrontmatter(filePath) {
866
+ if (!fs.existsSync(filePath)) {
867
+ return { description: "" };
868
+ }
869
+ const content = fs.readFileSync(filePath, "utf8");
870
+ const lines = content.split(/\r?\n/);
871
+ if (!lines.length || lines[0].trim() !== "---") {
872
+ return { description: "" };
873
+ }
874
+ const data = {};
875
+ for (let index = 1; index < lines.length; index += 1) {
876
+ const line = lines[index];
877
+ if (line.trim() === "---") {
878
+ break;
879
+ }
880
+ if (/^\s/.test(line)) {
881
+ continue;
882
+ }
883
+ const separatorIndex = line.indexOf(":");
884
+ if (separatorIndex === -1) {
885
+ continue;
886
+ }
887
+ const key = line.slice(0, separatorIndex).trim();
888
+ let value = line.slice(separatorIndex + 1).trim();
889
+ if (!value) {
890
+ continue;
891
+ }
892
+ value = unquote(value);
893
+ data[key] = value;
894
+ }
895
+ return { description: data.description || "" };
896
+ }
897
+ function unquote(value) {
898
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
899
+ return value.slice(1, -1);
900
+ }
901
+ return value;
902
+ }
903
+ function dirExists(targetPath) {
904
+ try {
905
+ return fs.statSync(targetPath).isDirectory();
906
+ } catch {
907
+ return false;
908
+ }
909
+ }
910
+ module2.exports = {
911
+ applyListFilter: applyListFilter2,
912
+ buildShortDescription,
913
+ filterSkillsByName: filterSkillsByName2,
914
+ findSkills: findSkills2,
915
+ loadSkillsJson,
916
+ splitRepositoryField
917
+ };
918
+ }
919
+ });
920
+
921
+ // node_modules/picocolors/picocolors.js
922
+ var require_picocolors = __commonJS({
923
+ "node_modules/picocolors/picocolors.js"(exports2, module2) {
924
+ var p = process || {};
925
+ var argv = p.argv || [];
926
+ var env = p.env || {};
927
+ var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
928
+ var formatter = (open, close, replace = open) => (input) => {
929
+ let string = "" + input, index = string.indexOf(close, open.length);
930
+ return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
931
+ };
932
+ var replaceClose = (string, close, replace, index) => {
933
+ let result = "", cursor = 0;
934
+ do {
935
+ result += string.substring(cursor, index) + replace;
936
+ cursor = index + close.length;
937
+ index = string.indexOf(close, cursor);
938
+ } while (~index);
939
+ return result + string.substring(cursor);
940
+ };
941
+ var createColors = (enabled = isColorSupported) => {
942
+ let f = enabled ? formatter : () => String;
943
+ return {
944
+ isColorSupported: enabled,
945
+ reset: f("\x1B[0m", "\x1B[0m"),
946
+ bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
947
+ dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
948
+ italic: f("\x1B[3m", "\x1B[23m"),
949
+ underline: f("\x1B[4m", "\x1B[24m"),
950
+ inverse: f("\x1B[7m", "\x1B[27m"),
951
+ hidden: f("\x1B[8m", "\x1B[28m"),
952
+ strikethrough: f("\x1B[9m", "\x1B[29m"),
953
+ black: f("\x1B[30m", "\x1B[39m"),
954
+ red: f("\x1B[31m", "\x1B[39m"),
955
+ green: f("\x1B[32m", "\x1B[39m"),
956
+ yellow: f("\x1B[33m", "\x1B[39m"),
957
+ blue: f("\x1B[34m", "\x1B[39m"),
958
+ magenta: f("\x1B[35m", "\x1B[39m"),
959
+ cyan: f("\x1B[36m", "\x1B[39m"),
960
+ white: f("\x1B[37m", "\x1B[39m"),
961
+ gray: f("\x1B[90m", "\x1B[39m"),
962
+ bgBlack: f("\x1B[40m", "\x1B[49m"),
963
+ bgRed: f("\x1B[41m", "\x1B[49m"),
964
+ bgGreen: f("\x1B[42m", "\x1B[49m"),
965
+ bgYellow: f("\x1B[43m", "\x1B[49m"),
966
+ bgBlue: f("\x1B[44m", "\x1B[49m"),
967
+ bgMagenta: f("\x1B[45m", "\x1B[49m"),
968
+ bgCyan: f("\x1B[46m", "\x1B[49m"),
969
+ bgWhite: f("\x1B[47m", "\x1B[49m"),
970
+ blackBright: f("\x1B[90m", "\x1B[39m"),
971
+ redBright: f("\x1B[91m", "\x1B[39m"),
972
+ greenBright: f("\x1B[92m", "\x1B[39m"),
973
+ yellowBright: f("\x1B[93m", "\x1B[39m"),
974
+ blueBright: f("\x1B[94m", "\x1B[39m"),
975
+ magentaBright: f("\x1B[95m", "\x1B[39m"),
976
+ cyanBright: f("\x1B[96m", "\x1B[39m"),
977
+ whiteBright: f("\x1B[97m", "\x1B[39m"),
978
+ bgBlackBright: f("\x1B[100m", "\x1B[49m"),
979
+ bgRedBright: f("\x1B[101m", "\x1B[49m"),
980
+ bgGreenBright: f("\x1B[102m", "\x1B[49m"),
981
+ bgYellowBright: f("\x1B[103m", "\x1B[49m"),
982
+ bgBlueBright: f("\x1B[104m", "\x1B[49m"),
983
+ bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
984
+ bgCyanBright: f("\x1B[106m", "\x1B[49m"),
985
+ bgWhiteBright: f("\x1B[107m", "\x1B[49m")
986
+ };
987
+ };
988
+ module2.exports = createColors();
989
+ module2.exports.createColors = createColors;
990
+ }
991
+ });
992
+
993
+ // src/theme.js
994
+ var require_theme = __commonJS({
995
+ "src/theme.js"(exports2, module2) {
996
+ "use strict";
997
+ var pc = require_picocolors();
998
+ function colorDepth() {
999
+ if (!pc.isColorSupported) {
1000
+ return 1;
1001
+ }
1002
+ if (process.stdout && typeof process.stdout.getColorDepth === "function") {
1003
+ try {
1004
+ return process.stdout.getColorDepth();
1005
+ } catch {
1006
+ return 4;
1007
+ }
1008
+ }
1009
+ return 4;
1010
+ }
1011
+ function applyAnsi(text, open, close) {
1012
+ return pc.isColorSupported ? `${open}${text}${close}` : text;
1013
+ }
1014
+ function colorize(text, rgb, ansi256, fallback) {
1015
+ const value = String(text);
1016
+ const depth = colorDepth();
1017
+ if (depth >= 24) {
1018
+ return applyAnsi(value, `\x1B[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m`, "\x1B[39m");
1019
+ }
1020
+ if (depth >= 8) {
1021
+ return applyAnsi(value, `\x1B[38;5;${ansi256}m`, "\x1B[39m");
1022
+ }
1023
+ return fallback(value);
1024
+ }
1025
+ function accent2(text) {
1026
+ return colorize(text, [217, 95, 43], 166, pc.yellow);
1027
+ }
1028
+ function warm2(text) {
1029
+ return colorize(text, [246, 211, 196], 224, pc.yellowBright);
1030
+ }
1031
+ function muted2(text) {
1032
+ return colorize(text, [108, 100, 92], 241, (value) => pc.dim(value));
1033
+ }
1034
+ function strong2(text) {
1035
+ return pc.bold(pc.white(String(text)));
1036
+ }
1037
+ function success2(text) {
1038
+ return accent2(text);
1039
+ }
1040
+ function danger2(text) {
1041
+ return pc.red(String(text));
1042
+ }
1043
+ function info(text) {
1044
+ return muted2(text);
1045
+ }
1046
+ function highlightMatch(text, query, baseColor = (value) => value) {
1047
+ const value = String(text || "");
1048
+ const needle = String(query || "").trim().toLowerCase();
1049
+ if (!needle) {
1050
+ return baseColor(value);
1051
+ }
1052
+ const lower = value.toLowerCase();
1053
+ const parts = [];
1054
+ let start = 0;
1055
+ let matchIndex = lower.indexOf(needle);
1056
+ while (matchIndex !== -1) {
1057
+ if (matchIndex > start) {
1058
+ parts.push(baseColor(value.slice(start, matchIndex)));
1059
+ }
1060
+ parts.push(accent2(pc.bold(value.slice(matchIndex, matchIndex + needle.length))));
1061
+ start = matchIndex + needle.length;
1062
+ matchIndex = lower.indexOf(needle, start);
1063
+ }
1064
+ if (start < value.length) {
1065
+ parts.push(baseColor(value.slice(start)));
1066
+ }
1067
+ return parts.join("");
1068
+ }
1069
+ module2.exports = {
1070
+ accent: accent2,
1071
+ danger: danger2,
1072
+ highlightMatch,
1073
+ info,
1074
+ muted: muted2,
1075
+ strong: strong2,
1076
+ success: success2,
1077
+ warm: warm2
1078
+ };
1079
+ }
1080
+ });
1081
+
1082
+ // src/search-multiselect.js
1083
+ var require_search_multiselect = __commonJS({
1084
+ "src/search-multiselect.js"(exports2, module2) {
1085
+ "use strict";
1086
+ var readline = require("readline");
1087
+ var { Writable } = require("stream");
1088
+ var { accent: accent2, danger: danger2, highlightMatch, muted: muted2, strong: strong2, warm: warm2 } = require_theme();
1089
+ var RAIL = muted2("\u2502");
1090
+ var RAIL_END = muted2("\u2514");
1091
+ var HEADER_MARKER = muted2("\u2022");
1092
+ var ACTIVE_CURSOR = accent2("\u203A");
1093
+ var SELECTED_DOT = accent2("\u25CF");
1094
+ var UNSELECTED_DOT = muted2("\u25CB");
1095
+ var silentOutput = new Writable({
1096
+ write(_chunk, _encoding, callback) {
1097
+ callback();
1098
+ }
1099
+ });
1100
+ var cancelSymbol2 = Symbol("cancel");
1101
+ var ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
1102
+ async function searchMultiselect(options) {
1103
+ return runListPrompt({
1104
+ ...options,
1105
+ searchable: true,
1106
+ multiple: true
1107
+ });
1108
+ }
1109
+ async function multiselectPrompt(options) {
1110
+ return runListPrompt({
1111
+ ...options,
1112
+ multiple: true
1113
+ });
1114
+ }
1115
+ async function selectPrompt(options) {
1116
+ return runListPrompt(options);
1117
+ }
1118
+ async function confirmPrompt(options) {
1119
+ return selectPrompt({
1120
+ message: options.message,
1121
+ items: [
1122
+ { value: true, label: options.active || "Yes" },
1123
+ { value: false, label: options.inactive || "No" }
1124
+ ],
1125
+ initialValue: options.initialValue ? true : false,
1126
+ instructions: "Use arrows to choose, then press enter to confirm.",
1127
+ emptyText: "No choices available."
1128
+ });
1129
+ }
1130
+ function summarizeLabels(labels) {
1131
+ if (!labels.length) {
1132
+ return muted2("(none)");
1133
+ }
1134
+ if (labels.length <= 3) {
1135
+ return labels.join(", ");
1136
+ }
1137
+ return `${labels.slice(0, 3).join(", ")} +${labels.length - 3} more`;
1138
+ }
1139
+ async function runListPrompt(options) {
1140
+ const {
1141
+ message,
1142
+ items,
1143
+ maxVisible = 8,
1144
+ required = false,
1145
+ searchable = false,
1146
+ multiple = false,
1147
+ initialValues = [],
1148
+ initialValue,
1149
+ instructions = "",
1150
+ emptyText = "No options available."
1151
+ } = options;
1152
+ if (!process.stdin.isTTY || !process.stdout.isTTY || typeof process.stdin.setRawMode !== "function") {
1153
+ throw new Error("Interactive selection requires a TTY terminal.");
1154
+ }
1155
+ return new Promise((resolve) => {
1156
+ const rl = readline.createInterface({
1157
+ input: process.stdin,
1158
+ output: silentOutput,
1159
+ terminal: false
1160
+ });
1161
+ process.stdin.setRawMode(true);
1162
+ readline.emitKeypressEvents(process.stdin, rl);
1163
+ let query = "";
1164
+ let cursor = 0;
1165
+ let lastHeight = 0;
1166
+ let errorMessage = "";
1167
+ const selected = new Set(multiple ? initialValues : []);
1168
+ const defaultIndex = initialValue === void 0 ? 0 : Math.max(
1169
+ items.findIndex((item) => item.value === initialValue),
1170
+ 0
1171
+ );
1172
+ cursor = defaultIndex;
1173
+ const filterItems = () => {
1174
+ if (!searchable) {
1175
+ return items;
1176
+ }
1177
+ const normalized = query.toLowerCase();
1178
+ if (!normalized) {
1179
+ return items;
1180
+ }
1181
+ return items.filter(
1182
+ (item) => [item.label, item.hint].filter(Boolean).some((field) => String(field).toLowerCase().includes(normalized))
1183
+ );
1184
+ };
1185
+ const syncCursor = () => {
1186
+ const filtered = filterItems();
1187
+ if (!filtered.length) {
1188
+ cursor = 0;
1189
+ return filtered;
1190
+ }
1191
+ cursor = Math.max(0, Math.min(cursor, filtered.length - 1));
1192
+ return filtered;
1193
+ };
1194
+ const clear = () => {
1195
+ if (!lastHeight) {
1196
+ return;
1197
+ }
1198
+ process.stdout.write(`\x1B[${lastHeight}A`);
1199
+ for (let index = 0; index < lastHeight; index += 1) {
1200
+ process.stdout.write("\x1B[2K\x1B[1B");
1201
+ }
1202
+ process.stdout.write(`\x1B[${lastHeight}A`);
1203
+ };
1204
+ const measureRenderedHeight = (lines) => {
1205
+ const columns = Math.max(process.stdout.columns || 80, 1);
1206
+ return lines.reduce((total, line) => {
1207
+ const visible = String(line || "").replace(ANSI_PATTERN, "");
1208
+ return total + Math.max(1, Math.ceil(visible.length / columns));
1209
+ }, 0);
1210
+ };
1211
+ const renderInstructions = () => {
1212
+ if (instructions) {
1213
+ return muted2(instructions);
1214
+ }
1215
+ const parts = [];
1216
+ if (searchable) {
1217
+ parts.push("type to filter");
1218
+ }
1219
+ parts.push("arrows to move");
1220
+ if (multiple) {
1221
+ parts.push("space to toggle");
1222
+ parts.push("a all");
1223
+ }
1224
+ parts.push(multiple ? "enter confirm" : "enter select");
1225
+ parts.push("esc cancel");
1226
+ return muted2(parts.join(" | "));
1227
+ };
1228
+ const withRail = (content = "") => content ? `${RAIL} ${content}` : RAIL;
1229
+ const withBranch = (content = "") => content ? `${RAIL_END} ${content}` : RAIL_END;
1230
+ const renderBadge = (badge, activeQuery) => {
1231
+ const label = highlightMatch(badge, activeQuery, warm2);
1232
+ return `${accent2("[")}${label}${accent2("]")}`;
1233
+ };
1234
+ const renderRow = (item, isActive, isSelected, activeQuery) => {
1235
+ const label = highlightMatch(
1236
+ item.label,
1237
+ activeQuery,
1238
+ isActive ? (value) => strong2(value) : (value) => muted2(value)
1239
+ );
1240
+ const cursorDot = isActive ? ACTIVE_CURSOR : " ";
1241
+ const selectDot = multiple ? isSelected ? SELECTED_DOT : UNSELECTED_DOT : isActive ? SELECTED_DOT : UNSELECTED_DOT;
1242
+ return withRail(`${cursorDot} ${selectDot} ${label}`);
1243
+ };
1244
+ const renderDetails = (item, isActive, activeQuery) => {
1245
+ const badges = Array.isArray(item.badges) ? item.badges : [];
1246
+ if (!item.hint && !badges.length) {
1247
+ return [];
1248
+ }
1249
+ const lines = [];
1250
+ if (badges.length) {
1251
+ lines.push(withRail(` ${badges.map((badge) => renderBadge(badge, activeQuery)).join(" ")}`));
1252
+ }
1253
+ if (item.hint) {
1254
+ lines.push(
1255
+ withRail(` ${highlightMatch(item.hint, activeQuery, isActive ? warm2 : muted2)}`)
1256
+ );
1257
+ }
1258
+ return lines;
1259
+ };
1260
+ const renderSummary = () => {
1261
+ if (!multiple) {
1262
+ const filtered = filterItems();
1263
+ const current = filtered[cursor];
1264
+ if (!current) {
1265
+ return withBranch(muted2("(none)"));
1266
+ }
1267
+ return withBranch(`${warm2("Current:")} ${strong2(current.label)}`);
1268
+ }
1269
+ const summary = summarizeLabels(
1270
+ items.filter((item) => selected.has(item.value)).map((item) => item.label)
1271
+ );
1272
+ return withBranch(`${warm2("Selected:")} ${summary}`);
1273
+ };
1274
+ const render = (mode = "active") => {
1275
+ clear();
1276
+ const filtered = syncCursor();
1277
+ const lines = [];
1278
+ lines.push(`${HEADER_MARKER} ${strong2(message)}`);
1279
+ if (mode === "submit") {
1280
+ const submitted = multiple ? summarizeLabels(items.filter((item) => selected.has(item.value)).map((item) => item.label)) : filtered[cursor] ? filtered[cursor].label : "";
1281
+ lines.push(withBranch(warm2(submitted || "(none)")));
1282
+ } else if (mode === "cancel") {
1283
+ lines.push(withBranch(danger2("Cancelled")));
1284
+ } else {
1285
+ lines.push(withRail(renderInstructions()));
1286
+ if (searchable) {
1287
+ lines.push(withRail(`${muted2("Filter:")} ${query ? warm2(query) : muted2("(none)")}`));
1288
+ }
1289
+ if (errorMessage) {
1290
+ lines.push(withRail(danger2(errorMessage)));
1291
+ }
1292
+ lines.push(RAIL);
1293
+ if (!filtered.length) {
1294
+ lines.push(withRail(muted2(emptyText)));
1295
+ } else {
1296
+ const visibleCount = Math.min(maxVisible, filtered.length);
1297
+ const start = Math.max(
1298
+ 0,
1299
+ Math.min(cursor - Math.floor(visibleCount / 2), filtered.length - visibleCount)
1300
+ );
1301
+ const visible = filtered.slice(start, start + visibleCount);
1302
+ visible.forEach((item, index) => {
1303
+ const actualIndex = start + index;
1304
+ const isActive = actualIndex === cursor;
1305
+ const isSelected = multiple && selected.has(item.value);
1306
+ lines.push(renderRow(item, isActive, isSelected, query));
1307
+ const detailLines = renderDetails(item, isActive, query);
1308
+ if (detailLines.length && isActive) {
1309
+ lines.push(...detailLines);
1310
+ }
1311
+ });
1312
+ if (filtered.length > visibleCount) {
1313
+ lines.push(RAIL);
1314
+ lines.push(
1315
+ withRail(muted2(`Showing ${start + 1}-${start + visible.length} of ${filtered.length}`))
1316
+ );
1317
+ }
1318
+ }
1319
+ lines.push(renderSummary());
1320
+ }
1321
+ process.stdout.write(`${lines.join("\n")}
1322
+ `);
1323
+ lastHeight = measureRenderedHeight(lines);
1324
+ };
1325
+ const cleanup = () => {
1326
+ process.stdin.removeListener("keypress", onKeypress);
1327
+ process.stdin.setRawMode(false);
1328
+ rl.close();
1329
+ };
1330
+ const submit = () => {
1331
+ const filtered = syncCursor();
1332
+ if (multiple) {
1333
+ if (required && selected.size === 0) {
1334
+ errorMessage = "Select at least one option.";
1335
+ render();
1336
+ return;
1337
+ }
1338
+ render("submit");
1339
+ cleanup();
1340
+ resolve(items.filter((item) => selected.has(item.value)).map((item) => item.value));
1341
+ return;
1342
+ }
1343
+ if (required && !filtered[cursor]) {
1344
+ errorMessage = "Select an option before continuing.";
1345
+ render();
1346
+ return;
1347
+ }
1348
+ render("submit");
1349
+ cleanup();
1350
+ resolve(filtered[cursor] ? filtered[cursor].value : cancelSymbol2);
1351
+ };
1352
+ const cancel2 = () => {
1353
+ render("cancel");
1354
+ cleanup();
1355
+ resolve(cancelSymbol2);
1356
+ };
1357
+ const onKeypress = (input, key = {}) => {
1358
+ const filtered = syncCursor();
1359
+ if (key.ctrl && key.name === "c") {
1360
+ cancel2();
1361
+ return;
1362
+ }
1363
+ if (key.name === "return") {
1364
+ submit();
1365
+ return;
1366
+ }
1367
+ if (key.name === "escape") {
1368
+ if (searchable && query) {
1369
+ query = "";
1370
+ errorMessage = "";
1371
+ render();
1372
+ return;
1373
+ }
1374
+ cancel2();
1375
+ return;
1376
+ }
1377
+ if (key.name === "up" || key.name === "left") {
1378
+ errorMessage = "";
1379
+ cursor = Math.max(0, cursor - 1);
1380
+ render();
1381
+ return;
1382
+ }
1383
+ if (key.name === "down" || key.name === "right") {
1384
+ errorMessage = "";
1385
+ cursor = Math.min(Math.max(filtered.length - 1, 0), cursor + 1);
1386
+ render();
1387
+ return;
1388
+ }
1389
+ if (multiple && key.name === "space") {
1390
+ const item = filtered[cursor];
1391
+ if (item) {
1392
+ if (selected.has(item.value)) {
1393
+ selected.delete(item.value);
1394
+ } else {
1395
+ selected.add(item.value);
1396
+ }
1397
+ errorMessage = "";
1398
+ }
1399
+ render();
1400
+ return;
1401
+ }
1402
+ if (multiple && input === "a") {
1403
+ const allSelected = items.length > 0 && items.every((item) => selected.has(item.value));
1404
+ selected.clear();
1405
+ if (!allSelected) {
1406
+ items.forEach((item) => selected.add(item.value));
1407
+ }
1408
+ errorMessage = "";
1409
+ render();
1410
+ return;
1411
+ }
1412
+ if (searchable && key.name === "backspace") {
1413
+ query = query.slice(0, -1);
1414
+ cursor = 0;
1415
+ errorMessage = "";
1416
+ render();
1417
+ return;
1418
+ }
1419
+ if (searchable && input && input.length === 1 && !key.ctrl && !key.meta) {
1420
+ query += input;
1421
+ cursor = 0;
1422
+ errorMessage = "";
1423
+ render();
1424
+ }
1425
+ };
1426
+ process.stdin.on("keypress", onKeypress);
1427
+ render();
1428
+ });
1429
+ }
1430
+ module2.exports = {
1431
+ cancelSymbol: cancelSymbol2,
1432
+ confirmPrompt,
1433
+ multiselectPrompt,
1434
+ searchMultiselect,
1435
+ selectPrompt
1436
+ };
1437
+ }
1438
+ });
1439
+
1440
+ // src/prompts.js
1441
+ var require_prompts = __commonJS({
1442
+ "src/prompts.js"(exports2, module2) {
1443
+ "use strict";
1444
+ var { INSTALL_TARGETS: INSTALL_TARGETS2 } = require_constants();
1445
+ var { splitRepositoryField } = require_skill_catalog();
1446
+ var {
1447
+ cancelSymbol: cancelSymbol2,
1448
+ confirmPrompt,
1449
+ multiselectPrompt,
1450
+ searchMultiselect,
1451
+ selectPrompt
1452
+ } = require_search_multiselect();
1453
+ var { accent: accent2, danger: danger2, muted: muted2, strong: strong2, success: success2, warm: warm2 } = require_theme();
1454
+ var STATUS_MARKER = muted2("\u2022");
1455
+ async function promptForSkills2(skills, options = {}) {
1456
+ const items = skills.map((skill) => ({
1457
+ value: skill,
1458
+ label: skill.name,
1459
+ hint: options.details ? skill.description : skill.shortDescription || skill.description,
1460
+ badges: splitRepositoryField(skill.repository)
1461
+ }));
1462
+ return searchMultiselect({
1463
+ message: "Select skills to install",
1464
+ items,
1465
+ required: true,
1466
+ emptyText: "No skills match the current filter."
1467
+ });
1468
+ }
1469
+ async function promptForTargets2() {
1470
+ return multiselectPrompt({
1471
+ message: "Choose install targets",
1472
+ items: INSTALL_TARGETS2.map((target) => ({
1473
+ value: target.key,
1474
+ label: target.name,
1475
+ hint: `${target.folder}/skills`
1476
+ })),
1477
+ initialValues: INSTALL_TARGETS2.filter((target) => target.key !== "claude").map((target) => target.key),
1478
+ required: true,
1479
+ instructions: "Use arrows to move, space to toggle, a for all, then press enter to confirm."
1480
+ });
1481
+ }
1482
+ async function promptForScope2() {
1483
+ return selectPrompt({
1484
+ message: "Install location",
1485
+ items: [
1486
+ {
1487
+ value: "global",
1488
+ label: "Global",
1489
+ hint: "Available everywhere on this machine"
1490
+ },
1491
+ {
1492
+ value: "project",
1493
+ label: "Project",
1494
+ hint: "Available only in the current project folder"
1495
+ }
1496
+ ],
1497
+ required: true
1498
+ });
1499
+ }
1500
+ async function promptForInstallMode2() {
1501
+ return selectPrompt({
1502
+ message: "Installation method",
1503
+ items: [
1504
+ {
1505
+ value: "symlink",
1506
+ label: "Symlink",
1507
+ hint: "Link each selected target directly to the source skill directory"
1508
+ },
1509
+ {
1510
+ value: "copy",
1511
+ label: "Copy",
1512
+ hint: "Copy files directly into each selected target"
1513
+ }
1514
+ ],
1515
+ required: true
1516
+ });
1517
+ }
1518
+ async function confirmOverwrite2() {
1519
+ return confirmPrompt({
1520
+ message: "Overwrite existing installed skills?",
1521
+ active: "Overwrite",
1522
+ inactive: "Skip existing",
1523
+ initialValue: false
1524
+ });
1525
+ }
1526
+ async function createSpinner2() {
1527
+ return createInlineSpinner();
1528
+ }
1529
+ async function intro2() {
1530
+ console.log(`${STATUS_MARKER} ${muted2("synskill installer")}`);
1531
+ }
1532
+ async function outro2(message) {
1533
+ console.log(`${STATUS_MARKER} ${success2(message)}`);
1534
+ }
1535
+ function createInlineSpinner() {
1536
+ const frames = ["-", "\\", "|", "/"];
1537
+ let timer = null;
1538
+ let frameIndex = 0;
1539
+ let activeMessage = "";
1540
+ let visible = false;
1541
+ const render = (message) => {
1542
+ const frame = muted2(frames[frameIndex % frames.length]);
1543
+ process.stdout.write(`\r\x1B[2K${frame} ${strong2(message)}`);
1544
+ };
1545
+ const clear = () => {
1546
+ if (!visible) {
1547
+ return;
1548
+ }
1549
+ process.stdout.write("\r\x1B[2K");
1550
+ visible = false;
1551
+ };
1552
+ const stopTimer = () => {
1553
+ if (timer) {
1554
+ clearInterval(timer);
1555
+ timer = null;
1556
+ }
1557
+ };
1558
+ return {
1559
+ start(message = "") {
1560
+ stopTimer();
1561
+ activeMessage = message;
1562
+ frameIndex = 0;
1563
+ visible = true;
1564
+ render(activeMessage);
1565
+ timer = setInterval(() => {
1566
+ frameIndex += 1;
1567
+ render(activeMessage);
1568
+ }, 80);
1569
+ },
1570
+ stop(message = activeMessage, state = "success") {
1571
+ stopTimer();
1572
+ clear();
1573
+ const painter = state === "error" ? danger2 : state === "warning" ? warm2 : success2;
1574
+ console.log(`${STATUS_MARKER} ${painter(message)}`);
1575
+ },
1576
+ message(message = "") {
1577
+ activeMessage = message;
1578
+ if (timer) {
1579
+ render(activeMessage);
1580
+ } else {
1581
+ console.log(`${STATUS_MARKER} ${strong2(activeMessage)}`);
1582
+ }
1583
+ }
1584
+ };
1585
+ }
1586
+ module2.exports = {
1587
+ cancelSymbol: cancelSymbol2,
1588
+ confirmOverwrite: confirmOverwrite2,
1589
+ createSpinner: createSpinner2,
1590
+ intro: intro2,
1591
+ outro: outro2,
1592
+ promptForInstallMode: promptForInstallMode2,
1593
+ promptForScope: promptForScope2,
1594
+ promptForSkills: promptForSkills2,
1595
+ promptForTargets: promptForTargets2
1596
+ };
1597
+ }
1598
+ });
1599
+
1600
+ // src/repo-cache.js
1601
+ var require_repo_cache = __commonJS({
1602
+ "src/repo-cache.js"(exports2, module2) {
1603
+ "use strict";
1604
+ var fs = require("fs");
1605
+ var path2 = require("path");
1606
+ var {
1607
+ CACHE_TTL_MS,
1608
+ DEFAULT_BRANCH: DEFAULT_BRANCH2,
1609
+ DEFAULT_REPO_URL: DEFAULT_REPO_URL2,
1610
+ getCacheDir,
1611
+ getCacheMetadataPath,
1612
+ LEGACY_CACHE_DIR_NAMES,
1613
+ LEGACY_CACHE_METADATA_NAMES,
1614
+ LEGACY_REPO_ENTRY_FILES,
1615
+ LEGACY_REPO_PACKAGE_NAMES,
1616
+ PRIVATE_REPO_ENV_VAR: PRIVATE_REPO_ENV_VAR2
1617
+ } = require_constants();
1618
+ var { GitCommandError, formatGitAccessError: formatGitAccessError2, runGit, syncInheritedGitConfig } = require_git();
1619
+ function getConfiguredRepoUrl2(options = {}, env = process.env) {
1620
+ return options.repoUrl || env[PRIVATE_REPO_ENV_VAR2] || DEFAULT_REPO_URL2;
1621
+ }
1622
+ function readCacheMetadata(cacheDir) {
1623
+ const metadataPath = getCacheMetadataPath(cacheDir);
1624
+ try {
1625
+ const raw = fs.readFileSync(metadataPath, "utf8");
1626
+ const parsed = JSON.parse(raw);
1627
+ if (!parsed || typeof parsed !== "object") {
1628
+ return null;
1629
+ }
1630
+ return {
1631
+ repoUrl: typeof parsed.repoUrl === "string" ? parsed.repoUrl : "",
1632
+ branch: typeof parsed.branch === "string" ? parsed.branch : DEFAULT_BRANCH2,
1633
+ lastFetchedAt: Number.isFinite(parsed.lastFetchedAt) ? parsed.lastFetchedAt : 0
1634
+ };
1635
+ } catch {
1636
+ return null;
1637
+ }
1638
+ }
1639
+ function writeCacheMetadata(cacheDir, metadata) {
1640
+ const metadataPath = getCacheMetadataPath(cacheDir);
1641
+ fs.mkdirSync(cacheDir, { recursive: true });
1642
+ fs.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}
1643
+ `, "utf8");
1644
+ }
1645
+ function isCacheStale(metadata, now = Date.now()) {
1646
+ if (!metadata || !metadata.lastFetchedAt) {
1647
+ return true;
1648
+ }
1649
+ return now - metadata.lastFetchedAt >= CACHE_TTL_MS;
1650
+ }
1651
+ function dirHasSkillEntries(base) {
1652
+ if (!isDirectory(base)) {
1653
+ return false;
1654
+ }
1655
+ const entries = fs.readdirSync(base, { withFileTypes: true });
1656
+ return entries.some(
1657
+ (entry) => entry.isDirectory() && fs.existsSync(path2.join(base, entry.name, "SKILL.md"))
1658
+ );
1659
+ }
1660
+ function dirHasSkills(root) {
1661
+ return dirHasSkillEntries(path2.join(root, "skills")) || dirHasSkillEntries(root);
1662
+ }
1663
+ function dirHasRepoMarkers(root) {
1664
+ if (fs.existsSync(path2.join(root, "skills.json"))) {
1665
+ return true;
1666
+ }
1667
+ const pkgPath = path2.join(root, "package.json");
1668
+ if (fs.existsSync(pkgPath)) {
1669
+ try {
1670
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
1671
+ if (pkg && (pkg.name === "synskill" || LEGACY_REPO_PACKAGE_NAMES.includes(pkg.name))) {
1672
+ return true;
1673
+ }
1674
+ } catch {
1675
+ return false;
1676
+ }
1677
+ }
1678
+ if (fs.existsSync(path2.join(root, "src", "synskill.js"))) {
1679
+ return true;
1680
+ }
1681
+ return LEGACY_REPO_ENTRY_FILES.some((entryFile) => fs.existsSync(path2.join(root, "src", entryFile)));
1682
+ }
1683
+ function findLocalRepoRoot(startDir) {
1684
+ let dir = path2.resolve(startDir);
1685
+ while (true) {
1686
+ if (dirHasSkills(dir) && dirHasRepoMarkers(dir)) {
1687
+ return dir;
1688
+ }
1689
+ const parent = path2.dirname(dir);
1690
+ if (parent === dir) {
1691
+ return null;
1692
+ }
1693
+ dir = parent;
1694
+ }
1695
+ }
1696
+ function ensureRepoCache(options = {}, deps = {}) {
1697
+ const cacheDir = deps.cacheDir || getCacheDir(deps.homeDir);
1698
+ const gitConfigContextCwd = deps.gitConfigContextCwd || process.cwd();
1699
+ migrateLegacyCache(cacheDir);
1700
+ const repoUrl = options.repoUrl || DEFAULT_REPO_URL2;
1701
+ const branch = options.branch || DEFAULT_BRANCH2;
1702
+ const now = deps.now || Date.now();
1703
+ const logger = deps.logger || null;
1704
+ const forceRefresh = Boolean(options.forceRefresh);
1705
+ const noPull = Boolean(options.noPull);
1706
+ const metadata = readCacheMetadata(cacheDir);
1707
+ const gitDir = path2.join(cacheDir, ".git");
1708
+ const hasGitRepo = isDirectory(gitDir);
1709
+ const remoteUrl = hasGitRepo ? readOriginUrl(cacheDir) : "";
1710
+ if (!hasGitRepo) {
1711
+ recreateCacheDir(cacheDir);
1712
+ cloneRepo(cacheDir, repoUrl, branch, gitConfigContextCwd);
1713
+ const nextMetadata = { repoUrl, branch, lastFetchedAt: now };
1714
+ writeCacheMetadata(cacheDir, nextMetadata);
1715
+ return { path: cacheDir, status: "cloned", metadata: nextMetadata };
1716
+ }
1717
+ if (metadata && metadata.repoUrl && metadata.repoUrl !== repoUrl || remoteUrl && remoteUrl !== repoUrl) {
1718
+ recreateCacheDir(cacheDir);
1719
+ cloneRepo(cacheDir, repoUrl, branch, gitConfigContextCwd);
1720
+ const nextMetadata = { repoUrl, branch, lastFetchedAt: now };
1721
+ writeCacheMetadata(cacheDir, nextMetadata);
1722
+ return { path: cacheDir, status: "recloned", metadata: nextMetadata };
1723
+ }
1724
+ if (forceRefresh) {
1725
+ refreshRepo(cacheDir, repoUrl, branch, gitConfigContextCwd);
1726
+ const nextMetadata = { repoUrl, branch, lastFetchedAt: now };
1727
+ writeCacheMetadata(cacheDir, nextMetadata);
1728
+ return { path: cacheDir, status: "refreshed", metadata: nextMetadata };
1729
+ }
1730
+ if (noPull) {
1731
+ return {
1732
+ path: cacheDir,
1733
+ status: "reused",
1734
+ metadata: metadata || { repoUrl, branch, lastFetchedAt: 0 }
1735
+ };
1736
+ }
1737
+ if (!isCacheStale(metadata, now)) {
1738
+ return { path: cacheDir, status: "reused", metadata };
1739
+ }
1740
+ try {
1741
+ refreshRepo(cacheDir, repoUrl, branch, gitConfigContextCwd);
1742
+ const nextMetadata = { repoUrl, branch, lastFetchedAt: now };
1743
+ writeCacheMetadata(cacheDir, nextMetadata);
1744
+ return { path: cacheDir, status: "refreshed", metadata: nextMetadata };
1745
+ } catch (error) {
1746
+ if (!(error instanceof GitCommandError)) {
1747
+ throw error;
1748
+ }
1749
+ if (logger && typeof logger.warn === "function") {
1750
+ logger.warn(
1751
+ `${formatGitAccessError2(repoUrl, error)}
1752
+ Using the existing local cache at ${cacheDir}.`
1753
+ );
1754
+ }
1755
+ return {
1756
+ path: cacheDir,
1757
+ status: "stale",
1758
+ metadata: metadata || { repoUrl, branch, lastFetchedAt: 0 },
1759
+ warning: error
1760
+ };
1761
+ }
1762
+ }
1763
+ function resolveRepoRoot2(options = {}, deps = {}) {
1764
+ const cwd = deps.cwd || process.cwd();
1765
+ const gitConfigContextCwd = deps.gitConfigContextCwd || cwd;
1766
+ const preferLocal = options.preferLocal !== false;
1767
+ const env = deps.env || process.env;
1768
+ const repoUrl = getConfiguredRepoUrl2(options, env);
1769
+ if (preferLocal && !options.repoUrl && !env[PRIVATE_REPO_ENV_VAR2]) {
1770
+ const localRepoRoot = findLocalRepoRoot(cwd);
1771
+ if (localRepoRoot) {
1772
+ return { path: localRepoRoot, status: "local", repoUrl: "" };
1773
+ }
1774
+ }
1775
+ const cacheResult = ensureRepoCache(
1776
+ {
1777
+ repoUrl,
1778
+ branch: options.branch || DEFAULT_BRANCH2,
1779
+ noPull: options.noPull,
1780
+ forceRefresh: options.forceRefresh
1781
+ },
1782
+ { ...deps, gitConfigContextCwd }
1783
+ );
1784
+ return {
1785
+ path: cacheResult.path,
1786
+ status: cacheResult.status,
1787
+ repoUrl,
1788
+ warning: cacheResult.warning
1789
+ };
1790
+ }
1791
+ function cloneRepo(cacheDir, repoUrl, branch, gitConfigContextCwd) {
1792
+ runGit(["clone", "--depth", "1", "--branch", branch, repoUrl, cacheDir], {
1793
+ repoUrl,
1794
+ gitConfigContextCwd
1795
+ });
1796
+ syncInheritedGitConfig(cacheDir, gitConfigContextCwd);
1797
+ }
1798
+ function refreshRepo(cacheDir, repoUrl, branch, gitConfigContextCwd) {
1799
+ syncInheritedGitConfig(cacheDir, gitConfigContextCwd);
1800
+ runGit(["fetch", "--depth", "1", "origin", branch], {
1801
+ cwd: cacheDir,
1802
+ repoUrl,
1803
+ gitConfigContextCwd
1804
+ });
1805
+ runGit(["reset", "--hard", "FETCH_HEAD"], {
1806
+ cwd: cacheDir,
1807
+ repoUrl,
1808
+ gitConfigContextCwd
1809
+ });
1810
+ runGit(["clean", "-fd", "-e", path2.basename(getCacheMetadataPath(cacheDir))], {
1811
+ cwd: cacheDir,
1812
+ repoUrl,
1813
+ gitConfigContextCwd
1814
+ });
1815
+ }
1816
+ function readOriginUrl(cacheDir) {
1817
+ try {
1818
+ const result = runGit(["config", "--get", "remote.origin.url"], { cwd: cacheDir });
1819
+ return result.stdout || "";
1820
+ } catch {
1821
+ return "";
1822
+ }
1823
+ }
1824
+ function recreateCacheDir(cacheDir) {
1825
+ fs.rmSync(cacheDir, { recursive: true, force: true });
1826
+ fs.mkdirSync(path2.dirname(cacheDir), { recursive: true });
1827
+ }
1828
+ function migrateLegacyCache(cacheDir) {
1829
+ const parentDir = path2.dirname(cacheDir);
1830
+ if (fs.existsSync(cacheDir)) {
1831
+ migrateLegacyMetadata(cacheDir);
1832
+ return;
1833
+ }
1834
+ for (const legacyDirName of LEGACY_CACHE_DIR_NAMES) {
1835
+ const legacyCacheDir = path2.join(parentDir, legacyDirName);
1836
+ if (cacheDir === legacyCacheDir || !fs.existsSync(legacyCacheDir)) {
1837
+ continue;
1838
+ }
1839
+ fs.renameSync(legacyCacheDir, cacheDir);
1840
+ migrateLegacyMetadata(cacheDir);
1841
+ return;
1842
+ }
1843
+ }
1844
+ function migrateLegacyMetadata(cacheDir) {
1845
+ for (const legacyMetadataName of LEGACY_CACHE_METADATA_NAMES) {
1846
+ const legacyMetadataPath = path2.join(cacheDir, legacyMetadataName);
1847
+ if (fs.existsSync(legacyMetadataPath) && !fs.existsSync(getCacheMetadataPath(cacheDir))) {
1848
+ fs.renameSync(legacyMetadataPath, getCacheMetadataPath(cacheDir));
1849
+ return;
1850
+ }
1851
+ }
1852
+ }
1853
+ function isDirectory(targetPath) {
1854
+ try {
1855
+ return fs.statSync(targetPath).isDirectory();
1856
+ } catch {
1857
+ return false;
1858
+ }
1859
+ }
1860
+ module2.exports = {
1861
+ dirHasRepoMarkers,
1862
+ dirHasSkillEntries,
1863
+ ensureRepoCache,
1864
+ findLocalRepoRoot,
1865
+ getConfiguredRepoUrl: getConfiguredRepoUrl2,
1866
+ isCacheStale,
1867
+ readCacheMetadata,
1868
+ resolveRepoRoot: resolveRepoRoot2,
1869
+ writeCacheMetadata
1870
+ };
1871
+ }
1872
+ });
1873
+
1874
+ // src/synskill.js
1875
+ var { readFileSync } = require("fs");
1876
+ var path = require("path");
1877
+ var { parseArgs } = require_args();
1878
+ var {
1879
+ CLI_NAME,
1880
+ DEFAULT_BRANCH,
1881
+ DEFAULT_REPO_URL,
1882
+ INSTALL_TARGETS,
1883
+ PRIVATE_REPO_ENV_VAR
1884
+ } = require_constants();
1885
+ var { formatGitAccessError } = require_git();
1886
+ var { buildInstallPlan, hasExistingSkills, installSkills } = require_installer();
1887
+ var {
1888
+ cancelSymbol,
1889
+ confirmOverwrite,
1890
+ createSpinner,
1891
+ intro,
1892
+ outro,
1893
+ promptForInstallMode,
1894
+ promptForScope,
1895
+ promptForSkills,
1896
+ promptForTargets
1897
+ } = require_prompts();
1898
+ var { getConfiguredRepoUrl, resolveRepoRoot } = require_repo_cache();
1899
+ var { getExplicitExternalSkills, materializeSkillSources } = require_external_skills();
1900
+ var { applyListFilter, filterSkillsByName, findSkills } = require_skill_catalog();
1901
+ var { accent, danger, muted, strong, success, warm } = require_theme();
1902
+ function usage() {
1903
+ console.log(`${CLI_NAME}`);
1904
+ console.log("");
1905
+ console.log("Commands:");
1906
+ console.log(" install Install skills (default)");
1907
+ console.log(" add Alias for install");
1908
+ console.log(" list List available skills from the private skills repo");
1909
+ console.log(" update Refresh the local private skills cache");
1910
+ console.log(" help Show this help");
1911
+ console.log("");
1912
+ console.log("Install options:");
1913
+ console.log(" --dest <path> Install into a single destination");
1914
+ console.log(" --global Install to user-level locations");
1915
+ console.log(" --project [path] Install to project-level locations (default: current directory)");
1916
+ console.log(" [skill ...] Install one or more skills by positional name");
1917
+ console.log(" --skills <list> Comma list of skill names to install");
1918
+ console.log(" --all Install all skills");
1919
+ console.log(" --copy Copy files instead of symlinking targets to the source skill directory");
1920
+ console.log(" --force Overwrite existing skills");
1921
+ console.log(" --no-pull Use the local cache as-is");
1922
+ console.log(" --yes, -y Skip prompts (defaults to all skills + global + .agents only)");
1923
+ console.log(" --filter <text> Filter skills by name, description, or repository");
1924
+ console.log(" --details Show full descriptions");
1925
+ console.log(" --repo-url <git-url> Override the private skills repo URL");
1926
+ console.log("");
1927
+ console.log("List options:");
1928
+ console.log(" --filter <text> Filter skills by name, description, or repository");
1929
+ console.log(" --details Show full descriptions");
1930
+ console.log(" --repo-url <git-url> Override the private skills repo URL");
1931
+ console.log("");
1932
+ console.log("Environment:");
1933
+ console.log(` ${PRIVATE_REPO_ENV_VAR} Override the private skills repo URL`);
1934
+ console.log("");
1935
+ console.log("Examples:");
1936
+ console.log(` npx ${CLI_NAME}`);
1937
+ console.log(` npx ${CLI_NAME} pr-review --project`);
1938
+ console.log(` npx ${CLI_NAME} skill-creator --global`);
1939
+ console.log(` npx ${CLI_NAME} install pr-review --project`);
1940
+ console.log(` npx ${CLI_NAME} add pr-review --project`);
1941
+ console.log(` npx ${CLI_NAME} list --filter review`);
1942
+ console.log(` npx ${CLI_NAME} install --skills pr-review --project`);
1943
+ console.log(` npx ${CLI_NAME} update`);
1944
+ }
1945
+ async function main() {
1946
+ const { command, options } = parseArgs(process.argv.slice(2));
1947
+ const effectiveCommand = command || "install";
1948
+ switch (effectiveCommand) {
1949
+ case "install":
1950
+ case "add":
1951
+ await runInstall(options);
1952
+ break;
1953
+ case "list":
1954
+ await runList(options);
1955
+ break;
1956
+ case "update":
1957
+ await runUpdate(options);
1958
+ break;
1959
+ case "help":
1960
+ usage();
1961
+ break;
1962
+ case "version":
1963
+ console.log(getVersion());
1964
+ break;
1965
+ default:
1966
+ console.error(`Unknown command: ${effectiveCommand}`);
1967
+ usage();
1968
+ process.exitCode = 1;
1969
+ }
1970
+ }
1971
+ async function runInstall(options) {
1972
+ const interactive = process.stdin.isTTY && process.stdout.isTTY;
1973
+ const spinner = interactive ? await createSpinner() : null;
1974
+ let preparedSkills = null;
1975
+ try {
1976
+ if (interactive && !options.yes) {
1977
+ await intro();
1978
+ }
1979
+ const explicitExternalSkills = getExplicitExternalSkills(options.skills);
1980
+ const explicitExternalNames = new Set(
1981
+ explicitExternalSkills.map((skill) => skill.name.toLowerCase())
1982
+ );
1983
+ const onlyExplicitExternalSkills = options.skills.length > 0 && options.skills.every((skillName) => explicitExternalNames.has(String(skillName).toLowerCase()));
1984
+ let availableSkills = explicitExternalSkills;
1985
+ if (!onlyExplicitExternalSkills) {
1986
+ const repoRoot = await withSpinner(
1987
+ spinner,
1988
+ "Resolving private skills repo",
1989
+ () => resolveRepoRoot({ ...options, preferLocal: true })
1990
+ );
1991
+ logRepoStatus(repoRoot, options);
1992
+ const skills = await withSpinner(spinner, "Loading skills", () => findSkills(repoRoot.path));
1993
+ availableSkills = mergeExplicitSkills(skills, explicitExternalSkills);
1994
+ }
1995
+ const filteredSkills = applyListFilter(availableSkills, options);
1996
+ if (options.filter && !filteredSkills.length) {
1997
+ throw new Error("No skills match the current filter.");
1998
+ }
1999
+ const selectedSkills = await resolveSelectedSkills(filteredSkills, options, interactive);
2000
+ const scope = await resolveInstallScope(options, interactive);
2001
+ const targetKeys = await resolveTargetKeys(options, interactive);
2002
+ const installMode = await resolveInstallMode(options, targetKeys, interactive);
2003
+ const installPlan = buildInstallPlan(options, targetKeys, scope, installMode);
2004
+ let force = options.force;
2005
+ if (!force && hasExistingSkills(installPlan, selectedSkills)) {
2006
+ if (!interactive || options.yes) {
2007
+ console.log(
2008
+ warm("Some skills already exist and will be skipped. Use --force to overwrite them.")
2009
+ );
2010
+ } else {
2011
+ const overwrite = await confirmOverwrite();
2012
+ if (overwrite === cancelSymbol) {
2013
+ cancel();
2014
+ return;
2015
+ }
2016
+ force = overwrite;
2017
+ }
2018
+ }
2019
+ preparedSkills = await withSpinner(
2020
+ spinner,
2021
+ "Preparing selected skills",
2022
+ () => materializeSkillSources(selectedSkills)
2023
+ );
2024
+ const results = await withSpinner(
2025
+ spinner,
2026
+ "Installing skills",
2027
+ () => installSkills(preparedSkills.skills, installPlan, force)
2028
+ );
2029
+ printInstallResults(results);
2030
+ if (interactive && !options.yes) {
2031
+ await outro("Installation complete. Restart your editor to load new skills.");
2032
+ } else {
2033
+ console.log(success("Done. Restart your editor to load new skills."));
2034
+ }
2035
+ } catch (error) {
2036
+ handleRunError(options, error);
2037
+ } finally {
2038
+ if (preparedSkills) {
2039
+ await preparedSkills.cleanup();
2040
+ }
2041
+ }
2042
+ }
2043
+ function mergeExplicitSkills(skills, explicitSkills) {
2044
+ if (!explicitSkills.length) {
2045
+ return skills;
2046
+ }
2047
+ const merged = new Map(skills.map((skill) => [skill.name.toLowerCase(), skill]));
2048
+ explicitSkills.forEach((skill) => {
2049
+ merged.set(skill.name.toLowerCase(), skill);
2050
+ });
2051
+ return Array.from(merged.values()).sort((left, right) => left.name.localeCompare(right.name));
2052
+ }
2053
+ async function runList(options) {
2054
+ const spinner = process.stdout.isTTY ? await createSpinner() : null;
2055
+ try {
2056
+ const repoRoot = await withSpinner(
2057
+ spinner,
2058
+ "Resolving private skills repo",
2059
+ () => resolveRepoRoot({ ...options, preferLocal: true })
2060
+ );
2061
+ logRepoStatus(repoRoot, options);
2062
+ let skills = await withSpinner(spinner, "Loading skills", () => findSkills(repoRoot.path));
2063
+ skills = applyListFilter(skills, options);
2064
+ if (!skills.length) {
2065
+ console.log(muted("No skills found."));
2066
+ return;
2067
+ }
2068
+ console.log(strong("Available skills:"));
2069
+ for (const skill of skills) {
2070
+ const description = options.details ? skill.description : skill.shortDescription || skill.description;
2071
+ const repository = skill.repository || "any";
2072
+ const suffix = description ? ` - ${description}` : "";
2073
+ console.log(` ${accent(">")} ${strong(skill.name)}${muted(suffix ? suffix : "")} ${warm(`[${repository}]`)}`);
2074
+ }
2075
+ } catch (error) {
2076
+ handleRunError(options, error);
2077
+ }
2078
+ }
2079
+ async function runUpdate(options) {
2080
+ const spinner = process.stdout.isTTY ? await createSpinner() : null;
2081
+ try {
2082
+ await withSpinner(
2083
+ spinner,
2084
+ "Refreshing private skills cache",
2085
+ () => resolveRepoRoot({
2086
+ ...options,
2087
+ repoUrl: getConfiguredRepoUrl(options),
2088
+ branch: DEFAULT_BRANCH,
2089
+ forceRefresh: true,
2090
+ preferLocal: false
2091
+ })
2092
+ );
2093
+ console.log(success("Private skills cache refreshed."));
2094
+ } catch (error) {
2095
+ handleRunError(options, error);
2096
+ }
2097
+ }
2098
+ async function resolveSelectedSkills(skills, options, interactive) {
2099
+ if (options.all || options.yes) {
2100
+ return skills;
2101
+ }
2102
+ if (options.skills.length) {
2103
+ return filterSkillsByName(skills, options.skills);
2104
+ }
2105
+ if (skills.length === 1) {
2106
+ return skills;
2107
+ }
2108
+ if (!interactive) {
2109
+ throw new Error(
2110
+ "Interactive skill selection is unavailable without a TTY. Use --skills, --all, or --yes."
2111
+ );
2112
+ }
2113
+ const selected = await promptForSkills(skills, options);
2114
+ if (selected === cancelSymbol) {
2115
+ cancel();
2116
+ process.exit(0);
2117
+ }
2118
+ return selected;
2119
+ }
2120
+ async function resolveTargetKeys(options, interactive) {
2121
+ if (options.dest) {
2122
+ return [];
2123
+ }
2124
+ if (options.yes || !interactive) {
2125
+ return ["agents"];
2126
+ }
2127
+ const selected = await promptForTargets();
2128
+ if (selected === cancelSymbol) {
2129
+ cancel();
2130
+ process.exit(0);
2131
+ }
2132
+ return selected;
2133
+ }
2134
+ async function resolveInstallScope(options, interactive) {
2135
+ if (options.dest) {
2136
+ return "dest";
2137
+ }
2138
+ if (options.global) {
2139
+ return "global";
2140
+ }
2141
+ if (options.projectPath !== void 0) {
2142
+ return "project";
2143
+ }
2144
+ if (options.yes) {
2145
+ return "global";
2146
+ }
2147
+ if (!interactive) {
2148
+ throw new Error("Install scope requires a TTY. Use --global, --project, --dest, or --yes.");
2149
+ }
2150
+ const scope = await promptForScope();
2151
+ if (scope === cancelSymbol) {
2152
+ cancel();
2153
+ process.exit(0);
2154
+ }
2155
+ return scope;
2156
+ }
2157
+ async function resolveInstallMode(options, targetKeys, interactive) {
2158
+ if (options.dest || options.copy) {
2159
+ return "copy";
2160
+ }
2161
+ const needsChoice = targetKeys.includes("claude");
2162
+ if (!needsChoice || options.yes || !interactive) {
2163
+ return "symlink";
2164
+ }
2165
+ const installMode = await promptForInstallMode();
2166
+ if (installMode === cancelSymbol) {
2167
+ cancel();
2168
+ process.exit(0);
2169
+ }
2170
+ return installMode;
2171
+ }
2172
+ function printInstallResults(results) {
2173
+ const installed = results.filter((result) => !result.skipped);
2174
+ const skipped = results.filter((result) => result.skipped);
2175
+ if (installed.length) {
2176
+ console.log(success(`Installed ${installed.length} target${installed.length === 1 ? "" : "s"}:`));
2177
+ installed.forEach((result) => {
2178
+ console.log(` ${accent(">")} ${strong(result.skill)} ${warm(`[${formatResultMode(result)}]`)} ${muted(`-> ${result.path}`)}`);
2179
+ });
2180
+ }
2181
+ if (skipped.length) {
2182
+ console.log(warm(`Skipped ${skipped.length} existing target${skipped.length === 1 ? "" : "s"}:`));
2183
+ skipped.forEach((result) => {
2184
+ console.log(` ${accent(">")} ${strong(result.skill)} ${warm(`[${formatResultMode(result)}]`)} ${muted(`-> ${result.path}`)}`);
2185
+ });
2186
+ }
2187
+ }
2188
+ function formatResultMode(result) {
2189
+ if (result.symlinkFailed) {
2190
+ return "copy-fallback";
2191
+ }
2192
+ return result.mode || "copy";
2193
+ }
2194
+ function logRepoStatus(repoRoot, options) {
2195
+ if (repoRoot.status === "local") {
2196
+ printDetailLine(`Using the local skills repo at ${repoRoot.path}`);
2197
+ return;
2198
+ }
2199
+ const repoUrl = repoRoot.repoUrl || getConfiguredRepoUrl(options);
2200
+ const repoLabel = repoUrl && repoUrl !== DEFAULT_REPO_URL ? repoUrl : "the private skills repo";
2201
+ switch (repoRoot.status) {
2202
+ case "cloned":
2203
+ printDetailLine(`Cloned ${repoLabel}.`);
2204
+ break;
2205
+ case "recloned":
2206
+ printDetailLine(`Recreated the cached ${repoLabel}.`);
2207
+ break;
2208
+ case "refreshed":
2209
+ printDetailLine("Refreshed the cached private skills repo.");
2210
+ break;
2211
+ case "stale":
2212
+ printDetailLine("Using the existing cached private skills repo because refresh failed.", warm);
2213
+ break;
2214
+ default:
2215
+ printDetailLine("Using the existing cached private skills repo.");
2216
+ break;
2217
+ }
2218
+ }
2219
+ function printDetailLine(message, tone = muted) {
2220
+ console.log(`${muted("\u2502")} ${tone(message)}`);
2221
+ }
2222
+ async function withSpinner(spinner, message, action) {
2223
+ if (spinner) {
2224
+ spinner.start(message);
2225
+ }
2226
+ try {
2227
+ const result = await action();
2228
+ if (spinner) {
2229
+ spinner.stop(message, "success");
2230
+ }
2231
+ return result;
2232
+ } catch (error) {
2233
+ if (spinner) {
2234
+ spinner.stop(message, "error");
2235
+ }
2236
+ throw error;
2237
+ }
2238
+ }
2239
+ function handleRunError(options, error) {
2240
+ const repoUrl = error && typeof error === "object" && typeof error.repoUrl === "string" && error.repoUrl ? error.repoUrl : getConfiguredRepoUrl(options);
2241
+ const message = error && typeof error === "object" && error.isAuthError !== void 0 ? formatGitAccessError(repoUrl, error) : error.message || String(error);
2242
+ console.error(danger(`Error: ${message}`));
2243
+ process.exitCode = 1;
2244
+ }
2245
+ function cancel() {
2246
+ }
2247
+ function getVersion() {
2248
+ try {
2249
+ const pkgPath = path.join(__dirname, "..", "package.json");
2250
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
2251
+ return pkg.version || "0.0.0";
2252
+ } catch {
2253
+ return "0.0.0";
2254
+ }
2255
+ }
2256
+ main().catch((error) => {
2257
+ console.error(danger(`Error: ${error.message || String(error)}`));
2258
+ process.exit(1);
2259
+ });