@hasna/skills 0.10.1 → 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.1",
20022
+ version: "0.10.2",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
@@ -46400,7 +46400,9 @@ var init_skill_version = __esm(() => {
46400
46400
 
46401
46401
  // src/lib/selection-cache.ts
46402
46402
  import { createHash as createHash7, randomUUID } from "crypto";
46403
- 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";
46404
46406
  import { dirname as dirname8, join as join14, parse as parse7, resolve as resolve7 } from "path";
46405
46407
  import { isDeepStrictEqual } from "util";
46406
46408
  function selectionCacheRoot(options = {}) {
@@ -46670,6 +46672,164 @@ function readSkillSessionSnapshotIfExists(sessionId, options = {}) {
46670
46672
  function skillSessionSnapshotBinding(snapshot) {
46671
46673
  return { sessionId: snapshot.sessionId, generation: snapshot.generation, receiptSha256: snapshot.receiptSha256 };
46672
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
+ }
46673
46833
  function withSessionWriteLocks(sessionIds, options, action) {
46674
46834
  const operationId = randomUUID();
46675
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);
@@ -46689,13 +46849,30 @@ function withSessionWriteLocks(sessionIds, options, action) {
46689
46849
  try {
46690
46850
  fd = openSync6(lock.path, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | constants3.O_NOFOLLOW, 384);
46691
46851
  } catch (error61) {
46692
- if (error61.code === "EEXIST")
46693
- 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.");
46694
- 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
+ }
46695
46871
  }
46696
46872
  const stat = fstatSync6(fd);
46697
46873
  owned.push({ path: lock.path, fd, dev: stat.dev, ino: stat.ino });
46698
- 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 })}
46699
46876
  `);
46700
46877
  }
46701
46878
  return action(assertOwned);
@@ -46758,7 +46935,17 @@ function syncDirectory(path) {
46758
46935
  closeSync6(fd);
46759
46936
  }
46760
46937
  }
46761
- 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;
46762
46949
  var init_selection_cache = __esm(() => {
46763
46950
  init_config();
46764
46951
  init_skill_bundle();
@@ -46816,7 +47003,7 @@ function capturePreparationProcess(child, platform = process.platform, signalGro
46816
47003
  }
46817
47004
 
46818
47005
  // src/lib/dependency-preparation.ts
46819
- 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";
46820
47007
  import { join as join15 } from "path";
46821
47008
  function hasDependencyPreparationMarker(skillPath) {
46822
47009
  try {
@@ -46846,7 +47033,7 @@ function claimPreparation(skillPath) {
46846
47033
  return;
46847
47034
  if (!lstatSync9(state).isFile() || lstatSync9(state).isSymbolicLink() || lstatSync9(state).size > 1024)
46848
47035
  return;
46849
- const previous = JSON.parse(readFileSync10(state, "utf8"));
47036
+ const previous = JSON.parse(readFileSync11(state, "utf8"));
46850
47037
  if (previous.version !== 1 || previous.status !== "failed" || Object.keys(previous).length !== 2)
46851
47038
  return;
46852
47039
  }
@@ -47398,7 +47585,7 @@ var init_skill_aliases = __esm(() => {
47398
47585
  });
47399
47586
 
47400
47587
  // src/lib/project-state.ts
47401
- 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";
47402
47589
  import { join as join17 } from "path";
47403
47590
  function getProjectStateDir(targetDir = process.cwd()) {
47404
47591
  return join17(targetDir, SKILLS_PROJECT_DIR);
@@ -47411,7 +47598,7 @@ function loadProjectConfig(targetDir = process.cwd()) {
47411
47598
  if (!existsSync8(path))
47412
47599
  return null;
47413
47600
  try {
47414
- return normalizeProjectConfig(JSON.parse(readFileSync11(path, "utf-8")));
47601
+ return normalizeProjectConfig(JSON.parse(readFileSync12(path, "utf-8")));
47415
47602
  } catch {
47416
47603
  return null;
47417
47604
  }
@@ -47519,7 +47706,7 @@ var init_project_state = __esm(() => {
47519
47706
  });
47520
47707
 
47521
47708
  // src/lib/installer.ts
47522
- import { existsSync as existsSync9, readFileSync as readFileSync12, rmSync } from "fs";
47709
+ import { existsSync as existsSync9, readFileSync as readFileSync13, rmSync } from "fs";
47523
47710
  import { join as join18 } from "path";
47524
47711
  import { homedir as homedir2 } from "os";
47525
47712
  function getSkillPath(name) {
@@ -47607,7 +47794,7 @@ function readBundledSkillVersion(name) {
47607
47794
  if (!existsSync9(pkgPath))
47608
47795
  return "unknown";
47609
47796
  try {
47610
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
47797
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
47611
47798
  return pkg.version || "unknown";
47612
47799
  } catch {
47613
47800
  return "unknown";
@@ -47671,7 +47858,7 @@ var init_skill_doc_file = __esm(() => {
47671
47858
  });
47672
47859
 
47673
47860
  // src/lib/skillinfo.ts
47674
- import { existsSync as existsSync10, readFileSync as readFileSync13 } from "fs";
47861
+ import { existsSync as existsSync10, readFileSync as readFileSync14 } from "fs";
47675
47862
  import { join as join19 } from "path";
47676
47863
  function isInstructionSkillDir(skillPath, meta3) {
47677
47864
  if (meta3?.kind === "instruction")
@@ -47680,7 +47867,7 @@ function isInstructionSkillDir(skillPath, meta3) {
47680
47867
  if (!existsSync10(skillMdPath))
47681
47868
  return false;
47682
47869
  try {
47683
- return parseSkillFrontmatter(readFileSync13(skillMdPath, "utf-8"))?.kind === "instruction";
47870
+ return parseSkillFrontmatter(readFileSync14(skillMdPath, "utf-8"))?.kind === "instruction";
47684
47871
  } catch {
47685
47872
  return false;
47686
47873
  }
@@ -47715,7 +47902,7 @@ function getSkillRequirements(name) {
47715
47902
  const meta3 = getSkill(name);
47716
47903
  let dependencies = {};
47717
47904
  try {
47718
- dependencies = JSON.parse(readFileSync13(join19(skillPath, "package.json"), "utf8")).dependencies || {};
47905
+ dependencies = JSON.parse(readFileSync14(join19(skillPath, "package.json"), "utf8")).dependencies || {};
47719
47906
  } catch {}
47720
47907
  return getSkillRequirementsFromContent(name, texts, dependencies, meta3);
47721
47908
  }
@@ -47783,7 +47970,7 @@ async function runSkill(name, args, options = {}) {
47783
47970
  }
47784
47971
  let entryPoint2;
47785
47972
  try {
47786
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
47973
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
47787
47974
  if (pkg.bin) {
47788
47975
  const binValues = Object.values(pkg.bin);
47789
47976
  entryPoint2 = binValues[0];
@@ -47828,7 +48015,7 @@ async function runSkill(name, args, options = {}) {
47828
48015
  function detectProjectSkills(cwd = process.cwd()) {
47829
48016
  let pkg;
47830
48017
  try {
47831
- pkg = JSON.parse(readFileSync13(join19(cwd, "package.json"), "utf8"));
48018
+ pkg = JSON.parse(readFileSync14(join19(cwd, "package.json"), "utf8"));
47832
48019
  } catch {
47833
48020
  return { detected: [], recommended: [] };
47834
48021
  }
@@ -47867,7 +48054,7 @@ function extractEnvVars(text2) {
47867
48054
  function readIfExists(path) {
47868
48055
  try {
47869
48056
  if (existsSync10(path)) {
47870
- return readFileSync13(path, "utf-8");
48057
+ return readFileSync14(path, "utf-8");
47871
48058
  }
47872
48059
  } catch {}
47873
48060
  return null;
@@ -48985,7 +49172,7 @@ var init_codex_hook_discovery_recovery = __esm(() => {
48985
49172
 
48986
49173
  // src/lib/agent-discovery.ts
48987
49174
  import { createHash as createHash14 } from "crypto";
48988
- 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";
48989
49176
  import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute13, join as join23, resolve as resolve13, sep as sep4 } from "path";
48990
49177
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
48991
49178
  function parseConfig(text2, path, toml = false) {
@@ -49017,7 +49204,7 @@ function read(path, changes) {
49017
49204
  return null;
49018
49205
  if (!stat2.isFile() || stat2.size > 16 * 1024 * 1024)
49019
49206
  throw new Error(`Unsupported or oversized native discovery input: ${path}`);
49020
- return readFileSync14(path, "utf8");
49207
+ return readFileSync15(path, "utf8");
49021
49208
  }
49022
49209
  function projected(source, changes, budget = discoveryByteBudget()) {
49023
49210
  if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
@@ -49894,7 +50081,7 @@ var init_search = __esm(() => {
49894
50081
  });
49895
50082
 
49896
50083
  // src/lib/registry.ts
49897
- 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";
49898
50085
  import { join as join26 } from "path";
49899
50086
  function parseSkillMdFrontmatter(content) {
49900
50087
  const match = content.match(/^---\n([\s\S]*?)\n---/);
@@ -49941,7 +50128,7 @@ function discoverSkillsInDir(dir, source = "custom") {
49941
50128
  continue;
49942
50129
  let content;
49943
50130
  try {
49944
- content = readFileSync15(skillMdPath, "utf-8");
50131
+ content = readFileSync16(skillMdPath, "utf-8");
49945
50132
  } catch {
49946
50133
  continue;
49947
50134
  }
@@ -49978,7 +50165,7 @@ function findExtensionSkillPath(name) {
49978
50165
  continue;
49979
50166
  let content;
49980
50167
  try {
49981
- content = readFileSync15(skillMdPath, "utf-8");
50168
+ content = readFileSync16(skillMdPath, "utf-8");
49982
50169
  } catch {
49983
50170
  continue;
49984
50171
  }
@@ -51170,7 +51357,7 @@ var init_remote_workspace_leave = __esm(() => {
51170
51357
  });
51171
51358
 
51172
51359
  // src/lib/auth-store.ts
51173
- 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";
51174
51361
  import { basename as basename10, dirname as dirname17, join as join27 } from "path";
51175
51362
  function getAuthFilePath(env = process.env) {
51176
51363
  return skillsCredentialFilePath(env);
@@ -53184,7 +53371,7 @@ var init_remote_auth = __esm(() => {
53184
53371
  });
53185
53372
 
53186
53373
  // src/lib/workspace-profile.ts
53187
- 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";
53188
53375
  function stat2(path) {
53189
53376
  try {
53190
53377
  return lstatSync18(path);
@@ -53202,7 +53389,7 @@ function safeText(file2) {
53202
53389
  const s = fstatSync14(fd);
53203
53390
  if (!s.isFile() || s.size > 65536 || ![256, 384].includes(s.mode & 4095) || process.getuid && s.uid !== process.getuid())
53204
53391
  return fail("The selected profile must use bounded owner-only regular files.");
53205
- return readFileSync17(fd, "utf8");
53392
+ return readFileSync18(fd, "utf8");
53206
53393
  } finally {
53207
53394
  closeSync14(fd);
53208
53395
  }
@@ -55658,7 +55845,7 @@ var init_credential_state = __esm(() => {
55658
55845
 
55659
55846
  // src/lib/run-state.ts
55660
55847
  import { createHash as createHash17, randomBytes } from "crypto";
55661
- 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";
55662
55849
  import { extname, join as join29, relative as relative5 } from "path";
55663
55850
  function createSkillRun(params, targetDir = process.cwd()) {
55664
55851
  const now = new Date;
@@ -55730,7 +55917,7 @@ function appendRunEvent(context2, event, data = {}) {
55730
55917
  const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data }) + `
55731
55918
  `;
55732
55919
  const path = join29(context2.runDir, "events.ndjson");
55733
- const previous = existsSync14(path) ? readFileSync18(path, "utf-8") : "";
55920
+ const previous = existsSync14(path) ? readFileSync19(path, "utf-8") : "";
55734
55921
  writeFileSync10(path, previous + line);
55735
55922
  }
55736
55923
  function findSkillRun(runId, targetDir = process.cwd()) {
@@ -55768,7 +55955,7 @@ function collectRunArtifacts(context2) {
55768
55955
  const artifacts = [];
55769
55956
  for (const path of walkFiles(context2.exportDir)) {
55770
55957
  const stat3 = statSync9(path);
55771
- const bytes = readFileSync18(path);
55958
+ const bytes = readFileSync19(path);
55772
55959
  artifacts.push({
55773
55960
  path: toProjectRelative(context2.targetDir, path),
55774
55961
  mime: mimeForPath(path),
@@ -55783,7 +55970,7 @@ function readRunRecord(runDir) {
55783
55970
  if (!existsSync14(path))
55784
55971
  return null;
55785
55972
  try {
55786
- return JSON.parse(readFileSync18(path, "utf-8"));
55973
+ return JSON.parse(readFileSync19(path, "utf-8"));
55787
55974
  } catch {
55788
55975
  return null;
55789
55976
  }
@@ -56043,8 +56230,8 @@ var init_cloud_executions = __esm(() => {
56043
56230
 
56044
56231
  // node_modules/@hasna/secrets/dist/sdk.js
56045
56232
  import { isIP as isIP2 } from "net";
56046
- import { spawnSync as spawnSync2 } from "child_process";
56047
- 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";
56048
56235
  import { O_NOFOLLOW as O_NOFOLLOW3, O_NONBLOCK as O_NONBLOCK3, O_RDONLY as O_RDONLY3 } from "constants";
56049
56236
  import { createRequire as createRequire2 } from "module";
56050
56237
  import { hostname as osHostname2 } from "os";
@@ -56170,7 +56357,7 @@ function readAppConfigFile2(path) {
56170
56357
  unsafe("the file is not owned by the current user");
56171
56358
  if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
56172
56359
  unsafe("the file exceeds the size limit");
56173
- const bytes = readFileSync19(fd);
56360
+ const bytes = readFileSync20(fd);
56174
56361
  const after = fstatSync16(fd);
56175
56362
  if (!configFileReadsCoherent2(before, after)) {
56176
56363
  unsafe("the file changed while being read");
@@ -56318,7 +56505,7 @@ function isAmbientEnvironment2(env) {
56318
56505
  return env === process.env || env[AMBIENT_ENVIRONMENT2] === true;
56319
56506
  }
56320
56507
  function defaultKeychainRunner2(argv) {
56321
- const result = spawnSync2(KEYCHAIN_SECURITY_BIN2, [...argv], {
56508
+ const result = spawnSync3(KEYCHAIN_SECURITY_BIN2, [...argv], {
56322
56509
  encoding: "utf8",
56323
56510
  stdio: ["ignore", "pipe", "pipe"],
56324
56511
  timeout: KEYCHAIN_SPAWN_TIMEOUT_MS2
@@ -58652,7 +58839,7 @@ var init_resource_meta_tools = __esm(() => {
58652
58839
  });
58653
58840
 
58654
58841
  // src/lib/scheduler.ts
58655
- 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";
58656
58843
  import { join as join34 } from "path";
58657
58844
  function getSchedulesPath(targetDir = process.cwd()) {
58658
58845
  return join34(targetDir, ".skills", "schedules.json");
@@ -58661,7 +58848,7 @@ function loadSchedules(targetDir = process.cwd()) {
58661
58848
  const path = getSchedulesPath(targetDir);
58662
58849
  if (existsSync17(path)) {
58663
58850
  try {
58664
- return JSON.parse(readFileSync20(path, "utf-8"));
58851
+ return JSON.parse(readFileSync21(path, "utf-8"));
58665
58852
  } catch {}
58666
58853
  }
58667
58854
  return { version: 1, schedules: [] };
@@ -58944,7 +59131,7 @@ import { createHash as createHash19, createHmac } from "crypto";
58944
59131
  import {
58945
59132
  existsSync as existsSync18,
58946
59133
  mkdirSync as mkdirSync15,
58947
- readFileSync as readFileSync21,
59134
+ readFileSync as readFileSync22,
58948
59135
  readdirSync as readdirSync11,
58949
59136
  statSync as statSync11,
58950
59137
  writeFileSync as writeFileSync13
@@ -59012,7 +59199,7 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
59012
59199
  const files = [];
59013
59200
  if (existsSync18(projectStateDir)) {
59014
59201
  for (const filePath of walkFiles2(projectStateDir)) {
59015
- const bytes = readFileSync21(filePath);
59202
+ const bytes = readFileSync22(filePath);
59016
59203
  const relativePath = toPosix(relative6(targetDir, filePath));
59017
59204
  files.push({
59018
59205
  path: relativePath,
@@ -62214,7 +62401,7 @@ async function handleMcpHttpNodeRequest(req, res) {
62214
62401
  return true;
62215
62402
  }
62216
62403
  async function startSkillsMcpHttpServer(options = {}) {
62217
- const hostname3 = options.hostname ?? "127.0.0.1";
62404
+ const hostname4 = options.hostname ?? "127.0.0.1";
62218
62405
  const port = options.port ?? parseMcpHttpPort(process.argv.slice(2));
62219
62406
  const httpServer = createServer(async (req, res) => {
62220
62407
  try {
@@ -62232,11 +62419,11 @@ async function startSkillsMcpHttpServer(options = {}) {
62232
62419
  });
62233
62420
  await new Promise((resolve19, reject) => {
62234
62421
  httpServer.once("error", reject);
62235
- httpServer.listen(port, hostname3, () => resolve19());
62422
+ httpServer.listen(port, hostname4, () => resolve19());
62236
62423
  });
62237
62424
  const address = httpServer.address();
62238
62425
  const listenPort = typeof address === "object" && address ? address.port : port;
62239
- console.error(`skills-mcp HTTP listening on http://${hostname3}:${listenPort}/mcp`);
62426
+ console.error(`skills-mcp HTTP listening on http://${hostname4}:${listenPort}/mcp`);
62240
62427
  return httpServer;
62241
62428
  }
62242
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.1",
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.1",
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
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.1",
24128
+ version: "0.10.2",
24129
24129
  description: "Skills library for AI coding agents",
24130
24130
  type: "module",
24131
24131
  bin: {
@@ -36401,15 +36401,11 @@ function resolveServerConfig(env = process.env) {
36401
36401
  requestBodyLimitBytes: parsePositiveInt(env.HASNA_SKILLS_REQUEST_BODY_LIMIT_BYTES, 1e6),
36402
36402
  skillBundleLimitBytes: parsePositiveInt(env.HASNA_SKILLS_BUNDLE_LIMIT_BYTES, 25000000),
36403
36403
  tombstoneWindowMs: parsePositiveInt(env.HASNA_SKILLS_TOMBSTONE_WINDOW_MS, 7 * 24 * 60 * 60 * 1000),
36404
- publicBaseUrl: (env.SKILLS_PUBLIC_BASE_URL || localOrigin(host, port)).replace(/\/+$/, ""),
36404
+ publicBaseUrl: (env.SKILLS_PUBLIC_BASE_URL || "").replace(/\/+$/, ""),
36405
36405
  nodeEnv,
36406
36406
  allowEphemeralStore: env.HASNA_SKILLS_ALLOW_EPHEMERAL_STORE === "1"
36407
36407
  };
36408
36408
  }
36409
- function localOrigin(host, port) {
36410
- const hostname = host === "0.0.0.0" || host === "::" ? "localhost" : host;
36411
- return `http://${hostname.includes(":") ? `[${hostname}]` : hostname}:${port}`;
36412
- }
36413
36409
  function parsePositiveInt(value, fallback) {
36414
36410
  const parsed = Number.parseInt(value ?? "", 10);
36415
36411
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;