@hasna/skills 0.10.0 → 0.10.2

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/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.0",
20022
+ version: "0.10.2",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
@@ -20974,10 +20974,16 @@ var init_transport = __esm(() => {
20974
20974
  ]);
20975
20975
  });
20976
20976
 
20977
+ // src/lib/cli-credential-profile.ts
20978
+ function selectedCliSkillsCredentialProfile() {
20979
+ return cliCredentialProfile;
20980
+ }
20981
+ var cliCredentialProfile;
20982
+
20977
20983
  // src/lib/instance-credentials.ts
20978
20984
  import { closeSync as closeSync3, constants, fstatSync as fstatSync3, lstatSync as lstatSync2, openSync as openSync3, readSync } from "fs";
20979
20985
  function selectedSkillsProfile(env, explicit) {
20980
- const selected = explicit ?? env.HASNA_PROFILE;
20986
+ const selected = explicit ?? selectedCliSkillsCredentialProfile() ?? env.HASNA_PROFILE;
20981
20987
  if (selected === undefined)
20982
20988
  return null;
20983
20989
  const profile = selected.trim();
@@ -21263,11 +21269,13 @@ function snapshotSkillsEnvironment(env) {
21263
21269
  return Object.freeze(snapshot);
21264
21270
  }
21265
21271
  function snapshotSkillsOptions(env, options) {
21272
+ const profile = options.credentials?.profile ?? selectedCliSkillsCredentialProfile();
21273
+ const credentials = profile === undefined ? options.credentials : { ...options.credentials, profile };
21266
21274
  if (env !== process.env)
21267
- return options;
21268
- return { ...options, credentials: { ...options.credentials, keychain: {
21269
- ...options.credentials?.keychain,
21270
- enabled: options.credentials?.keychain?.enabled ?? true
21275
+ return credentials === options.credentials ? options : { ...options, credentials };
21276
+ return { ...options, credentials: { ...credentials, keychain: {
21277
+ ...credentials?.keychain,
21278
+ enabled: credentials?.keychain?.enabled ?? true
21271
21279
  } } };
21272
21280
  }
21273
21281
  function resolveSkillsFleetOrThrow(env, options) {
@@ -21355,8 +21363,9 @@ async function resolveSkillsApiKey(env = process.env, options = {}) {
21355
21363
  }
21356
21364
  async function resolveSkillsConnection(env = process.env, options = {}) {
21357
21365
  const snapshotEnv = snapshotSkillsEnvironment(env);
21358
- const assertFilesUnchanged = selectsSkillsLocalMode(snapshotEnv) ? () => {} : captureSkillsCredentialFiles(skillsProfileCredentialFiles(snapshotEnv, options.credentials?.profile));
21359
- const fleet = resolveSkillsFleet(snapshotEnv, snapshotSkillsOptions(env, options));
21366
+ const resolvedOptions = snapshotSkillsOptions(env, options);
21367
+ const assertFilesUnchanged = selectsSkillsLocalMode(snapshotEnv) ? () => {} : captureSkillsCredentialFiles(skillsProfileCredentialFiles(snapshotEnv, resolvedOptions.credentials?.profile));
21368
+ const fleet = resolveSkillsFleet(snapshotEnv, resolvedOptions);
21360
21369
  if (fleet.mode === "local" && env === process.env)
21361
21370
  noticeLocalSkillsMode();
21362
21371
  if (fleet.mode !== "hosted")
@@ -46391,7 +46400,9 @@ var init_skill_version = __esm(() => {
46391
46400
 
46392
46401
  // src/lib/selection-cache.ts
46393
46402
  import { createHash as createHash7, randomUUID } from "crypto";
46394
- import { closeSync as closeSync6, constants as constants3, fstatSync as fstatSync6, fsyncSync, lstatSync as lstatSync8, mkdirSync as mkdirSync3, openSync as openSync6, readSync as readSync3, renameSync, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
46403
+ import { spawnSync as spawnSync2 } from "child_process";
46404
+ import { closeSync as closeSync6, constants as constants3, fstatSync as fstatSync6, fsyncSync, lstatSync as lstatSync8, mkdirSync as mkdirSync3, openSync as openSync6, readFileSync as readFileSync10, readlinkSync as readlinkSync2, readSync as readSync3, renameSync, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
46405
+ import { hostname as hostname3 } from "os";
46395
46406
  import { dirname as dirname8, join as join14, parse as parse7, resolve as resolve7 } from "path";
46396
46407
  import { isDeepStrictEqual } from "util";
46397
46408
  function selectionCacheRoot(options = {}) {
@@ -46661,6 +46672,164 @@ function readSkillSessionSnapshotIfExists(sessionId, options = {}) {
46661
46672
  function skillSessionSnapshotBinding(snapshot) {
46662
46673
  return { sessionId: snapshot.sessionId, generation: snapshot.generation, receiptSha256: snapshot.receiptSha256 };
46663
46674
  }
46675
+ function localBootId() {
46676
+ try {
46677
+ if (process.platform === "linux") {
46678
+ const value = readFileSync10("/proc/sys/kernel/random/boot_id", "utf8").trim();
46679
+ return /^[a-f0-9-]{36}$/i.test(value) ? value : null;
46680
+ }
46681
+ if (process.platform === "darwin") {
46682
+ const result = spawnSync2("/usr/sbin/sysctl", ["-n", "kern.boottime"], { encoding: "utf8", timeout: 3000, env: { LC_ALL: "C" } });
46683
+ if (result.status !== 0 || result.error)
46684
+ return null;
46685
+ const seconds = /\bsec\s*=\s*(\d+)\b/.exec(result.stdout)?.[1];
46686
+ return seconds ? `darwin:${seconds}` : null;
46687
+ }
46688
+ } catch {}
46689
+ return null;
46690
+ }
46691
+ function processStart(pid) {
46692
+ if (!Number.isSafeInteger(pid) || pid <= 0)
46693
+ return { state: "unknown" };
46694
+ if (process.platform === "linux") {
46695
+ try {
46696
+ const stat = readFileSync10(`/proc/${pid}/stat`, "utf8");
46697
+ const close = stat.lastIndexOf(")");
46698
+ const start = close < 0 ? undefined : stat.slice(close + 2).trim().split(/\s+/)[19];
46699
+ return start && /^\d+$/.test(start) ? { state: "alive", start: `linux:${start}` } : { state: "unknown" };
46700
+ } catch (error61) {
46701
+ return { state: error61.code === "ENOENT" ? "absent" : "unknown" };
46702
+ }
46703
+ }
46704
+ if (process.platform === "darwin") {
46705
+ const result = spawnSync2("/bin/ps", ["-p", String(pid), "-o", "lstart="], { encoding: "utf8", timeout: 3000, env: { LC_ALL: "C" } });
46706
+ if (result.error || result.signal)
46707
+ return { state: "unknown" };
46708
+ const start = result.stdout.trim();
46709
+ if (result.status === 1 && !start && !result.stderr.trim())
46710
+ return { state: "absent" };
46711
+ return result.status === 0 && start ? { state: "alive", start: `darwin:${start}` } : { state: "unknown" };
46712
+ }
46713
+ return { state: "unknown" };
46714
+ }
46715
+ function localLockIdentity() {
46716
+ const bootId = localBootId(), owner = processStart(process.pid);
46717
+ if (!bootId || owner.state !== "alive")
46718
+ return null;
46719
+ let pidNamespace;
46720
+ if (process.platform === "linux") {
46721
+ try {
46722
+ pidNamespace = readlinkSync2("/proc/self/ns/pid");
46723
+ } catch {
46724
+ return null;
46725
+ }
46726
+ if (!/^pid:\[\d+\]$/.test(pidNamespace))
46727
+ return null;
46728
+ }
46729
+ return { host: hostname3(), bootId, pidNamespace, processStart: owner.start };
46730
+ }
46731
+ function readSessionLock(sessionId, options) {
46732
+ const path = `${sessionReceiptPath(sessionId, options)}.write-lock`;
46733
+ const before = lstatSync8(path, { throwIfNoEntry: false });
46734
+ if (!before)
46735
+ return null;
46736
+ if (!before.isFile() || before.nlink !== 1 || process.getuid && before.uid !== process.getuid()) {
46737
+ throw new SkillSelectionError("SESSION_WRITE_LOCKED", "The session lock is not a private regular file; recovery requires review.");
46738
+ }
46739
+ const bytes = readRegularFile(path, SESSION_LOCK_MAX_BYTES);
46740
+ const after = lstatSync8(path, { throwIfNoEntry: false });
46741
+ if (!bytes || !after?.isFile() || before.dev !== after.dev || before.ino !== after.ino || before.mtimeMs !== after.mtimeMs) {
46742
+ throw new SkillSelectionError("SESSION_WRITE_LOCKED", "The session lock changed during inspection; recovery requires review.");
46743
+ }
46744
+ let marker;
46745
+ try {
46746
+ marker = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
46747
+ } catch {
46748
+ throw new SkillSelectionError("SESSION_WRITE_LOCKED", "The session lock is unreadable; recovery requires review.");
46749
+ }
46750
+ if (!marker || ![1, 2].includes(marker.schemaVersion) || marker.sessionId !== sessionId || !/^[a-f0-9-]{36}$/i.test(marker.operationId) || !Number.isSafeInteger(marker.pid) || marker.pid <= 0 || marker.schemaVersion === 2 && (!marker.host || !marker.bootId || !marker.processStart || process.platform === "linux" && !marker.pidNamespace)) {
46751
+ throw new SkillSelectionError("SESSION_WRITE_LOCKED", "The session lock has invalid ownership metadata; recovery requires review.");
46752
+ }
46753
+ return { path, bytes, marker, sha256: sha256Hex(bytes), dev: after.dev, ino: after.ino, mtimeMs: after.mtimeMs };
46754
+ }
46755
+ function writerIsGone(lock, explicitLocalHost) {
46756
+ if (Date.now() - lock.mtimeMs < SESSION_LOCK_MIN_AGE_MS)
46757
+ return false;
46758
+ const owner = processStart(lock.marker.pid);
46759
+ if (owner.state === "unknown")
46760
+ return false;
46761
+ if (lock.marker.schemaVersion === 1)
46762
+ return explicitLocalHost && owner.state === "absent";
46763
+ const local = localLockIdentity();
46764
+ if (!local || lock.marker.host !== local.host || lock.marker.bootId !== local.bootId || lock.marker.pidNamespace !== local.pidNamespace)
46765
+ return false;
46766
+ return owner.state === "absent" || owner.state === "alive" && owner.start !== lock.marker.processStart;
46767
+ }
46768
+ function archiveSessionLock(lock, receipt, explicitLocalHost) {
46769
+ const guardPath = `${lock.path}.recovery-guard`;
46770
+ let guardFd;
46771
+ try {
46772
+ guardFd = openSync6(guardPath, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | constants3.O_NOFOLLOW, 384);
46773
+ } catch (error61) {
46774
+ if (error61.code === "EEXIST")
46775
+ return null;
46776
+ throw error61;
46777
+ }
46778
+ const guardStat = fstatSync6(guardFd);
46779
+ try {
46780
+ writeFileSync3(guardFd, `${JSON.stringify({ schemaVersion: 1, pid: process.pid, operationId: randomUUID() })}
46781
+ `);
46782
+ fsyncSync(guardFd);
46783
+ const cacheOptions = { cacheDir: dirname8(dirname8(lock.path)) };
46784
+ const current = readSessionLock(lock.marker.sessionId, cacheOptions);
46785
+ if (!current || current.dev !== lock.dev || current.ino !== lock.ino || current.sha256 !== lock.sha256 || current.mtimeMs !== lock.mtimeMs || !writerIsGone(current, explicitLocalHost))
46786
+ return null;
46787
+ const currentReceipt = readSkillSessionSnapshotIfExists(lock.marker.sessionId, cacheOptions);
46788
+ if (currentReceipt?.sha256 !== receipt?.sha256 || currentReceipt?.generation !== receipt?.generation)
46789
+ return null;
46790
+ const directory = join14(dirname8(dirname8(lock.path)), "session-lock-recoveries", randomUUID());
46791
+ const archivePath = join14(directory, "original.write-lock");
46792
+ const recordPath = join14(directory, "recovery.json");
46793
+ assertRegularPath(archivePath, true);
46794
+ archiveBytes(recordPath, Buffer.from(`${JSON.stringify({
46795
+ schemaVersion: 1,
46796
+ sessionId: lock.marker.sessionId,
46797
+ originalPath: lock.path,
46798
+ lockSha256: lock.sha256,
46799
+ lockMtime: new Date(lock.mtimeMs).toISOString(),
46800
+ lockPid: lock.marker.pid,
46801
+ receiptSha256: receipt?.sha256 ?? null,
46802
+ receiptGeneration: receipt?.generation ?? null,
46803
+ status: "prepared"
46804
+ })}
46805
+ `));
46806
+ syncDirectory(directory);
46807
+ syncDirectory(dirname8(directory));
46808
+ const finalLock = readSessionLock(lock.marker.sessionId, cacheOptions);
46809
+ if (!finalLock || finalLock.dev !== lock.dev || finalLock.ino !== lock.ino || finalLock.sha256 !== lock.sha256 || readSkillSessionSnapshotIfExists(lock.marker.sessionId, cacheOptions)?.sha256 !== receipt?.sha256)
46810
+ return null;
46811
+ let moved = false;
46812
+ try {
46813
+ renameSync(lock.path, archivePath);
46814
+ moved = true;
46815
+ syncDirectory(directory);
46816
+ syncDirectory(dirname8(lock.path));
46817
+ if (sha256Hex(readRegularFile(archivePath, SESSION_LOCK_MAX_BYTES)) !== lock.sha256) {
46818
+ throw new Error("Archived lock bytes changed");
46819
+ }
46820
+ } catch (error61) {
46821
+ if (moved)
46822
+ throw new SkillSelectionError("SESSION_LOCK_RECOVERY_INCOMPLETE", "The lock moved, but durable recovery could not be confirmed; inspect the preserved recovery directory and session receipt.");
46823
+ throw error61;
46824
+ }
46825
+ return archivePath;
46826
+ } finally {
46827
+ closeSync6(guardFd);
46828
+ const current = lstatSync8(guardPath, { throwIfNoEntry: false });
46829
+ if (current?.isFile() && current.dev === guardStat.dev && current.ino === guardStat.ino)
46830
+ unlinkSync(guardPath);
46831
+ }
46832
+ }
46664
46833
  function withSessionWriteLocks(sessionIds, options, action) {
46665
46834
  const operationId = randomUUID();
46666
46835
  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);
@@ -46680,13 +46849,30 @@ function withSessionWriteLocks(sessionIds, options, action) {
46680
46849
  try {
46681
46850
  fd = openSync6(lock.path, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | constants3.O_NOFOLLOW, 384);
46682
46851
  } catch (error61) {
46683
- if (error61.code === "EEXIST")
46684
- 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.");
46685
- throw error61;
46852
+ if (error61.code !== "EEXIST")
46853
+ throw error61;
46854
+ try {
46855
+ const stale = readSessionLock(lock.sessionId, options);
46856
+ const receipt = stale && readSkillSessionSnapshotIfExists(lock.sessionId, options);
46857
+ if (stale?.marker.schemaVersion === 2 && writerIsGone(stale, false)) {
46858
+ archiveSessionLock(stale, receipt, false);
46859
+ }
46860
+ } catch (recoveryError) {
46861
+ if (recoveryError instanceof SkillSelectionError && recoveryError.code === "SESSION_LOCK_RECOVERY_INCOMPLETE")
46862
+ throw recoveryError;
46863
+ }
46864
+ try {
46865
+ fd = openSync6(lock.path, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | constants3.O_NOFOLLOW, 384);
46866
+ } catch (retryError) {
46867
+ if (retryError.code === "EEXIST")
46868
+ throw new SkillSelectionError("SESSION_WRITE_LOCKED", "Another Skills process owns a required session write lock; retry after it completes. A surviving legacy lock requires explicit recovery review.");
46869
+ throw retryError;
46870
+ }
46686
46871
  }
46687
46872
  const stat = fstatSync6(fd);
46688
46873
  owned.push({ path: lock.path, fd, dev: stat.dev, ino: stat.ino });
46689
- writeFileSync3(fd, `${JSON.stringify({ schemaVersion: 1, operationId, sessionId: lock.sessionId, pid: process.pid })}
46874
+ const identity3 = localLockIdentity();
46875
+ writeFileSync3(fd, `${JSON.stringify({ schemaVersion: identity3 ? 2 : 1, operationId, sessionId: lock.sessionId, pid: process.pid, ...identity3 })}
46690
46876
  `);
46691
46877
  }
46692
46878
  return action(assertOwned);
@@ -46749,7 +46935,17 @@ function syncDirectory(path) {
46749
46935
  closeSync6(fd);
46750
46936
  }
46751
46937
  }
46752
- var SkillSelectionError, MAX_CACHED_PROFILE_AGE_MS, SELECTION_LOCK_FILE = "selection.lock.json";
46938
+ function archiveBytes(path, bytes) {
46939
+ assertRegularPath(path, true);
46940
+ const fd = openSync6(path, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | constants3.O_NOFOLLOW, 384);
46941
+ try {
46942
+ writeFileSync3(fd, bytes);
46943
+ fsyncSync(fd);
46944
+ } finally {
46945
+ closeSync6(fd);
46946
+ }
46947
+ }
46948
+ var SkillSelectionError, MAX_CACHED_PROFILE_AGE_MS, SELECTION_LOCK_FILE = "selection.lock.json", SESSION_LOCK_MAX_BYTES = 4096, SESSION_LOCK_MIN_AGE_MS = 30000;
46753
46949
  var init_selection_cache = __esm(() => {
46754
46950
  init_config();
46755
46951
  init_skill_bundle();
@@ -46807,7 +47003,7 @@ function capturePreparationProcess(child, platform = process.platform, signalGro
46807
47003
  }
46808
47004
 
46809
47005
  // src/lib/dependency-preparation.ts
46810
- import { constants as constants4, closeSync as closeSync7, fstatSync as fstatSync7, lstatSync as lstatSync9, mkdirSync as mkdirSync4, openSync as openSync7, readFileSync as readFileSync10, readdirSync as readdirSync5, rmdirSync, unlinkSync as unlinkSync2, ftruncateSync, writeSync } from "fs";
47006
+ import { constants as constants4, closeSync as closeSync7, fstatSync as fstatSync7, lstatSync as lstatSync9, mkdirSync as mkdirSync4, openSync as openSync7, readFileSync as readFileSync11, readdirSync as readdirSync5, rmdirSync, unlinkSync as unlinkSync2, ftruncateSync, writeSync } from "fs";
46811
47007
  import { join as join15 } from "path";
46812
47008
  function hasDependencyPreparationMarker(skillPath) {
46813
47009
  try {
@@ -46837,7 +47033,7 @@ function claimPreparation(skillPath) {
46837
47033
  return;
46838
47034
  if (!lstatSync9(state).isFile() || lstatSync9(state).isSymbolicLink() || lstatSync9(state).size > 1024)
46839
47035
  return;
46840
- const previous = JSON.parse(readFileSync10(state, "utf8"));
47036
+ const previous = JSON.parse(readFileSync11(state, "utf8"));
46841
47037
  if (previous.version !== 1 || previous.status !== "failed" || Object.keys(previous).length !== 2)
46842
47038
  return;
46843
47039
  }
@@ -47389,7 +47585,7 @@ var init_skill_aliases = __esm(() => {
47389
47585
  });
47390
47586
 
47391
47587
  // src/lib/project-state.ts
47392
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
47588
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "fs";
47393
47589
  import { join as join17 } from "path";
47394
47590
  function getProjectStateDir(targetDir = process.cwd()) {
47395
47591
  return join17(targetDir, SKILLS_PROJECT_DIR);
@@ -47402,7 +47598,7 @@ function loadProjectConfig(targetDir = process.cwd()) {
47402
47598
  if (!existsSync8(path))
47403
47599
  return null;
47404
47600
  try {
47405
- return normalizeProjectConfig(JSON.parse(readFileSync11(path, "utf-8")));
47601
+ return normalizeProjectConfig(JSON.parse(readFileSync12(path, "utf-8")));
47406
47602
  } catch {
47407
47603
  return null;
47408
47604
  }
@@ -47510,7 +47706,7 @@ var init_project_state = __esm(() => {
47510
47706
  });
47511
47707
 
47512
47708
  // src/lib/installer.ts
47513
- import { existsSync as existsSync9, readFileSync as readFileSync12, rmSync } from "fs";
47709
+ import { existsSync as existsSync9, readFileSync as readFileSync13, rmSync } from "fs";
47514
47710
  import { join as join18 } from "path";
47515
47711
  import { homedir as homedir2 } from "os";
47516
47712
  function getSkillPath(name) {
@@ -47598,7 +47794,7 @@ function readBundledSkillVersion(name) {
47598
47794
  if (!existsSync9(pkgPath))
47599
47795
  return "unknown";
47600
47796
  try {
47601
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
47797
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
47602
47798
  return pkg.version || "unknown";
47603
47799
  } catch {
47604
47800
  return "unknown";
@@ -47662,7 +47858,7 @@ var init_skill_doc_file = __esm(() => {
47662
47858
  });
47663
47859
 
47664
47860
  // src/lib/skillinfo.ts
47665
- import { existsSync as existsSync10, readFileSync as readFileSync13 } from "fs";
47861
+ import { existsSync as existsSync10, readFileSync as readFileSync14 } from "fs";
47666
47862
  import { join as join19 } from "path";
47667
47863
  function isInstructionSkillDir(skillPath, meta3) {
47668
47864
  if (meta3?.kind === "instruction")
@@ -47671,7 +47867,7 @@ function isInstructionSkillDir(skillPath, meta3) {
47671
47867
  if (!existsSync10(skillMdPath))
47672
47868
  return false;
47673
47869
  try {
47674
- return parseSkillFrontmatter(readFileSync13(skillMdPath, "utf-8"))?.kind === "instruction";
47870
+ return parseSkillFrontmatter(readFileSync14(skillMdPath, "utf-8"))?.kind === "instruction";
47675
47871
  } catch {
47676
47872
  return false;
47677
47873
  }
@@ -47706,7 +47902,7 @@ function getSkillRequirements(name) {
47706
47902
  const meta3 = getSkill(name);
47707
47903
  let dependencies = {};
47708
47904
  try {
47709
- dependencies = JSON.parse(readFileSync13(join19(skillPath, "package.json"), "utf8")).dependencies || {};
47905
+ dependencies = JSON.parse(readFileSync14(join19(skillPath, "package.json"), "utf8")).dependencies || {};
47710
47906
  } catch {}
47711
47907
  return getSkillRequirementsFromContent(name, texts, dependencies, meta3);
47712
47908
  }
@@ -47774,7 +47970,7 @@ async function runSkill(name, args, options = {}) {
47774
47970
  }
47775
47971
  let entryPoint2;
47776
47972
  try {
47777
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
47973
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
47778
47974
  if (pkg.bin) {
47779
47975
  const binValues = Object.values(pkg.bin);
47780
47976
  entryPoint2 = binValues[0];
@@ -47819,7 +48015,7 @@ async function runSkill(name, args, options = {}) {
47819
48015
  function detectProjectSkills(cwd = process.cwd()) {
47820
48016
  let pkg;
47821
48017
  try {
47822
- pkg = JSON.parse(readFileSync13(join19(cwd, "package.json"), "utf8"));
48018
+ pkg = JSON.parse(readFileSync14(join19(cwd, "package.json"), "utf8"));
47823
48019
  } catch {
47824
48020
  return { detected: [], recommended: [] };
47825
48021
  }
@@ -47858,7 +48054,7 @@ function extractEnvVars(text2) {
47858
48054
  function readIfExists(path) {
47859
48055
  try {
47860
48056
  if (existsSync10(path)) {
47861
- return readFileSync13(path, "utf-8");
48057
+ return readFileSync14(path, "utf-8");
47862
48058
  }
47863
48059
  } catch {}
47864
48060
  return null;
@@ -48976,7 +49172,7 @@ var init_codex_hook_discovery_recovery = __esm(() => {
48976
49172
 
48977
49173
  // src/lib/agent-discovery.ts
48978
49174
  import { createHash as createHash14 } from "crypto";
48979
- import { lstatSync as lstatSync16, readFileSync as readFileSync14, readdirSync as readdirSync6, realpathSync as realpathSync3 } from "fs";
49175
+ import { lstatSync as lstatSync16, readFileSync as readFileSync15, readdirSync as readdirSync6, realpathSync as realpathSync3 } from "fs";
48980
49176
  import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute13, join as join23, resolve as resolve13, sep as sep4 } from "path";
48981
49177
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
48982
49178
  function parseConfig(text2, path, toml = false) {
@@ -49008,7 +49204,7 @@ function read(path, changes) {
49008
49204
  return null;
49009
49205
  if (!stat2.isFile() || stat2.size > 16 * 1024 * 1024)
49010
49206
  throw new Error(`Unsupported or oversized native discovery input: ${path}`);
49011
- return readFileSync14(path, "utf8");
49207
+ return readFileSync15(path, "utf8");
49012
49208
  }
49013
49209
  function projected(source, changes, budget = discoveryByteBudget()) {
49014
49210
  if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
@@ -49885,7 +50081,7 @@ var init_search = __esm(() => {
49885
50081
  });
49886
50082
 
49887
50083
  // src/lib/registry.ts
49888
- import { existsSync as existsSync12, readFileSync as readFileSync15, readdirSync as readdirSync8 } from "fs";
50084
+ import { existsSync as existsSync12, readFileSync as readFileSync16, readdirSync as readdirSync8 } from "fs";
49889
50085
  import { join as join26 } from "path";
49890
50086
  function parseSkillMdFrontmatter(content) {
49891
50087
  const match = content.match(/^---\n([\s\S]*?)\n---/);
@@ -49932,7 +50128,7 @@ function discoverSkillsInDir(dir, source = "custom") {
49932
50128
  continue;
49933
50129
  let content;
49934
50130
  try {
49935
- content = readFileSync15(skillMdPath, "utf-8");
50131
+ content = readFileSync16(skillMdPath, "utf-8");
49936
50132
  } catch {
49937
50133
  continue;
49938
50134
  }
@@ -49969,7 +50165,7 @@ function findExtensionSkillPath(name) {
49969
50165
  continue;
49970
50166
  let content;
49971
50167
  try {
49972
- content = readFileSync15(skillMdPath, "utf-8");
50168
+ content = readFileSync16(skillMdPath, "utf-8");
49973
50169
  } catch {
49974
50170
  continue;
49975
50171
  }
@@ -51161,7 +51357,7 @@ var init_remote_workspace_leave = __esm(() => {
51161
51357
  });
51162
51358
 
51163
51359
  // src/lib/auth-store.ts
51164
- import { chmodSync, existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync16, renameSync as renameSync4, statSync as statSync8, unlinkSync as unlinkSync4, writeFileSync as writeFileSync7 } from "fs";
51360
+ import { chmodSync, existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync17, renameSync as renameSync4, statSync as statSync8, unlinkSync as unlinkSync4, writeFileSync as writeFileSync7 } from "fs";
51165
51361
  import { basename as basename10, dirname as dirname17, join as join27 } from "path";
51166
51362
  function getAuthFilePath(env = process.env) {
51167
51363
  return skillsCredentialFilePath(env);
@@ -53175,7 +53371,7 @@ var init_remote_auth = __esm(() => {
53175
53371
  });
53176
53372
 
53177
53373
  // src/lib/workspace-profile.ts
53178
- import { constants as constants11, closeSync as closeSync14, fstatSync as fstatSync14, lstatSync as lstatSync18, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync2, openSync as openSync14, readFileSync as readFileSync17, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "fs";
53374
+ import { constants as constants11, closeSync as closeSync14, fstatSync as fstatSync14, lstatSync as lstatSync18, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync2, openSync as openSync14, readFileSync as readFileSync18, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "fs";
53179
53375
  function stat2(path) {
53180
53376
  try {
53181
53377
  return lstatSync18(path);
@@ -53193,7 +53389,7 @@ function safeText(file2) {
53193
53389
  const s = fstatSync14(fd);
53194
53390
  if (!s.isFile() || s.size > 65536 || ![256, 384].includes(s.mode & 4095) || process.getuid && s.uid !== process.getuid())
53195
53391
  return fail("The selected profile must use bounded owner-only regular files.");
53196
- return readFileSync17(fd, "utf8");
53392
+ return readFileSync18(fd, "utf8");
53197
53393
  } finally {
53198
53394
  closeSync14(fd);
53199
53395
  }
@@ -55649,7 +55845,7 @@ var init_credential_state = __esm(() => {
55649
55845
 
55650
55846
  // src/lib/run-state.ts
55651
55847
  import { createHash as createHash17, randomBytes } from "crypto";
55652
- import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync18, readdirSync as readdirSync9, statSync as statSync9, writeFileSync as writeFileSync10 } from "fs";
55848
+ import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync19, readdirSync as readdirSync9, statSync as statSync9, writeFileSync as writeFileSync10 } from "fs";
55653
55849
  import { extname, join as join29, relative as relative5 } from "path";
55654
55850
  function createSkillRun(params, targetDir = process.cwd()) {
55655
55851
  const now = new Date;
@@ -55721,7 +55917,7 @@ function appendRunEvent(context2, event, data = {}) {
55721
55917
  const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
55722
55918
  `;
55723
55919
  const path = join29(context2.runDir, "events.ndjson");
55724
- const previous = existsSync14(path) ? readFileSync18(path, "utf-8") : "";
55920
+ const previous = existsSync14(path) ? readFileSync19(path, "utf-8") : "";
55725
55921
  writeFileSync10(path, previous + line);
55726
55922
  }
55727
55923
  function findSkillRun(runId, targetDir = process.cwd()) {
@@ -55759,7 +55955,7 @@ function collectRunArtifacts(context2) {
55759
55955
  const artifacts = [];
55760
55956
  for (const path of walkFiles(context2.exportDir)) {
55761
55957
  const stat3 = statSync9(path);
55762
- const bytes = readFileSync18(path);
55958
+ const bytes = readFileSync19(path);
55763
55959
  artifacts.push({
55764
55960
  path: toProjectRelative(context2.targetDir, path),
55765
55961
  mime: mimeForPath(path),
@@ -55774,7 +55970,7 @@ function readRunRecord(runDir) {
55774
55970
  if (!existsSync14(path))
55775
55971
  return null;
55776
55972
  try {
55777
- return JSON.parse(readFileSync18(path, "utf-8"));
55973
+ return JSON.parse(readFileSync19(path, "utf-8"));
55778
55974
  } catch {
55779
55975
  return null;
55780
55976
  }
@@ -56034,8 +56230,8 @@ var init_cloud_executions = __esm(() => {
56034
56230
 
56035
56231
  // node_modules/@hasna/secrets/dist/sdk.js
56036
56232
  import { isIP as isIP2 } from "net";
56037
- import { spawnSync as spawnSync2 } from "child_process";
56038
- import { closeSync as closeSync16, fstatSync as fstatSync16, openSync as openSync16, readFileSync as readFileSync19 } from "fs";
56233
+ import { spawnSync as spawnSync3 } from "child_process";
56234
+ import { closeSync as closeSync16, fstatSync as fstatSync16, openSync as openSync16, readFileSync as readFileSync20 } from "fs";
56039
56235
  import { O_NOFOLLOW as O_NOFOLLOW3, O_NONBLOCK as O_NONBLOCK3, O_RDONLY as O_RDONLY3 } from "constants";
56040
56236
  import { createRequire as createRequire2 } from "module";
56041
56237
  import { hostname as osHostname2 } from "os";
@@ -56161,7 +56357,7 @@ function readAppConfigFile2(path) {
56161
56357
  unsafe("the file is not owned by the current user");
56162
56358
  if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
56163
56359
  unsafe("the file exceeds the size limit");
56164
- const bytes = readFileSync19(fd);
56360
+ const bytes = readFileSync20(fd);
56165
56361
  const after = fstatSync16(fd);
56166
56362
  if (!configFileReadsCoherent2(before, after)) {
56167
56363
  unsafe("the file changed while being read");
@@ -56309,7 +56505,7 @@ function isAmbientEnvironment2(env) {
56309
56505
  return env === process.env || env[AMBIENT_ENVIRONMENT2] === true;
56310
56506
  }
56311
56507
  function defaultKeychainRunner2(argv) {
56312
- const result = spawnSync2(KEYCHAIN_SECURITY_BIN2, [...argv], {
56508
+ const result = spawnSync3(KEYCHAIN_SECURITY_BIN2, [...argv], {
56313
56509
  encoding: "utf8",
56314
56510
  stdio: ["ignore", "pipe", "pipe"],
56315
56511
  timeout: KEYCHAIN_SPAWN_TIMEOUT_MS2
@@ -58643,7 +58839,7 @@ var init_resource_meta_tools = __esm(() => {
58643
58839
  });
58644
58840
 
58645
58841
  // src/lib/scheduler.ts
58646
- import { existsSync as existsSync17, readFileSync as readFileSync20, writeFileSync as writeFileSync12, mkdirSync as mkdirSync14 } from "fs";
58842
+ import { existsSync as existsSync17, readFileSync as readFileSync21, writeFileSync as writeFileSync12, mkdirSync as mkdirSync14 } from "fs";
58647
58843
  import { join as join34 } from "path";
58648
58844
  function getSchedulesPath(targetDir = process.cwd()) {
58649
58845
  return join34(targetDir, ".skills", "schedules.json");
@@ -58652,7 +58848,7 @@ function loadSchedules(targetDir = process.cwd()) {
58652
58848
  const path = getSchedulesPath(targetDir);
58653
58849
  if (existsSync17(path)) {
58654
58850
  try {
58655
- return JSON.parse(readFileSync20(path, "utf-8"));
58851
+ return JSON.parse(readFileSync21(path, "utf-8"));
58656
58852
  } catch {}
58657
58853
  }
58658
58854
  return { version: 1, schedules: [] };
@@ -58935,7 +59131,7 @@ import { createHash as createHash19, createHmac } from "crypto";
58935
59131
  import {
58936
59132
  existsSync as existsSync18,
58937
59133
  mkdirSync as mkdirSync15,
58938
- readFileSync as readFileSync21,
59134
+ readFileSync as readFileSync22,
58939
59135
  readdirSync as readdirSync11,
58940
59136
  statSync as statSync11,
58941
59137
  writeFileSync as writeFileSync13
@@ -59003,7 +59199,7 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
59003
59199
  const files = [];
59004
59200
  if (existsSync18(projectStateDir)) {
59005
59201
  for (const filePath of walkFiles2(projectStateDir)) {
59006
- const bytes = readFileSync21(filePath);
59202
+ const bytes = readFileSync22(filePath);
59007
59203
  const relativePath = toPosix(relative6(targetDir, filePath));
59008
59204
  files.push({
59009
59205
  path: relativePath,
@@ -62205,7 +62401,7 @@ async function handleMcpHttpNodeRequest(req, res) {
62205
62401
  return true;
62206
62402
  }
62207
62403
  async function startSkillsMcpHttpServer(options = {}) {
62208
- const hostname3 = options.hostname ?? "127.0.0.1";
62404
+ const hostname4 = options.hostname ?? "127.0.0.1";
62209
62405
  const port = options.port ?? parseMcpHttpPort(process.argv.slice(2));
62210
62406
  const httpServer = createServer(async (req, res) => {
62211
62407
  try {
@@ -62223,11 +62419,11 @@ async function startSkillsMcpHttpServer(options = {}) {
62223
62419
  });
62224
62420
  await new Promise((resolve19, reject) => {
62225
62421
  httpServer.once("error", reject);
62226
- httpServer.listen(port, hostname3, () => resolve19());
62422
+ httpServer.listen(port, hostname4, () => resolve19());
62227
62423
  });
62228
62424
  const address = httpServer.address();
62229
62425
  const listenPort = typeof address === "object" && address ? address.port : port;
62230
- console.error(`skills-mcp HTTP listening on http://${hostname3}:${listenPort}/mcp`);
62426
+ console.error(`skills-mcp HTTP listening on http://${hostname4}:${listenPort}/mcp`);
62231
62427
  return httpServer;
62232
62428
  }
62233
62429
  var MCP_HTTP_DEFAULT_PORT = 8836, MCP_HTTP_SERVICE_NAME = "skills";
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.0",
10
+ version: "0.10.2",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
@@ -201,15 +201,11 @@ function resolveServerConfig(env = process.env) {
201
201
  requestBodyLimitBytes: parsePositiveInt(env.HASNA_SKILLS_REQUEST_BODY_LIMIT_BYTES, 1e6),
202
202
  skillBundleLimitBytes: parsePositiveInt(env.HASNA_SKILLS_BUNDLE_LIMIT_BYTES, 25000000),
203
203
  tombstoneWindowMs: parsePositiveInt(env.HASNA_SKILLS_TOMBSTONE_WINDOW_MS, 7 * 24 * 60 * 60 * 1000),
204
- publicBaseUrl: (env.SKILLS_PUBLIC_BASE_URL || localOrigin(host, port)).replace(/\/+$/, ""),
204
+ publicBaseUrl: (env.SKILLS_PUBLIC_BASE_URL || "").replace(/\/+$/, ""),
205
205
  nodeEnv,
206
206
  allowEphemeralStore: env.HASNA_SKILLS_ALLOW_EPHEMERAL_STORE === "1"
207
207
  };
208
208
  }
209
- function localOrigin(host, port) {
210
- const hostname = host === "0.0.0.0" || host === "::" ? "localhost" : host;
211
- return `http://${hostname.includes(":") ? `[${hostname}]` : hostname}:${port}`;
212
- }
213
209
  function parsePositiveInt(value, fallback) {
214
210
  const parsed = Number.parseInt(value ?? "", 10);
215
211
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
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.0",
24125
+ version: "0.10.2",
24126
24126
  description: "Skills library for AI coding agents",
24127
24127
  type: "module",
24128
24128
  bin: {
@@ -42903,15 +42903,11 @@ function resolveServerConfig(env = process.env) {
42903
42903
  requestBodyLimitBytes: parsePositiveInt(env.HASNA_SKILLS_REQUEST_BODY_LIMIT_BYTES, 1e6),
42904
42904
  skillBundleLimitBytes: parsePositiveInt(env.HASNA_SKILLS_BUNDLE_LIMIT_BYTES, 25000000),
42905
42905
  tombstoneWindowMs: parsePositiveInt(env.HASNA_SKILLS_TOMBSTONE_WINDOW_MS, 7 * 24 * 60 * 60 * 1000),
42906
- publicBaseUrl: (env.SKILLS_PUBLIC_BASE_URL || localOrigin(host, port)).replace(/\/+$/, ""),
42906
+ publicBaseUrl: (env.SKILLS_PUBLIC_BASE_URL || "").replace(/\/+$/, ""),
42907
42907
  nodeEnv,
42908
42908
  allowEphemeralStore: env.HASNA_SKILLS_ALLOW_EPHEMERAL_STORE === "1"
42909
42909
  };
42910
42910
  }
42911
- function localOrigin(host, port) {
42912
- const hostname = host === "0.0.0.0" || host === "::" ? "localhost" : host;
42913
- return `http://${hostname.includes(":") ? `[${hostname}]` : hostname}:${port}`;
42914
- }
42915
42911
  function parsePositiveInt(value, fallback) {
42916
42912
  const parsed = Number.parseInt(value ?? "", 10);
42917
42913
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
@@ -44150,6 +44146,9 @@ async function createSkillsFetchHandler(options = {}) {
44150
44146
  const config = { ...resolveServerConfig(), ...options.config };
44151
44147
  if (!options.store)
44152
44148
  assertDurableTarget(resolveDatabaseTarget(config.databaseUrl), config);
44149
+ if (!config.publicBaseUrl) {
44150
+ throw new Error("refusing to start: set SKILLS_PUBLIC_BASE_URL to this Skills server's public API origin; a bound socket is not an authority for client receipts.");
44151
+ }
44153
44152
  const store = options.store ?? await createStore({
44154
44153
  databaseUrl: config.databaseUrl,
44155
44154
  bootstrapApiKey: config.bootstrapApiKey