@hasna/skills 0.8.7 → 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -70,6 +70,22 @@ skills hook install --agent all --selection-profile default --json
70
70
  skills hook install --agent all --selection-profile default --apply --json
71
71
  ```
72
72
 
73
+ Native migration includes the current directory, every ancestor, and the global
74
+ agent directories. Use `--project /path/to/project` to include another project
75
+ and its ancestors. This matches the prompt hook's project discovery, including
76
+ skills inherited from a parent workspace. Preview the JSON inventory before
77
+ applying it; archived copies retain recovery receipts. A drift refusal reports
78
+ the affected paths without printing their skill documents.
79
+
80
+ Claude hook installation sets `syncClaudeAiSkills: false` in user settings to
81
+ stop [account skill synchronization](https://code.claude.com/docs/en/skills#skills-synced-from-claudeai)
82
+ from recreating native copies. The prompt hook checks that setting. Vendor
83
+ migration also inventories `~/.claude/plugins/synced/` and archives its skill
84
+ documents while preserving other plugin files. Whole-plugin synchronization
85
+ remains unchanged because synced plugins can also provide hooks, MCP servers,
86
+ and language servers. A later plugin download that restores a skill document
87
+ will trigger another drift refusal and require review.
88
+
73
89
  Native hook invocations must use their installed adapter's selection profile.
74
90
  An old client command or environment override naming another profile refuses
75
91
  before synchronization or context loading. Review the hook installation and
@@ -178,9 +194,22 @@ Reviewed Hermes installations need fresh source and directory coverage in their
178
194
  reviewed bindings without changing existing source-only reviews.
179
195
 
180
196
  Hook installation preserves unrelated configuration, hooks, and plugin assets.
181
- It disables discovered Codex native skills; exact system-skill trees can remain
182
- only with their hash-bound disabled paths; migration preserves these package files. A client that restores or changes
183
- packaged skills requires a fresh inventory and disable plan. Native exports are
197
+ For Codex it sets `skills.bundled.enabled = false` in user configuration, preserving
198
+ the `skills-cli` bridge and disabling discovered native skill paths. This prevents
199
+ supported Codex clients from installing or loading their bundled `.system` skills.
200
+ The setting and its discovery witness are enrolled in the same configuration
201
+ transaction; existing installations must rerun `skills hook install`. Missing or
202
+ re-enabled bundled protection fails `skills hook check` before context loads.
203
+ Ordinary `[skills.bundled]` and legacy `[[skills.config]]` tables are supported;
204
+ inline or dotted target definitions that require rewriting refuse before changes.
205
+ Convert those definitions to ordinary tables while preserving their values, then
206
+ rerun installation. The control has been verified with Codex 0.153.0, 0.153.4 and
207
+ 0.154.0; this is not a claim that every older build supports it. Use a client that
208
+ supports `skills.bundled.enabled`; Skills does not infer support from version text
209
+ or silently substitute path-only protection. Existing exact system-skill trees can
210
+ remain only with their hash-bound disabled paths; migration preserves these package
211
+ files. A client that restores or changes packaged skills requires a fresh inventory
212
+ and disable plan. Native exports are
184
213
  refused while managed CLI loading is active. Migration preserves ordinary skill
185
214
  directories in private archives; `--include-unmanaged` includes user-authored
186
215
  copies, and `--include-vendor` retires vendor `SKILL.md` discovery files while
@@ -365,6 +394,18 @@ skills grants set default --file ./reviewed-policy.json --if-match <policy-revis
365
394
  skills grants show default --revision <prior-policy-revision> --json
366
395
  ```
367
396
 
397
+ For executable arguments, use `--` after the skill and Skills options:
398
+
399
+ ```bash
400
+ skills run --target local --selection-profile default --json \
401
+ your-skill@1.0.0 -- prepare --input child-input.json --json
402
+ ```
403
+
404
+ Everything after that separator belongs to the executable, including flags such
405
+ as `--input`, `--json` and any further `--`. Cloud execution accepts structured
406
+ Skills `--input` only and rejects executable arguments. Existing calls without a
407
+ separator retain their option parsing behavior.
408
+
368
409
  Updating a policy appends a revision and atomically changes its current pointer.
369
410
  An empty `grants` array revokes shared execution access. Historical policies remain
370
411
  readable and can be submitted as a new reviewed revision for rollback; they cannot
@@ -603,7 +644,7 @@ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
603
644
  | `skills search <query>` | `s` | Search by name, description, or tags |
604
645
  | `skills info <name>` | | Show metadata, env vars, and system dependencies |
605
646
  | `skills show <name>` | | Show account or owned portable skill details |
606
- | `skills docs <name>` | | Show documentation (SKILL.md > README.md > CLAUDE.md) |
647
+ | `skills docs <name>` | `--file skill\|readme\|claude` | Show preferred documentation; an explicit file must exist and unknown aliases fail |
607
648
  | `skills requires <name>` | | Show env vars, system deps, and npm dependencies |
608
649
  | `skills profiles show <id>` / `skills profiles set <id> --file <json>` | | Read an exact shared selection or update it with writer authorization |
609
650
  | `skills install [name@version] --selection-profile <id>` | | Cache selected immutable bundles; without names, sync the profile |
package/bin/index.js CHANGED
@@ -36868,7 +36868,7 @@ var package_default;
36868
36868
  var init_package = __esm(() => {
36869
36869
  package_default = {
36870
36870
  name: "@hasna/skills",
36871
- version: "0.8.7",
36871
+ version: "0.8.9",
36872
36872
  description: "Skills library for AI coding agents",
36873
36873
  type: "module",
36874
36874
  bin: {
@@ -51119,6 +51119,83 @@ var init_agent_adapters = __esm(() => {
51119
51119
  };
51120
51120
  });
51121
51121
 
51122
+ // src/lib/agent-codex.ts
51123
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
51124
+ function assertCodexPathConfigEditable(text2) {
51125
+ const before2 = Bun.TOML.parse(text2);
51126
+ if (before2.skills?.config === undefined)
51127
+ return;
51128
+ const expected = structuredClone(before2);
51129
+ delete expected.skills.config;
51130
+ try {
51131
+ const withoutSections = Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
51132
+ if (isDeepStrictEqual2(withoutSections, expected))
51133
+ return;
51134
+ } catch {}
51135
+ throw new Error("Codex skill path controls require ordinary [[skills.config]] TOML tables; preserve and convert inline or dotted config arrays before running skills hook install");
51136
+ }
51137
+ function disableCodexBundledSkills(text2) {
51138
+ const before2 = Bun.TOML.parse(text2);
51139
+ const table = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
51140
+ const refuse = () => new Error("Codex bundled skills configuration requires ordinary [skills.bundled] TOML with a boolean enabled setting; preserve and convert inline or dotted definitions before running skills hook install");
51141
+ if (before2.skills !== undefined && !table(before2.skills))
51142
+ throw refuse();
51143
+ if (before2.skills?.bundled !== undefined && !table(before2.skills.bundled))
51144
+ throw refuse();
51145
+ if (before2.skills !== undefined) {
51146
+ let root = before2, attempts = 0;
51147
+ for (const header of text2.matchAll(/^[ \t]*\[/gm)) {
51148
+ if (++attempts > 256)
51149
+ throw refuse();
51150
+ try {
51151
+ root = Bun.TOML.parse(text2.slice(0, header.index));
51152
+ break;
51153
+ } catch {}
51154
+ }
51155
+ if (root.skills !== undefined)
51156
+ throw refuse();
51157
+ }
51158
+ const enabled = before2.skills?.bundled?.enabled;
51159
+ if (enabled !== undefined && typeof enabled !== "boolean")
51160
+ throw refuse();
51161
+ if (enabled === false)
51162
+ return text2;
51163
+ const expected = structuredClone(before2);
51164
+ expected.skills ??= {};
51165
+ expected.skills.bundled ??= {};
51166
+ expected.skills.bundled.enabled = false;
51167
+ const candidates = new Set;
51168
+ const consider = (candidate) => {
51169
+ try {
51170
+ if (isDeepStrictEqual2(Bun.TOML.parse(candidate), expected))
51171
+ candidates.add(candidate);
51172
+ } catch {}
51173
+ };
51174
+ const newline = text2.includes(`\r
51175
+ `) ? `\r
51176
+ ` : `
51177
+ `;
51178
+ if (before2.skills?.bundled !== undefined) {
51179
+ for (const section of text2.matchAll(/^[ \t]*\[skills\.bundled\][ \t]*(?:#[^\r\n]*)?(?:\r?\n|$)(?:(?![ \t]*\[)[^\n]*(?:\n|$))*/gm)) {
51180
+ const headerEnd = section[0].indexOf(`
51181
+ `) + 1 || section[0].length;
51182
+ const replacement = enabled === true ? section[0].replace(/^([ \t]*enabled[ \t]*=[ \t]*)true([ \t]*(?:#[^\r\n]*)?)(\r?)$/m, "$1false$2$3") : section[0].slice(0, headerEnd) + (section[0].slice(0, headerEnd).endsWith(`
51183
+ `) ? "" : newline) + `enabled = false${newline}` + section[0].slice(headerEnd);
51184
+ consider(text2.slice(0, section.index) + replacement + text2.slice(section.index + section[0].length));
51185
+ }
51186
+ } else {
51187
+ consider(`${text2}${text2.endsWith(`
51188
+ `) || !text2 ? "" : newline}${newline}[skills.bundled]${newline}enabled = false${newline}`);
51189
+ }
51190
+ if (candidates.size !== 1)
51191
+ throw refuse();
51192
+ return [...candidates][0];
51193
+ }
51194
+ var CODEX_SKILL_CONFIG_SECTIONS;
51195
+ var init_agent_codex = __esm(() => {
51196
+ CODEX_SKILL_CONFIG_SECTIONS = /^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm;
51197
+ });
51198
+
51122
51199
  // src/lib/agent-integration.ts
51123
51200
  import { createHash as createHash9, randomUUID as randomUUID5 } from "crypto";
51124
51201
  import { existsSync as existsSync14, lstatSync as lstatSync11, statSync as statSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync9, opendirSync as opendirSync3, readlinkSync as readlinkSync2, realpathSync as realpathSync4, renameSync as renameSync3, rmdirSync, writeFileSync as writeFileSync5, unlinkSync as unlinkSync2, chmodSync, openSync as openSync4, closeSync as closeSync4, fsyncSync, fstatSync as fstatSync4, readSync as readSync4, constants as constants5, linkSync } from "fs";
@@ -51228,11 +51305,26 @@ function treeHash(root) {
51228
51305
  visit2(root);
51229
51306
  return hash2.digest("hex");
51230
51307
  }
51308
+ function projectAncestorDirectories(projects) {
51309
+ const directories = new Set;
51310
+ for (const project of projects) {
51311
+ for (let path = resolve10(project), depth = 0;; depth++) {
51312
+ if (depth >= 100)
51313
+ throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
51314
+ directories.add(path);
51315
+ const parent = dirname10(path);
51316
+ if (parent === path)
51317
+ break;
51318
+ path = parent;
51319
+ }
51320
+ }
51321
+ return [...directories];
51322
+ }
51231
51323
  function inventoryNativeSkills(home = homedir3(), options = {}) {
51232
51324
  const aliases = rootAliases(home, options.allowRootAliases);
51233
51325
  const roots = ROOTS.map(([agent, path]) => [agent, canonicalAgentPath(join21(home, path), aliases)]);
51234
51326
  const bridgePaths = Object.values(AGENT_ADAPTERS).map((adapter) => canonicalAgentPath(join21(home, adapter.root, CLI_BRIDGE_NAME), aliases));
51235
- for (const project of new Set([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []].map((path) => resolve10(path)))) {
51327
+ for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
51236
51328
  for (const [agent, path] of ROOTS)
51237
51329
  roots.push([agent, canonicalAgentPath(join21(project, path), aliases)]);
51238
51330
  }
@@ -51335,6 +51427,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
51335
51427
  if (options.includeVendor) {
51336
51428
  for (const agent of ["codex", "claude"])
51337
51429
  scanRoot(agent, canonicalAgentPath(join21(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
51430
+ scanRoot("claude", canonicalAgentPath(join21(home, ".claude", "plugins", "synced"), aliases), true);
51338
51431
  scanRoot("gemini", join21(home, ".gemini", "extensions"), true);
51339
51432
  }
51340
51433
  const configured = options.configured ? INTEGRATION_AGENTS.flatMap((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }).roots.map((path) => ({ agent, path }))) : [];
@@ -51399,6 +51492,7 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
51399
51492
  config.permissions.deny = nativeReady ? denied.filter((rule) => rule !== "Skill") : [...new Set([...denied, "Skill"])];
51400
51493
  config.permissions.allow = nativeReady ? [...new Set([...allowed, `Skill(${CLI_BRIDGE_NAME})`])] : allowed;
51401
51494
  config.disableBundledSkills = true;
51495
+ config.syncClaudeAiSkills = false;
51402
51496
  }
51403
51497
  if (agent === "gemini") {
51404
51498
  config.skills ??= {};
@@ -51429,16 +51523,21 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
51429
51523
  }
51430
51524
  }
51431
51525
  function disableCodexSkills(text2, skills, aliases, bridgePath) {
51432
- Bun.TOML.parse(text2);
51433
- let result2 = text2;
51526
+ const bundled = disableCodexBundledSkills(text2);
51527
+ let result2 = bundled;
51528
+ const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve10(bridgePath), resolve10(join21(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
51529
+ const previousConfig = Bun.TOML.parse(bundled).skills?.config;
51530
+ if (previousConfig !== undefined && (!Array.isArray(previousConfig) || previousConfig.some((entry) => !entry || typeof entry !== "object" || entry.path !== undefined && entry.name !== undefined)))
51531
+ throw new Error("Codex skill controls require one path or name selector per [[skills.config]] entry; review ambiguous entries before running skills hook install");
51532
+ const bridgeNeedsRepair = previousConfig?.some((entry) => entry.enabled === false && bridgeSelector(entry));
51434
51533
  const selections = [...skills.filter((entry) => entry.agent === "codex" && !entry.bridge).map((skill) => ({ path: skill.path, enabled: false })), { path: bridgePath, enabled: true }];
51435
51534
  for (const skill of selections) {
51436
51535
  const path = join21(skill.path, "SKILL.md");
51437
51536
  let found = false;
51438
- result2 = result2.replace(/^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm, (section) => {
51537
+ result2 = result2.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
51439
51538
  const parsed = Bun.TOML.parse(section);
51440
- const declared = parsed.skills?.config?.[0]?.path;
51441
- if (!declared || ![resolve10(path), resolve10(skill.path)].includes(canonicalAgentPath(declared, aliases)))
51539
+ const declared = parsed.skills?.config?.[0];
51540
+ if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [resolve10(path), resolve10(skill.path)].includes(canonicalAgentPath(declared.path, aliases))))
51442
51541
  return section;
51443
51542
  found = true;
51444
51543
  return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
@@ -51453,6 +51552,8 @@ path = ${JSON.stringify(path)}
51453
51552
  enabled = false
51454
51553
  `;
51455
51554
  }
51555
+ if (result2 !== bundled || bridgeNeedsRepair)
51556
+ assertCodexPathConfigEditable(bundled);
51456
51557
  Bun.TOML.parse(result2);
51457
51558
  return result2;
51458
51559
  }
@@ -51811,18 +51912,7 @@ function assertManagedAgentBridge(agent, options = {}) {
51811
51912
  const expected = canonicalAgentPath(join21(home, AGENT_ADAPTERS[agent].root, CLI_BRIDGE_NAME), aliases);
51812
51913
  if (!isOwnedCliBridge(expected, [expected]))
51813
51914
  throw new Error("NATIVE_SKILL_DRIFT: the native Skills bridge is missing or modified; repair it before continuing");
51814
- const roots = new Set;
51815
- for (const project of [options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]) {
51816
- for (let path = resolve10(project), depth = 0;; depth++) {
51817
- if (depth >= 100)
51818
- throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
51819
- roots.add(path);
51820
- const parent = dirname10(path);
51821
- if (parent === path)
51822
- break;
51823
- path = parent;
51824
- }
51825
- }
51915
+ const roots = projectAncestorDirectories([options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]);
51826
51916
  const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes"].includes(agent) && entry.path.includes(`${sep5}.agents${sep5}skills${sep5}`) || agent === "opencode" && entry.agent === "claude";
51827
51917
  assertProjectDiscovery(agent, [...roots], home, (path) => canonicalAgentPath(path, aliases));
51828
51918
  const configPath = canonicalAgentPath(join21(home, AGENT_ADAPTERS[agent].config), aliases), config = agent === "hermes" ? parseHermesConfig(readOptional(configPath)) : jsonObject(readOptional(configPath), configPath);
@@ -51849,13 +51939,17 @@ function assertManagedAgentBridge(agent, options = {}) {
51849
51939
  }
51850
51940
  if (agent === "claude" && (config.disableBundledSkills !== true || config.disableAllHooks === true || !config.permissions?.allow?.includes(`Skill(${CLI_BRIDGE_NAME})`) || config.permissions?.deny?.includes("Skill")))
51851
51941
  throw new Error("NATIVE_SKILL_DRIFT: Claude bridge or bundled-skill protection changed; retire copies and run skills hook install");
51942
+ if (agent === "claude" && config.syncClaudeAiSkills !== false)
51943
+ throw new Error("NATIVE_SKILL_DRIFT: Claude account skill synchronization is not disabled (syncClaudeAiSkills); run skills hook install to prevent native copies from returning");
51852
51944
  if (agent === "gemini" && (config.hooksConfig?.enabled === false || config.skills?.enabled !== true || !["antigravity-support", "skill-creator"].every((name) => config.skills?.disabled?.includes(name)) || config.skills?.disabled?.includes(CLI_BRIDGE_NAME)))
51853
51945
  throw new Error("NATIVE_SKILL_DRIFT: Gemini bridge or bundled-skill protection changed; run skills hook install");
51854
51946
  }
51855
51947
  const codexPath = canonicalAgentPath(join21(home, ".codex", "config.toml"), aliases);
51856
51948
  const codexConfig = agent === "codex" ? Bun.TOML.parse(readOptional(codexPath) ?? "") : {};
51949
+ if (agent === "codex" && codexConfig.skills?.bundled?.enabled !== false)
51950
+ throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
51857
51951
  const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join21(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
51858
- if (agent === "codex" && setting(expected).some((entry) => entry.enabled === false))
51952
+ if (agent === "codex" && [...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
51859
51953
  throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
51860
51954
  const disabledBuiltin = (entry) => {
51861
51955
  if (agent !== "codex" || !entry.vendor || !entry.path.startsWith(canonicalAgentPath(join21(home, ".codex", "skills", ".system"), aliases) + sep5))
@@ -51880,8 +51974,15 @@ function assertManagedAgentBridge(agent, options = {}) {
51880
51974
  throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
51881
51975
  }
51882
51976
  const inventory = inventoryNativeSkills(home, { includeVendor: true, guardHermes: agent === "hermes", projectDirs: [...roots], agentRoots: discovery.roots.map((path) => ({ agent, path })), allowRootAliases: aliases.length > 0 });
51883
- if (inventory.some((entry) => visible(entry) && !entry.bridge && !disabledBuiltin(entry)))
51884
- throw new Error("NATIVE_SKILL_DRIFT: unexpected native skill copies were found; review skills migrate native --include-unmanaged --include-vendor before continuing");
51977
+ const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !disabledBuiltin(entry));
51978
+ if (unexpected.length) {
51979
+ const paths = unexpected.slice(0, 3).map((entry) => {
51980
+ const escaped = JSON.stringify(entry.path).replace(/[\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
51981
+ return escaped.length > 768 ? `${escaped.slice(0, 384)}...${escaped.slice(-381)}` : escaped;
51982
+ });
51983
+ const remaining = unexpected.length - paths.length;
51984
+ throw new Error(`NATIVE_SKILL_DRIFT: ${unexpected.length} unexpected native skill copies were found: ${paths.join(", ")}${remaining ? `; ${remaining} more` : ""}. Review skills migrate native --project <working-directory> --include-unmanaged --include-vendor --json before continuing; it inventories that directory and its ancestors. Use --apply after reviewing the archive plan.`);
51985
+ }
51885
51986
  recheckRootAliases(aliases);
51886
51987
  }
51887
51988
  function normalizeAgentHookPrompt(agent, event, prompt) {
@@ -51926,6 +52027,7 @@ var init_agent_integration = __esm(() => {
51926
52027
  init_agent_bridge();
51927
52028
  init_agent_discovery();
51928
52029
  init_agent_adapters();
52030
+ init_agent_codex();
51929
52031
  init_agent_hermes();
51930
52032
  HOOK_EVENTS = ["UserPromptSubmit", "SessionStart", "SubagentStart"];
51931
52033
  ROOTS = [
@@ -73459,6 +73561,46 @@ var init_read_access = __esm(() => {
73459
73561
  init_remote_registry();
73460
73562
  });
73461
73563
 
73564
+ // src/cli/option-boundary.ts
73565
+ function optionPrefix(program2, args) {
73566
+ const indices = [];
73567
+ let command = program2, runArguments = false;
73568
+ for (let index = 0;index < args.length; index++) {
73569
+ const value = args[index];
73570
+ if (value === "--")
73571
+ return { indices, separator: index };
73572
+ indices.push(index);
73573
+ if (runArguments)
73574
+ continue;
73575
+ const options = [...command.options, ...program2.options];
73576
+ const option = options.find((option2) => option2.long === value || option2.short === value);
73577
+ if (option?.required || option?.optional && args[index + 1] !== undefined && !args[index + 1].startsWith("-")) {
73578
+ index++;
73579
+ continue;
73580
+ }
73581
+ if (!value.startsWith("--") && value.startsWith("-") && value.length > 2) {
73582
+ for (let offset = 1;offset < value.length; offset++) {
73583
+ const short = options.find((option2) => option2.short === `-${value[offset]}`);
73584
+ if (!short)
73585
+ break;
73586
+ if (short.required || short.optional) {
73587
+ if (offset === value.length - 1 && (short.required || args[index + 1] !== undefined && !args[index + 1].startsWith("-")))
73588
+ index++;
73589
+ break;
73590
+ }
73591
+ }
73592
+ }
73593
+ if (value.startsWith("-"))
73594
+ continue;
73595
+ const child = command.commands.find((child2) => child2.name() === value || child2.aliases().includes(value));
73596
+ if (child)
73597
+ command = child;
73598
+ else if (command.name() === "run" && command.parent === program2)
73599
+ runArguments = true;
73600
+ }
73601
+ return { indices, separator: -1 };
73602
+ }
73603
+
73462
73604
  // node_modules/zod/v4/classic/index.js
73463
73605
  var init_classic = __esm(() => {
73464
73606
  init_external();
@@ -76454,6 +76596,43 @@ async function prepareSkillDependencies(skillPath, env3, timeoutMs = DEFAULT_PRE
76454
76596
  var DEPENDENCY_PREPARATION_MARKER = ".skills-dependency-preparation", RECOVERY = "Skill dependency preparation is incomplete. Confirm no installer is running, successfully run bun install --no-save in the skill directory, then remove only .skills-dependency-preparation. See docs/skill-standard.md#recovering-interrupted-dependency-preparation.", DEFAULT_PREPARATION_TIMEOUT_MS = 60000;
76455
76597
  var init_dependency_preparation = () => {};
76456
76598
 
76599
+ // src/lib/skill-doc-file.ts
76600
+ function resolveSkillDocFile(file2) {
76601
+ switch (file2) {
76602
+ case undefined:
76603
+ case "":
76604
+ return;
76605
+ case "skill":
76606
+ return "SKILL.md";
76607
+ case "readme":
76608
+ return "README.md";
76609
+ case "claude":
76610
+ return "CLAUDE.md";
76611
+ default:
76612
+ throw new SkillDocFileError("SKILL_DOC_FILE_INVALID", "Unsupported documentation file. Choose skill, readme, or claude.");
76613
+ }
76614
+ }
76615
+ function selectSkillDoc(docs, file2) {
76616
+ const path = resolveSkillDocFile(file2);
76617
+ if (path === undefined)
76618
+ return docs.skillMd || docs.readme || docs.claudeMd || null;
76619
+ const content = path === "SKILL.md" ? docs.skillMd : path === "README.md" ? docs.readme : docs.claudeMd;
76620
+ if (content === null)
76621
+ throw new SkillDocFileError("SKILL_DOC_FILE_MISSING", `Documentation file ${path} was not found.`);
76622
+ return content;
76623
+ }
76624
+ var SkillDocFileError;
76625
+ var init_skill_doc_file = __esm(() => {
76626
+ SkillDocFileError = class SkillDocFileError extends Error {
76627
+ code;
76628
+ constructor(code, message) {
76629
+ super(message);
76630
+ this.code = code;
76631
+ this.name = "SkillDocFileError";
76632
+ }
76633
+ };
76634
+ });
76635
+
76457
76636
  // src/lib/skillinfo.ts
76458
76637
  var exports_skillinfo = {};
76459
76638
  __export(exports_skillinfo, {
@@ -76491,11 +76670,12 @@ function getSkillDocs(name) {
76491
76670
  claudeMd: readIfExists(join32(skillPath, "CLAUDE.md"))
76492
76671
  };
76493
76672
  }
76494
- function getSkillBestDoc(name) {
76673
+ function getSkillBestDoc(name, file2) {
76674
+ resolveSkillDocFile(file2);
76495
76675
  const docs = getSkillDocs(name);
76496
76676
  if (!docs)
76497
76677
  return null;
76498
- return docs.skillMd || docs.readme || docs.claudeMd || null;
76678
+ return selectSkillDoc(docs, file2);
76499
76679
  }
76500
76680
  function getSkillRequirements(name) {
76501
76681
  const skillPath = getSkillPath(name);
@@ -76815,6 +76995,7 @@ var init_skillinfo = __esm(() => {
76815
76995
  init_installer();
76816
76996
  init_registry();
76817
76997
  init_skill_validation();
76998
+ init_skill_doc_file();
76818
76999
  HOSTED_PROVIDER_ENV_PREFIXES = [
76819
77000
  "OPENAI_",
76820
77001
  "ANTHROPIC_",
@@ -77994,7 +78175,7 @@ function skillNotFound(name, similar = findSimilarSkills(name)) {
77994
78175
  console.error(source_default.dim(`Did you mean: ${similar.join(", ")}?`));
77995
78176
  }
77996
78177
  function handleReadRefusal(error61) {
77997
- if (!isSkillsFleetCredentialError(error61))
78178
+ if (!isSkillsFleetCredentialError(error61) && !(error61 instanceof SkillDocFileError))
77998
78179
  throw error61;
77999
78180
  console.error(source_default.red(error61.message));
78000
78181
  process.exitCode = 1;
@@ -78084,9 +78265,9 @@ async function resolveRemoteNotFound(name, remote, message) {
78084
78265
  }
78085
78266
  }
78086
78267
  async function handleDocs(name, options) {
78268
+ const file2 = resolveSkillDocFile(options.file);
78087
78269
  const access = await requireSkillsReadAccess();
78088
78270
  if (access.mode === "hosted") {
78089
- const file2 = options.file === "readme" ? "README.md" : options.file === "claude" ? "CLAUDE.md" : options.file ? "SKILL.md" : undefined;
78090
78271
  const result2 = await loadSelectedSkill(name, selectedProfileId(options.selectionProfile), { projectDir: process.cwd(), file: file2 });
78091
78272
  console.log(options.json ? JSON.stringify({ skill: name, ...result2 }) : result2.content);
78092
78273
  return;
@@ -78100,37 +78281,19 @@ async function handleDocs(name, options) {
78100
78281
  process.exitCode = 1;
78101
78282
  return;
78102
78283
  }
78284
+ const content = selectSkillDoc(docs, options.file);
78103
78285
  if (options.json) {
78104
78286
  console.log(JSON.stringify({
78105
78287
  skill: name,
78106
78288
  hasSkillMd: docs.skillMd !== null,
78107
78289
  hasReadme: docs.readme !== null,
78108
78290
  hasClaudeMd: docs.claudeMd !== null,
78109
- content: options.file ? docs[options.file === "skill" ? "skillMd" : options.file === "readme" ? "readme" : "claudeMd"] : docs.skillMd || docs.readme || docs.claudeMd
78291
+ content
78110
78292
  }, null, 2));
78111
78293
  return;
78112
78294
  }
78113
- let content = null;
78114
- if (options.file === "skill")
78115
- content = docs.skillMd;
78116
- else if (options.file === "readme")
78117
- content = docs.readme;
78118
- else if (options.file === "claude")
78119
- content = docs.claudeMd;
78120
- else
78121
- content = docs.skillMd || docs.readme || docs.claudeMd;
78122
- if (!content) {
78123
- const available = [];
78124
- if (docs.skillMd)
78125
- available.push("skill");
78126
- if (docs.readme)
78127
- available.push("readme");
78128
- if (docs.claudeMd)
78129
- available.push("claude");
78130
- if (!available.length)
78131
- console.log(source_default.dim(`No documentation found for '${name}'`));
78132
- else
78133
- console.log(source_default.dim(`File '${options.file}' not found. Available: ${available.join(", ")}`));
78295
+ if (content === null) {
78296
+ console.log(source_default.dim(`No documentation found for '${name}'`));
78134
78297
  return;
78135
78298
  }
78136
78299
  console.log(content);
@@ -78295,6 +78458,7 @@ var init_introspect = __esm(() => {
78295
78458
  init_fleet_credentials();
78296
78459
  init_read_access();
78297
78460
  init_skillinfo();
78461
+ init_skill_doc_file();
78298
78462
  init_installer();
78299
78463
  init_skill_validation();
78300
78464
  init_portable_skills();
@@ -81581,7 +81745,7 @@ var init_execution_grants = __esm(() => {
81581
81745
  });
81582
81746
 
81583
81747
  // src/lib/execution-grant-client.ts
81584
- import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
81748
+ import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
81585
81749
  function id(value) {
81586
81750
  if (!grantIdentifier(value))
81587
81751
  throw new Error("Invalid execution grant profile or revision identifier.");
@@ -81633,7 +81797,7 @@ async function saveExecutionGrantPolicy(profileId, grants, revision) {
81633
81797
  headers: revision === undefined ? { "If-None-Match": "*" } : { "If-Match": `"${revision}"` },
81634
81798
  body: JSON.stringify({ grants: snapshot })
81635
81799
  }));
81636
- if (policy.profileId !== profileId || policy.previousRevision !== (revision ?? null) || !isDeepStrictEqual2(policy.grants, snapshot)) {
81800
+ if (policy.profileId !== profileId || policy.previousRevision !== (revision ?? null) || !isDeepStrictEqual3(policy.grants, snapshot)) {
81637
81801
  throw new Error("The API did not preserve the reviewed execution grant policy; read it before retrying.");
81638
81802
  }
81639
81803
  return policy;
@@ -81644,7 +81808,7 @@ async function resolveExecutionGrant(input2) {
81644
81808
  if (!grantRecord(value) || Object.keys(value).length !== 3 || !grantIdentifier(value.policyRevision) || !grantIdentifier(value.grantId) || !grantRecord(value.bindings)) {
81645
81809
  throw new SkillSelectionError("INVALID_EXECUTION_GRANT", "The Skills API returned an invalid execution grant.");
81646
81810
  }
81647
- if (!isDeepStrictEqual2(value.bindings.selection, snapshot.selection) || !isDeepStrictEqual2(value.bindings.consumer, snapshot.consumer)) {
81811
+ if (!isDeepStrictEqual3(value.bindings.selection, snapshot.selection) || !isDeepStrictEqual3(value.bindings.consumer, snapshot.consumer)) {
81648
81812
  throw new SkillSelectionError("EXECUTION_GRANT_SCOPE_MISMATCH", "The Skills API returned a grant for a different selection or consumer.");
81649
81813
  }
81650
81814
  return value;
@@ -103274,7 +103438,12 @@ import { lstatSync as lstatSync21, mkdirSync as mkdirSync22, readFileSync as rea
103274
103438
  import { basename as basename6, isAbsolute as isAbsolute15, join as join49 } from "path";
103275
103439
  import { createInterface } from "readline";
103276
103440
  function registerRuntime(parent) {
103277
- parent.command("run").argument("<skill>", "Skill name").argument("[args...]", "Arguments to pass to the skill").allowUnknownOption(true).passThroughOptions(true).option("--json", "Output result as JSON", false).option("--remote", "Run on the configured server, using its catalog and quote", false).option("--target <target>", "Execution target: local or cloud").option("--secret-bindings <path>", "Reviewed, exact-selection local execution vault bindings (JSON file)").option("--secret-bindings-template", "Print a scoped bindings template without resolving secrets or running the skill", false).option("--input <json>", "Structured cloud execution input").option("--skill-version <version>", "Exact selected skill version").option("--selection-profile <id>", "Selected Skills profile").option("--session <id>", "Pinned Skills session").option("--cached", "Use the explicit verified selected cache", false).option("--yes", "Approve the server's quoted credit cost for this run", false).option("--idempotency-key <key>", "Reuse this key when retrying the same remote submission").option("--file <path>", "Attach a local input file to a remote run (repeatable)", (value, prior) => [...prior, value], []).option("--wait", "Poll remote runs until a terminal status", false).option("--poll-interval-ms <ms>", "Remote polling interval in milliseconds", "1000").option("--poll-timeout-ms <ms>", "Maximum time to wait for a remote run", "300000").description("Run a skill directly").action(async (name, args2, options) => handleRun(name, args2, options));
103441
+ parent.command("run").argument("<skill>", "Skill name").argument("[args...]", "Arguments to pass to the skill; use -- to end Skills options").allowUnknownOption(true).passThroughOptions(true).option("--json", "Output result as JSON", false).option("--remote", "Run on the configured server, using its catalog and quote", false).option("--target <target>", "Execution target: local or cloud").option("--secret-bindings <path>", "Reviewed, exact-selection local execution vault bindings (JSON file)").option("--secret-bindings-template", "Print a scoped bindings template without resolving secrets or running the skill", false).option("--input <json>", "Structured cloud execution input").option("--skill-version <version>", "Exact selected skill version").option("--selection-profile <id>", "Selected Skills profile").option("--session <id>", "Pinned Skills session").option("--cached", "Use the explicit verified selected cache", false).option("--yes", "Approve the server's quoted credit cost for this run", false).option("--idempotency-key <key>", "Reuse this key when retrying the same remote submission").option("--file <path>", "Attach a local input file to a remote run (repeatable)", (value, prior) => [...prior, value], []).option("--wait", "Poll remote runs until a terminal status", false).option("--poll-interval-ms <ms>", "Remote polling interval in milliseconds", "1000").option("--poll-timeout-ms <ms>", "Maximum time to wait for a remote run", "300000").description("Run a skill directly").action(async (name, args2, options, command) => {
103442
+ const parentArgs = command.parent.args;
103443
+ const prefix = parentArgs.slice(0, parentArgs.length - command.args.length);
103444
+ const separatorBeforeSkill = optionPrefix(command.parent, prefix).separator >= 0;
103445
+ await handleRun(name, args2, options, separatorBeforeSkill);
103446
+ });
103278
103447
  const executions = parent.command("executions").description("Inspect isolated cloud executions");
103279
103448
  for (const operation of ["show", "logs", "artifacts", "cancel"]) {
103280
103449
  executions.command(operation).argument("<execution-id>").option("--json", "Output as JSON", false).action(async (id2) => {
@@ -103536,7 +103705,7 @@ function promptLine(question) {
103536
103705
  });
103537
103706
  });
103538
103707
  }
103539
- async function handleRun(name, args2, options) {
103708
+ async function handleRun(name, args2, options, separatorBeforeSkill) {
103540
103709
  let managed;
103541
103710
  try {
103542
103711
  managed = requiresCliSkillLoading();
@@ -103545,6 +103714,12 @@ async function handleRun(name, args2, options) {
103545
103714
  process.exitCode = 1;
103546
103715
  return;
103547
103716
  }
103717
+ const separator = args2.indexOf("--");
103718
+ const childArgs = separatorBeforeSkill ? args2 : separator < 0 ? [] : args2.slice(separator + 1);
103719
+ if (separatorBeforeSkill)
103720
+ args2 = [];
103721
+ else if (separator >= 0)
103722
+ args2 = args2.slice(0, separator);
103548
103723
  const targetIndex = args2.indexOf("--target");
103549
103724
  if (managed && !options.remote || options.target || targetIndex >= 0 || options.secretBindings || options.secretBindingsTemplate || args2.includes("--secret-bindings") || args2.includes("--secret-bindings-template")) {
103550
103725
  options = { ...options };
@@ -103570,6 +103745,7 @@ async function handleRun(name, args2, options) {
103570
103745
  }
103571
103746
  }
103572
103747
  }
103748
+ args2 = [...args2, ...childArgs];
103573
103749
  if (options.remote && options.target) {
103574
103750
  console.error("Conflicting --remote and --target execution modes");
103575
103751
  process.exitCode = 1;
@@ -105609,10 +105785,10 @@ function registerAgentIntegration(parent) {
105609
105785
  }
105610
105786
  });
105611
105787
  const migrate = parent.command("migrate").description("Preserve and retire native agent skill copies");
105612
- migrate.command("native").option("--project <directory>", "Also inventory native skills in a project directory").option("--include-unmanaged", "Archive user-authored skills as well as Skills-managed copies", false).option("--include-vendor", "Retire vendor SKILL.md discovery files while preserving plugin scripts and assets", false).option("--discovery-inputs <file>", "Advanced reviewed active plugin roots and source hashes").option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Move selected skills to private archives outside agent discovery roots", false).option("--json", "Output inventory and archive receipt as JSON", false).description("Inventory native skill copies; preserve complete directories before retiring them").action(async (options) => {
105788
+ migrate.command("native").option("--project <directory>", "Also inventory a project and its ancestors (the current directory and its ancestors are always included)").option("--include-unmanaged", "Archive user-authored skills as well as Skills-managed copies", false).option("--include-vendor", "Retire vendor SKILL.md discovery files while preserving plugin scripts and assets", false).option("--discovery-inputs <file>", "Advanced reviewed active plugin roots and source hashes").option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Move selected skills to private archives outside agent discovery roots", false).option("--json", "Output inventory and archive receipt as JSON", false).description("Inventory native skill copies; preserve complete directories before retiring them").action(async (options) => {
105613
105789
  try {
105614
105790
  const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync37(options.discoveryInputs, "utf8")) : undefined;
105615
- const inventory = inventoryNativeSkills(undefined, { projectDir: options.project, includeVendor: options.includeVendor, configured: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases });
105791
+ const inventory = inventoryNativeSkills(undefined, { projectDirs: [process.cwd(), ...options.project ? [options.project] : []], includeVendor: options.includeVendor, configured: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases });
105616
105792
  const result2 = options.apply ? archiveNativeSkills(inventory, { includeUnmanaged: options.includeUnmanaged, includeVendor: options.includeVendor, allowRootAliases: options.allowRootAliases }) : { entries: [] };
105617
105793
  if (options.json)
105618
105794
  await writeCliOutput(JSON.stringify({ applied: options.apply, inventory, ...result2 }));
@@ -116915,11 +117091,6 @@ init_fleet_credentials();
116915
117091
  init_read_access();
116916
117092
  var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
116917
117093
  var isTTY2 = (process.stdout.isTTY ?? false) && (process.stdin.isTTY ?? false);
116918
- if (process.argv.includes("--no-color")) {
116919
- source_default.level = 0;
116920
- const idx = process.argv.indexOf("--no-color");
116921
- process.argv.splice(idx, 1);
116922
- }
116923
117094
  var program2 = new Command;
116924
117095
  program2.name("skills").description("Discover and run AI agent skills through the Skills CLI/MCP").version(package_default.version).option("--verbose", "Enable verbose logging", false).option("--profile <name>", "Use an isolated Skills instance credential profile").option("--no-color", "Disable colored output (also respects NO_COLOR env var)").enablePositionalOptions();
116925
117096
  program2.hook("preAction", () => {
@@ -117003,6 +117174,12 @@ registerStorage2(program2);
117003
117174
  var { registerRegistryReconcile: registerRegistryReconcile2 } = await Promise.resolve().then(() => (init_registry_reconcile2(), exports_registry_reconcile));
117004
117175
  registerRegistryReconcile2(program2);
117005
117176
  registerEventsCommands(program2, { source: "skills" });
117177
+ var cliArgs = process.argv.slice(2);
117178
+ var colorFlag = optionPrefix(program2, cliArgs).indices.find((index) => cliArgs[index] === "--no-color");
117179
+ if (colorFlag !== undefined) {
117180
+ source_default.level = 0;
117181
+ process.argv.splice(colorFlag + 2, 1);
117182
+ }
117006
117183
  try {
117007
117184
  await program2.parseAsync();
117008
117185
  } catch (err) {
package/bin/mcp.js CHANGED
@@ -20019,7 +20019,7 @@ var package_default;
20019
20019
  var init_package = __esm(() => {
20020
20020
  package_default = {
20021
20021
  name: "@hasna/skills",
20022
- version: "0.8.7",
20022
+ version: "0.8.9",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
@@ -47384,6 +47384,9 @@ var init_agent_discovery = __esm(() => {
47384
47384
  // src/lib/agent-adapters.ts
47385
47385
  var init_agent_adapters = () => {};
47386
47386
 
47387
+ // src/lib/agent-codex.ts
47388
+ var init_agent_codex = () => {};
47389
+
47387
47390
  // src/lib/agent-integration.ts
47388
47391
  var init_agent_integration = __esm(() => {
47389
47392
  init_config();
@@ -47391,6 +47394,7 @@ var init_agent_integration = __esm(() => {
47391
47394
  init_agent_bridge();
47392
47395
  init_agent_discovery();
47393
47396
  init_agent_adapters();
47397
+ init_agent_codex();
47394
47398
  init_agent_hermes();
47395
47399
  });
47396
47400
 
@@ -47649,6 +47653,43 @@ var init_installer = __esm(() => {
47649
47653
  };
47650
47654
  });
47651
47655
 
47656
+ // src/lib/skill-doc-file.ts
47657
+ function resolveSkillDocFile(file2) {
47658
+ switch (file2) {
47659
+ case undefined:
47660
+ case "":
47661
+ return;
47662
+ case "skill":
47663
+ return "SKILL.md";
47664
+ case "readme":
47665
+ return "README.md";
47666
+ case "claude":
47667
+ return "CLAUDE.md";
47668
+ default:
47669
+ throw new SkillDocFileError("SKILL_DOC_FILE_INVALID", "Unsupported documentation file. Choose skill, readme, or claude.");
47670
+ }
47671
+ }
47672
+ function selectSkillDoc(docs, file2) {
47673
+ const path = resolveSkillDocFile(file2);
47674
+ if (path === undefined)
47675
+ return docs.skillMd || docs.readme || docs.claudeMd || null;
47676
+ const content = path === "SKILL.md" ? docs.skillMd : path === "README.md" ? docs.readme : docs.claudeMd;
47677
+ if (content === null)
47678
+ throw new SkillDocFileError("SKILL_DOC_FILE_MISSING", `Documentation file ${path} was not found.`);
47679
+ return content;
47680
+ }
47681
+ var SkillDocFileError;
47682
+ var init_skill_doc_file = __esm(() => {
47683
+ SkillDocFileError = class SkillDocFileError extends Error {
47684
+ code;
47685
+ constructor(code, message) {
47686
+ super(message);
47687
+ this.code = code;
47688
+ this.name = "SkillDocFileError";
47689
+ }
47690
+ };
47691
+ });
47692
+
47652
47693
  // src/lib/skillinfo.ts
47653
47694
  import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
47654
47695
  import { join as join18 } from "path";
@@ -47674,11 +47715,12 @@ function getSkillDocs(name) {
47674
47715
  claudeMd: readIfExists(join18(skillPath, "CLAUDE.md"))
47675
47716
  };
47676
47717
  }
47677
- function getSkillBestDoc(name) {
47718
+ function getSkillBestDoc(name, file2) {
47719
+ resolveSkillDocFile(file2);
47678
47720
  const docs = getSkillDocs(name);
47679
47721
  if (!docs)
47680
47722
  return null;
47681
- return docs.skillMd || docs.readme || docs.claudeMd || null;
47723
+ return selectSkillDoc(docs, file2);
47682
47724
  }
47683
47725
  function getSkillRequirements(name) {
47684
47726
  const skillPath = getSkillPath(name);
@@ -47856,6 +47898,7 @@ var init_skillinfo = __esm(() => {
47856
47898
  init_installer();
47857
47899
  init_registry();
47858
47900
  init_skill_validation();
47901
+ init_skill_doc_file();
47859
47902
  HOSTED_PROVIDER_ENV_PREFIXES = [
47860
47903
  "OPENAI_",
47861
47904
  "ANTHROPIC_",
package/bin/migrate.js CHANGED
@@ -7,7 +7,7 @@ import { join as join5 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@hasna/skills",
10
- version: "0.8.7",
10
+ version: "0.8.9",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
package/bin/server.js CHANGED
@@ -83749,7 +83749,7 @@ var require_protocols9 = __commonJS((exports) => {
83749
83749
  // package.json
83750
83750
  var package_default = {
83751
83751
  name: "@hasna/skills",
83752
- version: "0.8.7",
83752
+ version: "0.8.9",
83753
83753
  description: "Skills library for AI coding agents",
83754
83754
  type: "module",
83755
83755
  bin: {
package/bin/worker.js CHANGED
@@ -76168,7 +76168,7 @@ import { randomUUID as randomUUID6 } from "crypto";
76168
76168
  // package.json
76169
76169
  var package_default = {
76170
76170
  name: "@hasna/skills",
76171
- version: "0.8.7",
76171
+ version: "0.8.9",
76172
76172
  description: "Skills library for AI coding agents",
76173
76173
  type: "module",
76174
76174
  bin: {
@@ -0,0 +1,6 @@
1
+ import type { Command } from "commander";
2
+ /** Locate a real terminator without mistaking a required option value for it. */
3
+ export declare function optionPrefix(program: Command, args: string[]): {
4
+ indices: number[];
5
+ separator: number;
6
+ };
package/dist/index.js CHANGED
@@ -11570,6 +11570,80 @@ export default async ({ directory }) => {
11570
11570
  `;
11571
11571
  }
11572
11572
 
11573
+ // src/lib/agent-codex.ts
11574
+ import { isDeepStrictEqual } from "util";
11575
+ var CODEX_SKILL_CONFIG_SECTIONS = /^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm;
11576
+ function assertCodexPathConfigEditable(text2) {
11577
+ const before = Bun.TOML.parse(text2);
11578
+ if (before.skills?.config === undefined)
11579
+ return;
11580
+ const expected = structuredClone(before);
11581
+ delete expected.skills.config;
11582
+ try {
11583
+ const withoutSections = Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
11584
+ if (isDeepStrictEqual(withoutSections, expected))
11585
+ return;
11586
+ } catch {}
11587
+ throw new Error("Codex skill path controls require ordinary [[skills.config]] TOML tables; preserve and convert inline or dotted config arrays before running skills hook install");
11588
+ }
11589
+ function disableCodexBundledSkills(text2) {
11590
+ const before = Bun.TOML.parse(text2);
11591
+ const table = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
11592
+ const refuse = () => new Error("Codex bundled skills configuration requires ordinary [skills.bundled] TOML with a boolean enabled setting; preserve and convert inline or dotted definitions before running skills hook install");
11593
+ if (before.skills !== undefined && !table(before.skills))
11594
+ throw refuse();
11595
+ if (before.skills?.bundled !== undefined && !table(before.skills.bundled))
11596
+ throw refuse();
11597
+ if (before.skills !== undefined) {
11598
+ let root = before, attempts = 0;
11599
+ for (const header of text2.matchAll(/^[ \t]*\[/gm)) {
11600
+ if (++attempts > 256)
11601
+ throw refuse();
11602
+ try {
11603
+ root = Bun.TOML.parse(text2.slice(0, header.index));
11604
+ break;
11605
+ } catch {}
11606
+ }
11607
+ if (root.skills !== undefined)
11608
+ throw refuse();
11609
+ }
11610
+ const enabled = before.skills?.bundled?.enabled;
11611
+ if (enabled !== undefined && typeof enabled !== "boolean")
11612
+ throw refuse();
11613
+ if (enabled === false)
11614
+ return text2;
11615
+ const expected = structuredClone(before);
11616
+ expected.skills ??= {};
11617
+ expected.skills.bundled ??= {};
11618
+ expected.skills.bundled.enabled = false;
11619
+ const candidates = new Set;
11620
+ const consider = (candidate) => {
11621
+ try {
11622
+ if (isDeepStrictEqual(Bun.TOML.parse(candidate), expected))
11623
+ candidates.add(candidate);
11624
+ } catch {}
11625
+ };
11626
+ const newline = text2.includes(`\r
11627
+ `) ? `\r
11628
+ ` : `
11629
+ `;
11630
+ if (before.skills?.bundled !== undefined) {
11631
+ for (const section of text2.matchAll(/^[ \t]*\[skills\.bundled\][ \t]*(?:#[^\r\n]*)?(?:\r?\n|$)(?:(?![ \t]*\[)[^\n]*(?:\n|$))*/gm)) {
11632
+ const headerEnd = section[0].indexOf(`
11633
+ `) + 1 || section[0].length;
11634
+ const replacement = enabled === true ? section[0].replace(/^([ \t]*enabled[ \t]*=[ \t]*)true([ \t]*(?:#[^\r\n]*)?)(\r?)$/m, "$1false$2$3") : section[0].slice(0, headerEnd) + (section[0].slice(0, headerEnd).endsWith(`
11635
+ `) ? "" : newline) + `enabled = false${newline}` + section[0].slice(headerEnd);
11636
+ consider(text2.slice(0, section.index) + replacement + text2.slice(section.index + section[0].length));
11637
+ }
11638
+ } else {
11639
+ consider(`${text2}${text2.endsWith(`
11640
+ `) || !text2 ? "" : newline}${newline}[skills.bundled]${newline}enabled = false${newline}`);
11641
+ }
11642
+ if (candidates.size !== 1)
11643
+ throw refuse();
11644
+ return [...candidates][0];
11645
+ }
11646
+
11573
11647
  // src/lib/agent-integration.ts
11574
11648
  var sha = (data) => createHash9("sha256").update(data).digest("hex");
11575
11649
  var ROOTS = [
@@ -11693,11 +11767,26 @@ function treeHash(root) {
11693
11767
  visit2(root);
11694
11768
  return hash2.digest("hex");
11695
11769
  }
11770
+ function projectAncestorDirectories(projects) {
11771
+ const directories = new Set;
11772
+ for (const project of projects) {
11773
+ for (let path = resolve9(project), depth = 0;; depth++) {
11774
+ if (depth >= 100)
11775
+ throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
11776
+ directories.add(path);
11777
+ const parent = dirname10(path);
11778
+ if (parent === path)
11779
+ break;
11780
+ path = parent;
11781
+ }
11782
+ }
11783
+ return [...directories];
11784
+ }
11696
11785
  function inventoryNativeSkills(home = homedir2(), options = {}) {
11697
11786
  const aliases = rootAliases(home, options.allowRootAliases);
11698
11787
  const roots = ROOTS.map(([agent, path]) => [agent, canonicalAgentPath(join17(home, path), aliases)]);
11699
11788
  const bridgePaths = Object.values(AGENT_ADAPTERS).map((adapter) => canonicalAgentPath(join17(home, adapter.root, CLI_BRIDGE_NAME), aliases));
11700
- for (const project of new Set([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []].map((path) => resolve9(path)))) {
11789
+ for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
11701
11790
  for (const [agent, path] of ROOTS)
11702
11791
  roots.push([agent, canonicalAgentPath(join17(project, path), aliases)]);
11703
11792
  }
@@ -11800,6 +11889,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
11800
11889
  if (options.includeVendor) {
11801
11890
  for (const agent of ["codex", "claude"])
11802
11891
  scanRoot(agent, canonicalAgentPath(join17(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
11892
+ scanRoot("claude", canonicalAgentPath(join17(home, ".claude", "plugins", "synced"), aliases), true);
11803
11893
  scanRoot("gemini", join17(home, ".gemini", "extensions"), true);
11804
11894
  }
11805
11895
  const configured = options.configured ? INTEGRATION_AGENTS.flatMap((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }).roots.map((path) => ({ agent, path }))) : [];
@@ -11864,6 +11954,7 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
11864
11954
  config.permissions.deny = nativeReady ? denied.filter((rule) => rule !== "Skill") : [...new Set([...denied, "Skill"])];
11865
11955
  config.permissions.allow = nativeReady ? [...new Set([...allowed, `Skill(${CLI_BRIDGE_NAME})`])] : allowed;
11866
11956
  config.disableBundledSkills = true;
11957
+ config.syncClaudeAiSkills = false;
11867
11958
  }
11868
11959
  if (agent === "gemini") {
11869
11960
  config.skills ??= {};
@@ -11894,16 +11985,21 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
11894
11985
  }
11895
11986
  }
11896
11987
  function disableCodexSkills(text2, skills, aliases, bridgePath) {
11897
- Bun.TOML.parse(text2);
11898
- let result = text2;
11988
+ const bundled = disableCodexBundledSkills(text2);
11989
+ let result = bundled;
11990
+ const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve9(bridgePath), resolve9(join17(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
11991
+ const previousConfig = Bun.TOML.parse(bundled).skills?.config;
11992
+ if (previousConfig !== undefined && (!Array.isArray(previousConfig) || previousConfig.some((entry) => !entry || typeof entry !== "object" || entry.path !== undefined && entry.name !== undefined)))
11993
+ throw new Error("Codex skill controls require one path or name selector per [[skills.config]] entry; review ambiguous entries before running skills hook install");
11994
+ const bridgeNeedsRepair = previousConfig?.some((entry) => entry.enabled === false && bridgeSelector(entry));
11899
11995
  const selections = [...skills.filter((entry) => entry.agent === "codex" && !entry.bridge).map((skill) => ({ path: skill.path, enabled: false })), { path: bridgePath, enabled: true }];
11900
11996
  for (const skill of selections) {
11901
11997
  const path = join17(skill.path, "SKILL.md");
11902
11998
  let found = false;
11903
- result = result.replace(/^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm, (section) => {
11999
+ result = result.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
11904
12000
  const parsed = Bun.TOML.parse(section);
11905
- const declared = parsed.skills?.config?.[0]?.path;
11906
- if (!declared || ![resolve9(path), resolve9(skill.path)].includes(canonicalAgentPath(declared, aliases)))
12001
+ const declared = parsed.skills?.config?.[0];
12002
+ if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [resolve9(path), resolve9(skill.path)].includes(canonicalAgentPath(declared.path, aliases))))
11907
12003
  return section;
11908
12004
  found = true;
11909
12005
  return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
@@ -11918,6 +12014,8 @@ path = ${JSON.stringify(path)}
11918
12014
  enabled = false
11919
12015
  `;
11920
12016
  }
12017
+ if (result !== bundled || bridgeNeedsRepair)
12018
+ assertCodexPathConfigEditable(bundled);
11921
12019
  Bun.TOML.parse(result);
11922
12020
  return result;
11923
12021
  }
@@ -13403,6 +13501,42 @@ async function prepareSkillDependencies(skillPath, env, timeoutMs = DEFAULT_PREP
13403
13501
  // src/lib/skillinfo.ts
13404
13502
  import { existsSync as existsSync15, readFileSync as readFileSync17 } from "fs";
13405
13503
  import { join as join23 } from "path";
13504
+
13505
+ // src/lib/skill-doc-file.ts
13506
+ class SkillDocFileError extends Error {
13507
+ code;
13508
+ constructor(code, message) {
13509
+ super(message);
13510
+ this.code = code;
13511
+ this.name = "SkillDocFileError";
13512
+ }
13513
+ }
13514
+ function resolveSkillDocFile(file) {
13515
+ switch (file) {
13516
+ case undefined:
13517
+ case "":
13518
+ return;
13519
+ case "skill":
13520
+ return "SKILL.md";
13521
+ case "readme":
13522
+ return "README.md";
13523
+ case "claude":
13524
+ return "CLAUDE.md";
13525
+ default:
13526
+ throw new SkillDocFileError("SKILL_DOC_FILE_INVALID", "Unsupported documentation file. Choose skill, readme, or claude.");
13527
+ }
13528
+ }
13529
+ function selectSkillDoc(docs, file) {
13530
+ const path = resolveSkillDocFile(file);
13531
+ if (path === undefined)
13532
+ return docs.skillMd || docs.readme || docs.claudeMd || null;
13533
+ const content = path === "SKILL.md" ? docs.skillMd : path === "README.md" ? docs.readme : docs.claudeMd;
13534
+ if (content === null)
13535
+ throw new SkillDocFileError("SKILL_DOC_FILE_MISSING", `Documentation file ${path} was not found.`);
13536
+ return content;
13537
+ }
13538
+
13539
+ // src/lib/skillinfo.ts
13406
13540
  function isInstructionSkillDir(skillPath, meta) {
13407
13541
  if (meta?.kind === "instruction")
13408
13542
  return true;
@@ -13441,11 +13575,12 @@ function getSkillDocs(name) {
13441
13575
  claudeMd: readIfExists(join23(skillPath, "CLAUDE.md"))
13442
13576
  };
13443
13577
  }
13444
- function getSkillBestDoc(name) {
13578
+ function getSkillBestDoc(name, file) {
13579
+ resolveSkillDocFile(file);
13445
13580
  const docs = getSkillDocs(name);
13446
13581
  if (!docs)
13447
13582
  return null;
13448
- return docs.skillMd || docs.readme || docs.claudeMd || null;
13583
+ return selectSkillDoc(docs, file);
13449
13584
  }
13450
13585
  function getSkillRequirements(name) {
13451
13586
  const skillPath = getSkillPath(name);
@@ -37705,7 +37840,7 @@ import { dirname as dirname14, relative as relative7 } from "path";
37705
37840
  // package.json
37706
37841
  var package_default = {
37707
37842
  name: "@hasna/skills",
37708
- version: "0.8.7",
37843
+ version: "0.8.9",
37709
37844
  description: "Skills library for AI coding agents",
37710
37845
  type: "module",
37711
37846
  bin: {
@@ -0,0 +1,10 @@
1
+ export declare const CODEX_SKILL_CONFIG_SECTIONS: RegExp;
2
+ /** The path editor supports ordinary array-of-table sections only. Removing
3
+ * those sections must remove exactly the parsed path array, with all other
4
+ * values unchanged. This also excludes header-like text inside user strings. */
5
+ export declare function assertCodexPathConfigEditable(text: string): void;
6
+ /** Preserve user TOML verbatim except for the supported bundled-skill control.
7
+ * Bun's parser accepts some illegal table reopenings, so never append a table
8
+ * over an existing inline/dotted skills definition. Refuse unfamiliar layouts
9
+ * before the integration transaction writes any files. */
10
+ export declare function disableCodexBundledSkills(text: string): string;
@@ -0,0 +1,9 @@
1
+ import type { SkillDocs } from "./skillinfo.js";
2
+ export declare class SkillDocFileError extends Error {
3
+ readonly code: "SKILL_DOC_FILE_INVALID" | "SKILL_DOC_FILE_MISSING";
4
+ constructor(code: "SKILL_DOC_FILE_INVALID" | "SKILL_DOC_FILE_MISSING", message: string);
5
+ }
6
+ /** The docs command's aliases are not arbitrary bundle paths (skills load supports those). */
7
+ export declare function resolveSkillDocFile(file?: string): "SKILL.md" | "README.md" | "CLAUDE.md" | undefined;
8
+ /** Fall back only when no file was requested, identically for text, JSON and SDK reads. */
9
+ export declare function selectSkillDoc(docs: SkillDocs, file?: string): string | null;
@@ -15,9 +15,10 @@ export interface SkillRequirements {
15
15
  */
16
16
  export declare function getSkillDocs(name: string): SkillDocs | null;
17
17
  /**
18
- * Get the best available documentation for a skill (SKILL.md > README.md > CLAUDE.md)
18
+ * Get preferred documentation, or an exact docs alias (skill, readme, claude).
19
+ * Unknown aliases and missing explicitly requested files refuse without fallback.
19
20
  */
20
- export declare function getSkillBestDoc(name: string): string | null;
21
+ export declare function getSkillBestDoc(name: string, file?: string): string | null;
21
22
  /**
22
23
  * Extract requirements from a skill's source files
23
24
  */
package/dist/sdk/index.js CHANGED
@@ -96141,7 +96141,7 @@ async function resolveExecutionGrant(input) {
96141
96141
  // package.json
96142
96142
  var package_default = {
96143
96143
  name: "@hasna/skills",
96144
- version: "0.8.7",
96144
+ version: "0.8.9",
96145
96145
  description: "Skills library for AI coding agents",
96146
96146
  type: "module",
96147
96147
  bin: {
@@ -97659,6 +97659,80 @@ export default async ({ directory }) => {
97659
97659
  `;
97660
97660
  }
97661
97661
 
97662
+ // src/lib/agent-codex.ts
97663
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
97664
+ var CODEX_SKILL_CONFIG_SECTIONS = /^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm;
97665
+ function assertCodexPathConfigEditable(text2) {
97666
+ const before = Bun.TOML.parse(text2);
97667
+ if (before.skills?.config === undefined)
97668
+ return;
97669
+ const expected = structuredClone(before);
97670
+ delete expected.skills.config;
97671
+ try {
97672
+ const withoutSections = Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
97673
+ if (isDeepStrictEqual2(withoutSections, expected))
97674
+ return;
97675
+ } catch {}
97676
+ throw new Error("Codex skill path controls require ordinary [[skills.config]] TOML tables; preserve and convert inline or dotted config arrays before running skills hook install");
97677
+ }
97678
+ function disableCodexBundledSkills(text2) {
97679
+ const before = Bun.TOML.parse(text2);
97680
+ const table = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
97681
+ const refuse2 = () => new Error("Codex bundled skills configuration requires ordinary [skills.bundled] TOML with a boolean enabled setting; preserve and convert inline or dotted definitions before running skills hook install");
97682
+ if (before.skills !== undefined && !table(before.skills))
97683
+ throw refuse2();
97684
+ if (before.skills?.bundled !== undefined && !table(before.skills.bundled))
97685
+ throw refuse2();
97686
+ if (before.skills !== undefined) {
97687
+ let root = before, attempts = 0;
97688
+ for (const header of text2.matchAll(/^[ \t]*\[/gm)) {
97689
+ if (++attempts > 256)
97690
+ throw refuse2();
97691
+ try {
97692
+ root = Bun.TOML.parse(text2.slice(0, header.index));
97693
+ break;
97694
+ } catch {}
97695
+ }
97696
+ if (root.skills !== undefined)
97697
+ throw refuse2();
97698
+ }
97699
+ const enabled = before.skills?.bundled?.enabled;
97700
+ if (enabled !== undefined && typeof enabled !== "boolean")
97701
+ throw refuse2();
97702
+ if (enabled === false)
97703
+ return text2;
97704
+ const expected = structuredClone(before);
97705
+ expected.skills ??= {};
97706
+ expected.skills.bundled ??= {};
97707
+ expected.skills.bundled.enabled = false;
97708
+ const candidates = new Set;
97709
+ const consider = (candidate) => {
97710
+ try {
97711
+ if (isDeepStrictEqual2(Bun.TOML.parse(candidate), expected))
97712
+ candidates.add(candidate);
97713
+ } catch {}
97714
+ };
97715
+ const newline = text2.includes(`\r
97716
+ `) ? `\r
97717
+ ` : `
97718
+ `;
97719
+ if (before.skills?.bundled !== undefined) {
97720
+ for (const section of text2.matchAll(/^[ \t]*\[skills\.bundled\][ \t]*(?:#[^\r\n]*)?(?:\r?\n|$)(?:(?![ \t]*\[)[^\n]*(?:\n|$))*/gm)) {
97721
+ const headerEnd = section[0].indexOf(`
97722
+ `) + 1 || section[0].length;
97723
+ const replacement = enabled === true ? section[0].replace(/^([ \t]*enabled[ \t]*=[ \t]*)true([ \t]*(?:#[^\r\n]*)?)(\r?)$/m, "$1false$2$3") : section[0].slice(0, headerEnd) + (section[0].slice(0, headerEnd).endsWith(`
97724
+ `) ? "" : newline) + `enabled = false${newline}` + section[0].slice(headerEnd);
97725
+ consider(text2.slice(0, section.index) + replacement + text2.slice(section.index + section[0].length));
97726
+ }
97727
+ } else {
97728
+ consider(`${text2}${text2.endsWith(`
97729
+ `) || !text2 ? "" : newline}${newline}[skills.bundled]${newline}enabled = false${newline}`);
97730
+ }
97731
+ if (candidates.size !== 1)
97732
+ throw refuse2();
97733
+ return [...candidates][0];
97734
+ }
97735
+
97662
97736
  // src/lib/agent-integration.ts
97663
97737
  var sha = (data) => createHash9("sha256").update(data).digest("hex");
97664
97738
  var ROOTS = [
@@ -97782,11 +97856,26 @@ function treeHash(root) {
97782
97856
  visit2(root);
97783
97857
  return hash2.digest("hex");
97784
97858
  }
97859
+ function projectAncestorDirectories(projects) {
97860
+ const directories = new Set;
97861
+ for (const project of projects) {
97862
+ for (let path = resolve8(project), depth = 0;; depth++) {
97863
+ if (depth >= 100)
97864
+ throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
97865
+ directories.add(path);
97866
+ const parent = dirname9(path);
97867
+ if (parent === path)
97868
+ break;
97869
+ path = parent;
97870
+ }
97871
+ }
97872
+ return [...directories];
97873
+ }
97785
97874
  function inventoryNativeSkills(home = homedir2(), options = {}) {
97786
97875
  const aliases = rootAliases(home, options.allowRootAliases);
97787
97876
  const roots = ROOTS.map(([agent, path]) => [agent, canonicalAgentPath(join16(home, path), aliases)]);
97788
97877
  const bridgePaths = Object.values(AGENT_ADAPTERS).map((adapter) => canonicalAgentPath(join16(home, adapter.root, CLI_BRIDGE_NAME), aliases));
97789
- for (const project of new Set([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []].map((path) => resolve8(path)))) {
97878
+ for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
97790
97879
  for (const [agent, path] of ROOTS)
97791
97880
  roots.push([agent, canonicalAgentPath(join16(project, path), aliases)]);
97792
97881
  }
@@ -97889,6 +97978,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
97889
97978
  if (options.includeVendor) {
97890
97979
  for (const agent of ["codex", "claude"])
97891
97980
  scanRoot(agent, canonicalAgentPath(join16(home, `.${agent}`, "plugins", "cache"), aliases), true, true);
97981
+ scanRoot("claude", canonicalAgentPath(join16(home, ".claude", "plugins", "synced"), aliases), true);
97892
97982
  scanRoot("gemini", join16(home, ".gemini", "extensions"), true);
97893
97983
  }
97894
97984
  const configured = options.configured ? INTEGRATION_AGENTS.flatMap((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }).roots.map((path) => ({ agent, path }))) : [];
@@ -97953,6 +98043,7 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
97953
98043
  config.permissions.deny = nativeReady ? denied.filter((rule) => rule !== "Skill") : [...new Set([...denied, "Skill"])];
97954
98044
  config.permissions.allow = nativeReady ? [...new Set([...allowed, `Skill(${CLI_BRIDGE_NAME})`])] : allowed;
97955
98045
  config.disableBundledSkills = true;
98046
+ config.syncClaudeAiSkills = false;
97956
98047
  }
97957
98048
  if (agent === "gemini") {
97958
98049
  config.skills ??= {};
@@ -97983,16 +98074,21 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
97983
98074
  }
97984
98075
  }
97985
98076
  function disableCodexSkills(text2, skills, aliases, bridgePath) {
97986
- Bun.TOML.parse(text2);
97987
- let result = text2;
98077
+ const bundled = disableCodexBundledSkills(text2);
98078
+ let result = bundled;
98079
+ const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve8(bridgePath), resolve8(join16(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
98080
+ const previousConfig = Bun.TOML.parse(bundled).skills?.config;
98081
+ if (previousConfig !== undefined && (!Array.isArray(previousConfig) || previousConfig.some((entry) => !entry || typeof entry !== "object" || entry.path !== undefined && entry.name !== undefined)))
98082
+ throw new Error("Codex skill controls require one path or name selector per [[skills.config]] entry; review ambiguous entries before running skills hook install");
98083
+ const bridgeNeedsRepair = previousConfig?.some((entry) => entry.enabled === false && bridgeSelector(entry));
97988
98084
  const selections = [...skills.filter((entry) => entry.agent === "codex" && !entry.bridge).map((skill) => ({ path: skill.path, enabled: false })), { path: bridgePath, enabled: true }];
97989
98085
  for (const skill of selections) {
97990
98086
  const path = join16(skill.path, "SKILL.md");
97991
98087
  let found = false;
97992
- result = result.replace(/^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm, (section) => {
98088
+ result = result.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
97993
98089
  const parsed = Bun.TOML.parse(section);
97994
- const declared = parsed.skills?.config?.[0]?.path;
97995
- if (!declared || ![resolve8(path), resolve8(skill.path)].includes(canonicalAgentPath(declared, aliases)))
98090
+ const declared = parsed.skills?.config?.[0];
98091
+ if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [resolve8(path), resolve8(skill.path)].includes(canonicalAgentPath(declared.path, aliases))))
97996
98092
  return section;
97997
98093
  found = true;
97998
98094
  return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
@@ -98007,6 +98103,8 @@ path = ${JSON.stringify(path)}
98007
98103
  enabled = false
98008
98104
  `;
98009
98105
  }
98106
+ if (result !== bundled || bridgeNeedsRepair)
98107
+ assertCodexPathConfigEditable(bundled);
98010
98108
  Bun.TOML.parse(result);
98011
98109
  return result;
98012
98110
  }
@@ -101453,6 +101551,40 @@ function loadProjectConfigCompat(targetDir) {
101453
101551
  return loadProjectConfig(targetDir);
101454
101552
  }
101455
101553
 
101554
+ // src/lib/skill-doc-file.ts
101555
+ class SkillDocFileError extends Error {
101556
+ code;
101557
+ constructor(code, message) {
101558
+ super(message);
101559
+ this.code = code;
101560
+ this.name = "SkillDocFileError";
101561
+ }
101562
+ }
101563
+ function resolveSkillDocFile(file) {
101564
+ switch (file) {
101565
+ case undefined:
101566
+ case "":
101567
+ return;
101568
+ case "skill":
101569
+ return "SKILL.md";
101570
+ case "readme":
101571
+ return "README.md";
101572
+ case "claude":
101573
+ return "CLAUDE.md";
101574
+ default:
101575
+ throw new SkillDocFileError("SKILL_DOC_FILE_INVALID", "Unsupported documentation file. Choose skill, readme, or claude.");
101576
+ }
101577
+ }
101578
+ function selectSkillDoc(docs, file) {
101579
+ const path = resolveSkillDocFile(file);
101580
+ if (path === undefined)
101581
+ return docs.skillMd || docs.readme || docs.claudeMd || null;
101582
+ const content = path === "SKILL.md" ? docs.skillMd : path === "README.md" ? docs.readme : docs.claudeMd;
101583
+ if (content === null)
101584
+ throw new SkillDocFileError("SKILL_DOC_FILE_MISSING", `Documentation file ${path} was not found.`);
101585
+ return content;
101586
+ }
101587
+
101456
101588
  // src/lib/skillinfo.ts
101457
101589
  function isInstructionSkillDir(skillPath, meta) {
101458
101590
  if (meta?.kind === "instruction")
@@ -101492,11 +101624,12 @@ function getSkillDocs(name) {
101492
101624
  claudeMd: readIfExists(join26(skillPath, "CLAUDE.md"))
101493
101625
  };
101494
101626
  }
101495
- function getSkillBestDoc(name) {
101627
+ function getSkillBestDoc(name, file) {
101628
+ resolveSkillDocFile(file);
101496
101629
  const docs = getSkillDocs(name);
101497
101630
  if (!docs)
101498
101631
  return null;
101499
- return docs.skillMd || docs.readme || docs.claudeMd || null;
101632
+ return selectSkillDoc(docs, file);
101500
101633
  }
101501
101634
  function getSkillRequirements(name) {
101502
101635
  const skillPath = getSkillPath(name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.8.7",
3
+ "version": "0.8.9",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {