ccski 1.1.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,41 +1,11 @@
1
- import { createRequire } from "node:module";
1
+ import { n as __require, r as __toESM, t as __commonJS } from "./chunk-B7onSSqq.mjs";
2
2
  import { existsSync, readFileSync, readdirSync } from "node:fs";
3
3
  import { TextDecoder } from "node:util";
4
4
  import { homedir } from "node:os";
5
- import { dirname, isAbsolute, join, resolve } from "node:path";
5
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
6
  import * as tty from "tty";
7
+ import debug from "debug";
7
8
 
8
- //#region rolldown:runtime
9
- var __create = Object.create;
10
- var __defProp = Object.defineProperty;
11
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
- var __getOwnPropNames = Object.getOwnPropertyNames;
13
- var __getProtoOf = Object.getPrototypeOf;
14
- var __hasOwnProp = Object.prototype.hasOwnProperty;
15
- var __commonJS = (cb, mod) => function() {
16
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
- };
18
- var __copyProps = (to, from, except, desc) => {
19
- if (from && typeof from === "object" || typeof from === "function") {
20
- for (var keys = __getOwnPropNames(from), i$1 = 0, n = keys.length, key; i$1 < n; i$1++) {
21
- key = keys[i$1];
22
- if (!__hasOwnProp.call(to, key) && key !== except) {
23
- __defProp(to, key, {
24
- get: ((k) => from[k]).bind(null, key),
25
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
26
- });
27
- }
28
- }
29
- }
30
- return to;
31
- };
32
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
33
- value: mod,
34
- enumerable: true
35
- }) : target, mod));
36
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
37
-
38
- //#endregion
39
9
  //#region src/types/errors.ts
40
10
  /**
41
11
  * Base error class for ccski errors
@@ -62,8 +32,8 @@ var SkillNotFoundError = class extends CcskiError {
62
32
  * Error thrown when a skill name is ambiguous
63
33
  */
64
34
  var AmbiguousSkillNameError = class extends CcskiError {
65
- constructor(skillName, matches) {
66
- super(`Skill name '${skillName}' is ambiguous. Multiple skills found: ${matches.join(", ")}`);
35
+ constructor(skillName, matches, suggestions = []) {
36
+ super(`Skill name '${skillName}' is ambiguous. Multiple skills found: ${matches.join(", ")}`, suggestions.length ? suggestions : matches);
67
37
  this.skillName = skillName;
68
38
  this.matches = matches;
69
39
  this.name = "AmbiguousSkillNameError";
@@ -3687,15 +3657,20 @@ const PluginEntrySchema = objectType({
3687
3657
  lastUpdated: stringType(),
3688
3658
  installPath: stringType(),
3689
3659
  gitCommitSha: stringType(),
3690
- isLocal: booleanType()
3691
- });
3660
+ isLocal: booleanType(),
3661
+ scope: stringType().optional()
3662
+ }).passthrough();
3692
3663
  /**
3693
3664
  * Schema for installed_plugins.json
3694
3665
  */
3695
3666
  const InstalledPluginsSchema = objectType({
3696
3667
  version: numberType(),
3697
- plugins: recordType(stringType(), PluginEntrySchema)
3668
+ plugins: recordType(stringType(), unionType([PluginEntrySchema, arrayType(PluginEntrySchema)]))
3698
3669
  });
3670
+ /**
3671
+ * Schema for Claude settings.json (subset we care about)
3672
+ */
3673
+ const ClaudeSettingsSchema = objectType({ enabledPlugins: recordType(stringType(), booleanType()).optional() }).passthrough();
3699
3674
 
3700
3675
  //#endregion
3701
3676
  //#region node_modules/.pnpm/kind-of@6.0.3/node_modules/kind-of/index.js
@@ -11699,9 +11674,9 @@ var require_excerpt = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/gray-matt
11699
11674
  const opts = defaults$2(options);
11700
11675
  if (file.data == null) file.data = {};
11701
11676
  if (typeof opts.excerpt === "function") return opts.excerpt(file, opts);
11702
- const sep = file.data.excerpt_separator || opts.excerpt_separator;
11703
- if (sep == null && (opts.excerpt === false || opts.excerpt == null)) return file;
11704
- const delimiter = typeof opts.excerpt === "string" ? opts.excerpt : sep || opts.delimiters[0];
11677
+ const sep$1 = file.data.excerpt_separator || opts.excerpt_separator;
11678
+ if (sep$1 == null && (opts.excerpt === false || opts.excerpt == null)) return file;
11679
+ const delimiter = typeof opts.excerpt === "string" ? opts.excerpt : sep$1 || opts.delimiters[0];
11705
11680
  const idx = file.content.indexOf(delimiter);
11706
11681
  if (idx !== -1) file.excerpt = file.content.slice(0, idx);
11707
11682
  return file;
@@ -12323,9 +12298,37 @@ function error(text) {
12323
12298
  function warn(text) {
12324
12299
  return tone.warning(text);
12325
12300
  }
12301
+ function info(text) {
12302
+ return tone.info(text);
12303
+ }
12326
12304
  function heading(text) {
12327
12305
  return tone.underline(tone.bold(tone.accent(text)));
12328
12306
  }
12307
+ const SUPERSCRIPT_DIGITS = [
12308
+ "⁰",
12309
+ "¹",
12310
+ "²",
12311
+ "³",
12312
+ "⁴",
12313
+ "⁵",
12314
+ "⁶",
12315
+ "⁷",
12316
+ "⁸",
12317
+ "⁹"
12318
+ ];
12319
+ function toSuperscript(num) {
12320
+ return num.toString().split("").map((d) => SUPERSCRIPT_DIGITS[parseInt(d, 10)] ?? d).join("");
12321
+ }
12322
+ /**
12323
+ * Create a duplicate badge with superscript number.
12324
+ * isPrimary: the chosen skill in auto mode (shows just the number)
12325
+ * !isPrimary: the duplicate that was filtered out (shows [dup] + number)
12326
+ */
12327
+ function duplicateBadge(groupIndex, isPrimary) {
12328
+ const sup = toSuperscript(groupIndex);
12329
+ if (isPrimary) return tone.accent(sup);
12330
+ return tone.accent(`[dup]${sup}`);
12331
+ }
12329
12332
  function escapeRegExp$1(value) {
12330
12333
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12331
12334
  }
@@ -12375,15 +12378,34 @@ function displayWidth(value) {
12375
12378
 
12376
12379
  //#endregion
12377
12380
  //#region src/core/discovery.ts
12378
- /**
12379
- * Skill directories in priority order (highest to lowest)
12380
- */
12381
- const SKILL_DIRECTORIES = [
12382
- ".agent/skills",
12383
- ".claude/skills",
12384
- `${homedir()}/.agent/skills`,
12385
- `${homedir()}/.claude/skills`
12386
- ];
12381
+ function getDefaultSkillDirectories(userDir) {
12382
+ return [
12383
+ {
12384
+ path: ".agent/skills",
12385
+ provider: "claude"
12386
+ },
12387
+ {
12388
+ path: ".claude/skills",
12389
+ provider: "claude"
12390
+ },
12391
+ {
12392
+ path: join(userDir, ".agent/skills"),
12393
+ provider: "claude"
12394
+ },
12395
+ {
12396
+ path: join(userDir, ".claude/skills"),
12397
+ provider: "claude"
12398
+ },
12399
+ {
12400
+ path: ".codex/skills",
12401
+ provider: "codex"
12402
+ },
12403
+ {
12404
+ path: join(userDir, ".codex/skills"),
12405
+ provider: "codex"
12406
+ }
12407
+ ];
12408
+ }
12387
12409
  /**
12388
12410
  * Check if a directory has bundled resources
12389
12411
  */
@@ -12397,12 +12419,12 @@ function checkBundledResources(skillDir) {
12397
12419
  /**
12398
12420
  * Determine skill location type based on directory path
12399
12421
  */
12400
- function determineLocation(dirPath) {
12422
+ function determineLocation(dirPath, userDir) {
12401
12423
  const normalizedPath = resolve(dirPath);
12402
12424
  const cwd = process.cwd();
12403
- const home = homedir();
12404
- if (normalizedPath.startsWith(join(cwd, ".agent")) || normalizedPath.startsWith(join(cwd, ".claude"))) return "project";
12405
- if (normalizedPath.startsWith(join(home, ".agent")) || normalizedPath.startsWith(join(home, ".claude"))) return "user";
12425
+ const home = userDir;
12426
+ if (normalizedPath.startsWith(join(cwd, ".agent")) || normalizedPath.startsWith(join(cwd, ".claude")) || normalizedPath.startsWith(join(cwd, ".codex"))) return "project";
12427
+ if (normalizedPath.startsWith(join(home, ".agent")) || normalizedPath.startsWith(join(home, ".claude")) || normalizedPath.startsWith(join(home, ".codex"))) return "user";
12406
12428
  return "user";
12407
12429
  }
12408
12430
  function collectSkillDirectories(root, recursive, diagnostics, accumulator, includeDisabled) {
@@ -12428,7 +12450,7 @@ function collectSkillDirectories(root, recursive, diagnostics, accumulator, incl
12428
12450
  /**
12429
12451
  * Scan a single skill directory and return found skills
12430
12452
  */
12431
- function scanSkillDirectory(dirPath, { location, recursive = true, diagnostics, includeDisabled = false }) {
12453
+ function scanSkillDirectory(dirPath, { location, recursive = true, diagnostics, includeDisabled = false }, provider, userDir = homedir(), scope) {
12432
12454
  const skills = [];
12433
12455
  const skillDirectories = /* @__PURE__ */ new Set();
12434
12456
  collectSkillDirectories(dirPath, recursive, diagnostics, skillDirectories, includeDisabled);
@@ -12450,16 +12472,20 @@ function scanSkillDirectory(dirPath, { location, recursive = true, diagnostics,
12450
12472
  if (candidates.length === 0) continue;
12451
12473
  for (const candidate of candidates) try {
12452
12474
  const parsed = parseSkillFile(candidate.path);
12475
+ const baseName = parsed.frontmatter.name;
12476
+ const scopedName = scope && !baseName.includes(":") ? `${scope}:${baseName}` : baseName;
12453
12477
  const resources = checkBundledResources(skillDir);
12454
- const skillLocation = location ?? determineLocation(skillDir);
12478
+ const skillLocation = location ?? determineLocation(skillDir, userDir);
12455
12479
  skills.push({
12456
- name: parsed.frontmatter.name,
12480
+ name: scopedName,
12457
12481
  description: parsed.frontmatter.description,
12482
+ provider,
12458
12483
  location: skillLocation,
12459
12484
  path: skillDir,
12460
12485
  disabled: candidate.disabled,
12461
12486
  ...resources
12462
12487
  });
12488
+ diagnostics.byProvider[provider] = (diagnostics.byProvider[provider] ?? 0) + 1;
12463
12489
  } catch (error$1) {
12464
12490
  console.warn(colors.yellow(`Warning: Failed to parse ${candidate.path}: ${error$1 instanceof Error ? error$1.message : String(error$1)}`));
12465
12491
  diagnostics.warnings.push(`Failed to parse ${candidate.path}: ${error$1 instanceof Error ? error$1.message : String(error$1)}`);
@@ -12474,26 +12500,46 @@ function discoverSkills(options = {}) {
12474
12500
  const diagnostics = {
12475
12501
  scannedDirectories: [],
12476
12502
  warnings: [],
12477
- conflicts: []
12503
+ conflicts: [],
12504
+ byProvider: {
12505
+ claude: 0,
12506
+ codex: 0,
12507
+ file: 0
12508
+ }
12478
12509
  };
12510
+ const userDir = options.userDir ? resolve(options.userDir) : homedir();
12479
12511
  const directories = [];
12480
- if (options.customDirs?.length) directories.push(...options.customDirs);
12481
- if (options.scanDefaultDirs !== false) directories.push(...SKILL_DIRECTORIES);
12482
- const skillMap = /* @__PURE__ */ new Map();
12483
- for (const dir of directories) {
12484
- const skills = scanSkillDirectory(dir.startsWith("/") ? dir : resolve(process.cwd(), dir), {
12512
+ if (options.customDirs?.length) for (const entry of options.customDirs) {
12513
+ const provider = options.customProvider ?? "file";
12514
+ if (typeof entry === "string") directories.push({
12515
+ path: entry,
12516
+ provider,
12517
+ scope: provider === "file" ? "other" : void 0
12518
+ });
12519
+ else directories.push({
12520
+ path: entry.path,
12521
+ provider,
12522
+ scope: entry.scope ?? (provider === "file" ? "other" : void 0)
12523
+ });
12524
+ }
12525
+ if (options.scanDefaultDirs !== false) directories.push(...getDefaultSkillDirectories(userDir));
12526
+ const skills = [];
12527
+ const firstPathByName = /* @__PURE__ */ new Map();
12528
+ for (const entry of directories) {
12529
+ const { path: dir, provider, scope } = entry;
12530
+ const skillsFromDir = scanSkillDirectory(dir.startsWith("/") ? dir : resolve(process.cwd(), dir), {
12485
12531
  diagnostics,
12486
12532
  recursive: true,
12487
12533
  includeDisabled: options.includeDisabled === true
12488
- });
12489
- for (const skill of skills) if (!skillMap.has(skill.name)) skillMap.set(skill.name, skill);
12490
- else {
12491
- const existing = skillMap.get(skill.name);
12492
- diagnostics.conflicts.push(`Keeping ${existing?.path ?? "unknown"} and skipping ${skill.path} for skill '${skill.name}' (higher priority already loaded).`);
12534
+ }, provider, userDir, scope);
12535
+ for (const skill of skillsFromDir) {
12536
+ if (firstPathByName.has(skill.name)) diagnostics.conflicts.push(`Duplicate skill '${skill.name}' found at ${skill.path} (first seen at ${firstPathByName.get(skill.name)})`);
12537
+ else firstPathByName.set(skill.name, skill.path);
12538
+ skills.push(skill);
12493
12539
  }
12494
12540
  }
12495
12541
  return {
12496
- skills: Array.from(skillMap.values()),
12542
+ skills,
12497
12543
  diagnostics
12498
12544
  };
12499
12545
  }
@@ -12511,15 +12557,52 @@ function loadSkill(metadata) {
12511
12557
 
12512
12558
  //#endregion
12513
12559
  //#region src/core/plugins.ts
12514
- const PLUGINS_FILE = join(homedir(), ".claude/plugins/installed_plugins.json");
12515
- const PLUGINS_ROOT = join(homedir(), ".claude/plugins");
12560
+ function defaultPluginsFile(userDir) {
12561
+ return join(userDir, ".claude/plugins/installed_plugins.json");
12562
+ }
12563
+ function defaultPluginsRoot(userDir) {
12564
+ return join(userDir, ".claude/plugins");
12565
+ }
12566
+ function defaultSettingsFile(userDir) {
12567
+ return join(userDir, ".claude/settings.json");
12568
+ }
12516
12569
  function resolveInstallPath(rawPath, pluginsRoot) {
12517
12570
  return isAbsolute(rawPath) ? rawPath : join(pluginsRoot, rawPath);
12518
12571
  }
12572
+ function normalizePluginEntries(entry) {
12573
+ return Array.isArray(entry) ? entry : [entry];
12574
+ }
12575
+ function loadClaudeSettings(settingsFile, diagnostics) {
12576
+ if (!existsSync(settingsFile)) {
12577
+ diagnostics?.warnings.push(`Settings file not found at ${settingsFile}`);
12578
+ return null;
12579
+ }
12580
+ try {
12581
+ const content = readFileSync(settingsFile, "utf-8");
12582
+ const json = JSON.parse(content);
12583
+ const validation = ClaudeSettingsSchema.safeParse(json);
12584
+ if (!validation.success) {
12585
+ console.warn(colors.yellow(`Warning: Invalid settings.json format:`), validation.error);
12586
+ diagnostics?.warnings.push("Invalid settings.json format");
12587
+ return null;
12588
+ }
12589
+ return validation.data;
12590
+ } catch (error$1) {
12591
+ console.warn(colors.yellow(`Warning: Failed to load settings.json:`), error$1);
12592
+ diagnostics?.warnings.push(`Failed to load settings.json: ${error$1 instanceof Error ? error$1.message : String(error$1)}`);
12593
+ return null;
12594
+ }
12595
+ }
12596
+ function resolveEnabledPlugins(settings) {
12597
+ const enabledPlugins = settings?.enabledPlugins;
12598
+ if (!enabledPlugins) return null;
12599
+ const enabled = Object.entries(enabledPlugins).filter(([, isEnabled]) => isEnabled).map(([pluginKey]) => pluginKey);
12600
+ return new Set(enabled);
12601
+ }
12519
12602
  /**
12520
12603
  * Parse installed_plugins.json
12521
12604
  */
12522
- function loadInstalledPlugins(pluginsFile = PLUGINS_FILE, diagnostics) {
12605
+ function loadInstalledPlugins(pluginsFile, diagnostics) {
12523
12606
  if (!existsSync(pluginsFile)) {
12524
12607
  console.info(`[ccski] No plugin marketplace skills found at ${pluginsFile}`);
12525
12608
  diagnostics?.warnings.push(`Plugins file not found at ${pluginsFile}`);
@@ -12564,31 +12647,104 @@ function findSkillFiles(dir, diagnostics) {
12564
12647
  * Discover skills from installed plugins
12565
12648
  */
12566
12649
  function discoverPluginSkills(options = {}) {
12650
+ const log = debug("ccski:plugins");
12567
12651
  const diagnostics = {
12568
12652
  scannedPlugins: [],
12569
12653
  warnings: []
12570
12654
  };
12571
- const pluginsFile = options.pluginsFile ?? PLUGINS_FILE;
12655
+ const userDir = options.userDir ? resolve(options.userDir) : homedir();
12656
+ const pluginsFile = options.pluginsFile ?? defaultPluginsFile(userDir);
12572
12657
  diagnostics.scannedPlugins.push(pluginsFile);
12573
- const plugins = loadInstalledPlugins(pluginsFile, diagnostics);
12574
- if (!plugins) return {
12575
- skills: [],
12576
- diagnostics
12577
- };
12658
+ const settingsFile = options.settingsFile ?? defaultSettingsFile(userDir);
12578
12659
  const skills = [];
12579
- const pluginsRoot = options.pluginsRoot ?? PLUGINS_ROOT;
12580
- for (const [pluginKey, plugin] of Object.entries(plugins.plugins)) {
12660
+ const seenPaths = /* @__PURE__ */ new Set();
12661
+ const pluginsRoot = options.pluginsRoot ?? defaultPluginsRoot(userDir);
12662
+ let missingOrEmpty = false;
12663
+ const plugins = loadInstalledPlugins(pluginsFile, diagnostics);
12664
+ const enabledPlugins = resolveEnabledPlugins(loadClaudeSettings(settingsFile, diagnostics));
12665
+ log("plugins file=%s loaded=%s", pluginsFile, Boolean(plugins));
12666
+ log("settings file=%s enabledPlugins=%s", settingsFile, enabledPlugins?.size ?? "all");
12667
+ if (plugins) for (const [pluginKey, plugin] of Object.entries(plugins.plugins)) {
12668
+ if (enabledPlugins && !enabledPlugins.has(pluginKey)) {
12669
+ log("plugin %s disabled by settings", pluginKey);
12670
+ continue;
12671
+ }
12581
12672
  const [pluginName, marketplace] = pluginKey.split("@");
12582
12673
  if (!pluginName || !marketplace) continue;
12583
- const installPath = resolveInstallPath(plugin.installPath, pluginsRoot);
12584
- const skillFiles = findSkillFiles(installPath, diagnostics);
12585
- diagnostics.scannedPlugins.push(installPath);
12674
+ const entries = normalizePluginEntries(plugin);
12675
+ for (const entry of entries) {
12676
+ const installPath = resolveInstallPath(entry.installPath, pluginsRoot);
12677
+ const skillFiles = findSkillFiles(installPath, diagnostics);
12678
+ if (!skillFiles.length) {
12679
+ if (!existsSync(installPath)) {
12680
+ diagnostics.warnings.push(`Plugin install path not found: ${installPath}`);
12681
+ log("missing installPath: %s", installPath);
12682
+ } else {
12683
+ diagnostics.warnings.push(`No skills found in plugin install path: ${installPath}`);
12684
+ log("empty installPath: %s", installPath);
12685
+ }
12686
+ missingOrEmpty = true;
12687
+ }
12688
+ diagnostics.scannedPlugins.push(installPath);
12689
+ log("scanned manifest plugin=%s skills=%d", pluginName, skillFiles.length);
12690
+ for (const skillFile of skillFiles) try {
12691
+ const parsed = parseSkillFile(skillFile);
12692
+ const skillDir = dirname(skillFile);
12693
+ if (seenPaths.has(skillDir)) continue;
12694
+ const skillName = parsed.frontmatter.name;
12695
+ const namespaced = skillName.includes(":") ? skillName : pluginName === skillName ? skillName : `${pluginName}:${skillName}`;
12696
+ skills.push({
12697
+ name: namespaced,
12698
+ description: parsed.frontmatter.description,
12699
+ provider: "claude",
12700
+ location: "plugin",
12701
+ path: skillDir,
12702
+ hasReferences: existsSync(join(skillDir, "references")),
12703
+ hasScripts: existsSync(join(skillDir, "scripts")),
12704
+ hasAssets: existsSync(join(skillDir, "assets")),
12705
+ pluginInfo: {
12706
+ pluginName,
12707
+ marketplace,
12708
+ version: entry.version
12709
+ }
12710
+ });
12711
+ seenPaths.add(skillDir);
12712
+ } catch (error$1) {
12713
+ console.warn(colors.yellow(`Warning: Failed to parse plugin skill ${skillFile}:`), error$1);
12714
+ diagnostics.warnings.push(`Failed to parse plugin skill ${skillFile}: ${error$1 instanceof Error ? error$1.message : String(error$1)}`);
12715
+ }
12716
+ }
12717
+ }
12718
+ if (!(process.env.CCSKI_CLAUDE_PLUGINS_FALLBACK === "true")) {
12719
+ log("fallback scan skipped (CCSKI_CLAUDE_PLUGINS_FALLBACK!=true)");
12720
+ return {
12721
+ skills,
12722
+ diagnostics
12723
+ };
12724
+ }
12725
+ if (enabledPlugins) {
12726
+ log("fallback scan skipped (enabledPlugins present)");
12727
+ return {
12728
+ skills,
12729
+ diagnostics
12730
+ };
12731
+ }
12732
+ if (!plugins || skills.length === 0 || missingOrEmpty) {
12733
+ const fallbackRoot = join(pluginsRoot, "skills");
12734
+ diagnostics.scannedPlugins.push(fallbackRoot);
12735
+ log("running fallback scan at %s", fallbackRoot);
12736
+ const skillFiles = findSkillFiles(fallbackRoot, diagnostics);
12586
12737
  for (const skillFile of skillFiles) try {
12587
12738
  const parsed = parseSkillFile(skillFile);
12588
12739
  const skillDir = dirname(skillFile);
12740
+ if (seenPaths.has(skillDir)) continue;
12741
+ const pluginName = relative(fallbackRoot, skillDir).split(sep).filter(Boolean)[0] ?? "local";
12742
+ const skillName = parsed.frontmatter.name;
12743
+ const namespaced = skillName.includes(":") ? skillName : pluginName === skillName ? skillName : `${pluginName}:${skillName}`;
12589
12744
  skills.push({
12590
- name: `${pluginName}:${parsed.frontmatter.name}`,
12745
+ name: namespaced,
12591
12746
  description: parsed.frontmatter.description,
12747
+ provider: "claude",
12592
12748
  location: "plugin",
12593
12749
  path: skillDir,
12594
12750
  hasReferences: existsSync(join(skillDir, "references")),
@@ -12596,10 +12752,12 @@ function discoverPluginSkills(options = {}) {
12596
12752
  hasAssets: existsSync(join(skillDir, "assets")),
12597
12753
  pluginInfo: {
12598
12754
  pluginName,
12599
- marketplace,
12600
- version: plugin.version
12755
+ marketplace: "local",
12756
+ version: "unknown"
12601
12757
  }
12602
12758
  });
12759
+ seenPaths.add(skillDir);
12760
+ log("fallback skill added %s (%s)", namespaced, skillDir);
12603
12761
  } catch (error$1) {
12604
12762
  console.warn(colors.yellow(`Warning: Failed to parse plugin skill ${skillFile}:`), error$1);
12605
12763
  diagnostics.warnings.push(`Failed to parse plugin skill ${skillFile}: ${error$1 instanceof Error ? error$1.message : String(error$1)}`);
@@ -12657,8 +12815,8 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
12657
12815
  toUpper: (str) => str.toLocaleUpperCase(),
12658
12816
  toLower: (str) => str.toLocaleLowerCase(),
12659
12817
  compare: null,
12660
- sort: (info, haystack, needle, compare = cmp) => {
12661
- let { idx, chars, terms, interLft2, interLft1, start, intraIns, interIns, cases } = info;
12818
+ sort: (info$1, haystack, needle, compare = cmp) => {
12819
+ let { idx, chars, terms, interLft2, interLft1, start, intraIns, interIns, cases } = info$1;
12662
12820
  return idx.map((v, i$1) => i$1).sort((ia, ib) => chars[ib] - chars[ia] || intraIns[ia] - intraIns[ib] || terms[ib] + interLft2[ib] + .5 * interLft1[ib] - (terms[ia] + interLft2[ia] + .5 * interLft1[ia]) || interIns[ia] - interIns[ib] || start[ia] - start[ib] || cases[ib] - cases[ia] || compare(haystack[idx[ia]], haystack[idx[ib]]));
12663
12821
  }
12664
12822
  };
@@ -12806,7 +12964,7 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
12806
12964
  let withIntraBound = !!_intraBound;
12807
12965
  let interBound = new RegExp(_interBound, uFlag);
12808
12966
  let intraBound = new RegExp(_intraBound, uFlag);
12809
- const info = (idxs, haystack, needle) => {
12967
+ const info$1 = (idxs, haystack, needle) => {
12810
12968
  let [query, parts, contrs] = prepQuery(needle, 1);
12811
12969
  let partsCased = split(needle, true);
12812
12970
  let [queryR] = prepQuery(needle, 2);
@@ -12823,7 +12981,7 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
12823
12981
  }
12824
12982
  let len = idxs.length;
12825
12983
  let field = Array(len).fill(0);
12826
- let info$1 = {
12984
+ let info$2 = {
12827
12985
  idx: Array(len),
12828
12986
  start: field.slice(),
12829
12987
  chars: field.slice(),
@@ -12958,17 +13116,17 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
12958
13116
  if (j < partsLen - 1) idxAcc += groupLen + m[k + 1].length;
12959
13117
  }
12960
13118
  if (!disc) {
12961
- info$1.idx[ii] = idxs[i$1];
12962
- info$1.interLft2[ii] = lft2;
12963
- info$1.interLft1[ii] = lft1;
12964
- info$1.interRgt2[ii] = rgt2;
12965
- info$1.interRgt1[ii] = rgt1;
12966
- info$1.chars[ii] = chars;
12967
- info$1.terms[ii] = terms;
12968
- info$1.cases[ii] = cases;
12969
- info$1.interIns[ii] = inter;
12970
- info$1.intraIns[ii] = intra;
12971
- info$1.start[ii] = start;
13119
+ info$2.idx[ii] = idxs[i$1];
13120
+ info$2.interLft2[ii] = lft2;
13121
+ info$2.interLft1[ii] = lft1;
13122
+ info$2.interRgt2[ii] = rgt2;
13123
+ info$2.interRgt1[ii] = rgt1;
13124
+ info$2.chars[ii] = chars;
13125
+ info$2.terms[ii] = terms;
13126
+ info$2.cases[ii] = cases;
13127
+ info$2.interIns[ii] = inter;
13128
+ info$2.intraIns[ii] = intra;
13129
+ info$2.start[ii] = start;
12972
13130
  let m$1 = mhstr.match(queryR);
12973
13131
  let idxAcc$1 = m$1.index + m$1[1].length;
12974
13132
  let refLen = refine.length;
@@ -12995,7 +13153,7 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
12995
13153
  }
12996
13154
  }
12997
13155
  idxAcc$1 = m$1.index + m$1[1].length;
12998
- let ranges = info$1.ranges[ii] = [];
13156
+ let ranges = info$2.ranges[ii] = [];
12999
13157
  let from = idxAcc$1;
13000
13158
  let to = idxAcc$1;
13001
13159
  for (let i$2 = 2; i$2 < m$1.length; i$2++) {
@@ -13011,8 +13169,8 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
13011
13169
  ii++;
13012
13170
  }
13013
13171
  }
13014
- if (ii < idxs.length) for (let k in info$1) info$1[k] = info$1[k].slice(0, ii);
13015
- return info$1;
13172
+ if (ii < idxs.length) for (let k in info$2) info$2[k] = info$2[k].slice(0, ii);
13173
+ return info$2;
13016
13174
  };
13017
13175
  const refineMatch = (m, k, idxInNext, termLen) => {
13018
13176
  let prepend = m[k] + m[k + 1].slice(0, idxInNext);
@@ -13093,7 +13251,7 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
13093
13251
  let idxs = matches[ni];
13094
13252
  if (idxs == null || idxs.length == 0) continue;
13095
13253
  let needle$1 = needles[ni];
13096
- let _info = info(idxs, haystack, needle$1);
13254
+ let _info = info$1(idxs, haystack, needle$1);
13097
13255
  let order = opts.sort(_info, haystack, needle$1, compare);
13098
13256
  if (ni > 0) for (let i$1 = 0; i$1 < order.length; i$1++) order[i$1] += retOrder.length;
13099
13257
  for (let k in _info) retInfo[k] = (retInfo[k] ?? []).concat(_info[k]);
@@ -13112,7 +13270,7 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
13112
13270
  },
13113
13271
  split,
13114
13272
  filter,
13115
- info,
13273
+ info: info$1,
13116
13274
  sort: opts.sort
13117
13275
  };
13118
13276
  }
@@ -13217,18 +13375,85 @@ function containsCaseInsensitive(haystack, needle) {
13217
13375
  return haystack.toLowerCase().includes(needle.toLowerCase());
13218
13376
  }
13219
13377
 
13378
+ //#endregion
13379
+ //#region src/utils/skill-id.ts
13380
+ function stripPluginPrefix(name, pluginName) {
13381
+ const prefix = `${pluginName}:`;
13382
+ return name.startsWith(prefix) ? name.slice(prefix.length) : name;
13383
+ }
13384
+ const defaultFormatOptions = {
13385
+ includeProvider: true,
13386
+ color: true
13387
+ };
13388
+ /**
13389
+ * Build a copy/paste friendly skill id that includes provider and plugin (when present).
13390
+ * Format: provider:@plugin:name or provider:name
13391
+ */
13392
+ function formatSkillId(skill, opts) {
13393
+ const { includeProvider } = {
13394
+ ...defaultFormatOptions,
13395
+ ...opts
13396
+ };
13397
+ if (skill.pluginInfo) {
13398
+ const base = stripPluginPrefix(skill.name, skill.pluginInfo.pluginName);
13399
+ const id = `@${skill.pluginInfo.pluginName}:${base}`;
13400
+ return includeProvider ? `${skill.provider}:${id}` : id;
13401
+ }
13402
+ return includeProvider ? `${skill.provider}:${skill.name}` : skill.name;
13403
+ }
13404
+ /**
13405
+ * Return a set of aliases (lowercased) that should match user input, including plugin-qualified variants.
13406
+ */
13407
+ function skillAliases(skill) {
13408
+ const aliases = /* @__PURE__ */ new Set();
13409
+ aliases.add(skill.name.toLowerCase());
13410
+ const short = skill.name.toLowerCase().split(":").pop() ?? skill.name.toLowerCase();
13411
+ aliases.add(short);
13412
+ if (skill.pluginInfo) {
13413
+ const plugin = skill.pluginInfo.pluginName.toLowerCase();
13414
+ const base = stripPluginPrefix(skill.name.toLowerCase(), plugin);
13415
+ aliases.add(`${plugin}:${base}`);
13416
+ aliases.add(`@${plugin}:${base}`);
13417
+ aliases.add(`${skill.provider}:${plugin}:${base}`);
13418
+ aliases.add(`${skill.provider}:@${plugin}:${base}`);
13419
+ }
13420
+ return Array.from(aliases);
13421
+ }
13422
+ /**
13423
+ * Render a human-friendly label with colors, highlighting provider/plugin/name components.
13424
+ * Default includes provider to stay copy/pasteable; set includeProvider=false when a provider badge already exists.
13425
+ */
13426
+ function formatSkillLabel(skill, opts) {
13427
+ const { includeProvider, color, providerColor } = {
13428
+ ...defaultFormatOptions,
13429
+ ...opts
13430
+ };
13431
+ const useColor = color !== false;
13432
+ const providerPart = includeProvider ? `${skill.provider}:` : "";
13433
+ const pluginPart = skill.pluginInfo ? `@${skill.pluginInfo.pluginName}:` : "";
13434
+ const baseName = skill.pluginInfo ? stripPluginPrefix(skill.name, skill.pluginInfo.pluginName) : skill.name;
13435
+ if (!useColor) return `${providerPart}${pluginPart}${baseName}`;
13436
+ const defaultProviderColor = skill.provider === "claude" ? colors.green : skill.provider === "codex" ? colors.cyan : dim;
13437
+ const green$1 = (providerColor ?? defaultProviderColor)(providerPart.replace(/:$/, ""));
13438
+ const blue$1 = colors.blue(baseName);
13439
+ const pluginColored = pluginPart ? dim(pluginPart) : "";
13440
+ return `${includeProvider ? `${green$1}:` : ""}${pluginColored}${blue$1}`;
13441
+ }
13442
+
13220
13443
  //#endregion
13221
13444
  //#region src/core/registry.ts
13222
13445
  /**
13223
13446
  * Skill registry for managing discovered skills
13224
13447
  */
13225
13448
  var SkillRegistry = class {
13226
- skills = /* @__PURE__ */ new Map();
13449
+ skills = [];
13227
13450
  diagnostics = {
13228
13451
  directoriesScanned: [],
13229
13452
  pluginSources: [],
13230
13453
  warnings: [],
13231
- conflicts: []
13454
+ conflicts: [],
13455
+ byProvider: {},
13456
+ byLocation: {}
13232
13457
  };
13233
13458
  constructor(options = {}) {
13234
13459
  this.options = options;
@@ -13238,56 +13463,75 @@ var SkillRegistry = class {
13238
13463
  * Refresh the skill registry by rescanning directories
13239
13464
  */
13240
13465
  refresh() {
13241
- this.skills.clear();
13466
+ this.skills = [];
13242
13467
  this.diagnostics = {
13243
13468
  directoriesScanned: [],
13244
13469
  pluginSources: [],
13245
13470
  warnings: [],
13246
- conflicts: []
13471
+ conflicts: [],
13472
+ byProvider: {},
13473
+ byLocation: {}
13247
13474
  };
13248
13475
  const discovered = discoverSkills(this.options);
13249
13476
  this.diagnostics.directoriesScanned.push(...discovered.diagnostics.scannedDirectories);
13250
13477
  this.diagnostics.warnings.push(...discovered.diagnostics.warnings);
13251
13478
  this.diagnostics.conflicts.push(...discovered.diagnostics.conflicts);
13252
- for (const skill of discovered.skills) this.skills.set(skill.name, skill);
13479
+ for (const skill of discovered.skills) {
13480
+ this.skills.push(skill);
13481
+ this.diagnostics.byProvider[skill.provider] = (this.diagnostics.byProvider[skill.provider] ?? 0) + 1;
13482
+ this.diagnostics.byLocation[skill.location] = (this.diagnostics.byLocation[skill.location] ?? 0) + 1;
13483
+ }
13253
13484
  if (!this.options.skipPlugins) {
13254
13485
  const pluginOptions = {};
13255
13486
  if (this.options.pluginsFile) pluginOptions.pluginsFile = this.options.pluginsFile;
13256
13487
  if (this.options.pluginsRoot) pluginOptions.pluginsRoot = this.options.pluginsRoot;
13488
+ if (this.options.userDir) pluginOptions.userDir = this.options.userDir;
13257
13489
  const pluginSkills = discoverPluginSkills(pluginOptions);
13258
13490
  this.diagnostics.pluginSources.push(...pluginSkills.diagnostics.scannedPlugins);
13259
13491
  this.diagnostics.warnings.push(...pluginSkills.diagnostics.warnings);
13260
- for (const skill of pluginSkills.skills) if (!this.skills.has(skill.name)) this.skills.set(skill.name, skill);
13261
- else this.diagnostics.conflicts.push(`Skipping plugin skill ${skill.name} from ${skill.path} because a higher-priority skill is already loaded.`);
13492
+ for (const skill of pluginSkills.skills) {
13493
+ if (this.skills.some((existing) => existing.name.toLowerCase() === skill.name.toLowerCase() && existing.location !== "plugin")) this.diagnostics.conflicts.push(`Duplicate skill ${skill.name} from plugin at ${skill.path}; keeping for filter-stage resolution.`);
13494
+ this.skills.push(skill);
13495
+ this.diagnostics.byProvider[skill.provider] = (this.diagnostics.byProvider[skill.provider] ?? 0) + 1;
13496
+ this.diagnostics.byLocation[skill.location] = (this.diagnostics.byLocation[skill.location] ?? 0) + 1;
13497
+ }
13262
13498
  }
13263
13499
  }
13264
13500
  /**
13265
13501
  * Get all skills
13266
13502
  */
13267
13503
  getAll() {
13268
- return Array.from(this.skills.values());
13504
+ return [...this.skills];
13269
13505
  }
13270
13506
  /**
13271
13507
  * Find a skill by name (case-insensitive, supports full and short names)
13272
13508
  */
13273
13509
  find(name) {
13274
13510
  const normalizedName = name.toLowerCase();
13275
- for (const skill of this.skills.values()) if (skill.name.toLowerCase() === normalizedName) return skill;
13276
- if (name.includes(":")) throw new SkillNotFoundError(name, this.getSuggestions(name));
13277
- const shortMatches = Array.from(this.skills.values()).filter((skill) => {
13278
- return skill.name.split(":").pop()?.toLowerCase() === normalizedName;
13511
+ const [maybeProvider, rest] = normalizedName.includes(":") ? (() => {
13512
+ const parts = normalizedName.split(":");
13513
+ if (parts.length > 1 && [
13514
+ "claude",
13515
+ "codex",
13516
+ "file"
13517
+ ].includes(parts[0])) return [parts.shift(), parts.join(":")];
13518
+ return [void 0, normalizedName];
13519
+ })() : [void 0, normalizedName];
13520
+ const targetProvider = maybeProvider;
13521
+ const targetName = rest;
13522
+ const candidates = this.skills.filter((skill) => {
13523
+ const skillName = skill.name.toLowerCase();
13524
+ const shortName = skillName.split(":").pop();
13525
+ const matchesName = skillName === targetName || shortName === targetName;
13526
+ const matchesProvider = targetProvider ? skill.provider === targetProvider : true;
13527
+ return matchesName && matchesProvider;
13279
13528
  });
13280
- if (shortMatches.length === 0) throw new SkillNotFoundError(name, this.getSuggestions(name));
13281
- for (const tier of [
13282
- "project",
13283
- "user",
13284
- "plugin"
13285
- ]) {
13286
- const tierMatches = shortMatches.filter((skill) => skill.location === tier);
13287
- if (tierMatches.length === 1) return tierMatches[0];
13288
- if (tierMatches.length > 1) throw new AmbiguousSkillNameError(name, tierMatches.map((s) => s.name));
13289
- }
13290
- throw new SkillNotFoundError(name, this.getSuggestions(name));
13529
+ if (candidates.length === 1) return candidates[0];
13530
+ if (candidates.length > 1) {
13531
+ const suggestions = candidates.map((s) => this.formatNameWithProvider(s));
13532
+ throw new AmbiguousSkillNameError(name, candidates.map((s) => s.name), suggestions);
13533
+ }
13534
+ throw new SkillNotFoundError(name, this.getSuggestions(name, targetProvider));
13291
13535
  }
13292
13536
  /**
13293
13537
  * Check if a skill exists
@@ -13309,27 +13553,29 @@ var SkillRegistry = class {
13309
13553
  /**
13310
13554
  * Get suggestions for similar skill names (fuzzy matching)
13311
13555
  */
13312
- getSuggestions(query, limit = 3) {
13313
- const names = Array.from(this.skills.keys());
13314
- return rankStrings(names, query).slice(0, limit).map((index) => names[index]);
13556
+ getSuggestions(query, provider, limit = 3) {
13557
+ const pool = this.skills.filter((s) => provider ? s.provider === provider : true).map((s) => this.formatNameWithProvider(s));
13558
+ return rankStrings(pool, query).slice(0, limit).map((index) => pool[index]);
13559
+ }
13560
+ formatNameWithProvider(skill) {
13561
+ return formatSkillId(skill);
13315
13562
  }
13316
13563
  /**
13317
13564
  * Get diagnostic information
13318
13565
  */
13319
13566
  getDiagnostics() {
13320
- const byLocation = {};
13321
- for (const skill of this.skills.values()) byLocation[skill.location] = (byLocation[skill.location] ?? 0) + 1;
13322
13567
  return {
13323
- totalSkills: this.skills.size,
13324
- byLocation,
13325
- directoriesScanned: this.diagnostics.directoriesScanned,
13326
- pluginSources: this.diagnostics.pluginSources,
13327
- warnings: this.diagnostics.warnings,
13328
- conflicts: this.diagnostics.conflicts
13568
+ totalSkills: this.skills.length,
13569
+ byLocation: { ...this.diagnostics.byLocation },
13570
+ byProvider: { ...this.diagnostics.byProvider },
13571
+ directoriesScanned: [...this.diagnostics.directoriesScanned],
13572
+ pluginSources: [...this.diagnostics.pluginSources],
13573
+ warnings: [...this.diagnostics.warnings],
13574
+ conflicts: [...this.diagnostics.conflicts]
13329
13575
  };
13330
13576
  }
13331
13577
  };
13332
13578
 
13333
13579
  //#endregion
13334
- export { intersectionType as A, CcskiError as B, PluginEntrySchema as C, custom as D, booleanType as E, recordType as F, __require as G, SkillNotFoundError as H, stringType as I, __toESM as K, unionType as L, numberType as M, objectType as N, discriminatedUnionType as O, optionalType as P, unknownType as R, InstalledPluginsSchema as S, arrayType as T, ValidationError as U, ParseError as V, __commonJS as W, tone as _, discoverSkills as a, parseSkillFile as b, colors as c, formatBytes as d, heading as f, success as g, setColorEnabled as h, SKILL_DIRECTORIES as i, literalType as j, enumType as k, dim as l, renderList as m, containsCaseInsensitive as n, loadSkill as o, highlight$1 as p, rankStrings as r, scanSkillDirectory as s, SkillRegistry as t, error as u, warn as v, SkillFrontmatterSchema as w, validateSkillFile as x, wrap as y, AmbiguousSkillNameError as z };
13335
- //# sourceMappingURL=registry-DtGaEhsM.mjs.map
13580
+ export { SkillFrontmatterSchema as A, optionalType as B, warn as C, ClaudeSettingsSchema as D, validateSkillFile as E, enumType as F, AmbiguousSkillNameError as G, stringType as H, intersectionType as I, SkillNotFoundError as J, CcskiError as K, literalType as L, booleanType as M, custom as N, InstalledPluginsSchema as O, discriminatedUnionType as P, numberType as R, tone as S, parseSkillFile as T, unionType as U, recordType as V, unknownType as W, ValidationError as Y, highlight$1 as _, containsCaseInsensitive as a, setColorEnabled as b, getDefaultSkillDirectories as c, colors as d, dim as f, heading as g, formatBytes as h, skillAliases as i, arrayType as j, PluginEntrySchema as k, loadSkill as l, error as m, formatSkillId as n, rankStrings as o, duplicateBadge as p, ParseError as q, formatSkillLabel as r, discoverSkills as s, SkillRegistry as t, scanSkillDirectory as u, info as v, wrap as w, success as x, renderList as y, objectType as z };
13581
+ //# sourceMappingURL=registry-DNMfmLqa.mjs.map