@hasna/skills 0.10.16 → 0.10.18

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/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.18",
36872
36872
  description: "Skills library for AI coding agents",
36873
36873
  type: "module",
36874
36874
  bin: {
@@ -108698,13 +108698,13 @@ function comparePackageTrees(expectedRoot, installedRoot) {
108698
108698
  throw new Error("INSTALLED_PACKAGE_BYTES_MISMATCH");
108699
108699
  return expected.digest;
108700
108700
  }
108701
- function replaceLink(path, target, backupPath, oldLinkTarget) {
108701
+ function replaceLink(path, target, backupPath, oldLinkTarget, oldTarget = resolve23(dirname30(path), oldLinkTarget)) {
108702
108702
  const stat3 = lstatSync28(path);
108703
108703
  if (!stat3.isSymbolicLink() || entryExists(backupPath))
108704
108704
  throw new Error("LAUNCHER_PREIMAGE_DRIFT");
108705
108705
  const temp = `${path}.skills-next-${randomUUID9()}`;
108706
108706
  symlinkSync(oldLinkTarget, backupPath);
108707
- if (readlinkSync5(backupPath) !== oldLinkTarget || realpathSync14(backupPath) !== resolve23(dirname30(path), oldLinkTarget))
108707
+ if (readlinkSync5(backupPath) !== oldLinkTarget || realpathSync14(backupPath) !== oldTarget)
108708
108708
  throw new Error("LAUNCHER_BACKUP_READBACK_MISMATCH");
108709
108709
  symlinkSync(target, temp);
108710
108710
  if (realpathSync14(temp) !== target)
@@ -108745,6 +108745,324 @@ function persistReceipt(path, value) {
108745
108745
  }
108746
108746
  atomicJson(path, value);
108747
108747
  }
108748
+ function persistAliasReceipt(path, value) {
108749
+ if (entryExists(path)) {
108750
+ const priorBytes = readFileSync37(path);
108751
+ const prior = object9(JSON.parse(priorBytes.toString("utf8")), "ALIAS_RECEIPT_INVALID");
108752
+ if (prior.schema !== value.schema || prior.id !== value.id || typeof prior.state !== "string")
108753
+ throw new Error("ALIAS_RECEIPT_DRIFT");
108754
+ const nextBytes = Buffer.from(`${JSON.stringify(value, null, 2)}
108755
+ `);
108756
+ if (priorBytes.equals(nextBytes))
108757
+ return;
108758
+ const history = join55(dirname30(path), "history");
108759
+ if (!entryExists(history))
108760
+ mkdirSync24(history, { mode: 448 });
108761
+ const historyStat = lstatSync28(history);
108762
+ if (!historyStat.isDirectory() || historyStat.isSymbolicLink() || (historyStat.mode & 63) !== 0)
108763
+ throw new Error("ALIAS_RECEIPT_HISTORY_UNSAFE");
108764
+ const saved = join55(history, `${value.id}-${String(prior.state)}-${randomUUID9()}.json`);
108765
+ writeFileSync23(saved, priorBytes, { flag: "wx", mode: 256 });
108766
+ chmodSync4(saved, 256);
108767
+ if (!readFileSync37(saved).equals(priorBytes))
108768
+ throw new Error("ALIAS_RECEIPT_HISTORY_READBACK_MISMATCH");
108769
+ }
108770
+ atomicJson(path, value);
108771
+ }
108772
+ function assertOwnedSafePath(path, home, requireWritable = false) {
108773
+ assertNoSymlinkAncestors(path);
108774
+ for (let current = path;; current = dirname30(current)) {
108775
+ const stat3 = lstatSync28(current);
108776
+ if (!stat3.isDirectory() || ![0, process.getuid?.() ?? -1].includes(stat3.uid) || stat3.mode & 18)
108777
+ throw new Error("ALIAS_PATH_UNSAFE");
108778
+ if (current === path && requireWritable && (stat3.uid !== (process.getuid?.() ?? -1) || (stat3.mode & 128) === 0))
108779
+ throw new Error("ALIAS_DIRECTORY_NOT_OWNED");
108780
+ if (current === dirname30(current) || current === home)
108781
+ break;
108782
+ }
108783
+ }
108784
+ function activeAliasRuntime(home, pathValue) {
108785
+ const layout = resolveRuntimeLayout(home, pathValue);
108786
+ const runtimePath = dirname30(dirname30(dirname30(layout.currentPackageRoot)));
108787
+ const receiptPath = join55(runtimePath, "rollout-receipt.json");
108788
+ const rollout = object9(JSON.parse(readFileSync37(receiptPath, "utf8")), "ROLLOUT_RECEIPT_INVALID");
108789
+ 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) {
108790
+ throw new Error("ACTIVE_RUNTIME_RECEIPT_DRIFT");
108791
+ }
108792
+ verifyPreimageManifest(runtimePath, rollout, home);
108793
+ return { layout, rollout };
108794
+ }
108795
+ function aliasDirs(home) {
108796
+ return [join55(home, ".local", "bin"), join55(home, ".bun", "bin"), "/opt/homebrew/bin", "/usr/local/bin"];
108797
+ }
108798
+ function aliasPackageLink(home, aliasPath) {
108799
+ const dir = dirname30(aliasPath);
108800
+ if (dir === join55(home, ".local", "bin"))
108801
+ return join55(home, ".local", "lib", "node_modules", "@hasna", "skills");
108802
+ if (dir === "/opt/homebrew/bin")
108803
+ return "/opt/homebrew/lib/node_modules/@hasna/skills";
108804
+ if (dir === "/usr/local/bin")
108805
+ return "/usr/local/lib/node_modules/@hasna/skills";
108806
+ return null;
108807
+ }
108808
+ function inspectAliasPackageChain(home, aliasPath, oldLinkTarget, oldTarget, bin, expected, verify = false) {
108809
+ const immediate = resolve23(dirname30(aliasPath), oldLinkTarget);
108810
+ if (immediate === oldTarget) {
108811
+ if (expected !== undefined)
108812
+ throw new Error("ALIAS_LINK_CHAIN_DRIFT");
108813
+ return;
108814
+ }
108815
+ const packageLinkPath = aliasPackageLink(home, aliasPath);
108816
+ const physicalPackage = dirname30(dirname30(oldTarget));
108817
+ if (!packageLinkPath || immediate !== join55(packageLinkPath, bin) || !isWithin(join55(home, ".hasna", "skills", "runtime"), physicalPackage)) {
108818
+ throw new Error("ALIAS_LINK_CHAIN_UNSUPPORTED");
108819
+ }
108820
+ assertOwnedSafePath(dirname30(packageLinkPath), home);
108821
+ let stat3, packageLinkTarget, resolved;
108822
+ try {
108823
+ stat3 = lstatSync28(packageLinkPath);
108824
+ packageLinkTarget = readlinkSync5(packageLinkPath);
108825
+ resolved = realpathSync14(packageLinkPath);
108826
+ } catch {
108827
+ throw new Error("ALIAS_LINK_CHAIN_UNSUPPORTED");
108828
+ }
108829
+ if (expected !== undefined && (expected.packageLinkPath !== packageLinkPath || expected.packageLinkTarget !== packageLinkTarget))
108830
+ throw new Error("ALIAS_LINK_CHAIN_DRIFT");
108831
+ if (!stat3.isSymbolicLink() || stat3.uid !== (process.getuid?.() ?? -1) || resolve23(dirname30(packageLinkPath), packageLinkTarget) !== physicalPackage || resolved !== physicalPackage)
108832
+ throw new Error("ALIAS_LINK_CHAIN_UNSUPPORTED");
108833
+ const chain = { packageLinkPath, packageLinkTarget };
108834
+ if (verify && expected === undefined)
108835
+ throw new Error("ALIAS_LINK_CHAIN_DRIFT");
108836
+ return chain;
108837
+ }
108838
+ function validateAliasSource(home, name, actual) {
108839
+ const packageRoot = dirname30(dirname30(actual));
108840
+ const roots = [join55(home, ".hasna", "skills", "runtime"), join55(home, ".bun", "install"), join55(home, ".local", "lib", "node_modules")];
108841
+ if (basename13(packageRoot) !== "skills" || basename13(dirname30(packageRoot)) !== "@hasna" || !roots.some((root2) => isWithin(root2, packageRoot))) {
108842
+ throw new Error("ALIAS_SOURCE_ROOT_UNSUPPORTED");
108843
+ }
108844
+ assertOwnedSafePath(packageRoot, home);
108845
+ const manifestPath = join55(packageRoot, "package.json");
108846
+ const manifestStat = lstatSync28(manifestPath), binaryStat = lstatSync28(actual);
108847
+ 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)
108848
+ throw new Error("ALIAS_SOURCE_UNSAFE");
108849
+ const pkg = readPackage(manifestPath);
108850
+ if (!pkg.bins[name] || actual !== join55(packageRoot, pkg.bins[name]))
108851
+ throw new Error("ALIAS_SOURCE_BIN_MISMATCH");
108852
+ return { version: pkg.version, packageRoot, binarySha256: hash5(readFileSync37(actual)) };
108853
+ }
108854
+ function adoptCopyfileAliases(options = {}) {
108855
+ const home = realpathSync14(resolve23(options.homeDir ?? process.env.HOME ?? ""));
108856
+ const pathValue = options.pathValue ?? process.env.PATH ?? "";
108857
+ const { layout, rollout } = activeAliasRuntime(home, pathValue);
108858
+ const releaseLock = acquireRuntimeLock(layout.runtimeRoot);
108859
+ const id2 = randomUUID9();
108860
+ const aliases = [];
108861
+ const currentRuntime = dirname30(dirname30(dirname30(layout.currentPackageRoot)));
108862
+ const receiptDir = join55(currentRuntime, "alias-adoptions");
108863
+ const receiptPath = join55(receiptDir, `${id2}.json`);
108864
+ let receipt;
108865
+ try {
108866
+ const locked2 = activeAliasRuntime(home, pathValue);
108867
+ if (locked2.layout.currentPackageRoot !== layout.currentPackageRoot || locked2.rollout.id !== rollout.id)
108868
+ throw new Error("ACTIVE_RUNTIME_CHANGED_BEFORE_ALIAS_ADOPTION");
108869
+ for (const dir of aliasDirs(home)) {
108870
+ if (!entryExists(dir))
108871
+ continue;
108872
+ for (const [name, target] of Object.entries(layout.bin)) {
108873
+ const path = join55(dir, name);
108874
+ if (!entryExists(path))
108875
+ continue;
108876
+ assertOwnedSafePath(dir, home, true);
108877
+ const stat3 = lstatSync28(path);
108878
+ if (!stat3.isSymbolicLink() || stat3.uid !== (process.getuid?.() ?? -1))
108879
+ throw new Error("ALIAS_LINK_NOT_OWNED");
108880
+ const oldLinkTarget = readlinkSync5(path);
108881
+ let actual;
108882
+ try {
108883
+ actual = realpathSync14(path);
108884
+ } catch {
108885
+ throw new Error("ALIAS_LINK_CHAIN_UNSUPPORTED");
108886
+ }
108887
+ const newTarget = join55(layout.currentPackageRoot, target);
108888
+ if (actual === newTarget)
108889
+ continue;
108890
+ const chain = inspectAliasPackageChain(home, path, oldLinkTarget, actual, target);
108891
+ const source = validateAliasSource(home, name, actual);
108892
+ const backupPath = `${path}.skills-alias-prev-${id2}`;
108893
+ if (entryExists(backupPath))
108894
+ throw new Error("ALIAS_BACKUP_COLLISION");
108895
+ aliases.push({ path, oldTarget: actual, oldLinkTarget, oldVersion: source.version, oldBinarySha256: source.binarySha256, newTarget, backupPath, ...chain ? { chain } : {} });
108896
+ }
108897
+ }
108898
+ if (!aliases.length)
108899
+ return { adopted: true, version: layout.currentVersion, aliasCount: 0 };
108900
+ mkdirSync24(receiptDir, { recursive: true, mode: 448 });
108901
+ const receiptDirStat = lstatSync28(receiptDir);
108902
+ if (!receiptDirStat.isDirectory() || receiptDirStat.isSymbolicLink() || receiptDirStat.mode & 63)
108903
+ throw new Error("ALIAS_RECEIPT_DIRECTORY_UNSAFE");
108904
+ receipt = {
108905
+ schema: "skills.copyfile-alias-adoption.v1",
108906
+ id: id2,
108907
+ state: "prepared",
108908
+ targetVersion: layout.currentVersion,
108909
+ targetPackageRoot: layout.currentPackageRoot,
108910
+ rolloutReceiptId: rollout.id,
108911
+ aliases,
108912
+ switchedAliases: [],
108913
+ rollbackCompletedAliases: []
108914
+ };
108915
+ writeJsonPrivate(receiptPath, receipt);
108916
+ for (const item of aliases) {
108917
+ if (readlinkSync5(item.path) !== item.oldLinkTarget || realpathSync14(item.path) !== item.oldTarget)
108918
+ throw new Error("ALIAS_CHANGED_BEFORE_SWITCH");
108919
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, layout.bin[basename13(item.path)], item.chain, true);
108920
+ const source = validateAliasSource(home, basename13(item.path), item.oldTarget);
108921
+ if (source.version !== item.oldVersion || source.binarySha256 !== item.oldBinarySha256)
108922
+ throw new Error("ALIAS_SOURCE_DRIFT");
108923
+ receipt.state = "switching";
108924
+ receipt.switchedAliases = [...receipt.switchedAliases, item.path];
108925
+ persistAliasReceipt(receiptPath, receipt);
108926
+ replaceLink(item.path, item.newTarget, item.backupPath, item.oldLinkTarget, item.oldTarget);
108927
+ if (realpathSync14(item.path) !== item.newTarget || realpathSync14(item.backupPath) !== item.oldTarget)
108928
+ throw new Error("ALIAS_SWITCH_READBACK_MISMATCH");
108929
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, layout.bin[basename13(item.path)], item.chain, true);
108930
+ options.onAliasSwitched?.(item.path);
108931
+ }
108932
+ for (const item of aliases) {
108933
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, layout.bin[basename13(item.path)], item.chain, true);
108934
+ if (readlinkSync5(item.backupPath) !== item.oldLinkTarget || realpathSync14(item.backupPath) !== item.oldTarget)
108935
+ throw new Error("ALIAS_BACKUP_DRIFT");
108936
+ }
108937
+ receipt.state = "switched";
108938
+ persistAliasReceipt(receiptPath, receipt);
108939
+ return { adopted: true, version: layout.currentVersion, receiptId: id2, aliasCount: aliases.length };
108940
+ } catch (error61) {
108941
+ if (receipt) {
108942
+ let failed = false;
108943
+ for (const item of [...aliases].reverse()) {
108944
+ try {
108945
+ if (!entryExists(item.backupPath)) {
108946
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, layout.bin[basename13(item.path)], item.chain, true);
108947
+ const source2 = validateAliasSource(home, basename13(item.path), item.oldTarget);
108948
+ if (!entryExists(item.path) || readlinkSync5(item.path) !== item.oldLinkTarget || realpathSync14(item.path) !== item.oldTarget || source2.version !== item.oldVersion || source2.binarySha256 !== item.oldBinarySha256)
108949
+ failed = true;
108950
+ continue;
108951
+ }
108952
+ if (readlinkSync5(item.backupPath) !== item.oldLinkTarget || realpathSync14(item.backupPath) !== item.oldTarget)
108953
+ throw new Error("ALIAS_BACKUP_DRIFT");
108954
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, layout.bin[basename13(item.path)], item.chain, true);
108955
+ const source = validateAliasSource(home, basename13(item.path), item.oldTarget);
108956
+ if (source.version !== item.oldVersion || source.binarySha256 !== item.oldBinarySha256)
108957
+ throw new Error("ALIAS_SOURCE_DRIFT");
108958
+ if (realpathSync14(item.path) === item.oldTarget && readlinkSync5(item.path) === item.oldLinkTarget)
108959
+ continue;
108960
+ if (realpathSync14(item.path) !== item.newTarget)
108961
+ throw new Error("ALIAS_CHANGED_DURING_ROLLBACK");
108962
+ const after2 = `${item.path}.skills-alias-after-${id2}`;
108963
+ if (entryExists(after2))
108964
+ throw new Error("ALIAS_AFTER_COLLISION");
108965
+ symlinkSync(item.newTarget, after2);
108966
+ const temp = `${item.path}.skills-alias-rollback-${randomUUID9()}`;
108967
+ symlinkSync(item.oldLinkTarget, temp);
108968
+ renameSync13(temp, item.path);
108969
+ if (readlinkSync5(item.path) !== item.oldLinkTarget)
108970
+ throw new Error("ALIAS_ROLLBACK_READBACK_MISMATCH");
108971
+ receipt.rollbackCompletedAliases = [...receipt.rollbackCompletedAliases, item.path];
108972
+ persistAliasReceipt(receiptPath, receipt);
108973
+ } catch {
108974
+ failed = true;
108975
+ }
108976
+ }
108977
+ receipt.state = failed ? "rollback-required" : "rolled-back";
108978
+ try {
108979
+ persistAliasReceipt(receiptPath, receipt);
108980
+ } catch {
108981
+ failed = true;
108982
+ }
108983
+ throw new Error(failed ? "ALIAS_ADOPTION_ROLLBACK_REQUIRED" : "ALIAS_ADOPTION_ROLLED_BACK");
108984
+ }
108985
+ throw error61 instanceof Error ? error61 : new Error("ALIAS_ADOPTION_FAILED");
108986
+ } finally {
108987
+ releaseLock();
108988
+ }
108989
+ }
108990
+ function rollbackCopyfileAliases(receiptId, options = {}) {
108991
+ if (!/^[0-9a-f-]{36}$/.test(receiptId))
108992
+ throw new Error("ALIAS_RECEIPT_ID_INVALID");
108993
+ const home = realpathSync14(resolve23(options.homeDir ?? process.env.HOME ?? ""));
108994
+ const pathValue = options.pathValue ?? process.env.PATH ?? "";
108995
+ const { layout, rollout } = activeAliasRuntime(home, pathValue);
108996
+ const runtimePath = dirname30(dirname30(dirname30(layout.currentPackageRoot)));
108997
+ const receiptPath = join55(runtimePath, "alias-adoptions", `${receiptId}.json`);
108998
+ const releaseLock = acquireRuntimeLock(layout.runtimeRoot);
108999
+ try {
109000
+ const locked2 = activeAliasRuntime(home, pathValue);
109001
+ if (locked2.layout.currentPackageRoot !== layout.currentPackageRoot || locked2.rollout.id !== rollout.id)
109002
+ throw new Error("ACTIVE_RUNTIME_CHANGED_BEFORE_ALIAS_ROLLBACK");
109003
+ const receipt = object9(JSON.parse(readFileSync37(receiptPath, "utf8")), "ALIAS_RECEIPT_INVALID");
109004
+ 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))
109005
+ throw new Error("ALIAS_RECEIPT_BINDING_INVALID");
109006
+ const seen = new Set;
109007
+ for (const item of receipt.aliases) {
109008
+ const name = basename13(item.path), target = layout.bin[name];
109009
+ 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")) {
109010
+ throw new Error("ALIAS_RECEIPT_LAUNCHER_INVALID");
109011
+ }
109012
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, target, item.chain, true);
109013
+ seen.add(item.path);
109014
+ assertOwnedSafePath(dirname30(item.path), home, true);
109015
+ const source = validateAliasSource(home, name, item.oldTarget);
109016
+ if (source.version !== item.oldVersion || source.binarySha256 !== item.oldBinarySha256)
109017
+ throw new Error("ALIAS_PREIMAGE_DRIFT");
109018
+ const stat3 = lstatSync28(item.path);
109019
+ if (!stat3.isSymbolicLink() || stat3.uid !== (process.getuid?.() ?? -1))
109020
+ throw new Error("ALIAS_LINK_DRIFT");
109021
+ const current = realpathSync14(item.path);
109022
+ if (current !== item.newTarget && (current !== item.oldTarget || readlinkSync5(item.path) !== item.oldLinkTarget))
109023
+ throw new Error("ALIAS_TARGET_DRIFT");
109024
+ if (entryExists(item.backupPath)) {
109025
+ const backup = lstatSync28(item.backupPath);
109026
+ if (!backup.isSymbolicLink() || readlinkSync5(item.backupPath) !== item.oldLinkTarget || realpathSync14(item.backupPath) !== item.oldTarget)
109027
+ throw new Error("ALIAS_PREIMAGE_DRIFT");
109028
+ } else if (current !== item.oldTarget) {
109029
+ throw new Error("ALIAS_BACKUP_MISSING_FOR_SWITCH");
109030
+ }
109031
+ }
109032
+ let restored = 0;
109033
+ for (const item of [...receipt.aliases].reverse()) {
109034
+ if (realpathSync14(item.path) === item.oldTarget && readlinkSync5(item.path) === item.oldLinkTarget)
109035
+ continue;
109036
+ receipt.state = "rollback-required";
109037
+ persistAliasReceipt(receiptPath, receipt);
109038
+ const after2 = `${item.path}.skills-alias-after-${receiptId}`;
109039
+ if (!entryExists(after2)) {
109040
+ symlinkSync(item.newTarget, after2);
109041
+ if (realpathSync14(after2) !== item.newTarget)
109042
+ throw new Error("ALIAS_AFTER_READBACK_MISMATCH");
109043
+ } else if (realpathSync14(after2) !== item.newTarget)
109044
+ throw new Error("ALIAS_AFTER_DRIFT");
109045
+ if (realpathSync14(item.path) !== item.newTarget)
109046
+ throw new Error("ALIAS_TARGET_DRIFT");
109047
+ const temp = `${item.path}.skills-alias-rollback-${randomUUID9()}`;
109048
+ symlinkSync(item.oldLinkTarget, temp);
109049
+ renameSync13(temp, item.path);
109050
+ if (readlinkSync5(item.path) !== item.oldLinkTarget || realpathSync14(item.path) !== item.oldTarget)
109051
+ throw new Error("ALIAS_ROLLBACK_READBACK_MISMATCH");
109052
+ receipt.rollbackCompletedAliases = [...new Set([...receipt.rollbackCompletedAliases, item.path])];
109053
+ persistAliasReceipt(receiptPath, receipt);
109054
+ restored++;
109055
+ }
109056
+ for (const item of receipt.aliases) {
109057
+ inspectAliasPackageChain(home, item.path, item.oldLinkTarget, item.oldTarget, layout.bin[basename13(item.path)], item.chain, true);
109058
+ }
109059
+ receipt.state = "rolled-back";
109060
+ persistAliasReceipt(receiptPath, receipt);
109061
+ return { rolledBack: true, receiptId, restoredAliasCount: restored };
109062
+ } finally {
109063
+ releaseLock();
109064
+ }
109065
+ }
108748
109066
  async function updateCopyfileRuntime(version2, options = {}) {
108749
109067
  if (!new RegExp(SEMVER_PATTERN).test(version2))
108750
109068
  throw new Error("EXACT_VERSION_REQUIRED");
@@ -109091,12 +109409,40 @@ function registerRuntime(parent) {
109091
109409
  });
109092
109410
  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
109411
  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" }));
109412
+ 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) => {
109413
+ if ([options.version, options.rollback, options.adoptAliases, options.rollbackAliases].filter(Boolean).length > 1) {
109414
+ console.error(JSON.stringify({ error: "SELF_UPDATE_OPERATIONS_ARE_MUTUALLY_EXCLUSIVE" }));
109097
109415
  process.exitCode = 1;
109098
109416
  return;
109099
109417
  }
109418
+ if (options.adoptAliases) {
109419
+ try {
109420
+ const result2 = adoptCopyfileAliases();
109421
+ if (options.json)
109422
+ console.log(JSON.stringify(result2));
109423
+ else
109424
+ console.log(source_default.green(`Adopted ${String(result2.aliasCount)} verified Skills aliases into the active copyfile runtime.`));
109425
+ } catch (error61) {
109426
+ const message = error61 instanceof Error && /^[A-Z0-9_]+$/.test(error61.message) ? error61.message : "ALIAS_ADOPTION_FAILED";
109427
+ console.error(JSON.stringify({ adopted: false, error: message }));
109428
+ process.exitCode = 1;
109429
+ }
109430
+ return;
109431
+ }
109432
+ if (options.rollbackAliases) {
109433
+ try {
109434
+ const result2 = rollbackCopyfileAliases(options.rollbackAliases);
109435
+ if (options.json)
109436
+ console.log(JSON.stringify(result2));
109437
+ else
109438
+ console.log(source_default.green(`Restored ${String(result2.restoredAliasCount)} Skills aliases from the adoption receipt.`));
109439
+ } catch (error61) {
109440
+ const message = error61 instanceof Error && /^[A-Z0-9_]+$/.test(error61.message) ? error61.message : "ALIAS_ROLLBACK_FAILED";
109441
+ console.error(JSON.stringify({ rolledBack: false, error: message }));
109442
+ process.exitCode = 1;
109443
+ }
109444
+ return;
109445
+ }
109100
109446
  if (options.version) {
109101
109447
  try {
109102
109448
  const result2 = await updateCopyfileRuntime(options.version);
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.18",
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.18",
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.18",
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.18",
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.18",
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.18",
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.18",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {