@hasna/skills 0.8.10 → 0.8.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.8.10",
20022
+ version: "0.8.11",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
@@ -20081,10 +20081,11 @@ var init_package = __esm(() => {
20081
20081
  typecheck: "tsc --noEmit",
20082
20082
  "verify:release": "bun run scripts/release-guard.ts",
20083
20083
  "verify:consumer-types": "bun run scripts/consumer-types.ts",
20084
- prepare: "bun run build:js",
20085
- prepack: "bun run build && bun run verify:release && bun run verify:consumer-types",
20084
+ prepack: "bun run verify:generated && bun run verify:release && bun run verify:consumer-types",
20086
20085
  prepublishOnly: "bun run verify:producer && bun run typecheck && bun run test",
20087
- "verify:producer": "bun run scripts/release-dependencies.ts"
20086
+ "verify:producer": "bun run scripts/release-dependencies.ts",
20087
+ "verify:generated": "HASNA_SKILLS_RELEASE_ARTIFACT_TEST=1 bun test src/lib/release-artifacts.test.ts",
20088
+ "verify:consumer-credentials": "bun run scripts/consumer-credentials.ts"
20088
20089
  },
20089
20090
  keywords: [
20090
20091
  "skills",
@@ -20106,7 +20107,6 @@ var init_package = __esm(() => {
20106
20107
  license: "Apache-2.0",
20107
20108
  devDependencies: {
20108
20109
  "@hasna/contracts": "1.1.0",
20109
- "@hasna/secrets": "0.4.2",
20110
20110
  "@types/bun": "1.3.14",
20111
20111
  "@types/node": "25.2.3",
20112
20112
  "@types/react": "^18.2.0",
@@ -20119,6 +20119,7 @@ var init_package = __esm(() => {
20119
20119
  "@aws-sdk/client-ecs": "^3.1079.0",
20120
20120
  "@aws-sdk/client-s3": "^3.1079.0",
20121
20121
  "@hasna/events": "0.1.18",
20122
+ "@hasna/secrets": "0.4.2",
20122
20123
  "@modelcontextprotocol/sdk": "^1.26.0",
20123
20124
  chalk: "^5.3.0",
20124
20125
  commander: "^12.1.0",
@@ -21107,6 +21108,7 @@ __export(exports_fleet_credentials, {
21107
21108
  SKILLS_API_URL_ENV: () => SKILLS_API_URL_ENV,
21108
21109
  SKILLS_API_KEY_ENV_KEYS: () => SKILLS_API_KEY_ENV_KEYS,
21109
21110
  SKILLS_API_KEY_ENV: () => SKILLS_API_KEY_ENV,
21111
+ REDACTED_VAULT_REFERENCE: () => REDACTED_VAULT_REFERENCE,
21110
21112
  MissingSkillsFleetError: () => MissingSkillsFleetError
21111
21113
  });
21112
21114
  function isCredentialResolutionError(error61) {
@@ -21115,10 +21117,15 @@ function isCredentialResolutionError(error61) {
21115
21117
  function isSkillsFleetCredentialError(error61) {
21116
21118
  return error61 instanceof SkillsFleetCredentialError || typeof error61 === "object" && error61 !== null && error61.name === "SkillsFleetCredentialError";
21117
21119
  }
21118
- function asSkillsFleetCredentialError(error61) {
21120
+ function redactVaultReference(message, vaultReference) {
21121
+ if (!vaultReference)
21122
+ return message;
21123
+ return message.split(vaultReference).join(REDACTED_VAULT_REFERENCE);
21124
+ }
21125
+ function asSkillsFleetCredentialError(error61, vaultReference) {
21119
21126
  if (!isCredentialResolutionError(error61))
21120
21127
  return null;
21121
- return new SkillsFleetCredentialError(error61.message, "MISSING_API_CREDENTIAL");
21128
+ return new SkillsFleetCredentialError(redactVaultReference(error61.message, vaultReference), "MISSING_API_CREDENTIAL");
21122
21129
  }
21123
21130
  function normalizeSkillsApiOrigin(apiUrl) {
21124
21131
  const url2 = new URL(apiUrl);
@@ -21305,7 +21312,7 @@ async function resolveSkillsConnection(env = process.env, options = {}) {
21305
21312
  try {
21306
21313
  completed = await completePointerCredential(SKILLS_APP, pointer, env);
21307
21314
  } catch (error61) {
21308
- const translated = asSkillsFleetCredentialError(error61);
21315
+ const translated = asSkillsFleetCredentialError(error61, pointer.pointerVaultKey);
21309
21316
  if (translated)
21310
21317
  throw translated;
21311
21318
  throw error61;
@@ -21361,7 +21368,7 @@ function requireSkillsFleet(action = "This command", env = process.env, options
21361
21368
  return fleet;
21362
21369
  throw new MissingSkillsFleetError(action);
21363
21370
  }
21364
- var SKILLS_APP = "skills", ENV_KEYS, SKILLS_API_URL_ENV_KEYS, SKILLS_API_KEY_ENV_KEYS, SKILLS_API_URL_ENV, SKILLS_API_KEY_ENV, SkillsFleetCredentialError, localNoticePrinted = false, MissingSkillsFleetError;
21371
+ var SKILLS_APP = "skills", ENV_KEYS, SKILLS_API_URL_ENV_KEYS, SKILLS_API_KEY_ENV_KEYS, SKILLS_API_URL_ENV, SKILLS_API_KEY_ENV, SkillsFleetCredentialError, REDACTED_VAULT_REFERENCE = "[redacted vault reference]", localNoticePrinted = false, MissingSkillsFleetError;
21365
21372
  var init_fleet_credentials = __esm(() => {
21366
21373
  init_transport();
21367
21374
  init_instance_credentials();
@@ -39490,14 +39497,15 @@ function profileSelectionSnapshot(selections) {
39490
39497
  var MAX_PROFILE_SELECTIONS = 4096, MAX_PROFILE_DOCUMENT_BYTES, MAX_SKILL_SESSION_ID_CHARS = 256, PROFILE_SESSION_ENVELOPE_BYTES, MAX_RESOLVED_PROFILE_BYTES, LEGACY_PROFILE_SELECTIONS = 256, LEGACY_PROFILE_DOCUMENT_BYTES = 1e6;
39491
39498
  var init_profile_limits = __esm(() => {
39492
39499
  MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
39493
- PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 6 + 256;
39500
+ PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
39494
39501
  MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
39495
39502
  });
39496
39503
 
39497
39504
  // src/lib/selection-cache.ts
39498
39505
  import { createHash as createHash3, randomUUID } from "crypto";
39499
- import { closeSync as closeSync5, constants as constants2, fstatSync as fstatSync5, lstatSync as lstatSync5, mkdirSync as mkdirSync4, openSync as openSync5, readSync as readSync2, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
39506
+ import { closeSync as closeSync5, constants as constants2, fstatSync as fstatSync5, fsyncSync, lstatSync as lstatSync5, mkdirSync as mkdirSync4, openSync as openSync5, readSync as readSync2, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
39500
39507
  import { dirname as dirname5, join as join13, parse as parse6, resolve as resolve3 } from "path";
39508
+ import { isDeepStrictEqual } from "util";
39501
39509
  function selectionCacheRoot(options = {}) {
39502
39510
  return resolve3(options.cacheDir ?? join13(getDataDirReadOnly(), "selection-cache"));
39503
39511
  }
@@ -39601,12 +39609,21 @@ function writeSelectionJson(path, value) {
39601
39609
  throw new SkillSelectionError("RECEIPT_TOO_LARGE", "The Skills selection receipt exceeds its size limit.");
39602
39610
  atomicWrite(path, bytes, 384);
39603
39611
  }
39604
- function atomicWrite(path, bytes, mode) {
39612
+ function atomicWrite(path, bytes, mode, durable = false) {
39605
39613
  assertRegularPath(path, true);
39606
39614
  const temporary = join13(dirname5(path), `.selection-${randomUUID()}.tmp`);
39607
39615
  try {
39608
- writeFileSync4(temporary, bytes, { flag: "wx", mode });
39616
+ const fd = openSync5(temporary, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, mode);
39617
+ try {
39618
+ writeFileSync4(fd, bytes);
39619
+ if (durable)
39620
+ fsyncSync(fd);
39621
+ } finally {
39622
+ closeSync5(fd);
39623
+ }
39609
39624
  renameSync2(temporary, path);
39625
+ if (durable)
39626
+ syncDirectory(dirname5(path));
39610
39627
  } finally {
39611
39628
  try {
39612
39629
  unlinkSync(temporary);
@@ -39719,16 +39736,130 @@ function sessionReceiptPath(sessionId, options = {}) {
39719
39736
  throw new SkillSelectionError("INVALID_SESSION", `A Skills session id must contain 1\u2013${MAX_SKILL_SESSION_ID_CHARS} characters.`);
39720
39737
  return join13(selectionCacheRoot(options), "sessions", `${hash2(sessionId)}.json`);
39721
39738
  }
39722
- function readSkillSession(sessionId, options = {}) {
39723
- const receipt = readSelectionJson(sessionReceiptPath(sessionId, options));
39724
- if (receipt) {
39725
- validateSelectionReceipt(receipt);
39726
- const keys = new Set(receipt.profile.selections.map(selectionKey));
39727
- if (receipt.sessionId !== sessionId || !Array.isArray(receipt.loaded) || receipt.loaded.length > keys.size || new Set(receipt.loaded).size !== receipt.loaded.length || !receipt.loaded.every((key) => keys.has(key))) {
39728
- throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt is invalid.");
39739
+ function skillSessionGeneration(receipt) {
39740
+ return receipt.generation ?? 0;
39741
+ }
39742
+ function validateSkillSessionBinding(binding) {
39743
+ if (!binding || typeof binding.sessionId !== "string" || !binding.sessionId.trim() || binding.sessionId.length > MAX_SKILL_SESSION_ID_CHARS || !Number.isSafeInteger(binding.generation) || binding.generation < 0 || !/^[a-f0-9]{64}$/.test(binding.receiptSha256)) {
39744
+ throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt contains an invalid parent snapshot binding.");
39745
+ }
39746
+ }
39747
+ function validateSkillSessionReceipt(receipt, sessionId) {
39748
+ validateSelectionReceipt(receipt);
39749
+ const keys = new Set(receipt.profile.selections.map(selectionKey));
39750
+ if (receipt.sessionId !== sessionId || !Array.isArray(receipt.loaded) || receipt.loaded.length > keys.size || new Set(receipt.loaded).size !== receipt.loaded.length || !receipt.loaded.every((key) => keys.has(key)) || receipt.generation !== undefined && (!Number.isSafeInteger(receipt.generation) || receipt.generation < 1)) {
39751
+ throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt is invalid.");
39752
+ }
39753
+ if (receipt.parent) {
39754
+ validateSkillSessionBinding(receipt.parent);
39755
+ if (receipt.parent.sessionId === sessionId)
39756
+ throw new SkillSelectionError("INVALID_RECEIPT", "A Skills child session cannot name itself as its parent.");
39757
+ }
39758
+ }
39759
+ function readSkillSessionSnapshotIfExists(sessionId, options = {}) {
39760
+ const path = sessionReceiptPath(sessionId, options), bytes = readRegularFile(path, MAX_PROFILE_DOCUMENT_BYTES);
39761
+ if (!bytes)
39762
+ return null;
39763
+ let receipt;
39764
+ try {
39765
+ receipt = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
39766
+ } catch {
39767
+ throw new SkillSelectionError("INVALID_RECEIPT", "The Skills session receipt is unreadable.");
39768
+ }
39769
+ validateSkillSessionReceipt(receipt, sessionId);
39770
+ const sha256 = sha256Hex(bytes);
39771
+ return { path, bytes, receipt, sha256, sessionId, generation: skillSessionGeneration(receipt), receiptSha256: sha256 };
39772
+ }
39773
+ function skillSessionSnapshotBinding(snapshot) {
39774
+ return { sessionId: snapshot.sessionId, generation: snapshot.generation, receiptSha256: snapshot.receiptSha256 };
39775
+ }
39776
+ function withSessionWriteLocks(sessionIds, options, action) {
39777
+ const operationId = randomUUID();
39778
+ 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);
39779
+ const owned = [];
39780
+ const assertOwned = () => {
39781
+ for (const lock of owned) {
39782
+ const current = lstatSync5(lock.path, { throwIfNoEntry: false });
39783
+ if (!current?.isFile() || current.dev !== lock.dev || current.ino !== lock.ino) {
39784
+ throw new SkillSelectionError("SESSION_WRITE_LOCK_CHANGED", "A session write lock changed during the operation; no replacement is permitted.");
39785
+ }
39786
+ }
39787
+ };
39788
+ try {
39789
+ for (const lock of locks) {
39790
+ assertRegularPath(lock.path, true);
39791
+ let fd;
39792
+ try {
39793
+ fd = openSync5(lock.path, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
39794
+ } catch (error61) {
39795
+ if (error61.code === "EEXIST")
39796
+ 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.");
39797
+ throw error61;
39798
+ }
39799
+ const stat = fstatSync5(fd);
39800
+ owned.push({ path: lock.path, fd, dev: stat.dev, ino: stat.ino });
39801
+ writeFileSync4(fd, `${JSON.stringify({ schemaVersion: 1, operationId, sessionId: lock.sessionId, pid: process.pid })}
39802
+ `);
39803
+ }
39804
+ return action(assertOwned);
39805
+ } finally {
39806
+ for (const lock of owned.reverse()) {
39807
+ closeSync5(lock.fd);
39808
+ const current = lstatSync5(lock.path, { throwIfNoEntry: false });
39809
+ if (current?.isFile() && current.dev === lock.dev && current.ino === lock.ino)
39810
+ unlinkSync(lock.path);
39729
39811
  }
39730
39812
  }
39731
- return receipt;
39813
+ }
39814
+ function sameSnapshot(actual, expected) {
39815
+ return actual === null ? expected === null : expected !== null && actual.sessionId === expected.sessionId && actual.generation === expected.generation && actual.receiptSha256 === expected.receiptSha256;
39816
+ }
39817
+ function nextSkillSessionGeneration(generation) {
39818
+ if (!Number.isSafeInteger(generation) || generation < 0 || generation >= Number.MAX_SAFE_INTEGER) {
39819
+ throw new SkillSelectionError("SESSION_GENERATION_EXHAUSTED", "The Skills session generation cannot advance safely; preserve the receipt and review recovery before continuing.");
39820
+ }
39821
+ return generation + 1;
39822
+ }
39823
+ function parentChanged() {
39824
+ throw new SkillSelectionError("SESSION_PARENT_CHANGED", "The parent Skills session changed while the child was resolving; resolve the child again from the current parent pin.");
39825
+ }
39826
+ function writeSkillSession(receipt, expected, options = {}) {
39827
+ validateSkillSessionReceipt(receipt, receipt.sessionId);
39828
+ if (expected.parent)
39829
+ validateSkillSessionBinding(expected.parent);
39830
+ if (expected.parent?.sessionId === receipt.sessionId)
39831
+ throw new SkillSelectionError("INVALID_RECEIPT", "A Skills child session cannot name itself as its parent.");
39832
+ const lockIds = expected.parent ? [receipt.sessionId, expected.parent.sessionId] : [receipt.sessionId];
39833
+ withSessionWriteLocks(lockIds, options, (assertOwned) => {
39834
+ const current = readSkillSessionSnapshotIfExists(receipt.sessionId, options);
39835
+ if (!sameSnapshot(current, expected.current) || current && !isDeepStrictEqual(current.receipt.profile, receipt.profile)) {
39836
+ throw new SkillSelectionError("SESSION_RECEIPT_CHANGED", "The Skills session receipt changed while context was loading; resolve context again without replacing the current pin.");
39837
+ }
39838
+ let parent = null;
39839
+ if (expected.parent) {
39840
+ parent = readSkillSessionSnapshotIfExists(expected.parent.sessionId, options);
39841
+ if (!sameSnapshot(parent, expected.parent) || !parent || !isDeepStrictEqual(parent.receipt.profile, receipt.profile))
39842
+ parentChanged();
39843
+ }
39844
+ const generation = nextSkillSessionGeneration(current?.generation ?? 0);
39845
+ const next = current ? { ...current.receipt, generation, loaded: [...new Set([...current.receipt.loaded, ...receipt.loaded])] } : { ...receipt, generation, ...expected.parent ? { parent: expected.parent } : {} };
39846
+ assertOwned();
39847
+ if (expected.parent) {
39848
+ parent = readSkillSessionSnapshotIfExists(expected.parent.sessionId, options);
39849
+ if (!sameSnapshot(parent, expected.parent))
39850
+ parentChanged();
39851
+ }
39852
+ writeSelectionJson(sessionReceiptPath(receipt.sessionId, options), next);
39853
+ });
39854
+ }
39855
+ function syncDirectory(path) {
39856
+ assertRegularPath(path);
39857
+ const fd = openSync5(path, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
39858
+ try {
39859
+ fsyncSync(fd);
39860
+ } finally {
39861
+ closeSync5(fd);
39862
+ }
39732
39863
  }
39733
39864
  var SkillSelectionError, MAX_CACHED_PROFILE_AGE_MS, SELECTION_LOCK_FILE = "selection.lock.json";
39734
39865
  var init_selection_cache = __esm(() => {
@@ -39748,6 +39879,7 @@ var init_selection_cache = __esm(() => {
39748
39879
  });
39749
39880
 
39750
39881
  // src/lib/agent-policy-limits.ts
39882
+ import { basename as basename4, isAbsolute as isAbsolute4, resolve as resolve4 } from "path";
39751
39883
  function object4(value) {
39752
39884
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
39753
39885
  }
@@ -39812,7 +39944,9 @@ function assertAgentPolicyCollections(policy) {
39812
39944
  text(source.path);
39813
39945
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
39814
39946
  if (source.hashMode !== undefined)
39815
- requireBound(["bytes", "path-bytes", "claude-plugin-registry"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
39947
+ requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
39948
+ if (source.hashMode === "claude-marketplace-registry")
39949
+ requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute4(source.path) && resolve4(source.path) === source.path && basename4(source.path) === "known_marketplaces.json");
39816
39950
  if (source.hashMode === "claude-plugin-registry") {
39817
39951
  requireBound(agent === "claude");
39818
39952
  const managed = array3(source.managedPlugins, 64);
@@ -47400,7 +47534,7 @@ var init_plugin_projection = __esm(() => {
47400
47534
  });
47401
47535
 
47402
47536
  // src/lib/plugin-projection-store.ts
47403
- import { constants as constants5, closeSync as closeSync8, fchmodSync, fstatSync as fstatSync8, fsyncSync, linkSync, lstatSync as lstatSync8, mkdirSync as mkdirSync6, openSync as openSync8, opendirSync, readSync as readSync4, renameSync as renameSync3, rmdirSync as rmdirSync2, rmSync as rmSync2, unlinkSync as unlinkSync3, writeSync as writeSync2 } from "fs";
47537
+ import { constants as constants5, closeSync as closeSync8, fchmodSync, fstatSync as fstatSync8, fsyncSync as fsyncSync2, linkSync, lstatSync as lstatSync8, mkdirSync as mkdirSync6, openSync as openSync8, opendirSync, readSync as readSync4, renameSync as renameSync3, rmdirSync as rmdirSync2, rmSync as rmSync2, unlinkSync as unlinkSync3, writeSync as writeSync2 } from "fs";
47404
47538
  var READ_FLAGS, WRITE_FLAGS;
47405
47539
  var init_plugin_projection_store = __esm(() => {
47406
47540
  init_plugin_projection();
@@ -47452,6 +47586,12 @@ var init_plugin_discovery = __esm(() => {
47452
47586
  init_plugin_projection_store();
47453
47587
  });
47454
47588
 
47589
+ // src/lib/claude-marketplace-registry.ts
47590
+ var CLAUDE_MARKETPLACE_REGISTRY_LIMITS;
47591
+ var init_claude_marketplace_registry = __esm(() => {
47592
+ CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
47593
+ });
47594
+
47455
47595
  // src/lib/agent-discovery.ts
47456
47596
  var init_agent_discovery = __esm(() => {
47457
47597
  init_agent_policy_limits();
@@ -47461,6 +47601,7 @@ var init_agent_discovery = __esm(() => {
47461
47601
  init_agent_discovery_path_bytes();
47462
47602
  init_plugin_discovery();
47463
47603
  init_plugin_admission();
47604
+ init_claude_marketplace_registry();
47464
47605
  init_agent_discovery_directories();
47465
47606
  });
47466
47607
 
@@ -48024,11 +48165,18 @@ var init_selected_document = __esm(() => {
48024
48165
 
48025
48166
  // src/lib/selection-resolver.ts
48026
48167
  async function resolveSelectionContext(profileId, options = {}) {
48027
- const session = options.sessionId ? readSkillSession(options.sessionId, options) : null;
48028
- const parentSession = !session && options.parentSessionId ? readSkillSession(options.parentSessionId, options) : null;
48168
+ const sessionState = options.sessionId ? readSkillSessionSnapshotIfExists(options.sessionId, options) : null;
48169
+ const session = sessionState?.receipt ?? null;
48170
+ const parentState = !session && options.parentSessionId ? readSkillSessionSnapshotIfExists(options.parentSessionId, options) : null;
48171
+ if (!session && options.parentSessionId && !parentState) {
48172
+ throw new SkillSelectionError("SESSION_PARENT_NOT_FOUND", "A child Skills session requires an existing parent receipt so its inherited pin can be bound exactly.");
48173
+ }
48174
+ const parentSession = parentState?.receipt ?? null;
48175
+ const sessionSnapshot = sessionState ? skillSessionSnapshotBinding(sessionState) : null;
48176
+ const parentSnapshot = parentState ? skillSessionSnapshotBinding(parentState) : undefined;
48029
48177
  const project = options.projectDir ? readProjectSelection(options.projectDir) : null;
48030
48178
  if (session && session.profile.profileId !== profileId || parentSession && parentSession.profile.profileId !== profileId || project && project.profile.profileId !== profileId) {
48031
- throw new SkillSelectionError("PROFILE_LOCK_MISMATCH", "The session or project is pinned to a different Skills profile. Select that profile or explicitly sync the new selection.");
48179
+ throw new SkillSelectionError("PROFILE_LOCK_MISMATCH", "The session or project is pinned to a different Skills profile. Select the pinned profile, or review skills sessions reconcile for an intentional session change and explicit project sync for a project change.");
48032
48180
  }
48033
48181
  if (options.cached) {
48034
48182
  const receipt2 = session ?? parentSession ?? project ?? readSelectionProfile(profileId, options);
@@ -48036,7 +48184,7 @@ async function resolveSelectionContext(profileId, options = {}) {
48036
48184
  throw new SkillSelectionError("CACHED_PROFILE_MISSING", "No verified Skills profile is cached; authenticate and sync it first.");
48037
48185
  assertFreshCachedProfile(receipt2, options);
48038
48186
  assertBinding(receipt2.profile, options);
48039
- return { receipt: receipt2, session, inheritedLoaded: parentSession?.loaded };
48187
+ return { receipt: receipt2, session, sessionSnapshot, parentSnapshot, inheritedLoaded: parentSession?.loaded };
48040
48188
  }
48041
48189
  const client = options.client ?? await createProfileClient();
48042
48190
  const current = await client.resolveProfile(profileId);
@@ -48049,7 +48197,7 @@ async function resolveSelectionContext(profileId, options = {}) {
48049
48197
  throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The pinned selection belongs to another Skills authority or workspace.");
48050
48198
  }
48051
48199
  const receipt = locked ?? { schemaVersion: 1, verifiedAt: new Date((options.now ?? Date.now)()).toISOString(), profile: current };
48052
- return { receipt, session, client, inheritedLoaded: parentSession?.loaded };
48200
+ return { receipt, session, sessionSnapshot, parentSnapshot, client, inheritedLoaded: parentSession?.loaded };
48053
48201
  }
48054
48202
  function assertBinding(profile, options) {
48055
48203
  if (options.authority !== undefined && profile.authority !== options.authority || options.workspaceId !== undefined && profile.workspaceId !== options.workspaceId) {
@@ -48088,7 +48236,7 @@ async function loadSelectedSkill(spec, profileId, options = {}) {
48088
48236
  sessionId: options.sessionId,
48089
48237
  loaded: [...new Set([...context.session?.loaded ?? [], selectionKey(selection)])]
48090
48238
  };
48091
- writeSelectionJson(sessionReceiptPath(options.sessionId, options), session);
48239
+ writeSkillSession(session, { current: context.sessionSnapshot, parent: context.parentSnapshot }, options);
48092
48240
  }
48093
48241
  return { content, selection, receipt };
48094
48242
  }
@@ -48132,6 +48280,14 @@ var init_skill_context = __esm(() => {
48132
48280
  init_selected_document();
48133
48281
  });
48134
48282
 
48283
+ // src/lib/session-reconciliation.ts
48284
+ var SESSION_RECONCILIATION_PLAN_TTL_MS;
48285
+ var init_session_reconciliation = __esm(() => {
48286
+ init_profile_client();
48287
+ init_selection_cache();
48288
+ SESSION_RECONCILIATION_PLAN_TTL_MS = 5 * 60 * 1000;
48289
+ });
48290
+
48135
48291
  // src/cli/commands/context.ts
48136
48292
  function selectedProfileId(explicit) {
48137
48293
  return explicit ?? process.env.HASNA_SKILLS_SELECTION_PROFILE ?? readManagedSkillPolicy()?.profileId ?? "default";
@@ -48142,6 +48298,7 @@ var init_context = __esm(() => {
48142
48298
  init_selection_resolver();
48143
48299
  init_selection_cache();
48144
48300
  init_managed_policy();
48301
+ init_session_reconciliation();
48145
48302
  });
48146
48303
 
48147
48304
  // src/lib/remote-customer-operations.ts
@@ -48153,7 +48310,7 @@ var init_remote_customer_operations = __esm(() => {
48153
48310
  { name: "list_remote_skills", title: "List Remote Skills", parameter: null, mutates: false, invoke: (client) => client.listSkills() },
48154
48311
  { name: "get_billing_status", title: "Get Billing Status", parameter: null, mutates: false, invoke: (client) => client.getBillingStatus() },
48155
48312
  { name: "list_credit_packs", title: "List Credit Packs", parameter: null, mutates: false, invoke: (client) => client.listCreditPacks() },
48156
- { name: "create_credit_checkout", title: "Create Credit Checkout", parameter: "pack_id", mutates: true, invoke: (client, value) => client.createCreditCheckout(value) },
48313
+ { name: "create_credit_checkout", title: "Create Credit Checkout", parameter: "pack_id", mutates: true, invoke: (client, value, options) => client.createCreditCheckout(value, options) },
48157
48314
  { name: "get_billing_usage", title: "Get Billing Usage", parameter: null, mutates: false, invoke: (client) => client.getUsage() },
48158
48315
  { name: "list_invoices", title: "List Invoices", parameter: null, mutates: false, invoke: (client) => client.listInvoices() },
48159
48316
  { name: "create_billing_checkout", title: "Create Billing Checkout", parameter: null, mutates: true, invoke: (client) => client.createBillingCheckout() },
@@ -48961,13 +49118,13 @@ var init_remote_workspace_leave = __esm(() => {
48961
49118
 
48962
49119
  // src/lib/auth-store.ts
48963
49120
  import { chmodSync, existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync15, renameSync as renameSync4, statSync as statSync8, unlinkSync as unlinkSync4, writeFileSync as writeFileSync6 } from "fs";
48964
- import { basename as basename4, dirname as dirname6, join as join19 } from "path";
49121
+ import { basename as basename5, dirname as dirname6, join as join19 } from "path";
48965
49122
  function getAuthFilePath(env = process.env) {
48966
49123
  return skillsCredentialFilePath(env);
48967
49124
  }
48968
49125
  function getIdentityFilePath(env = process.env) {
48969
49126
  const file2 = skillsCredentialFilePath(env);
48970
- return join19(dirname6(file2), basename4(file2).replace(/^credentials/, "identity") + ".json");
49127
+ return join19(dirname6(file2), basename5(file2).replace(/^credentials/, "identity") + ".json");
48971
49128
  }
48972
49129
  function getApiUrl(action, env = process.env, options = {}) {
48973
49130
  return requireSkillsApiOrigin(action, env, options);
@@ -49121,6 +49278,35 @@ var init_remote_account = __esm(() => {
49121
49278
  };
49122
49279
  });
49123
49280
 
49281
+ // src/lib/remote-credit-checkout.ts
49282
+ function creditCheckoutRequestKey(value) {
49283
+ if (value === undefined)
49284
+ return crypto.randomUUID();
49285
+ if (typeof value !== "string" || !creditCheckoutKeyPattern.test(value))
49286
+ throw new Error("Checkout idempotency key must be 8-255 URL-safe characters");
49287
+ return value;
49288
+ }
49289
+ function creditCheckoutFailure(value, status, key) {
49290
+ if (!value || typeof value !== "object" || Array.isArray(value))
49291
+ return { code: "CREDIT_CHECKOUT_UNCONFIRMED" };
49292
+ const row = value;
49293
+ if (row.requestIdempotencyKey !== undefined && row.requestIdempotencyKey !== key)
49294
+ return { code: "CREDIT_CHECKOUT_UNCONFIRMED" };
49295
+ const code = status === 409 && row.error === "credit checkout in_progress" ? "CREDIT_CHECKOUT_IN_PROGRESS" : status === 409 && row.error === "credit checkout expired" && row.requestIdempotencyKey === key ? "CREDIT_CHECKOUT_EXPIRED" : status === 409 && row.error === "credit checkout fulfilled" && row.requestIdempotencyKey === key ? "CREDIT_CHECKOUT_FULFILLED" : "CREDIT_CHECKOUT_UNCONFIRMED";
49296
+ const delay = row.retryAfterSeconds;
49297
+ return { code, ...code === "CREDIT_CHECKOUT_IN_PROGRESS" || status === 503 && row.error === "credit checkout creation unresolved" ? typeof delay === "number" && Number.isSafeInteger(delay) && delay > 0 && delay <= 3600 ? { retryAfterSeconds: delay } : {} : {} };
49298
+ }
49299
+ var creditCheckoutKeyPattern, creditCheckoutMessages;
49300
+ var init_remote_credit_checkout = __esm(() => {
49301
+ creditCheckoutKeyPattern = /^[A-Za-z0-9._:-]{8,255}$/;
49302
+ creditCheckoutMessages = {
49303
+ CREDIT_CHECKOUT_UNCONFIRMED: "Checkout creation could not be confirmed. Keep the request idempotency key, inspect billing, and use the same key for an explicit retry on the same server, account and pack. No automatic retry was made.",
49304
+ CREDIT_CHECKOUT_IN_PROGRESS: "This checkout is still being created. Wait before explicitly retrying the same server, account and pack with the same request idempotency key. No automatic retry was made.",
49305
+ CREDIT_CHECKOUT_EXPIRED: "This checkout has expired. Inspect billing before deliberately starting a new purchase with a new request idempotency key. Do not retry this checkout automatically.",
49306
+ CREDIT_CHECKOUT_FULFILLED: "This checkout is already fulfilled. Inspect the account balance; do not create another checkout to recover this purchase."
49307
+ };
49308
+ });
49309
+
49124
49310
  // src/lib/remote-profile.ts
49125
49311
  function customerNamePatch(input2, field) {
49126
49312
  if (!isRecord4(input2) || Object.keys(input2).length !== 1 || !Object.hasOwn(input2, field))
@@ -49166,10 +49352,10 @@ async function readQuoteUnavailableCode(response) {
49166
49352
  let timer;
49167
49353
  let deadlineExceeded = false;
49168
49354
  const expired = Symbol("quote body deadline");
49169
- const deadline = new Promise((resolve4) => {
49355
+ const deadline = new Promise((resolve5) => {
49170
49356
  timer = setTimeout(() => {
49171
49357
  deadlineExceeded = true;
49172
- resolve4(expired);
49358
+ resolve5(expired);
49173
49359
  reader.cancel().catch(() => {});
49174
49360
  }, 1500);
49175
49361
  });
@@ -49284,6 +49470,7 @@ __export(exports_remote_client, {
49284
49470
  RemoteRouteUnsupportedError: () => RemoteRouteUnsupportedError,
49285
49471
  RemoteRequestError: () => RemoteRequestError,
49286
49472
  RemoteQuoteUnavailableError: () => RemoteQuoteUnavailableError,
49473
+ RemoteCreditCheckoutError: () => RemoteCreditCheckoutError,
49287
49474
  RemoteCapabilityUnavailableError: () => RemoteCapabilityUnavailableError
49288
49475
  });
49289
49476
 
@@ -49734,14 +49921,43 @@ class RemoteSkillsClient {
49734
49921
  async listCreditPacks() {
49735
49922
  return parseRemoteCreditPacks(await (await this.requestNewRoute("/api/v1/billing/credits")).json());
49736
49923
  }
49737
- async createCreditCheckout(packId) {
49924
+ async createCreditCheckout(packId, options = {}) {
49925
+ const requestIdempotencyKey = creditCheckoutRequestKey(options.idempotencyKey);
49738
49926
  const packs = await this.listCreditPacks();
49739
49927
  if (!packs.some((pack) => pack.id === packId))
49740
49928
  throw new Error("Choose a credit pack returned by skills credits packs");
49741
- return parseRemoteCheckout(await (await this.requestNewRoute("/api/v1/billing/credits", {
49742
- method: "POST",
49743
- body: JSON.stringify({ packId })
49744
- })).json());
49929
+ let response;
49930
+ try {
49931
+ response = await this.request("/api/v1/billing/credits", {
49932
+ method: "POST",
49933
+ body: JSON.stringify({ packId, idempotencyKey: requestIdempotencyKey })
49934
+ });
49935
+ } catch {
49936
+ throw new RemoteCreditCheckoutError("CREDIT_CHECKOUT_UNCONFIRMED", 0, requestIdempotencyKey);
49937
+ }
49938
+ if (response.status === 404 || response.status === 405) {
49939
+ response.body?.cancel().catch(() => {});
49940
+ throw new RemoteRouteUnsupportedError("/api/v1/billing/credits", response.status, this.apiUrl);
49941
+ }
49942
+ let value;
49943
+ try {
49944
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readBoundedResponse(response, 4096)));
49945
+ } catch {
49946
+ throw new RemoteCreditCheckoutError("CREDIT_CHECKOUT_UNCONFIRMED", response.status, requestIdempotencyKey);
49947
+ }
49948
+ if (!response.ok) {
49949
+ const failure = creditCheckoutFailure(value, response.status, requestIdempotencyKey);
49950
+ throw new RemoteCreditCheckoutError(failure.code, response.status, requestIdempotencyKey, failure.retryAfterSeconds);
49951
+ }
49952
+ try {
49953
+ const checkout = parseRemoteCheckout(value);
49954
+ const echo = value.requestIdempotencyKey;
49955
+ if (echo !== undefined && echo !== requestIdempotencyKey)
49956
+ throw new Error("checkout request key changed");
49957
+ return { ...checkout, requestIdempotencyKey };
49958
+ } catch {
49959
+ throw new RemoteCreditCheckoutError("CREDIT_CHECKOUT_UNCONFIRMED", response.status, requestIdempotencyKey);
49960
+ }
49745
49961
  }
49746
49962
  async getUsage() {
49747
49963
  return this.arrayResponse("/api/v1/billing/usage");
@@ -50111,7 +50327,7 @@ async function createRemoteSkillsClient(env = process.env) {
50111
50327
  function createRemoteSkillsClientReadOnly(env = process.env) {
50112
50328
  return createRemoteSkillsClient(env);
50113
50329
  }
50114
- var RemoteRouteUnsupportedError, RemoteRequestError, RemoteQuoteUnavailableError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
50330
+ var RemoteRouteUnsupportedError, RemoteRequestError, RemoteCreditCheckoutError, RemoteQuoteUnavailableError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
50115
50331
  var init_remote_client = __esm(() => {
50116
50332
  init_remote_recurring();
50117
50333
  init_remote_invitations();
@@ -50123,6 +50339,7 @@ var init_remote_client = __esm(() => {
50123
50339
  init_fleet_credentials();
50124
50340
  init_remote_account();
50125
50341
  init_remote_files();
50342
+ init_remote_credit_checkout();
50126
50343
  init_remote_quote_errors();
50127
50344
  init_remote_permissions();
50128
50345
  RemoteRouteUnsupportedError = class RemoteRouteUnsupportedError extends Error {
@@ -50147,6 +50364,19 @@ var init_remote_client = __esm(() => {
50147
50364
  this.name = "RemoteRequestError";
50148
50365
  }
50149
50366
  };
50367
+ RemoteCreditCheckoutError = class RemoteCreditCheckoutError extends RemoteRequestError {
50368
+ code;
50369
+ requestIdempotencyKey;
50370
+ retryAfterSeconds;
50371
+ constructor(code, status, requestIdempotencyKey, retryAfterSeconds) {
50372
+ super("/api/v1/billing/credits", status);
50373
+ this.code = code;
50374
+ this.requestIdempotencyKey = requestIdempotencyKey;
50375
+ this.retryAfterSeconds = retryAfterSeconds;
50376
+ this.name = "RemoteCreditCheckoutError";
50377
+ this.message = creditCheckoutMessages[code];
50378
+ }
50379
+ };
50150
50380
  RemoteQuoteUnavailableError = class RemoteQuoteUnavailableError extends RemoteRequestError {
50151
50381
  code;
50152
50382
  constructor(path, code) {
@@ -50533,12 +50763,12 @@ class RemotePrivatePublicationsClient {
50533
50763
  previous = view;
50534
50764
  if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
50535
50765
  return view;
50536
- await new Promise((resolve4) => {
50766
+ await new Promise((resolve5) => {
50537
50767
  const timer = setTimeout(done, Math.min(1000, until - Date.now()));
50538
50768
  function done() {
50539
50769
  clearTimeout(timer);
50540
50770
  options.signal?.removeEventListener("abort", done);
50541
- resolve4();
50771
+ resolve5();
50542
50772
  }
50543
50773
  options.signal?.addEventListener("abort", done, { once: true });
50544
50774
  });
@@ -51058,8 +51288,8 @@ var init_recurring_customer = __esm(() => {
51058
51288
  });
51059
51289
 
51060
51290
  // src/lib/recurring-recovery.ts
51061
- import { constants as constants7, closeSync as closeSync10, fsyncSync as fsyncSync2, fstatSync as fstatSync10, lstatSync as lstatSync10, mkdirSync as mkdirSync10, openSync as openSync10, readSync as readSync5, realpathSync as realpathSync4, renameSync as renameSync6, unlinkSync as unlinkSync5, writeFileSync as writeFileSync8 } from "fs";
51062
- import { dirname as dirname7, isAbsolute as isAbsolute4, join as join20, resolve as resolve4 } from "path";
51291
+ import { constants as constants7, closeSync as closeSync10, fsyncSync as fsyncSync3, fstatSync as fstatSync10, lstatSync as lstatSync10, mkdirSync as mkdirSync10, openSync as openSync10, readSync as readSync5, realpathSync as realpathSync4, renameSync as renameSync6, unlinkSync as unlinkSync5, writeFileSync as writeFileSync8 } from "fs";
51292
+ import { dirname as dirname7, isAbsolute as isAbsolute5, join as join20, resolve as resolve5 } from "path";
51063
51293
  import { randomUUID as randomUUID2 } from "crypto";
51064
51294
 
51065
51295
  class RecoveryOwner {
@@ -51067,7 +51297,7 @@ class RecoveryOwner {
51067
51297
  parents = [];
51068
51298
  constructor(directory) {
51069
51299
  this.directory = directory;
51070
- if (!isAbsolute4(directory) || resolve4(directory) !== directory || realpathSync4(directory) !== directory)
51300
+ if (!isAbsolute5(directory) || resolve5(directory) !== directory || realpathSync4(directory) !== directory)
51071
51301
  invalid5();
51072
51302
  for (let path = directory;; path = dirname7(path)) {
51073
51303
  const stat2 = lstatSync10(path);
@@ -51122,7 +51352,7 @@ class RecoveryOwner {
51122
51352
  const fd = openSync10(join20(this.directory, name), constants7.O_WRONLY | constants7.O_CREAT | constants7.O_EXCL | constants7.O_NOFOLLOW, 384);
51123
51353
  try {
51124
51354
  writeFileSync8(fd, bytes);
51125
- fsyncSync2(fd);
51355
+ fsyncSync3(fd);
51126
51356
  } finally {
51127
51357
  closeSync10(fd);
51128
51358
  }
@@ -51133,7 +51363,7 @@ class RecoveryOwner {
51133
51363
  this.assert();
51134
51364
  const fd = openSync10(this.directory, constants7.O_RDONLY | constants7.O_NOFOLLOW);
51135
51365
  try {
51136
- fsyncSync2(fd);
51366
+ fsyncSync3(fd);
51137
51367
  } finally {
51138
51368
  closeSync10(fd);
51139
51369
  }
@@ -51160,7 +51390,7 @@ class RecoveryOwner {
51160
51390
  const owner = fstatSync10(fd);
51161
51391
  try {
51162
51392
  writeFileSync8(fd, JSON.stringify({ pid: process.pid }));
51163
- fsyncSync2(fd);
51393
+ fsyncSync3(fd);
51164
51394
  this.sync();
51165
51395
  return await action();
51166
51396
  } finally {
@@ -51202,7 +51432,7 @@ function base(customer, organizationId) {
51202
51432
  };
51203
51433
  }
51204
51434
  function create(directory, intent) {
51205
- if (!isAbsolute4(directory) || resolve4(directory) !== directory || realpathSync4(dirname7(directory)) !== dirname7(directory))
51435
+ if (!isAbsolute5(directory) || resolve5(directory) !== directory || realpathSync4(dirname7(directory)) !== dirname7(directory))
51206
51436
  invalid5();
51207
51437
  mkdirSync10(directory, { mode: 448 });
51208
51438
  const owner = new RecoveryOwner(directory), checked = intentSchema.parse(intent), intentSha256 = canonicalJsonSha256(checked);
@@ -51365,7 +51595,7 @@ var init_recurring_recovery = __esm(() => {
51365
51595
  });
51366
51596
 
51367
51597
  // src/lib/recurring-surface.ts
51368
- import { isAbsolute as isAbsolute5 } from "path";
51598
+ import { isAbsolute as isAbsolute6 } from "path";
51369
51599
  function recurringMcpSchema(action) {
51370
51600
  return action === "activate" ? recurringSurfaceSchemas.activate.extend({ code, confirm: exports_external.literal(true) }) : recurringSurfaceSchemas[action];
51371
51601
  }
@@ -51470,7 +51700,7 @@ var init_recurring_surface = __esm(() => {
51470
51700
  uuid13 = exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
51471
51701
  context = { userId: uuid13.optional(), membershipId: uuid13.optional() };
51472
51702
  page2 = { limit: exports_external.number().int().min(1).max(100).optional(), cursor: exports_external.string().min(1).max(2048).optional() };
51473
- directory = exports_external.string().max(4096).refine(isAbsolute5, "Use an absolute recovery directory");
51703
+ directory = exports_external.string().max(4096).refine(isAbsolute6, "Use an absolute recovery directory");
51474
51704
  email4 = exports_external.string().trim().toLowerCase().email().max(254);
51475
51705
  code = exports_external.string().regex(/^\d{6}$/);
51476
51706
  recurringSurfaceSchemas = {
@@ -52439,11 +52669,11 @@ var init_mcp_contracts = __esm(() => {
52439
52669
  name: operation.name,
52440
52670
  title: operation.title,
52441
52671
  description: `${operation.title} on the configured server; unavailable capabilities fail explicitly.`,
52442
- params: operation.parameter ? [operation.parameter] : [],
52672
+ params: operation.parameter ? [operation.parameter, ...operation.name === "create_credit_checkout" ? ["idempotency_key?"] : []] : [],
52443
52673
  category: "execution",
52444
52674
  sideEffects: operation.mutates ? "local-process-or-remote-run" : "none",
52445
52675
  stable: true,
52446
- inputSchema: objectSchema(operation.parameter ? { [operation.parameter]: stringSchema("Server resource identifier.") } : {}, operation.parameter ? [operation.parameter] : []),
52676
+ inputSchema: objectSchema(operation.parameter ? { [operation.parameter]: stringSchema("Server resource identifier."), ...operation.name === "create_credit_checkout" ? { idempotency_key: { type: "string", pattern: "^[A-Za-z0-9._:-]{8,255}$", description: "Caller-owned key retained before submission; reuse only for explicit recovery of the same server/account/pack." } } : {} } : {}, operation.parameter ? [operation.parameter] : []),
52447
52677
  outputSchema: { oneOf: [objectSchema({}, [], "Server response.", true), { type: "array", items: objectSchema({}, [], "Server record.", true) }] }
52448
52678
  }));
52449
52679
  remoteCustomerContracts.push({
@@ -53535,7 +53765,7 @@ import { closeSync as closeSync11, fstatSync as fstatSync11, openSync as openSyn
53535
53765
  import { O_NOFOLLOW as O_NOFOLLOW3, O_NONBLOCK as O_NONBLOCK3, O_RDONLY as O_RDONLY3 } from "constants";
53536
53766
  import { createRequire as createRequire2 } from "module";
53537
53767
  import { hostname as osHostname2 } from "os";
53538
- import { isAbsolute as isAbsolute6, join as join22 } from "path";
53768
+ import { isAbsolute as isAbsolute7, join as join22 } from "path";
53539
53769
  import { createRequire as createRequire22 } from "module";
53540
53770
  function envToken2(name) {
53541
53771
  return name.toUpperCase().replace(/-/g, "_");
@@ -53559,7 +53789,7 @@ function homeDir2(env) {
53559
53789
  }
53560
53790
  function absoluteOverride2(env, key) {
53561
53791
  const value = env[key]?.trim();
53562
- return value && isAbsolute6(value) ? value : null;
53792
+ return value && isAbsolute7(value) ? value : null;
53563
53793
  }
53564
53794
  function hasnaHomeDir2(env) {
53565
53795
  const override = absoluteOverride2(env, HASNA_HOME_ENV_KEY2);
@@ -54696,7 +54926,7 @@ function createSecretsClientFromEnv(env = process.env, overrides = {}) {
54696
54926
  });
54697
54927
  return new SecretsClient({ ...clientOverrides, baseUrl, apiKey });
54698
54928
  }
54699
- var __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), CREDENTIAL_PROFILE_ENV_KEY2 = "HASNA_PROFILE", CredentialResolutionError2, CredentialFileUnsafeError2, HASNA_HOME_ENV_KEY2 = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY2 = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY2 = "HASNA_STATION", HASNA_HOME_DIR2 = ".hasna", CONFIG_SUBDIR2 = "config", CREDENTIALS_FILE2 = "credentials", KEYCHAIN_SECURITY_BIN2 = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX2 = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS2 = 44, KEYCHAIN_SPAWN_TIMEOUT_MS2 = 1e4, MAX_CREDENTIAL_FILE_BYTES2, SAFE_APP_SLUG2, SAFE_PROFILE2, ILLEGAL_IN_HEADER_VALUE2, VAULT_POINTER_SHAPE2, CREDENTIAL_SHAPED_KEY2, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE = "caller-supplied CredentialProvider", AMBIENT_ENVIRONMENT2, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk2, DEFAULT_FLEET_GATEWAY_ORIGIN2 = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN2, DNS_LABEL_PATTERN2, ClientTransportConfigurationError2, HasnaHttpError, DEFAULT_RETRY_STATUSES, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2, defaultSleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)), init_transport2, MAX_CREDENTIAL_FILE_BYTES22, INSPECT_CUSTOM22, CREDENTIAL_SEAL22, AMBIENT_ENVIRONMENT22, SECRETS_PACKAGE_SPECIFIER2, requireSecretsSdk22, IDEMPOTENT_METHODS22, AUTHORITY_OVERRIDE_HEADERS22, init_storage, PATHS_RESOLVER_APP_SLUG_RE2, SQLITE_SIDECAR_FILES, LEGACY_SERVICE_FILES, init_data_dir, SecretsTestIsolationError, TEST_ENTRYPOINT_RE, init_test_isolation, init_client, ApiError;
54929
+ var __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), CREDENTIAL_PROFILE_ENV_KEY2 = "HASNA_PROFILE", CredentialResolutionError2, CredentialFileUnsafeError2, HASNA_HOME_ENV_KEY2 = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY2 = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY2 = "HASNA_STATION", HASNA_HOME_DIR2 = ".hasna", CONFIG_SUBDIR2 = "config", CREDENTIALS_FILE2 = "credentials", KEYCHAIN_SECURITY_BIN2 = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX2 = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS2 = 44, KEYCHAIN_SPAWN_TIMEOUT_MS2 = 1e4, MAX_CREDENTIAL_FILE_BYTES2, SAFE_APP_SLUG2, SAFE_PROFILE2, ILLEGAL_IN_HEADER_VALUE2, VAULT_POINTER_SHAPE2, CREDENTIAL_SHAPED_KEY2, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE = "caller-supplied CredentialProvider", AMBIENT_ENVIRONMENT2, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk2, DEFAULT_FLEET_GATEWAY_ORIGIN2 = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN2, DNS_LABEL_PATTERN2, ClientTransportConfigurationError2, HasnaHttpError, DEFAULT_RETRY_STATUSES, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2, defaultSleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)), init_transport2, MAX_CREDENTIAL_FILE_BYTES22, INSPECT_CUSTOM22, CREDENTIAL_SEAL22, AMBIENT_ENVIRONMENT22, SECRETS_PACKAGE_SPECIFIER2, requireSecretsSdk22, IDEMPOTENT_METHODS22, AUTHORITY_OVERRIDE_HEADERS22, init_storage, PATHS_RESOLVER_APP_SLUG_RE2, SQLITE_SIDECAR_FILES, LEGACY_SERVICE_FILES, init_data_dir, SecretsTestIsolationError, TEST_ENTRYPOINT_RE, init_test_isolation, init_client, ApiError;
54700
54930
  var init_sdk = __esm(() => {
54701
54931
  init_transport2 = __esm2(() => {
54702
54932
  CredentialResolutionError2 = class CredentialResolutionError3 extends Error {
@@ -55011,7 +55241,7 @@ var init_execution_grants = __esm(() => {
55011
55241
  });
55012
55242
 
55013
55243
  // src/lib/execution-grant-client.ts
55014
- import { isDeepStrictEqual } from "util";
55244
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
55015
55245
  function id(value) {
55016
55246
  if (!grantIdentifier(value))
55017
55247
  throw new Error("Invalid execution grant profile or revision identifier.");
@@ -55054,7 +55284,7 @@ async function resolveExecutionGrant(input2) {
55054
55284
  if (!grantRecord(value) || Object.keys(value).length !== 3 || !grantIdentifier(value.policyRevision) || !grantIdentifier(value.grantId) || !grantRecord(value.bindings)) {
55055
55285
  throw new SkillSelectionError("INVALID_EXECUTION_GRANT", "The Skills API returned an invalid execution grant.");
55056
55286
  }
55057
- if (!isDeepStrictEqual(value.bindings.selection, snapshot.selection) || !isDeepStrictEqual(value.bindings.consumer, snapshot.consumer)) {
55287
+ if (!isDeepStrictEqual2(value.bindings.selection, snapshot.selection) || !isDeepStrictEqual2(value.bindings.consumer, snapshot.consumer)) {
55058
55288
  throw new SkillSelectionError("EXECUTION_GRANT_SCOPE_MISMATCH", "The Skills API returned a grant for a different selection or consumer.");
55059
55289
  }
55060
55290
  return value;
@@ -55076,7 +55306,7 @@ __export(exports_selected_run, {
55076
55306
  import { createHash as createHash9 } from "crypto";
55077
55307
  import { spawn } from "child_process";
55078
55308
  import { mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync10, realpathSync as realpathSync6 } from "fs";
55079
- import { dirname as dirname8, join as join23, resolve as resolve5 } from "path";
55309
+ import { dirname as dirname8, join as join23, resolve as resolve6 } from "path";
55080
55310
  import { tmpdir, hostname as stationHostname2 } from "os";
55081
55311
  async function resolveSelectedRun(spec, profileId, options = {}) {
55082
55312
  const context2 = await resolveSelectionContext(profileId, options);
@@ -55169,7 +55399,7 @@ async function executeSelectedLocal(selected, options = {}) {
55169
55399
  writeFileSync10(destination, entry.bytes, { flag: "wx", mode: entry.mode & 511 });
55170
55400
  }
55171
55401
  env.SKILLS_INPUT_JSON = input2;
55172
- env.SKILLS_WORKSPACE_DIR = resolve5(options.cwd ?? process.cwd());
55402
+ env.SKILLS_WORKSPACE_DIR = resolve6(options.cwd ?? process.cwd());
55173
55403
  env.SKILLS_RUN_DIR = runDirectory;
55174
55404
  if (runtime.version) {
55175
55405
  const version2 = await subprocess([executable, ...runtimeName === "bun" ? ["--no-env-file"] : [], "--version"], runDirectory, env, "", Math.min(timeoutMs, 5000));
@@ -56930,11 +57160,11 @@ var init_private_publication_customer = __esm(() => {
56930
57160
  });
56931
57161
 
56932
57162
  // src/lib/private-publication-recovery.ts
56933
- import { constants as constants9, closeSync as closeSync13, fsyncSync as fsyncSync3, fstatSync as fstatSync13, lstatSync as lstatSync11, mkdirSync as mkdirSync16, openSync as openSync13, readSync as readSync7, realpathSync as realpathSync7, renameSync as renameSync7, unlinkSync as unlinkSync6, writeFileSync as writeFileSync13 } from "fs";
56934
- import { dirname as dirname11, isAbsolute as isAbsolute7, join as join28, resolve as resolve6 } from "path";
57163
+ import { constants as constants9, closeSync as closeSync13, fsyncSync as fsyncSync4, fstatSync as fstatSync13, lstatSync as lstatSync11, mkdirSync as mkdirSync16, openSync as openSync13, readSync as readSync7, realpathSync as realpathSync7, renameSync as renameSync7, unlinkSync as unlinkSync6, writeFileSync as writeFileSync13 } from "fs";
57164
+ import { dirname as dirname11, isAbsolute as isAbsolute8, join as join28, resolve as resolve7 } from "path";
56935
57165
  import { randomUUID as randomUUID3 } from "crypto";
56936
57166
  function safeDirectory(directory2) {
56937
- if (!isAbsolute7(directory2) || directory2 !== resolve6(directory2) || realpathSync7(directory2) !== directory2)
57167
+ if (!isAbsolute8(directory2) || directory2 !== resolve7(directory2) || realpathSync7(directory2) !== directory2)
56938
57168
  return fail2();
56939
57169
  for (let path = directory2;; path = dirname11(path)) {
56940
57170
  const stat2 = lstatSync11(path);
@@ -56981,7 +57211,7 @@ function writeOwned(directory2, name, bytes) {
56981
57211
  const fd = openSync13(join28(directory2, name), constants9.O_WRONLY | constants9.O_CREAT | constants9.O_EXCL | constants9.O_NOFOLLOW, 384);
56982
57212
  try {
56983
57213
  writeFileSync13(fd, bytes);
56984
- fsyncSync3(fd);
57214
+ fsyncSync4(fd);
56985
57215
  } finally {
56986
57216
  closeSync13(fd);
56987
57217
  }
@@ -56994,7 +57224,7 @@ function save(directory2, value) {
56994
57224
  renameSync7(join28(directory2, temporary), join28(directory2, "receipt.json"));
56995
57225
  const fd = openSync13(directory2, constants9.O_RDONLY | constants9.O_NOFOLLOW);
56996
57226
  try {
56997
- fsyncSync3(fd);
57227
+ fsyncSync4(fd);
56998
57228
  } finally {
56999
57229
  closeSync13(fd);
57000
57230
  }
@@ -57015,7 +57245,7 @@ async function locked(directory2, action) {
57015
57245
  try {
57016
57246
  writeFileSync13(fd, JSON.stringify({ pid: process.pid }) + `
57017
57247
  `);
57018
- fsyncSync3(fd);
57248
+ fsyncSync4(fd);
57019
57249
  return await action();
57020
57250
  } finally {
57021
57251
  closeSync13(fd);
@@ -57072,13 +57302,13 @@ async function preparePrivatePublication(client, sourceDirectory, recoveryDirect
57072
57302
  phase: "prepared",
57073
57303
  intent: null
57074
57304
  };
57075
- if (!isAbsolute7(recoveryDirectory) || resolve6(recoveryDirectory) !== recoveryDirectory || realpathSync7(dirname11(recoveryDirectory)) !== dirname11(recoveryDirectory))
57305
+ if (!isAbsolute8(recoveryDirectory) || resolve7(recoveryDirectory) !== recoveryDirectory || realpathSync7(dirname11(recoveryDirectory)) !== dirname11(recoveryDirectory))
57076
57306
  return fail2();
57077
57307
  mkdirSync16(recoveryDirectory, { mode: 448 });
57078
57308
  safeDirectory(recoveryDirectory);
57079
57309
  const parent = openSync13(dirname11(recoveryDirectory), constants9.O_RDONLY | constants9.O_NOFOLLOW);
57080
57310
  try {
57081
- fsyncSync3(parent);
57311
+ fsyncSync4(parent);
57082
57312
  } finally {
57083
57313
  closeSync13(parent);
57084
57314
  }
@@ -57173,10 +57403,10 @@ var init_private_publication_recovery = __esm(() => {
57173
57403
  });
57174
57404
 
57175
57405
  // src/mcp/private-publication-tools.ts
57176
- import { isAbsolute as isAbsolute8 } from "path";
57406
+ import { isAbsolute as isAbsolute9 } from "path";
57177
57407
  function registerPrivatePublicationTools(server) {
57178
57408
  const uuid9 = exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
57179
- const directory2 = exports_external.string().max(4096).refine(isAbsolute8, "Use an absolute local directory");
57409
+ const directory2 = exports_external.string().max(4096).refine(isAbsolute9, "Use an absolute local directory");
57180
57410
  const verification2 = { email: exports_external.string().email().max(254), code: exports_external.string().regex(/^\d{6}$/), userId: uuid9, membershipId: uuid9, recoveryDirectory: directory2 };
57181
57411
  const description = "Manage a private source publication using fresh workspace-bound verification and a host-local recovery directory. Publishing requires explicit UUID current-version comparison and upload consent. Resume reuses immutable saved bytes and intent; an uncertain PUT is finalized for server verification, never uploaded twice. Sessions and signed URLs are not returned or persisted. Private execution is unavailable. Host request history may contain the supplied verification code.";
57182
57412
  const handler = (action) => async (value) => {
@@ -57331,6 +57561,14 @@ function registerRemoteCustomerTools(server) {
57331
57561
  });
57332
57562
  }
57333
57563
  for (const operation of REMOTE_CUSTOMER_OPERATIONS) {
57564
+ if (operation.name === "create_credit_checkout") {
57565
+ server.registerTool(operation.name, {
57566
+ title: operation.title,
57567
+ description: "Create one external checkout link on the selected Skills server. Retain an idempotency_key before calling and reuse it only for explicit recovery on the same server, account and pack. No automatic retries; payment requires customer confirmation.",
57568
+ inputSchema: { pack_id: exports_external.string().min(1), idempotency_key: exports_external.string().regex(/^[A-Za-z0-9._:-]{8,255}$/).optional() }
57569
+ }, ({ pack_id, idempotency_key }) => callRemote((client) => operation.invoke(client, pack_id, { idempotencyKey: idempotency_key })));
57570
+ continue;
57571
+ }
57334
57572
  const inputSchema = {};
57335
57573
  if (operation.parameter)
57336
57574
  inputSchema[operation.parameter] = exports_external.string().min(1);
@@ -57407,6 +57645,15 @@ async function callRemote(action) {
57407
57645
  return mcpError("AUTH_REQUIRED", "Configure a Skills API and sign in with skills auth login");
57408
57646
  return mcpJson(await action(client));
57409
57647
  } catch (error61) {
57648
+ if (error61 instanceof RemoteCreditCheckoutError) {
57649
+ return { ...mcpJson({
57650
+ code: error61.code,
57651
+ message: error61.message,
57652
+ status: error61.status,
57653
+ requestIdempotencyKey: error61.requestIdempotencyKey,
57654
+ ...error61.retryAfterSeconds === undefined ? {} : { retryAfterSeconds: error61.retryAfterSeconds }
57655
+ }), isError: true };
57656
+ }
57410
57657
  if (error61 instanceof RemoteCapabilityUnavailableError) {
57411
57658
  return { ...mcpJson({ code: error61.code, message: error61.message, status: error61.status }), isError: true };
57412
57659
  }
@@ -57871,7 +58118,7 @@ var RequestError, nonJoinedHeaders, validHeaderName, isHttpWhitespace = (code2)
57871
58118
  request2[bodyBufferKey] = buffered;
57872
58119
  return Promise.resolve(buffered);
57873
58120
  }
57874
- const promise3 = new Promise((resolve7, reject) => {
58121
+ const promise3 = new Promise((resolve8, reject) => {
57875
58122
  const chunks = [];
57876
58123
  let settled = false;
57877
58124
  const finish = (callback) => {
@@ -57893,7 +58140,7 @@ var RequestError, nonJoinedHeaders, validHeaderName, isHttpWhitespace = (code2)
57893
58140
  reject(error61 ?? normalizeAbortError(request2, incoming));
57894
58141
  else {
57895
58142
  request2[bodyBufferKey] = recovered;
57896
- resolve7(recovered);
58143
+ resolve8(recovered);
57897
58144
  }
57898
58145
  });
57899
58146
  return true;
@@ -57905,7 +58152,7 @@ var RequestError, nonJoinedHeaders, validHeaderName, isHttpWhitespace = (code2)
57905
58152
  finish(() => {
57906
58153
  const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks);
57907
58154
  request2[bodyBufferKey] = buffer;
57908
- resolve7(buffer);
58155
+ resolve8(buffer);
57909
58156
  });
57910
58157
  };
57911
58158
  const onError = (error61) => {
@@ -58179,7 +58426,7 @@ var RequestError, nonJoinedHeaders, validHeaderName, isHttpWhitespace = (code2)
58179
58426
  });
58180
58427
  if (!chunk) {
58181
58428
  if (i === 1) {
58182
- await new Promise((resolve7) => setTimeout(resolve7));
58429
+ await new Promise((resolve8) => setTimeout(resolve8));
58183
58430
  maxReadCount = 3;
58184
58431
  continue;
58185
58432
  }
@@ -59409,9 +59656,9 @@ data:
59409
59656
  const initRequest = messages.find((m) => isInitializeRequest(m));
59410
59657
  const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
59411
59658
  if (this._enableJsonResponse) {
59412
- return new Promise((resolve7) => {
59659
+ return new Promise((resolve8) => {
59413
59660
  this._streamMapping.set(streamId, {
59414
- resolveJson: resolve7,
59661
+ resolveJson: resolve8,
59415
59662
  cleanup: () => {
59416
59663
  this._streamMapping.delete(streamId);
59417
59664
  }
@@ -59780,19 +60027,19 @@ async function connectMcpForNode() {
59780
60027
  return { server: server2, transport };
59781
60028
  }
59782
60029
  function readBody(req) {
59783
- return new Promise((resolve7, reject) => {
60030
+ return new Promise((resolve8, reject) => {
59784
60031
  const chunks = [];
59785
60032
  req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
59786
60033
  req.on("end", () => {
59787
60034
  const raw = Buffer.concat(chunks).toString("utf-8");
59788
60035
  if (!raw.trim()) {
59789
- resolve7(undefined);
60036
+ resolve8(undefined);
59790
60037
  return;
59791
60038
  }
59792
60039
  try {
59793
- resolve7(JSON.parse(raw));
60040
+ resolve8(JSON.parse(raw));
59794
60041
  } catch {
59795
- resolve7(undefined);
60042
+ resolve8(undefined);
59796
60043
  }
59797
60044
  });
59798
60045
  req.on("error", reject);
@@ -59834,9 +60081,9 @@ async function startSkillsMcpHttpServer(options = {}) {
59834
60081
  }
59835
60082
  }
59836
60083
  });
59837
- await new Promise((resolve7, reject) => {
60084
+ await new Promise((resolve8, reject) => {
59838
60085
  httpServer.once("error", reject);
59839
- httpServer.listen(port, hostname3, () => resolve7());
60086
+ httpServer.listen(port, hostname3, () => resolve8());
59840
60087
  });
59841
60088
  const address = httpServer.address();
59842
60089
  const listenPort = typeof address === "object" && address ? address.port : port;