@hasna/skills 0.8.10 → 0.8.11

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/dist/index.js CHANGED
@@ -9650,7 +9650,7 @@ import { homedir as homedir5 } from "os";
9650
9650
  import {
9651
9651
  cpSync as cpSync2,
9652
9652
  existsSync as existsSync12,
9653
- lstatSync as lstatSync17,
9653
+ lstatSync as lstatSync18,
9654
9654
  mkdirSync as mkdirSync8,
9655
9655
  mkdtempSync as mkdtempSync2,
9656
9656
  readFileSync as readFileSync15,
@@ -9661,12 +9661,12 @@ import {
9661
9661
  writeFileSync as writeFileSync6
9662
9662
  } from "fs";
9663
9663
  import { homedir as homedir4 } from "os";
9664
- import { basename as basename5, dirname as dirname14, join as join26 } from "path";
9664
+ import { basename as basename7, dirname as dirname15, join as join26 } from "path";
9665
9665
 
9666
9666
  // src/lib/agent-integration.ts
9667
- import { createHash as createHash12, randomUUID as randomUUID3 } from "crypto";
9668
- import { existsSync as existsSync11, lstatSync as lstatSync16, statSync as statSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync14, readdirSync as readdirSync10, opendirSync as opendirSync5, readlinkSync as readlinkSync2, realpathSync as realpathSync5, renameSync as renameSync4, rmdirSync as rmdirSync3, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, chmodSync, openSync as openSync9, closeSync as closeSync9, fsyncSync as fsyncSync2, fstatSync as fstatSync9, readSync as readSync7, constants as constants8, linkSync as linkSync2 } from "fs";
9669
- import { dirname as dirname13, join as join25, resolve as resolve12, relative as relative5, isAbsolute as isAbsolute12, sep as sep6 } from "path";
9667
+ import { createHash as createHash13, randomUUID as randomUUID3 } from "crypto";
9668
+ import { existsSync as existsSync11, lstatSync as lstatSync17, statSync as statSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync14, readdirSync as readdirSync10, opendirSync as opendirSync5, readlinkSync as readlinkSync2, realpathSync as realpathSync5, renameSync as renameSync4, rmdirSync as rmdirSync3, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, chmodSync, openSync as openSync10, closeSync as closeSync10, fsyncSync as fsyncSync3, fstatSync as fstatSync10, readSync as readSync8, constants as constants9, linkSync as linkSync2 } from "fs";
9669
+ import { dirname as dirname14, join as join25, resolve as resolve14, relative as relative5, isAbsolute as isAbsolute14, sep as sep6 } from "path";
9670
9670
  import { homedir as homedir3 } from "os";
9671
9671
 
9672
9672
  // src/lib/managed-policy.ts
@@ -9675,8 +9675,9 @@ import { join as join11 } from "path";
9675
9675
 
9676
9676
  // src/lib/selection-cache.ts
9677
9677
  import { createHash as createHash3, randomUUID } from "crypto";
9678
- import { closeSync, constants, fstatSync, lstatSync as lstatSync3, mkdirSync as mkdirSync4, openSync, readSync, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
9678
+ import { closeSync, constants, fstatSync, fsyncSync, lstatSync as lstatSync3, mkdirSync as mkdirSync4, openSync, readSync, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
9679
9679
  import { dirname as dirname4, join as join10, parse, resolve as resolve3 } from "path";
9680
+ import { isDeepStrictEqual } from "util";
9680
9681
 
9681
9682
  // src/lib/skill-bundle.ts
9682
9683
  import { createHash as createHash2 } from "crypto";
@@ -10271,7 +10272,7 @@ function selectionSnapshotsEqual(left, right) {
10271
10272
  var MAX_PROFILE_SELECTIONS = 4096;
10272
10273
  var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
10273
10274
  var MAX_SKILL_SESSION_ID_CHARS = 256;
10274
- var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 6 + 256;
10275
+ var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
10275
10276
  var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
10276
10277
  var LEGACY_PROFILE_SELECTIONS = 256;
10277
10278
  var LEGACY_PROFILE_DOCUMENT_BYTES = 1e6;
@@ -10420,12 +10421,21 @@ function writeSelectionJson(path, value) {
10420
10421
  throw new SkillSelectionError("RECEIPT_TOO_LARGE", "The Skills selection receipt exceeds its size limit.");
10421
10422
  atomicWrite(path, bytes, 384);
10422
10423
  }
10423
- function atomicWrite(path, bytes, mode) {
10424
+ function atomicWrite(path, bytes, mode, durable = false) {
10424
10425
  assertRegularPath(path, true);
10425
10426
  const temporary = join10(dirname4(path), `.selection-${randomUUID()}.tmp`);
10426
10427
  try {
10427
- writeFileSync4(temporary, bytes, { flag: "wx", mode });
10428
+ const fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, mode);
10429
+ try {
10430
+ writeFileSync4(fd, bytes);
10431
+ if (durable)
10432
+ fsyncSync(fd);
10433
+ } finally {
10434
+ closeSync(fd);
10435
+ }
10428
10436
  renameSync2(temporary, path);
10437
+ if (durable)
10438
+ syncDirectory(dirname4(path));
10429
10439
  } finally {
10430
10440
  try {
10431
10441
  unlinkSync(temporary);
@@ -10544,19 +10554,194 @@ function sessionReceiptPath(sessionId, options = {}) {
10544
10554
  throw new SkillSelectionError("INVALID_SESSION", `A Skills session id must contain 1\u2013${MAX_SKILL_SESSION_ID_CHARS} characters.`);
10545
10555
  return join10(selectionCacheRoot(options), "sessions", `${hash(sessionId)}.json`);
10546
10556
  }
10547
- function readSkillSession(sessionId, options = {}) {
10548
- const receipt = readSelectionJson(sessionReceiptPath(sessionId, options));
10549
- if (receipt) {
10550
- validateSelectionReceipt(receipt);
10551
- const keys = new Set(receipt.profile.selections.map(selectionKey));
10552
- if (receipt.sessionId !== sessionId || !Array.isArray(receipt.loaded) || receipt.loaded.length > keys.size || new Set(receipt.loaded).size !== receipt.loaded.length || !receipt.loaded.every((key) => keys.has(key))) {
10553
- throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt is invalid.");
10557
+ function skillSessionGeneration(receipt) {
10558
+ return receipt.generation ?? 0;
10559
+ }
10560
+ function validateSkillSessionBinding(binding) {
10561
+ if (!binding || typeof binding.sessionId !== "string" || !binding.sessionId.trim() || binding.sessionId.length > MAX_SKILL_SESSION_ID_CHARS || !Number.isSafeInteger(binding.generation) || binding.generation < 0 || !/^[a-f0-9]{64}$/.test(binding.receiptSha256)) {
10562
+ throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt contains an invalid parent snapshot binding.");
10563
+ }
10564
+ }
10565
+ function validateSkillSessionReceipt(receipt, sessionId) {
10566
+ validateSelectionReceipt(receipt);
10567
+ const keys = new Set(receipt.profile.selections.map(selectionKey));
10568
+ if (receipt.sessionId !== sessionId || !Array.isArray(receipt.loaded) || receipt.loaded.length > keys.size || new Set(receipt.loaded).size !== receipt.loaded.length || !receipt.loaded.every((key) => keys.has(key)) || receipt.generation !== undefined && (!Number.isSafeInteger(receipt.generation) || receipt.generation < 1)) {
10569
+ throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt is invalid.");
10570
+ }
10571
+ if (receipt.parent) {
10572
+ validateSkillSessionBinding(receipt.parent);
10573
+ if (receipt.parent.sessionId === sessionId)
10574
+ throw new SkillSelectionError("INVALID_RECEIPT", "A Skills child session cannot name itself as its parent.");
10575
+ }
10576
+ }
10577
+ function readSkillSessionSnapshotIfExists(sessionId, options = {}) {
10578
+ const path = sessionReceiptPath(sessionId, options), bytes = readRegularFile(path, MAX_PROFILE_DOCUMENT_BYTES);
10579
+ if (!bytes)
10580
+ return null;
10581
+ let receipt;
10582
+ try {
10583
+ receipt = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
10584
+ } catch {
10585
+ throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt is unreadable.");
10586
+ }
10587
+ validateSkillSessionReceipt(receipt, sessionId);
10588
+ const sha256 = sha256Hex(bytes);
10589
+ return { path, bytes, receipt, sha256, sessionId, generation: skillSessionGeneration(receipt), receiptSha256: sha256 };
10590
+ }
10591
+ function readSkillSessionSnapshot(sessionId, options = {}) {
10592
+ const snapshot = readSkillSessionSnapshotIfExists(sessionId, options);
10593
+ if (!snapshot)
10594
+ throw new SkillSelectionError("SESSION_NOT_FOUND", "The requested Skills session receipt does not exist.");
10595
+ return snapshot;
10596
+ }
10597
+ function skillSessionSnapshotBinding(snapshot) {
10598
+ return { sessionId: snapshot.sessionId, generation: snapshot.generation, receiptSha256: snapshot.receiptSha256 };
10599
+ }
10600
+ function withSessionWriteLocks(sessionIds, options, action) {
10601
+ const operationId = randomUUID();
10602
+ const locks = [...new Set(sessionIds)].map((sessionId) => ({ sessionId, path: `${sessionReceiptPath(sessionId, options)}.write-lock` })).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
10603
+ const owned = [];
10604
+ const assertOwned = () => {
10605
+ for (const lock of owned) {
10606
+ const current = lstatSync3(lock.path, { throwIfNoEntry: false });
10607
+ if (!current?.isFile() || current.dev !== lock.dev || current.ino !== lock.ino) {
10608
+ throw new SkillSelectionError("SESSION_WRITE_LOCK_CHANGED", "A session write lock changed during the operation; no replacement is permitted.");
10609
+ }
10610
+ }
10611
+ };
10612
+ try {
10613
+ for (const lock of locks) {
10614
+ assertRegularPath(lock.path, true);
10615
+ let fd;
10616
+ try {
10617
+ fd = openSync(lock.path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
10618
+ } catch (error) {
10619
+ if (error.code === "EEXIST")
10620
+ throw new SkillSelectionError("SESSION_WRITE_LOCKED", "Another Skills process owns a required session write lock; retry after it completes. A surviving lock requires explicit recovery review.");
10621
+ throw error;
10622
+ }
10623
+ const stat = fstatSync(fd);
10624
+ owned.push({ path: lock.path, fd, dev: stat.dev, ino: stat.ino });
10625
+ writeFileSync4(fd, `${JSON.stringify({ schemaVersion: 1, operationId, sessionId: lock.sessionId, pid: process.pid })}
10626
+ `);
10554
10627
  }
10628
+ return action(assertOwned);
10629
+ } finally {
10630
+ for (const lock of owned.reverse()) {
10631
+ closeSync(lock.fd);
10632
+ const current = lstatSync3(lock.path, { throwIfNoEntry: false });
10633
+ if (current?.isFile() && current.dev === lock.dev && current.ino === lock.ino)
10634
+ unlinkSync(lock.path);
10635
+ }
10636
+ }
10637
+ }
10638
+ function withSessionWriteLock(sessionId, options, action) {
10639
+ return withSessionWriteLocks([sessionId], options, action);
10640
+ }
10641
+ function sameSnapshot(actual, expected) {
10642
+ return actual === null ? expected === null : expected !== null && actual.sessionId === expected.sessionId && actual.generation === expected.generation && actual.receiptSha256 === expected.receiptSha256;
10643
+ }
10644
+ function nextSkillSessionGeneration(generation) {
10645
+ if (!Number.isSafeInteger(generation) || generation < 0 || generation >= Number.MAX_SAFE_INTEGER) {
10646
+ throw new SkillSelectionError("SESSION_GENERATION_EXHAUSTED", "The Skills session generation cannot advance safely; preserve the receipt and review recovery before continuing.");
10647
+ }
10648
+ return generation + 1;
10649
+ }
10650
+ function parentChanged() {
10651
+ throw new SkillSelectionError("SESSION_PARENT_CHANGED", "The parent Skills session changed while the child was resolving; resolve the child again from the current parent pin.");
10652
+ }
10653
+ function writeSkillSession(receipt, expected, options = {}) {
10654
+ validateSkillSessionReceipt(receipt, receipt.sessionId);
10655
+ if (expected.parent)
10656
+ validateSkillSessionBinding(expected.parent);
10657
+ if (expected.parent?.sessionId === receipt.sessionId)
10658
+ throw new SkillSelectionError("INVALID_RECEIPT", "A Skills child session cannot name itself as its parent.");
10659
+ const lockIds = expected.parent ? [receipt.sessionId, expected.parent.sessionId] : [receipt.sessionId];
10660
+ withSessionWriteLocks(lockIds, options, (assertOwned) => {
10661
+ const current = readSkillSessionSnapshotIfExists(receipt.sessionId, options);
10662
+ if (!sameSnapshot(current, expected.current) || current && !isDeepStrictEqual(current.receipt.profile, receipt.profile)) {
10663
+ throw new SkillSelectionError("SESSION_RECEIPT_CHANGED", "The Skills session receipt changed while context was loading; resolve context again without replacing the current pin.");
10664
+ }
10665
+ let parent = null;
10666
+ if (expected.parent) {
10667
+ parent = readSkillSessionSnapshotIfExists(expected.parent.sessionId, options);
10668
+ if (!sameSnapshot(parent, expected.parent) || !parent || !isDeepStrictEqual(parent.receipt.profile, receipt.profile))
10669
+ parentChanged();
10670
+ }
10671
+ const generation = nextSkillSessionGeneration(current?.generation ?? 0);
10672
+ const next = current ? { ...current.receipt, generation, loaded: [...new Set([...current.receipt.loaded, ...receipt.loaded])] } : { ...receipt, generation, ...expected.parent ? { parent: expected.parent } : {} };
10673
+ assertOwned();
10674
+ if (expected.parent) {
10675
+ parent = readSkillSessionSnapshotIfExists(expected.parent.sessionId, options);
10676
+ if (!sameSnapshot(parent, expected.parent))
10677
+ parentChanged();
10678
+ }
10679
+ writeSelectionJson(sessionReceiptPath(receipt.sessionId, options), next);
10680
+ });
10681
+ }
10682
+ function syncDirectory(path) {
10683
+ assertRegularPath(path);
10684
+ const fd = openSync(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
10685
+ try {
10686
+ fsyncSync(fd);
10687
+ } finally {
10688
+ closeSync(fd);
10689
+ }
10690
+ }
10691
+ function archiveBytes(path, bytes) {
10692
+ assertRegularPath(path, true);
10693
+ const fd = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
10694
+ try {
10695
+ writeFileSync4(fd, bytes);
10696
+ fsyncSync(fd);
10697
+ } finally {
10698
+ closeSync(fd);
10555
10699
  }
10556
- return receipt;
10700
+ }
10701
+ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCommit = () => {}) {
10702
+ validateSkillSessionReceipt(replacement, replacement.sessionId);
10703
+ const bytes = Buffer.from(`${JSON.stringify(replacement)}
10704
+ `);
10705
+ if (bytes.length > MAX_PROFILE_DOCUMENT_BYTES)
10706
+ throw new SkillSelectionError("RECEIPT_TOO_LARGE", "The Skills selection receipt exceeds its size limit.");
10707
+ return withSessionWriteLock(replacement.sessionId, options, (assertOwned) => {
10708
+ const before = readSkillSessionSnapshot(replacement.sessionId, options);
10709
+ if (!sameSnapshot(before, expected))
10710
+ throw new SkillSelectionError("SESSION_RECEIPT_CHANGED", "The Skills session receipt generation or bytes changed after review; inspect it and prepare a new plan.");
10711
+ if (replacement.generation !== nextSkillSessionGeneration(before.generation))
10712
+ throw new SkillSelectionError("SESSION_GENERATION_CHANGED", "The replacement Skills session must advance exactly one generation.");
10713
+ if (before.receipt.profile.authority !== replacement.profile.authority || before.receipt.profile.workspaceId !== replacement.profile.workspaceId) {
10714
+ throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "A session reconciliation cannot change its Skills authority or workspace.");
10715
+ }
10716
+ const directory = join10(selectionCacheRoot(options), "session-reconciliations", randomUUID());
10717
+ const archivePath = join10(directory, "original.json"), replacementPath = join10(directory, "replacement.json"), receiptPath = join10(directory, "receipt.json");
10718
+ const afterSha256 = sha256Hex(bytes);
10719
+ archiveBytes(archivePath, before.bytes);
10720
+ archiveBytes(replacementPath, bytes);
10721
+ const receipt = { schemaVersion: 1, status: "prepared", sessionId: replacement.sessionId, beforeGeneration: before.generation, afterGeneration: replacement.generation, beforeSha256: before.sha256, afterSha256, archivePath, replacementPath, plan };
10722
+ archiveBytes(receiptPath, Buffer.from(`${JSON.stringify(receipt)}
10723
+ `));
10724
+ syncDirectory(directory);
10725
+ syncDirectory(dirname4(directory));
10726
+ syncDirectory(selectionCacheRoot(options));
10727
+ if (sha256Hex(readRegularFile(archivePath, MAX_PROFILE_DOCUMENT_BYTES)) !== before.sha256 || !sameSnapshot(readSkillSessionSnapshot(replacement.sessionId, options), expected)) {
10728
+ throw new SkillSelectionError("SESSION_RECEIPT_CHANGED", "The session or its preservation archive changed before replacement; retained evidence must be reviewed.");
10729
+ }
10730
+ beforeCommit();
10731
+ assertOwned();
10732
+ try {
10733
+ atomicWrite(before.path, bytes, 384, true);
10734
+ atomicWrite(receiptPath, Buffer.from(`${JSON.stringify({ ...receipt, status: "applied" })}
10735
+ `), 384, true);
10736
+ } catch {
10737
+ throw new SkillSelectionError("SESSION_RECONCILIATION_INCOMPLETE", `Inspect sessions show and the preserved operation receipt at ${receiptPath} before any retry; replacement may already have committed.`);
10738
+ }
10739
+ return { archivePath, receiptPath, beforeSha256: before.sha256, afterSha256 };
10740
+ });
10557
10741
  }
10558
10742
 
10559
10743
  // src/lib/agent-policy-limits.ts
10744
+ import { basename as basename3, isAbsolute as isAbsolute3, resolve as resolve4 } from "path";
10560
10745
  var AGENT_POLICY_LIMITS = Object.freeze({ bytes: 1024 * 1024, agents: 16, discoverySources: 2048, discoveryRawSourceBytes: 64 * 1024 * 1024, discoveryRawTotalBytes: 256 * 1024 * 1024, discoveryPathLinks: 40, discoveryPathSteps: 256, discoveryPathSourceMetadataBytes: 64 * 1024, discoveryPathTotalMetadataBytes: 8 * 1024 * 1024, discoveryRoots: 512, discoveryDirectories: 64, discoveryDirectoryEntries: 20000, discoveryDirectoryBytes: 8 * 1024 * 1024, builtinNames: 2048, rootAliases: 2, fields: 64, pathCharacters: 4096 });
10561
10746
  function object(value) {
10562
10747
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -10622,7 +10807,9 @@ function assertAgentPolicyCollections(policy) {
10622
10807
  text(source.path);
10623
10808
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
10624
10809
  if (source.hashMode !== undefined)
10625
- requireBound(["bytes", "path-bytes", "claude-plugin-registry"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
10810
+ requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
10811
+ if (source.hashMode === "claude-marketplace-registry")
10812
+ requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve4(source.path) === source.path && basename3(source.path) === "known_marketplaces.json");
10626
10813
  if (source.hashMode === "claude-plugin-registry") {
10627
10814
  requireBound(agent === "claude");
10628
10815
  const managed = array(source.managedPlugins, 64);
@@ -10774,9 +10961,9 @@ function isOwnedCliBridge(path, expectedPaths) {
10774
10961
  }
10775
10962
 
10776
10963
  // src/lib/agent-discovery.ts
10777
- import { createHash as createHash11 } from "crypto";
10778
- import { lstatSync as lstatSync15, readFileSync as readFileSync13, readdirSync as readdirSync9, realpathSync as realpathSync4 } from "fs";
10779
- import { dirname as dirname12, isAbsolute as isAbsolute11, join as join23, resolve as resolve11, sep as sep5 } from "path";
10964
+ import { createHash as createHash12 } from "crypto";
10965
+ import { lstatSync as lstatSync16, readFileSync as readFileSync13, readdirSync as readdirSync9, realpathSync as realpathSync4 } from "fs";
10966
+ import { dirname as dirname13, isAbsolute as isAbsolute13, join as join23, resolve as resolve13, sep as sep5 } from "path";
10780
10967
 
10781
10968
  // node_modules/yaml/dist/index.js
10782
10969
  var composer = require_composer();
@@ -10825,7 +11012,7 @@ var $visit = visit.visit;
10825
11012
  var $visitAsync = visit.visitAsync;
10826
11013
 
10827
11014
  // src/lib/agent-hermes.ts
10828
- import { dirname as dirname5, isAbsolute as isAbsolute3, join as join13, resolve as resolve4 } from "path";
11015
+ import { dirname as dirname5, isAbsolute as isAbsolute4, join as join13, resolve as resolve5 } from "path";
10829
11016
  import { lstatSync as lstatSync6, readFileSync as readFileSync9, opendirSync } from "fs";
10830
11017
  var HERMES_OPT_OUT = ".no-bundled-skills";
10831
11018
  var EVENTS = ["pre_llm_call", "pre_tool_call"];
@@ -10863,7 +11050,7 @@ function quoted(value) {
10863
11050
  return `'${value.replace(/'/g, `'\\''`)}'`;
10864
11051
  }
10865
11052
  function hermesHookDefinitions(supervisor) {
10866
- if (!isAbsolute3(supervisor.runtime) || !isAbsolute3(supervisor.path))
11053
+ if (!isAbsolute4(supervisor.runtime) || !isAbsolute4(supervisor.path))
10867
11054
  throw new Error("Expected absolute Hermes supervisor paths");
10868
11055
  const entry = (event) => ({ command: `${quoted(supervisor.runtime)} ${quoted(supervisor.path)} --event ${event}`, timeout: 15, ...event === "pre_tool_call" ? { fail_closed: true } : {} });
10869
11056
  return { pre_llm_call: entry("pre_llm_call"), pre_tool_call: entry("pre_tool_call") };
@@ -10899,14 +11086,14 @@ function assertHermesEnvironment(home) {
10899
11086
  throw new Error(`NATIVE_SKILL_DRIFT: Hermes ${variable} requires a dedicated discovery adapter; unset it before using this adapter`);
10900
11087
  }
10901
11088
  const selected = process.env.HERMES_HOME?.trim();
10902
- if (selected && (!isAbsolute3(selected) || resolve4(selected) !== join13(resolve4(home), ".hermes")))
11089
+ if (selected && (!isAbsolute4(selected) || resolve5(selected) !== join13(resolve5(home), ".hermes")))
10903
11090
  throw new Error("NATIVE_SKILL_DRIFT: custom Hermes homes/profiles require their own reviewed bridge");
10904
11091
  if (process.env.HERMES_ENABLE_PROJECT_PLUGINS?.trim() && !["0", "false", "no", "off"].includes(process.env.HERMES_ENABLE_PROJECT_PLUGINS.trim().toLowerCase()))
10905
11092
  throw new Error("NATIVE_SKILL_DRIFT: Hermes project plugins require a dedicated discovery review");
10906
11093
  }
10907
11094
  function assertNoHermesLegacyShadow(root) {
10908
11095
  let entries = 0;
10909
- for (let cursor = resolve4(root);; cursor = dirname5(cursor)) {
11096
+ for (let cursor = resolve5(root);; cursor = dirname5(cursor)) {
10910
11097
  if (lstatSync6(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
10911
11098
  throw new Error("NATIVE_SKILL_DRIFT: symlink Hermes legacy discovery root");
10912
11099
  if (dirname5(cursor) === cursor)
@@ -10992,13 +11179,13 @@ if (failed) process.exitCode = 2;
10992
11179
  // src/lib/agent-discovery-directories.ts
10993
11180
  import { createHash as createHash5 } from "crypto";
10994
11181
  import { lstatSync as lstatSync7, opendirSync as opendirSync2 } from "fs";
10995
- import { dirname as dirname6, isAbsolute as isAbsolute4, join as join14, resolve as resolve5 } from "path";
11182
+ import { dirname as dirname6, isAbsolute as isAbsolute5, join as join14, resolve as resolve6 } from "path";
10996
11183
  function need(value, message) {
10997
11184
  if (!value)
10998
11185
  throw new Error(message);
10999
11186
  }
11000
11187
  function safe(path) {
11001
- need(typeof path === "string" && path.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute4(path) && resolve5(path) === path && !path.includes("\x00"), "Expected a canonical absolute discovery directory path");
11188
+ need(typeof path === "string" && path.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute5(path) && resolve6(path) === path && !path.includes("\x00"), "Expected a canonical absolute discovery directory path");
11002
11189
  for (let cursor = path;; cursor = dirname6(cursor)) {
11003
11190
  need(!lstatSync7(cursor, { throwIfNoEntry: false })?.isSymbolicLink(), "Refusing symlink native discovery directory");
11004
11191
  if (dirname6(cursor) === cursor)
@@ -11063,12 +11250,12 @@ function verifyDiscoveryDirectories(directories) {
11063
11250
  // src/lib/agent-discovery-bytes.ts
11064
11251
  import { createHash as createHash6 } from "crypto";
11065
11252
  import { closeSync as closeSync3, constants as constants3, fstatSync as fstatSync3, lstatSync as lstatSync8, openSync as openSync3, readSync as readSync3 } from "fs";
11066
- import { dirname as dirname7, isAbsolute as isAbsolute5, resolve as resolve6 } from "path";
11253
+ import { dirname as dirname7, isAbsolute as isAbsolute6, resolve as resolve7 } from "path";
11067
11254
  function discoveryByteBudget() {
11068
11255
  return { remaining: AGENT_POLICY_LIMITS.discoveryRawTotalBytes };
11069
11256
  }
11070
11257
  function safe2(path) {
11071
- if (typeof path !== "string" || !isAbsolute5(path) || path !== resolve6(path) || path.length > AGENT_POLICY_LIMITS.pathCharacters || path.includes("\x00"))
11258
+ if (typeof path !== "string" || !isAbsolute6(path) || path !== resolve7(path) || path.length > AGENT_POLICY_LIMITS.pathCharacters || path.includes("\x00"))
11072
11259
  throw new Error("Expected a canonical absolute raw discovery path");
11073
11260
  for (let cursor = path;; cursor = dirname7(cursor)) {
11074
11261
  if (lstatSync8(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
@@ -11131,7 +11318,7 @@ function hashRawDiscoveryFile(path, budget, changes) {
11131
11318
  // src/lib/agent-discovery-path-bytes.ts
11132
11319
  import { createHash as createHash7 } from "crypto";
11133
11320
  import { lstatSync as lstatSync9, readlinkSync } from "fs";
11134
- import { dirname as dirname8, isAbsolute as isAbsolute6, join as join15, parse as parse2, resolve as resolve7, sep as sep3 } from "path";
11321
+ import { dirname as dirname8, isAbsolute as isAbsolute7, join as join15, parse as parse2, resolve as resolve8, sep as sep3 } from "path";
11135
11322
  function identity2(stat, mutable = false) {
11136
11323
  return {
11137
11324
  dev: String(stat.dev),
@@ -11143,7 +11330,7 @@ function identity2(stat, mutable = false) {
11143
11330
  };
11144
11331
  }
11145
11332
  function canonical(path) {
11146
- if (typeof path !== "string" || !isAbsolute6(path) || path !== resolve7(path) || path.length > AGENT_POLICY_LIMITS.pathCharacters || path.includes("\x00") || Buffer.from(path).toString("utf8") !== path)
11333
+ if (typeof path !== "string" || !isAbsolute7(path) || path !== resolve8(path) || path.length > AGENT_POLICY_LIMITS.pathCharacters || path.includes("\x00") || Buffer.from(path).toString("utf8") !== path)
11147
11334
  throw new Error("Expected a canonical absolute discovery path");
11148
11335
  }
11149
11336
  function trace(path) {
@@ -11192,11 +11379,11 @@ function trace(path) {
11192
11379
  if (!target || target.includes("\x00") || Buffer.byteLength(target) > AGENT_POLICY_LIMITS.pathCharacters || !after.isSymbolicLink() || JSON.stringify(identity2(stat, true)) !== JSON.stringify(identity2(after, true)))
11193
11380
  throw new Error("Discovery link changed during read");
11194
11381
  record2(candidate, { type: "link", target, ...identity2(stat, true) });
11195
- if (isAbsolute6(target)) {
11382
+ if (isAbsolute7(target)) {
11196
11383
  cursor = parse2(target).root;
11197
11384
  root();
11198
11385
  }
11199
- const remainder = isAbsolute6(target) ? target.slice(parse2(target).root.length) : target;
11386
+ const remainder = isAbsolute7(target) ? target.slice(parse2(target).root.length) : target;
11200
11387
  pending = [...remainder.split(sep3), ...pending];
11201
11388
  if (pending.length + steps > AGENT_POLICY_LIMITS.discoveryPathSteps)
11202
11389
  throw new Error("Discovery path step limit exceeded");
@@ -11244,15 +11431,15 @@ function hashDiscoveryPathFile(path, budget, changes) {
11244
11431
 
11245
11432
  // src/lib/plugin-discovery.ts
11246
11433
  import { closeSync as closeSync8, constants as constants7, fstatSync as fstatSync8, lstatSync as lstatSync14, openSync as openSync8, opendirSync as opendirSync4, readSync as readSync6 } from "fs";
11247
- import { basename as basename4, dirname as dirname11, isAbsolute as isAbsolute10, join as join21, resolve as resolve10 } from "path";
11434
+ import { basename as basename5, dirname as dirname11, isAbsolute as isAbsolute11, join as join21, resolve as resolve11 } from "path";
11248
11435
 
11249
11436
  // src/lib/plugin-admission.ts
11250
11437
  import { homedir as homedir2 } from "os";
11251
- import { isAbsolute as isAbsolute9, join as join20, resolve as resolve9 } from "path";
11438
+ import { isAbsolute as isAbsolute10, join as join20, resolve as resolve10 } from "path";
11252
11439
  // package.json
11253
11440
  var package_default = {
11254
11441
  name: "@hasna/skills",
11255
- version: "0.8.10",
11442
+ version: "0.8.11",
11256
11443
  description: "Skills library for AI coding agents",
11257
11444
  type: "module",
11258
11445
  bin: {
@@ -11314,10 +11501,11 @@ var package_default = {
11314
11501
  typecheck: "tsc --noEmit",
11315
11502
  "verify:release": "bun run scripts/release-guard.ts",
11316
11503
  "verify:consumer-types": "bun run scripts/consumer-types.ts",
11317
- prepare: "bun run build:js",
11318
- prepack: "bun run build && bun run verify:release && bun run verify:consumer-types",
11504
+ prepack: "bun run verify:generated && bun run verify:release && bun run verify:consumer-types",
11319
11505
  prepublishOnly: "bun run verify:producer && bun run typecheck && bun run test",
11320
- "verify:producer": "bun run scripts/release-dependencies.ts"
11506
+ "verify:producer": "bun run scripts/release-dependencies.ts",
11507
+ "verify:generated": "HASNA_SKILLS_RELEASE_ARTIFACT_TEST=1 bun test src/lib/release-artifacts.test.ts",
11508
+ "verify:consumer-credentials": "bun run scripts/consumer-credentials.ts"
11321
11509
  },
11322
11510
  keywords: [
11323
11511
  "skills",
@@ -11339,7 +11527,6 @@ var package_default = {
11339
11527
  license: "Apache-2.0",
11340
11528
  devDependencies: {
11341
11529
  "@hasna/contracts": "1.1.0",
11342
- "@hasna/secrets": "0.4.2",
11343
11530
  "@types/bun": "1.3.14",
11344
11531
  "@types/node": "25.2.3",
11345
11532
  "@types/react": "^18.2.0",
@@ -11352,6 +11539,7 @@ var package_default = {
11352
11539
  "@aws-sdk/client-ecs": "^3.1079.0",
11353
11540
  "@aws-sdk/client-s3": "^3.1079.0",
11354
11541
  "@hasna/events": "0.1.18",
11542
+ "@hasna/secrets": "0.4.2",
11355
11543
  "@modelcontextprotocol/sdk": "^1.26.0",
11356
11544
  chalk: "^5.3.0",
11357
11545
  commander: "^12.1.0",
@@ -11386,12 +11574,12 @@ import { spawnSync } from "child_process";
11386
11574
  import { closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readFileSync as readFileSync22 } from "fs";
11387
11575
  import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
11388
11576
  import { hostname as osHostname } from "os";
11389
- import { isAbsolute as isAbsolute7, join as join22 } from "path";
11577
+ import { isAbsolute as isAbsolute8, join as join22 } from "path";
11390
11578
  import { createRequire } from "module";
11391
11579
  import { fileURLToPath as fileURLToPath2 } from "url";
11392
11580
  import { closeSync as closeSync4, fstatSync as fstatSync4, lstatSync as lstatSync10, openSync as openSync4, readFileSync as readFileSync10, realpathSync as realpathSync3, statSync as statSync7 } from "fs";
11393
11581
  import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
11394
- import { basename as basename3, dirname as dirname9, join as join16, relative as relative4, sep as sep4 } from "path";
11582
+ import { basename as basename4, dirname as dirname9, join as join16, relative as relative4, sep as sep4 } from "path";
11395
11583
  import { fileURLToPath, pathToFileURL } from "url";
11396
11584
  function envToken(name) {
11397
11585
  return name.toUpperCase().replace(/-/g, "_");
@@ -11499,7 +11687,7 @@ function entryPoint(directory) {
11499
11687
  function resolveInstalledSecrets(parentUrl) {
11500
11688
  let directory = dirname9(fileURLToPath(parentUrl));
11501
11689
  for (;; ) {
11502
- if (basename3(directory) !== "node_modules") {
11690
+ if (basename4(directory) !== "node_modules") {
11503
11691
  const candidate = join16(directory, "node_modules", PACKAGE);
11504
11692
  let absent = false;
11505
11693
  try {
@@ -11559,7 +11747,7 @@ function homeDir(env) {
11559
11747
  }
11560
11748
  function absoluteOverride(env, key) {
11561
11749
  const value = env[key]?.trim();
11562
- return value && isAbsolute7(value) ? value : null;
11750
+ return value && isAbsolute8(value) ? value : null;
11563
11751
  }
11564
11752
  function hasnaHomeDir(env) {
11565
11753
  const override = absoluteOverride(env, HASNA_HOME_ENV_KEY);
@@ -12338,10 +12526,16 @@ function isCredentialResolutionError(error) {
12338
12526
  function isSkillsFleetCredentialError(error) {
12339
12527
  return error instanceof SkillsFleetCredentialError || typeof error === "object" && error !== null && error.name === "SkillsFleetCredentialError";
12340
12528
  }
12341
- function asSkillsFleetCredentialError(error) {
12529
+ var REDACTED_VAULT_REFERENCE = "[redacted vault reference]";
12530
+ function redactVaultReference(message, vaultReference) {
12531
+ if (!vaultReference)
12532
+ return message;
12533
+ return message.split(vaultReference).join(REDACTED_VAULT_REFERENCE);
12534
+ }
12535
+ function asSkillsFleetCredentialError(error, vaultReference) {
12342
12536
  if (!isCredentialResolutionError(error))
12343
12537
  return null;
12344
- return new SkillsFleetCredentialError(error.message, "MISSING_API_CREDENTIAL");
12538
+ return new SkillsFleetCredentialError(redactVaultReference(error.message, vaultReference), "MISSING_API_CREDENTIAL");
12345
12539
  }
12346
12540
  function normalizeSkillsApiOrigin(apiUrl) {
12347
12541
  const url = new URL(apiUrl);
@@ -12526,7 +12720,7 @@ async function resolveSkillsConnection(env = process.env, options = {}) {
12526
12720
  try {
12527
12721
  completed = await completePointerCredential(SKILLS_APP, pointer, env);
12528
12722
  } catch (error) {
12529
- const translated = asSkillsFleetCredentialError(error);
12723
+ const translated = asSkillsFleetCredentialError(error, pointer.pointerVaultKey);
12530
12724
  if (translated)
12531
12725
  throw translated;
12532
12726
  throw error;
@@ -13407,11 +13601,18 @@ async function syncSelectionProfile(profileId, options = {}) {
13407
13601
  return { profile, receipt, changed, downloaded, stationReported };
13408
13602
  }
13409
13603
  async function resolveSelectionContext(profileId, options = {}) {
13410
- const session = options.sessionId ? readSkillSession(options.sessionId, options) : null;
13411
- const parentSession = !session && options.parentSessionId ? readSkillSession(options.parentSessionId, options) : null;
13604
+ const sessionState = options.sessionId ? readSkillSessionSnapshotIfExists(options.sessionId, options) : null;
13605
+ const session = sessionState?.receipt ?? null;
13606
+ const parentState = !session && options.parentSessionId ? readSkillSessionSnapshotIfExists(options.parentSessionId, options) : null;
13607
+ if (!session && options.parentSessionId && !parentState) {
13608
+ throw new SkillSelectionError("SESSION_PARENT_NOT_FOUND", "A child Skills session requires an existing parent receipt so its inherited pin can be bound exactly.");
13609
+ }
13610
+ const parentSession = parentState?.receipt ?? null;
13611
+ const sessionSnapshot = sessionState ? skillSessionSnapshotBinding(sessionState) : null;
13612
+ const parentSnapshot = parentState ? skillSessionSnapshotBinding(parentState) : undefined;
13412
13613
  const project = options.projectDir ? readProjectSelection(options.projectDir) : null;
13413
13614
  if (session && session.profile.profileId !== profileId || parentSession && parentSession.profile.profileId !== profileId || project && project.profile.profileId !== profileId) {
13414
- throw new SkillSelectionError("PROFILE_LOCK_MISMATCH", "The session or project is pinned to a different Skills profile. Select that profile or explicitly sync the new selection.");
13615
+ throw new SkillSelectionError("PROFILE_LOCK_MISMATCH", "The session or project is pinned to a different Skills profile. Select the pinned profile, or review skills sessions reconcile for an intentional session change and explicit project sync for a project change.");
13415
13616
  }
13416
13617
  if (options.cached) {
13417
13618
  const receipt2 = session ?? parentSession ?? project ?? readSelectionProfile(profileId, options);
@@ -13419,7 +13620,7 @@ async function resolveSelectionContext(profileId, options = {}) {
13419
13620
  throw new SkillSelectionError("CACHED_PROFILE_MISSING", "No verified Skills profile is cached; authenticate and sync it first.");
13420
13621
  assertFreshCachedProfile(receipt2, options);
13421
13622
  assertBinding(receipt2.profile, options);
13422
- return { receipt: receipt2, session, inheritedLoaded: parentSession?.loaded };
13623
+ return { receipt: receipt2, session, sessionSnapshot, parentSnapshot, inheritedLoaded: parentSession?.loaded };
13423
13624
  }
13424
13625
  const client = options.client ?? await createProfileClient();
13425
13626
  const current = await client.resolveProfile(profileId);
@@ -13432,7 +13633,7 @@ async function resolveSelectionContext(profileId, options = {}) {
13432
13633
  throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The pinned selection belongs to another Skills authority or workspace.");
13433
13634
  }
13434
13635
  const receipt = locked ?? { schemaVersion: 1, verifiedAt: new Date((options.now ?? Date.now)()).toISOString(), profile: current };
13435
- return { receipt, session, client, inheritedLoaded: parentSession?.loaded };
13636
+ return { receipt, session, sessionSnapshot, parentSnapshot, client, inheritedLoaded: parentSession?.loaded };
13436
13637
  }
13437
13638
  function assertBinding(profile, options) {
13438
13639
  if (options.authority !== undefined && profile.authority !== options.authority || options.workspaceId !== undefined && profile.workspaceId !== options.workspaceId) {
@@ -13661,8 +13862,8 @@ function buildPluginProjection(entries) {
13661
13862
 
13662
13863
  // src/lib/plugin-projection-store.ts
13663
13864
  import { createHash as createHash10, randomUUID as randomUUID2 } from "crypto";
13664
- import { constants as constants6, closeSync as closeSync7, fchmodSync, fstatSync as fstatSync7, fsyncSync, linkSync, lstatSync as lstatSync13, mkdirSync as mkdirSync6, openSync as openSync7, opendirSync as opendirSync3, readSync as readSync5, renameSync as renameSync3, rmdirSync as rmdirSync2, rmSync as rmSync2, unlinkSync as unlinkSync3, writeSync as writeSync2 } from "fs";
13665
- import { dirname as dirname10, isAbsolute as isAbsolute8, join as join19, parse as parse4, resolve as resolve8 } from "path";
13865
+ import { constants as constants6, closeSync as closeSync7, fchmodSync, fstatSync as fstatSync7, fsyncSync as fsyncSync2, linkSync, lstatSync as lstatSync13, mkdirSync as mkdirSync6, openSync as openSync7, opendirSync as opendirSync3, readSync as readSync5, renameSync as renameSync3, rmdirSync as rmdirSync2, rmSync as rmSync2, unlinkSync as unlinkSync3, writeSync as writeSync2 } from "fs";
13866
+ import { dirname as dirname10, isAbsolute as isAbsolute9, join as join19, parse as parse4, resolve as resolve9 } from "path";
13666
13867
  var READ_FLAGS = constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK;
13667
13868
  var WRITE_FLAGS = constants6.O_WRONLY | constants6.O_CREAT | constants6.O_EXCL | constants6.O_NOFOLLOW | constants6.O_NONBLOCK;
13668
13869
  var uid = () => typeof process.getuid === "function" ? BigInt(process.getuid()) : undefined;
@@ -13677,7 +13878,7 @@ function stat(path) {
13677
13878
  }
13678
13879
  }
13679
13880
  function canonical2(path) {
13680
- pluginNeed(typeof path === "string" && path.length <= 8192 && isAbsolute8(path) && resolve8(path) === path && !/[\x00-\x1f\x7f]/.test(path), "Plugin storage paths must be canonical absolute paths");
13881
+ pluginNeed(typeof path === "string" && path.length <= 8192 && isAbsolute9(path) && resolve9(path) === path && !/[\x00-\x1f\x7f]/.test(path), "Plugin storage paths must be canonical absolute paths");
13681
13882
  }
13682
13883
  function owner(stats, system = false) {
13683
13884
  const current = uid();
@@ -13886,7 +14087,7 @@ function writeExclusive(path, bytes, mode) {
13886
14087
  pluginNeed(count > 0, "Plugin file write did not progress");
13887
14088
  offset += count;
13888
14089
  }
13889
- fsyncSync(fd);
14090
+ fsyncSync2(fd);
13890
14091
  } catch (error) {
13891
14092
  if (created) {
13892
14093
  same3(created, stat(path), true);
@@ -14060,7 +14261,7 @@ function validatePluginTarget(value) {
14060
14261
  for (const registration of value.registrations) {
14061
14262
  pluginKeys(registration, ["scope", "projectPath"]);
14062
14263
  pluginNeed(registration.scope === "user" || registration.scope === "project", "Unsupported plugin registration scope");
14063
- pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute9(registration.projectPath) && resolve9(registration.projectPath) === registration.projectPath, "Plugin scope requires its exact project path");
14264
+ pluginNeed(registration.scope === "user" ? registration.projectPath === null : typeof registration.projectPath === "string" && isAbsolute10(registration.projectPath) && resolve10(registration.projectPath) === registration.projectPath, "Plugin scope requires its exact project path");
14064
14265
  const key = JSON.stringify([registration.scope, registration.projectPath]);
14065
14266
  pluginNeed(!scopes.has(key), "Duplicate plugin registration scope");
14066
14267
  scopes.add(key);
@@ -14068,7 +14269,7 @@ function validatePluginTarget(value) {
14068
14269
  for (const key of ["native", "resolver"]) {
14069
14270
  pluginKeys(value[key], key === "native" ? ["version", "executable", "digest"] : ["executable", "digest"]);
14070
14271
  pluginText(value[key].executable);
14071
- pluginNeed(isAbsolute9(value[key].executable) && resolve9(value[key].executable) === value[key].executable, "Plugin executable must have a canonical absolute path");
14272
+ pluginNeed(isAbsolute10(value[key].executable) && resolve10(value[key].executable) === value[key].executable, "Plugin executable must have a canonical absolute path");
14072
14273
  pluginDigest(value[key].digest);
14073
14274
  }
14074
14275
  pluginNeed(["2.1.274", "2.1.276"].includes(value.native.version), "Plugin admission requires a certified Claude command-source runtime (2.1.274 or 2.1.276)");
@@ -14220,7 +14421,7 @@ function pluginResolverCommand(binding) {
14220
14421
  }
14221
14422
  function root(options) {
14222
14423
  const path = options.storeRoot ?? pluginAdmissionRoot();
14223
- pluginNeed(isAbsolute9(path) && resolve9(path) === path, "Invalid plugin admission store root");
14424
+ pluginNeed(isAbsolute10(path) && resolve10(path) === path, "Invalid plugin admission store root");
14224
14425
  return path;
14225
14426
  }
14226
14427
  function pluginBindingPath(storeRoot, id) {
@@ -14425,12 +14626,12 @@ function validateManagedPluginWitnesses(value) {
14425
14626
  pluginNeed(typeof item.bindingId === "string" && /^[a-f0-9]{64}$/.test(item.bindingId) && !seen.has(item.bindingId), "Invalid or duplicate managed plugin witness");
14426
14627
  seen.add(item.bindingId);
14427
14628
  pluginText(item.storeRoot);
14428
- pluginNeed(isAbsolute10(item.storeRoot) && resolve10(item.storeRoot) === item.storeRoot, "Invalid plugin receipt store path");
14629
+ pluginNeed(isAbsolute11(item.storeRoot) && resolve11(item.storeRoot) === item.storeRoot, "Invalid plugin receipt store path");
14429
14630
  }
14430
14631
  }
14431
14632
  function safe3(path) {
14432
14633
  pluginText(path);
14433
- pluginNeed(isAbsolute10(path) && resolve10(path) === path, "Invalid plugin registry path");
14634
+ pluginNeed(isAbsolute11(path) && resolve11(path) === path, "Invalid plugin registry path");
14434
14635
  for (let at = path;; at = dirname11(at)) {
14435
14636
  pluginNeed(!lstatSync14(at, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
14436
14637
  if (at === dirname11(at))
@@ -14499,7 +14700,7 @@ function hashManagedPluginRegistry(path, managed) {
14499
14700
  pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath === join21(cacheRoot, row.version), "Managed plugin cache path or version is outside its registration");
14500
14701
  const approval = (producer) => {
14501
14702
  pluginText(producer);
14502
- const digest = `sha256:${basename4(producer)}`;
14703
+ const digest = `sha256:${basename5(producer)}`;
14503
14704
  const receipt2 = readPluginAdmissionReceipt(witness.storeRoot, witness.bindingId, digest);
14504
14705
  pluginNeed(receipt2.materializedPath === producer, "Managed producer path is not an approved immutable projection");
14505
14706
  if (!verified.has(producer)) {
@@ -14560,8 +14761,178 @@ function captureManagedPluginRegistry(path, managedPlugins) {
14560
14761
  return { path, hashMode: "claude-plugin-registry", managedPlugins, sha256: hashManagedPluginRegistry(path, managedPlugins) };
14561
14762
  }
14562
14763
 
14764
+ // src/lib/claude-marketplace-registry.ts
14765
+ import { createHash as createHash11 } from "crypto";
14766
+ import { closeSync as closeSync9, constants as constants8, fstatSync as fstatSync9, lstatSync as lstatSync15, openSync as openSync9, readSync as readSync7 } from "fs";
14767
+ import { basename as basename6, dirname as dirname12, isAbsolute as isAbsolute12, resolve as resolve12 } from "path";
14768
+ var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
14769
+ function need2(value, reason) {
14770
+ if (!value)
14771
+ throw new Error(`Claude marketplace registry ${reason}`);
14772
+ }
14773
+ function absolutePath(value) {
14774
+ need2(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute12(value) && resolve12(value) === value, "requires a normalized absolute path");
14775
+ }
14776
+ var sameFile = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs;
14777
+ function parents(path) {
14778
+ const result = [];
14779
+ for (let at = dirname12(path);; at = dirname12(at)) {
14780
+ const stat2 = lstatSync15(at, { bigint: true });
14781
+ need2(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
14782
+ result.push([at, stat2]);
14783
+ if (at === dirname12(at))
14784
+ return result;
14785
+ }
14786
+ }
14787
+ function readRegistry2(path, budget) {
14788
+ absolutePath(path);
14789
+ need2(basename6(path) === "known_marketplaces.json", "requires known_marketplaces.json");
14790
+ need2(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
14791
+ const ancestors2 = parents(path), initial = lstatSync15(path, { bigint: true });
14792
+ need2(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
14793
+ need2(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
14794
+ const fd = openSync9(path, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
14795
+ try {
14796
+ const opened = fstatSync9(fd, { bigint: true });
14797
+ need2(opened.isFile() && sameFile(initial, opened), "changed during open");
14798
+ const bytes = Buffer.alloc(Number(opened.size) + 1);
14799
+ let length = 0;
14800
+ while (length < bytes.length) {
14801
+ const count = readSync7(fd, bytes, length, bytes.length - length, null);
14802
+ if (!count)
14803
+ break;
14804
+ length += count;
14805
+ }
14806
+ const after = fstatSync9(fd, { bigint: true }), current = lstatSync15(path, { bigint: true });
14807
+ need2(current.isFile() && sameFile(opened, after) && sameFile(after, current) && BigInt(length) === opened.size, "changed during read");
14808
+ for (const [ancestor, before] of ancestors2) {
14809
+ const now = lstatSync15(ancestor, { bigint: true });
14810
+ need2(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
14811
+ }
14812
+ budget.remaining -= length;
14813
+ try {
14814
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes.subarray(0, length));
14815
+ } catch {
14816
+ throw new Error("Claude marketplace registry is not strict UTF-8");
14817
+ }
14818
+ } finally {
14819
+ closeSync9(fd);
14820
+ }
14821
+ }
14822
+ function parse5(text2) {
14823
+ let at = 0, nodes = 0;
14824
+ const numberPattern = /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/y;
14825
+ const space = () => {
14826
+ while (at < text2.length && /[\x20\t\r\n]/.test(text2[at]))
14827
+ at++;
14828
+ };
14829
+ function string() {
14830
+ need2(text2[at] === '"', "contains invalid JSON");
14831
+ const start = at++;
14832
+ while (at < text2.length) {
14833
+ const character = text2[at++];
14834
+ if (character === '"') {
14835
+ let value2;
14836
+ try {
14837
+ value2 = JSON.parse(text2.slice(start, at));
14838
+ } catch {
14839
+ throw new Error("Claude marketplace registry contains invalid JSON string");
14840
+ }
14841
+ need2(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
14842
+ return value2;
14843
+ }
14844
+ if (character === "\\")
14845
+ at++;
14846
+ }
14847
+ throw new Error("Claude marketplace registry contains an unterminated JSON string");
14848
+ }
14849
+ function value(depth) {
14850
+ need2(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
14851
+ space();
14852
+ const character = text2[at];
14853
+ if (character === '"')
14854
+ return { kind: "string", value: string() };
14855
+ if (character === "{" || character === "[") {
14856
+ at++;
14857
+ space();
14858
+ const object4 = character === "{", end = object4 ? "}" : "]", entries = [], items = [], keys = new Set;
14859
+ if (text2[at] !== end)
14860
+ while (true) {
14861
+ if (object4) {
14862
+ space();
14863
+ const key = string();
14864
+ need2(!keys.has(key), "contains a duplicate JSON key");
14865
+ keys.add(key);
14866
+ space();
14867
+ need2(text2[at++] === ":", "contains invalid JSON");
14868
+ entries.push([key, value(depth + 1)]);
14869
+ } else
14870
+ items.push(value(depth + 1));
14871
+ space();
14872
+ if (text2[at] === end)
14873
+ break;
14874
+ need2(text2[at++] === ",", "contains invalid JSON");
14875
+ }
14876
+ at++;
14877
+ return object4 ? { kind: "object", entries } : { kind: "array", items };
14878
+ }
14879
+ for (const literal of ["true", "false", "null"])
14880
+ if (text2.startsWith(literal, at)) {
14881
+ at += literal.length;
14882
+ return { kind: "literal", value: literal };
14883
+ }
14884
+ numberPattern.lastIndex = at;
14885
+ const number = numberPattern.exec(text2);
14886
+ need2(number, "contains invalid JSON");
14887
+ at += number[0].length;
14888
+ return { kind: "number", value: number[0] };
14889
+ }
14890
+ const root2 = value(0);
14891
+ space();
14892
+ need2(at === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
14893
+ need2(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
14894
+ return root2;
14895
+ }
14896
+ function exactKeys(value, keys) {
14897
+ return value?.kind === "object" && value.entries.length === keys.length && value.entries.every(([key]) => keys.includes(key));
14898
+ }
14899
+ var field = (object4, key) => object4.entries.find(([name]) => name === key)?.[1];
14900
+ function textField(value) {
14901
+ need2(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
14902
+ return value.value;
14903
+ }
14904
+ function normalizeRow(row) {
14905
+ if (!exactKeys(row, ["source", "installLocation", "lastUpdated"]))
14906
+ return;
14907
+ const source = field(row, "source");
14908
+ if (source?.kind !== "object")
14909
+ return;
14910
+ const type = field(source, "source");
14911
+ if (type?.kind !== "string" || !["github", "directory"].includes(type.value))
14912
+ return;
14913
+ const location = type.value === "github" ? "repo" : "path";
14914
+ if (!exactKeys(source, ["source", location]))
14915
+ return;
14916
+ const sourceLocation = textField(field(source, location));
14917
+ if (type.value === "directory")
14918
+ absolutePath(sourceLocation);
14919
+ absolutePath(textField(field(row, "installLocation")));
14920
+ const updated = textField(field(row, "lastUpdated"));
14921
+ need2(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(updated), "contains an invalid recognized update timestamp");
14922
+ const millis = Date.parse(updated);
14923
+ need2(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
14924
+ row.entries = row.entries.map(([key, value]) => [key, key === "lastUpdated" ? { kind: "validated-update-time" } : value]);
14925
+ }
14926
+ function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024 * 1024 }) {
14927
+ const value = parse5(readRegistry2(path, budget));
14928
+ for (const [, row] of value.entries)
14929
+ normalizeRow(row);
14930
+ const sha2562 = createHash11("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
14931
+ return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
14932
+ }
14933
+
14563
14934
  // src/lib/agent-discovery.ts
14564
- var digest = (text2) => createHash11("sha256").update(text2).digest("hex");
14935
+ var digest = (text2) => createHash12("sha256").update(text2).digest("hex");
14565
14936
  function parseConfig(text2, path, toml = false) {
14566
14937
  try {
14567
14938
  const value = toml ? Bun.TOML.parse(text2) : JSON.parse(text2);
@@ -14573,12 +14944,12 @@ function parseConfig(text2, path, toml = false) {
14573
14944
  }
14574
14945
  }
14575
14946
  function safe4(path) {
14576
- if (!isAbsolute11(path) || path.includes("\x00"))
14947
+ if (!isAbsolute13(path) || path.includes("\x00"))
14577
14948
  throw new Error("Expected an absolute native discovery path");
14578
- for (let cursor = path;; cursor = dirname12(cursor)) {
14579
- if (lstatSync15(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
14949
+ for (let cursor = path;; cursor = dirname13(cursor)) {
14950
+ if (lstatSync16(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
14580
14951
  throw new Error(`Refusing symlink native discovery input: ${cursor}`);
14581
- if (dirname12(cursor) === cursor)
14952
+ if (dirname13(cursor) === cursor)
14582
14953
  break;
14583
14954
  }
14584
14955
  }
@@ -14586,7 +14957,7 @@ function read(path, changes) {
14586
14957
  safe4(path);
14587
14958
  if (changes?.has(path))
14588
14959
  return changes.get(path);
14589
- const stat2 = lstatSync15(path, { throwIfNoEntry: false });
14960
+ const stat2 = lstatSync16(path, { throwIfNoEntry: false });
14590
14961
  if (!stat2)
14591
14962
  return null;
14592
14963
  if (!stat2.isFile() || stat2.size > 16 * 1024 * 1024)
@@ -14594,6 +14965,14 @@ function read(path, changes) {
14594
14965
  return readFileSync13(path, "utf8");
14595
14966
  }
14596
14967
  function projected(source, changes, budget = discoveryByteBudget()) {
14968
+ if (source.hashMode === "claude-marketplace-registry") {
14969
+ if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256) || changes?.has(source.path))
14970
+ throw new Error("Claude marketplace witnesses require an exact reviewed registry without synthetic changes");
14971
+ const sha2562 = captureClaudeMarketplaceRegistry(source.path, budget).sha256;
14972
+ if (sha2562 !== source.sha256)
14973
+ throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
14974
+ return sha2562;
14975
+ }
14597
14976
  if (source.hashMode === "claude-plugin-registry") {
14598
14977
  if (source.format !== undefined || source.fields !== undefined || !source.managedPlugins || changes?.has(source.path))
14599
14978
  throw new Error("Managed plugin registry witnesses require verified native registrations");
@@ -14618,7 +14997,7 @@ function projected(source, changes, budget = discoveryByteBudget()) {
14618
14997
  const object4 = source.format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, source.path, source.format === "toml");
14619
14998
  if (!object4 || typeof object4 !== "object" || Array.isArray(object4))
14620
14999
  throw new Error("Expected native discovery configuration object");
14621
- return digest(JSON.stringify(Object.fromEntries((source.fields ?? []).map((field) => [field, object4[field] ?? null]))));
15000
+ return digest(JSON.stringify(Object.fromEntries((source.fields ?? []).map((field2) => [field2, object4[field2] ?? null]))));
14622
15001
  }
14623
15002
  function verifyAgentDiscovery(binding) {
14624
15003
  if (!binding || !Array.isArray(binding.sources) || !Array.isArray(binding.roots) || binding.sources.length > AGENT_POLICY_LIMITS.discoverySources || binding.roots.length > AGENT_POLICY_LIMITS.discoveryRoots)
@@ -14629,9 +15008,10 @@ function verifyAgentDiscovery(binding) {
14629
15008
  verifyDiscoveryDirectories(binding.directories);
14630
15009
  const budget = discoveryByteBudget();
14631
15010
  for (const source of binding.sources) {
15011
+ assertMarketplaceBinding(binding, source);
14632
15012
  if (source.hashMode === "claude-plugin-registry" && binding.agent !== "claude")
14633
15013
  throw new Error("Managed Claude registry witnesses cannot apply to another agent");
14634
- if (source.format !== undefined && (!["json", "toml", "yaml"].includes(source.format) || !Array.isArray(source.fields) || !source.fields.length || source.fields.length > 64 || source.fields.some((field) => typeof field !== "string" || !field)))
15014
+ if (source.format !== undefined && (!["json", "toml", "yaml"].includes(source.format) || !Array.isArray(source.fields) || !source.fields.length || source.fields.length > 64 || source.fields.some((field2) => typeof field2 !== "string" || !field2)))
14635
15015
  throw new Error("Invalid native discovery projection");
14636
15016
  if (source.sha256 !== null && !/^[a-f0-9]{64}$/.test(source.sha256))
14637
15017
  throw new Error("Invalid native discovery digest");
@@ -14641,9 +15021,16 @@ function verifyAgentDiscovery(binding) {
14641
15021
  for (const root2 of binding.roots)
14642
15022
  safe4(root2);
14643
15023
  }
15024
+ function assertMarketplaceBinding(binding, source) {
15025
+ if (source.hashMode === "claude-marketplace-registry" && (binding.agent !== "claude" || binding.method !== "reviewed"))
15026
+ throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
15027
+ }
14644
15028
  function rebindAgentDiscovery(binding, changes) {
14645
15029
  const budget = discoveryByteBudget();
14646
- return { ...binding, sources: binding.sources.map((source) => ({ ...source, sha256: projected(source, changes, budget) })) };
15030
+ return { ...binding, sources: binding.sources.map((source) => {
15031
+ assertMarketplaceBinding(binding, source);
15032
+ return { ...source, sha256: projected(source, changes, budget) };
15033
+ }) };
14647
15034
  }
14648
15035
  function captureDiscoveryByteSources(paths) {
14649
15036
  if (!Array.isArray(paths) || paths.length > AGENT_POLICY_LIMITS.discoverySources || new Set(paths).size !== paths.length)
@@ -14658,7 +15045,7 @@ function captureDiscoveryPathSources(paths) {
14658
15045
  return paths.map((path) => ({ path, hashMode: "path-bytes", sha256: hashDiscoveryPathFile(path, budget) }));
14659
15046
  }
14660
15047
  function resolveAgentDiscovery(options) {
14661
- const canonical3 = options.canonical ?? resolve11, home = resolve11(options.home), agent = options.agent;
15048
+ const canonical3 = options.canonical ?? resolve13, home = resolve13(options.home), agent = options.agent;
14662
15049
  const sources = [], roots = new Set, builtinNames = [];
14663
15050
  const witness = (path, format, fields) => {
14664
15051
  const source = { path: canonical3(path), sha256: null, ...format ? { format, fields } : {} };
@@ -14689,7 +15076,7 @@ function resolveAgentDiscovery(options) {
14689
15076
  continue;
14690
15077
  if (normalized.startsWith("~") && normalized !== "~" && !normalized.startsWith("~/"))
14691
15078
  unresolved("user-specific tilde expansion requires a separate discovery adapter");
14692
- const path = normalized === "~" ? home : normalized.startsWith("~/") ? join23(home, normalized.slice(2)) : resolve11(home, ".hermes", normalized);
15079
+ const path = normalized === "~" ? home : normalized.startsWith("~/") ? join23(home, normalized.slice(2)) : resolve13(home, ".hermes", normalized);
14693
15080
  safe4(path);
14694
15081
  roots.add(path);
14695
15082
  }
@@ -14698,21 +15085,21 @@ function resolveAgentDiscovery(options) {
14698
15085
  const executable = Bun.which("gemini");
14699
15086
  if (executable) {
14700
15087
  let packageRoot;
14701
- for (let directory = dirname12(realpathSync4(executable)), depth = 0;depth < 8; directory = dirname12(directory), depth++) {
15088
+ for (let directory = dirname13(realpathSync4(executable)), depth = 0;depth < 8; directory = dirname13(directory), depth++) {
14702
15089
  const path = join23(directory, "package.json"), raw = read(path);
14703
15090
  if (raw !== null && parseConfig(raw, path).name === "@google/gemini-cli") {
14704
15091
  packageRoot = directory;
14705
15092
  witness(path);
14706
15093
  break;
14707
15094
  }
14708
- if (dirname12(directory) === directory)
15095
+ if (dirname13(directory) === directory)
14709
15096
  break;
14710
15097
  }
14711
15098
  if (!packageRoot)
14712
15099
  unresolved("installed Gemini package layout is not recognized");
14713
15100
  const builtinRoot = join23(packageRoot, "bundle/builtin");
14714
15101
  safe4(builtinRoot);
14715
- if (!lstatSync15(builtinRoot, { throwIfNoEntry: false })?.isDirectory())
15102
+ if (!lstatSync16(builtinRoot, { throwIfNoEntry: false })?.isDirectory())
14716
15103
  unresolved("installed Gemini builtin root is missing");
14717
15104
  for (const name of readdirSync9(builtinRoot).sort()) {
14718
15105
  const text2 = witness(join23(builtinRoot, name, "SKILL.md"));
@@ -14750,7 +15137,7 @@ function resolveAgentDiscovery(options) {
14750
15137
  unresolved("plugin hooks require a separate no-skill-injection review");
14751
15138
  const commands = join23(root2, "commands");
14752
15139
  safe4(commands);
14753
- if (agent === "claude" && (manifest.commands !== undefined || lstatSync15(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync9(commands).length))
15140
+ if (agent === "claude" && (manifest.commands !== undefined || lstatSync16(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync9(commands).length))
14754
15141
  unresolved("legacy command discovery requires retirement or a dedicated format adapter");
14755
15142
  const rootSkill = witness(join23(root2, "SKILL.md"));
14756
15143
  if (rootSkill !== null)
@@ -14761,10 +15148,10 @@ function resolveAgentDiscovery(options) {
14761
15148
  if (agent === "claude")
14762
15149
  roots.add(join23(root2, "skills"));
14763
15150
  for (const value of declared) {
14764
- const path = resolve11(root2, value);
15151
+ const path = resolve13(root2, value);
14765
15152
  if (path !== root2 && !path.startsWith(root2 + sep5))
14766
15153
  unresolved("plugin skill path escapes its root");
14767
- roots.add(path.endsWith(`${sep5}SKILL.md`) ? dirname12(path) : path);
15154
+ roots.add(path.endsWith(`${sep5}SKILL.md`) ? dirname13(path) : path);
14768
15155
  }
14769
15156
  }
14770
15157
  if (agent === "claude") {
@@ -14772,7 +15159,7 @@ function resolveAgentDiscovery(options) {
14772
15159
  const installed = registrations === null ? {} : parseConfig(registrations, join23(home, ".claude/plugins/installed_plugins.json")).plugins;
14773
15160
  const commands = canonical3(join23(home, ".claude/commands"));
14774
15161
  safe4(commands);
14775
- if (lstatSync15(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync9(commands).length)
15162
+ if (lstatSync16(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync9(commands).length)
14776
15163
  unresolved("legacy home command discovery requires a dedicated format adapter");
14777
15164
  for (const [id, enabled] of Object.entries(config.enabledPlugins ?? {})) {
14778
15165
  if (enabled === false)
@@ -14792,7 +15179,7 @@ function resolveAgentDiscovery(options) {
14792
15179
  unresolved("unsupported plugin enablement");
14793
15180
  const split = id.lastIndexOf("@"), name = id.slice(0, split), marketplace = id.slice(split + 1);
14794
15181
  const market = config.marketplaces?.[marketplace] ?? (marketplace === "personal" ? { source_type: "local", source: home } : undefined);
14795
- if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !isAbsolute11(market.source))
15182
+ if (split < 1 || market?.source_type !== "local" || typeof market.source !== "string" || !isAbsolute13(market.source))
14796
15183
  unresolved("enabled plugin marketplace is not an explicit local source");
14797
15184
  const catalogText = witness(join23(market.source, ".agents/plugins/marketplace.json"));
14798
15185
  if (catalogText === null)
@@ -14800,7 +15187,7 @@ function resolveAgentDiscovery(options) {
14800
15187
  const catalog = parseConfig(catalogText, join23(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
14801
15188
  if (matches.length !== 1 || matches[0]?.source?.source !== "local" || typeof matches[0]?.source?.path !== "string")
14802
15189
  unresolved("plugin catalog source is missing or ambiguous");
14803
- plugin(resolve11(market.source, matches[0].source.path));
15190
+ plugin(resolve13(market.source, matches[0].source.path));
14804
15191
  }
14805
15192
  } else if (agent === "opencode") {
14806
15193
  if (witness(join23(home, ".config/opencode/opencode.jsonc")) !== null || witness(join23(home, ".config/opencode/config.json")) !== null)
@@ -14808,18 +15195,18 @@ function resolveAgentDiscovery(options) {
14808
15195
  if ((config.plugin?.length ?? 0) > 0 || config.skills?.urls?.length)
14809
15196
  unresolved("external plugin or remote skill source");
14810
15197
  for (const path of config.skills?.paths ?? []) {
14811
- if (typeof path !== "string" || !isAbsolute11(path))
15198
+ if (typeof path !== "string" || !isAbsolute13(path))
14812
15199
  unresolved("relative or malformed added skill root");
14813
15200
  roots.add(path);
14814
15201
  }
14815
15202
  const localPlugins = join23(home, ".config/opencode/plugins");
14816
15203
  safe4(localPlugins);
14817
- if (lstatSync15(localPlugins, { throwIfNoEntry: false }) && readdirSync9(localPlugins).some((name) => name !== "skills-cli.js"))
15204
+ if (lstatSync16(localPlugins, { throwIfNoEntry: false }) && readdirSync9(localPlugins).some((name) => name !== "skills-cli.js"))
14818
15205
  unresolved("additional local plugin hooks require review");
14819
15206
  } else if (agent === "hermes") {
14820
15207
  for (const directory of [join23(home, ".hermes/plugins"), join23(home, ".hermes/hermes-agent")]) {
14821
15208
  safe4(directory);
14822
- if (lstatSync15(directory, { throwIfNoEntry: false }))
15209
+ if (lstatSync16(directory, { throwIfNoEntry: false }))
14823
15210
  unresolved("installed runtime/plugin discovery requires reviewed source bindings");
14824
15211
  }
14825
15212
  if (Bun.which("hermes") || Object.keys(config.plugins ?? {}).length)
@@ -14829,11 +15216,11 @@ function resolveAgentDiscovery(options) {
14829
15216
  unresolved("custom extension or skill paths");
14830
15217
  const directory = join23(home, ".gemini/extensions");
14831
15218
  safe4(directory);
14832
- if (lstatSync15(directory, { throwIfNoEntry: false }))
15219
+ if (lstatSync16(directory, { throwIfNoEntry: false }))
14833
15220
  for (const name of readdirSync9(directory)) {
14834
15221
  const root2 = join23(directory, name);
14835
15222
  safe4(root2);
14836
- if (!lstatSync15(root2)?.isDirectory())
15223
+ if (!lstatSync16(root2)?.isDirectory())
14837
15224
  continue;
14838
15225
  const raw = witness(join23(root2, "gemini-extension.json"));
14839
15226
  if (raw === null)
@@ -14914,7 +15301,7 @@ export default async ({ directory }) => {
14914
15301
  }
14915
15302
 
14916
15303
  // src/lib/agent-codex.ts
14917
- import { isDeepStrictEqual } from "util";
15304
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
14918
15305
  var CODEX_SKILL_CONFIG_SECTIONS = /^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm;
14919
15306
  function assertCodexPathConfigEditable(text2) {
14920
15307
  const before = Bun.TOML.parse(text2);
@@ -14924,7 +15311,7 @@ function assertCodexPathConfigEditable(text2) {
14924
15311
  delete expected.skills.config;
14925
15312
  try {
14926
15313
  const withoutSections = Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
14927
- if (isDeepStrictEqual(withoutSections, expected))
15314
+ if (isDeepStrictEqual2(withoutSections, expected))
14928
15315
  return;
14929
15316
  } catch {}
14930
15317
  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");
@@ -14962,7 +15349,7 @@ function disableCodexBundledSkills(text2) {
14962
15349
  const candidates = new Set;
14963
15350
  const consider = (candidate2) => {
14964
15351
  try {
14965
- if (isDeepStrictEqual(Bun.TOML.parse(candidate2), expected))
15352
+ if (isDeepStrictEqual2(Bun.TOML.parse(candidate2), expected))
14966
15353
  candidates.add(candidate2);
14967
15354
  } catch {}
14968
15355
  };
@@ -14988,7 +15375,7 @@ function disableCodexBundledSkills(text2) {
14988
15375
  }
14989
15376
 
14990
15377
  // src/lib/agent-integration.ts
14991
- var sha = (data) => createHash12("sha256").update(data).digest("hex");
15378
+ var sha = (data) => createHash13("sha256").update(data).digest("hex");
14992
15379
  var ROOTS = [
14993
15380
  ["claude", ".claude/skills"],
14994
15381
  ["codex", ".codex/skills"],
@@ -15007,11 +15394,11 @@ var ROOTS = [
15007
15394
  ["copilot", ".github/skills"]
15008
15395
  ];
15009
15396
  function assertSafePath(path) {
15010
- let cursor = resolve12(path);
15397
+ let cursor = resolve14(path);
15011
15398
  while (true) {
15012
- if (lstatSync16(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
15399
+ if (lstatSync17(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
15013
15400
  throw new Error(`Refusing symlink path: ${cursor}`);
15014
- const parent = dirname13(cursor);
15401
+ const parent = dirname14(cursor);
15015
15402
  if (parent === cursor)
15016
15403
  return;
15017
15404
  cursor = parent;
@@ -15020,16 +15407,16 @@ function assertSafePath(path) {
15020
15407
  function readRootAlias(home, agent) {
15021
15408
  assertSafePath(home);
15022
15409
  const realHome = realpathSync5(home), alias = join25(realHome, `.${agent}`);
15023
- const stat2 = lstatSync16(alias, { throwIfNoEntry: false, bigint: true });
15410
+ const stat2 = lstatSync17(alias, { throwIfNoEntry: false, bigint: true });
15024
15411
  if (!stat2?.isSymbolicLink())
15025
15412
  throw new Error(`Agent root alias changed after planning: ${alias}`);
15026
- const link = readlinkSync2(alias), targetPath = resolve12(dirname13(alias), link);
15413
+ const link = readlinkSync2(alias), targetPath = resolve14(dirname14(alias), link);
15027
15414
  assertSafePath(targetPath);
15028
- const targetStat = lstatSync16(targetPath, { throwIfNoEntry: false });
15415
+ const targetStat = lstatSync17(targetPath, { throwIfNoEntry: false });
15029
15416
  if (!targetStat?.isDirectory())
15030
15417
  throw new Error(`Agent root alias must target an existing directory: ${alias}`);
15031
15418
  const target = realpathSync5(targetPath), inside = relative5(realHome, target);
15032
- if (!inside || inside === ".." || inside.startsWith(`..${sep6}`) || isAbsolute12(inside))
15419
+ if (!inside || inside === ".." || inside.startsWith(`..${sep6}`) || isAbsolute14(inside))
15033
15420
  throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
15034
15421
  return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
15035
15422
  }
@@ -15038,14 +15425,14 @@ function rootAliases(home, allowed = false) {
15038
15425
  return [];
15039
15426
  const result = [];
15040
15427
  for (const agent of ["claude", "codex"])
15041
- if (lstatSync16(join25(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
15428
+ if (lstatSync17(join25(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
15042
15429
  result.push(readRootAlias(home, agent));
15043
15430
  if (result.some((item, index) => result.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep6) || other.target.startsWith(item.target + sep6))))
15044
15431
  throw new Error("Agent root alias targets must not overlap");
15045
15432
  return result;
15046
15433
  }
15047
15434
  function canonicalAgentPath(path, aliases) {
15048
- const absolute = resolve12(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep6));
15435
+ const absolute = resolve14(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep6));
15049
15436
  return binding ? join25(binding.target, relative5(binding.alias, absolute)) : absolute;
15050
15437
  }
15051
15438
  function recheckRootAliases(aliases) {
@@ -15059,35 +15446,35 @@ function recheckRootAliases(aliases) {
15059
15446
  }
15060
15447
  function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
15061
15448
  assertSafePath(path);
15062
- const before = lstatSync16(path);
15449
+ const before = lstatSync17(path);
15063
15450
  if (!before.isFile() || before.size > maximum)
15064
15451
  throw new Error("Unsupported or oversized native skill file");
15065
- const descriptor = openSync9(path, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
15452
+ const descriptor = openSync10(path, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
15066
15453
  try {
15067
- const opened = fstatSync9(descriptor);
15454
+ const opened = fstatSync10(descriptor);
15068
15455
  if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size)
15069
15456
  throw new Error("Native skill file changed while reading");
15070
15457
  const bytes = Buffer.allocUnsafe(before.size + 1);
15071
15458
  let length = 0;
15072
15459
  while (length < bytes.length) {
15073
- const count = readSync7(descriptor, bytes, length, bytes.length - length, null);
15460
+ const count = readSync8(descriptor, bytes, length, bytes.length - length, null);
15074
15461
  if (!count)
15075
15462
  break;
15076
15463
  length += count;
15077
15464
  }
15078
- const after = fstatSync9(descriptor);
15465
+ const after = fstatSync10(descriptor);
15079
15466
  if (length !== before.size || after.size !== before.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs)
15080
15467
  throw new Error("Native skill file changed while reading");
15081
15468
  return bytes.subarray(0, length);
15082
15469
  } finally {
15083
- closeSync9(descriptor);
15470
+ closeSync10(descriptor);
15084
15471
  }
15085
15472
  }
15086
15473
  function treeHash(root2) {
15087
- const hash2 = createHash12("sha256");
15474
+ const hash2 = createHash13("sha256");
15088
15475
  let bytes = 0, files = 0;
15089
15476
  function visit2(path) {
15090
- const stat2 = lstatSync16(path);
15477
+ const stat2 = lstatSync17(path);
15091
15478
  if (stat2.isSymbolicLink())
15092
15479
  throw new Error(`Refusing symlink in skill: ${path}`);
15093
15480
  const rel = relative5(root2, path);
@@ -15113,11 +15500,11 @@ function treeHash(root2) {
15113
15500
  function projectAncestorDirectories(projects) {
15114
15501
  const directories = new Set;
15115
15502
  for (const project of projects) {
15116
- for (let path = resolve12(project), depth = 0;; depth++) {
15503
+ for (let path = resolve14(project), depth = 0;; depth++) {
15117
15504
  if (depth >= 100)
15118
15505
  throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
15119
15506
  directories.add(path);
15120
- const parent = dirname13(path);
15507
+ const parent = dirname14(path);
15121
15508
  if (parent === path)
15122
15509
  break;
15123
15510
  path = parent;
@@ -15144,10 +15531,10 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
15144
15531
  throw new Error("Native skill discovery metadata limit exceeded");
15145
15532
  }
15146
15533
  function emptySiblingCacheAlias(path, parent) {
15147
- const link = readlinkSync2(path), target = resolve12(parent, link);
15148
- if (dirname13(target) !== parent || (isAbsolute12(link) ? link !== target : dirname13(link) !== "."))
15534
+ const link = readlinkSync2(path), target = resolve14(parent, link);
15535
+ if (dirname14(target) !== parent || (isAbsolute14(link) ? link !== target : dirname14(link) !== "."))
15149
15536
  return false;
15150
- if (!lstatSync16(target, { throwIfNoEntry: false })?.isDirectory())
15537
+ if (!lstatSync17(target, { throwIfNoEntry: false })?.isDirectory())
15151
15538
  return false;
15152
15539
  const scan = cacheScans.get(target);
15153
15540
  return Boolean(scan?.complete && !scan.hasSkills && scan.entries <= maxAliasProofEntries);
@@ -15159,7 +15546,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
15159
15546
  assertSafePath(path);
15160
15547
  if (!existsSync11(path))
15161
15548
  return scan;
15162
- const stat2 = lstatSync16(path);
15549
+ const stat2 = lstatSync17(path);
15163
15550
  if (stat2.isFile())
15164
15551
  return scan;
15165
15552
  if (!stat2.isDirectory())
@@ -15178,7 +15565,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
15178
15565
  }
15179
15566
  const bridge = !vendor && isOwnedCliBridge(path, bridgePaths);
15180
15567
  const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep6));
15181
- entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" && path.startsWith(canonicalAgentPath(join25(home, ".codex", "skills", ".system"), aliases) + sep6) ? { system: true } : {}, ...bridge ? { bridge: true, bridgeHome: resolve12(home) } : {}, ...rootAlias ? { rootAlias } : {} });
15568
+ entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...agent === "codex" && path.startsWith(canonicalAgentPath(join25(home, ".codex", "skills", ".system"), aliases) + sep6) ? { system: true } : {}, ...bridge ? { bridge: true, bridgeHome: resolve14(home) } : {}, ...rootAlias ? { rootAlias } : {} });
15182
15569
  return scan;
15183
15570
  }
15184
15571
  if (depth > (vendor ? 32 : 3))
@@ -15205,7 +15592,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
15205
15592
  if (isVendor && !options.includeVendor)
15206
15593
  continue;
15207
15594
  const child = join25(path, name);
15208
- if (isVendor && lstatSync16(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
15595
+ if (isVendor && lstatSync17(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
15209
15596
  const target = statSync8(child, { throwIfNoEntry: false });
15210
15597
  if (target?.isFile() || pluginCache && target?.isDirectory() && emptySiblingCacheAlias(child, path)) {
15211
15598
  scan.entries++;
@@ -15327,7 +15714,7 @@ function configureHooks(config, agent, command, profileId, nativeReady) {
15327
15714
  function disableCodexSkills(text2, skills, aliases, bridgePath) {
15328
15715
  const bundled = disableCodexBundledSkills(text2);
15329
15716
  let result = bundled;
15330
- const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve12(bridgePath), resolve12(join25(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
15717
+ const bridgeSelector = (entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME || typeof entry.path === "string" && [resolve14(bridgePath), resolve14(join25(bridgePath, "SKILL.md"))].includes(canonicalAgentPath(entry.path, aliases));
15331
15718
  const previousConfig = Bun.TOML.parse(bundled).skills?.config;
15332
15719
  if (previousConfig !== undefined && (!Array.isArray(previousConfig) || previousConfig.some((entry) => !entry || typeof entry !== "object" || entry.path !== undefined && entry.name !== undefined)))
15333
15720
  throw new Error("Codex skill controls require one path or name selector per [[skills.config]] entry; review ambiguous entries before running skills hook install");
@@ -15339,7 +15726,7 @@ function disableCodexSkills(text2, skills, aliases, bridgePath) {
15339
15726
  result = result.replace(CODEX_SKILL_CONFIG_SECTIONS, (section) => {
15340
15727
  const parsed = Bun.TOML.parse(section);
15341
15728
  const declared = parsed.skills?.config?.[0];
15342
- if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [resolve12(path), resolve12(skill.path)].includes(canonicalAgentPath(declared.path, aliases))))
15729
+ if (!declared || !(skill.enabled && bridgeSelector(declared)) && !(typeof declared.path === "string" && [resolve14(path), resolve14(skill.path)].includes(canonicalAgentPath(declared.path, aliases))))
15343
15730
  return section;
15344
15731
  found = true;
15345
15732
  return /^\s*enabled\s*=/m.test(section) ? section.replace(/^\s*enabled\s*=.*$/m, `enabled = ${skill.enabled}`) : `${section.trimEnd()}
@@ -15369,9 +15756,9 @@ function planAgentIntegration(options) {
15369
15756
  throw new Error("Invalid existing Skills bridge policy");
15370
15757
  if (policy.bridge?.agents !== undefined && (!Array.isArray(policy.bridge.agents) || policy.bridge.agents.some((agent) => !INTEGRATION_AGENTS.includes(agent))))
15371
15758
  throw new Error("Invalid existing bridge agent inventory");
15372
- for (const field of ["commands", "profiles"])
15373
- if (policy.bridge?.[field] !== undefined && (!policy.bridge[field] || typeof policy.bridge[field] !== "object" || Array.isArray(policy.bridge[field]) || Object.entries(policy.bridge[field]).some(([key, value]) => !INTEGRATION_AGENTS.includes(key) || typeof value !== "string" || !value || value.includes("\x00"))))
15374
- throw new Error(`Invalid existing bridge ${field} binding`);
15759
+ for (const field2 of ["commands", "profiles"])
15760
+ if (policy.bridge?.[field2] !== undefined && (!policy.bridge[field2] || typeof policy.bridge[field2] !== "object" || Array.isArray(policy.bridge[field2]) || Object.entries(policy.bridge[field2]).some(([key, value]) => !INTEGRATION_AGENTS.includes(key) || typeof value !== "string" || !value || value.includes("\x00"))))
15761
+ throw new Error(`Invalid existing bridge ${field2} binding`);
15375
15762
  const priorAgents = policy.bridge?.agents ?? [];
15376
15763
  const profileId = options.profileId ?? policy.profileId ?? "default";
15377
15764
  const validateProfile = (value) => {
@@ -15457,7 +15844,7 @@ function planAgentIntegration(options) {
15457
15844
  version: CLI_BRIDGE_VERSION,
15458
15845
  digest: CLI_BRIDGE_DIGEST,
15459
15846
  agents: [...new Set([...priorAgents, ...options.agents])].sort(),
15460
- home: resolve12(home),
15847
+ home: resolve14(home),
15461
15848
  includeVendor: true,
15462
15849
  ...options.agents.includes("hermes") ? { supervisors: { ...policy.bridge?.supervisors, hermes: { path: join25(dataDir2, "agent-hooks", "hermes.js"), runtime: process.execPath, sha256: sha(renderHermesSupervisor(bindings.get("hermes").command, bindings.get("hermes").profileId)) } } } : {},
15463
15850
  commands: { ...policy.bridge?.commands, ...Object.fromEntries([...bindings].map(([agent, binding]) => [agent, binding.command])) },
@@ -15474,7 +15861,7 @@ function planAgentIntegration(options) {
15474
15861
  }
15475
15862
  function atomicWrite2(path, content) {
15476
15863
  assertSafePath(path);
15477
- mkdirSync7(dirname13(path), { recursive: true, mode: 448 });
15864
+ mkdirSync7(dirname14(path), { recursive: true, mode: 448 });
15478
15865
  const temporary = `${path}.skills-${randomUUID3()}`;
15479
15866
  try {
15480
15867
  writeFileSync5(temporary, content, { mode: 384, flag: "wx" });
@@ -15487,10 +15874,10 @@ function atomicWrite2(path, content) {
15487
15874
  function applyAgentIntegration(plan) {
15488
15875
  const policyPath = join25(plan.dataDir, "agent-policy.json");
15489
15876
  assertSafePath(policyPath);
15490
- const currentText = (path) => resolve12(path) === resolve12(policyPath) ? readManagedSkillPolicySnapshot(plan.dataDir)?.text ?? null : readOptional(path);
15877
+ const currentText = (path) => resolve14(path) === resolve14(policyPath) ? readManagedSkillPolicySnapshot(plan.dataDir)?.text ?? null : readOptional(path);
15491
15878
  readManagedSkillPolicySnapshot(plan.dataDir);
15492
15879
  for (const change of plan.changes)
15493
- if (resolve12(change.path) === resolve12(policyPath))
15880
+ if (resolve14(change.path) === resolve14(policyPath))
15494
15881
  parseManagedSkillPolicy(change.after);
15495
15882
  const aliases = plan.rootAliases ?? [];
15496
15883
  recheckRootAliases(aliases);
@@ -15520,7 +15907,7 @@ function applyAgentIntegration(plan) {
15520
15907
  recheckRootAliases(aliases);
15521
15908
  if (currentText(change.path) !== change.before)
15522
15909
  throw new Error(`Configuration changed after planning: ${change.path}`);
15523
- for (let directory = dirname13(change.path);!existsSync11(directory); directory = dirname13(directory))
15910
+ for (let directory = dirname14(change.path);!existsSync11(directory); directory = dirname14(directory))
15524
15911
  createdDirectories.add(directory);
15525
15912
  const after = change.after;
15526
15913
  atomicWrite2(change.path, after);
@@ -15560,11 +15947,11 @@ function applyAgentIntegration(plan) {
15560
15947
  }
15561
15948
  function syncArchiveDirectory(path) {
15562
15949
  assertSafePath(path);
15563
- const descriptor = openSync9(path, constants8.O_RDONLY | constants8.O_DIRECTORY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
15950
+ const descriptor = openSync10(path, constants9.O_RDONLY | constants9.O_DIRECTORY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
15564
15951
  try {
15565
- fsyncSync2(descriptor);
15952
+ fsyncSync3(descriptor);
15566
15953
  } finally {
15567
- closeSync9(descriptor);
15954
+ closeSync10(descriptor);
15568
15955
  }
15569
15956
  }
15570
15957
  function writeArchiveJournal(path, value) {
@@ -15572,18 +15959,18 @@ function writeArchiveJournal(path, value) {
15572
15959
  const temporary = `${path}.skills-${randomUUID3()}`;
15573
15960
  let descriptor;
15574
15961
  try {
15575
- descriptor = openSync9(temporary, constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW, 384);
15962
+ descriptor = openSync10(temporary, constants9.O_WRONLY | constants9.O_CREAT | constants9.O_EXCL | constants9.O_NOFOLLOW, 384);
15576
15963
  writeFileSync5(descriptor, `${JSON.stringify(value)}
15577
15964
  `);
15578
- fsyncSync2(descriptor);
15579
- closeSync9(descriptor);
15965
+ fsyncSync3(descriptor);
15966
+ closeSync10(descriptor);
15580
15967
  descriptor = undefined;
15581
15968
  renameSync4(temporary, path);
15582
- syncArchiveDirectory(dirname13(path));
15969
+ syncArchiveDirectory(dirname14(path));
15583
15970
  } finally {
15584
15971
  if (descriptor !== undefined)
15585
- closeSync9(descriptor);
15586
- if (lstatSync16(temporary, { throwIfNoEntry: false }))
15972
+ closeSync10(descriptor);
15973
+ if (lstatSync17(temporary, { throwIfNoEntry: false }))
15587
15974
  unlinkSync4(temporary);
15588
15975
  }
15589
15976
  }
@@ -15616,7 +16003,7 @@ function archiveNativeSkills(inventory, options) {
15616
16003
  const moved = [];
15617
16004
  const verifyArchive = (entry) => {
15618
16005
  assertSafePath(entry.archive);
15619
- const stat2 = lstatSync16(entry.archive);
16006
+ const stat2 = lstatSync17(entry.archive);
15620
16007
  if (entry.discoveryOnly ? !stat2.isFile() : !stat2.isDirectory())
15621
16008
  throw new Error("Archive type changed");
15622
16009
  if ((entry.discoveryOnly ? sha(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
@@ -15624,14 +16011,14 @@ function archiveNativeSkills(inventory, options) {
15624
16011
  };
15625
16012
  assertSafePath(archiveRoot);
15626
16013
  const created = [];
15627
- for (let path = archiveRoot;!existsSync11(path); path = dirname13(path))
16014
+ for (let path = archiveRoot;!existsSync11(path); path = dirname14(path))
15628
16015
  created.push(path);
15629
16016
  mkdirSync7(archiveRoot, { recursive: true, mode: 448 });
15630
16017
  try {
15631
16018
  for (const directory of created)
15632
16019
  syncArchiveDirectory(directory);
15633
16020
  if (created.length)
15634
- syncArchiveDirectory(dirname13(created.at(-1)));
16021
+ syncArchiveDirectory(dirname14(created.at(-1)));
15635
16022
  writeArchiveJournal(receiptPath, journal);
15636
16023
  for (const [index, entry] of selected.entries()) {
15637
16024
  recheckRootAliases(aliases);
@@ -15643,7 +16030,7 @@ function archiveNativeSkills(inventory, options) {
15643
16030
  writeArchiveJournal(receiptPath, journal);
15644
16031
  renameSync4(move.source, move.archive);
15645
16032
  moved.push(move);
15646
- syncArchiveDirectory(dirname13(move.source));
16033
+ syncArchiveDirectory(dirname14(move.source));
15647
16034
  syncArchiveDirectory(archiveRoot);
15648
16035
  verifyArchive(move);
15649
16036
  move.status = "archived";
@@ -15664,7 +16051,7 @@ function archiveNativeSkills(inventory, options) {
15664
16051
  recheckRootAliases(aliases);
15665
16052
  assertSafePath(entry.source);
15666
16053
  assertSafePath(entry.archive);
15667
- if (lstatSync16(entry.source, { throwIfNoEntry: false })) {
16054
+ if (lstatSync17(entry.source, { throwIfNoEntry: false })) {
15668
16055
  entry.status = "recovery-required";
15669
16056
  entry.conflict = "source-occupied";
15670
16057
  continue;
@@ -15678,10 +16065,10 @@ function archiveNativeSkills(inventory, options) {
15678
16065
  }
15679
16066
  if (entry.discoveryOnly) {
15680
16067
  linkSync2(entry.archive, entry.source);
15681
- syncArchiveDirectory(dirname13(entry.source));
16068
+ syncArchiveDirectory(dirname14(entry.source));
15682
16069
  unlinkSync4(entry.archive);
15683
16070
  } else {
15684
- if (lstatSync16(entry.source, { throwIfNoEntry: false })) {
16071
+ if (lstatSync17(entry.source, { throwIfNoEntry: false })) {
15685
16072
  entry.status = "recovery-required";
15686
16073
  entry.conflict = "source-occupied";
15687
16074
  continue;
@@ -15689,7 +16076,7 @@ function archiveNativeSkills(inventory, options) {
15689
16076
  renameSync4(entry.archive, entry.source);
15690
16077
  }
15691
16078
  entry.status = "restored";
15692
- syncArchiveDirectory(dirname13(entry.source));
16079
+ syncArchiveDirectory(dirname14(entry.source));
15693
16080
  syncArchiveDirectory(archiveRoot);
15694
16081
  } catch {
15695
16082
  entry.status = "recovery-required";
@@ -15728,7 +16115,7 @@ function isSkillsOwnershipMarker(marker) {
15728
16115
  function hasSkillsOwnershipMarker(dir) {
15729
16116
  const path = join26(dir, SYNC_MARKER_FILE);
15730
16117
  try {
15731
- if (!lstatSync17(path).isFile())
16118
+ if (!lstatSync18(path).isFile())
15732
16119
  return false;
15733
16120
  const marker = JSON.parse(readFileSync15(path, "utf8"));
15734
16121
  return isSkillsOwnershipMarker(marker);
@@ -15954,9 +16341,9 @@ function writeManagedSkillDir(dir, skillMd, options) {
15954
16341
  const action = dirExists ? "update" : "create";
15955
16342
  if (options.dryRun)
15956
16343
  return { action, path: skillMdPath };
15957
- const parentDir = dirname14(dir);
16344
+ const parentDir = dirname15(dir);
15958
16345
  mkdirSync8(parentDir, { recursive: true });
15959
- const transactionDir = mkdtempSync2(join26(parentDir, `.hasna-skills-write-${basename5(dir)}-`));
16346
+ const transactionDir = mkdtempSync2(join26(parentDir, `.hasna-skills-write-${basename7(dir)}-`));
15960
16347
  const candidateDir = join26(transactionDir, "candidate");
15961
16348
  const backupDir = join26(transactionDir, "backup");
15962
16349
  const candidateSkillMdPath = join26(candidateDir, "SKILL.md");
@@ -16445,7 +16832,7 @@ function loadProjectConfigCompat(targetDir) {
16445
16832
  return loadProjectConfig(targetDir);
16446
16833
  }
16447
16834
  // src/lib/run-state.ts
16448
- import { createHash as createHash13, randomBytes } from "crypto";
16835
+ import { createHash as createHash14, randomBytes } from "crypto";
16449
16836
  import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync18, readdirSync as readdirSync12, statSync as statSync10, writeFileSync as writeFileSync8 } from "fs";
16450
16837
  import { extname, join as join29, relative as relative6 } from "path";
16451
16838
  function createSkillRun(params, targetDir = process.cwd()) {
@@ -16572,7 +16959,7 @@ function collectRunArtifacts(context) {
16572
16959
  artifacts.push({
16573
16960
  path: toProjectRelative(context.targetDir, path),
16574
16961
  mime: mimeForPath(path),
16575
- sha256: createHash13("sha256").update(bytes).digest("hex"),
16962
+ sha256: createHash14("sha256").update(bytes).digest("hex"),
16576
16963
  sizeBytes: stat2.size
16577
16964
  });
16578
16965
  }
@@ -16700,7 +17087,7 @@ __export(exports_external, {
16700
17087
  pipe: () => pipe,
16701
17088
  partialRecord: () => partialRecord,
16702
17089
  parseAsync: () => parseAsync2,
16703
- parse: () => parse7,
17090
+ parse: () => parse8,
16704
17091
  overwrite: () => _overwrite,
16705
17092
  output: () => output,
16706
17093
  optional: () => optional,
@@ -16920,7 +17307,7 @@ __export(exports_core2, {
16920
17307
  prettifyError: () => prettifyError,
16921
17308
  parseURLObject: () => parseURLObject,
16922
17309
  parseAsync: () => parseAsync,
16923
- parse: () => parse5,
17310
+ parse: () => parse6,
16924
17311
  meta: () => meta,
16925
17312
  mergeValues: () => mergeValues,
16926
17313
  memoizer: () => memoizer,
@@ -18374,7 +18761,7 @@ var _parse = (_Err) => {
18374
18761
  };
18375
18762
  return fn;
18376
18763
  };
18377
- var parse5 = /* @__PURE__ */ _parse($ZodRealError);
18764
+ var parse6 = /* @__PURE__ */ _parse($ZodRealError);
18378
18765
  var _parseAsync = (_Err) => {
18379
18766
  const fn = async (schema, value, _ctx, params) => {
18380
18767
  const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
@@ -18445,18 +18832,18 @@ var validateAsync = async (schema, value, _ctx) => {
18445
18832
  return result.issues.length === 0;
18446
18833
  };
18447
18834
  var _encode = (_Err) => {
18448
- const parse6 = _parse(_Err);
18835
+ const parse7 = _parse(_Err);
18449
18836
  const fn = (schema, value, _ctx, _params) => {
18450
18837
  const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
18451
- return parse6(schema, value, ctx, finalizeParams(fn, _params));
18838
+ return parse7(schema, value, ctx, finalizeParams(fn, _params));
18452
18839
  };
18453
18840
  return fn;
18454
18841
  };
18455
18842
  var encode = /* @__PURE__ */ _encode($ZodRealError);
18456
18843
  var _decode = (_Err) => {
18457
- const parse6 = _parse(_Err);
18844
+ const parse7 = _parse(_Err);
18458
18845
  const fn = (schema, value, _ctx, _params) => {
18459
- return parse6(schema, value, _ctx, finalizeParams(fn, _params));
18846
+ return parse7(schema, value, _ctx, finalizeParams(fn, _params));
18460
18847
  };
18461
18848
  return fn;
18462
18849
  };
@@ -20101,14 +20488,14 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
20101
20488
  const shape = zod.def.shape;
20102
20489
  const propValues = {};
20103
20490
  for (const key in shape) {
20104
- const field = shape[key]._zod;
20105
- if (field.values) {
20491
+ const field2 = shape[key]._zod;
20492
+ if (field2.values) {
20106
20493
  if (!Object.prototype.hasOwnProperty.call(propValues, key)) {
20107
20494
  assignProp(propValues, key, new Set);
20108
20495
  }
20109
- for (const v of field.values)
20496
+ for (const v of field2.values)
20110
20497
  propValues[key].add(v);
20111
- if (field.optin !== undefined)
20498
+ if (field2.optin !== undefined)
20112
20499
  propValues[key].add(undefined);
20113
20500
  }
20114
20501
  }
@@ -21379,10 +21766,10 @@ var $ZodFunction = /* @__PURE__ */ $constructor("$ZodFunction", (inst, def) => {
21379
21766
  throw new Error("implement() must be called with a function");
21380
21767
  }
21381
21768
  return Object.defineProperty(function(...args) {
21382
- const parsedArgs = inst._def.input ? parse5(inst._def.input, args) : args;
21769
+ const parsedArgs = inst._def.input ? parse6(inst._def.input, args) : args;
21383
21770
  const result = Reflect.apply(func, this, parsedArgs);
21384
21771
  if (inst._def.output) {
21385
- return parse5(inst._def.output, result);
21772
+ return parse6(inst._def.output, result);
21386
21773
  }
21387
21774
  return result;
21388
21775
  }, "_zod", { value: inst._zod, enumerable: false });
@@ -32301,11 +32688,11 @@ var objectProcessor = (schema, ctx, _json, params) => {
32301
32688
  }
32302
32689
  const allKeys = new Set(Object.keys(shape));
32303
32690
  const requiredKeys = new Set([...allKeys].filter((key) => {
32304
- const field = def.shape[key];
32691
+ const field2 = def.shape[key];
32305
32692
  if (ctx.io === "input") {
32306
- return inputOptin(field) === undefined;
32693
+ return inputOptin(field2) === undefined;
32307
32694
  } else {
32308
- return field._zod.optout === undefined;
32695
+ return field2._zod.optout === undefined;
32309
32696
  }
32310
32697
  }));
32311
32698
  if (requiredKeys.size > 0) {
@@ -33020,7 +33407,7 @@ var ZodRealError = /* @__PURE__ */ $constructor("ZodError", initializer2, undefi
33020
33407
  });
33021
33408
 
33022
33409
  // node_modules/zod/v4/classic/parse.js
33023
- var parse7 = /* @__PURE__ */ _parse(ZodRealError);
33410
+ var parse8 = /* @__PURE__ */ _parse(ZodRealError);
33024
33411
  var parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
33025
33412
  var safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
33026
33413
  var safeParseAsync2 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
@@ -33156,7 +33543,7 @@ var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
33156
33543
  exports_util.own(this, "~standard", value);
33157
33544
  },
33158
33545
  parse: function _parse2(data, params) {
33159
- return parse7(this, data, params, { callee: _parse2 });
33546
+ return parse8(this, data, params, { callee: _parse2 });
33160
33547
  },
33161
33548
  parseAsync: async function _parseAsync2(data, params) {
33162
33549
  return await parseAsync2(this, data, params, { callee: _parseAsync2 });
@@ -35987,7 +36374,7 @@ function clone2(value) {
35987
36374
  return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
35988
36375
  }
35989
36376
  // src/lib/canonical-json.ts
35990
- import { createHash as createHash14 } from "crypto";
36377
+ import { createHash as createHash15 } from "crypto";
35991
36378
  function canonicalJson2(value, maximumBytes = 2 * 1024 * 1024) {
35992
36379
  return canonicalJsonAtDepth(value, maximumBytes, 64);
35993
36380
  }
@@ -36031,7 +36418,7 @@ function canonicalJsonAtDepth(value, maximumBytes, maximumDepth) {
36031
36418
  return visit3(value, 0);
36032
36419
  }
36033
36420
  function canonicalJsonSha256(value, maximumBytes) {
36034
- return createHash14("sha256").update(canonicalJson2(value, maximumBytes)).digest("hex");
36421
+ return createHash15("sha256").update(canonicalJson2(value, maximumBytes)).digest("hex");
36035
36422
  }
36036
36423
 
36037
36424
  // src/lib/remote-recurring-contract.ts
@@ -36913,15 +37300,41 @@ function object5(value) {
36913
37300
  return value;
36914
37301
  }
36915
37302
 
37303
+ // src/lib/remote-credit-checkout.ts
37304
+ var creditCheckoutKeyPattern = /^[A-Za-z0-9._:-]{8,255}$/;
37305
+ function creditCheckoutRequestKey(value) {
37306
+ if (value === undefined)
37307
+ return crypto.randomUUID();
37308
+ if (typeof value !== "string" || !creditCheckoutKeyPattern.test(value))
37309
+ throw new Error("Checkout idempotency key must be 8-255 URL-safe characters");
37310
+ return value;
37311
+ }
37312
+ var creditCheckoutMessages = {
37313
+ CREDIT_CHECKOUT_UNCONFIRMED: "Checkout creation could not be confirmed. Keep the request idempotency key, inspect billing, and use the same key for an explicit retry on the same server, account and pack. No automatic retry was made.",
37314
+ CREDIT_CHECKOUT_IN_PROGRESS: "This checkout is still being created. Wait before explicitly retrying the same server, account and pack with the same request idempotency key. No automatic retry was made.",
37315
+ CREDIT_CHECKOUT_EXPIRED: "This checkout has expired. Inspect billing before deliberately starting a new purchase with a new request idempotency key. Do not retry this checkout automatically.",
37316
+ CREDIT_CHECKOUT_FULFILLED: "This checkout is already fulfilled. Inspect the account balance; do not create another checkout to recover this purchase."
37317
+ };
37318
+ function creditCheckoutFailure(value, status, key) {
37319
+ if (!value || typeof value !== "object" || Array.isArray(value))
37320
+ return { code: "CREDIT_CHECKOUT_UNCONFIRMED" };
37321
+ const row = value;
37322
+ if (row.requestIdempotencyKey !== undefined && row.requestIdempotencyKey !== key)
37323
+ return { code: "CREDIT_CHECKOUT_UNCONFIRMED" };
37324
+ const code = status === 409 && row.error === "credit checkout in_progress" ? "CREDIT_CHECKOUT_IN_PROGRESS" : status === 409 && row.error === "credit checkout expired" && row.requestIdempotencyKey === key ? "CREDIT_CHECKOUT_EXPIRED" : status === 409 && row.error === "credit checkout fulfilled" && row.requestIdempotencyKey === key ? "CREDIT_CHECKOUT_FULFILLED" : "CREDIT_CHECKOUT_UNCONFIRMED";
37325
+ const delay = row.retryAfterSeconds;
37326
+ return { code, ...code === "CREDIT_CHECKOUT_IN_PROGRESS" || status === 503 && row.error === "credit checkout creation unresolved" ? typeof delay === "number" && Number.isSafeInteger(delay) && delay > 0 && delay <= 3600 ? { retryAfterSeconds: delay } : {} : {} };
37327
+ }
37328
+
36916
37329
  // src/lib/remote-profile.ts
36917
- function customerNamePatch(input2, field) {
36918
- if (!isRecord4(input2) || Object.keys(input2).length !== 1 || !Object.hasOwn(input2, field))
37330
+ function customerNamePatch(input2, field2) {
37331
+ if (!isRecord4(input2) || Object.keys(input2).length !== 1 || !Object.hasOwn(input2, field2))
36919
37332
  throw new Error("Provide only the requested name field.");
36920
- const value = input2[field];
37333
+ const value = input2[field2];
36921
37334
  if (typeof value !== "string" || /[\p{Cc}\p{Cs}\u2028\u2029]/u.test(value) || !value.trim() || [...value.trim()].length > 100) {
36922
37335
  throw new Error("Use a name of 1\u2013100 characters without control characters or newlines.");
36923
37336
  }
36924
- return { [field]: value.trim() };
37337
+ return { [field2]: value.trim() };
36925
37338
  }
36926
37339
  function isRecord4(value) {
36927
37340
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -36966,10 +37379,10 @@ async function readQuoteUnavailableCode(response) {
36966
37379
  let timer;
36967
37380
  let deadlineExceeded = false;
36968
37381
  const expired = Symbol("quote body deadline");
36969
- const deadline = new Promise((resolve13) => {
37382
+ const deadline = new Promise((resolve15) => {
36970
37383
  timer = setTimeout(() => {
36971
37384
  deadlineExceeded = true;
36972
- resolve13(expired);
37385
+ resolve15(expired);
36973
37386
  reader.cancel().catch(() => {});
36974
37387
  }, 1500);
36975
37388
  });
@@ -37086,6 +37499,20 @@ class RemoteRequestError extends Error {
37086
37499
  }
37087
37500
  }
37088
37501
 
37502
+ class RemoteCreditCheckoutError extends RemoteRequestError {
37503
+ code;
37504
+ requestIdempotencyKey;
37505
+ retryAfterSeconds;
37506
+ constructor(code, status, requestIdempotencyKey, retryAfterSeconds) {
37507
+ super("/api/v1/billing/credits", status);
37508
+ this.code = code;
37509
+ this.requestIdempotencyKey = requestIdempotencyKey;
37510
+ this.retryAfterSeconds = retryAfterSeconds;
37511
+ this.name = "RemoteCreditCheckoutError";
37512
+ this.message = creditCheckoutMessages[code];
37513
+ }
37514
+ }
37515
+
37089
37516
  class RemoteQuoteUnavailableError extends RemoteRequestError {
37090
37517
  code;
37091
37518
  constructor(path, code) {
@@ -37574,14 +38001,43 @@ class RemoteSkillsClient {
37574
38001
  async listCreditPacks() {
37575
38002
  return parseRemoteCreditPacks(await (await this.requestNewRoute("/api/v1/billing/credits")).json());
37576
38003
  }
37577
- async createCreditCheckout(packId) {
38004
+ async createCreditCheckout(packId, options = {}) {
38005
+ const requestIdempotencyKey = creditCheckoutRequestKey(options.idempotencyKey);
37578
38006
  const packs = await this.listCreditPacks();
37579
38007
  if (!packs.some((pack) => pack.id === packId))
37580
38008
  throw new Error("Choose a credit pack returned by skills credits packs");
37581
- return parseRemoteCheckout(await (await this.requestNewRoute("/api/v1/billing/credits", {
37582
- method: "POST",
37583
- body: JSON.stringify({ packId })
37584
- })).json());
38009
+ let response;
38010
+ try {
38011
+ response = await this.request("/api/v1/billing/credits", {
38012
+ method: "POST",
38013
+ body: JSON.stringify({ packId, idempotencyKey: requestIdempotencyKey })
38014
+ });
38015
+ } catch {
38016
+ throw new RemoteCreditCheckoutError("CREDIT_CHECKOUT_UNCONFIRMED", 0, requestIdempotencyKey);
38017
+ }
38018
+ if (response.status === 404 || response.status === 405) {
38019
+ response.body?.cancel().catch(() => {});
38020
+ throw new RemoteRouteUnsupportedError("/api/v1/billing/credits", response.status, this.apiUrl);
38021
+ }
38022
+ let value;
38023
+ try {
38024
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readBoundedResponse(response, 4096)));
38025
+ } catch {
38026
+ throw new RemoteCreditCheckoutError("CREDIT_CHECKOUT_UNCONFIRMED", response.status, requestIdempotencyKey);
38027
+ }
38028
+ if (!response.ok) {
38029
+ const failure = creditCheckoutFailure(value, response.status, requestIdempotencyKey);
38030
+ throw new RemoteCreditCheckoutError(failure.code, response.status, requestIdempotencyKey, failure.retryAfterSeconds);
38031
+ }
38032
+ try {
38033
+ const checkout = parseRemoteCheckout(value);
38034
+ const echo = value.requestIdempotencyKey;
38035
+ if (echo !== undefined && echo !== requestIdempotencyKey)
38036
+ throw new Error("checkout request key changed");
38037
+ return { ...checkout, requestIdempotencyKey };
38038
+ } catch {
38039
+ throw new RemoteCreditCheckoutError("CREDIT_CHECKOUT_UNCONFIRMED", response.status, requestIdempotencyKey);
38040
+ }
37585
38041
  }
37586
38042
  async getUsage() {
37587
38043
  return this.arrayResponse("/api/v1/billing/usage");
@@ -37814,13 +38270,13 @@ class RemoteSkillsClient {
37814
38270
  return normalizeUpdatedSincePage(await response.json());
37815
38271
  }
37816
38272
  }
37817
- function requireOptionalString(record7, field) {
37818
- if (record7[field] === undefined)
38273
+ function requireOptionalString(record7, field2) {
38274
+ if (record7[field2] === undefined)
37819
38275
  return;
37820
- if (typeof record7[field] !== "string") {
37821
- throw new Error(`Remote payload did not match the expected contract (${field} must be a string when present)`);
38276
+ if (typeof record7[field2] !== "string") {
38277
+ throw new Error(`Remote payload did not match the expected contract (${field2} must be a string when present)`);
37822
38278
  }
37823
- return record7[field];
38279
+ return record7[field2];
37824
38280
  }
37825
38281
  var INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
37826
38282
  function isVersionRecord(value) {
@@ -38154,7 +38610,7 @@ function recordScheduleRun(id, status, targetDir) {
38154
38610
  saveSchedules(data, targetDir);
38155
38611
  }
38156
38612
  // src/lib/revision.ts
38157
- import { createHash as createHash15 } from "crypto";
38613
+ import { createHash as createHash16 } from "crypto";
38158
38614
  var REVISION_ID_PATTERN = /^[0-9a-f]{64}$/;
38159
38615
  function revisionIdOf(content) {
38160
38616
  const canonical3 = JSON.stringify({
@@ -38170,14 +38626,14 @@ function revisionIdOf(content) {
38170
38626
  bundleSha256: content.bundleSha256 ?? null,
38171
38627
  bundleByteSize: content.bundleByteSize ?? null
38172
38628
  });
38173
- return createHash15("sha256").update(canonical3).digest("hex");
38629
+ return createHash16("sha256").update(canonical3).digest("hex");
38174
38630
  }
38175
38631
  function revisionIdOfRecord(record7) {
38176
38632
  return revisionIdOf(record7);
38177
38633
  }
38178
38634
  // src/lib/pull.ts
38179
38635
  import { existsSync as existsSync17, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync3, readFileSync as readFileSync20, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync10 } from "fs";
38180
- import { dirname as dirname15, join as join31 } from "path";
38636
+ import { dirname as dirname16, join as join31 } from "path";
38181
38637
 
38182
38638
  // src/lib/skill-bundles.ts
38183
38639
  import { createHmac, timingSafeEqual } from "crypto";
@@ -38519,7 +38975,7 @@ function installBundleAtomically(name, entries, options = {}, marker = {}) {
38519
38975
  try {
38520
38976
  for (const entry of entries) {
38521
38977
  const destination = join31(staging, entry.path);
38522
- mkdirSync12(dirname15(destination), { recursive: true });
38978
+ mkdirSync12(dirname16(destination), { recursive: true });
38523
38979
  writeFileSync10(destination, entry.bytes, { mode: entry.mode });
38524
38980
  }
38525
38981
  writePullMarker(staging, {
@@ -38613,7 +39069,7 @@ function splitNameVersion(raw) {
38613
39069
  return version2 ? { name, version: version2 } : { name };
38614
39070
  }
38615
39071
  // src/lib/recurring-surface.ts
38616
- import { isAbsolute as isAbsolute13 } from "path";
39072
+ import { isAbsolute as isAbsolute15 } from "path";
38617
39073
 
38618
39074
  // src/lib/remote-invitation-recovery.ts
38619
39075
  class InvitationEmailInputError extends Error {
@@ -38711,7 +39167,7 @@ async function requestInvitationEmail(origin, action, input2) {
38711
39167
  }
38712
39168
 
38713
39169
  // src/lib/remote-private-publications.ts
38714
- import { createHash as createHash16 } from "crypto";
39170
+ import { createHash as createHash17 } from "crypto";
38715
39171
  var PRIVATE_PUBLICATION_MAX_BYTES = 16 * 1024 * 1024;
38716
39172
  var failures2 = {
38717
39173
  INVALID_REQUEST: [400, "The publication request is invalid."],
@@ -38758,7 +39214,7 @@ var hash3 = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v);
38758
39214
  var record8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
38759
39215
  var exact = (v, keys) => record8(v) && Object.keys(v).sort().join(",") === keys.sort().join(",");
38760
39216
  var date5 = (v) => typeof v === "string" && /^\d{4}-\d\d-\d\dT[0-9:.]+(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(v) && Number.isFinite(Date.parse(v));
38761
- var publicationSha256 = (bytes) => createHash16("sha256").update(bytes).digest("hex");
39217
+ var publicationSha256 = (bytes) => createHash17("sha256").update(bytes).digest("hex");
38762
39218
  function checkedPublicationDeclaration(value) {
38763
39219
  if (!exact(value, ["idempotencyKey", "version", "expectedCurrentVersionId", "manifestText", "archiveSha256", "archiveByteSize"]) || !publicationUuid(value.idempotencyKey) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || typeof value.manifestText !== "string" || Buffer.byteLength(value.manifestText) > 16384 || !hash3(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES)
38764
39220
  return bad();
@@ -39003,12 +39459,12 @@ class RemotePrivatePublicationsClient {
39003
39459
  previous = view;
39004
39460
  if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
39005
39461
  return view;
39006
- await new Promise((resolve13) => {
39462
+ await new Promise((resolve15) => {
39007
39463
  const timer = setTimeout(done, Math.min(1000, until - Date.now()));
39008
39464
  function done() {
39009
39465
  clearTimeout(timer);
39010
39466
  options.signal?.removeEventListener("abort", done);
39011
- resolve13();
39467
+ resolve15();
39012
39468
  }
39013
39469
  options.signal?.addEventListener("abort", done, { once: true });
39014
39470
  });
@@ -39272,7 +39728,7 @@ var stateSchema = exports_external.object({
39272
39728
  var uuid13 = exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
39273
39729
  var context = { userId: uuid13.optional(), membershipId: uuid13.optional() };
39274
39730
  var page2 = { limit: exports_external.number().int().min(1).max(100).optional(), cursor: exports_external.string().min(1).max(2048).optional() };
39275
- var directory = exports_external.string().max(4096).refine(isAbsolute13, "Use an absolute recovery directory");
39731
+ var directory = exports_external.string().max(4096).refine(isAbsolute15, "Use an absolute recovery directory");
39276
39732
  var email4 = exports_external.string().trim().toLowerCase().email().max(254);
39277
39733
  var code = exports_external.string().regex(/^\d{6}$/);
39278
39734
  var recurringSurfaceSchemas = {
@@ -39423,7 +39879,7 @@ function findSkillsParityForMcpTool(tool) {
39423
39879
  }
39424
39880
  // src/lib/registry-sync.ts
39425
39881
  import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
39426
- import { dirname as dirname16, relative as relative7 } from "path";
39882
+ import { dirname as dirname17, relative as relative7 } from "path";
39427
39883
  function createRegistrySyncArtifact(options = {}) {
39428
39884
  const profile = options.profile ?? "all";
39429
39885
  const includeDocs = options.includeDocs ?? true;
@@ -39480,7 +39936,7 @@ function createRegistrySyncArtifact(options = {}) {
39480
39936
  };
39481
39937
  }
39482
39938
  function writeRegistrySyncArtifact(path, artifact) {
39483
- mkdirSync13(dirname16(path), { recursive: true });
39939
+ mkdirSync13(dirname17(path), { recursive: true });
39484
39940
  writeFileSync11(path, `${JSON.stringify(artifact, null, 2)}
39485
39941
  `);
39486
39942
  }
@@ -39498,7 +39954,7 @@ var REMOTE_CUSTOMER_OPERATIONS = [
39498
39954
  { name: "list_remote_skills", title: "List Remote Skills", parameter: null, mutates: false, invoke: (client2) => client2.listSkills() },
39499
39955
  { name: "get_billing_status", title: "Get Billing Status", parameter: null, mutates: false, invoke: (client2) => client2.getBillingStatus() },
39500
39956
  { name: "list_credit_packs", title: "List Credit Packs", parameter: null, mutates: false, invoke: (client2) => client2.listCreditPacks() },
39501
- { name: "create_credit_checkout", title: "Create Credit Checkout", parameter: "pack_id", mutates: true, invoke: (client2, value) => client2.createCreditCheckout(value) },
39957
+ { name: "create_credit_checkout", title: "Create Credit Checkout", parameter: "pack_id", mutates: true, invoke: (client2, value, options) => client2.createCreditCheckout(value, options) },
39502
39958
  { name: "get_billing_usage", title: "Get Billing Usage", parameter: null, mutates: false, invoke: (client2) => client2.getUsage() },
39503
39959
  { name: "list_invoices", title: "List Invoices", parameter: null, mutates: false, invoke: (client2) => client2.listInvoices() },
39504
39960
  { name: "create_billing_checkout", title: "Create Billing Checkout", parameter: null, mutates: true, invoke: (client2) => client2.createBillingCheckout() },
@@ -40320,11 +40776,11 @@ var remoteCustomerContracts = REMOTE_CUSTOMER_OPERATIONS.map((operation) => ({
40320
40776
  name: operation.name,
40321
40777
  title: operation.title,
40322
40778
  description: `${operation.title} on the configured server; unavailable capabilities fail explicitly.`,
40323
- params: operation.parameter ? [operation.parameter] : [],
40779
+ params: operation.parameter ? [operation.parameter, ...operation.name === "create_credit_checkout" ? ["idempotency_key?"] : []] : [],
40324
40780
  category: "execution",
40325
40781
  sideEffects: operation.mutates ? "local-process-or-remote-run" : "none",
40326
40782
  stable: true,
40327
- inputSchema: objectSchema(operation.parameter ? { [operation.parameter]: stringSchema("Server resource identifier.") } : {}, operation.parameter ? [operation.parameter] : []),
40783
+ inputSchema: objectSchema(operation.parameter ? { [operation.parameter]: stringSchema("Server resource identifier."), ...operation.name === "create_credit_checkout" ? { idempotency_key: { type: "string", pattern: "^[A-Za-z0-9._:-]{8,255}$", description: "Caller-owned key retained before submission; reuse only for explicit recovery of the same server/account/pack." } } : {} } : {}, operation.parameter ? [operation.parameter] : []),
40328
40784
  outputSchema: { oneOf: [objectSchema({}, [], "Server response.", true), { type: "array", items: objectSchema({}, [], "Server record.", true) }] }
40329
40785
  }));
40330
40786
  remoteCustomerContracts.push({
@@ -40565,14 +41021,14 @@ function clone3(value) {
40565
41021
  }
40566
41022
  // src/lib/feedback.ts
40567
41023
  import { appendFileSync, existsSync as existsSync18, mkdirSync as mkdirSync14 } from "fs";
40568
- import { dirname as dirname17, join as join32 } from "path";
41024
+ import { dirname as dirname18, join as join32 } from "path";
40569
41025
  import { Database } from "bun:sqlite";
40570
41026
  function getFeedbackDbPath() {
40571
41027
  return join32(getDataDir(), "skills.db");
40572
41028
  }
40573
41029
  function getFeedbackDb() {
40574
41030
  const dbPath = getFeedbackDbPath();
40575
- const dir = dirname17(dbPath);
41031
+ const dir = dirname18(dbPath);
40576
41032
  if (!existsSync18(dir))
40577
41033
  mkdirSync14(dir, { recursive: true });
40578
41034
  const db = new Database(dbPath);
@@ -40601,7 +41057,7 @@ function saveFeedback(input2) {
40601
41057
  const category = input2.category ?? "general";
40602
41058
  if (isApiMode()) {
40603
41059
  const path = join32(getDataDir(), "feedback.jsonl");
40604
- const dir = dirname17(path);
41060
+ const dir = dirname18(path);
40605
41061
  if (!existsSync18(dir))
40606
41062
  mkdirSync14(dir, { recursive: true });
40607
41063
  appendFileSync(path, JSON.stringify({ message, category, email: input2.email ?? null, agent: input2.agent ?? null, version: input2.version ?? null, createdAt: new Date().toISOString() }) + `
@@ -40624,7 +41080,7 @@ function isApiMode(env = process.env) {
40624
41080
  }
40625
41081
  }
40626
41082
  // src/lib/native-storage.ts
40627
- import { createHash as createHash17, createHmac as createHmac2 } from "crypto";
41083
+ import { createHash as createHash18, createHmac as createHmac2 } from "crypto";
40628
41084
  import {
40629
41085
  existsSync as existsSync19,
40630
41086
  mkdirSync as mkdirSync15,
@@ -40633,7 +41089,7 @@ import {
40633
41089
  statSync as statSync11,
40634
41090
  writeFileSync as writeFileSync12
40635
41091
  } from "fs";
40636
- import { dirname as dirname18, join as join33, normalize as normalize3, relative as relative8, sep as sep7 } from "path";
41092
+ import { dirname as dirname19, join as join33, normalize as normalize3, relative as relative8, sep as sep7 } from "path";
40637
41093
  var SKILLS_STORAGE_TABLES = [
40638
41094
  "skills_sync_records",
40639
41095
  "skills_sync_cursors"
@@ -40800,7 +41256,7 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
40800
41256
  files.push({
40801
41257
  path: relativePath,
40802
41258
  sizeBytes: bytes.byteLength,
40803
- sha256: createHash17("sha256").update(bytes).digest("hex"),
41259
+ sha256: createHash18("sha256").update(bytes).digest("hex"),
40804
41260
  ...options.includeFileContents ? { contentBase64: Buffer.from(bytes).toString("base64") } : {}
40805
41261
  });
40806
41262
  }
@@ -40819,18 +41275,18 @@ function importSkillsLocalSnapshot(snapshot, targetDir = process.cwd(), options
40819
41275
  skipped += 1;
40820
41276
  continue;
40821
41277
  }
40822
- const absolutePath = resolveSnapshotPath(targetDir, file2.path);
40823
- if (existsSync19(absolutePath) && !options.overwrite) {
41278
+ const absolutePath2 = resolveSnapshotPath(targetDir, file2.path);
41279
+ if (existsSync19(absolutePath2) && !options.overwrite) {
40824
41280
  skipped += 1;
40825
41281
  continue;
40826
41282
  }
40827
41283
  const bytes = Buffer.from(file2.contentBase64, "base64");
40828
- const hash4 = createHash17("sha256").update(bytes).digest("hex");
41284
+ const hash4 = createHash18("sha256").update(bytes).digest("hex");
40829
41285
  if (hash4 !== file2.sha256) {
40830
41286
  throw new Error(`Snapshot file checksum mismatch: ${file2.path}`);
40831
41287
  }
40832
- mkdirSync15(dirname18(absolutePath), { recursive: true });
40833
- writeFileSync12(absolutePath, bytes);
41288
+ mkdirSync15(dirname19(absolutePath2), { recursive: true });
41289
+ writeFileSync12(absolutePath2, bytes);
40834
41290
  written += 1;
40835
41291
  }
40836
41292
  return { written, skipped };
@@ -41157,7 +41613,7 @@ function toIsoString(value) {
41157
41613
  return Number.isNaN(date6.getTime()) ? value : date6.toISOString();
41158
41614
  }
41159
41615
  function sha256Hex2(value) {
41160
- return createHash17("sha256").update(value).digest("hex");
41616
+ return createHash18("sha256").update(value).digest("hex");
41161
41617
  }
41162
41618
  function normalizeHeaders(headers) {
41163
41619
  const result = {};
@@ -41209,7 +41665,7 @@ function toArrayBuffer(bytes) {
41209
41665
  return buffer;
41210
41666
  }
41211
41667
  // src/lib/station-snapshot.ts
41212
- import { createHash as createHash18 } from "crypto";
41668
+ import { createHash as createHash19 } from "crypto";
41213
41669
  import {
41214
41670
  copyFileSync,
41215
41671
  mkdirSync as mkdirSync16,
@@ -41217,7 +41673,7 @@ import {
41217
41673
  statSync as statSync13,
41218
41674
  writeFileSync as writeFileSync13
41219
41675
  } from "fs";
41220
- import { dirname as dirname19, isAbsolute as isAbsolute14, relative as relative9, resolve as resolve13, sep as sep9 } from "path";
41676
+ import { dirname as dirname20, isAbsolute as isAbsolute16, relative as relative9, resolve as resolve15, sep as sep9 } from "path";
41221
41677
 
41222
41678
  // src/lib/portable-snapshot-filter.ts
41223
41679
  import { readdirSync as readdirSync14, statSync as statSync12 } from "fs";
@@ -41384,7 +41840,7 @@ function validateStationId(stationId) {
41384
41840
  }
41385
41841
  }
41386
41842
  function sha256File(filePath) {
41387
- return createHash18("sha256").update(readFileSync23(filePath)).digest("hex");
41843
+ return createHash19("sha256").update(readFileSync23(filePath)).digest("hex");
41388
41844
  }
41389
41845
  function scanHome(definition, homesRoot) {
41390
41846
  const homePath = homePathFor(definition, homesRoot);
@@ -41455,7 +41911,7 @@ function humanHomes(scanned) {
41455
41911
  }));
41456
41912
  }
41457
41913
  function writeStationSnapshot(options) {
41458
- const repoRoot = resolve13(options.repoRoot ?? process.cwd());
41914
+ const repoRoot = resolve15(options.repoRoot ?? process.cwd());
41459
41915
  const { scanned, plans, totalBytes } = planStationSnapshot(options);
41460
41916
  const manifestFiles = plans.map((plan) => ({
41461
41917
  relativePath: plan.source.relativePath,
@@ -41481,9 +41937,9 @@ function writeStationSnapshot(options) {
41481
41937
  const conflicts = [];
41482
41938
  const untouched = [];
41483
41939
  for (const plan of plans) {
41484
- const destination = resolve13(repoRoot, plan.destination);
41940
+ const destination = resolve15(repoRoot, plan.destination);
41485
41941
  const destinationRelative = relative9(repoRoot, destination);
41486
- if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep9) || isAbsolute14(destinationRelative)) {
41942
+ if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep9) || isAbsolute16(destinationRelative)) {
41487
41943
  throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
41488
41944
  }
41489
41945
  let existingDigest = null;
@@ -41504,8 +41960,8 @@ function writeStationSnapshot(options) {
41504
41960
  }
41505
41961
  let written = 0;
41506
41962
  for (const plan of untouched) {
41507
- const destination = resolve13(repoRoot, plan.destination);
41508
- mkdirSync16(dirname19(destination), { recursive: true });
41963
+ const destination = resolve15(repoRoot, plan.destination);
41964
+ mkdirSync16(dirname20(destination), { recursive: true });
41509
41965
  copyFileSync(plan.source.fullPath, destination);
41510
41966
  written += 1;
41511
41967
  }
@@ -41523,8 +41979,8 @@ function writeStationSnapshot(options) {
41523
41979
  },
41524
41980
  files: manifestFiles
41525
41981
  };
41526
- const manifestPath = resolve13(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
41527
- mkdirSync16(dirname19(manifestPath), { recursive: true });
41982
+ const manifestPath = resolve15(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
41983
+ mkdirSync16(dirname20(manifestPath), { recursive: true });
41528
41984
  writeFileSync13(manifestPath, `${JSON.stringify(manifest, null, 2)}
41529
41985
  `);
41530
41986
  return {
@@ -41535,7 +41991,7 @@ function writeStationSnapshot(options) {
41535
41991
  };
41536
41992
  }
41537
41993
  // src/lib/station-hydrate.ts
41538
- import { createHash as createHash19 } from "crypto";
41994
+ import { createHash as createHash20 } from "crypto";
41539
41995
  import {
41540
41996
  copyFileSync as copyFileSync2,
41541
41997
  mkdirSync as mkdirSync17,
@@ -41544,7 +42000,7 @@ import {
41544
42000
  statSync as statSync14,
41545
42001
  writeFileSync as writeFileSync14
41546
42002
  } from "fs";
41547
- import { dirname as dirname20, join as join35, resolve as resolve14, sep as sep10 } from "path";
42003
+ import { dirname as dirname21, join as join35, resolve as resolve16, sep as sep10 } from "path";
41548
42004
  var STATION_HYDRATION_MANIFEST_SCHEMA = "hasna.fleet-resources.skills-hydration-manifest/v1";
41549
42005
  var STATION_HYDRATION_PRODUCER = { name: "@hasna/skills", version: package_default.version };
41550
42006
  var MANIFEST_HASH_KEY_SEP = String.fromCharCode(0);
@@ -41741,12 +42197,12 @@ function skillSha256(skill) {
41741
42197
  return sha256File(skill.files[0].winner.fullPath);
41742
42198
  }
41743
42199
  const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
41744
- return createHash19("sha256").update(joined.sort().join(`
42200
+ return createHash20("sha256").update(joined.sort().join(`
41745
42201
  `)).digest("hex");
41746
42202
  }
41747
42203
  function writeStationHydration(options) {
41748
- const repoRoot = resolve14(options.repoRoot ?? process.cwd());
41749
- const cacheRoot = resolve14(options.cacheRoot ?? resolveCorpusRoot());
42204
+ const repoRoot = resolve16(options.repoRoot ?? process.cwd());
42205
+ const cacheRoot = resolve16(options.cacheRoot ?? resolveCorpusRoot());
41750
42206
  const plan = planStationHydration(options.stationId, repoRoot);
41751
42207
  const resultSkills = plan.winners.map((skill) => ({
41752
42208
  ident: skill.ident,
@@ -41800,7 +42256,7 @@ function writeStationHydration(options) {
41800
42256
  }
41801
42257
  let written = 0;
41802
42258
  for (const entry of toWrite) {
41803
- mkdirSync17(dirname20(entry.destination), { recursive: true });
42259
+ mkdirSync17(dirname21(entry.destination), { recursive: true });
41804
42260
  copyFileSync2(entry.fullPath, entry.destination);
41805
42261
  written += 1;
41806
42262
  }
@@ -41821,8 +42277,8 @@ function writeStationHydration(options) {
41821
42277
  },
41822
42278
  skills: resultSkills
41823
42279
  };
41824
- const hydrationManifestPath = join35(dirname20(cacheRoot), `hydration-${options.stationId}.json`);
41825
- mkdirSync17(dirname20(hydrationManifestPath), { recursive: true });
42280
+ const hydrationManifestPath = join35(dirname21(cacheRoot), `hydration-${options.stationId}.json`);
42281
+ mkdirSync17(dirname21(hydrationManifestPath), { recursive: true });
41826
42282
  writeFileSync14(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
41827
42283
  `);
41828
42284
  return {
@@ -41833,23 +42289,23 @@ function writeStationHydration(options) {
41833
42289
  };
41834
42290
  }
41835
42291
  // src/lib/private-publication-recovery.ts
41836
- import { constants as constants9, closeSync as closeSync10, fsyncSync as fsyncSync3, fstatSync as fstatSync10, lstatSync as lstatSync18, mkdirSync as mkdirSync18, openSync as openSync10, readSync as readSync8, realpathSync as realpathSync6, renameSync as renameSync7, unlinkSync as unlinkSync5, writeFileSync as writeFileSync15 } from "fs";
41837
- import { dirname as dirname21, isAbsolute as isAbsolute15, join as join36, resolve as resolve15 } from "path";
42292
+ import { constants as constants10, closeSync as closeSync11, fsyncSync as fsyncSync4, fstatSync as fstatSync11, lstatSync as lstatSync19, mkdirSync as mkdirSync18, openSync as openSync11, readSync as readSync9, realpathSync as realpathSync6, renameSync as renameSync7, unlinkSync as unlinkSync5, writeFileSync as writeFileSync15 } from "fs";
42293
+ import { dirname as dirname22, isAbsolute as isAbsolute17, join as join36, resolve as resolve17 } from "path";
41838
42294
  import { randomUUID as randomUUID4 } from "crypto";
41839
42295
  var fail2 = () => {
41840
42296
  throw new PrivatePublicationError("PUBLICATION_RECOVERY_INVALID", "The recovery directory is invalid or changed. Preserve it and inspect the existing intent; do not start a replacement automatically.");
41841
42297
  };
41842
42298
  function safeDirectory(directory2) {
41843
- if (!isAbsolute15(directory2) || directory2 !== resolve15(directory2) || realpathSync6(directory2) !== directory2)
42299
+ if (!isAbsolute17(directory2) || directory2 !== resolve17(directory2) || realpathSync6(directory2) !== directory2)
41844
42300
  return fail2();
41845
- for (let path = directory2;; path = dirname21(path)) {
41846
- const stat2 = lstatSync18(path);
42301
+ for (let path = directory2;; path = dirname22(path)) {
42302
+ const stat2 = lstatSync19(path);
41847
42303
  if (!stat2.isDirectory() || stat2.isSymbolicLink())
41848
42304
  return fail2();
41849
- if (path === dirname21(path))
42305
+ if (path === dirname22(path))
41850
42306
  break;
41851
42307
  }
41852
- const own2 = lstatSync18(directory2);
42308
+ const own2 = lstatSync19(directory2);
41853
42309
  if ((own2.mode & 63) !== 0 || process.getuid && own2.uid !== process.getuid())
41854
42310
  return fail2();
41855
42311
  return { dev: own2.dev, ino: own2.ino };
@@ -41861,35 +42317,35 @@ function unchangedDirectory(directory2, identity3) {
41861
42317
  }
41862
42318
  function readOwned(directory2, name, max) {
41863
42319
  const identity3 = safeDirectory(directory2), file2 = join36(directory2, name);
41864
- const fd = openSync10(file2, constants9.O_RDONLY | constants9.O_NOFOLLOW);
42320
+ const fd = openSync11(file2, constants10.O_RDONLY | constants10.O_NOFOLLOW);
41865
42321
  try {
41866
- const stat2 = fstatSync10(fd);
42322
+ const stat2 = fstatSync11(fd);
41867
42323
  if (!stat2.isFile() || stat2.nlink !== 1 || stat2.size > max || stat2.size < 1 || (stat2.mode & 63) !== 0 || process.getuid && stat2.uid !== process.getuid())
41868
42324
  return fail2();
41869
42325
  const buffer = Buffer.alloc(Math.min(max, stat2.size) + 1);
41870
42326
  let length = 0;
41871
42327
  while (length < buffer.length) {
41872
- const count = readSync8(fd, buffer, length, buffer.length - length, length);
42328
+ const count = readSync9(fd, buffer, length, buffer.length - length, length);
41873
42329
  if (count === 0)
41874
42330
  break;
41875
42331
  length += count;
41876
42332
  }
41877
- const bytes = buffer.subarray(0, length), after = fstatSync10(fd);
42333
+ const bytes = buffer.subarray(0, length), after = fstatSync11(fd);
41878
42334
  unchangedDirectory(directory2, identity3);
41879
42335
  if (bytes.length !== stat2.size || stat2.size !== after.size || stat2.mtimeMs !== after.mtimeMs || stat2.ctimeMs !== after.ctimeMs)
41880
42336
  return fail2();
41881
42337
  return bytes;
41882
42338
  } finally {
41883
- closeSync10(fd);
42339
+ closeSync11(fd);
41884
42340
  }
41885
42341
  }
41886
42342
  function writeOwned(directory2, name, bytes) {
41887
- const fd = openSync10(join36(directory2, name), constants9.O_WRONLY | constants9.O_CREAT | constants9.O_EXCL | constants9.O_NOFOLLOW, 384);
42343
+ const fd = openSync11(join36(directory2, name), constants10.O_WRONLY | constants10.O_CREAT | constants10.O_EXCL | constants10.O_NOFOLLOW, 384);
41888
42344
  try {
41889
42345
  writeFileSync15(fd, bytes);
41890
- fsyncSync3(fd);
42346
+ fsyncSync4(fd);
41891
42347
  } finally {
41892
- closeSync10(fd);
42348
+ closeSync11(fd);
41893
42349
  }
41894
42350
  }
41895
42351
  function save(directory2, value) {
@@ -41898,11 +42354,11 @@ function save(directory2, value) {
41898
42354
  `);
41899
42355
  unchangedDirectory(directory2, identity3);
41900
42356
  renameSync7(join36(directory2, temporary), join36(directory2, "receipt.json"));
41901
- const fd = openSync10(directory2, constants9.O_RDONLY | constants9.O_NOFOLLOW);
42357
+ const fd = openSync11(directory2, constants10.O_RDONLY | constants10.O_NOFOLLOW);
41902
42358
  try {
41903
- fsyncSync3(fd);
42359
+ fsyncSync4(fd);
41904
42360
  } finally {
41905
- closeSync10(fd);
42361
+ closeSync11(fd);
41906
42362
  }
41907
42363
  }
41908
42364
  function bind(client2, receipt) {
@@ -41913,20 +42369,20 @@ async function locked(directory2, action) {
41913
42369
  const identity3 = safeDirectory(directory2), file2 = join36(directory2, "operation.lock");
41914
42370
  let fd;
41915
42371
  try {
41916
- fd = openSync10(file2, constants9.O_WRONLY | constants9.O_CREAT | constants9.O_EXCL | constants9.O_NOFOLLOW, 384);
42372
+ fd = openSync11(file2, constants10.O_WRONLY | constants10.O_CREAT | constants10.O_EXCL | constants10.O_NOFOLLOW, 384);
41917
42373
  } catch {
41918
42374
  throw new PrivatePublicationError("PUBLICATION_RECOVERY_BUSY", "Another operation holds this recovery directory. If it crashed, confirm that process has stopped before explicitly removing operation.lock and resuming.");
41919
42375
  }
41920
- const lock = fstatSync10(fd);
42376
+ const lock = fstatSync11(fd);
41921
42377
  try {
41922
42378
  writeFileSync15(fd, JSON.stringify({ pid: process.pid }) + `
41923
42379
  `);
41924
- fsyncSync3(fd);
42380
+ fsyncSync4(fd);
41925
42381
  return await action();
41926
42382
  } finally {
41927
- closeSync10(fd);
42383
+ closeSync11(fd);
41928
42384
  unchangedDirectory(directory2, identity3);
41929
- const now = lstatSync18(file2);
42385
+ const now = lstatSync19(file2);
41930
42386
  if (now.dev !== lock.dev || now.ino !== lock.ino || !now.isFile() || now.isSymbolicLink())
41931
42387
  fail2();
41932
42388
  unlinkSync5(file2);
@@ -41978,15 +42434,15 @@ async function preparePrivatePublication(client2, sourceDirectory, recoveryDirec
41978
42434
  phase: "prepared",
41979
42435
  intent: null
41980
42436
  };
41981
- if (!isAbsolute15(recoveryDirectory) || resolve15(recoveryDirectory) !== recoveryDirectory || realpathSync6(dirname21(recoveryDirectory)) !== dirname21(recoveryDirectory))
42437
+ if (!isAbsolute17(recoveryDirectory) || resolve17(recoveryDirectory) !== recoveryDirectory || realpathSync6(dirname22(recoveryDirectory)) !== dirname22(recoveryDirectory))
41982
42438
  return fail2();
41983
42439
  mkdirSync18(recoveryDirectory, { mode: 448 });
41984
42440
  safeDirectory(recoveryDirectory);
41985
- const parent = openSync10(dirname21(recoveryDirectory), constants9.O_RDONLY | constants9.O_NOFOLLOW);
42441
+ const parent = openSync11(dirname22(recoveryDirectory), constants10.O_RDONLY | constants10.O_NOFOLLOW);
41986
42442
  try {
41987
- fsyncSync3(parent);
42443
+ fsyncSync4(parent);
41988
42444
  } finally {
41989
- closeSync10(parent);
42445
+ closeSync11(parent);
41990
42446
  }
41991
42447
  writeOwned(recoveryDirectory, "bundle.tgz", packed.bytes);
41992
42448
  save(recoveryDirectory, receipt);
@@ -42069,8 +42525,8 @@ async function inspectLocked(client2, directory2, cancel) {
42069
42525
  return privatePublicationResult(directory2, receipt);
42070
42526
  }
42071
42527
  // src/lib/skill-context.ts
42072
- import { createHash as createHash20 } from "crypto";
42073
- import { resolve as resolve16 } from "path";
42528
+ import { createHash as createHash21 } from "crypto";
42529
+ import { resolve as resolve18 } from "path";
42074
42530
  function words(value) {
42075
42531
  return new Set(value.toLowerCase().match(/[a-z0-9]+/g) ?? []);
42076
42532
  }
@@ -42105,7 +42561,7 @@ async function buildSkillContext(input2, options = {}) {
42105
42561
  const promptWords = words(prompt);
42106
42562
  const promptNames = new Set(prompt.toLowerCase().match(/[a-z0-9]+(?:-[a-z0-9]+)*/g) ?? []);
42107
42563
  const loaded = new Set(resolved.session?.loaded ?? []);
42108
- const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd, resolve16(input2.cwd)] : []];
42564
+ const paths = [...input2.paths ?? [], ...input2.cwd ? [input2.cwd, resolve18(input2.cwd)] : []];
42109
42565
  const candidates = profile.selections.map((selection) => {
42110
42566
  const key = selectionKey(selection);
42111
42567
  if (input2.restore && loaded.has(key))
@@ -42158,7 +42614,7 @@ ${content}`;
42158
42614
  `);
42159
42615
  const receipt = {
42160
42616
  schemaVersion: 1,
42161
- id: createHash20("sha256").update(JSON.stringify({ profile: profile.profileRevision, sessionId, selections, omitted })).digest("hex"),
42617
+ id: createHash21("sha256").update(JSON.stringify({ profile: profile.profileRevision, sessionId, selections, omitted })).digest("hex"),
42162
42618
  profileId: profile.profileId,
42163
42619
  profileRevision: profile.profileRevision,
42164
42620
  authority: profile.authority,
@@ -42170,9 +42626,105 @@ ${content}`;
42170
42626
  selections
42171
42627
  };
42172
42628
  if (sessionId)
42173
- writeSelectionJson(sessionReceiptPath(sessionId, options), { ...resolved.receipt, sessionId, loaded: [...loaded] });
42629
+ writeSkillSession({ ...resolved.receipt, sessionId, loaded: [...loaded] }, { current: resolved.sessionSnapshot, parent: resolved.parentSnapshot }, options);
42174
42630
  return { context: context2, selections, omitted, receipt };
42175
42631
  }
42632
+ // src/lib/session-reconciliation.ts
42633
+ import { createHash as createHash22 } from "crypto";
42634
+ var SESSION_RECONCILIATION_PLAN_TTL_MS = 5 * 60 * 1000;
42635
+ var digest3 = (value) => createHash22("sha256").update(JSON.stringify(value)).digest("hex");
42636
+ function exactTimestamp(value) {
42637
+ const parsed = value === undefined ? Number.NaN : Date.parse(value);
42638
+ if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) {
42639
+ throw new SkillSelectionError("SESSION_PLAN_WINDOW_INVALID", "The reviewed plan must include its exact canonical issuedAt and expiresAt timestamps.");
42640
+ }
42641
+ return parsed;
42642
+ }
42643
+ function assertPlanWindow(issuedAt, expiresAt, now) {
42644
+ const issued = exactTimestamp(issuedAt), expires = exactTimestamp(expiresAt);
42645
+ if (expires - issued !== SESSION_RECONCILIATION_PLAN_TTL_MS) {
42646
+ throw new SkillSelectionError("SESSION_PLAN_WINDOW_INVALID", "A session reconciliation approval is valid for exactly five minutes and cannot be extended by the caller.");
42647
+ }
42648
+ if (now < issued || now >= expires) {
42649
+ throw new SkillSelectionError("SESSION_PLAN_EXPIRED", "The reviewed session reconciliation plan is not currently valid; inspect the receipt and prepare a new plan.");
42650
+ }
42651
+ }
42652
+ function inspectSkillSession(sessionId, options = {}) {
42653
+ const snapshot = readSkillSessionSnapshot(sessionId, options), { receipt } = snapshot;
42654
+ return {
42655
+ sessionId,
42656
+ path: snapshot.path,
42657
+ receiptSha256: snapshot.sha256,
42658
+ generation: snapshot.generation,
42659
+ verifiedAt: receipt.verifiedAt,
42660
+ authority: receipt.profile.authority,
42661
+ workspaceId: receipt.profile.workspaceId,
42662
+ profileId: receipt.profile.profileId,
42663
+ profileRevision: receipt.profile.profileRevision,
42664
+ selectionCount: receipt.profile.selections.length,
42665
+ loadedCount: receipt.loaded.length
42666
+ };
42667
+ }
42668
+ async function reconcileSkillSession(input2, options = {}) {
42669
+ if (![input2.fromProfile, input2.fromRevision, input2.selectionProfile, input2.profileRevision].every((value) => typeof value === "string" && value.trim()) || !/^[a-f0-9]{64}$/.test(input2.receiptSha256)) {
42670
+ throw new SkillSelectionError("INVALID_SESSION_RECONCILIATION", "Name the exact old receipt SHA256, old profile/revision and intended target profile/revision.");
42671
+ }
42672
+ if (input2.apply && (!input2.planDigest || !input2.planIssuedAt || !input2.planExpiresAt)) {
42673
+ throw new SkillSelectionError("SESSION_PLAN_REQUIRED", "Review the session reconciliation plan and provide its exact digest, issuedAt and expiresAt before applying it.");
42674
+ }
42675
+ if (!input2.apply && (input2.planDigest !== undefined || input2.planIssuedAt !== undefined || input2.planExpiresAt !== undefined)) {
42676
+ throw new SkillSelectionError("SESSION_PLAN_WINDOW_INVALID", "Plan timestamps are minted by the planner and may only be replayed with --apply.");
42677
+ }
42678
+ const now = (options.now ?? Date.now)();
42679
+ const issuedAt = input2.apply ? input2.planIssuedAt : new Date(now).toISOString();
42680
+ const expiresAt = input2.apply ? input2.planExpiresAt : new Date(now + SESSION_RECONCILIATION_PLAN_TTL_MS).toISOString();
42681
+ if (input2.apply)
42682
+ assertPlanWindow(issuedAt, expiresAt, now);
42683
+ const snapshot = readSkillSessionSnapshot(input2.sessionId, options), old = snapshot.receipt;
42684
+ if (snapshot.sha256 !== input2.receiptSha256 || old.profile.profileId !== input2.fromProfile || old.profile.profileRevision !== input2.fromRevision) {
42685
+ throw new SkillSelectionError("SESSION_RECEIPT_CHANGED", "The current session receipt does not match the reviewed old bytes, profile and revision.");
42686
+ }
42687
+ const client2 = options.client ?? await createProfileClient();
42688
+ const target = structuredClone(await client2.resolveProfile(input2.selectionProfile));
42689
+ validateResolvedProfile(target, client2.authority);
42690
+ if (target.profileId !== input2.selectionProfile || target.profileRevision !== input2.profileRevision) {
42691
+ throw new SkillSelectionError("SESSION_TARGET_CHANGED", "The API selection profile does not match the intended target revision; review the current selection before proceeding.");
42692
+ }
42693
+ if (old.profile.authority !== target.authority || old.profile.workspaceId !== target.workspaceId) {
42694
+ throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "A session reconciliation cannot change its Skills authority or workspace.");
42695
+ }
42696
+ const targetKeys = new Set(target.selections.map(selectionKey));
42697
+ const loaded = old.loaded.filter((key) => targetKeys.has(key));
42698
+ const replacementGeneration = nextSkillSessionGeneration(snapshot.generation);
42699
+ const plan = {
42700
+ schemaVersion: 1,
42701
+ issuedAt,
42702
+ expiresAt,
42703
+ sessionId: input2.sessionId,
42704
+ sessionPath: snapshot.path,
42705
+ before: { receiptSha256: snapshot.sha256, generation: snapshot.generation, profileId: old.profile.profileId, profileRevision: old.profile.profileRevision, selectionCount: old.profile.selections.length },
42706
+ target: { authority: target.authority, workspaceId: target.workspaceId, profileId: target.profileId, profileRevision: target.profileRevision, profileSha256: digest3(target), selectionCount: target.selections.length },
42707
+ replacementGeneration,
42708
+ retainedLoadedCount: loaded.length,
42709
+ retiredLoadedCount: old.loaded.length - loaded.length,
42710
+ scope: "Only this receipt; existing child sessions, project locks, shared profiles, hooks and running processes are unchanged."
42711
+ };
42712
+ const planDigest = digest3(plan);
42713
+ if (input2.planDigest !== undefined && input2.planDigest !== planDigest)
42714
+ throw new SkillSelectionError("SESSION_PLAN_CHANGED", "The session reconciliation plan differs from the reviewed plan.");
42715
+ if (!input2.apply)
42716
+ return { applied: false, plan, planDigest, archivePath: undefined, receiptPath: undefined, beforeSha256: snapshot.sha256, afterSha256: undefined };
42717
+ assertPlanWindow(issuedAt, expiresAt, (options.now ?? Date.now)());
42718
+ const replacement = {
42719
+ ...old,
42720
+ generation: replacementGeneration,
42721
+ verifiedAt: new Date((options.now ?? Date.now)()).toISOString(),
42722
+ profile: target,
42723
+ loaded
42724
+ };
42725
+ const result = replaceSkillSession(skillSessionSnapshotBinding(snapshot), replacement, { ...plan, planDigest }, options, () => assertPlanWindow(issuedAt, expiresAt, (options.now ?? Date.now)()));
42726
+ return { applied: true, plan, planDigest, ...result };
42727
+ }
42176
42728
  export {
42177
42729
  writeStationSnapshot,
42178
42730
  writeStationHydration,
@@ -42237,6 +42789,7 @@ export {
42237
42789
  removeSchedule,
42238
42790
  removeManagedAgentSkill,
42239
42791
  recordScheduleRun,
42792
+ reconcileSkillSession,
42240
42793
  readPrivatePublicationRecovery,
42241
42794
  readPortableSkillManifest,
42242
42795
  pullSkills,
@@ -42292,6 +42845,7 @@ export {
42292
42845
  installSkillManifest,
42293
42846
  installSkillForAgent,
42294
42847
  installSkill,
42848
+ inspectSkillSession,
42295
42849
  inspectPrivatePublication,
42296
42850
  importSkillsLocalSnapshot,
42297
42851
  homePathFor,
@@ -42367,6 +42921,7 @@ export {
42367
42921
  captureDiscoveryPathSources,
42368
42922
  captureDiscoveryDirectories,
42369
42923
  captureDiscoveryByteSources,
42924
+ captureClaudeMarketplaceRegistry,
42370
42925
  canonicalizeManifest,
42371
42926
  buildSkillsS3ObjectUrl,
42372
42927
  buildSkillsApiUrl,
@@ -42430,6 +42985,7 @@ export {
42430
42985
  RemotePrivatePublicationsClient,
42431
42986
  RemoteInvitationEmailUnconfirmedError,
42432
42987
  RemoteInvitationEmailError,
42988
+ RemoteCreditCheckoutError,
42433
42989
  RemoteCreditApprovalError,
42434
42990
  RemoteCapabilityUnavailableError,
42435
42991
  REMOTE_SKILL_RUN_CONTRACT_VERSION,