@hasna/skills 0.10.16 → 0.10.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -466,8 +466,18 @@ preserves and reads back the original policy before writing. Claude upgrades to
466
466
  verbosity preferences. Provider mappings, environment, hooks, plugins, skill rules
467
467
  and all unknown settings stay bound. Runtime hooks never silently upgrade a witness.
468
468
 
469
- A reviewed Codex 0.159.2 native `skills/list` receipt can be passed to `hook install`
470
- with `--codex-native-catalog <file>`. Its safe shape is `{version,cwd,skills}`;
469
+ A Codex 0.159.2 native `skills/list` receipt can be captured through the
470
+ package-owned client into a new private file, then reviewed and passed to
471
+ `hook install` with `--codex-native-catalog <file>`:
472
+
473
+ ```bash
474
+ skills hook native-catalog --cwd /absolute/project --output /private/catalog.json --json
475
+ skills hook install --agent codex --codex-native-catalog /private/catalog.json --json
476
+ ```
477
+
478
+ The capture refuses an existing output file, writes mode `0600`, verifies the
479
+ exact bytes, and reports only version, directory, count, file path and SHA-256.
480
+ Its safe catalog shape is `{version,cwd,skills}`;
471
481
  each skill has `{name,path,enabled,pluginId}`. The installer matches the native
472
482
  qualified name against a bounded explicit frontmatter name and owning plugin
473
483
  manifest. It preserves path disables and adds exact qualified name disables.
@@ -480,8 +490,9 @@ Only manifest version metadata may vary. Reviewed app declarations use the impli
480
490
  file and its presence stay bound; it preserves existing connector availability and
481
491
  native capability hints. Changed or unknown app fields, custom references, native
482
492
  hooks, commands and MCP controls refuse. Name controls suppress Skills, not every
483
- plugin capability. No prompt hook starts an app server to obtain this receipt: native
484
- startup can maintain caches. Capture it through an authorized isolated consumer.
493
+ plugin capability. No prompt hook starts an app server to obtain this receipt:
494
+ native startup can maintain caches. The explicit capture starts and closes only its
495
+ own bounded native client; it does not reload an existing agent session.
485
496
  Neither a receipt nor a config write proves an existing session adopted new rules;
486
497
  use the actual native session's supported acceptance path separately.
487
498
 
package/bin/index.js CHANGED
@@ -36868,7 +36868,7 @@ var package_default;
36868
36868
  var init_package = __esm(() => {
36869
36869
  package_default = {
36870
36870
  name: "@hasna/skills",
36871
- version: "0.10.16",
36871
+ version: "0.10.19",
36872
36872
  description: "Skills library for AI coding agents",
36873
36873
  type: "module",
36874
36874
  bin: {
@@ -51052,6 +51052,24 @@ function projectCodexNativeSkillCatalog(response, cwd2) {
51052
51052
  return { name: value.name, path: value.path, enabled: value.enabled, pluginId: value.pluginId ?? null };
51053
51053
  });
51054
51054
  }
51055
+ async function captureCodexNativeSkillCatalog(options, connect = connectCodexHookRpc) {
51056
+ if (!absolutePath2(options.cwd) || !absolutePath2(options.home))
51057
+ refuse();
51058
+ const rpc = await connect({
51059
+ command: options.command,
51060
+ home: options.home,
51061
+ ...options.codexHome === undefined ? {} : { codexHome: options.codexHome },
51062
+ ...options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }
51063
+ });
51064
+ try {
51065
+ if (!SUPPORTED_NAME_CONTROL_VERSIONS.has(rpc.version))
51066
+ throw new Error("CODEX_NATIVE_SKILL_CATALOG_UNSUPPORTED_VERSION");
51067
+ const response = await rpc.request("skills/list", { cwds: [options.cwd], forceReload: true });
51068
+ return { version: rpc.version, cwd: options.cwd, skills: projectCodexNativeSkillCatalog(response, options.cwd) };
51069
+ } finally {
51070
+ await rpc.close();
51071
+ }
51072
+ }
51055
51073
  function canonicalPath(path) {
51056
51074
  try {
51057
51075
  return realpathSync4(path);
@@ -108698,13 +108716,13 @@ function comparePackageTrees(expectedRoot, installedRoot) {
108698
108716
  throw new Error("INSTALLED_PACKAGE_BYTES_MISMATCH");
108699
108717
  return expected.digest;
108700
108718
  }
108701
- function replaceLink(path, target, backupPath, oldLinkTarget) {
108719
+ function replaceLink(path, target, backupPath, oldLinkTarget, oldTarget = resolve23(dirname30(path), oldLinkTarget)) {
108702
108720
  const stat3 = lstatSync28(path);
108703
108721
  if (!stat3.isSymbolicLink() || entryExists(backupPath))
108704
108722
  throw new Error("LAUNCHER_PREIMAGE_DRIFT");
108705
108723
  const temp = `${path}.skills-next-${randomUUID9()}`;
108706
108724
  symlinkSync(oldLinkTarget, backupPath);
108707
- if (readlinkSync5(backupPath) !== oldLinkTarget || realpathSync14(backupPath) !== resolve23(dirname30(path), oldLinkTarget))
108725
+ if (readlinkSync5(backupPath) !== oldLinkTarget || realpathSync14(backupPath) !== oldTarget)
108708
108726
  throw new Error("LAUNCHER_BACKUP_READBACK_MISMATCH");
108709
108727
  symlinkSync(target, temp);
108710
108728
  if (realpathSync14(temp) !== target)
@@ -108745,6 +108763,324 @@ function persistReceipt(path, value) {
108745
108763
  }
108746
108764
  atomicJson(path, value);
108747
108765
  }
108766
+ function persistAliasReceipt(path, value) {
108767
+ if (entryExists(path)) {
108768
+ const priorBytes = readFileSync37(path);
108769
+ const prior = object9(JSON.parse(priorBytes.toString("utf8")), "ALIAS_RECEIPT_INVALID");
108770
+ if (prior.schema !== value.schema || prior.id !== value.id || typeof prior.state !== "string")
108771
+ throw new Error("ALIAS_RECEIPT_DRIFT");
108772
+ const nextBytes = Buffer.from(`${JSON.stringify(value, null, 2)}
108773
+ `);
108774
+ if (priorBytes.equals(nextBytes))
108775
+ return;
108776
+ const history = join55(dirname30(path), "history");
108777
+ if (!entryExists(history))
108778
+ mkdirSync24(history, { mode: 448 });
108779
+ const historyStat = lstatSync28(history);
108780
+ if (!historyStat.isDirectory() || historyStat.isSymbolicLink() || (historyStat.mode & 63) !== 0)
108781
+ throw new Error("ALIAS_RECEIPT_HISTORY_UNSAFE");
108782
+ const saved = join55(history, `${value.id}-${String(prior.state)}-${randomUUID9()}.json`);
108783
+ writeFileSync23(saved, priorBytes, { flag: "wx", mode: 256 });
108784
+ chmodSync4(saved, 256);
108785
+ if (!readFileSync37(saved).equals(priorBytes))
108786
+ throw new Error("ALIAS_RECEIPT_HISTORY_READBACK_MISMATCH");
108787
+ }
108788
+ atomicJson(path, value);
108789
+ }
108790
+ function assertOwnedSafePath(path, home, requireWritable = false) {
108791
+ assertNoSymlinkAncestors(path);
108792
+ for (let current = path;; current = dirname30(current)) {
108793
+ const stat3 = lstatSync28(current);
108794
+ if (!stat3.isDirectory() || ![0, process.getuid?.() ?? -1].includes(stat3.uid) || stat3.mode & 18)
108795
+ throw new Error("ALIAS_PATH_UNSAFE");
108796
+ if (current === path && requireWritable && (stat3.uid !== (process.getuid?.() ?? -1) || (stat3.mode & 128) === 0))
108797
+ throw new Error("ALIAS_DIRECTORY_NOT_OWNED");
108798
+ if (current === dirname30(current) || current === home)
108799
+ break;
108800
+ }
108801
+ }
108802
+ function activeAliasRuntime(home, pathValue) {
108803
+ const layout = resolveRuntimeLayout(home, pathValue);
108804
+ const runtimePath = dirname30(dirname30(dirname30(layout.currentPackageRoot)));
108805
+ const receiptPath = join55(runtimePath, "rollout-receipt.json");
108806
+ const rollout = object9(JSON.parse(readFileSync37(receiptPath, "utf8")), "ROLLOUT_RECEIPT_INVALID");
108807
+ if (rollout.schema !== "skills.copyfile-runtime-receipt.v1" || rollout.state !== "switched" || rollout.targetPackageRoot !== layout.currentPackageRoot || rollout.targetVersion !== layout.currentVersion || rollout.runtimeRoot !== layout.runtimeRoot || rollout.package !== PACKAGE_NAME || hash5(readFileSync37(join55(runtimePath, "verified-package.tgz"))) !== rollout.tarballSha256 || readTree(join55(runtimePath, "node_modules"), true).digest !== rollout.runtimeTreeSha256) {
108808
+ throw new Error("ACTIVE_RUNTIME_RECEIPT_DRIFT");
108809
+ }
108810
+ verifyPreimageManifest(runtimePath, rollout, home);
108811
+ return { layout, rollout };
108812
+ }
108813
+ function aliasDirs(home) {
108814
+ return [join55(home, ".local", "bin"), join55(home, ".bun", "bin"), "/opt/homebrew/bin", "/usr/local/bin"];
108815
+ }
108816
+ function aliasPackageLink(home, aliasPath) {
108817
+ const dir = dirname30(aliasPath);
108818
+ if (dir === join55(home, ".local", "bin"))
108819
+ return join55(home, ".local", "lib", "node_modules", "@hasna", "skills");
108820
+ if (dir === "/opt/homebrew/bin")
108821
+ return "/opt/homebrew/lib/node_modules/@hasna/skills";
108822
+ if (dir === "/usr/local/bin")
108823
+ return "/usr/local/lib/node_modules/@hasna/skills";
108824
+ return null;
108825
+ }
108826
+ function inspectAliasPackageChain(home, aliasPath, oldLinkTarget, oldTarget, bin, expected, verify = false) {
108827
+ const immediate = resolve23(dirname30(aliasPath), oldLinkTarget);
108828
+ if (immediate === oldTarget) {
108829
+ if (expected !== undefined)
108830
+ throw new Error("ALIAS_LINK_CHAIN_DRIFT");
108831
+ return;
108832
+ }
108833
+ const packageLinkPath = aliasPackageLink(home, aliasPath);
108834
+ const physicalPackage = dirname30(dirname30(oldTarget));
108835
+ if (!packageLinkPath || immediate !== join55(packageLinkPath, bin) || !isWithin(join55(home, ".hasna", "skills", "runtime"), physicalPackage)) {
108836
+ throw new Error("ALIAS_LINK_CHAIN_UNSUPPORTED");
108837
+ }
108838
+ assertOwnedSafePath(dirname30(packageLinkPath), home);
108839
+ let stat3, packageLinkTarget, resolved;
108840
+ try {
108841
+ stat3 = lstatSync28(packageLinkPath);
108842
+ packageLinkTarget = readlinkSync5(packageLinkPath);
108843
+ resolved = realpathSync14(packageLinkPath);
108844
+ } catch {
108845
+ throw new Error("ALIAS_LINK_CHAIN_UNSUPPORTED");
108846
+ }
108847
+ if (expected !== undefined && (expected.packageLinkPath !== packageLinkPath || expected.packageLinkTarget !== packageLinkTarget))
108848
+ throw new Error("ALIAS_LINK_CHAIN_DRIFT");
108849
+ if (!stat3.isSymbolicLink() || stat3.uid !== (process.getuid?.() ?? -1) || resolve23(dirname30(packageLinkPath), packageLinkTarget) !== physicalPackage || resolved !== physicalPackage)
108850
+ throw new Error("ALIAS_LINK_CHAIN_UNSUPPORTED");
108851
+ const chain = { packageLinkPath, packageLinkTarget };
108852
+ if (verify && expected === undefined)
108853
+ throw new Error("ALIAS_LINK_CHAIN_DRIFT");
108854
+ return chain;
108855
+ }
108856
+ function validateAliasSource(home, name, actual) {
108857
+ const packageRoot = dirname30(dirname30(actual));
108858
+ const roots = [join55(home, ".hasna", "skills", "runtime"), join55(home, ".bun", "install"), join55(home, ".local", "lib", "node_modules")];
108859
+ if (basename13(packageRoot) !== "skills" || basename13(dirname30(packageRoot)) !== "@hasna" || !roots.some((root2) => isWithin(root2, packageRoot))) {
108860
+ throw new Error("ALIAS_SOURCE_ROOT_UNSUPPORTED");
108861
+ }
108862
+ assertOwnedSafePath(packageRoot, home);
108863
+ const manifestPath = join55(packageRoot, "package.json");
108864
+ const manifestStat = lstatSync28(manifestPath), binaryStat = lstatSync28(actual);
108865
+ if (!manifestStat.isFile() || manifestStat.isSymbolicLink() || !binaryStat.isFile() || binaryStat.isSymbolicLink() || ![0, process.getuid?.() ?? -1].includes(manifestStat.uid) || ![0, process.getuid?.() ?? -1].includes(binaryStat.uid) || manifestStat.mode & 18)
108866
+ throw new Error("ALIAS_SOURCE_UNSAFE");
108867
+ const pkg = readPackage(manifestPath);
108868
+ if (!pkg.bins[name] || actual !== join55(packageRoot, pkg.bins[name]))
108869
+ throw new Error("ALIAS_SOURCE_BIN_MISMATCH");
108870
+ return { version: pkg.version, packageRoot, binarySha256: hash5(readFileSync37(actual)) };
108871
+ }
108872
+ function adoptCopyfileAliases(options = {}) {
108873
+ const home = realpathSync14(resolve23(options.homeDir ?? process.env.HOME ?? ""));
108874
+ const pathValue = options.pathValue ?? process.env.PATH ?? "";
108875
+ const { layout, rollout } = activeAliasRuntime(home, pathValue);
108876
+ const releaseLock = acquireRuntimeLock(layout.runtimeRoot);
108877
+ const id2 = randomUUID9();
108878
+ const aliases = [];
108879
+ const currentRuntime = dirname30(dirname30(dirname30(layout.currentPackageRoot)));
108880
+ const receiptDir = join55(currentRuntime, "alias-adoptions");
108881
+ const receiptPath = join55(receiptDir, `${id2}.json`);
108882
+ let receipt;
108883
+ try {
108884
+ const locked2 = activeAliasRuntime(home, pathValue);
108885
+ if (locked2.layout.currentPackageRoot !== layout.currentPackageRoot || locked2.rollout.id !== rollout.id)
108886
+ throw new Error("ACTIVE_RUNTIME_CHANGED_BEFORE_ALIAS_ADOPTION");
108887
+ for (const dir of aliasDirs(home)) {
108888
+ if (!entryExists(dir))
108889
+ continue;
108890
+ for (const [name, target] of Object.entries(layout.bin)) {
108891
+ const path = join55(dir, name);
108892
+ if (!entryExists(path))
108893
+ continue;
108894
+ assertOwnedSafePath(dir, home, true);
108895
+ const stat3 = lstatSync28(path);
108896
+ if (!stat3.isSymbolicLink() || stat3.uid !== (process.getuid?.() ?? -1))
108897
+ throw new Error("ALIAS_LINK_NOT_OWNED");
108898
+ const oldLinkTarget = readlinkSync5(path);
108899
+ let actual;
108900
+ try {
108901
+ actual = realpathSync14(path);
108902
+ } catch {
108903
+ throw new Error("ALIAS_LINK_CHAIN_UNSUPPORTED");
108904
+ }
108905
+ const newTarget = join55(layout.currentPackageRoot, target);
108906
+ if (actual === newTarget)
108907
+ continue;
108908
+ const chain = inspectAliasPackageChain(home, path, oldLinkTarget, actual, target);
108909
+ const source = validateAliasSource(home, name, actual);
108910
+ const backupPath = `${path}.skills-alias-prev-${id2}`;
108911
+ if (entryExists(backupPath))
108912
+ throw new Error("ALIAS_BACKUP_COLLISION");
108913
+ aliases.push({ path, oldTarget: actual, oldLinkTarget, oldVersion: source.version, oldBinarySha256: source.binarySha256, newTarget, backupPath, ...chain ? { chain } : {} });
108914
+ }
108915
+ }
108916
+ if (!aliases.length)
108917
+ return { adopted: true, version: layout.currentVersion, aliasCount: 0 };
108918
+ mkdirSync24(receiptDir, { recursive: true, mode: 448 });
108919
+ const receiptDirStat = lstatSync28(receiptDir);
108920
+ if (!receiptDirStat.isDirectory() || receiptDirStat.isSymbolicLink() || receiptDirStat.mode & 63)
108921
+ throw new Error("ALIAS_RECEIPT_DIRECTORY_UNSAFE");
108922
+ receipt = {
108923
+ schema: "skills.copyfile-alias-adoption.v1",
108924
+ id: id2,
108925
+ state: "prepared",
108926
+ targetVersion: layout.currentVersion,
108927
+ targetPackageRoot: layout.currentPackageRoot,
108928
+ rolloutReceiptId: rollout.id,
108929
+ aliases,
108930
+ switchedAliases: [],
108931
+ rollbackCompletedAliases: []
108932
+ };
108933
+ writeJsonPrivate(receiptPath, receipt);
108934
+ for (const item of aliases) {
108935
+ if (readlinkSync5(item.path) !== item.oldLinkTarget || realpathSync14(item.path) !== item.oldTarget)
108936
+ throw new Error("ALIAS_CHANGED_BEFORE_SWITCH");
108937
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, layout.bin[basename13(item.path)], item.chain, true);
108938
+ const source = validateAliasSource(home, basename13(item.path), item.oldTarget);
108939
+ if (source.version !== item.oldVersion || source.binarySha256 !== item.oldBinarySha256)
108940
+ throw new Error("ALIAS_SOURCE_DRIFT");
108941
+ receipt.state = "switching";
108942
+ receipt.switchedAliases = [...receipt.switchedAliases, item.path];
108943
+ persistAliasReceipt(receiptPath, receipt);
108944
+ replaceLink(item.path, item.newTarget, item.backupPath, item.oldLinkTarget, item.oldTarget);
108945
+ if (realpathSync14(item.path) !== item.newTarget || realpathSync14(item.backupPath) !== item.oldTarget)
108946
+ throw new Error("ALIAS_SWITCH_READBACK_MISMATCH");
108947
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, layout.bin[basename13(item.path)], item.chain, true);
108948
+ options.onAliasSwitched?.(item.path);
108949
+ }
108950
+ for (const item of aliases) {
108951
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, layout.bin[basename13(item.path)], item.chain, true);
108952
+ if (readlinkSync5(item.backupPath) !== item.oldLinkTarget || realpathSync14(item.backupPath) !== item.oldTarget)
108953
+ throw new Error("ALIAS_BACKUP_DRIFT");
108954
+ }
108955
+ receipt.state = "switched";
108956
+ persistAliasReceipt(receiptPath, receipt);
108957
+ return { adopted: true, version: layout.currentVersion, receiptId: id2, aliasCount: aliases.length };
108958
+ } catch (error61) {
108959
+ if (receipt) {
108960
+ let failed = false;
108961
+ for (const item of [...aliases].reverse()) {
108962
+ try {
108963
+ if (!entryExists(item.backupPath)) {
108964
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, layout.bin[basename13(item.path)], item.chain, true);
108965
+ const source2 = validateAliasSource(home, basename13(item.path), item.oldTarget);
108966
+ if (!entryExists(item.path) || readlinkSync5(item.path) !== item.oldLinkTarget || realpathSync14(item.path) !== item.oldTarget || source2.version !== item.oldVersion || source2.binarySha256 !== item.oldBinarySha256)
108967
+ failed = true;
108968
+ continue;
108969
+ }
108970
+ if (readlinkSync5(item.backupPath) !== item.oldLinkTarget || realpathSync14(item.backupPath) !== item.oldTarget)
108971
+ throw new Error("ALIAS_BACKUP_DRIFT");
108972
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, layout.bin[basename13(item.path)], item.chain, true);
108973
+ const source = validateAliasSource(home, basename13(item.path), item.oldTarget);
108974
+ if (source.version !== item.oldVersion || source.binarySha256 !== item.oldBinarySha256)
108975
+ throw new Error("ALIAS_SOURCE_DRIFT");
108976
+ if (realpathSync14(item.path) === item.oldTarget && readlinkSync5(item.path) === item.oldLinkTarget)
108977
+ continue;
108978
+ if (realpathSync14(item.path) !== item.newTarget)
108979
+ throw new Error("ALIAS_CHANGED_DURING_ROLLBACK");
108980
+ const after2 = `${item.path}.skills-alias-after-${id2}`;
108981
+ if (entryExists(after2))
108982
+ throw new Error("ALIAS_AFTER_COLLISION");
108983
+ symlinkSync(item.newTarget, after2);
108984
+ const temp = `${item.path}.skills-alias-rollback-${randomUUID9()}`;
108985
+ symlinkSync(item.oldLinkTarget, temp);
108986
+ renameSync13(temp, item.path);
108987
+ if (readlinkSync5(item.path) !== item.oldLinkTarget)
108988
+ throw new Error("ALIAS_ROLLBACK_READBACK_MISMATCH");
108989
+ receipt.rollbackCompletedAliases = [...receipt.rollbackCompletedAliases, item.path];
108990
+ persistAliasReceipt(receiptPath, receipt);
108991
+ } catch {
108992
+ failed = true;
108993
+ }
108994
+ }
108995
+ receipt.state = failed ? "rollback-required" : "rolled-back";
108996
+ try {
108997
+ persistAliasReceipt(receiptPath, receipt);
108998
+ } catch {
108999
+ failed = true;
109000
+ }
109001
+ throw new Error(failed ? "ALIAS_ADOPTION_ROLLBACK_REQUIRED" : "ALIAS_ADOPTION_ROLLED_BACK");
109002
+ }
109003
+ throw error61 instanceof Error ? error61 : new Error("ALIAS_ADOPTION_FAILED");
109004
+ } finally {
109005
+ releaseLock();
109006
+ }
109007
+ }
109008
+ function rollbackCopyfileAliases(receiptId, options = {}) {
109009
+ if (!/^[0-9a-f-]{36}$/.test(receiptId))
109010
+ throw new Error("ALIAS_RECEIPT_ID_INVALID");
109011
+ const home = realpathSync14(resolve23(options.homeDir ?? process.env.HOME ?? ""));
109012
+ const pathValue = options.pathValue ?? process.env.PATH ?? "";
109013
+ const { layout, rollout } = activeAliasRuntime(home, pathValue);
109014
+ const runtimePath = dirname30(dirname30(dirname30(layout.currentPackageRoot)));
109015
+ const receiptPath = join55(runtimePath, "alias-adoptions", `${receiptId}.json`);
109016
+ const releaseLock = acquireRuntimeLock(layout.runtimeRoot);
109017
+ try {
109018
+ const locked2 = activeAliasRuntime(home, pathValue);
109019
+ if (locked2.layout.currentPackageRoot !== layout.currentPackageRoot || locked2.rollout.id !== rollout.id)
109020
+ throw new Error("ACTIVE_RUNTIME_CHANGED_BEFORE_ALIAS_ROLLBACK");
109021
+ const receipt = object9(JSON.parse(readFileSync37(receiptPath, "utf8")), "ALIAS_RECEIPT_INVALID");
109022
+ if (receipt.schema !== "skills.copyfile-alias-adoption.v1" || receipt.id !== receiptId || !["switched", "switching", "rollback-required"].includes(receipt.state) || receipt.targetPackageRoot !== layout.currentPackageRoot || receipt.targetVersion !== layout.currentVersion || receipt.rolloutReceiptId !== rollout.id || !Array.isArray(receipt.aliases))
109023
+ throw new Error("ALIAS_RECEIPT_BINDING_INVALID");
109024
+ const seen = new Set;
109025
+ for (const item of receipt.aliases) {
109026
+ const name = basename13(item.path), target = layout.bin[name];
109027
+ if (!target || seen.has(item.path) || !aliasDirs(home).includes(dirname30(item.path)) || item.newTarget !== join55(layout.currentPackageRoot, target) || item.backupPath !== `${item.path}.skills-alias-prev-${receiptId}` || item.chain !== undefined && (!item.chain || typeof item.chain !== "object" || typeof item.chain.packageLinkPath !== "string" || typeof item.chain.packageLinkTarget !== "string")) {
109028
+ throw new Error("ALIAS_RECEIPT_LAUNCHER_INVALID");
109029
+ }
109030
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, target, item.chain, true);
109031
+ seen.add(item.path);
109032
+ assertOwnedSafePath(dirname30(item.path), home, true);
109033
+ const source = validateAliasSource(home, name, item.oldTarget);
109034
+ if (source.version !== item.oldVersion || source.binarySha256 !== item.oldBinarySha256)
109035
+ throw new Error("ALIAS_PREIMAGE_DRIFT");
109036
+ const stat3 = lstatSync28(item.path);
109037
+ if (!stat3.isSymbolicLink() || stat3.uid !== (process.getuid?.() ?? -1))
109038
+ throw new Error("ALIAS_LINK_DRIFT");
109039
+ const current = realpathSync14(item.path);
109040
+ if (current !== item.newTarget && (current !== item.oldTarget || readlinkSync5(item.path) !== item.oldLinkTarget))
109041
+ throw new Error("ALIAS_TARGET_DRIFT");
109042
+ if (entryExists(item.backupPath)) {
109043
+ const backup = lstatSync28(item.backupPath);
109044
+ if (!backup.isSymbolicLink() || readlinkSync5(item.backupPath) !== item.oldLinkTarget || realpathSync14(item.backupPath) !== item.oldTarget)
109045
+ throw new Error("ALIAS_PREIMAGE_DRIFT");
109046
+ } else if (current !== item.oldTarget) {
109047
+ throw new Error("ALIAS_BACKUP_MISSING_FOR_SWITCH");
109048
+ }
109049
+ }
109050
+ let restored = 0;
109051
+ for (const item of [...receipt.aliases].reverse()) {
109052
+ if (realpathSync14(item.path) === item.oldTarget && readlinkSync5(item.path) === item.oldLinkTarget)
109053
+ continue;
109054
+ receipt.state = "rollback-required";
109055
+ persistAliasReceipt(receiptPath, receipt);
109056
+ const after2 = `${item.path}.skills-alias-after-${receiptId}`;
109057
+ if (!entryExists(after2)) {
109058
+ symlinkSync(item.newTarget, after2);
109059
+ if (realpathSync14(after2) !== item.newTarget)
109060
+ throw new Error("ALIAS_AFTER_READBACK_MISMATCH");
109061
+ } else if (realpathSync14(after2) !== item.newTarget)
109062
+ throw new Error("ALIAS_AFTER_DRIFT");
109063
+ if (realpathSync14(item.path) !== item.newTarget)
109064
+ throw new Error("ALIAS_TARGET_DRIFT");
109065
+ const temp = `${item.path}.skills-alias-rollback-${randomUUID9()}`;
109066
+ symlinkSync(item.oldLinkTarget, temp);
109067
+ renameSync13(temp, item.path);
109068
+ if (readlinkSync5(item.path) !== item.oldLinkTarget || realpathSync14(item.path) !== item.oldTarget)
109069
+ throw new Error("ALIAS_ROLLBACK_READBACK_MISMATCH");
109070
+ receipt.rollbackCompletedAliases = [...new Set([...receipt.rollbackCompletedAliases, item.path])];
109071
+ persistAliasReceipt(receiptPath, receipt);
109072
+ restored++;
109073
+ }
109074
+ for (const item of receipt.aliases) {
109075
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, layout.bin[basename13(item.path)], item.chain, true);
109076
+ }
109077
+ receipt.state = "rolled-back";
109078
+ persistAliasReceipt(receiptPath, receipt);
109079
+ return { rolledBack: true, receiptId, restoredAliasCount: restored };
109080
+ } finally {
109081
+ releaseLock();
109082
+ }
109083
+ }
108748
109084
  async function updateCopyfileRuntime(version2, options = {}) {
108749
109085
  if (!new RegExp(SEMVER_PATTERN).test(version2))
108750
109086
  throw new Error("EXACT_VERSION_REQUIRED");
@@ -109091,12 +109427,40 @@ function registerRuntime(parent) {
109091
109427
  });
109092
109428
  const setup = parent.command("setup").description("Point this CLI at a Skills API server, or register agent integrations").option("--api-url <url>", "Skills API origin to send remote work to").option("--global", "Accepted for compatibility; the API origin is always per-user", false).option("--json", "Output setup result as JSON", false).action(async (options) => handleSetup(options));
109093
109429
  setup.command("agents").option("--json", "Output registration result as JSON", false).description("Register the Skills MCP server with all supported agents").action(async (options) => handleMcp({ register: "all", json: options.json }));
109094
- parent.command("self-update").description("Update @hasna/skills to the latest version").option("--json", "Output result as JSON", false).option("--version <version>", "Install this exact @hasna/skills version as a staged copyfile runtime").option("--rollback <receipt-id>", "Restore launcher links from an exact copyfile rollout receipt").action(async (options) => {
109095
- if (options.version && options.rollback) {
109096
- console.error(JSON.stringify({ error: "UPDATE_AND_ROLLBACK_ARE_MUTUALLY_EXCLUSIVE" }));
109430
+ parent.command("self-update").description("Update @hasna/skills to the latest version").option("--json", "Output result as JSON", false).option("--version <version>", "Install this exact @hasna/skills version as a staged copyfile runtime").option("--rollback <receipt-id>", "Restore launcher links from an exact copyfile rollout receipt").option("--adopt-aliases", "Switch verified legacy @hasna/skills aliases to the active copyfile runtime").option("--rollback-aliases <receipt-id>", "Restore aliases from an exact adoption receipt").action(async (options) => {
109431
+ if ([options.version, options.rollback, options.adoptAliases, options.rollbackAliases].filter(Boolean).length > 1) {
109432
+ console.error(JSON.stringify({ error: "SELF_UPDATE_OPERATIONS_ARE_MUTUALLY_EXCLUSIVE" }));
109097
109433
  process.exitCode = 1;
109098
109434
  return;
109099
109435
  }
109436
+ if (options.adoptAliases) {
109437
+ try {
109438
+ const result2 = adoptCopyfileAliases();
109439
+ if (options.json)
109440
+ console.log(JSON.stringify(result2));
109441
+ else
109442
+ console.log(source_default.green(`Adopted ${String(result2.aliasCount)} verified Skills aliases into the active copyfile runtime.`));
109443
+ } catch (error61) {
109444
+ const message = error61 instanceof Error && /^[A-Z0-9_]+$/.test(error61.message) ? error61.message : "ALIAS_ADOPTION_FAILED";
109445
+ console.error(JSON.stringify({ adopted: false, error: message }));
109446
+ process.exitCode = 1;
109447
+ }
109448
+ return;
109449
+ }
109450
+ if (options.rollbackAliases) {
109451
+ try {
109452
+ const result2 = rollbackCopyfileAliases(options.rollbackAliases);
109453
+ if (options.json)
109454
+ console.log(JSON.stringify(result2));
109455
+ else
109456
+ console.log(source_default.green(`Restored ${String(result2.restoredAliasCount)} Skills aliases from the adoption receipt.`));
109457
+ } catch (error61) {
109458
+ const message = error61 instanceof Error && /^[A-Z0-9_]+$/.test(error61.message) ? error61.message : "ALIAS_ROLLBACK_FAILED";
109459
+ console.error(JSON.stringify({ rolledBack: false, error: message }));
109460
+ process.exitCode = 1;
109461
+ }
109462
+ return;
109463
+ }
109100
109464
  if (options.version) {
109101
109465
  try {
109102
109466
  const result2 = await updateCopyfileRuntime(options.version);
@@ -111848,7 +112212,9 @@ var exports_agent_integration = {};
111848
112212
  __export(exports_agent_integration, {
111849
112213
  registerAgentIntegration: () => registerAgentIntegration
111850
112214
  });
111851
- import { readFileSync as readFileSync41 } from "fs";
112215
+ import { existsSync as existsSync35, lstatSync as lstatSync32, readFileSync as readFileSync41, writeFileSync as writeFileSync26 } from "fs";
112216
+ import { createHash as createHash31 } from "crypto";
112217
+ import { homedir as homedir13 } from "os";
111852
112218
  import { isAbsolute as isAbsolute29 } from "path";
111853
112219
  function pinnedHookProfile(input3, configuredProfile) {
111854
112220
  try {
@@ -111915,6 +112281,45 @@ function agents(value) {
111915
112281
  }
111916
112282
  function registerAgentIntegration(parent) {
111917
112283
  const hook = parent.command("hook").description("Load selected Skills context through agent lifecycle hooks");
112284
+ hook.command("native-catalog").requiredOption("--cwd <path>", "Absolute project directory observed by native Codex skills/list").requiredOption("--output <file>", "New private file for the projected native catalog").option("--codex-command <path>", "Installed Codex executable used by the native client", "codex").option("--json", "Output bounded capture metadata as JSON", false).description("Capture a reviewed Codex native skill catalog for exact hook enrollment").action(async (options) => {
112285
+ try {
112286
+ if (!isAbsolute29(options.cwd) || !isAbsolute29(options.output))
112287
+ throw new Error("CODEX_NATIVE_SKILL_CATALOG_ABSOLUTE_PATH_REQUIRED");
112288
+ if (existsSync35(options.output))
112289
+ throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
112290
+ const catalog = await captureCodexNativeSkillCatalog({
112291
+ command: options.codexCommand,
112292
+ home: homedir13(),
112293
+ ...process.env.CODEX_HOME === undefined ? {} : { codexHome: process.env.CODEX_HOME },
112294
+ cwd: options.cwd
112295
+ });
112296
+ const bytes = JSON.stringify(catalog, null, 2) + `
112297
+ `;
112298
+ try {
112299
+ writeFileSync26(options.output, bytes, { flag: "wx", mode: 384 });
112300
+ } catch (error61) {
112301
+ if (error61 && typeof error61 === "object" && "code" in error61 && error61.code === "EEXIST")
112302
+ throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
112303
+ throw error61;
112304
+ }
112305
+ const written = lstatSync32(options.output);
112306
+ if (!written.isFile() || written.isSymbolicLink() || (written.mode & 63) !== 0 || readFileSync41(options.output, "utf8") !== bytes)
112307
+ throw new Error("CODEX_NATIVE_SKILL_CATALOG_READBACK_FAILED");
112308
+ const receipt = {
112309
+ version: catalog.version,
112310
+ cwd: catalog.cwd,
112311
+ skillCount: catalog.skills.length,
112312
+ output: options.output,
112313
+ bytes: Buffer.byteLength(bytes),
112314
+ sha256: createHash31("sha256").update(bytes).digest("hex")
112315
+ };
112316
+ await writeCliOutput(options.json ? JSON.stringify(receipt) : `Captured ${receipt.skillCount} native Codex skill(s) in ${receipt.output}.`);
112317
+ } catch (error61) {
112318
+ const message = error61 instanceof Error && /^[A-Z][A-Z0-9_]{2,120}$/.test(error61.message) ? error61.message : "CODEX_NATIVE_SKILL_CATALOG_CAPTURE_FAILED";
112319
+ console.error(message);
112320
+ process.exitCode = 1;
112321
+ }
112322
+ });
111918
112323
  hook.command("witness").requiredOption("--kind <kind>", "claude-settings-v3, claude-settings-v2, codex-settings-v1 or claude-marketplace-registry-v2").requiredOption("--path <path>", "Canonical absolute path to the reviewed settings or registry file").option("--json", "Output the discovery witness as JSON", false).description("Capture an explicit versioned review witness without installing it").action(async (options) => {
111919
112324
  const capture2 = options.kind === "codex-settings-v1" ? captureCodexSettings : options.kind === "claude-settings-v3" ? captureClaudeSettingsV3 : options.kind === "claude-settings-v2" ? captureClaudeSettingsV2 : options.kind === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : null;
111920
112325
  if (!capture2)
@@ -112146,6 +112551,7 @@ var init_agent_integration2 = __esm(() => {
112146
112551
  init_claude_settings_witness();
112147
112552
  init_codex_settings_witness();
112148
112553
  init_claude_marketplace_registry();
112554
+ init_codex_native_skill_catalog();
112149
112555
  RECOVERABLE_CONTEXT_CACHE_ERRORS = new Set(["CACHED_PROFILE_EXPIRED", "CACHED_PROFILE_MISSING", "CACHED_BUNDLE_MISSING"]);
112150
112556
  });
112151
112557
 
@@ -112284,7 +112690,7 @@ var exports_profiles = {};
112284
112690
  __export(exports_profiles, {
112285
112691
  registerProfiles: () => registerProfiles
112286
112692
  });
112287
- import { closeSync as closeSync21, constants as constants20, fstatSync as fstatSync21, openSync as openSync21, readSync as readSync17, writeFileSync as writeFileSync26 } from "fs";
112693
+ import { closeSync as closeSync21, constants as constants20, fstatSync as fstatSync21, openSync as openSync21, readSync as readSync17, writeFileSync as writeFileSync27 } from "fs";
112288
112694
  function readProfileInput(path) {
112289
112695
  const fd = openSync21(path, constants20.O_RDONLY | constants20.O_NONBLOCK);
112290
112696
  try {
@@ -112312,7 +112718,7 @@ function registerProfiles(parent) {
112312
112718
  try {
112313
112719
  const profile = await readSkillProfile(id3), text4 = JSON.stringify(profile, null, 2);
112314
112720
  if (options.save)
112315
- writeFileSync26(options.save, JSON.stringify(profile), { mode: 384, flag: "wx" });
112721
+ writeFileSync27(options.save, JSON.stringify(profile), { mode: 384, flag: "wx" });
112316
112722
  await writeCliOutput(options.json ? text4 : `${profile.id} at ${profile.revision}: ${profile.selections.map((skill) => `${skill.slug}@${skill.version}`).join(", ")}`);
112317
112723
  } catch (error61) {
112318
112724
  console.error(error61.message);
@@ -112359,7 +112765,7 @@ import {
112359
112765
  fstatSync as fstatSync23,
112360
112766
  openSync as openSync23,
112361
112767
  readSync as readSync18,
112362
- writeFileSync as writeFileSync27
112768
+ writeFileSync as writeFileSync28
112363
112769
  } from "fs";
112364
112770
  function readInput(path) {
112365
112771
  const fd = openSync23(path, constants21.O_RDONLY | constants21.O_NONBLOCK);
@@ -112392,7 +112798,7 @@ function registerGrants(parent) {
112392
112798
  try {
112393
112799
  const policy = await readExecutionGrantPolicy(profile, options.revision);
112394
112800
  if (options.save)
112395
- writeFileSync27(options.save, JSON.stringify(policy, null, 2), {
112801
+ writeFileSync28(options.save, JSON.stringify(policy, null, 2), {
112396
112802
  mode: 384,
112397
112803
  flag: "wx"
112398
112804
  });
@@ -112419,14 +112825,14 @@ var init_grants = __esm(() => {
112419
112825
  });
112420
112826
 
112421
112827
  // src/lib/station-hydrate.ts
112422
- import { createHash as createHash31 } from "crypto";
112828
+ import { createHash as createHash32 } from "crypto";
112423
112829
  import {
112424
112830
  copyFileSync as copyFileSync3,
112425
112831
  mkdirSync as mkdirSync28,
112426
112832
  readdirSync as readdirSync24,
112427
112833
  readFileSync as readFileSync42,
112428
112834
  statSync as statSync22,
112429
- writeFileSync as writeFileSync28
112835
+ writeFileSync as writeFileSync29
112430
112836
  } from "fs";
112431
112837
  import { dirname as dirname35, join as join61, resolve as resolve26, sep as sep13 } from "path";
112432
112838
  function fail5(code2, message, detail = []) {
@@ -112622,7 +113028,7 @@ function skillSha256(skill) {
112622
113028
  return sha256File(skill.files[0].winner.fullPath);
112623
113029
  }
112624
113030
  const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
112625
- return createHash31("sha256").update(joined.sort().join(`
113031
+ return createHash32("sha256").update(joined.sort().join(`
112626
113032
  `)).digest("hex");
112627
113033
  }
112628
113034
  function writeStationHydration(options) {
@@ -112704,7 +113110,7 @@ function writeStationHydration(options) {
112704
113110
  };
112705
113111
  const hydrationManifestPath = join61(dirname35(cacheRoot), `hydration-${options.stationId}.json`);
112706
113112
  mkdirSync28(dirname35(hydrationManifestPath), { recursive: true });
112707
- writeFileSync28(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
113113
+ writeFileSync29(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
112708
113114
  `);
112709
113115
  return {
112710
113116
  ...base3,
@@ -112779,8 +113185,8 @@ var init_hydrate = __esm(() => {
112779
113185
  });
112780
113186
 
112781
113187
  // src/lib/prepare-skill.ts
112782
- import { createHash as createHash32, randomUUID as randomUUID12 } from "crypto";
112783
- import { chmodSync as chmodSync5, existsSync as existsSync35, lstatSync as lstatSync32, mkdirSync as mkdirSync29, mkdtempSync as mkdtempSync6, readFileSync as readFileSync43, readdirSync as readdirSync25, renameSync as renameSync14, rmSync as rmSync9, writeFileSync as writeFileSync29 } from "fs";
113188
+ import { createHash as createHash33, randomUUID as randomUUID12 } from "crypto";
113189
+ import { chmodSync as chmodSync5, existsSync as existsSync36, lstatSync as lstatSync33, mkdirSync as mkdirSync29, mkdtempSync as mkdtempSync6, readFileSync as readFileSync43, readdirSync as readdirSync25, renameSync as renameSync14, rmSync as rmSync9, writeFileSync as writeFileSync30 } from "fs";
112784
113190
  import { tmpdir as tmpdir2 } from "os";
112785
113191
  import { dirname as dirname36, isAbsolute as isAbsolute30, join as join62, relative as relative10, resolve as resolve27, sep as sep14 } from "path";
112786
113192
  function prepareSkill(name, options) {
@@ -112829,10 +113235,10 @@ function prepareSkill(name, options) {
112829
113235
  for (const file2 of before2.files) {
112830
113236
  const destination = join62(staging, file2.path);
112831
113237
  mkdirSync29(dirname36(destination), { recursive: true });
112832
- writeFileSync29(destination, file2.bytes, { mode: file2.mode });
113238
+ writeFileSync30(destination, file2.bytes, { mode: file2.mode });
112833
113239
  }
112834
113240
  const stagedManifest = join62(staging, "skill.json");
112835
- writeFileSync29(stagedManifest, `${JSON.stringify(candidate2, null, 2)}
113241
+ writeFileSync30(stagedManifest, `${JSON.stringify(candidate2, null, 2)}
112836
113242
  `);
112837
113243
  const contentHash = computeContentHash(staging);
112838
113244
  const changed3 = options.version !== previousVersion || kind !== manifest.kind || contentHash !== provenance?.content_hash;
@@ -112842,7 +113248,7 @@ function prepareSkill(name, options) {
112842
113248
  candidate2.provenance.content_hash = contentHash;
112843
113249
  const candidateBytes = `${JSON.stringify(candidate2, null, 2)}
112844
113250
  `;
112845
- writeFileSync29(stagedManifest, candidateBytes);
113251
+ writeFileSync30(stagedManifest, candidateBytes);
112846
113252
  validateRawCandidate(candidate2);
112847
113253
  validateRuntimeEntrypoint(candidate2, staging);
112848
113254
  const validation = validatePortableSkillDirectory(normalized, staging);
@@ -112856,7 +113262,7 @@ ${validation.issues.map((issue2) => `${issue2.code}: ${issue2.message}`).join(`
112856
113262
  if (changed3 && !options.dryRun) {
112857
113263
  const temporary = join62(path, `.skill-prepare-${randomUUID12()}.tmp`);
112858
113264
  try {
112859
- writeFileSync29(temporary, candidateBytes, { flag: "wx", mode: manifestFile.mode });
113265
+ writeFileSync30(temporary, candidateBytes, { flag: "wx", mode: manifestFile.mode });
112860
113266
  chmodSync5(temporary, manifestFile.mode);
112861
113267
  renameSync14(temporary, join62(path, "skill.json"));
112862
113268
  } finally {
@@ -112889,7 +113295,7 @@ function validateRuntimeEntrypoint(candidate2, root2) {
112889
113295
  if (typeof entry !== "string")
112890
113296
  return;
112891
113297
  const target = resolve27(root2, entry), rel = relative10(root2, target);
112892
- if (isAbsolute30(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep14}`) || isAbsolute30(rel) || !existsSync35(target) || !lstatSync32(target).isFile()) {
113298
+ if (isAbsolute30(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep14}`) || isAbsolute30(rel) || !existsSync36(target) || !lstatSync33(target).isFile()) {
112893
113299
  throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
112894
113300
  }
112895
113301
  }
@@ -112974,11 +113380,11 @@ function compareVersions(left, right) {
112974
113380
  return 0;
112975
113381
  }
112976
113382
  function snapshot2(root2) {
112977
- const rootStat = lstatSync32(root2);
113383
+ const rootStat = lstatSync33(root2);
112978
113384
  if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
112979
113385
  throw new Error("Skill root must be a real directory, not a symlink.");
112980
113386
  const files = [], directories = [];
112981
- const hash6 = createHash32("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
113387
+ const hash6 = createHash33("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
112982
113388
  let count = 0, bytes = 0;
112983
113389
  const walk2 = (relative11) => {
112984
113390
  for (const name of readdirSync25(join62(root2, relative11)).sort()) {
@@ -112987,7 +113393,7 @@ function snapshot2(root2) {
112987
113393
  const path = relative11 ? `${relative11}/${name}` : name;
112988
113394
  if (++count > 20000)
112989
113395
  throw new Error("Skill exceeds the preparation entry limit (20000).");
112990
- const absolute = join62(root2, path), stat3 = lstatSync32(absolute);
113396
+ const absolute = join62(root2, path), stat3 = lstatSync33(absolute);
112991
113397
  if (stat3.isSymbolicLink())
112992
113398
  throw new Error(`Skill contains a symlink: ${path}. No files were changed.`);
112993
113399
  if (stat3.isDirectory() && name === "node_modules")
@@ -113402,7 +113808,7 @@ var init_schedule = __esm(() => {
113402
113808
  });
113403
113809
 
113404
113810
  // src/lib/registry-sync.ts
113405
- import { mkdirSync as mkdirSync30, writeFileSync as writeFileSync30 } from "fs";
113811
+ import { mkdirSync as mkdirSync30, writeFileSync as writeFileSync31 } from "fs";
113406
113812
  import { dirname as dirname37, relative as relative11 } from "path";
113407
113813
  function createRegistrySyncArtifact(options = {}) {
113408
113814
  const profile = options.profile ?? "all";
@@ -113461,7 +113867,7 @@ function createRegistrySyncArtifact(options = {}) {
113461
113867
  }
113462
113868
  function writeRegistrySyncArtifact(path, artifact) {
113463
113869
  mkdirSync30(dirname37(path), { recursive: true });
113464
- writeFileSync30(path, `${JSON.stringify(artifact, null, 2)}
113870
+ writeFileSync31(path, `${JSON.stringify(artifact, null, 2)}
113465
113871
  `);
113466
113872
  }
113467
113873
  function buildDocs(name) {
@@ -113480,7 +113886,7 @@ var init_registry_sync = __esm(() => {
113480
113886
  });
113481
113887
 
113482
113888
  // src/lib/review-export.ts
113483
- import { constants as constants22, closeSync as closeSync25, existsSync as existsSync36, fstatSync as fstatSync25, fsyncSync as fsyncSync7, lstatSync as lstatSync33, openSync as openSync25, realpathSync as realpathSync18, statSync as statSync23, writeFileSync as writeFileSync31 } from "fs";
113889
+ import { constants as constants22, closeSync as closeSync25, existsSync as existsSync37, fstatSync as fstatSync25, fsyncSync as fsyncSync7, lstatSync as lstatSync34, openSync as openSync25, realpathSync as realpathSync18, statSync as statSync23, writeFileSync as writeFileSync32 } from "fs";
113484
113890
  import { basename as basename15, dirname as dirname38, isAbsolute as isAbsolute31, join as join63, parse as parse12, resolve as resolve28, sep as sep15 } from "path";
113485
113891
  function exactVersion(spec) {
113486
113892
  const at2 = spec.indexOf("@");
@@ -113496,7 +113902,7 @@ function assertPrivateDestination(output2) {
113496
113902
  throw new Error("Review output must be an absolute, normalized .tar.gz path.");
113497
113903
  }
113498
113904
  const parent = dirname38(output2);
113499
- if (realpathSync18(parent) !== parent || lstatSync33(parent).isSymbolicLink()) {
113905
+ if (realpathSync18(parent) !== parent || lstatSync34(parent).isSymbolicLink()) {
113500
113906
  throw new Error("Review output parent must be a real directory, not a symlink.");
113501
113907
  }
113502
113908
  const parentStat = statSync23(parent);
@@ -113508,7 +113914,7 @@ function assertPrivateDestination(output2) {
113508
113914
  throw new Error("Review output cannot be placed in agent-native discovery or Skills installation paths.");
113509
113915
  }
113510
113916
  for (let current = parent;; current = dirname38(current)) {
113511
- if (existsSync36(join63(current, ".git"))) {
113917
+ if (existsSync37(join63(current, ".git"))) {
113512
113918
  throw new Error("Review output cannot be placed inside a Git worktree.");
113513
113919
  }
113514
113920
  if (current === parse12(current).root)
@@ -113519,7 +113925,7 @@ function assertPrivateDestination(output2) {
113519
113925
  async function exportSkillVersionForReview(spec, output2, client2) {
113520
113926
  const { slug: slug2, version: version2 } = exactVersion(spec);
113521
113927
  const target = assertPrivateDestination(output2);
113522
- if (existsSync36(target))
113928
+ if (existsSync37(target))
113523
113929
  throw new Error("Review output already exists; choose a new filename.");
113524
113930
  const metadata = await client2.getSkillVersion(slug2, version2);
113525
113931
  if (!metadata)
@@ -113546,7 +113952,7 @@ async function exportSkillVersionForReview(spec, output2, client2) {
113546
113952
  }
113547
113953
  const fd = openSync25(target, constants22.O_WRONLY | constants22.O_CREAT | constants22.O_EXCL | constants22.O_NOFOLLOW, 384);
113548
113954
  try {
113549
- writeFileSync31(fd, bytes);
113955
+ writeFileSync32(fd, bytes);
113550
113956
  fsyncSync7(fd);
113551
113957
  if (fstatSync25(fd).size !== bytes.byteLength)
113552
113958
  throw new Error("Incomplete review output write.");
@@ -113793,7 +114199,7 @@ __export(exports_publish, {
113793
114199
  PushSkillError: () => PushSkillError
113794
114200
  });
113795
114201
  import { execFileSync as execFileSync2 } from "child_process";
113796
- import { existsSync as existsSync37, readFileSync as readFileSync44 } from "fs";
114202
+ import { existsSync as existsSync38, readFileSync as readFileSync44 } from "fs";
113797
114203
  import { hostname as hostname5 } from "os";
113798
114204
  import { join as join64 } from "path";
113799
114205
  function registerPublish(parent) {
@@ -113848,7 +114254,7 @@ async function pushSkill(name, options = {}) {
113848
114254
  await verifyPackedCanonicalHash(skill.name, packed);
113849
114255
  const versionManifest = buildVersionManifest(skill.path, packed);
113850
114256
  const skillMdPath = join64(skill.path, "SKILL.md");
113851
- const skillMd = existsSync37(skillMdPath) ? readFileSync44(skillMdPath, "utf-8") : undefined;
114257
+ const skillMd = existsSync38(skillMdPath) ? readFileSync44(skillMdPath, "utf-8") : undefined;
113852
114258
  const base3 = {
113853
114259
  slug: skill.name,
113854
114260
  path: skill.path,
@@ -115567,7 +115973,7 @@ var init_storage2 = __esm(() => {
115567
115973
  });
115568
115974
 
115569
115975
  // src/lib/registry-reconcile.ts
115570
- import { existsSync as existsSync38, readFileSync as readFileSync45, statSync as statSync24, writeFileSync as writeFileSync32 } from "fs";
115976
+ import { existsSync as existsSync39, readFileSync as readFileSync45, statSync as statSync24, writeFileSync as writeFileSync33 } from "fs";
115571
115977
  import { join as join65 } from "path";
115572
115978
  function isDirectory2(path) {
115573
115979
  try {
@@ -115584,7 +115990,7 @@ function migrationNeeded(options) {
115584
115990
  }
115585
115991
  function readBaseline(skillDir) {
115586
115992
  const markerPath = join65(skillDir, PULL_MARKER_FILE);
115587
- if (!existsSync38(markerPath))
115993
+ if (!existsSync39(markerPath))
115588
115994
  return;
115589
115995
  try {
115590
115996
  const marker = JSON.parse(readFileSync45(markerPath, "utf-8"));
@@ -115600,7 +116006,7 @@ function readBaseline(skillDir) {
115600
116006
  }
115601
116007
  function readCursor(root2) {
115602
116008
  const path = join65(root2, SYNC_CURSOR_FILE);
115603
- if (!existsSync38(path))
116009
+ if (!existsSync39(path))
115604
116010
  return { runCount: 0 };
115605
116011
  try {
115606
116012
  const cursor2 = JSON.parse(readFileSync45(path, "utf-8"));
@@ -115631,7 +116037,7 @@ function remoteRowToSkill(record11) {
115631
116037
  }
115632
116038
  function recheckLocalSide(plannedLocal, localDir, ops = {
115633
116039
  pack: (dir) => packSkillBundle(dir).sha256,
115634
- exists: existsSync38
116040
+ exists: existsSync39
115635
116041
  }) {
115636
116042
  let localNow;
115637
116043
  try {
@@ -115882,7 +116288,7 @@ async function reconcileRegistry(options = {}) {
115882
116288
  runCount: readCursor(root2).runCount + 1,
115883
116289
  summary
115884
116290
  };
115885
- writeFileSync32(join65(root2, SYNC_CURSOR_FILE), `${JSON.stringify(cursor2, null, 2)}
116291
+ writeFileSync33(join65(root2, SYNC_CURSOR_FILE), `${JSON.stringify(cursor2, null, 2)}
115886
116292
  `);
115887
116293
  return {
115888
116294
  corpusRoot: root2,
package/bin/mcp.js CHANGED
@@ -20019,7 +20019,7 @@ var package_default;
20019
20019
  var init_package = __esm(() => {
20020
20020
  package_default = {
20021
20021
  name: "@hasna/skills",
20022
- version: "0.10.16",
20022
+ version: "0.10.19",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
package/bin/migrate.js CHANGED
@@ -7,7 +7,7 @@ import { join as join5 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@hasna/skills",
10
- version: "0.10.16",
10
+ version: "0.10.19",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
package/bin/server.js CHANGED
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
24122
24122
  // package.json
24123
24123
  var package_default = {
24124
24124
  name: "@hasna/skills",
24125
- version: "0.10.16",
24125
+ version: "0.10.19",
24126
24126
  description: "Skills library for AI coding agents",
24127
24127
  type: "module",
24128
24128
  bin: {
package/bin/worker.js CHANGED
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
24125
24125
  // package.json
24126
24126
  var package_default = {
24127
24127
  name: "@hasna/skills",
24128
- version: "0.10.16",
24128
+ version: "0.10.19",
24129
24129
  description: "Skills library for AI coding agents",
24130
24130
  type: "module",
24131
24131
  bin: {
@@ -12,6 +12,15 @@ export declare function preflightTarball(bytes: Uint8Array, limits?: {
12
12
  expandedBytes: number;
13
13
  }>;
14
14
  export declare function readBodyCapped(response: Response, limit: number, errorCode: string): Promise<Uint8Array>;
15
+ export declare function adoptCopyfileAliases(options?: {
16
+ homeDir?: string;
17
+ pathValue?: string;
18
+ onAliasSwitched?: (path: string) => void;
19
+ }): JsonObject;
20
+ export declare function rollbackCopyfileAliases(receiptId: string, options?: {
21
+ homeDir?: string;
22
+ pathValue?: string;
23
+ }): JsonObject;
15
24
  export declare function updateCopyfileRuntime(version: string, options?: {
16
25
  homeDir?: string;
17
26
  pathValue?: string;
package/dist/index.js CHANGED
@@ -10877,7 +10877,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
10877
10877
  // package.json
10878
10878
  var package_default = {
10879
10879
  name: "@hasna/skills",
10880
- version: "0.10.16",
10880
+ version: "0.10.19",
10881
10881
  description: "Skills library for AI coding agents",
10882
10882
  type: "module",
10883
10883
  bin: {
package/dist/sdk/index.js CHANGED
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
32373
32373
  // package.json
32374
32374
  var package_default = {
32375
32375
  name: "@hasna/skills",
32376
- version: "0.10.16",
32376
+ version: "0.10.19",
32377
32377
  description: "Skills library for AI coding agents",
32378
32378
  type: "module",
32379
32379
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.10.16",
3
+ "version": "0.10.19",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {