@hasna/skills 0.8.8 → 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
@@ -194,9 +194,22 @@ Reviewed Hermes installations need fresh source and directory coverage in their
194
194
  reviewed bindings without changing existing source-only reviews.
195
195
 
196
196
  Hook installation preserves unrelated configuration, hooks, and plugin assets.
197
- It disables discovered Codex native skills; exact system-skill trees can remain
198
- only with their hash-bound disabled paths; migration preserves these package files. A client that restores or changes
199
- 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
200
213
  refused while managed CLI loading is active. Migration preserves ordinary skill
201
214
  directories in private archives; `--include-unmanaged` includes user-authored
202
215
  copies, and `--include-vendor` retires vendor `SKILL.md` discovery files while
@@ -381,6 +394,18 @@ skills grants set default --file ./reviewed-policy.json --if-match <policy-revis
381
394
  skills grants show default --revision <prior-policy-revision> --json
382
395
  ```
383
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
+
384
409
  Updating a policy appends a revision and atomically changes its current pointer.
385
410
  An empty `grants` array revokes shared execution access. Historical policies remain
386
411
  readable and can be submitted as a new reviewed revision for rollback; they cannot
@@ -619,7 +644,7 @@ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
619
644
  | `skills search <query>` | `s` | Search by name, description, or tags |
620
645
  | `skills info <name>` | | Show metadata, env vars, and system dependencies |
621
646
  | `skills show <name>` | | Show account or owned portable skill details |
622
- | `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 |
623
648
  | `skills requires <name>` | | Show env vars, system deps, and npm dependencies |
624
649
  | `skills profiles show <id>` / `skills profiles set <id> --file <json>` | | Read an exact shared selection or update it with writer authorization |
625
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.8",
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";
@@ -51446,16 +51523,21 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
51446
51523
  }
51447
51524
  }
51448
51525
  function disableCodexSkills(text2, skills, aliases, bridgePath) {
51449
- Bun.TOML.parse(text2);
51450
- 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));
51451
51533
  const selections = [...skills.filter((entry) => entry.agent === "codex" && !entry.bridge).map((skill) => ({ path: skill.path, enabled: false })), { path: bridgePath, enabled: true }];
51452
51534
  for (const skill of selections) {
51453
51535
  const path = join21(skill.path, "SKILL.md");
51454
51536
  let found = false;
51455
- result2 = result2.replace(/^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm, (section) => {
51537
+ result2 = result2.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
51456
51538
  const parsed = Bun.TOML.parse(section);
51457
- const declared = parsed.skills?.config?.[0]?.path;
51458
- 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))))
51459
51541
  return section;
51460
51542
  found = true;
51461
51543
  return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
@@ -51470,6 +51552,8 @@ path = ${JSON.stringify(path)}
51470
51552
  enabled = false
51471
51553
  `;
51472
51554
  }
51555
+ if (result2 !== bundled || bridgeNeedsRepair)
51556
+ assertCodexPathConfigEditable(bundled);
51473
51557
  Bun.TOML.parse(result2);
51474
51558
  return result2;
51475
51559
  }
@@ -51862,8 +51946,10 @@ function assertManagedAgentBridge(agent, options = {}) {
51862
51946
  }
51863
51947
  const codexPath = canonicalAgentPath(join21(home, ".codex", "config.toml"), aliases);
51864
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");
51865
51951
  const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join21(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
51866
- 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))
51867
51953
  throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
51868
51954
  const disabledBuiltin = (entry) => {
51869
51955
  if (agent !== "codex" || !entry.vendor || !entry.path.startsWith(canonicalAgentPath(join21(home, ".codex", "skills", ".system"), aliases) + sep5))
@@ -51941,6 +52027,7 @@ var init_agent_integration = __esm(() => {
51941
52027
  init_agent_bridge();
51942
52028
  init_agent_discovery();
51943
52029
  init_agent_adapters();
52030
+ init_agent_codex();
51944
52031
  init_agent_hermes();
51945
52032
  HOOK_EVENTS = ["UserPromptSubmit", "SessionStart", "SubagentStart"];
51946
52033
  ROOTS = [
@@ -73474,6 +73561,46 @@ var init_read_access = __esm(() => {
73474
73561
  init_remote_registry();
73475
73562
  });
73476
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
+
73477
73604
  // node_modules/zod/v4/classic/index.js
73478
73605
  var init_classic = __esm(() => {
73479
73606
  init_external();
@@ -76469,6 +76596,43 @@ async function prepareSkillDependencies(skillPath, env3, timeoutMs = DEFAULT_PRE
76469
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;
76470
76597
  var init_dependency_preparation = () => {};
76471
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
+
76472
76636
  // src/lib/skillinfo.ts
76473
76637
  var exports_skillinfo = {};
76474
76638
  __export(exports_skillinfo, {
@@ -76506,11 +76670,12 @@ function getSkillDocs(name) {
76506
76670
  claudeMd: readIfExists(join32(skillPath, "CLAUDE.md"))
76507
76671
  };
76508
76672
  }
76509
- function getSkillBestDoc(name) {
76673
+ function getSkillBestDoc(name, file2) {
76674
+ resolveSkillDocFile(file2);
76510
76675
  const docs = getSkillDocs(name);
76511
76676
  if (!docs)
76512
76677
  return null;
76513
- return docs.skillMd || docs.readme || docs.claudeMd || null;
76678
+ return selectSkillDoc(docs, file2);
76514
76679
  }
76515
76680
  function getSkillRequirements(name) {
76516
76681
  const skillPath = getSkillPath(name);
@@ -76830,6 +76995,7 @@ var init_skillinfo = __esm(() => {
76830
76995
  init_installer();
76831
76996
  init_registry();
76832
76997
  init_skill_validation();
76998
+ init_skill_doc_file();
76833
76999
  HOSTED_PROVIDER_ENV_PREFIXES = [
76834
77000
  "OPENAI_",
76835
77001
  "ANTHROPIC_",
@@ -78009,7 +78175,7 @@ function skillNotFound(name, similar = findSimilarSkills(name)) {
78009
78175
  console.error(source_default.dim(`Did you mean: ${similar.join(", ")}?`));
78010
78176
  }
78011
78177
  function handleReadRefusal(error61) {
78012
- if (!isSkillsFleetCredentialError(error61))
78178
+ if (!isSkillsFleetCredentialError(error61) && !(error61 instanceof SkillDocFileError))
78013
78179
  throw error61;
78014
78180
  console.error(source_default.red(error61.message));
78015
78181
  process.exitCode = 1;
@@ -78099,9 +78265,9 @@ async function resolveRemoteNotFound(name, remote, message) {
78099
78265
  }
78100
78266
  }
78101
78267
  async function handleDocs(name, options) {
78268
+ const file2 = resolveSkillDocFile(options.file);
78102
78269
  const access = await requireSkillsReadAccess();
78103
78270
  if (access.mode === "hosted") {
78104
- const file2 = options.file === "readme" ? "README.md" : options.file === "claude" ? "CLAUDE.md" : options.file ? "SKILL.md" : undefined;
78105
78271
  const result2 = await loadSelectedSkill(name, selectedProfileId(options.selectionProfile), { projectDir: process.cwd(), file: file2 });
78106
78272
  console.log(options.json ? JSON.stringify({ skill: name, ...result2 }) : result2.content);
78107
78273
  return;
@@ -78115,37 +78281,19 @@ async function handleDocs(name, options) {
78115
78281
  process.exitCode = 1;
78116
78282
  return;
78117
78283
  }
78284
+ const content = selectSkillDoc(docs, options.file);
78118
78285
  if (options.json) {
78119
78286
  console.log(JSON.stringify({
78120
78287
  skill: name,
78121
78288
  hasSkillMd: docs.skillMd !== null,
78122
78289
  hasReadme: docs.readme !== null,
78123
78290
  hasClaudeMd: docs.claudeMd !== null,
78124
- content: options.file ? docs[options.file === "skill" ? "skillMd" : options.file === "readme" ? "readme" : "claudeMd"] : docs.skillMd || docs.readme || docs.claudeMd
78291
+ content
78125
78292
  }, null, 2));
78126
78293
  return;
78127
78294
  }
78128
- let content = null;
78129
- if (options.file === "skill")
78130
- content = docs.skillMd;
78131
- else if (options.file === "readme")
78132
- content = docs.readme;
78133
- else if (options.file === "claude")
78134
- content = docs.claudeMd;
78135
- else
78136
- content = docs.skillMd || docs.readme || docs.claudeMd;
78137
- if (!content) {
78138
- const available = [];
78139
- if (docs.skillMd)
78140
- available.push("skill");
78141
- if (docs.readme)
78142
- available.push("readme");
78143
- if (docs.claudeMd)
78144
- available.push("claude");
78145
- if (!available.length)
78146
- console.log(source_default.dim(`No documentation found for '${name}'`));
78147
- else
78148
- 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}'`));
78149
78297
  return;
78150
78298
  }
78151
78299
  console.log(content);
@@ -78310,6 +78458,7 @@ var init_introspect = __esm(() => {
78310
78458
  init_fleet_credentials();
78311
78459
  init_read_access();
78312
78460
  init_skillinfo();
78461
+ init_skill_doc_file();
78313
78462
  init_installer();
78314
78463
  init_skill_validation();
78315
78464
  init_portable_skills();
@@ -81596,7 +81745,7 @@ var init_execution_grants = __esm(() => {
81596
81745
  });
81597
81746
 
81598
81747
  // src/lib/execution-grant-client.ts
81599
- import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
81748
+ import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
81600
81749
  function id(value) {
81601
81750
  if (!grantIdentifier(value))
81602
81751
  throw new Error("Invalid execution grant profile or revision identifier.");
@@ -81648,7 +81797,7 @@ async function saveExecutionGrantPolicy(profileId, grants, revision) {
81648
81797
  headers: revision === undefined ? { "If-None-Match": "*" } : { "If-Match": `"${revision}"` },
81649
81798
  body: JSON.stringify({ grants: snapshot })
81650
81799
  }));
81651
- 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)) {
81652
81801
  throw new Error("The API did not preserve the reviewed execution grant policy; read it before retrying.");
81653
81802
  }
81654
81803
  return policy;
@@ -81659,7 +81808,7 @@ async function resolveExecutionGrant(input2) {
81659
81808
  if (!grantRecord(value) || Object.keys(value).length !== 3 || !grantIdentifier(value.policyRevision) || !grantIdentifier(value.grantId) || !grantRecord(value.bindings)) {
81660
81809
  throw new SkillSelectionError("INVALID_EXECUTION_GRANT", "The Skills API returned an invalid execution grant.");
81661
81810
  }
81662
- 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)) {
81663
81812
  throw new SkillSelectionError("EXECUTION_GRANT_SCOPE_MISMATCH", "The Skills API returned a grant for a different selection or consumer.");
81664
81813
  }
81665
81814
  return value;
@@ -103289,7 +103438,12 @@ import { lstatSync as lstatSync21, mkdirSync as mkdirSync22, readFileSync as rea
103289
103438
  import { basename as basename6, isAbsolute as isAbsolute15, join as join49 } from "path";
103290
103439
  import { createInterface } from "readline";
103291
103440
  function registerRuntime(parent) {
103292
- 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
+ });
103293
103447
  const executions = parent.command("executions").description("Inspect isolated cloud executions");
103294
103448
  for (const operation of ["show", "logs", "artifacts", "cancel"]) {
103295
103449
  executions.command(operation).argument("<execution-id>").option("--json", "Output as JSON", false).action(async (id2) => {
@@ -103551,7 +103705,7 @@ function promptLine(question) {
103551
103705
  });
103552
103706
  });
103553
103707
  }
103554
- async function handleRun(name, args2, options) {
103708
+ async function handleRun(name, args2, options, separatorBeforeSkill) {
103555
103709
  let managed;
103556
103710
  try {
103557
103711
  managed = requiresCliSkillLoading();
@@ -103560,6 +103714,12 @@ async function handleRun(name, args2, options) {
103560
103714
  process.exitCode = 1;
103561
103715
  return;
103562
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);
103563
103723
  const targetIndex = args2.indexOf("--target");
103564
103724
  if (managed && !options.remote || options.target || targetIndex >= 0 || options.secretBindings || options.secretBindingsTemplate || args2.includes("--secret-bindings") || args2.includes("--secret-bindings-template")) {
103565
103725
  options = { ...options };
@@ -103585,6 +103745,7 @@ async function handleRun(name, args2, options) {
103585
103745
  }
103586
103746
  }
103587
103747
  }
103748
+ args2 = [...args2, ...childArgs];
103588
103749
  if (options.remote && options.target) {
103589
103750
  console.error("Conflicting --remote and --target execution modes");
103590
103751
  process.exitCode = 1;
@@ -116930,11 +117091,6 @@ init_fleet_credentials();
116930
117091
  init_read_access();
116931
117092
  var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
116932
117093
  var isTTY2 = (process.stdout.isTTY ?? false) && (process.stdin.isTTY ?? false);
116933
- if (process.argv.includes("--no-color")) {
116934
- source_default.level = 0;
116935
- const idx = process.argv.indexOf("--no-color");
116936
- process.argv.splice(idx, 1);
116937
- }
116938
117094
  var program2 = new Command;
116939
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();
116940
117096
  program2.hook("preAction", () => {
@@ -117018,6 +117174,12 @@ registerStorage2(program2);
117018
117174
  var { registerRegistryReconcile: registerRegistryReconcile2 } = await Promise.resolve().then(() => (init_registry_reconcile2(), exports_registry_reconcile));
117019
117175
  registerRegistryReconcile2(program2);
117020
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
+ }
117021
117183
  try {
117022
117184
  await program2.parseAsync();
117023
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.8",
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.8",
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.8",
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.8",
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 = [
@@ -11911,16 +11985,21 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
11911
11985
  }
11912
11986
  }
11913
11987
  function disableCodexSkills(text2, skills, aliases, bridgePath) {
11914
- Bun.TOML.parse(text2);
11915
- 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));
11916
11995
  const selections = [...skills.filter((entry) => entry.agent === "codex" && !entry.bridge).map((skill) => ({ path: skill.path, enabled: false })), { path: bridgePath, enabled: true }];
11917
11996
  for (const skill of selections) {
11918
11997
  const path = join17(skill.path, "SKILL.md");
11919
11998
  let found = false;
11920
- result = result.replace(/^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm, (section) => {
11999
+ result = result.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
11921
12000
  const parsed = Bun.TOML.parse(section);
11922
- const declared = parsed.skills?.config?.[0]?.path;
11923
- 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))))
11924
12003
  return section;
11925
12004
  found = true;
11926
12005
  return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
@@ -11935,6 +12014,8 @@ path = ${JSON.stringify(path)}
11935
12014
  enabled = false
11936
12015
  `;
11937
12016
  }
12017
+ if (result !== bundled || bridgeNeedsRepair)
12018
+ assertCodexPathConfigEditable(bundled);
11938
12019
  Bun.TOML.parse(result);
11939
12020
  return result;
11940
12021
  }
@@ -13420,6 +13501,42 @@ async function prepareSkillDependencies(skillPath, env, timeoutMs = DEFAULT_PREP
13420
13501
  // src/lib/skillinfo.ts
13421
13502
  import { existsSync as existsSync15, readFileSync as readFileSync17 } from "fs";
13422
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
13423
13540
  function isInstructionSkillDir(skillPath, meta) {
13424
13541
  if (meta?.kind === "instruction")
13425
13542
  return true;
@@ -13458,11 +13575,12 @@ function getSkillDocs(name) {
13458
13575
  claudeMd: readIfExists(join23(skillPath, "CLAUDE.md"))
13459
13576
  };
13460
13577
  }
13461
- function getSkillBestDoc(name) {
13578
+ function getSkillBestDoc(name, file) {
13579
+ resolveSkillDocFile(file);
13462
13580
  const docs = getSkillDocs(name);
13463
13581
  if (!docs)
13464
13582
  return null;
13465
- return docs.skillMd || docs.readme || docs.claudeMd || null;
13583
+ return selectSkillDoc(docs, file);
13466
13584
  }
13467
13585
  function getSkillRequirements(name) {
13468
13586
  const skillPath = getSkillPath(name);
@@ -37722,7 +37840,7 @@ import { dirname as dirname14, relative as relative7 } from "path";
37722
37840
  // package.json
37723
37841
  var package_default = {
37724
37842
  name: "@hasna/skills",
37725
- version: "0.8.8",
37843
+ version: "0.8.9",
37726
37844
  description: "Skills library for AI coding agents",
37727
37845
  type: "module",
37728
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.8",
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 = [
@@ -98000,16 +98074,21 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
98000
98074
  }
98001
98075
  }
98002
98076
  function disableCodexSkills(text2, skills, aliases, bridgePath) {
98003
- Bun.TOML.parse(text2);
98004
- 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));
98005
98084
  const selections = [...skills.filter((entry) => entry.agent === "codex" && !entry.bridge).map((skill) => ({ path: skill.path, enabled: false })), { path: bridgePath, enabled: true }];
98006
98085
  for (const skill of selections) {
98007
98086
  const path = join16(skill.path, "SKILL.md");
98008
98087
  let found = false;
98009
- result = result.replace(/^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm, (section) => {
98088
+ result = result.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
98010
98089
  const parsed = Bun.TOML.parse(section);
98011
- const declared = parsed.skills?.config?.[0]?.path;
98012
- 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))))
98013
98092
  return section;
98014
98093
  found = true;
98015
98094
  return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
@@ -98024,6 +98103,8 @@ path = ${JSON.stringify(path)}
98024
98103
  enabled = false
98025
98104
  `;
98026
98105
  }
98106
+ if (result !== bundled || bridgeNeedsRepair)
98107
+ assertCodexPathConfigEditable(bundled);
98027
98108
  Bun.TOML.parse(result);
98028
98109
  return result;
98029
98110
  }
@@ -101470,6 +101551,40 @@ function loadProjectConfigCompat(targetDir) {
101470
101551
  return loadProjectConfig(targetDir);
101471
101552
  }
101472
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
+
101473
101588
  // src/lib/skillinfo.ts
101474
101589
  function isInstructionSkillDir(skillPath, meta) {
101475
101590
  if (meta?.kind === "instruction")
@@ -101509,11 +101624,12 @@ function getSkillDocs(name) {
101509
101624
  claudeMd: readIfExists(join26(skillPath, "CLAUDE.md"))
101510
101625
  };
101511
101626
  }
101512
- function getSkillBestDoc(name) {
101627
+ function getSkillBestDoc(name, file) {
101628
+ resolveSkillDocFile(file);
101513
101629
  const docs = getSkillDocs(name);
101514
101630
  if (!docs)
101515
101631
  return null;
101516
- return docs.skillMd || docs.readme || docs.claudeMd || null;
101632
+ return selectSkillDoc(docs, file);
101517
101633
  }
101518
101634
  function getSkillRequirements(name) {
101519
101635
  const skillPath = getSkillPath(name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.8.8",
3
+ "version": "0.8.9",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {