ccski 1.1.0 → 2.0.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";
@@ -11699,9 +11669,9 @@ var require_excerpt = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/gray-matt
11699
11669
  const opts = defaults$2(options);
11700
11670
  if (file.data == null) file.data = {};
11701
11671
  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];
11672
+ const sep$1 = file.data.excerpt_separator || opts.excerpt_separator;
11673
+ if (sep$1 == null && (opts.excerpt === false || opts.excerpt == null)) return file;
11674
+ const delimiter = typeof opts.excerpt === "string" ? opts.excerpt : sep$1 || opts.delimiters[0];
11705
11675
  const idx = file.content.indexOf(delimiter);
11706
11676
  if (idx !== -1) file.excerpt = file.content.slice(0, idx);
11707
11677
  return file;
@@ -12323,9 +12293,37 @@ function error(text) {
12323
12293
  function warn(text) {
12324
12294
  return tone.warning(text);
12325
12295
  }
12296
+ function info(text) {
12297
+ return tone.info(text);
12298
+ }
12326
12299
  function heading(text) {
12327
12300
  return tone.underline(tone.bold(tone.accent(text)));
12328
12301
  }
12302
+ const SUPERSCRIPT_DIGITS = [
12303
+ "⁰",
12304
+ "¹",
12305
+ "²",
12306
+ "³",
12307
+ "⁴",
12308
+ "⁵",
12309
+ "⁶",
12310
+ "⁷",
12311
+ "⁸",
12312
+ "⁹"
12313
+ ];
12314
+ function toSuperscript(num) {
12315
+ return num.toString().split("").map((d) => SUPERSCRIPT_DIGITS[parseInt(d, 10)] ?? d).join("");
12316
+ }
12317
+ /**
12318
+ * Create a duplicate badge with superscript number.
12319
+ * isPrimary: the chosen skill in auto mode (shows just the number)
12320
+ * !isPrimary: the duplicate that was filtered out (shows [dup] + number)
12321
+ */
12322
+ function duplicateBadge(groupIndex, isPrimary) {
12323
+ const sup = toSuperscript(groupIndex);
12324
+ if (isPrimary) return tone.accent(sup);
12325
+ return tone.accent(`[dup]${sup}`);
12326
+ }
12329
12327
  function escapeRegExp$1(value) {
12330
12328
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12331
12329
  }
@@ -12375,15 +12373,34 @@ function displayWidth(value) {
12375
12373
 
12376
12374
  //#endregion
12377
12375
  //#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
- ];
12376
+ function getDefaultSkillDirectories(userDir) {
12377
+ return [
12378
+ {
12379
+ path: ".agent/skills",
12380
+ provider: "claude"
12381
+ },
12382
+ {
12383
+ path: ".claude/skills",
12384
+ provider: "claude"
12385
+ },
12386
+ {
12387
+ path: join(userDir, ".agent/skills"),
12388
+ provider: "claude"
12389
+ },
12390
+ {
12391
+ path: join(userDir, ".claude/skills"),
12392
+ provider: "claude"
12393
+ },
12394
+ {
12395
+ path: ".codex/skills",
12396
+ provider: "codex"
12397
+ },
12398
+ {
12399
+ path: join(userDir, ".codex/skills"),
12400
+ provider: "codex"
12401
+ }
12402
+ ];
12403
+ }
12387
12404
  /**
12388
12405
  * Check if a directory has bundled resources
12389
12406
  */
@@ -12397,12 +12414,12 @@ function checkBundledResources(skillDir) {
12397
12414
  /**
12398
12415
  * Determine skill location type based on directory path
12399
12416
  */
12400
- function determineLocation(dirPath) {
12417
+ function determineLocation(dirPath, userDir) {
12401
12418
  const normalizedPath = resolve(dirPath);
12402
12419
  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";
12420
+ const home = userDir;
12421
+ if (normalizedPath.startsWith(join(cwd, ".agent")) || normalizedPath.startsWith(join(cwd, ".claude")) || normalizedPath.startsWith(join(cwd, ".codex"))) return "project";
12422
+ if (normalizedPath.startsWith(join(home, ".agent")) || normalizedPath.startsWith(join(home, ".claude")) || normalizedPath.startsWith(join(home, ".codex"))) return "user";
12406
12423
  return "user";
12407
12424
  }
12408
12425
  function collectSkillDirectories(root, recursive, diagnostics, accumulator, includeDisabled) {
@@ -12428,7 +12445,7 @@ function collectSkillDirectories(root, recursive, diagnostics, accumulator, incl
12428
12445
  /**
12429
12446
  * Scan a single skill directory and return found skills
12430
12447
  */
12431
- function scanSkillDirectory(dirPath, { location, recursive = true, diagnostics, includeDisabled = false }) {
12448
+ function scanSkillDirectory(dirPath, { location, recursive = true, diagnostics, includeDisabled = false }, provider, userDir = homedir(), scope) {
12432
12449
  const skills = [];
12433
12450
  const skillDirectories = /* @__PURE__ */ new Set();
12434
12451
  collectSkillDirectories(dirPath, recursive, diagnostics, skillDirectories, includeDisabled);
@@ -12450,16 +12467,20 @@ function scanSkillDirectory(dirPath, { location, recursive = true, diagnostics,
12450
12467
  if (candidates.length === 0) continue;
12451
12468
  for (const candidate of candidates) try {
12452
12469
  const parsed = parseSkillFile(candidate.path);
12470
+ const baseName = parsed.frontmatter.name;
12471
+ const scopedName = scope && !baseName.includes(":") ? `${scope}:${baseName}` : baseName;
12453
12472
  const resources = checkBundledResources(skillDir);
12454
- const skillLocation = location ?? determineLocation(skillDir);
12473
+ const skillLocation = location ?? determineLocation(skillDir, userDir);
12455
12474
  skills.push({
12456
- name: parsed.frontmatter.name,
12475
+ name: scopedName,
12457
12476
  description: parsed.frontmatter.description,
12477
+ provider,
12458
12478
  location: skillLocation,
12459
12479
  path: skillDir,
12460
12480
  disabled: candidate.disabled,
12461
12481
  ...resources
12462
12482
  });
12483
+ diagnostics.byProvider[provider] = (diagnostics.byProvider[provider] ?? 0) + 1;
12463
12484
  } catch (error$1) {
12464
12485
  console.warn(colors.yellow(`Warning: Failed to parse ${candidate.path}: ${error$1 instanceof Error ? error$1.message : String(error$1)}`));
12465
12486
  diagnostics.warnings.push(`Failed to parse ${candidate.path}: ${error$1 instanceof Error ? error$1.message : String(error$1)}`);
@@ -12474,26 +12495,46 @@ function discoverSkills(options = {}) {
12474
12495
  const diagnostics = {
12475
12496
  scannedDirectories: [],
12476
12497
  warnings: [],
12477
- conflicts: []
12498
+ conflicts: [],
12499
+ byProvider: {
12500
+ claude: 0,
12501
+ codex: 0,
12502
+ file: 0
12503
+ }
12478
12504
  };
12505
+ const userDir = options.userDir ? resolve(options.userDir) : homedir();
12479
12506
  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), {
12507
+ if (options.customDirs?.length) for (const entry of options.customDirs) {
12508
+ const provider = options.customProvider ?? "file";
12509
+ if (typeof entry === "string") directories.push({
12510
+ path: entry,
12511
+ provider,
12512
+ scope: provider === "file" ? "other" : void 0
12513
+ });
12514
+ else directories.push({
12515
+ path: entry.path,
12516
+ provider,
12517
+ scope: entry.scope ?? (provider === "file" ? "other" : void 0)
12518
+ });
12519
+ }
12520
+ if (options.scanDefaultDirs !== false) directories.push(...getDefaultSkillDirectories(userDir));
12521
+ const skills = [];
12522
+ const firstPathByName = /* @__PURE__ */ new Map();
12523
+ for (const entry of directories) {
12524
+ const { path: dir, provider, scope } = entry;
12525
+ const skillsFromDir = scanSkillDirectory(dir.startsWith("/") ? dir : resolve(process.cwd(), dir), {
12485
12526
  diagnostics,
12486
12527
  recursive: true,
12487
12528
  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).`);
12529
+ }, provider, userDir, scope);
12530
+ for (const skill of skillsFromDir) {
12531
+ if (firstPathByName.has(skill.name)) diagnostics.conflicts.push(`Duplicate skill '${skill.name}' found at ${skill.path} (first seen at ${firstPathByName.get(skill.name)})`);
12532
+ else firstPathByName.set(skill.name, skill.path);
12533
+ skills.push(skill);
12493
12534
  }
12494
12535
  }
12495
12536
  return {
12496
- skills: Array.from(skillMap.values()),
12537
+ skills,
12497
12538
  diagnostics
12498
12539
  };
12499
12540
  }
@@ -12511,15 +12552,19 @@ function loadSkill(metadata) {
12511
12552
 
12512
12553
  //#endregion
12513
12554
  //#region src/core/plugins.ts
12514
- const PLUGINS_FILE = join(homedir(), ".claude/plugins/installed_plugins.json");
12515
- const PLUGINS_ROOT = join(homedir(), ".claude/plugins");
12555
+ function defaultPluginsFile(userDir) {
12556
+ return join(userDir, ".claude/plugins/installed_plugins.json");
12557
+ }
12558
+ function defaultPluginsRoot(userDir) {
12559
+ return join(userDir, ".claude/plugins");
12560
+ }
12516
12561
  function resolveInstallPath(rawPath, pluginsRoot) {
12517
12562
  return isAbsolute(rawPath) ? rawPath : join(pluginsRoot, rawPath);
12518
12563
  }
12519
12564
  /**
12520
12565
  * Parse installed_plugins.json
12521
12566
  */
12522
- function loadInstalledPlugins(pluginsFile = PLUGINS_FILE, diagnostics) {
12567
+ function loadInstalledPlugins(pluginsFile, diagnostics) {
12523
12568
  if (!existsSync(pluginsFile)) {
12524
12569
  console.info(`[ccski] No plugin marketplace skills found at ${pluginsFile}`);
12525
12570
  diagnostics?.warnings.push(`Plugins file not found at ${pluginsFile}`);
@@ -12564,31 +12609,47 @@ function findSkillFiles(dir, diagnostics) {
12564
12609
  * Discover skills from installed plugins
12565
12610
  */
12566
12611
  function discoverPluginSkills(options = {}) {
12612
+ const log = debug("ccski:plugins");
12567
12613
  const diagnostics = {
12568
12614
  scannedPlugins: [],
12569
12615
  warnings: []
12570
12616
  };
12571
- const pluginsFile = options.pluginsFile ?? PLUGINS_FILE;
12617
+ const userDir = options.userDir ? resolve(options.userDir) : homedir();
12618
+ const pluginsFile = options.pluginsFile ?? defaultPluginsFile(userDir);
12572
12619
  diagnostics.scannedPlugins.push(pluginsFile);
12573
- const plugins = loadInstalledPlugins(pluginsFile, diagnostics);
12574
- if (!plugins) return {
12575
- skills: [],
12576
- diagnostics
12577
- };
12578
12620
  const skills = [];
12579
- const pluginsRoot = options.pluginsRoot ?? PLUGINS_ROOT;
12580
- for (const [pluginKey, plugin] of Object.entries(plugins.plugins)) {
12621
+ const seenPaths = /* @__PURE__ */ new Set();
12622
+ const pluginsRoot = options.pluginsRoot ?? defaultPluginsRoot(userDir);
12623
+ let missingOrEmpty = false;
12624
+ const plugins = loadInstalledPlugins(pluginsFile, diagnostics);
12625
+ log("plugins file=%s loaded=%s", pluginsFile, Boolean(plugins));
12626
+ if (plugins) for (const [pluginKey, plugin] of Object.entries(plugins.plugins)) {
12581
12627
  const [pluginName, marketplace] = pluginKey.split("@");
12582
12628
  if (!pluginName || !marketplace) continue;
12583
12629
  const installPath = resolveInstallPath(plugin.installPath, pluginsRoot);
12584
12630
  const skillFiles = findSkillFiles(installPath, diagnostics);
12631
+ if (!skillFiles.length) {
12632
+ if (!existsSync(installPath)) {
12633
+ diagnostics.warnings.push(`Plugin install path not found: ${installPath}`);
12634
+ log("missing installPath: %s", installPath);
12635
+ } else {
12636
+ diagnostics.warnings.push(`No skills found in plugin install path: ${installPath}`);
12637
+ log("empty installPath: %s", installPath);
12638
+ }
12639
+ missingOrEmpty = true;
12640
+ }
12585
12641
  diagnostics.scannedPlugins.push(installPath);
12642
+ log("scanned manifest plugin=%s skills=%d", pluginName, skillFiles.length);
12586
12643
  for (const skillFile of skillFiles) try {
12587
12644
  const parsed = parseSkillFile(skillFile);
12588
12645
  const skillDir = dirname(skillFile);
12646
+ if (seenPaths.has(skillDir)) continue;
12647
+ const skillName = parsed.frontmatter.name;
12648
+ const namespaced = skillName.includes(":") ? skillName : pluginName === skillName ? skillName : `${pluginName}:${skillName}`;
12589
12649
  skills.push({
12590
- name: `${pluginName}:${parsed.frontmatter.name}`,
12650
+ name: namespaced,
12591
12651
  description: parsed.frontmatter.description,
12652
+ provider: "claude",
12592
12653
  location: "plugin",
12593
12654
  path: skillDir,
12594
12655
  hasReferences: existsSync(join(skillDir, "references")),
@@ -12600,6 +12661,48 @@ function discoverPluginSkills(options = {}) {
12600
12661
  version: plugin.version
12601
12662
  }
12602
12663
  });
12664
+ seenPaths.add(skillDir);
12665
+ } catch (error$1) {
12666
+ console.warn(colors.yellow(`Warning: Failed to parse plugin skill ${skillFile}:`), error$1);
12667
+ diagnostics.warnings.push(`Failed to parse plugin skill ${skillFile}: ${error$1 instanceof Error ? error$1.message : String(error$1)}`);
12668
+ }
12669
+ }
12670
+ if (!(process.env.CCSKI_CLAUDE_PLUGINS_FALLBACK === "true")) {
12671
+ log("fallback scan skipped (CCSKI_CLAUDE_PLUGINS_FALLBACK!=true)");
12672
+ return {
12673
+ skills,
12674
+ diagnostics
12675
+ };
12676
+ }
12677
+ if (!plugins || skills.length === 0 || missingOrEmpty) {
12678
+ const fallbackRoot = join(pluginsRoot, "skills");
12679
+ diagnostics.scannedPlugins.push(fallbackRoot);
12680
+ log("running fallback scan at %s", fallbackRoot);
12681
+ const skillFiles = findSkillFiles(fallbackRoot, diagnostics);
12682
+ for (const skillFile of skillFiles) try {
12683
+ const parsed = parseSkillFile(skillFile);
12684
+ const skillDir = dirname(skillFile);
12685
+ if (seenPaths.has(skillDir)) continue;
12686
+ const pluginName = relative(fallbackRoot, skillDir).split(sep).filter(Boolean)[0] ?? "local";
12687
+ const skillName = parsed.frontmatter.name;
12688
+ const namespaced = skillName.includes(":") ? skillName : pluginName === skillName ? skillName : `${pluginName}:${skillName}`;
12689
+ skills.push({
12690
+ name: namespaced,
12691
+ description: parsed.frontmatter.description,
12692
+ provider: "claude",
12693
+ location: "plugin",
12694
+ path: skillDir,
12695
+ hasReferences: existsSync(join(skillDir, "references")),
12696
+ hasScripts: existsSync(join(skillDir, "scripts")),
12697
+ hasAssets: existsSync(join(skillDir, "assets")),
12698
+ pluginInfo: {
12699
+ pluginName,
12700
+ marketplace: "local",
12701
+ version: "unknown"
12702
+ }
12703
+ });
12704
+ seenPaths.add(skillDir);
12705
+ log("fallback skill added %s (%s)", namespaced, skillDir);
12603
12706
  } catch (error$1) {
12604
12707
  console.warn(colors.yellow(`Warning: Failed to parse plugin skill ${skillFile}:`), error$1);
12605
12708
  diagnostics.warnings.push(`Failed to parse plugin skill ${skillFile}: ${error$1 instanceof Error ? error$1.message : String(error$1)}`);
@@ -12657,8 +12760,8 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
12657
12760
  toUpper: (str) => str.toLocaleUpperCase(),
12658
12761
  toLower: (str) => str.toLocaleLowerCase(),
12659
12762
  compare: null,
12660
- sort: (info, haystack, needle, compare = cmp) => {
12661
- let { idx, chars, terms, interLft2, interLft1, start, intraIns, interIns, cases } = info;
12763
+ sort: (info$1, haystack, needle, compare = cmp) => {
12764
+ let { idx, chars, terms, interLft2, interLft1, start, intraIns, interIns, cases } = info$1;
12662
12765
  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
12766
  }
12664
12767
  };
@@ -12806,7 +12909,7 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
12806
12909
  let withIntraBound = !!_intraBound;
12807
12910
  let interBound = new RegExp(_interBound, uFlag);
12808
12911
  let intraBound = new RegExp(_intraBound, uFlag);
12809
- const info = (idxs, haystack, needle) => {
12912
+ const info$1 = (idxs, haystack, needle) => {
12810
12913
  let [query, parts, contrs] = prepQuery(needle, 1);
12811
12914
  let partsCased = split(needle, true);
12812
12915
  let [queryR] = prepQuery(needle, 2);
@@ -12823,7 +12926,7 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
12823
12926
  }
12824
12927
  let len = idxs.length;
12825
12928
  let field = Array(len).fill(0);
12826
- let info$1 = {
12929
+ let info$2 = {
12827
12930
  idx: Array(len),
12828
12931
  start: field.slice(),
12829
12932
  chars: field.slice(),
@@ -12958,17 +13061,17 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
12958
13061
  if (j < partsLen - 1) idxAcc += groupLen + m[k + 1].length;
12959
13062
  }
12960
13063
  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;
13064
+ info$2.idx[ii] = idxs[i$1];
13065
+ info$2.interLft2[ii] = lft2;
13066
+ info$2.interLft1[ii] = lft1;
13067
+ info$2.interRgt2[ii] = rgt2;
13068
+ info$2.interRgt1[ii] = rgt1;
13069
+ info$2.chars[ii] = chars;
13070
+ info$2.terms[ii] = terms;
13071
+ info$2.cases[ii] = cases;
13072
+ info$2.interIns[ii] = inter;
13073
+ info$2.intraIns[ii] = intra;
13074
+ info$2.start[ii] = start;
12972
13075
  let m$1 = mhstr.match(queryR);
12973
13076
  let idxAcc$1 = m$1.index + m$1[1].length;
12974
13077
  let refLen = refine.length;
@@ -12995,7 +13098,7 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
12995
13098
  }
12996
13099
  }
12997
13100
  idxAcc$1 = m$1.index + m$1[1].length;
12998
- let ranges = info$1.ranges[ii] = [];
13101
+ let ranges = info$2.ranges[ii] = [];
12999
13102
  let from = idxAcc$1;
13000
13103
  let to = idxAcc$1;
13001
13104
  for (let i$2 = 2; i$2 < m$1.length; i$2++) {
@@ -13011,8 +13114,8 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
13011
13114
  ii++;
13012
13115
  }
13013
13116
  }
13014
- if (ii < idxs.length) for (let k in info$1) info$1[k] = info$1[k].slice(0, ii);
13015
- return info$1;
13117
+ if (ii < idxs.length) for (let k in info$2) info$2[k] = info$2[k].slice(0, ii);
13118
+ return info$2;
13016
13119
  };
13017
13120
  const refineMatch = (m, k, idxInNext, termLen) => {
13018
13121
  let prepend = m[k] + m[k + 1].slice(0, idxInNext);
@@ -13093,7 +13196,7 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
13093
13196
  let idxs = matches[ni];
13094
13197
  if (idxs == null || idxs.length == 0) continue;
13095
13198
  let needle$1 = needles[ni];
13096
- let _info = info(idxs, haystack, needle$1);
13199
+ let _info = info$1(idxs, haystack, needle$1);
13097
13200
  let order = opts.sort(_info, haystack, needle$1, compare);
13098
13201
  if (ni > 0) for (let i$1 = 0; i$1 < order.length; i$1++) order[i$1] += retOrder.length;
13099
13202
  for (let k in _info) retInfo[k] = (retInfo[k] ?? []).concat(_info[k]);
@@ -13112,7 +13215,7 @@ var require_uFuzzy = /* @__PURE__ */ __commonJS({ "node_modules/.pnpm/@leeoniya+
13112
13215
  },
13113
13216
  split,
13114
13217
  filter,
13115
- info,
13218
+ info: info$1,
13116
13219
  sort: opts.sort
13117
13220
  };
13118
13221
  }
@@ -13217,18 +13320,85 @@ function containsCaseInsensitive(haystack, needle) {
13217
13320
  return haystack.toLowerCase().includes(needle.toLowerCase());
13218
13321
  }
13219
13322
 
13323
+ //#endregion
13324
+ //#region src/utils/skill-id.ts
13325
+ function stripPluginPrefix(name, pluginName) {
13326
+ const prefix = `${pluginName}:`;
13327
+ return name.startsWith(prefix) ? name.slice(prefix.length) : name;
13328
+ }
13329
+ const defaultFormatOptions = {
13330
+ includeProvider: true,
13331
+ color: true
13332
+ };
13333
+ /**
13334
+ * Build a copy/paste friendly skill id that includes provider and plugin (when present).
13335
+ * Format: provider:@plugin:name or provider:name
13336
+ */
13337
+ function formatSkillId(skill, opts) {
13338
+ const { includeProvider } = {
13339
+ ...defaultFormatOptions,
13340
+ ...opts
13341
+ };
13342
+ if (skill.pluginInfo) {
13343
+ const base = stripPluginPrefix(skill.name, skill.pluginInfo.pluginName);
13344
+ const id = `@${skill.pluginInfo.pluginName}:${base}`;
13345
+ return includeProvider ? `${skill.provider}:${id}` : id;
13346
+ }
13347
+ return includeProvider ? `${skill.provider}:${skill.name}` : skill.name;
13348
+ }
13349
+ /**
13350
+ * Return a set of aliases (lowercased) that should match user input, including plugin-qualified variants.
13351
+ */
13352
+ function skillAliases(skill) {
13353
+ const aliases = /* @__PURE__ */ new Set();
13354
+ aliases.add(skill.name.toLowerCase());
13355
+ const short = skill.name.toLowerCase().split(":").pop() ?? skill.name.toLowerCase();
13356
+ aliases.add(short);
13357
+ if (skill.pluginInfo) {
13358
+ const plugin = skill.pluginInfo.pluginName.toLowerCase();
13359
+ const base = stripPluginPrefix(skill.name.toLowerCase(), plugin);
13360
+ aliases.add(`${plugin}:${base}`);
13361
+ aliases.add(`@${plugin}:${base}`);
13362
+ aliases.add(`${skill.provider}:${plugin}:${base}`);
13363
+ aliases.add(`${skill.provider}:@${plugin}:${base}`);
13364
+ }
13365
+ return Array.from(aliases);
13366
+ }
13367
+ /**
13368
+ * Render a human-friendly label with colors, highlighting provider/plugin/name components.
13369
+ * Default includes provider to stay copy/pasteable; set includeProvider=false when a provider badge already exists.
13370
+ */
13371
+ function formatSkillLabel(skill, opts) {
13372
+ const { includeProvider, color, providerColor } = {
13373
+ ...defaultFormatOptions,
13374
+ ...opts
13375
+ };
13376
+ const useColor = color !== false;
13377
+ const providerPart = includeProvider ? `${skill.provider}:` : "";
13378
+ const pluginPart = skill.pluginInfo ? `@${skill.pluginInfo.pluginName}:` : "";
13379
+ const baseName = skill.pluginInfo ? stripPluginPrefix(skill.name, skill.pluginInfo.pluginName) : skill.name;
13380
+ if (!useColor) return `${providerPart}${pluginPart}${baseName}`;
13381
+ const defaultProviderColor = skill.provider === "claude" ? colors.green : skill.provider === "codex" ? colors.cyan : dim;
13382
+ const green$1 = (providerColor ?? defaultProviderColor)(providerPart.replace(/:$/, ""));
13383
+ const blue$1 = colors.blue(baseName);
13384
+ const pluginColored = pluginPart ? dim(pluginPart) : "";
13385
+ return `${includeProvider ? `${green$1}:` : ""}${pluginColored}${blue$1}`;
13386
+ }
13387
+
13220
13388
  //#endregion
13221
13389
  //#region src/core/registry.ts
13222
13390
  /**
13223
13391
  * Skill registry for managing discovered skills
13224
13392
  */
13225
13393
  var SkillRegistry = class {
13226
- skills = /* @__PURE__ */ new Map();
13394
+ skills = [];
13227
13395
  diagnostics = {
13228
13396
  directoriesScanned: [],
13229
13397
  pluginSources: [],
13230
13398
  warnings: [],
13231
- conflicts: []
13399
+ conflicts: [],
13400
+ byProvider: {},
13401
+ byLocation: {}
13232
13402
  };
13233
13403
  constructor(options = {}) {
13234
13404
  this.options = options;
@@ -13238,56 +13408,75 @@ var SkillRegistry = class {
13238
13408
  * Refresh the skill registry by rescanning directories
13239
13409
  */
13240
13410
  refresh() {
13241
- this.skills.clear();
13411
+ this.skills = [];
13242
13412
  this.diagnostics = {
13243
13413
  directoriesScanned: [],
13244
13414
  pluginSources: [],
13245
13415
  warnings: [],
13246
- conflicts: []
13416
+ conflicts: [],
13417
+ byProvider: {},
13418
+ byLocation: {}
13247
13419
  };
13248
13420
  const discovered = discoverSkills(this.options);
13249
13421
  this.diagnostics.directoriesScanned.push(...discovered.diagnostics.scannedDirectories);
13250
13422
  this.diagnostics.warnings.push(...discovered.diagnostics.warnings);
13251
13423
  this.diagnostics.conflicts.push(...discovered.diagnostics.conflicts);
13252
- for (const skill of discovered.skills) this.skills.set(skill.name, skill);
13424
+ for (const skill of discovered.skills) {
13425
+ this.skills.push(skill);
13426
+ this.diagnostics.byProvider[skill.provider] = (this.diagnostics.byProvider[skill.provider] ?? 0) + 1;
13427
+ this.diagnostics.byLocation[skill.location] = (this.diagnostics.byLocation[skill.location] ?? 0) + 1;
13428
+ }
13253
13429
  if (!this.options.skipPlugins) {
13254
13430
  const pluginOptions = {};
13255
13431
  if (this.options.pluginsFile) pluginOptions.pluginsFile = this.options.pluginsFile;
13256
13432
  if (this.options.pluginsRoot) pluginOptions.pluginsRoot = this.options.pluginsRoot;
13433
+ if (this.options.userDir) pluginOptions.userDir = this.options.userDir;
13257
13434
  const pluginSkills = discoverPluginSkills(pluginOptions);
13258
13435
  this.diagnostics.pluginSources.push(...pluginSkills.diagnostics.scannedPlugins);
13259
13436
  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.`);
13437
+ for (const skill of pluginSkills.skills) {
13438
+ 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.`);
13439
+ this.skills.push(skill);
13440
+ this.diagnostics.byProvider[skill.provider] = (this.diagnostics.byProvider[skill.provider] ?? 0) + 1;
13441
+ this.diagnostics.byLocation[skill.location] = (this.diagnostics.byLocation[skill.location] ?? 0) + 1;
13442
+ }
13262
13443
  }
13263
13444
  }
13264
13445
  /**
13265
13446
  * Get all skills
13266
13447
  */
13267
13448
  getAll() {
13268
- return Array.from(this.skills.values());
13449
+ return [...this.skills];
13269
13450
  }
13270
13451
  /**
13271
13452
  * Find a skill by name (case-insensitive, supports full and short names)
13272
13453
  */
13273
13454
  find(name) {
13274
13455
  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;
13456
+ const [maybeProvider, rest] = normalizedName.includes(":") ? (() => {
13457
+ const parts = normalizedName.split(":");
13458
+ if (parts.length > 1 && [
13459
+ "claude",
13460
+ "codex",
13461
+ "file"
13462
+ ].includes(parts[0])) return [parts.shift(), parts.join(":")];
13463
+ return [void 0, normalizedName];
13464
+ })() : [void 0, normalizedName];
13465
+ const targetProvider = maybeProvider;
13466
+ const targetName = rest;
13467
+ const candidates = this.skills.filter((skill) => {
13468
+ const skillName = skill.name.toLowerCase();
13469
+ const shortName = skillName.split(":").pop();
13470
+ const matchesName = skillName === targetName || shortName === targetName;
13471
+ const matchesProvider = targetProvider ? skill.provider === targetProvider : true;
13472
+ return matchesName && matchesProvider;
13279
13473
  });
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));
13474
+ if (candidates.length === 1) return candidates[0];
13475
+ if (candidates.length > 1) {
13476
+ const suggestions = candidates.map((s) => this.formatNameWithProvider(s));
13477
+ throw new AmbiguousSkillNameError(name, candidates.map((s) => s.name), suggestions);
13478
+ }
13479
+ throw new SkillNotFoundError(name, this.getSuggestions(name, targetProvider));
13291
13480
  }
13292
13481
  /**
13293
13482
  * Check if a skill exists
@@ -13309,27 +13498,29 @@ var SkillRegistry = class {
13309
13498
  /**
13310
13499
  * Get suggestions for similar skill names (fuzzy matching)
13311
13500
  */
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]);
13501
+ getSuggestions(query, provider, limit = 3) {
13502
+ const pool = this.skills.filter((s) => provider ? s.provider === provider : true).map((s) => this.formatNameWithProvider(s));
13503
+ return rankStrings(pool, query).slice(0, limit).map((index) => pool[index]);
13504
+ }
13505
+ formatNameWithProvider(skill) {
13506
+ return formatSkillId(skill);
13315
13507
  }
13316
13508
  /**
13317
13509
  * Get diagnostic information
13318
13510
  */
13319
13511
  getDiagnostics() {
13320
- const byLocation = {};
13321
- for (const skill of this.skills.values()) byLocation[skill.location] = (byLocation[skill.location] ?? 0) + 1;
13322
13512
  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
13513
+ totalSkills: this.skills.length,
13514
+ byLocation: { ...this.diagnostics.byLocation },
13515
+ byProvider: { ...this.diagnostics.byProvider },
13516
+ directoriesScanned: [...this.diagnostics.directoriesScanned],
13517
+ pluginSources: [...this.diagnostics.pluginSources],
13518
+ warnings: [...this.diagnostics.warnings],
13519
+ conflicts: [...this.diagnostics.conflicts]
13329
13520
  };
13330
13521
  }
13331
13522
  };
13332
13523
 
13333
13524
  //#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
13525
+ export { arrayType as A, recordType as B, warn as C, InstalledPluginsSchema as D, validateSkillFile as E, intersectionType as F, CcskiError as G, unionType as H, literalType as I, ValidationError as J, ParseError as K, numberType as L, custom as M, discriminatedUnionType as N, PluginEntrySchema as O, enumType as P, objectType as R, tone as S, parseSkillFile as T, unknownType as U, stringType as V, AmbiguousSkillNameError as W, 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, booleanType as j, SkillFrontmatterSchema as k, loadSkill as l, error as m, formatSkillId as n, rankStrings as o, duplicateBadge as p, SkillNotFoundError 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, optionalType as z };
13526
+ //# sourceMappingURL=registry-Do2Htns7.mjs.map