@hasna/skills 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/index.js CHANGED
@@ -36860,7 +36860,7 @@ var package_default;
36860
36860
  var init_package = __esm(() => {
36861
36861
  package_default = {
36862
36862
  name: "@hasna/skills",
36863
- version: "0.5.3",
36863
+ version: "0.5.5",
36864
36864
  description: "Skills library for AI coding agents",
36865
36865
  type: "module",
36866
36866
  bin: {
@@ -39472,6 +39472,186 @@ function hashBundleFiles(files) {
39472
39472
  hash.update(part);
39473
39473
  return hash.digest("hex");
39474
39474
  }
39475
+ async function hashBundleFilesCooperatively(files, check) {
39476
+ const hash = createHash(CONTENT_HASH_ALGORITHM);
39477
+ let bytesSinceYield = 0;
39478
+ for (const part of bundleHashParts(files)) {
39479
+ for (let offset = 0;offset < part.byteLength; offset += 64 * 1024) {
39480
+ check();
39481
+ const chunk2 = part.subarray(offset, offset + 64 * 1024);
39482
+ hash.update(chunk2);
39483
+ bytesSinceYield += chunk2.byteLength;
39484
+ if (bytesSinceYield >= 256 * 1024) {
39485
+ await new Promise((resolve3) => setImmediate(resolve3));
39486
+ bytesSinceYield = 0;
39487
+ }
39488
+ }
39489
+ }
39490
+ check();
39491
+ return hash.digest("hex");
39492
+ }
39493
+ function invalidContent(message = "Invalid content hash input") {
39494
+ throw new ContentHashInputError("CONTENT_HASH_INVALID", message);
39495
+ }
39496
+ function contentLimit(message) {
39497
+ throw new ContentHashInputError("CONTENT_HASH_LIMIT", message);
39498
+ }
39499
+ function contentRecord(value, allowed) {
39500
+ if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
39501
+ invalidContent();
39502
+ const result2 = Object.create(null);
39503
+ for (const key of Reflect.ownKeys(value)) {
39504
+ if (typeof key !== "string" || !allowed.includes(key))
39505
+ invalidContent();
39506
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
39507
+ if (!descriptor || !("value" in descriptor))
39508
+ invalidContent("Accessor content hash input is unsupported");
39509
+ result2[key] = descriptor.value;
39510
+ }
39511
+ return result2;
39512
+ }
39513
+ function contentOptions(options) {
39514
+ const record = contentRecord(options, ["limits", "signal"]);
39515
+ const limits = { ...CONTENT_HASH_LIMITS };
39516
+ if (record.limits !== undefined) {
39517
+ const supplied = contentRecord(record.limits, Object.keys(limits));
39518
+ for (const key of Object.keys(supplied)) {
39519
+ const value = supplied[key];
39520
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0 || value > limits[key])
39521
+ contentLimit("Invalid content hash limit");
39522
+ limits[key] = value;
39523
+ }
39524
+ }
39525
+ if (record.signal !== undefined && !(record.signal instanceof AbortSignal))
39526
+ invalidContent("Invalid content hash signal");
39527
+ return { limits, signal: record.signal };
39528
+ }
39529
+ function snapshotContentEntries(entries, limits, check) {
39530
+ if (!Array.isArray(entries))
39531
+ invalidContent("Content hash entries must be an array");
39532
+ if (entries.length > limits.entries)
39533
+ contentLimit("Content hash entry limit exceeded");
39534
+ if (Reflect.ownKeys(entries).length !== entries.length + 1)
39535
+ invalidContent("Invalid content hash entry array");
39536
+ const snapshot = [];
39537
+ const paths = new SkillEntryPaths;
39538
+ let rawBytes = 0;
39539
+ for (let index = 0;index < entries.length; index++) {
39540
+ check();
39541
+ const descriptor = Object.getOwnPropertyDescriptor(entries, String(index));
39542
+ if (!descriptor || !("value" in descriptor))
39543
+ invalidContent("Invalid content hash entry array");
39544
+ const entry = contentRecord(descriptor.value, ["path", "bytes", "mode"]);
39545
+ if (typeof entry.path !== "string" || typeof entry.mode !== "number" || !Number.isInteger(entry.mode) || entry.mode < 0 || entry.mode > 511)
39546
+ invalidContent("Invalid regular-file content hash entry");
39547
+ paths.add(entry.path, limits.pathBytes, invalidContent, () => contentLimit("Content hash path limit exceeded"));
39548
+ if (!(entry.bytes instanceof Uint8Array) || !ArrayBuffer.isView(entry.bytes))
39549
+ invalidContent("Content hash entry requires bytes");
39550
+ const size2 = byteLengthOf.call(entry.bytes);
39551
+ if (!(bufferOf.call(entry.bytes) instanceof ArrayBuffer))
39552
+ invalidContent("Shared content hash bytes are unsupported");
39553
+ if (size2 > limits.fileBytes || rawBytes + size2 > limits.rawBytes)
39554
+ contentLimit("Content hash raw byte limit exceeded");
39555
+ if (entry.path === "skill.json" && size2 > limits.manifestBytes)
39556
+ contentLimit("Content hash manifest byte limit exceeded");
39557
+ rawBytes += size2;
39558
+ const bytes = new Uint8Array(new ArrayBuffer(size2));
39559
+ bytes.set(entry.bytes);
39560
+ snapshot.push({ path: entry.path, bytes, mode: entry.mode });
39561
+ }
39562
+ check();
39563
+ return snapshot;
39564
+ }
39565
+ function coveredContentPath(path) {
39566
+ const segments = path.split("/");
39567
+ if (!HASH_COVERAGE.includes(segments[0]))
39568
+ return false;
39569
+ return !segments.slice(1).some((segment, index) => excludedHashEntry(segment, index < segments.length - 2));
39570
+ }
39571
+ function boundedManifest(raw, maxDepth) {
39572
+ let parsed;
39573
+ try {
39574
+ parsed = JSON.parse(raw);
39575
+ } catch {
39576
+ return;
39577
+ }
39578
+ const pending = [{ value: parsed, depth: 1 }];
39579
+ while (pending.length) {
39580
+ const { value, depth } = pending.pop();
39581
+ if (!value || typeof value !== "object")
39582
+ continue;
39583
+ if (depth > maxDepth)
39584
+ contentLimit("Content hash manifest depth limit exceeded");
39585
+ for (const child of Object.values(value))
39586
+ pending.push({ value: child, depth: depth + 1 });
39587
+ }
39588
+ return parsed;
39589
+ }
39590
+ async function hashContentEntries(entries, options) {
39591
+ const { limits, signal } = contentOptions(options);
39592
+ const deadline = performance.now() + limits.timeoutMs;
39593
+ let terminal;
39594
+ const abort = () => {
39595
+ terminal ??= new ContentHashInputError("CONTENT_HASH_ABORTED", "Content hashing aborted");
39596
+ };
39597
+ const timer = setTimeout(() => {
39598
+ terminal ??= new ContentHashInputError("CONTENT_HASH_TIMEOUT", "Content hashing deadline exceeded");
39599
+ }, limits.timeoutMs);
39600
+ const check = () => {
39601
+ if (signal?.aborted)
39602
+ abort();
39603
+ if (terminal)
39604
+ throw terminal;
39605
+ if (performance.now() >= deadline)
39606
+ throw new ContentHashInputError("CONTENT_HASH_TIMEOUT", "Content hashing deadline exceeded");
39607
+ };
39608
+ try {
39609
+ signal?.addEventListener("abort", abort, { once: true });
39610
+ check();
39611
+ const snapshot = snapshotContentEntries(entries, limits, check);
39612
+ const normalized = [];
39613
+ let normalizedBytes = 0;
39614
+ let manifest;
39615
+ await new Promise((resolve3) => setImmediate(resolve3));
39616
+ for (const entry of snapshot) {
39617
+ check();
39618
+ if (!coveredContentPath(entry.path))
39619
+ continue;
39620
+ if (entry.path === "skill.json")
39621
+ manifest = boundedManifest(new TextDecoder().decode(entry.bytes), limits.manifestDepth);
39622
+ const file = normalizeBundleFile(entry.path, entry.bytes);
39623
+ check();
39624
+ if (file.content.byteLength > limits.normalizedFileBytes || normalizedBytes + file.content.byteLength > limits.normalizedBytes)
39625
+ contentLimit("Content hash normalized byte limit exceeded");
39626
+ normalizedBytes += file.content.byteLength;
39627
+ normalized.push(file);
39628
+ await new Promise((resolve3) => setImmediate(resolve3));
39629
+ }
39630
+ normalized.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
39631
+ check();
39632
+ return { hash: await hashBundleFilesCooperatively(normalized, check), manifest };
39633
+ } catch (error) {
39634
+ if (error instanceof ContentHashInputError)
39635
+ throw error;
39636
+ throw new ContentHashInputError("CONTENT_HASH_INVALID", "Invalid content hash input");
39637
+ } finally {
39638
+ clearTimeout(timer);
39639
+ signal?.removeEventListener("abort", abort);
39640
+ }
39641
+ }
39642
+ async function verifyContentHashFromEntries(entries, options = {}) {
39643
+ const { hash, manifest } = await hashContentEntries(entries, options);
39644
+ const provenance = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest.provenance : undefined;
39645
+ const value = provenance && typeof provenance === "object" && !Array.isArray(provenance) ? provenance.content_hash : undefined;
39646
+ if (value !== undefined && typeof value !== "string")
39647
+ invalidContent("Invalid content hash declaration");
39648
+ const declaredHash = value?.trim() || undefined;
39649
+ if (!declaredHash)
39650
+ return { declared: false, valid: false };
39651
+ if (!/^[a-f0-9]{64}$/.test(declaredHash))
39652
+ return { declared: true, valid: false, declaredHash };
39653
+ return { declared: true, valid: hash === declaredHash, declaredHash, computedHash: hash };
39654
+ }
39475
39655
  function verifyContentHash(skillPath, manifest) {
39476
39656
  const declaredHash = manifest?.provenance?.content_hash?.trim() || undefined;
39477
39657
  if (!declaredHash)
@@ -39506,7 +39686,7 @@ function hashSkillMarkdown(content) {
39506
39686
  function hashSkillMarkdownFile(path) {
39507
39687
  return hashSkillMarkdown(readFileSync5(path, "utf-8"));
39508
39688
  }
39509
- var CONTENT_HASH_ALGORITHM = "sha256", HASH_EXCLUDE_DIRS, HASH_COVERAGE, CONTENT_HASH_LIMITS, typedArrayPrototype, byteLengthOf, bufferOf;
39689
+ var CONTENT_HASH_ALGORITHM = "sha256", HASH_EXCLUDE_DIRS, HASH_COVERAGE, CONTENT_HASH_LIMITS, ContentHashInputError, typedArrayPrototype, byteLengthOf, bufferOf;
39510
39690
  var init_skill_hash = __esm(() => {
39511
39691
  HASH_EXCLUDE_DIRS = new Set([".git", "node_modules", "dist", "build", ".turbo"]);
39512
39692
  HASH_COVERAGE = [
@@ -39531,6 +39711,14 @@ var init_skill_hash = __esm(() => {
39531
39711
  manifestDepth: 64,
39532
39712
  timeoutMs: 5000
39533
39713
  });
39714
+ ContentHashInputError = class ContentHashInputError extends Error {
39715
+ code;
39716
+ constructor(code, message) {
39717
+ super(message);
39718
+ this.code = code;
39719
+ this.name = "ContentHashInputError";
39720
+ }
39721
+ };
39534
39722
  typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
39535
39723
  byteLengthOf = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength").get;
39536
39724
  bufferOf = Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
@@ -43841,7 +44029,7 @@ import { isIP as isIP2 } from "net";
43841
44029
  import { spawnSync } from "child_process";
43842
44030
  import { closeSync, fstatSync, openSync, readFileSync as readFileSync11 } from "fs";
43843
44031
  import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
43844
- import { createRequire } from "module";
44032
+ import { createRequire as createRequire2 } from "module";
43845
44033
  import { hostname as osHostname } from "os";
43846
44034
  import { isAbsolute as isAbsolute3, join as join17 } from "path";
43847
44035
  function envToken(name) {
@@ -43962,7 +44150,7 @@ function readAppConfigFile(path) {
43962
44150
  const uid = process.getuid?.() ?? process.geteuid?.();
43963
44151
  if (uid !== undefined && before2.uid !== uid)
43964
44152
  unsafe("the file is not owned by the current user");
43965
- if (before2.size > MAX_CREDENTIAL_FILE_BYTES)
44153
+ if (before2.size > MAX_CREDENTIAL_FILE_BYTES2)
43966
44154
  unsafe("the file exceeds the size limit");
43967
44155
  const bytes = readFileSync11(fd);
43968
44156
  const after2 = fstatSync(fd);
@@ -44046,13 +44234,13 @@ function sealCredential(fields) {
44046
44234
  configurable: false
44047
44235
  });
44048
44236
  }
44049
- Object.defineProperty(sealed, INSPECT_CUSTOM, {
44237
+ Object.defineProperty(sealed, INSPECT_CUSTOM2, {
44050
44238
  value: () => ({ ...visible, apiKey: "[redacted]" }),
44051
44239
  enumerable: false,
44052
44240
  writable: false,
44053
44241
  configurable: false
44054
44242
  });
44055
- Object.defineProperty(sealed, CREDENTIAL_SEAL, {
44243
+ Object.defineProperty(sealed, CREDENTIAL_SEAL2, {
44056
44244
  value: true,
44057
44245
  enumerable: false,
44058
44246
  writable: false,
@@ -44071,7 +44259,7 @@ function firstEnvValue(env3, keys2) {
44071
44259
  return null;
44072
44260
  }
44073
44261
  function isAmbientEnvironment(env3) {
44074
- return env3 === process.env || env3[AMBIENT_ENVIRONMENT] === true;
44262
+ return env3 === process.env || env3[AMBIENT_ENVIRONMENT2] === true;
44075
44263
  }
44076
44264
  function defaultKeychainRunner(argv) {
44077
44265
  const result2 = spawnSync(KEYCHAIN_SECURITY_BIN, [...argv], {
@@ -44165,7 +44353,7 @@ function snapshotClientEnvironment(name, env3) {
44165
44353
  snapshot[key] = descriptor.value;
44166
44354
  }
44167
44355
  if (ambient) {
44168
- Object.defineProperty(snapshot, AMBIENT_ENVIRONMENT, {
44356
+ Object.defineProperty(snapshot, AMBIENT_ENVIRONMENT2, {
44169
44357
  value: true,
44170
44358
  enumerable: false,
44171
44359
  writable: false,
@@ -44322,7 +44510,7 @@ async function completePointerCredential(name, pointerResolution, env3 = process
44322
44510
  }
44323
44511
  let secretsSdk;
44324
44512
  try {
44325
- secretsSdk = requireSecretsSdk(SECRETS_PACKAGE_SPECIFIER);
44513
+ secretsSdk = requireSecretsSdk2(SECRETS_PACKAGE_SPECIFIER2);
44326
44514
  } catch {
44327
44515
  throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
44328
44516
  }
@@ -44468,7 +44656,7 @@ function toV1BaseUrl(apiUrl) {
44468
44656
  url.pathname = `${path}/v1`;
44469
44657
  return url.toString().replace(/\/+$/, "");
44470
44658
  }
44471
- var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM, CREDENTIAL_SEAL, AMBIENT_ENVIRONMENT, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
44659
+ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES2, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, AMBIENT_ENVIRONMENT2, SECRETS_PACKAGE_SPECIFIER2, requireSecretsSdk2, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2;
44472
44660
  var init_transport = __esm(() => {
44473
44661
  CredentialResolutionError = class CredentialResolutionError extends Error {
44474
44662
  appName;
@@ -44488,21 +44676,21 @@ var init_transport = __esm(() => {
44488
44676
  this.path = path;
44489
44677
  }
44490
44678
  };
44491
- MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
44679
+ MAX_CREDENTIAL_FILE_BYTES2 = 64 * 1024;
44492
44680
  SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
44493
44681
  SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
44494
44682
  ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
44495
44683
  VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
44496
44684
  CREDENTIAL_SHAPED_KEY = /(?:^|_)(?:API_KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)(?:_|$)/;
44497
- INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
44498
- CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
44499
- AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
44500
- SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
44501
- requireSecretsSdk = createRequire(import.meta.url);
44685
+ INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
44686
+ CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
44687
+ AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
44688
+ SECRETS_PACKAGE_SPECIFIER2 = "@hasna/" + "secrets";
44689
+ requireSecretsSdk2 = createRequire2(import.meta.url);
44502
44690
  ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
44503
44691
  DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
44504
- IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
44505
- AUTHORITY_OVERRIDE_HEADERS = new Set([
44692
+ IDEMPOTENT_METHODS2 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
44693
+ AUTHORITY_OVERRIDE_HEADERS2 = new Set([
44506
44694
  "host",
44507
44695
  ":authority",
44508
44696
  "forwarded",
@@ -44634,6 +44822,7 @@ __export(exports_fleet_credentials, {
44634
44822
  skillsCredentialOrReason: () => skillsCredentialOrReason,
44635
44823
  skillsCredentialFiles: () => skillsCredentialFiles,
44636
44824
  skillsCredentialFilePath: () => skillsCredentialFilePath,
44825
+ skillsApiRequestUrl: () => skillsApiRequestUrl,
44637
44826
  selectsSkillsLocalMode: () => selectsSkillsLocalMode,
44638
44827
  resolveSkillsFleet: () => resolveSkillsFleet,
44639
44828
  resolveSkillsConnection: () => resolveSkillsConnection,
@@ -44674,7 +44863,9 @@ function normalizeSkillsApiOrigin(apiUrl) {
44674
44863
  throw new SkillsFleetCredentialError("A Skills API URL must use HTTPS (or loopback HTTP), without credentials, query or fragment", "INVALID_API_URL");
44675
44864
  }
44676
44865
  const pathname = url.pathname.replace(/\/+$/, "");
44677
- if (pathname === "/api" || pathname === "/api/v1") {
44866
+ if (url.origin === "https://api.hasna.com" && pathname === "/skills/v1") {
44867
+ url.pathname = "/skills";
44868
+ } else if (pathname === "/api" || pathname === "/api/v1") {
44678
44869
  url.pathname = "/";
44679
44870
  } else if (pathname.endsWith("/api/v1")) {
44680
44871
  url.pathname = pathname.slice(0, -"/api/v1".length) || "/";
@@ -44683,6 +44874,21 @@ function normalizeSkillsApiOrigin(apiUrl) {
44683
44874
  }
44684
44875
  return url.toString().replace(/\/+$/, "");
44685
44876
  }
44877
+ function skillsApiRequestUrl(apiUrl, route) {
44878
+ const origin = normalizeSkillsApiOrigin(apiUrl);
44879
+ if (!route.startsWith("/api/") || route.includes("#") || route.includes("\\")) {
44880
+ throw new SkillsFleetCredentialError("Invalid Skills API route", "INVALID_API_URL");
44881
+ }
44882
+ if (origin === "https://api.hasna.com/skills") {
44883
+ if (route === "/api/auth/whoami")
44884
+ return `${origin}/v1/auth/whoami`;
44885
+ if (!route.startsWith("/api/v1/")) {
44886
+ throw new SkillsFleetCredentialError("The internal Skills gateway has no established login contract yet. Select an explicitly configured instance with supported authentication.", "GATEWAY_AUTH_UNAVAILABLE");
44887
+ }
44888
+ return `${origin}${route.slice("/api".length)}`;
44889
+ }
44890
+ return `${origin}${route}`;
44891
+ }
44686
44892
  function configuredSkillsApiUrl(env3 = process.env, keychain, profile) {
44687
44893
  const declared = SKILLS_API_URL_ENV_KEYS.filter((key) => env3[key] !== undefined).map((key) => ({ key, value: env3[key] }));
44688
44894
  for (const entry of declared) {
@@ -48903,7 +49109,14 @@ function getConfiguredApiUrl(env3 = process.env) {
48903
49109
  }
48904
49110
  function buildSkillsApiUrl(apiUrl, endpoint = "/skills") {
48905
49111
  const url = new URL(apiUrl);
49112
+ if (url.origin === "https://api.hasna.com" && /^\/skills\/(?:api\/)?v1\/skills\/?$/.test(url.pathname)) {
49113
+ url.pathname = "/skills";
49114
+ apiUrl = url.toString();
49115
+ }
48906
49116
  const cleanEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
49117
+ if (normalizeSkillsApiOrigin(apiUrl) === "https://api.hasna.com/skills") {
49118
+ return skillsApiRequestUrl(apiUrl, `/api/v1${cleanEndpoint}`);
49119
+ }
48907
49120
  const pathname = url.pathname.replace(/\/+$/, "");
48908
49121
  const apiBase = /\/api(?:\/v1)?\/skills$/.test(pathname) ? pathname.slice(0, -"/skills".length) : pathname;
48909
49122
  if (/\/api(?:\/v1)?$/.test(apiBase)) {
@@ -49744,8 +49957,17 @@ function creditCount(value) {
49744
49957
  }
49745
49958
  return value;
49746
49959
  }
49960
+ function runQuoteReceipt(value) {
49961
+ if (value === undefined)
49962
+ return;
49963
+ if (typeof value !== "string" || !value.length || Buffer.byteLength(value, "utf8") > 4096) {
49964
+ throw new Error("Invalid quote receipt");
49965
+ }
49966
+ return value;
49967
+ }
49747
49968
  function parseRemoteRunQuote(value) {
49748
49969
  const quote = object(value);
49970
+ runQuoteReceipt(quote.quoteReceipt);
49749
49971
  const pricing = object(quote.pricing);
49750
49972
  if (typeof quote.skill !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(quote.skill))
49751
49973
  throw new Error("Invalid quoted skill");
@@ -49931,6 +50153,72 @@ function parseUpdatedWorkspace(value) {
49931
50153
  return { organization: { id: organization2.id, slug: organization2.slug, name: organization2.name } };
49932
50154
  }
49933
50155
 
50156
+ // src/lib/remote-quote-errors.ts
50157
+ async function readQuoteUnavailableCode(response) {
50158
+ const maximum = 16 * 1024;
50159
+ const length = response.headers.get("content-length");
50160
+ if (response.status !== 503 || response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() !== "application/json" || length !== null && (!/^\d+$/.test(length) || Number(length) > maximum)) {
50161
+ response.body?.cancel().catch(() => {});
50162
+ return null;
50163
+ }
50164
+ const reader = response.body?.getReader();
50165
+ if (!reader)
50166
+ return null;
50167
+ let timer;
50168
+ let deadlineExceeded = false;
50169
+ const expired = Symbol("quote body deadline");
50170
+ const deadline = new Promise((resolve3) => {
50171
+ timer = setTimeout(() => {
50172
+ deadlineExceeded = true;
50173
+ resolve3(expired);
50174
+ reader.cancel().catch(() => {});
50175
+ }, 1500);
50176
+ });
50177
+ const chunks = [];
50178
+ let size2 = 0;
50179
+ try {
50180
+ while (true) {
50181
+ const next = await Promise.race([reader.read(), deadline]);
50182
+ if (next === expired || deadlineExceeded)
50183
+ return null;
50184
+ if (next.done)
50185
+ break;
50186
+ size2 += next.value.byteLength;
50187
+ if (size2 > maximum)
50188
+ return null;
50189
+ chunks.push(next.value);
50190
+ }
50191
+ const bytes = new Uint8Array(size2);
50192
+ let offset = 0;
50193
+ for (const chunk2 of chunks) {
50194
+ bytes.set(chunk2, offset);
50195
+ offset += chunk2.byteLength;
50196
+ }
50197
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
50198
+ if (!value || typeof value !== "object" || Array.isArray(value))
50199
+ return null;
50200
+ const code = value.code;
50201
+ return typeof code === "string" && Object.hasOwn(quoteUnavailableMessages, code) ? code : null;
50202
+ } catch {
50203
+ return null;
50204
+ } finally {
50205
+ clearTimeout(timer);
50206
+ reader.cancel().catch(() => {});
50207
+ reader.releaseLock();
50208
+ }
50209
+ }
50210
+ var quoteUnavailableMessages;
50211
+ var init_remote_quote_errors = __esm(() => {
50212
+ quoteUnavailableMessages = Object.freeze({
50213
+ HOSTED_PROVIDER_UNAVAILABLE: "Hosted execution is temporarily unavailable on this Skills instance.",
50214
+ HOSTED_CONNECTORS_UNAVAILABLE: "Hosted connector execution is unavailable on this Skills instance.",
50215
+ SKILL_IMPLEMENTATION_UNAVAILABLE: "This skill has no hosted execution implementation.",
50216
+ HOSTED_PRICING_UNAVAILABLE: "Hosted execution is unavailable while this skill's pricing is reviewed.",
50217
+ RUNTIME_ALLOWLIST_REQUIRED: "Hosted execution is unavailable until this Skills instance enables its skill catalog.",
50218
+ RUNTIME_SKILL_NOT_ALLOWED: "This skill is not enabled for hosted execution on this Skills instance."
50219
+ });
50220
+ });
50221
+
49934
50222
  // src/lib/remote-client.ts
49935
50223
  var exports_remote_client = {};
49936
50224
  __export(exports_remote_client, {
@@ -49941,6 +50229,7 @@ __export(exports_remote_client, {
49941
50229
  RemoteSkillsClient: () => RemoteSkillsClient,
49942
50230
  RemoteRouteUnsupportedError: () => RemoteRouteUnsupportedError,
49943
50231
  RemoteRequestError: () => RemoteRequestError,
50232
+ RemoteQuoteUnavailableError: () => RemoteQuoteUnavailableError,
49944
50233
  RemoteCapabilityUnavailableError: () => RemoteCapabilityUnavailableError
49945
50234
  });
49946
50235
 
@@ -49953,7 +50242,7 @@ class RemoteSkillsClient {
49953
50242
  this.apiUrl = normalizeSkillsApiOrigin(apiUrl);
49954
50243
  }
49955
50244
  async request(path, options) {
49956
- return fetch(`${this.apiUrl}${path}`, {
50245
+ return fetch(skillsApiRequestUrl(this.apiUrl, path), {
49957
50246
  ...options,
49958
50247
  redirect: "error",
49959
50248
  credentials: "omit",
@@ -49976,6 +50265,11 @@ class RemoteSkillsClient {
49976
50265
  throw new RemoteRouteUnsupportedError(routePath, response.status, this.apiUrl);
49977
50266
  }
49978
50267
  if (!response.ok) {
50268
+ if (opts.quoteRefusal && options?.method === "POST" && /^\/api\/v1\/skills\/[^/?#]+\/quote$/.test(routePath) && response.status === 503) {
50269
+ const code = await readQuoteUnavailableCode(response);
50270
+ if (code)
50271
+ throw new RemoteQuoteUnavailableError(routePath, code);
50272
+ }
49979
50273
  if (path === "/api/v1/billing/checkout" && options?.method === "POST" && response.status === 503 && await responseBodyCarriesCode(response, ["SUBSCRIPTION_CHECKOUT_UNAVAILABLE"])) {
49980
50274
  throw new RemoteCapabilityUnavailableError;
49981
50275
  }
@@ -50008,6 +50302,7 @@ class RemoteSkillsClient {
50008
50302
  return { status: res.status, body };
50009
50303
  }
50010
50304
  async submitRun(slug, input, args, approval = {}) {
50305
+ const quoteReceipt = runQuoteReceipt(approval.quoteReceipt);
50011
50306
  if (approval.idempotencyKey !== undefined && !/^[A-Za-z0-9._:-]{1,128}$/.test(approval.idempotencyKey))
50012
50307
  throw new Error("Idempotency key must be 1-128 URL-safe characters");
50013
50308
  if (approval.maxCostCents !== undefined)
@@ -50024,16 +50319,21 @@ class RemoteSkillsClient {
50024
50319
  ...approval.maxCredits !== undefined ? { maxCredits: approval.maxCredits } : {},
50025
50320
  ...approval.maxCostCents !== undefined ? { maxCostCents: approval.maxCostCents } : {},
50026
50321
  ...approval.idempotencyKey !== undefined ? { idempotencyKey: approval.idempotencyKey } : {},
50027
- ...approval.inputFiles !== undefined ? { files: approval.inputFiles } : {}
50322
+ ...approval.inputFiles !== undefined ? { files: approval.inputFiles } : {},
50323
+ ...quoteReceipt !== undefined ? { quoteReceipt } : {}
50028
50324
  })
50029
50325
  });
50326
+ if (!res.ok) {
50327
+ res.body?.cancel().catch(() => {});
50328
+ throw new RemoteRequestError(`/api/v1/runs/${encodeURIComponent(slug)}`, res.status);
50329
+ }
50030
50330
  return normalizeRemoteSkillRunContract(await res.json(), slug);
50031
50331
  }
50032
- async quoteRun(slug, input = {}, args = []) {
50332
+ async quoteRun(slug, input = {}, args = [], files) {
50033
50333
  const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/quote`, {
50034
50334
  method: "POST",
50035
- body: JSON.stringify({ input, args })
50036
- });
50335
+ body: JSON.stringify({ input, args, ...files === undefined ? {} : { files } })
50336
+ }, { quoteRefusal: true });
50037
50337
  return parseRemoteRunQuote(await response.json());
50038
50338
  }
50039
50339
  getCapabilities() {
@@ -50048,17 +50348,24 @@ class RemoteSkillsClient {
50048
50348
  return this.capabilities;
50049
50349
  }
50050
50350
  async submitQuotedRun(slug, input = {}, args = [], approval = {}) {
50351
+ runQuoteReceipt(approval.quoteReceipt);
50352
+ ({ input, args, approval } = JSON.parse(JSON.stringify({ input, args, approval })));
50051
50353
  const maximum = creditCount(approval.maxCredits ?? approval.maxCostCents ?? 0);
50052
50354
  if (approval.maxCostCents !== undefined && approval.maxCostCents !== maximum)
50053
50355
  throw new Error("Credit approval fields disagree");
50054
- const quote = await this.quoteRun(slug, input, args);
50055
- if (quote.pricing.costCents > maximum)
50356
+ const quote = approval.quoteReceipt === undefined ? await this.quoteRun(slug, input, args, approval.inputFiles?.length ? approval.inputFiles : undefined) : undefined;
50357
+ if (quote && quote.pricing.costCents > maximum)
50056
50358
  throw new RemoteCreditApprovalError(quote.pricing.costCents, maximum);
50057
50359
  const capabilities = await this.getCapabilities();
50058
50360
  if (!capabilities.capabilities.includes("runs.submit") || capabilities.billing?.boundedRunApproval !== true || capabilities.billing.unit !== "credits") {
50059
50361
  throw new Error("The configured server does not support bounded credit approval; refusing remote submission");
50060
50362
  }
50061
- return this.submitRun(quote.skill, input, args, { ...approval, maxCredits: maximum, maxCostCents: maximum });
50363
+ return this.submitRun(quote?.skill ?? slug, input, args, {
50364
+ ...approval,
50365
+ maxCredits: maximum,
50366
+ maxCostCents: maximum,
50367
+ ...(quote?.quoteReceipt ?? approval.quoteReceipt) === undefined ? {} : { quoteReceipt: quote?.quoteReceipt ?? approval.quoteReceipt }
50368
+ });
50062
50369
  }
50063
50370
  async getIdentity() {
50064
50371
  return (await this.requestNewRoute("/api/auth/whoami")).json();
@@ -50346,6 +50653,10 @@ class RemoteSkillsClient {
50346
50653
  return { id: artifactId, fileName: String(artifact.fileName ?? artifactId), bytes, byteSize: bytes.byteLength, sha256: artifact.sha256 };
50347
50654
  }
50348
50655
  async submitQuotedRunWithFiles(slug, input, args, files, approval = {}) {
50656
+ runQuoteReceipt(approval.quoteReceipt);
50657
+ ({ input, args, approval } = JSON.parse(JSON.stringify({ input, args, approval })));
50658
+ describeRemoteFiles(files);
50659
+ files = files.map((file) => ({ name: file.name, contentType: file.contentType, bytes: new Uint8Array(file.bytes) }));
50349
50660
  const inputFiles = describeRemoteFiles(files);
50350
50661
  if (files.length && !(await this.getCapabilities()).capabilities.includes("runs.uploads"))
50351
50662
  throw new Error("The configured server does not support input uploads");
@@ -50409,7 +50720,7 @@ class RemoteSkillsClient {
50409
50720
  const headers = { Authorization: `Bearer ${this.apiKey}` };
50410
50721
  if (ifMatch)
50411
50722
  headers["If-Match"] = ifMatch;
50412
- return fetch(`${this.apiUrl}/api/v1/skills`, {
50723
+ return fetch(skillsApiRequestUrl(this.apiUrl, "/api/v1/skills"), {
50413
50724
  method: "POST",
50414
50725
  headers,
50415
50726
  body: form,
@@ -50632,7 +50943,7 @@ async function createRemoteSkillsClient(env3 = process.env) {
50632
50943
  function createRemoteSkillsClientReadOnly(env3 = process.env) {
50633
50944
  return createRemoteSkillsClient(env3);
50634
50945
  }
50635
- var RemoteRouteUnsupportedError, RemoteRequestError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
50946
+ var RemoteRouteUnsupportedError, RemoteRequestError, RemoteQuoteUnavailableError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
50636
50947
  var init_remote_client = __esm(() => {
50637
50948
  init_remote_invitations();
50638
50949
  init_remote_workspace_leave();
@@ -50643,6 +50954,7 @@ var init_remote_client = __esm(() => {
50643
50954
  init_fleet_credentials();
50644
50955
  init_remote_account();
50645
50956
  init_remote_files();
50957
+ init_remote_quote_errors();
50646
50958
  RemoteRouteUnsupportedError = class RemoteRouteUnsupportedError extends Error {
50647
50959
  path;
50648
50960
  status;
@@ -50665,6 +50977,17 @@ var init_remote_client = __esm(() => {
50665
50977
  this.name = "RemoteRequestError";
50666
50978
  }
50667
50979
  };
50980
+ RemoteQuoteUnavailableError = class RemoteQuoteUnavailableError extends RemoteRequestError {
50981
+ code;
50982
+ constructor(path, code) {
50983
+ super(path, 503);
50984
+ this.code = code;
50985
+ if (!Object.hasOwn(quoteUnavailableMessages, code))
50986
+ throw new Error("Unknown quote refusal code");
50987
+ this.name = "RemoteQuoteUnavailableError";
50988
+ this.message = quoteUnavailableMessages[code];
50989
+ }
50990
+ };
50668
50991
  RemoteWorkspaceMemberError = class RemoteWorkspaceMemberError extends RemoteRequestError {
50669
50992
  code;
50670
50993
  constructor(path, code) {
@@ -50720,6 +51043,7 @@ var init_revision = __esm(() => {
50720
51043
  import { createHash as createHash4 } from "crypto";
50721
51044
  import { readFileSync as readFileSync13, readdirSync as readdirSync9, statSync as statSync10 } from "fs";
50722
51045
  import { join as join19, relative as relative2 } from "path";
51046
+ import { createGunzip } from "zlib";
50723
51047
  function isDotenvFile(lower) {
50724
51048
  if (lower === ".env" || lower.startsWith(".env."))
50725
51049
  return true;
@@ -50922,6 +51246,88 @@ function concat2(chunks) {
50922
51246
  function invalidBundle(message) {
50923
51247
  throw new SkillBundleInspectionError("BUNDLE_INVALID", message);
50924
51248
  }
51249
+ function inspectionLimits(options) {
51250
+ const limits = { ...SKILL_BUNDLE_INSPECTION_LIMITS };
51251
+ for (const key of Object.keys(options.limits ?? {})) {
51252
+ if (!Object.hasOwn(limits, key))
51253
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Unknown bundle limit");
51254
+ const field = key;
51255
+ const value = options.limits[field];
51256
+ if (!Number.isSafeInteger(value) || value <= 0 || value > limits[field]) {
51257
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle limits must be positive integers within the hard ceilings");
51258
+ }
51259
+ limits[field] = value;
51260
+ }
51261
+ return limits;
51262
+ }
51263
+ async function inspectSkillBundle(bundle, options = {}) {
51264
+ const signal = options.signal;
51265
+ const limits = inspectionLimits(options);
51266
+ const deadline = performance.now() + limits.timeoutMs;
51267
+ const check = () => {
51268
+ if (signal?.aborted)
51269
+ throw new SkillBundleInspectionError("BUNDLE_ABORTED", "Bundle inspection aborted");
51270
+ if (performance.now() >= deadline)
51271
+ throw new SkillBundleInspectionError("BUNDLE_TIMEOUT", "Bundle inspection deadline exceeded");
51272
+ };
51273
+ check();
51274
+ if (bundle.byteLength > limits.compressedBytes)
51275
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Compressed bundle exceeds byte limit");
51276
+ const snapshot = ownBytes(bundle);
51277
+ check();
51278
+ const sha2562 = sha256Hex(snapshot);
51279
+ check();
51280
+ const parser = new BoundedTarReader(limits, check);
51281
+ const streamOptions = { chunkSize: 16 * 1024, highWaterMark: 16 * 1024 };
51282
+ const decoder = createGunzip(streamOptions);
51283
+ let terminalError;
51284
+ const stop = (code) => {
51285
+ terminalError ??= new SkillBundleInspectionError(code, code === "BUNDLE_ABORTED" ? "Bundle inspection aborted" : "Bundle inspection deadline exceeded");
51286
+ decoder.destroy(terminalError);
51287
+ };
51288
+ const onAbort = () => stop("BUNDLE_ABORTED");
51289
+ const timer = setTimeout(() => stop("BUNDLE_TIMEOUT"), Math.max(1, deadline - performance.now()));
51290
+ signal?.addEventListener("abort", onAbort, { once: true });
51291
+ let decompressedByteSize = 0;
51292
+ let bytesSinceYield = 0;
51293
+ try {
51294
+ check();
51295
+ decoder.end(snapshot);
51296
+ for await (const chunk2 of decoder) {
51297
+ check();
51298
+ decompressedByteSize += chunk2.byteLength;
51299
+ if (decompressedByteSize > limits.decompressedBytes)
51300
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Decompressed bundle exceeds byte limit");
51301
+ parser.push(chunk2);
51302
+ bytesSinceYield += chunk2.byteLength;
51303
+ if (bytesSinceYield >= 256 * 1024) {
51304
+ await new Promise((resolve3) => setTimeout(resolve3, 0));
51305
+ bytesSinceYield = 0;
51306
+ check();
51307
+ }
51308
+ }
51309
+ check();
51310
+ const entries = parser.finish();
51311
+ return {
51312
+ entries,
51313
+ sha256: sha2562,
51314
+ compressedByteSize: snapshot.byteLength,
51315
+ decompressedByteSize,
51316
+ unpackedByteSize: parser.fileBytes,
51317
+ fileCount: entries.length
51318
+ };
51319
+ } catch (error) {
51320
+ if (terminalError)
51321
+ throw terminalError;
51322
+ if (error instanceof SkillBundleInspectionError)
51323
+ throw error;
51324
+ throw new SkillBundleInspectionError("BUNDLE_INVALID", "Invalid or truncated gzip bundle");
51325
+ } finally {
51326
+ clearTimeout(timer);
51327
+ signal?.removeEventListener("abort", onAbort);
51328
+ decoder.destroy();
51329
+ }
51330
+ }
50925
51331
 
50926
51332
  class BoundedTarReader {
50927
51333
  limits;
@@ -53795,7 +54201,7 @@ var init_init = __esm(() => {
53795
54201
  });
53796
54202
 
53797
54203
  // src/lib/home-adoption.ts
53798
- import { existsSync as existsSync22, mkdirSync as mkdirSync9, readdirSync as readdirSync10, readFileSync as readFileSync18, rmSync as rmSync6, statSync as statSync11, writeFileSync as writeFileSync10 } from "fs";
54204
+ import { constants as constants3, closeSync as closeSync3, fstatSync as fstatSync3, lstatSync as lstatSync5, openSync as openSync3, readSync as readSync2, existsSync as existsSync22, mkdirSync as mkdirSync9, readdirSync as readdirSync10, readFileSync as readFileSync18, rmSync as rmSync6, statSync as statSync11, writeFileSync as writeFileSync10 } from "fs";
53799
54205
  import { homedir as homedir5 } from "os";
53800
54206
  import { join as join24 } from "path";
53801
54207
  function indexCanonicalCorpus(corpusRoot) {
@@ -53947,6 +54353,41 @@ function adoptUnmarkedHomes(options = {}) {
53947
54353
  }
53948
54354
  return { ...scan, applied: true, rollbackFile };
53949
54355
  }
54356
+ function pruneOwnership(dir) {
54357
+ let descriptor;
54358
+ try {
54359
+ const directory = lstatSync5(dir);
54360
+ if (!directory.isDirectory())
54361
+ return;
54362
+ const markerPath = join24(dir, SYNC_MARKER_FILE), markerPathBefore = lstatSync5(markerPath);
54363
+ if (!markerPathBefore.isFile())
54364
+ return;
54365
+ descriptor = openSync3(markerPath, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
54366
+ const before2 = fstatSync3(descriptor);
54367
+ if (!before2.isFile() || before2.dev !== markerPathBefore.dev || before2.ino !== markerPathBefore.ino || before2.size !== markerPathBefore.size || before2.mtimeMs !== markerPathBefore.mtimeMs || before2.ctimeMs !== markerPathBefore.ctimeMs || before2.size < 1 || before2.size > 65536)
54368
+ return;
54369
+ const bytes = Buffer.alloc(before2.size + 1);
54370
+ let length = 0;
54371
+ while (length < bytes.length) {
54372
+ const count = readSync2(descriptor, bytes, length, bytes.length - length, length);
54373
+ if (!count)
54374
+ break;
54375
+ length += count;
54376
+ }
54377
+ const after2 = fstatSync3(descriptor), current = lstatSync5(dir), markerPathAfter = lstatSync5(markerPath);
54378
+ if (!markerPathAfter.isFile() || markerPathAfter.dev !== before2.dev || markerPathAfter.ino !== before2.ino || markerPathAfter.size !== before2.size || markerPathAfter.mtimeMs !== before2.mtimeMs || markerPathAfter.ctimeMs !== before2.ctimeMs || length !== before2.size || after2.size !== before2.size || after2.mtimeMs !== before2.mtimeMs || after2.ctimeMs !== before2.ctimeMs || !current.isDirectory() || current.dev !== directory.dev || current.ino !== directory.ino)
54379
+ return;
54380
+ const text2 = bytes.subarray(0, length).toString("utf8"), marker = JSON.parse(text2);
54381
+ if (!isSkillsOwnershipMarker(marker))
54382
+ return;
54383
+ return { marker, identity: JSON.stringify([directory.dev, directory.ino, before2.dev, before2.ino, before2.mtimeMs, before2.ctimeMs, text2]) };
54384
+ } catch {
54385
+ return;
54386
+ } finally {
54387
+ if (descriptor !== undefined)
54388
+ closeSync3(descriptor);
54389
+ }
54390
+ }
53950
54391
  function pruneStrayHomes(options = {}) {
53951
54392
  const homeDir2 = options.homeDir ?? homedir5();
53952
54393
  const corpusRoot = resolveCorpusRoot(options);
@@ -53954,6 +54395,7 @@ function pruneStrayHomes(options = {}) {
53954
54395
  const agents = options.agents?.length ? options.agents : [...SYNC_AGENTS];
53955
54396
  const names = selectedHomeNames(options, index, homeDir2, agents);
53956
54397
  const candidates = [];
54398
+ const ownership = new Map;
53957
54399
  for (const agent of agents) {
53958
54400
  const home = agentGlobalSkillsDir(agent, homeDir2);
53959
54401
  if (!existsSync22(home))
@@ -53976,20 +54418,13 @@ function pruneStrayHomes(options = {}) {
53976
54418
  } catch {
53977
54419
  continue;
53978
54420
  }
53979
- const markerPath = join24(dir, SYNC_MARKER_FILE);
53980
- if (!existsSync22(markerPath))
53981
- continue;
53982
54421
  if (index.has(skill))
53983
54422
  continue;
53984
- let marker;
53985
- try {
53986
- const parsed = JSON.parse(readFileSync18(markerPath, "utf-8"));
53987
- if (!parsed || typeof parsed !== "object" || parsed.managedBy !== SYNC_MARKER_MANAGED_BY)
53988
- continue;
53989
- marker = parsed;
53990
- } catch {
54423
+ const owned = pruneOwnership(dir);
54424
+ if (!owned)
53991
54425
  continue;
53992
- }
54426
+ const marker = owned.marker;
54427
+ ownership.set(dir, owned.identity);
53993
54428
  const skillMdPath = join24(dir, "SKILL.md");
53994
54429
  const hash = existsSync22(skillMdPath) ? hashSkillMarkdownFile(skillMdPath) : "";
53995
54430
  candidates.push({ agent, skill, home, path: dir, hash, marker });
@@ -54000,10 +54435,14 @@ function pruneStrayHomes(options = {}) {
54000
54435
  }
54001
54436
  const appDir = options.homeDir ? join24(options.homeDir, ".hasna", "skills") : getDataDir();
54002
54437
  const rollbackFile = writeRollbackRecord("prune", candidates.map(({ agent, skill, path, hash, marker }) => ({ agent, skill, path, hash, marker })), appDir);
54438
+ let pruned = 0;
54003
54439
  for (const candidate of candidates) {
54440
+ if (pruneOwnership(candidate.path)?.identity !== ownership.get(candidate.path))
54441
+ continue;
54004
54442
  rmSync6(candidate.path, { recursive: true, force: true });
54443
+ pruned++;
54005
54444
  }
54006
- return { candidates, pruned: candidates.length, dryRun: false, rollbackFile };
54445
+ return { candidates, pruned, dryRun: false, rollbackFile };
54007
54446
  }
54008
54447
  var CONFLICTS_LEDGER_FILE = "conflicts.json", ROLLBACK_DIRNAME = "rollback";
54009
54448
  var init_home_adoption = __esm(() => {
@@ -54131,7 +54570,7 @@ var init_home_census = __esm(() => {
54131
54570
  });
54132
54571
 
54133
54572
  // src/cli/env-assignment.ts
54134
- import { closeSync as closeSync3, constants as constants3, fstatSync as fstatSync3, ftruncateSync, lstatSync as lstatSync5, openSync as openSync3, readFileSync as readFileSync20, writeSync } from "fs";
54573
+ import { closeSync as closeSync4, constants as constants4, fstatSync as fstatSync4, ftruncateSync, lstatSync as lstatSync6, openSync as openSync4, readFileSync as readFileSync20, writeSync } from "fs";
54135
54574
  function serializeValue(value) {
54136
54575
  if (controls.test(value))
54137
54576
  throw new EnvAssignmentError("Environment values cannot contain control characters or newlines.");
@@ -54210,7 +54649,7 @@ function setEnvAssignment(path, assignment) {
54210
54649
  try {
54211
54650
  const previous = (() => {
54212
54651
  try {
54213
- return lstatSync5(path);
54652
+ return lstatSync6(path);
54214
54653
  } catch (error) {
54215
54654
  if (error.code === "ENOENT")
54216
54655
  return;
@@ -54219,8 +54658,8 @@ function setEnvAssignment(path, assignment) {
54219
54658
  })();
54220
54659
  if (previous && !previous.isFile())
54221
54660
  throw new EnvAssignmentError("The project .env must be a regular file, not a symlink or special file.");
54222
- descriptor = openSync3(path, previous ? constants3.O_RDWR | constants3.O_NOFOLLOW | constants3.O_NONBLOCK : constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | constants3.O_NOFOLLOW, 384);
54223
- const opened = fstatSync3(descriptor);
54661
+ descriptor = openSync4(path, previous ? constants4.O_RDWR | constants4.O_NOFOLLOW | constants4.O_NONBLOCK : constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL | constants4.O_NOFOLLOW, 384);
54662
+ const opened = fstatSync4(descriptor);
54224
54663
  if (!opened.isFile() || previous && (opened.ino !== previous.ino || opened.dev !== previous.dev)) {
54225
54664
  throw new EnvAssignmentError("The project .env changed while opening it; retry with a regular file.");
54226
54665
  }
@@ -54247,7 +54686,7 @@ function setEnvAssignment(path, assignment) {
54247
54686
  throw new EnvAssignmentError("Cannot safely read or write the project .env file.");
54248
54687
  } finally {
54249
54688
  if (descriptor !== undefined)
54250
- closeSync3(descriptor);
54689
+ closeSync4(descriptor);
54251
54690
  }
54252
54691
  }
54253
54692
  var INVALID_LAYOUT = "Cannot safely update .env with multiline or unsupported assignments. Use single-line KEY=VALUE entries or edit the file manually.", controls, EnvAssignmentError;
@@ -63446,11 +63885,11 @@ var require_codegen = __commonJS((exports) => {
63446
63885
  const rhs = this.rhs === undefined ? "" : ` = ${this.rhs}`;
63447
63886
  return `${varKind} ${this.name}${rhs};` + _n;
63448
63887
  }
63449
- optimizeNames(names, constants4) {
63888
+ optimizeNames(names, constants5) {
63450
63889
  if (!names[this.name.str])
63451
63890
  return;
63452
63891
  if (this.rhs)
63453
- this.rhs = optimizeExpr(this.rhs, names, constants4);
63892
+ this.rhs = optimizeExpr(this.rhs, names, constants5);
63454
63893
  return this;
63455
63894
  }
63456
63895
  get names() {
@@ -63468,10 +63907,10 @@ var require_codegen = __commonJS((exports) => {
63468
63907
  render({ _n }) {
63469
63908
  return `${this.lhs} = ${this.rhs};` + _n;
63470
63909
  }
63471
- optimizeNames(names, constants4) {
63910
+ optimizeNames(names, constants5) {
63472
63911
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
63473
63912
  return;
63474
- this.rhs = optimizeExpr(this.rhs, names, constants4);
63913
+ this.rhs = optimizeExpr(this.rhs, names, constants5);
63475
63914
  return this;
63476
63915
  }
63477
63916
  get names() {
@@ -63537,8 +63976,8 @@ var require_codegen = __commonJS((exports) => {
63537
63976
  optimizeNodes() {
63538
63977
  return `${this.code}` ? this : undefined;
63539
63978
  }
63540
- optimizeNames(names, constants4) {
63541
- this.code = optimizeExpr(this.code, names, constants4);
63979
+ optimizeNames(names, constants5) {
63980
+ this.code = optimizeExpr(this.code, names, constants5);
63542
63981
  return this;
63543
63982
  }
63544
63983
  get names() {
@@ -63568,12 +64007,12 @@ var require_codegen = __commonJS((exports) => {
63568
64007
  }
63569
64008
  return nodes.length > 0 ? this : undefined;
63570
64009
  }
63571
- optimizeNames(names, constants4) {
64010
+ optimizeNames(names, constants5) {
63572
64011
  const { nodes } = this;
63573
64012
  let i = nodes.length;
63574
64013
  while (i--) {
63575
64014
  const n = nodes[i];
63576
- if (n.optimizeNames(names, constants4))
64015
+ if (n.optimizeNames(names, constants5))
63577
64016
  continue;
63578
64017
  subtractNames(names, n.names);
63579
64018
  nodes.splice(i, 1);
@@ -63630,12 +64069,12 @@ var require_codegen = __commonJS((exports) => {
63630
64069
  return;
63631
64070
  return this;
63632
64071
  }
63633
- optimizeNames(names, constants4) {
64072
+ optimizeNames(names, constants5) {
63634
64073
  var _a;
63635
- this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants4);
63636
- if (!(super.optimizeNames(names, constants4) || this.else))
64074
+ this.else = (_a = this.else) === null || _a === undefined ? undefined : _a.optimizeNames(names, constants5);
64075
+ if (!(super.optimizeNames(names, constants5) || this.else))
63637
64076
  return;
63638
- this.condition = optimizeExpr(this.condition, names, constants4);
64077
+ this.condition = optimizeExpr(this.condition, names, constants5);
63639
64078
  return this;
63640
64079
  }
63641
64080
  get names() {
@@ -63660,10 +64099,10 @@ var require_codegen = __commonJS((exports) => {
63660
64099
  render(opts) {
63661
64100
  return `for(${this.iteration})` + super.render(opts);
63662
64101
  }
63663
- optimizeNames(names, constants4) {
63664
- if (!super.optimizeNames(names, constants4))
64102
+ optimizeNames(names, constants5) {
64103
+ if (!super.optimizeNames(names, constants5))
63665
64104
  return;
63666
- this.iteration = optimizeExpr(this.iteration, names, constants4);
64105
+ this.iteration = optimizeExpr(this.iteration, names, constants5);
63667
64106
  return this;
63668
64107
  }
63669
64108
  get names() {
@@ -63701,10 +64140,10 @@ var require_codegen = __commonJS((exports) => {
63701
64140
  render(opts) {
63702
64141
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
63703
64142
  }
63704
- optimizeNames(names, constants4) {
63705
- if (!super.optimizeNames(names, constants4))
64143
+ optimizeNames(names, constants5) {
64144
+ if (!super.optimizeNames(names, constants5))
63706
64145
  return;
63707
- this.iterable = optimizeExpr(this.iterable, names, constants4);
64146
+ this.iterable = optimizeExpr(this.iterable, names, constants5);
63708
64147
  return this;
63709
64148
  }
63710
64149
  get names() {
@@ -63749,11 +64188,11 @@ var require_codegen = __commonJS((exports) => {
63749
64188
  (_b = this.finally) === null || _b === undefined || _b.optimizeNodes();
63750
64189
  return this;
63751
64190
  }
63752
- optimizeNames(names, constants4) {
64191
+ optimizeNames(names, constants5) {
63753
64192
  var _a, _b;
63754
- super.optimizeNames(names, constants4);
63755
- (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants4);
63756
- (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants4);
64193
+ super.optimizeNames(names, constants5);
64194
+ (_a = this.catch) === null || _a === undefined || _a.optimizeNames(names, constants5);
64195
+ (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants5);
63757
64196
  return this;
63758
64197
  }
63759
64198
  get names() {
@@ -64027,7 +64466,7 @@ var require_codegen = __commonJS((exports) => {
64027
64466
  function addExprNames(names, from) {
64028
64467
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
64029
64468
  }
64030
- function optimizeExpr(expr, names, constants4) {
64469
+ function optimizeExpr(expr, names, constants5) {
64031
64470
  if (expr instanceof code_1.Name)
64032
64471
  return replaceName(expr);
64033
64472
  if (!canOptimize(expr))
@@ -64042,14 +64481,14 @@ var require_codegen = __commonJS((exports) => {
64042
64481
  return items;
64043
64482
  }, []));
64044
64483
  function replaceName(n) {
64045
- const c = constants4[n.str];
64484
+ const c = constants5[n.str];
64046
64485
  if (c === undefined || names[n.str] !== 1)
64047
64486
  return n;
64048
64487
  delete names[n.str];
64049
64488
  return c;
64050
64489
  }
64051
64490
  function canOptimize(e) {
64052
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants4[c.str] !== undefined);
64491
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants5[c.str] !== undefined);
64053
64492
  }
64054
64493
  }
64055
64494
  function subtractNames(names, from) {
@@ -71665,7 +72104,7 @@ var MCP_CONTRACT_SCHEMA_VERSION = 1, stringSchema = (description) => ({
71665
72104
  type: "array",
71666
72105
  items,
71667
72106
  ...description ? { description } : {}
71668
- }), skillNameInput, optionalAgentInput, scopeInput, runInputSchema, runArgsSchema, errorSchema, skillSummarySchema, toolPrimitiveSummarySchema, skillToolDependencySchema, validationMessageSchema, validationOutputSchema, installOutputSchema, runOutputSchema, toolContracts, remoteCustomerContracts, contracts, resourceContracts;
72107
+ }), skillNameInput, optionalAgentInput, scopeInput, runInputSchema, runArgsSchema, errorSchema, skillSummarySchema, toolPrimitiveSummarySchema, skillToolDependencySchema, validationMessageSchema, validationOutputSchema, installOutputSchema, runOutputSchema, toolContracts, remoteCustomerContracts, publicationUuidSchema, publicationVerification, privatePublicationContracts, contracts, resourceContracts;
71669
72108
  var init_mcp_contracts = __esm(() => {
71670
72109
  init_remote_customer_operations();
71671
72110
  skillNameInput = stringSchema("skill name or alias.");
@@ -72171,7 +72610,7 @@ var init_mcp_contracts = __esm(() => {
72171
72610
  name: "run_skill",
72172
72611
  title: "Run Skill",
72173
72612
  description: "Run a skill locally or through a configured remote runner. Returns compact stdout/stderr previews and run summaries by default; pass detail:true for full records.",
72174
- params: ["name", "input?", "args?", "detail?", "remote?", "maxCredits?", "maxCostCents?", "idempotency_key?", "files?"],
72613
+ params: ["name", "input?", "args?", "detail?", "remote?", "maxCredits?", "maxCostCents?", "quoteReceipt?", "idempotency_key?", "files?"],
72175
72614
  category: "execution",
72176
72615
  sideEffects: "local-process-or-remote-run",
72177
72616
  stable: true,
@@ -72183,6 +72622,7 @@ var init_mcp_contracts = __esm(() => {
72183
72622
  remote: { type: "boolean", description: "Use the configured server catalog." },
72184
72623
  maxCredits: { type: "integer", minimum: 0, description: "Maximum explicitly approved integer credits; omitted permits only free remote runs." },
72185
72624
  maxCostCents: { type: "integer", minimum: 0, description: "Legacy alias for maxCredits; both must agree." },
72625
+ quoteReceipt: { type: "string", minLength: 1, maxLength: 4096, description: "Opaque approved quote receipt, at most 4096 UTF-8 bytes. Preserve it and the quoted input/args unchanged; never refresh after confirmation." },
72186
72626
  idempotency_key: { type: "string", pattern: "^[A-Za-z0-9._:-]{1,128}$", description: "Stable retry key for the same remote submission." },
72187
72627
  files: { type: "array", maxItems: 10, items: objectSchema({ name: stringSchema("Safe basename"), base64: { type: "string", maxLength: 1398104 }, contentType: stringSchema("MIME type") }, ["name", "base64"]), description: "Inline remote inputs, at most 1 MiB combined." }
72188
72628
  }, ["name"]),
@@ -72466,12 +72906,17 @@ var init_mcp_contracts = __esm(() => {
72466
72906
  name: "quote_skill",
72467
72907
  title: "Quote Remote Skill",
72468
72908
  description: "Get a server credit quote without submitting a run.",
72469
- params: ["name", "input?", "args?"],
72909
+ params: ["name", "input?", "args?", "files?"],
72470
72910
  category: "execution",
72471
72911
  sideEffects: "none",
72472
72912
  stable: true,
72473
- inputSchema: objectSchema({ name: skillNameInput, input: runInputSchema, args: runArgsSchema }, ["name"]),
72474
- outputSchema: objectSchema({ skill: stringSchema("Canonical server skill."), pricing: objectSchema({}, [], "Quoted integer credits.", true) }, ["skill", "pricing"], undefined, true)
72913
+ inputSchema: objectSchema({
72914
+ name: skillNameInput,
72915
+ input: runInputSchema,
72916
+ args: runArgsSchema,
72917
+ files: { type: "array", maxItems: 10, items: objectSchema({ name: stringSchema("Safe basename"), base64: { type: "string", maxLength: 1398104 }, contentType: stringSchema("MIME type") }, ["name", "base64"]), description: "Same inline files to submit after approval, at most 1 MiB combined." }
72918
+ }, ["name"]),
72919
+ outputSchema: objectSchema({ skill: stringSchema("Canonical server skill."), pricing: objectSchema({}, [], "Quoted integer credits.", true), quoteReceipt: { type: "string", minLength: 1, maxLength: 4096, description: "Opaque server quote binding, at most 4096 UTF-8 bytes; preserve verbatim for approval." } }, ["skill", "pricing"], undefined, true)
72475
72920
  });
72476
72921
  remoteCustomerContracts.push({
72477
72922
  name: "download_run_artifact",
@@ -72484,7 +72929,47 @@ var init_mcp_contracts = __esm(() => {
72484
72929
  inputSchema: objectSchema({ run_id: stringSchema("Run identifier."), artifact_id: stringSchema("Artifact identifier.") }, ["run_id", "artifact_id"]),
72485
72930
  outputSchema: objectSchema({ id: stringSchema("Artifact identifier."), fileName: stringSchema("Artifact file name."), base64: stringSchema("Verified bytes."), sha256: stringSchema("SHA256 digest."), byteSize: { type: "integer", minimum: 0 } }, ["id", "fileName", "base64", "sha256", "byteSize"])
72486
72931
  });
72487
- contracts = [...toolContracts, ...remoteCustomerContracts].sort((a, b) => a.name.localeCompare(b.name));
72932
+ publicationUuidSchema = { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" };
72933
+ publicationVerification = {
72934
+ email: { type: "string", format: "email", maxLength: 254 },
72935
+ code: { type: "string", pattern: "^\\d{6}$" },
72936
+ userId: publicationUuidSchema,
72937
+ membershipId: publicationUuidSchema,
72938
+ recoveryDirectory: { type: "string", maxLength: 4096, description: "Absolute host-local recovery directory without symbolic links." }
72939
+ };
72940
+ privatePublicationContracts = [
72941
+ { name: "publish_private_skill", title: "Publish private skill", extras: {
72942
+ directory: { type: "string", maxLength: 4096, description: "Absolute local skill source directory." },
72943
+ skillId: publicationUuidSchema,
72944
+ expectedCurrentVersionId: { oneOf: [publicationUuidSchema, { type: "null" }] },
72945
+ idempotencyKey: publicationUuidSchema,
72946
+ confirm: { const: true },
72947
+ waitMs: { type: "integer", minimum: 0, maximum: 300000 }
72948
+ }, required: ["directory", "skillId", "expectedCurrentVersionId", "confirm"] },
72949
+ { name: "get_private_publication", title: "Get private publication", extras: {}, required: [] },
72950
+ { name: "resume_private_publication", title: "Resume private publication", extras: { confirm: { const: true }, waitMs: { type: "integer", minimum: 0, maximum: 300000 } }, required: ["confirm"] },
72951
+ { name: "cancel_private_publication", title: "Cancel private publication", extras: { confirm: { const: true } }, required: ["confirm"] }
72952
+ ].map((operation) => ({
72953
+ name: operation.name,
72954
+ title: operation.title,
72955
+ description: "Manage private source publication with fresh workspace verification and durable host-local recovery. Upload consent and current version comparison are explicit; private execution remains unavailable.",
72956
+ params: [...Object.keys(publicationVerification), ...Object.keys(operation.extras)],
72957
+ category: "storage",
72958
+ sideEffects: "filesystem",
72959
+ stable: true,
72960
+ inputSchema: objectSchema({ ...publicationVerification, ...operation.extras }, [...Object.keys(publicationVerification), ...operation.required]),
72961
+ outputSchema: objectSchema({
72962
+ recoveryDirectory: { type: "string" },
72963
+ skillId: publicationUuidSchema,
72964
+ intentId: { oneOf: [publicationUuidSchema, { type: "null" }] },
72965
+ state: { type: "string" },
72966
+ versionId: { oneOf: [publicationUuidSchema, { type: "null" }] },
72967
+ committed: { type: "boolean" },
72968
+ executionEnabled: { const: false },
72969
+ nextAction: { type: "string" }
72970
+ }, ["recoveryDirectory", "skillId", "intentId", "state", "versionId", "committed", "executionEnabled", "nextAction"])
72971
+ }));
72972
+ contracts = [...toolContracts, ...remoteCustomerContracts, ...privatePublicationContracts].sort((a, b) => a.name.localeCompare(b.name));
72488
72973
  resourceContracts = [
72489
72974
  {
72490
72975
  uri: "skills://mcp/contracts",
@@ -72999,11 +73484,12 @@ function registerOperationTools(server) {
72999
73484
  detail: exports_external.boolean().optional(),
73000
73485
  maxCostCents: exports_external.number().int().min(0).max(2147483647).optional().describe("Maximum integer credits explicitly approved by the user for a remote run; omitted permits only free runs"),
73001
73486
  maxCredits: exports_external.number().int().min(0).max(2147483647).optional(),
73487
+ quoteReceipt: exports_external.string().min(1).max(4096).refine((value) => Buffer.byteLength(value, "utf8") <= 4096, "Quote receipt exceeds 4096 UTF-8 bytes").optional().describe("Opaque receipt from the approved quote; send unchanged with the same input, args and files. Never refresh after confirmation."),
73002
73488
  remote: exports_external.boolean().optional().describe("Use the configured server catalog, including skills not installed locally"),
73003
73489
  idempotency_key: exports_external.string().regex(/^[A-Za-z0-9._:-]{1,128}$/).optional().describe("Reuse for the same approved submission after an interrupted response"),
73004
73490
  files: exports_external.array(exports_external.object({ name: exports_external.string(), base64: exports_external.string().max(1398104), contentType: exports_external.string().optional() })).max(10).optional().describe("Inline remote inputs, at most 1 MiB combined; use CLI or SDK for larger files")
73005
73491
  }
73006
- }, async ({ name, input, args, detail, maxCostCents, maxCredits, remote, idempotency_key, files }) => {
73492
+ }, async ({ name, input, args, detail, maxCostCents, maxCredits, quoteReceipt, remote, idempotency_key, files }) => {
73007
73493
  const skill = remote ? { name, serverOwned: true } : getSkill(name);
73008
73494
  if (!skill) {
73009
73495
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
@@ -73044,7 +73530,7 @@ function registerOperationTools(server) {
73044
73530
  try {
73045
73531
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
73046
73532
  const client = new RemoteSkillsClient2(routing.apiKey, routing.apiOrigin);
73047
- const run = await client.submitQuotedRunWithFiles(skillName, runInput, runArgs, inputFiles, { maxCredits, maxCostCents, idempotencyKey: idempotency_key ?? runContext.record.id });
73533
+ const run = await client.submitQuotedRunWithFiles(skillName, runInput, runArgs, inputFiles, { maxCredits, maxCostCents, quoteReceipt, idempotencyKey: idempotency_key ?? runContext.record.id });
73048
73534
  if (run.error) {
73049
73535
  writeRunLogs(runContext, "", String(run.error) + `
73050
73536
  `);
@@ -74065,7 +74551,7 @@ async function requestInvitationEmail(origin, action, input) {
74065
74551
  }
74066
74552
  const body = JSON.stringify({ invitationId: value.invitationId, token: value.token, challengeId: value.challengeId, ...action === "accept" ? { code: value.code } : {} });
74067
74553
  try {
74068
- const response = await fetch(`${target}/api/v1/account/invitations/email-${action}`, {
74554
+ const response = await fetch(skillsApiRequestUrl(target, `/api/v1/account/invitations/email-${action}`), {
74069
74555
  method: "POST",
74070
74556
  headers: { "Content-Type": "application/json" },
74071
74557
  body,
@@ -74140,14 +74626,318 @@ var init_remote_invitation_recovery = __esm(() => {
74140
74626
  };
74141
74627
  });
74142
74628
 
74629
+ // src/lib/remote-private-publications.ts
74630
+ import { createHash as createHash7 } from "crypto";
74631
+ function checkedPublicationDeclaration(value) {
74632
+ if (!exact(value, ["idempotencyKey", "version", "expectedCurrentVersionId", "manifestText", "archiveSha256", "archiveByteSize"]) || !publicationUuid(value.idempotencyKey) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || typeof value.manifestText !== "string" || Buffer.byteLength(value.manifestText) > 16384 || !hash(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES)
74633
+ return bad();
74634
+ try {
74635
+ const manifest = JSON.parse(value.manifestText);
74636
+ if (!record7(manifest) || manifest.version !== value.version || validatePortableManifestContract(manifest, { strict: true }).length || !hash(manifest.provenance?.content_hash))
74637
+ return bad();
74638
+ } catch {
74639
+ return bad();
74640
+ }
74641
+ return Object.freeze({ ...value });
74642
+ }
74643
+ function checkedPublicationView(value, skillId, intentId, declaration) {
74644
+ if (!exact(value, ["id", "skillId", "version", "expectedCurrentVersionId", "archiveSha256", "archiveByteSize", "state", "expiresAt", "createdAt", "versionId"]) || !publicationUuid(value.id) || value.skillId !== skillId || intentId !== undefined && value.id !== intentId || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || !hash(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES || typeof value.state !== "string" || !["awaiting_upload", "queued", "verifying", "needs_attention", "committed", "rejected", "cancelled", "expired"].includes(value.state) || !date4(value.expiresAt) || !date4(value.createdAt) || !(value.versionId === null || publicationUuid(value.versionId)) || value.state === "committed" && value.versionId === null)
74645
+ return invalid3();
74646
+ if (declaration && ["version", "expectedCurrentVersionId", "archiveSha256", "archiveByteSize"].some((k) => value[k] !== declaration[k]))
74647
+ return invalid3();
74648
+ return Object.freeze({ ...value });
74649
+ }
74650
+ async function boundedJson(url, init, token, mutation, budget = 15000, signal) {
74651
+ const controller = new AbortController;
74652
+ let reader;
74653
+ let response;
74654
+ const timeout = new PrivatePublicationError("PUBLICATION_UNCONFIRMED", "No confirmed publication result. Inspect the saved intent before retrying.", mutation);
74655
+ let timer;
74656
+ let abort;
74657
+ try {
74658
+ return await Promise.race([(async () => {
74659
+ if (signal?.aborted)
74660
+ throw timeout;
74661
+ response = await fetch(url, {
74662
+ ...init,
74663
+ redirect: "error",
74664
+ credentials: "omit",
74665
+ signal: controller.signal,
74666
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }
74667
+ });
74668
+ const length = response.headers.get("content-length");
74669
+ if (length !== null && (!/^\d+$/.test(length) || Number(length) > 65536))
74670
+ return invalid3();
74671
+ reader = response.body?.getReader();
74672
+ const chunks = [];
74673
+ let size2 = 0;
74674
+ if (reader)
74675
+ while (true) {
74676
+ const part = await reader.read();
74677
+ if (part.done)
74678
+ break;
74679
+ size2 += part.value.byteLength;
74680
+ if (size2 > 65536)
74681
+ return invalid3();
74682
+ chunks.push(part.value);
74683
+ }
74684
+ if (controller.signal.aborted)
74685
+ throw timeout;
74686
+ const bytes = Buffer.concat(chunks);
74687
+ let body;
74688
+ try {
74689
+ body = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
74690
+ } catch {
74691
+ return invalid3();
74692
+ }
74693
+ if (!response.ok) {
74694
+ const code = record7(body) && typeof body.code === "string" && Object.hasOwn(failures, body.code) ? body.code : null;
74695
+ if (code && failures[code][0] === response.status)
74696
+ throw new PrivatePublicationError(code, failures[code][1], mutation && code === "PUBLICATION_UNCERTAIN", response.status);
74697
+ throw new PrivatePublicationError("PUBLICATION_REQUEST_FAILED", "The publication request was refused. Inspect its status before retrying.", mutation && response.status >= 500, response.status);
74698
+ }
74699
+ return body;
74700
+ })(), new Promise((_, reject2) => {
74701
+ abort = () => {
74702
+ controller.abort();
74703
+ reject2(timeout);
74704
+ };
74705
+ timer = setTimeout(abort, Math.max(1, Math.min(15000, budget)));
74706
+ signal?.addEventListener("abort", abort, { once: true });
74707
+ })]);
74708
+ } catch (error2) {
74709
+ if (error2 instanceof PrivatePublicationError) {
74710
+ if (mutation && error2.code === "INVALID_PUBLICATION_RESPONSE")
74711
+ throw timeout;
74712
+ throw error2;
74713
+ }
74714
+ throw timeout;
74715
+ } finally {
74716
+ if (timer)
74717
+ clearTimeout(timer);
74718
+ if (abort)
74719
+ signal?.removeEventListener("abort", abort);
74720
+ controller.abort();
74721
+ if (reader)
74722
+ reader.cancel().catch(() => {});
74723
+ else
74724
+ response?.body?.cancel().catch(() => {});
74725
+ }
74726
+ }
74727
+
74728
+ class RemotePrivatePublicationsClient {
74729
+ apiOrigin;
74730
+ organizationId;
74731
+ userId;
74732
+ membershipId;
74733
+ #token;
74734
+ constructor(apiUrl, session) {
74735
+ const checked = parseWorkspaceSession(session, { userId: session?.user?.id, membershipId: session?.user?.membershipId });
74736
+ if (checked.user.role === "viewer")
74737
+ throw new PrivatePublicationError("PUBLICATION_FORBIDDEN", failures.PUBLICATION_FORBIDDEN[1]);
74738
+ this.apiOrigin = normalizeSkillsApiOrigin(apiUrl);
74739
+ this.#token = checked.token;
74740
+ this.organizationId = checked.organization.id;
74741
+ this.userId = checked.user.id;
74742
+ this.membershipId = checked.user.membershipId;
74743
+ Object.freeze(this);
74744
+ }
74745
+ async getCapability(options = {}) {
74746
+ if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 15000))
74747
+ return bad();
74748
+ const response = await boundedJson(skillsApiRequestUrl(this.apiOrigin, "/api/v1/capabilities"), {}, this.#token, false, options.timeoutMs, options.signal);
74749
+ const p = record7(response) && response.privatePublishing;
74750
+ if (!record7(response) || response.contractVersion !== 1 || response.apiVersion !== 1 || !exact(p, ["contractVersion", "enabled", "authentication", "maxArchiveBytes", "uploadMaxTtlSeconds", "executionEnabled"]) || p.contractVersion !== 1 || typeof p.enabled !== "boolean" || p.authentication !== "interactive-session" || p.maxArchiveBytes !== PRIVATE_PUBLICATION_MAX_BYTES || p.uploadMaxTtlSeconds !== 300 || p.executionEnabled !== false)
74751
+ throw new PrivatePublicationError("PUBLICATION_CONTRACT_UNAVAILABLE", "This server does not support the hosted private publication contract.");
74752
+ return Object.freeze({ ...p });
74753
+ }
74754
+ async#gate(enabled, options = {}) {
74755
+ if (!(await this.getCapability(options)).enabled && enabled)
74756
+ throw new PrivatePublicationError("PUBLICATION_CAPABILITY_UNAVAILABLE", failures.PUBLICATION_CAPABILITY_UNAVAILABLE[1]);
74757
+ }
74758
+ #path(skillId, intentId) {
74759
+ if (!publicationUuid(skillId) || intentId !== undefined && !publicationUuid(intentId))
74760
+ return bad();
74761
+ return skillsApiRequestUrl(this.apiOrigin, `/api/v1/skills/${skillId}/publication-uploads${intentId ? `/${intentId}` : ""}`);
74762
+ }
74763
+ async#view(path, method2, skillId, intentId, declaration, options = {}) {
74764
+ const value = await boundedJson(path, { method: method2, ...method2 === "GET" ? {} : { body: JSON.stringify(declaration ?? {}) } }, this.#token, method2 !== "GET", options.timeoutMs, options.signal);
74765
+ try {
74766
+ if (!record7(value) || !Object.keys(value).every((k) => k === "upload" || k === "changed") || value.changed !== undefined && typeof value.changed !== "boolean")
74767
+ return invalid3();
74768
+ return checkedPublicationView(value.upload, skillId, intentId, declaration);
74769
+ } catch (error2) {
74770
+ if (method2 !== "GET")
74771
+ throw new PrivatePublicationError("PUBLICATION_UNCONFIRMED", "The publication result could not be confirmed. Reconcile the saved intent before another action.", true);
74772
+ throw error2;
74773
+ }
74774
+ }
74775
+ async begin(skillId, input) {
74776
+ const path = this.#path(skillId), declaration = checkedPublicationDeclaration(input);
74777
+ await this.#gate(true);
74778
+ return this.#view(path, "POST", skillId, undefined, declaration);
74779
+ }
74780
+ async get(skillId, intentId, options = {}) {
74781
+ const path = this.#path(skillId, intentId), until = Date.now() + (options.timeoutMs ?? 15000);
74782
+ await this.#gate(false, options);
74783
+ return this.#view(path, "GET", skillId, intentId, undefined, { ...options, timeoutMs: Math.max(1, until - Date.now()) });
74784
+ }
74785
+ async finalize(skillId, intentId) {
74786
+ const path = this.#path(skillId, intentId);
74787
+ await this.#gate(true);
74788
+ return this.#view(`${path}/finalize`, "POST", skillId, intentId);
74789
+ }
74790
+ async cancel(skillId, intentId) {
74791
+ const path = this.#path(skillId, intentId);
74792
+ await this.#gate(false);
74793
+ return this.#view(path, "DELETE", skillId, intentId);
74794
+ }
74795
+ async upload(skillId, intent, bytes) {
74796
+ const captured = checkedPublicationView(intent, skillId), path = this.#path(skillId, captured.id);
74797
+ if (!(bytes instanceof Uint8Array) || bytes.byteLength !== captured.archiveByteSize)
74798
+ return bad();
74799
+ const owned = Buffer.from(bytes);
74800
+ if (captured.state !== "awaiting_upload" || owned.byteLength !== captured.archiveByteSize || publicationSha256(owned) !== captured.archiveSha256)
74801
+ return bad();
74802
+ await this.#gate(true);
74803
+ const value = await boundedJson(`${path}/upload-url`, { method: "POST", body: "{}" }, this.#token, false);
74804
+ const upload = this.#upload(value, captured);
74805
+ const controller = new AbortController;
74806
+ let timer;
74807
+ const uncertain = () => new PrivatePublicationError("PUBLICATION_UPLOAD_UNCONFIRMED", "Upload acceptance is uncertain. Resume this saved intent to finalize and inspect it; do not upload again.", true);
74808
+ try {
74809
+ await Promise.race([(async () => {
74810
+ const response = await fetch(upload.uploadUrl, { method: "PUT", headers: upload.headers, body: owned, redirect: "error", credentials: "omit", signal: controller.signal });
74811
+ response.body?.cancel().catch(() => {});
74812
+ if (response.status !== 200 || controller.signal.aborted)
74813
+ throw uncertain();
74814
+ })(), new Promise((_, reject2) => {
74815
+ timer = setTimeout(() => {
74816
+ controller.abort();
74817
+ reject2(uncertain());
74818
+ }, 30000);
74819
+ })]);
74820
+ } catch {
74821
+ throw uncertain();
74822
+ } finally {
74823
+ if (timer)
74824
+ clearTimeout(timer);
74825
+ controller.abort();
74826
+ }
74827
+ }
74828
+ #upload(value, intent) {
74829
+ if (!exact(value, ["upload"]) || !exact(value.upload, ["method", "uploadUrl", "headers", "expiresAt"]))
74830
+ return invalid3();
74831
+ const p = value.upload;
74832
+ if (p.method !== "PUT" || typeof p.uploadUrl !== "string" || p.uploadUrl.length > 8192 || /[\x00-\x20\x7f]/.test(p.uploadUrl) || !date4(p.expiresAt) || Date.parse(p.expiresAt) - Date.now() < 1000 || Date.parse(p.expiresAt) - Date.now() > 300000 || Date.parse(p.expiresAt) > Date.parse(intent.expiresAt) || !exact(p.headers, ["content-type", "content-length", "x-amz-checksum-sha256", "x-amz-expected-bucket-owner"]) || p.headers["content-type"] !== "application/gzip" || p.headers["content-length"] !== String(intent.archiveByteSize) || p.headers["x-amz-checksum-sha256"] !== Buffer.from(intent.archiveSha256, "hex").toString("base64") || typeof p.headers["x-amz-expected-bucket-owner"] !== "string" || !/^\d{12}$/.test(p.headers["x-amz-expected-bucket-owner"]))
74833
+ return invalid3();
74834
+ let url;
74835
+ try {
74836
+ url = new URL(p.uploadUrl);
74837
+ } catch {
74838
+ return invalid3();
74839
+ }
74840
+ if (url.protocol !== "https:" || url.port || url.username || url.password || url.hash || !/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]\.s3\.[a-z]{2}(?:-[a-z]+)+-[1-9]\.amazonaws\.com$/.test(url.hostname) || url.pathname !== `/private-publication-staging/${this.organizationId}/${intent.id}/bundle.tgz` || !url.search || url.searchParams.get("X-Amz-Algorithm") !== "AWS4-HMAC-SHA256" || url.searchParams.get("X-Amz-SignedHeaders") !== "content-length;content-type;host;x-amz-checksum-sha256;x-amz-expected-bucket-owner" || !/^[a-f0-9]{64}$/.test(url.searchParams.get("X-Amz-Signature") ?? ""))
74841
+ return invalid3();
74842
+ const queryKeys = ["X-Amz-Algorithm", "X-Amz-Credential", "X-Amz-Date", "X-Amz-Expires", "X-Amz-Security-Token", "X-Amz-Signature", "X-Amz-SignedHeaders"];
74843
+ if ([...url.searchParams.keys()].sort().join(",") !== queryKeys.sort().join(","))
74844
+ return invalid3();
74845
+ const issued = url.searchParams.get("X-Amz-Date"), ttl = url.searchParams.get("X-Amz-Expires"), credential = url.searchParams.get("X-Amz-Credential");
74846
+ const timestamp3 = /^(\d{4})(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)Z$/.exec(issued);
74847
+ const region = url.hostname.split(".s3.")[1].split(".amazonaws.com")[0];
74848
+ if (!timestamp3 || !/^[1-9]\d{0,2}$/.test(ttl) || Number(ttl) > 300 || !/^[A-Z0-9]{16,128}\//.test(credential) || credential.split("/").slice(1).join("/") !== `${issued.slice(0, 8)}/${region}/s3/aws4_request` || !/^[\x21-\x7e]{1,4096}$/.test(url.searchParams.get("X-Amz-Security-Token")))
74849
+ return invalid3();
74850
+ const issuedAt = Date.parse(`${timestamp3[1]}-${timestamp3[2]}-${timestamp3[3]}T${timestamp3[4]}:${timestamp3[5]}:${timestamp3[6]}Z`);
74851
+ if (!Number.isFinite(issuedAt) || issuedAt > Date.now() + 1000 || issuedAt + Number(ttl) * 1000 !== Date.parse(p.expiresAt))
74852
+ return invalid3();
74853
+ return { method: "PUT", uploadUrl: url.href, expiresAt: p.expiresAt, headers: Object.freeze({ ...p.headers }) };
74854
+ }
74855
+ async wait(skillId, intentId, options = {}) {
74856
+ const timeout = options.timeoutMs ?? 60000;
74857
+ if (!Number.isSafeInteger(timeout) || timeout < 0 || timeout > 300000)
74858
+ return bad();
74859
+ const until = Date.now() + timeout;
74860
+ let previous;
74861
+ while (true) {
74862
+ if (options.signal?.aborted)
74863
+ throw new PrivatePublicationError("PUBLICATION_WAIT_ABORTED", "Stopped waiting. The server publication continues; inspect the saved intent.");
74864
+ let view;
74865
+ try {
74866
+ view = await this.get(skillId, intentId, { timeoutMs: timeout === 0 ? 15000 : Math.max(1, Math.min(15000, until - Date.now())), signal: options.signal });
74867
+ } catch (error2) {
74868
+ if (previous && Date.now() >= until && !options.signal?.aborted)
74869
+ return previous;
74870
+ throw error2;
74871
+ }
74872
+ previous = view;
74873
+ if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
74874
+ return view;
74875
+ await new Promise((resolve3) => {
74876
+ const timer = setTimeout(done, Math.min(1000, until - Date.now()));
74877
+ function done() {
74878
+ clearTimeout(timer);
74879
+ options.signal?.removeEventListener("abort", done);
74880
+ resolve3();
74881
+ }
74882
+ options.signal?.addEventListener("abort", done, { once: true });
74883
+ });
74884
+ }
74885
+ }
74886
+ }
74887
+ var PRIVATE_PUBLICATION_MAX_BYTES, failures, PrivatePublicationError, bad = () => {
74888
+ throw new PrivatePublicationError("INVALID_PUBLICATION_INPUT", "Invalid publication input or recovery data.");
74889
+ }, invalid3 = () => {
74890
+ throw new PrivatePublicationError("INVALID_PUBLICATION_RESPONSE", "The server returned an invalid publication result.");
74891
+ }, publicationUuid = (v) => typeof v === "string" && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v), hash = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v), record7 = (v) => !!v && typeof v === "object" && !Array.isArray(v), exact = (v, keys2) => record7(v) && Object.keys(v).sort().join(",") === keys2.sort().join(","), date4 = (v) => typeof v === "string" && /^\d{4}-\d\d-\d\dT[0-9:.]+(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(v) && Number.isFinite(Date.parse(v)), publicationSha256 = (bytes) => createHash7("sha256").update(bytes).digest("hex");
74892
+ var init_remote_private_publications = __esm(() => {
74893
+ init_fleet_credentials();
74894
+ init_remote_workspace_selection();
74895
+ init_skill_contract();
74896
+ PRIVATE_PUBLICATION_MAX_BYTES = 16 * 1024 * 1024;
74897
+ failures = {
74898
+ INVALID_REQUEST: [400, "The publication request is invalid."],
74899
+ SESSION_EXPIRED: [401, "Sign in again to manage this publication."],
74900
+ ACCOUNT_UNAVAILABLE: [403, "The account is unavailable."],
74901
+ INTERACTIVE_SESSION_REQUIRED: [403, "An interactive account session is required."],
74902
+ PUBLICATION_FORBIDDEN: [403, "This session cannot manage the publication."],
74903
+ PUBLICATION_ENTITLEMENT_REQUIRED: [403, "The workspace is not entitled to publish private skills."],
74904
+ PUBLICATION_UNAVAILABLE: [404, "The publication is unavailable to this session."],
74905
+ MANIFEST_NAME_MISMATCH: [409, "The manifest name does not match the selected skill."],
74906
+ IDEMPOTENCY_CONFLICT: [409, "This request key already identifies different publication bytes. Use the saved recovery directory."],
74907
+ CURRENT_VERSION_CHANGED: [409, "The current version changed. Inspect it before explicitly starting another publication."],
74908
+ VERSION_EXISTS: [409, "This version already exists."],
74909
+ VERSION_RESERVED: [409, "This version is reserved by another publication."],
74910
+ PUBLICATION_COMMITTED: [409, "The publication is already committed."],
74911
+ PUBLICATION_UPLOAD_UNAVAILABLE: [409, "This publication cannot receive another upload."],
74912
+ PUBLICATION_LIMIT: [429, "The workspace publication limit has been reached."],
74913
+ PUBLICATION_BUSY: [503, "The publication is busy. Reconcile the saved intent before retrying."],
74914
+ PUBLICATION_UNCERTAIN: [503, "The publication outcome is uncertain. Reconcile the saved intent."],
74915
+ PUBLICATION_CAPABILITY_UNAVAILABLE: [503, "Private publishing is not enabled on this server."],
74916
+ PUBLICATION_SIGNING_UNAVAILABLE: [503, "Upload authorization is temporarily unavailable. Keep the same intent."]
74917
+ };
74918
+ PrivatePublicationError = class PrivatePublicationError extends Error {
74919
+ code;
74920
+ uncertain;
74921
+ status;
74922
+ constructor(code, message, uncertain = false, status) {
74923
+ super(message);
74924
+ this.code = code;
74925
+ this.uncertain = uncertain;
74926
+ this.status = status;
74927
+ this.name = "PrivatePublicationError";
74928
+ }
74929
+ };
74930
+ });
74931
+
74143
74932
  // src/lib/remote-auth.ts
74144
74933
  async function requestAuthApi(instance, path, options) {
74145
74934
  const url = normalizeSkillsApiOrigin(instance);
74146
74935
  const safeUrl = url;
74147
- const endpoint = `${(options?.method || "GET").toUpperCase()} ${safeUrl}${path}`;
74936
+ const requestUrl = skillsApiRequestUrl(url, path);
74937
+ const endpoint = `${(options?.method || "GET").toUpperCase()} ${requestUrl}`;
74148
74938
  let res;
74149
74939
  try {
74150
- res = await fetch(`${url}${path}`, {
74940
+ res = await fetch(requestUrl, {
74151
74941
  ...options,
74152
74942
  redirect: "error",
74153
74943
  signal: options?.signal ?? AbortSignal.timeout(15000),
@@ -74162,10 +74952,10 @@ async function requestAuthApi(instance, path, options) {
74162
74952
  const text2 = await res.text();
74163
74953
  const body = text2 ? parseJsonBody(text2) : {};
74164
74954
  if (!res.ok) {
74165
- const record7 = isRecord5(body) ? body : {};
74166
- const detail = typeof record7.detail === "string" ? record7.detail : undefined;
74167
- const error2 = typeof record7.error === "string" ? record7.error : undefined;
74168
- const code = typeof record7.code === "string" ? record7.code : undefined;
74955
+ const record8 = isRecord5(body) ? body : {};
74956
+ const detail = typeof record8.detail === "string" ? record8.detail : undefined;
74957
+ const error2 = typeof record8.error === "string" ? record8.error : undefined;
74958
+ const code = typeof record8.code === "string" ? record8.code : undefined;
74169
74959
  throw new HostedApiError(detail || error2 || `${res.status} ${res.statusText}`, {
74170
74960
  status: res.status,
74171
74961
  code,
@@ -74199,6 +74989,10 @@ class RemoteSkillsAuthClient {
74199
74989
  constructor(apiUrl) {
74200
74990
  this.apiOrigin = normalizeSkillsApiOrigin(apiUrl);
74201
74991
  }
74992
+ async openPrivatePublications(email3, code, context) {
74993
+ const origin = this.apiOrigin, captured = workspaceContext(context);
74994
+ return new RemotePrivatePublicationsClient(origin, await this.switchWorkspace(email3, code, captured));
74995
+ }
74202
74996
  requestInvitationEmailChallenge(input) {
74203
74997
  return requestInvitationEmail(this.apiOrigin, "challenge", input);
74204
74998
  }
@@ -74246,9 +75040,10 @@ class RemoteSkillsAuthClient {
74246
75040
  const apiOrigin = this.apiOrigin;
74247
75041
  if (typeof email3 !== "string" || !email3.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
74248
75042
  throw new Error("Fresh email and six-digit verification code are required to manage this account");
75043
+ const requestUrl = skillsApiRequestUrl(apiOrigin, "/api/auth/verify");
74249
75044
  let response;
74250
75045
  try {
74251
- response = await fetch(`${apiOrigin}/api/auth/verify`, {
75046
+ response = await fetch(requestUrl, {
74252
75047
  method: "POST",
74253
75048
  redirect: "error",
74254
75049
  credentials: "omit",
@@ -74342,6 +75137,7 @@ class RemoteSkillsAuthClient {
74342
75137
  var MAX_ERROR_DETAIL_LENGTH = 200, HostedApiError;
74343
75138
  var init_remote_auth = __esm(() => {
74344
75139
  init_remote_invitation_recovery();
75140
+ init_remote_private_publications();
74345
75141
  init_remote_invitations();
74346
75142
  init_remote_workspace_leave();
74347
75143
  init_remote_workspace_selection();
@@ -74369,11 +75165,11 @@ var init_remote_auth = __esm(() => {
74369
75165
  });
74370
75166
 
74371
75167
  // src/lib/workspace-profile.ts
74372
- import { constants as constants4, closeSync as closeSync4, fstatSync as fstatSync4, lstatSync as lstatSync6, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync4, openSync as openSync4, readFileSync as readFileSync25, renameSync as renameSync6, rmSync as rmSync7, writeFileSync as writeFileSync14 } from "fs";
75168
+ import { constants as constants5, closeSync as closeSync5, fstatSync as fstatSync5, lstatSync as lstatSync7, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync4, openSync as openSync5, readFileSync as readFileSync25, renameSync as renameSync6, rmSync as rmSync7, writeFileSync as writeFileSync14 } from "fs";
74373
75169
  import { dirname as dirname10, join as join32 } from "path";
74374
75170
  function stat(path) {
74375
75171
  try {
74376
- return lstatSync6(path);
75172
+ return lstatSync7(path);
74377
75173
  } catch (error2) {
74378
75174
  if (error2.code === "ENOENT")
74379
75175
  return null;
@@ -74383,14 +75179,14 @@ function stat(path) {
74383
75179
  function safeText(file) {
74384
75180
  if (stat(file) === null)
74385
75181
  return null;
74386
- const fd = openSync4(file, constants4.O_RDONLY | constants4.O_NOFOLLOW | constants4.O_NONBLOCK);
75182
+ const fd = openSync5(file, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
74387
75183
  try {
74388
- const s = fstatSync4(fd);
75184
+ const s = fstatSync5(fd);
74389
75185
  if (!s.isFile() || s.size > 65536 || ![256, 384].includes(s.mode & 4095) || process.getuid && s.uid !== process.getuid())
74390
75186
  return fail2("The selected profile must use bounded owner-only regular files.");
74391
75187
  return readFileSync25(fd, "utf8");
74392
75188
  } finally {
74393
- closeSync4(fd);
75189
+ closeSync5(fd);
74394
75190
  }
74395
75191
  }
74396
75192
  function checkIdentityMetadata(file, identity2) {
@@ -74680,9 +75476,325 @@ var init_remote_invitation_tools = __esm(() => {
74680
75476
  init_helpers();
74681
75477
  });
74682
75478
 
75479
+ // src/lib/private-publication-customer.ts
75480
+ async function privatePublicationSession(email3, code, requested) {
75481
+ const target = await captureProfileWorkspace("Manage private publications");
75482
+ const context = requested ?? target.context;
75483
+ if (!context || !publicationUuid(context.userId) || !publicationUuid(context.membershipId) || target.context && (context.userId !== target.context.userId || context.membershipId !== target.context.membershipId))
75484
+ throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide the observed user and membership IDs, or select an enrolled workspace profile.");
75485
+ target.unchanged();
75486
+ const client = await new RemoteSkillsAuthClient(target.origin).openPrivatePublications(email3, code, context);
75487
+ target.unchanged();
75488
+ return client;
75489
+ }
75490
+ function privatePublicationCustomerError(error2) {
75491
+ return error2 instanceof PrivatePublicationError ? { error: error2.message, code: error2.code, uncertain: error2.uncertain } : { error: "The publication action could not be confirmed. Preserve the recovery directory and inspect the same intent; credentials were not changed.", code: "PUBLICATION_ACTION_UNCONFIRMED", uncertain: true };
75492
+ }
75493
+ var init_private_publication_customer = __esm(() => {
75494
+ init_remote_auth();
75495
+ init_remote_private_publications();
75496
+ init_workspace_profile();
75497
+ });
75498
+
75499
+ // src/lib/private-publication-recovery.ts
75500
+ import { constants as constants6, closeSync as closeSync6, fsyncSync, fstatSync as fstatSync6, lstatSync as lstatSync8, mkdirSync as mkdirSync15, openSync as openSync6, readSync as readSync3, realpathSync as realpathSync2, renameSync as renameSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync15 } from "fs";
75501
+ import { dirname as dirname11, isAbsolute as isAbsolute4, join as join33, resolve as resolve3 } from "path";
75502
+ import { randomUUID as randomUUID3 } from "crypto";
75503
+ function safeDirectory(directory) {
75504
+ if (!isAbsolute4(directory) || directory !== resolve3(directory) || realpathSync2(directory) !== directory)
75505
+ return fail3();
75506
+ for (let path = directory;; path = dirname11(path)) {
75507
+ const stat2 = lstatSync8(path);
75508
+ if (!stat2.isDirectory() || stat2.isSymbolicLink())
75509
+ return fail3();
75510
+ if (path === dirname11(path))
75511
+ break;
75512
+ }
75513
+ const own = lstatSync8(directory);
75514
+ if ((own.mode & 63) !== 0 || process.getuid && own.uid !== process.getuid())
75515
+ return fail3();
75516
+ return { dev: own.dev, ino: own.ino };
75517
+ }
75518
+ function unchangedDirectory(directory, identity2) {
75519
+ const now3 = safeDirectory(directory);
75520
+ if (now3.dev !== identity2.dev || now3.ino !== identity2.ino)
75521
+ return fail3();
75522
+ }
75523
+ function readOwned(directory, name, max2) {
75524
+ const identity2 = safeDirectory(directory), file = join33(directory, name);
75525
+ const fd = openSync6(file, constants6.O_RDONLY | constants6.O_NOFOLLOW);
75526
+ try {
75527
+ const stat2 = fstatSync6(fd);
75528
+ if (!stat2.isFile() || stat2.nlink !== 1 || stat2.size > max2 || stat2.size < 1 || (stat2.mode & 63) !== 0 || process.getuid && stat2.uid !== process.getuid())
75529
+ return fail3();
75530
+ const buffer = Buffer.alloc(Math.min(max2, stat2.size) + 1);
75531
+ let length = 0;
75532
+ while (length < buffer.length) {
75533
+ const count = readSync3(fd, buffer, length, buffer.length - length, length);
75534
+ if (count === 0)
75535
+ break;
75536
+ length += count;
75537
+ }
75538
+ const bytes = buffer.subarray(0, length), after2 = fstatSync6(fd);
75539
+ unchangedDirectory(directory, identity2);
75540
+ if (bytes.length !== stat2.size || stat2.size !== after2.size || stat2.mtimeMs !== after2.mtimeMs || stat2.ctimeMs !== after2.ctimeMs)
75541
+ return fail3();
75542
+ return bytes;
75543
+ } finally {
75544
+ closeSync6(fd);
75545
+ }
75546
+ }
75547
+ function writeOwned(directory, name, bytes) {
75548
+ const fd = openSync6(join33(directory, name), constants6.O_WRONLY | constants6.O_CREAT | constants6.O_EXCL | constants6.O_NOFOLLOW, 384);
75549
+ try {
75550
+ writeFileSync15(fd, bytes);
75551
+ fsyncSync(fd);
75552
+ } finally {
75553
+ closeSync6(fd);
75554
+ }
75555
+ }
75556
+ function save(directory, value) {
75557
+ const identity2 = safeDirectory(directory), temporary = `.receipt-${randomUUID3()}.json`;
75558
+ writeOwned(directory, temporary, JSON.stringify(value) + `
75559
+ `);
75560
+ unchangedDirectory(directory, identity2);
75561
+ renameSync7(join33(directory, temporary), join33(directory, "receipt.json"));
75562
+ const fd = openSync6(directory, constants6.O_RDONLY | constants6.O_NOFOLLOW);
75563
+ try {
75564
+ fsyncSync(fd);
75565
+ } finally {
75566
+ closeSync6(fd);
75567
+ }
75568
+ }
75569
+ function bind2(client, receipt) {
75570
+ if (["apiOrigin", "organizationId", "userId", "membershipId"].some((k) => client[k] !== receipt[k]))
75571
+ throw new PrivatePublicationError("PUBLICATION_IDENTITY_CHANGED", "The fresh session does not match the recovery directory's server, account and membership.");
75572
+ }
75573
+ async function locked(directory, action) {
75574
+ const identity2 = safeDirectory(directory), file = join33(directory, "operation.lock");
75575
+ let fd;
75576
+ try {
75577
+ fd = openSync6(file, constants6.O_WRONLY | constants6.O_CREAT | constants6.O_EXCL | constants6.O_NOFOLLOW, 384);
75578
+ } catch {
75579
+ throw new PrivatePublicationError("PUBLICATION_RECOVERY_BUSY", "Another operation holds this recovery directory. If it crashed, confirm that process has stopped before explicitly removing operation.lock and resuming.");
75580
+ }
75581
+ const lock = fstatSync6(fd);
75582
+ try {
75583
+ writeFileSync15(fd, JSON.stringify({ pid: process.pid }) + `
75584
+ `);
75585
+ fsyncSync(fd);
75586
+ return await action();
75587
+ } finally {
75588
+ closeSync6(fd);
75589
+ unchangedDirectory(directory, identity2);
75590
+ const now3 = lstatSync8(file);
75591
+ if (now3.dev !== lock.dev || now3.ino !== lock.ino || !now3.isFile() || now3.isSymbolicLink())
75592
+ fail3();
75593
+ unlinkSync2(file);
75594
+ }
75595
+ }
75596
+ function readPrivatePublicationRecovery(directory) {
75597
+ try {
75598
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(readOwned(directory, "receipt.json", 65536)));
75599
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== ["contractVersion", "apiOrigin", "organizationId", "userId", "membershipId", "skillId", "declaration", "phase", "intent"].sort().join(",") || value.contractVersion !== 1 || ![value.organizationId, value.userId, value.membershipId, value.skillId].every(publicationUuid) || typeof value.apiOrigin !== "string" || normalizeSkillsApiOrigin(value.apiOrigin) !== value.apiOrigin || !["prepared", "begin_uncertain", "awaiting_upload", "upload_uncertain", "uploaded", "finalize_uncertain", "observed"].includes(value.phase))
75600
+ return fail3();
75601
+ value.declaration = checkedPublicationDeclaration(value.declaration);
75602
+ if (value.intent !== null)
75603
+ value.intent = checkedPublicationView(value.intent, value.skillId, undefined, value.declaration);
75604
+ if (value.intent === null && !["prepared", "begin_uncertain"].includes(value.phase))
75605
+ return fail3();
75606
+ const bytes = readOwned(directory, "bundle.tgz", PRIVATE_PUBLICATION_MAX_BYTES);
75607
+ if (bytes.length !== value.declaration.archiveByteSize || publicationSha256(bytes) !== value.declaration.archiveSha256)
75608
+ return fail3();
75609
+ return { receipt: value, bytes };
75610
+ } catch {
75611
+ return fail3();
75612
+ }
75613
+ }
75614
+ async function preparePrivatePublication(client, sourceDirectory, recoveryDirectory, input) {
75615
+ if (!publicationUuid(input.skillId) || !(input.expectedCurrentVersionId === null || publicationUuid(input.expectedCurrentVersionId)) || input.idempotencyKey !== undefined && !publicationUuid(input.idempotencyKey))
75616
+ return fail3();
75617
+ const packed = packSkillBundle(sourceDirectory, { maxUnpackedBytes: 32 * 1024 * 1024 });
75618
+ const inspected = await inspectSkillBundle(packed.bytes, { limits: { compressedBytes: PRIVATE_PUBLICATION_MAX_BYTES } });
75619
+ const manifest = inspected.entries.find((entry) => entry.path === "skill.json");
75620
+ if (!manifest || manifest.bytes.length > 16384 || !(await verifyContentHashFromEntries(inspected.entries)).valid)
75621
+ throw new PrivatePublicationError("PUBLICATION_BUNDLE_INVALID", "The skill must have a valid skill.json with its current content hash. Validate the skill before publishing.");
75622
+ const manifestText = new TextDecoder("utf-8", { fatal: true }).decode(manifest.bytes);
75623
+ const declaration = checkedPublicationDeclaration({
75624
+ idempotencyKey: input.idempotencyKey ?? randomUUID3(),
75625
+ version: JSON.parse(manifestText).version,
75626
+ expectedCurrentVersionId: input.expectedCurrentVersionId,
75627
+ manifestText,
75628
+ archiveSha256: inspected.sha256,
75629
+ archiveByteSize: packed.bytes.length
75630
+ });
75631
+ const receipt = {
75632
+ contractVersion: 1,
75633
+ apiOrigin: client.apiOrigin,
75634
+ organizationId: client.organizationId,
75635
+ userId: client.userId,
75636
+ membershipId: client.membershipId,
75637
+ skillId: input.skillId,
75638
+ declaration,
75639
+ phase: "prepared",
75640
+ intent: null
75641
+ };
75642
+ if (!isAbsolute4(recoveryDirectory) || resolve3(recoveryDirectory) !== recoveryDirectory || realpathSync2(dirname11(recoveryDirectory)) !== dirname11(recoveryDirectory))
75643
+ return fail3();
75644
+ mkdirSync15(recoveryDirectory, { mode: 448 });
75645
+ safeDirectory(recoveryDirectory);
75646
+ const parent = openSync6(dirname11(recoveryDirectory), constants6.O_RDONLY | constants6.O_NOFOLLOW);
75647
+ try {
75648
+ fsyncSync(parent);
75649
+ } finally {
75650
+ closeSync6(parent);
75651
+ }
75652
+ writeOwned(recoveryDirectory, "bundle.tgz", packed.bytes);
75653
+ save(recoveryDirectory, receipt);
75654
+ return receipt;
75655
+ }
75656
+ function privatePublicationResult(directory, receipt) {
75657
+ const state = receipt.intent?.state ?? receipt.phase, committed = state === "committed";
75658
+ const nextAction = committed ? "The version is published. Private execution remains unavailable." : ["rejected", "cancelled", "expired"].includes(state) ? "This intent is terminal. Inspect the result before explicitly preparing another version." : state === "needs_attention" ? "Keep this intent and contact the service operator; do not create a replacement or upload again." : receipt.phase === "upload_uncertain" ? "Run publication resume with this recovery directory to finalize the same intent without another upload." : receipt.intent ? "Run publication status or resume with this recovery directory; cancel explicitly if you want to stop." : "Run publication resume with this recovery directory to reconcile the identical request key and declaration.";
75659
+ return {
75660
+ recoveryDirectory: directory,
75661
+ skillId: receipt.skillId,
75662
+ intentId: receipt.intent?.id ?? null,
75663
+ state,
75664
+ versionId: receipt.intent?.versionId ?? null,
75665
+ committed,
75666
+ executionEnabled: false,
75667
+ nextAction
75668
+ };
75669
+ }
75670
+ async function continuePrivatePublication(client, directory, options) {
75671
+ if (options.confirm !== true)
75672
+ throw new PrivatePublicationError("PUBLICATION_CONFIRM_REQUIRED", "Explicit upload confirmation is required.");
75673
+ if (options.waitMs !== undefined && (!Number.isSafeInteger(options.waitMs) || options.waitMs < 0 || options.waitMs > 300000))
75674
+ return fail3();
75675
+ return locked(directory, () => continueLocked(client, directory, options));
75676
+ }
75677
+ async function continueLocked(client, directory, options) {
75678
+ const { receipt, bytes } = readPrivatePublicationRecovery(directory);
75679
+ bind2(client, receipt);
75680
+ if (!receipt.intent) {
75681
+ receipt.phase = "begin_uncertain";
75682
+ save(directory, receipt);
75683
+ receipt.intent = await client.begin(receipt.skillId, receipt.declaration);
75684
+ receipt.phase = "awaiting_upload";
75685
+ save(directory, receipt);
75686
+ } else {
75687
+ receipt.intent = checkedPublicationView(await client.get(receipt.skillId, receipt.intent.id), receipt.skillId, receipt.intent.id, receipt.declaration);
75688
+ save(directory, receipt);
75689
+ }
75690
+ if (receipt.intent.state === "awaiting_upload") {
75691
+ if (receipt.phase === "awaiting_upload") {
75692
+ receipt.phase = "upload_uncertain";
75693
+ save(directory, receipt);
75694
+ try {
75695
+ await client.upload(receipt.skillId, receipt.intent, bytes);
75696
+ } catch (error2) {
75697
+ if (error2 instanceof PrivatePublicationError && !error2.uncertain) {
75698
+ receipt.phase = "awaiting_upload";
75699
+ save(directory, receipt);
75700
+ }
75701
+ throw error2;
75702
+ }
75703
+ receipt.phase = "uploaded";
75704
+ save(directory, receipt);
75705
+ }
75706
+ receipt.phase = "finalize_uncertain";
75707
+ save(directory, receipt);
75708
+ receipt.intent = checkedPublicationView(await client.finalize(receipt.skillId, receipt.intent.id), receipt.skillId, receipt.intent.id, receipt.declaration);
75709
+ receipt.phase = "observed";
75710
+ save(directory, receipt);
75711
+ }
75712
+ if (options.waitMs !== undefined && options.waitMs > 0 && ["queued", "verifying"].includes(receipt.intent.state)) {
75713
+ receipt.intent = checkedPublicationView(await client.wait(receipt.skillId, receipt.intent.id, { timeoutMs: options.waitMs }), receipt.skillId, receipt.intent.id, receipt.declaration);
75714
+ receipt.phase = "observed";
75715
+ save(directory, receipt);
75716
+ }
75717
+ return privatePublicationResult(directory, receipt);
75718
+ }
75719
+ async function inspectPrivatePublication(client, directory, cancel = false) {
75720
+ return locked(directory, () => inspectLocked(client, directory, cancel));
75721
+ }
75722
+ async function inspectLocked(client, directory, cancel) {
75723
+ const { receipt } = readPrivatePublicationRecovery(directory);
75724
+ bind2(client, receipt);
75725
+ if (receipt.intent) {
75726
+ receipt.intent = checkedPublicationView(await (cancel ? client.cancel(receipt.skillId, receipt.intent.id) : client.get(receipt.skillId, receipt.intent.id)), receipt.skillId, receipt.intent.id, receipt.declaration);
75727
+ save(directory, receipt);
75728
+ } else if (cancel)
75729
+ throw new PrivatePublicationError("PUBLICATION_INTENT_UNKNOWN", "Reconcile the saved begin request with publication resume before cancelling its intent.");
75730
+ return privatePublicationResult(directory, receipt);
75731
+ }
75732
+ var fail3 = () => {
75733
+ throw new PrivatePublicationError("PUBLICATION_RECOVERY_INVALID", "The recovery directory is invalid or changed. Preserve it and inspect the existing intent; do not start a replacement automatically.");
75734
+ };
75735
+ var init_private_publication_recovery = __esm(() => {
75736
+ init_skill_bundle();
75737
+ init_skill_hash();
75738
+ init_remote_private_publications();
75739
+ init_fleet_credentials();
75740
+ });
75741
+
75742
+ // src/mcp/private-publication-tools.ts
75743
+ import { isAbsolute as isAbsolute5 } from "path";
75744
+ function registerPrivatePublicationTools(server) {
75745
+ const uuid6 = exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
75746
+ const directory = exports_external.string().max(4096).refine(isAbsolute5, "Use an absolute local directory");
75747
+ const verification = { email: exports_external.string().email().max(254), code: exports_external.string().regex(/^\d{6}$/), userId: uuid6, membershipId: uuid6, recoveryDirectory: directory };
75748
+ 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.";
75749
+ const handler = (action) => async (value) => {
75750
+ try {
75751
+ const client = await privatePublicationSession(String(value.email), String(value.code), { userId: String(value.userId), membershipId: String(value.membershipId) });
75752
+ const recovery = String(value.recoveryDirectory);
75753
+ if (action === "publish")
75754
+ await preparePrivatePublication(client, String(value.directory), recovery, { skillId: String(value.skillId), expectedCurrentVersionId: value.expectedCurrentVersionId, idempotencyKey: value.idempotencyKey });
75755
+ const result2 = action === "publish" || action === "resume" ? await continuePrivatePublication(client, recovery, { confirm: true, waitMs: value.waitMs }) : await inspectPrivatePublication(client, recovery, action === "cancel");
75756
+ return mcpJson(result2);
75757
+ } catch (error2) {
75758
+ const result2 = privatePublicationCustomerError(error2);
75759
+ return mcpError(result2.code, result2.error);
75760
+ }
75761
+ };
75762
+ server.registerTool("publish_private_skill", {
75763
+ title: "Publish private skill",
75764
+ description,
75765
+ annotations: { destructiveHint: true, readOnlyHint: false, idempotentHint: false },
75766
+ inputSchema: exports_external.object({ ...verification, directory, skillId: uuid6, expectedCurrentVersionId: uuid6.nullable(), idempotencyKey: uuid6.optional(), confirm: exports_external.literal(true), waitMs: exports_external.number().int().min(0).max(300000).optional() }).strict()
75767
+ }, handler("publish"));
75768
+ server.registerTool("get_private_publication", {
75769
+ title: "Get private publication",
75770
+ description,
75771
+ annotations: { destructiveHint: false, readOnlyHint: true, idempotentHint: true },
75772
+ inputSchema: exports_external.object(verification).strict()
75773
+ }, handler("get"));
75774
+ server.registerTool("resume_private_publication", {
75775
+ title: "Resume private publication",
75776
+ description,
75777
+ annotations: { destructiveHint: true, readOnlyHint: false, idempotentHint: true },
75778
+ inputSchema: exports_external.object({ ...verification, confirm: exports_external.literal(true), waitMs: exports_external.number().int().min(0).max(300000).optional() }).strict()
75779
+ }, handler("resume"));
75780
+ server.registerTool("cancel_private_publication", {
75781
+ title: "Cancel private publication",
75782
+ description,
75783
+ annotations: { destructiveHint: true, readOnlyHint: false, idempotentHint: true },
75784
+ inputSchema: exports_external.object({ ...verification, confirm: exports_external.literal(true) }).strict()
75785
+ }, handler("cancel"));
75786
+ }
75787
+ var init_private_publication_tools = __esm(() => {
75788
+ init_zod();
75789
+ init_private_publication_customer();
75790
+ init_private_publication_recovery();
75791
+ init_helpers();
75792
+ });
75793
+
74683
75794
  // src/mcp/remote-customer-tools.ts
74684
75795
  function registerRemoteCustomerTools(server) {
74685
75796
  registerRemoteInvitationTools(server);
75797
+ registerPrivatePublicationTools(server);
74686
75798
  const memberRole = exports_external.enum(["owner", "admin", "member", "viewer"]);
74687
75799
  const memberInput = {
74688
75800
  membershipId: exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/),
@@ -74800,8 +75912,16 @@ function registerRemoteCustomerTools(server) {
74800
75912
  server.registerTool("quote_skill", {
74801
75913
  title: "Quote Remote Skill",
74802
75914
  description: "Get the configured server's credit quote without submitting a run.",
74803
- inputSchema: { name: exports_external.string(), input: exports_external.record(exports_external.string(), exports_external.unknown()).optional(), args: exports_external.array(exports_external.string()).optional() }
74804
- }, ({ name, input, args }) => callRemote((client) => client.quoteRun(name, input, args)));
75915
+ inputSchema: {
75916
+ name: exports_external.string(),
75917
+ input: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
75918
+ args: exports_external.array(exports_external.string()).optional(),
75919
+ files: exports_external.array(exports_external.object({ name: exports_external.string(), base64: exports_external.string().max(1398104), contentType: exports_external.string().optional() })).max(10).optional().describe("The same inline files to submit after approval; quoted descriptors bind their exact bytes, names and types.")
75920
+ }
75921
+ }, ({ name, input, args, files }) => callRemote((client) => {
75922
+ const descriptors = describeRemoteFiles(decodeRemoteFiles(files ?? []));
75923
+ return client.quoteRun(name, input, args, descriptors.length ? descriptors : undefined);
75924
+ }));
74805
75925
  server.registerTool("download_run_artifact", {
74806
75926
  title: "Download Verified Run Artifact",
74807
75927
  description: "Return verified artifact bytes as base64 (at most 1 MiB); use the CLI for larger files.",
@@ -74835,6 +75955,7 @@ async function freshAccount(action, operation) {
74835
75955
  }
74836
75956
  var init_remote_customer_tools = __esm(() => {
74837
75957
  init_remote_invitation_tools();
75958
+ init_private_publication_tools();
74838
75959
  init_zod();
74839
75960
  init_remote_auth();
74840
75961
  init_workspace_profile();
@@ -74842,6 +75963,7 @@ var init_remote_customer_tools = __esm(() => {
74842
75963
  init_remote_client();
74843
75964
  init_remote_workspace_leave();
74844
75965
  init_helpers();
75966
+ init_remote_files();
74845
75967
  });
74846
75968
 
74847
75969
  // src/mcp/server.ts
@@ -74929,9 +76051,9 @@ var init_mcp2 = __esm(() => {
74929
76051
  });
74930
76052
 
74931
76053
  // src/cli/commands/runtime-mcp.ts
74932
- import { existsSync as existsSync30, mkdirSync as mkdirSync15, readFileSync as readFileSync26, writeFileSync as writeFileSync15 } from "fs";
76054
+ import { existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync26, writeFileSync as writeFileSync16 } from "fs";
74933
76055
  import { homedir as homedir7 } from "os";
74934
- import { dirname as dirname11, join as join33 } from "path";
76056
+ import { dirname as dirname12, join as join34 } from "path";
74935
76057
  async function handleMcp(options) {
74936
76058
  if (options.register) {
74937
76059
  let agents;
@@ -74974,24 +76096,24 @@ async function registerMcpForAgent(agent, command) {
74974
76096
  case "codex":
74975
76097
  return registerCodexMcp(command);
74976
76098
  case "gemini":
74977
- return registerJsonMcpServer(agent, join33(homedir7(), ".gemini", "settings.json"), "mcpServers", {
76099
+ return registerJsonMcpServer(agent, join34(homedir7(), ".gemini", "settings.json"), "mcpServers", {
74978
76100
  command,
74979
76101
  args: []
74980
76102
  });
74981
76103
  case "pi":
74982
- return registerJsonMcpServer(agent, join33(homedir7(), ".pi", "agent", "mcp.json"), "mcpServers", {
76104
+ return registerJsonMcpServer(agent, join34(homedir7(), ".pi", "agent", "mcp.json"), "mcpServers", {
74983
76105
  command,
74984
76106
  args: []
74985
76107
  });
74986
76108
  case "opencode":
74987
76109
  return registerOpenCodeMcp(command);
74988
76110
  case "cursor":
74989
- return registerJsonMcpServer(agent, join33(homedir7(), ".cursor", "mcp.json"), "mcpServers", {
76111
+ return registerJsonMcpServer(agent, join34(homedir7(), ".cursor", "mcp.json"), "mcpServers", {
74990
76112
  command,
74991
76113
  args: []
74992
76114
  });
74993
76115
  case "windsurf":
74994
- return registerJsonMcpServer(agent, join33(homedir7(), ".windsurf", "mcp.json"), "mcpServers", {
76116
+ return registerJsonMcpServer(agent, join34(homedir7(), ".windsurf", "mcp.json"), "mcpServers", {
74995
76117
  command,
74996
76118
  args: []
74997
76119
  });
@@ -75014,7 +76136,7 @@ async function registerClaudeMcp(command) {
75014
76136
  if (exitCode === 0) {
75015
76137
  return { agent: "claude", success: true, command: cliCommand };
75016
76138
  }
75017
- const fallback = registerJsonMcpServer("claude", join33(homedir7(), ".claude", ".mcp.json"), "mcpServers", {
76139
+ const fallback = registerJsonMcpServer("claude", join34(homedir7(), ".claude", ".mcp.json"), "mcpServers", {
75018
76140
  command,
75019
76141
  args: []
75020
76142
  });
@@ -75024,7 +76146,7 @@ async function registerClaudeMcp(command) {
75024
76146
  error: fallback.success ? undefined : `claude exited with ${exitCode}: ${(stderr || stdout).trim()}`
75025
76147
  };
75026
76148
  } catch (err) {
75027
- const fallback = registerJsonMcpServer("claude", join33(homedir7(), ".claude", ".mcp.json"), "mcpServers", {
76149
+ const fallback = registerJsonMcpServer("claude", join34(homedir7(), ".claude", ".mcp.json"), "mcpServers", {
75028
76150
  command,
75029
76151
  args: []
75030
76152
  });
@@ -75036,7 +76158,7 @@ async function registerClaudeMcp(command) {
75036
76158
  }
75037
76159
  }
75038
76160
  function registerCodexMcp(command) {
75039
- const path = join33(homedir7(), ".codex", "config.toml");
76161
+ const path = join34(homedir7(), ".codex", "config.toml");
75040
76162
  const config2 = `[mcp_servers.${MCP_SERVER_NAME}]
75041
76163
  command = ${JSON.stringify(command)}`;
75042
76164
  try {
@@ -75048,7 +76170,7 @@ command = ${JSON.stringify(command)}`;
75048
76170
  }
75049
76171
  }
75050
76172
  function registerOpenCodeMcp(command) {
75051
- const path = join33(homedir7(), ".config", "opencode", "opencode.json");
76173
+ const path = join34(homedir7(), ".config", "opencode", "opencode.json");
75052
76174
  const config2 = JSON.stringify({
75053
76175
  $schema: "https://opencode.ai/config.json",
75054
76176
  mcp: {
@@ -75105,8 +76227,8 @@ function writeJsonObject(path, data) {
75105
76227
  `);
75106
76228
  }
75107
76229
  function writeTextFile(path, content) {
75108
- mkdirSync15(dirname11(path), { recursive: true });
75109
- writeFileSync15(path, content.endsWith(`
76230
+ mkdirSync16(dirname12(path), { recursive: true });
76231
+ writeFileSync16(path, content.endsWith(`
75110
76232
  `) ? content : `${content}
75111
76233
  `);
75112
76234
  }
@@ -75136,7 +76258,7 @@ function findCommandOnPath(command) {
75136
76258
  for (const dir of pathValue.split(":")) {
75137
76259
  if (!dir)
75138
76260
  continue;
75139
- const candidate = join33(dir, command);
76261
+ const candidate = join34(dir, command);
75140
76262
  if (existsSync30(candidate))
75141
76263
  return candidate;
75142
76264
  }
@@ -75182,7 +76304,7 @@ async function execute(options, action) {
75182
76304
  if (options.json)
75183
76305
  console.log(JSON.stringify({
75184
76306
  error: message,
75185
- ...error2 instanceof RemoteCapabilityUnavailableError ? { code: error2.code, status: error2.status } : {}
76307
+ ...error2 instanceof RemoteCapabilityUnavailableError || error2 instanceof RemoteQuoteUnavailableError ? { code: error2.code, status: error2.status } : {}
75186
76308
  }));
75187
76309
  else
75188
76310
  console.error(message);
@@ -75198,8 +76320,8 @@ var exports_runtime = {};
75198
76320
  __export(exports_runtime, {
75199
76321
  registerRuntime: () => registerRuntime
75200
76322
  });
75201
- import { lstatSync as lstatSync7, mkdirSync as mkdirSync16, readFileSync as readFileSync27, realpathSync as realpathSync2, writeFileSync as writeFileSync16 } from "fs";
75202
- import { basename as basename5, isAbsolute as isAbsolute4, join as join34 } from "path";
76323
+ import { lstatSync as lstatSync9, mkdirSync as mkdirSync17, readFileSync as readFileSync27, realpathSync as realpathSync3, writeFileSync as writeFileSync17 } from "fs";
76324
+ import { basename as basename5, isAbsolute as isAbsolute6, join as join35 } from "path";
75203
76325
  import { createInterface } from "readline";
75204
76326
  function registerRuntime(parent) {
75205
76327
  parent.command("run").argument("<skill>", "Skill name").argument("[args...]", "Arguments to pass to the skill").allowUnknownOption(true).passThroughOptions(true).option("--json", "Output result as JSON", false).option("--remote", "Run on the configured server, using its catalog and quote", false).option("--yes", "Approve the server's quoted credit cost for this run", false).option("--idempotency-key <key>", "Reuse this key when retrying the same remote submission").option("--file <path>", "Attach a local input file to a remote run (repeatable)", (value, prior) => [...prior, value], []).option("--wait", "Poll remote runs until a terminal status", false).option("--poll-interval-ms <ms>", "Remote polling interval in milliseconds", "1000").option("--poll-timeout-ms <ms>", "Maximum time to wait for a remote run", "300000").description("Run a skill directly").action(async (name, args2, options) => handleRun(name, args2, options));
@@ -75282,14 +76404,14 @@ Updating ${name}...
75282
76404
  async function readUpdatedVersion(bunExecutable) {
75283
76405
  const output = await readUpdateCommand([bunExecutable, "pm", "bin", "-g"]);
75284
76406
  const globalBin = output.replace(/\r?\n$/, "");
75285
- if (!globalBin || !isAbsolute4(globalBin) || /[\r\n\0]/.test(globalBin))
76407
+ if (!globalBin || !isAbsolute6(globalBin) || /[\r\n\0]/.test(globalBin))
75286
76408
  throw new Error("Update bin discovery failed");
75287
76409
  const installed = Bun.which("skills", { PATH: globalBin, cwd: globalBin });
75288
76410
  const selected = Bun.which("skills");
75289
76411
  if (!installed || !selected)
75290
76412
  throw new Error("Updated command is unavailable");
75291
- const selectedPath = realpathSync2(selected);
75292
- if (selectedPath !== realpathSync2(installed))
76413
+ const selectedPath = realpathSync3(selected);
76414
+ if (selectedPath !== realpathSync3(installed))
75293
76415
  throw new Error("Updated command is shadowed on PATH");
75294
76416
  const version2 = (await readUpdateCommand([selectedPath, "--version"])).trim();
75295
76417
  if (!new RegExp(SEMVER_PATTERN).test(version2))
@@ -75418,14 +76540,14 @@ function requireHttpUrl(value) {
75418
76540
  }
75419
76541
  function promptLine(question) {
75420
76542
  const rl = createInterface({ input: process.stdin, output: process.stdout });
75421
- return new Promise((resolve3) => {
76543
+ return new Promise((resolve4) => {
75422
76544
  let settled = false;
75423
76545
  const finish = (answer) => {
75424
76546
  if (settled)
75425
76547
  return;
75426
76548
  settled = true;
75427
76549
  rl.close();
75428
- resolve3(answer);
76550
+ resolve4(answer);
75429
76551
  };
75430
76552
  rl.once("SIGINT", () => finish(null));
75431
76553
  rl.once("close", () => finish(null));
@@ -75468,20 +76590,23 @@ async function handleRun(name, args2, options) {
75468
76590
  }
75469
76591
  let client;
75470
76592
  let approvedCredits = 0;
76593
+ let quoteReceipt;
75471
76594
  let inputFiles = [];
75472
76595
  if (routing.route === "remote") {
75473
76596
  try {
75474
76597
  parsePollingOptions(options);
75475
76598
  inputFiles = (options.file ?? []).map((path) => {
75476
- const info = lstatSync7(path);
76599
+ const info = lstatSync9(path);
75477
76600
  if (!info.isFile() || info.size > 20 * 1024 * 1024)
75478
76601
  throw new Error("Input must be a regular file no larger than 20 MiB");
75479
76602
  return { name: basename5(path), bytes: new Uint8Array(readFileSync27(path)) };
75480
76603
  });
75481
76604
  describeRemoteFiles(inputFiles);
75482
76605
  client = new RemoteSkillsClient(routing.apiKey, routing.apiOrigin);
75483
- const quote = await client.quoteRun(skill.name, {}, args2);
76606
+ const descriptors = describeRemoteFiles(inputFiles);
76607
+ const quote = await client.quoteRun(skill.name, {}, args2, descriptors.length ? descriptors : undefined);
75484
76608
  approvedCredits = quote.pricing.costCents;
76609
+ quoteReceipt = quote.quoteReceipt;
75485
76610
  if (approvedCredits > 0 && !options.yes) {
75486
76611
  if (options.json || !process.stdin.isTTY || !process.stdout.isTTY) {
75487
76612
  throw new Error(`CREDIT_APPROVAL_REQUIRED: This run costs ${approvedCredits} credits. Review skills quote, then rerun with --yes before the skill name.`);
@@ -75530,6 +76655,7 @@ async function handleRun(name, args2, options) {
75530
76655
  try {
75531
76656
  const run = await client.submitQuotedRunWithFiles(skill.name, {}, args2, inputFiles, {
75532
76657
  maxCredits: approvedCredits,
76658
+ quoteReceipt,
75533
76659
  idempotencyKey: options.idempotencyKey ?? runContext.record.id
75534
76660
  });
75535
76661
  if (run.error) {
@@ -75847,7 +76973,7 @@ async function handleExportsDownload(runId, options) {
75847
76973
  const canonicalSkill = typeof remoteRun.skill === "string" ? remoteRun.skill : "remote";
75848
76974
  const requestedSkill = typeof remoteRun.requestedSlug === "string" && remoteRun.requestedSlug.trim() ? remoteRun.requestedSlug : canonicalSkill;
75849
76975
  const exportDir = getRunExportDir(runId, requestedSkill);
75850
- mkdirSync16(exportDir, { recursive: true });
76976
+ mkdirSync17(exportDir, { recursive: true });
75851
76977
  const downloaded = [];
75852
76978
  for (const artifact of artifacts) {
75853
76979
  const artifactId = String(artifact.id || "");
@@ -75855,9 +76981,9 @@ async function handleExportsDownload(runId, options) {
75855
76981
  continue;
75856
76982
  const verified = await client.getVerifiedRunArtifact(runId, artifactId);
75857
76983
  const relativePath = safeArtifactRelativePath(typeof artifact.relativePath === "string" ? artifact.relativePath : artifact.fileName, String(artifact.fileName || artifactId));
75858
- const outputPath = join34(exportDir, relativePath);
76984
+ const outputPath = join35(exportDir, relativePath);
75859
76985
  ensureSafeExportParent(exportDir, relativePath);
75860
- writeFileSync16(outputPath, verified.bytes, { flag: "wx", mode: 384 });
76986
+ writeFileSync17(outputPath, verified.bytes, { flag: "wx", mode: 384 });
75861
76987
  downloaded.push({ id: artifactId, path: outputPath, byteSize: verified.byteSize });
75862
76988
  }
75863
76989
  const payload = {
@@ -75894,14 +77020,14 @@ function ensureSafeExportParent(root, relativePath) {
75894
77020
  let parent = root;
75895
77021
  for (const part of ["", ...parts]) {
75896
77022
  if (part)
75897
- parent = join34(parent, part);
77023
+ parent = join35(parent, part);
75898
77024
  try {
75899
- if (!lstatSync7(parent).isDirectory() || lstatSync7(parent).isSymbolicLink())
77025
+ if (!lstatSync9(parent).isDirectory() || lstatSync9(parent).isSymbolicLink())
75900
77026
  throw new Error("Unsafe artifact directory");
75901
77027
  } catch (error2) {
75902
77028
  if (error2.code !== "ENOENT")
75903
77029
  throw error2;
75904
- mkdirSync16(parent, { mode: 448 });
77030
+ mkdirSync17(parent, { mode: 448 });
75905
77031
  }
75906
77032
  }
75907
77033
  }
@@ -75929,7 +77055,7 @@ async function pollRemoteRun(client, runId, options) {
75929
77055
  const remaining = deadline - Date.now();
75930
77056
  if (remaining <= 0)
75931
77057
  break;
75932
- await new Promise((resolve3) => setTimeout(resolve3, Math.min(options.intervalMs, remaining)));
77058
+ await new Promise((resolve4) => setTimeout(resolve4, Math.min(options.intervalMs, remaining)));
75933
77059
  }
75934
77060
  return {
75935
77061
  run: current ?? { id: runId, status: "queued" },
@@ -76239,7 +77365,7 @@ var init_completion = __esm(() => {
76239
77365
  // src/lib/portable-snapshot-filter.ts
76240
77366
  import { readdirSync as readdirSync16, statSync as statSync17 } from "fs";
76241
77367
  import { homedir as homedir8 } from "os";
76242
- import { join as join35, sep as sep3 } from "path";
77368
+ import { join as join36, sep as sep3 } from "path";
76243
77369
  function isExcludedSkillFileName(fileName) {
76244
77370
  if (EXCLUDE_FILE_NAMES.has(fileName)) {
76245
77371
  return true;
@@ -76262,16 +77388,16 @@ function isPortableWithinSkill(relativeParts) {
76262
77388
  function homePathFor(definition, homesRoot) {
76263
77389
  const home = homesRoot ?? homedir8();
76264
77390
  if (definition.subClass === "skills" || definition.subClass === "custom") {
76265
- return join35(skillsDataRootForHome(home), definition.name);
77391
+ return join36(skillsDataRootForHome(home), definition.name);
76266
77392
  }
76267
77393
  if (definition.agent === "opencode") {
76268
- return join35(home, ".config", "opencode", "skills");
77394
+ return join36(home, ".config", "opencode", "skills");
76269
77395
  }
76270
- return join35(home, `.${definition.agent}`, "skills");
77396
+ return join36(home, `.${definition.agent}`, "skills");
76271
77397
  }
76272
77398
  function destinationFor(definition, stationId, relativePath) {
76273
- const category = definition.subClass === "agent-homes" ? join35("agent-homes", definition.agent ?? "") : definition.name;
76274
- return join35("resources", stationId, "skills", category, ...relativePath.split(sep3));
77399
+ const category = definition.subClass === "agent-homes" ? join36("agent-homes", definition.agent ?? "") : definition.name;
77400
+ return join36("resources", stationId, "skills", category, ...relativePath.split(sep3));
76275
77401
  }
76276
77402
  function walkEntries(absoluteRoot) {
76277
77403
  let entries;
@@ -76282,7 +77408,7 @@ function walkEntries(absoluteRoot) {
76282
77408
  }
76283
77409
  const output = [];
76284
77410
  for (const entry of entries) {
76285
- const childFull = join35(absoluteRoot, entry.name);
77411
+ const childFull = join36(absoluteRoot, entry.name);
76286
77412
  if (entry.isSymbolicLink()) {
76287
77413
  output.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
76288
77414
  continue;
@@ -76293,7 +77419,7 @@ function walkEntries(absoluteRoot) {
76293
77419
  }
76294
77420
  const nested = walkEntries(childFull);
76295
77421
  for (const item of nested) {
76296
- output.push({ ...item, relativePath: join35(entry.name, item.relativePath) });
77422
+ output.push({ ...item, relativePath: join36(entry.name, item.relativePath) });
76297
77423
  }
76298
77424
  continue;
76299
77425
  }
@@ -76386,22 +77512,22 @@ var init_portable_snapshot_filter = __esm(() => {
76386
77512
  });
76387
77513
 
76388
77514
  // src/lib/station-snapshot.ts
76389
- import { createHash as createHash7 } from "crypto";
77515
+ import { createHash as createHash8 } from "crypto";
76390
77516
  import {
76391
77517
  copyFileSync as copyFileSync2,
76392
- mkdirSync as mkdirSync17,
77518
+ mkdirSync as mkdirSync18,
76393
77519
  readFileSync as readFileSync28,
76394
77520
  statSync as statSync18,
76395
- writeFileSync as writeFileSync17
77521
+ writeFileSync as writeFileSync18
76396
77522
  } from "fs";
76397
- import { dirname as dirname13, isAbsolute as isAbsolute5, relative as relative5, resolve as resolve3, sep as sep4 } from "path";
77523
+ import { dirname as dirname14, isAbsolute as isAbsolute7, relative as relative5, resolve as resolve4, sep as sep4 } from "path";
76398
77524
  function validateStationId(stationId) {
76399
77525
  if (!/^[a-z0-9-]+$/.test(stationId)) {
76400
77526
  throw new StationSnapshotError("INVALID_STATION", `station id must be a slug, got: ${stationId}`);
76401
77527
  }
76402
77528
  }
76403
77529
  function sha256File(filePath) {
76404
- return createHash7("sha256").update(readFileSync28(filePath)).digest("hex");
77530
+ return createHash8("sha256").update(readFileSync28(filePath)).digest("hex");
76405
77531
  }
76406
77532
  function scanHome(definition, homesRoot) {
76407
77533
  const homePath = homePathFor(definition, homesRoot);
@@ -76472,7 +77598,7 @@ function humanHomes(scanned) {
76472
77598
  }));
76473
77599
  }
76474
77600
  function writeStationSnapshot(options) {
76475
- const repoRoot = resolve3(options.repoRoot ?? process.cwd());
77601
+ const repoRoot = resolve4(options.repoRoot ?? process.cwd());
76476
77602
  const { scanned, plans, totalBytes } = planStationSnapshot(options);
76477
77603
  const manifestFiles = plans.map((plan) => ({
76478
77604
  relativePath: plan.source.relativePath,
@@ -76498,9 +77624,9 @@ function writeStationSnapshot(options) {
76498
77624
  const conflicts = [];
76499
77625
  const untouched = [];
76500
77626
  for (const plan of plans) {
76501
- const destination = resolve3(repoRoot, plan.destination);
77627
+ const destination = resolve4(repoRoot, plan.destination);
76502
77628
  const destinationRelative = relative5(repoRoot, destination);
76503
- if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep4) || isAbsolute5(destinationRelative)) {
77629
+ if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep4) || isAbsolute7(destinationRelative)) {
76504
77630
  throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
76505
77631
  }
76506
77632
  let existingDigest = null;
@@ -76521,8 +77647,8 @@ function writeStationSnapshot(options) {
76521
77647
  }
76522
77648
  let written = 0;
76523
77649
  for (const plan of untouched) {
76524
- const destination = resolve3(repoRoot, plan.destination);
76525
- mkdirSync17(dirname13(destination), { recursive: true });
77650
+ const destination = resolve4(repoRoot, plan.destination);
77651
+ mkdirSync18(dirname14(destination), { recursive: true });
76526
77652
  copyFileSync2(plan.source.fullPath, destination);
76527
77653
  written += 1;
76528
77654
  }
@@ -76540,9 +77666,9 @@ function writeStationSnapshot(options) {
76540
77666
  },
76541
77667
  files: manifestFiles
76542
77668
  };
76543
- const manifestPath = resolve3(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
76544
- mkdirSync17(dirname13(manifestPath), { recursive: true });
76545
- writeFileSync17(manifestPath, `${JSON.stringify(manifest, null, 2)}
77669
+ const manifestPath = resolve4(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
77670
+ mkdirSync18(dirname14(manifestPath), { recursive: true });
77671
+ writeFileSync18(manifestPath, `${JSON.stringify(manifest, null, 2)}
76546
77672
  `);
76547
77673
  return {
76548
77674
  ...base2,
@@ -76574,7 +77700,7 @@ __export(exports_create_sync_config, {
76574
77700
  registerCreateSync: () => registerCreateSync
76575
77701
  });
76576
77702
  import { existsSync as existsSync31 } from "fs";
76577
- import { join as join36 } from "path";
77703
+ import { join as join37 } from "path";
76578
77704
  function registerCreateSync(parent) {
76579
77705
  const configCmd = parent.command("config").description("Manage skills configuration");
76580
77706
  configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
@@ -76680,7 +77806,7 @@ function handleCreate(name, options) {
76680
77806
  console.log(source_default.green(`\u2713 Created custom skill '${result2.name}' at ${result2.path}`));
76681
77807
  console.log(source_default.dim(` Category: ${result2.manifest.category}`));
76682
77808
  console.log(source_default.dim(` Tags: ${result2.manifest.tags?.join(", ")}`));
76683
- console.log(` ${source_default.cyan("Edit:")} ${join36(result2.path, "src", "index.ts")}`);
77809
+ console.log(` ${source_default.cyan("Edit:")} ${join37(result2.path, "src", "index.ts")}`);
76684
77810
  console.log(` ${source_default.cyan("Run:")} skills run ${result2.name} --help`);
76685
77811
  }
76686
77812
  } catch (error2) {
@@ -76956,30 +78082,30 @@ var init_create_sync_config = __esm(() => {
76956
78082
  });
76957
78083
 
76958
78084
  // src/lib/station-hydrate.ts
76959
- import { createHash as createHash8 } from "crypto";
78085
+ import { createHash as createHash9 } from "crypto";
76960
78086
  import {
76961
78087
  copyFileSync as copyFileSync3,
76962
- mkdirSync as mkdirSync18,
78088
+ mkdirSync as mkdirSync19,
76963
78089
  readdirSync as readdirSync17,
76964
78090
  readFileSync as readFileSync29,
76965
78091
  statSync as statSync19,
76966
- writeFileSync as writeFileSync18
78092
+ writeFileSync as writeFileSync19
76967
78093
  } from "fs";
76968
- import { dirname as dirname14, join as join37, resolve as resolve4, sep as sep5 } from "path";
76969
- function fail3(code, message, detail = []) {
78094
+ import { dirname as dirname15, join as join38, resolve as resolve5, sep as sep5 } from "path";
78095
+ function fail4(code, message, detail = []) {
76970
78096
  throw new StationSnapshotError(code, message, detail);
76971
78097
  }
76972
78098
  function snapshotRootFor(repoRoot, stationId) {
76973
- return join37(repoRoot, "resources", stationId, "skills");
78099
+ return join38(repoRoot, "resources", stationId, "skills");
76974
78100
  }
76975
78101
  function readSnapshotManifest(repoRoot, stationId) {
76976
78102
  const snapshotRoot = snapshotRootFor(repoRoot, stationId);
76977
- const manifestPath = join37(snapshotRoot, "sync-manifest.json");
78103
+ const manifestPath = join38(snapshotRoot, "sync-manifest.json");
76978
78104
  let manifest;
76979
78105
  try {
76980
78106
  manifest = JSON.parse(readFileSync29(manifestPath, "utf8"));
76981
78107
  } catch (error2) {
76982
- fail3("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error2.message}`);
78108
+ fail4("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error2.message}`);
76983
78109
  }
76984
78110
  const sourceSnapshotSha = sha256File(manifestPath);
76985
78111
  return { manifest, manifestPath, sourceSnapshotSha };
@@ -77001,7 +78127,7 @@ function planStationHydration(stationId, repoRoot) {
77001
78127
  const hashMismatches = [];
77002
78128
  const skippedByRule = [];
77003
78129
  for (const agent of SYNC_AGENTS) {
77004
- const agentRoot = join37(snapshotRoot, "agent-homes", agent);
78130
+ const agentRoot = join38(snapshotRoot, "agent-homes", agent);
77005
78131
  let identEntries;
77006
78132
  try {
77007
78133
  identEntries = readdirSync17(agentRoot, { withFileTypes: true });
@@ -77012,7 +78138,7 @@ function planStationHydration(stationId, repoRoot) {
77012
78138
  if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
77013
78139
  continue;
77014
78140
  }
77015
- const identRoot = join37(agentRoot, identEntry.name);
78141
+ const identRoot = join38(agentRoot, identEntry.name);
77016
78142
  const entries = walkEntries(identRoot);
77017
78143
  for (const entry of entries) {
77018
78144
  const relativeParts = [identEntry.name, ...entry.relativePath.split(sep5)];
@@ -77086,10 +78212,10 @@ function planStationHydration(stationId, repoRoot) {
77086
78212
  }
77087
78213
  }
77088
78214
  if (symlinks.length > 0) {
77089
- fail3("SYMLINKS_REFUSED", `${symlinks.length} symlink(s) inside the snapshot; symlinks are refused (fail closed)`);
78215
+ fail4("SYMLINKS_REFUSED", `${symlinks.length} symlink(s) inside the snapshot; symlinks are refused (fail closed)`);
77090
78216
  }
77091
78217
  if (hashMismatches.length > 0) {
77092
- fail3("MANIFEST_HASH_MISMATCH", `${hashMismatches.length} snapshot file(s) no longer match their sync-manifest sha256; ` + "stale or tampered content is refused (fail closed), nothing written \u2014 re-run sync to refresh the manifest", hashMismatches.map((mismatch) => `agent-homes/${mismatch.agent}/${mismatch.ident}/${mismatch.relativePath}`));
78218
+ fail4("MANIFEST_HASH_MISMATCH", `${hashMismatches.length} snapshot file(s) no longer match their sync-manifest sha256; ` + "stale or tampered content is refused (fail closed), nothing written \u2014 re-run sync to refresh the manifest", hashMismatches.map((mismatch) => `agent-homes/${mismatch.agent}/${mismatch.ident}/${mismatch.relativePath}`));
77093
78219
  }
77094
78220
  const byIdent = new Map;
77095
78221
  for (const candidate of candidates) {
@@ -77159,12 +78285,12 @@ function skillSha256(skill) {
77159
78285
  return sha256File(skill.files[0].winner.fullPath);
77160
78286
  }
77161
78287
  const joined = skill.files.map((file) => sha256File(file.winner.fullPath));
77162
- return createHash8("sha256").update(joined.sort().join(`
78288
+ return createHash9("sha256").update(joined.sort().join(`
77163
78289
  `)).digest("hex");
77164
78290
  }
77165
78291
  function writeStationHydration(options) {
77166
- const repoRoot = resolve4(options.repoRoot ?? process.cwd());
77167
- const cacheRoot = resolve4(options.cacheRoot ?? resolveCorpusRoot());
78292
+ const repoRoot = resolve5(options.repoRoot ?? process.cwd());
78293
+ const cacheRoot = resolve5(options.cacheRoot ?? resolveCorpusRoot());
77168
78294
  const plan = planStationHydration(options.stationId, repoRoot);
77169
78295
  const resultSkills = plan.winners.map((skill) => ({
77170
78296
  ident: skill.ident,
@@ -77197,7 +78323,7 @@ function writeStationHydration(options) {
77197
78323
  const toWrite = [];
77198
78324
  for (const skill of plan.winners) {
77199
78325
  for (const file of skill.files) {
77200
- const destination = join37(cacheRoot, skill.ident, file.withinIdent);
78326
+ const destination = join38(cacheRoot, skill.ident, file.withinIdent);
77201
78327
  const digest = sha256File(file.winner.fullPath);
77202
78328
  let existingDigest = null;
77203
78329
  try {
@@ -77214,11 +78340,11 @@ function writeStationHydration(options) {
77214
78340
  }
77215
78341
  }
77216
78342
  if (conflicts.length > 0) {
77217
- fail3("CONFLICT", `${conflicts.length} conflict(s); terminal non-acceptance, nothing written`, conflicts);
78343
+ fail4("CONFLICT", `${conflicts.length} conflict(s); terminal non-acceptance, nothing written`, conflicts);
77218
78344
  }
77219
78345
  let written = 0;
77220
78346
  for (const entry of toWrite) {
77221
- mkdirSync18(dirname14(entry.destination), { recursive: true });
78347
+ mkdirSync19(dirname15(entry.destination), { recursive: true });
77222
78348
  copyFileSync3(entry.fullPath, entry.destination);
77223
78349
  written += 1;
77224
78350
  }
@@ -77239,9 +78365,9 @@ function writeStationHydration(options) {
77239
78365
  },
77240
78366
  skills: resultSkills
77241
78367
  };
77242
- const hydrationManifestPath = join37(dirname14(cacheRoot), `hydration-${options.stationId}.json`);
77243
- mkdirSync18(dirname14(hydrationManifestPath), { recursive: true });
77244
- writeFileSync18(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
78368
+ const hydrationManifestPath = join38(dirname15(cacheRoot), `hydration-${options.stationId}.json`);
78369
+ mkdirSync19(dirname15(hydrationManifestPath), { recursive: true });
78370
+ writeFileSync19(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
77245
78371
  `);
77246
78372
  return {
77247
78373
  ...base2,
@@ -77669,8 +78795,8 @@ var init_schedule = __esm(() => {
77669
78795
  });
77670
78796
 
77671
78797
  // src/lib/registry-sync.ts
77672
- import { mkdirSync as mkdirSync19, writeFileSync as writeFileSync19 } from "fs";
77673
- import { dirname as dirname15, relative as relative6 } from "path";
78798
+ import { mkdirSync as mkdirSync20, writeFileSync as writeFileSync20 } from "fs";
78799
+ import { dirname as dirname16, relative as relative6 } from "path";
77674
78800
  function createRegistrySyncArtifact(options = {}) {
77675
78801
  const profile = options.profile ?? "all";
77676
78802
  const includeDocs = options.includeDocs ?? true;
@@ -77727,8 +78853,8 @@ function createRegistrySyncArtifact(options = {}) {
77727
78853
  };
77728
78854
  }
77729
78855
  function writeRegistrySyncArtifact(path, artifact) {
77730
- mkdirSync19(dirname15(path), { recursive: true });
77731
- writeFileSync19(path, `${JSON.stringify(artifact, null, 2)}
78856
+ mkdirSync20(dirname16(path), { recursive: true });
78857
+ writeFileSync20(path, `${JSON.stringify(artifact, null, 2)}
77732
78858
  `);
77733
78859
  }
77734
78860
  function buildDocs(name) {
@@ -77760,12 +78886,12 @@ function registerRegistry(parent) {
77760
78886
  async function writeJson2(value, space) {
77761
78887
  const text2 = `${JSON.stringify(value, null, space)}
77762
78888
  `;
77763
- await new Promise((resolve5, reject2) => {
78889
+ await new Promise((resolve6, reject2) => {
77764
78890
  process.stdout.write(text2, (error2) => {
77765
78891
  if (error2)
77766
78892
  reject2(error2);
77767
78893
  else
77768
- resolve5();
78894
+ resolve6();
77769
78895
  });
77770
78896
  });
77771
78897
  }
@@ -77793,10 +78919,10 @@ async function handleRegistrySync(options) {
77793
78919
  await writeJson2(artifact, 2);
77794
78920
  return;
77795
78921
  }
77796
- const invalid3 = artifact.summary.invalidSkillCount ?? "not checked";
78922
+ const invalid4 = artifact.summary.invalidSkillCount ?? "not checked";
77797
78923
  console.log(source_default.green(`Registry sync artifact written to ${options.output}`));
77798
78924
  console.log(source_default.dim(` Skills: ${artifact.summary.skillCount}`));
77799
- console.log(source_default.dim(` Invalid: ${invalid3}`));
78925
+ console.log(source_default.dim(` Invalid: ${invalid4}`));
77800
78926
  }
77801
78927
  function registerPull(parent) {
77802
78928
  parent.command("pull").argument("[names...]", "Skills to pull from the configured instance (name or name@version)").option("--all", "Pull every skill the instance serves", false).option("--for-machine", "Prepare this machine with the instance's full catalog (implies --all)", false).option("--json", "Output results as JSON", false).description("Fetch skills from the configured Skills instance into this machine's corpus").action(async (names, options) => {
@@ -77903,7 +79029,7 @@ __export(exports_publish, {
77903
79029
  import { execFileSync } from "child_process";
77904
79030
  import { existsSync as existsSync32, readFileSync as readFileSync30 } from "fs";
77905
79031
  import { hostname as hostname2 } from "os";
77906
- import { join as join38 } from "path";
79032
+ import { join as join39 } from "path";
77907
79033
  function registerPublish(parent) {
77908
79034
  parent.command("push").argument("<name>", "Name of a skill in the local corpus (~/.hasna/skills/installed or the migrated ~/.hasna/skills/skills)").option("--version <version>", "Override the version recorded on the instance").option("--force-new-version", "If name@version already exists with different content, publish as the next patch version", false).option("--dry-run", "Pack and validate without uploading", false).option("--json", "Output result as JSON", false).description("Publish a local skill to the configured skills instance").action(async (name, options) => {
77909
79035
  try {
@@ -77950,7 +79076,7 @@ async function pushSkill(name, options = {}) {
77950
79076
  }
77951
79077
  const packed = packSkillBundle(skill.path, { maxUnpackedBytes: MAX_UNPACKED_BYTES });
77952
79078
  const versionManifest = buildVersionManifest(skill.path, packed);
77953
- const skillMdPath = join38(skill.path, "SKILL.md");
79079
+ const skillMdPath = join39(skill.path, "SKILL.md");
77954
79080
  const skillMd = existsSync32(skillMdPath) ? readFileSync30(skillMdPath, "utf-8") : undefined;
77955
79081
  const base2 = {
77956
79082
  slug: skill.name,
@@ -78016,21 +79142,21 @@ async function readPublishRevision(client, slug) {
78016
79142
  throw new PushSkillError("Publishing was refused because the current skill revision could not be verified.", ["Check the configured instance and connection, then retry the push. No upload was attempted."]);
78017
79143
  }
78018
79144
  const body = lookup.body;
78019
- const record7 = body !== null && typeof body === "object" && !Array.isArray(body) ? body : undefined;
78020
- const nestedError = record7?.error;
78021
- const code = typeof record7?.code === "string" ? record7.code : nestedError !== null && typeof nestedError === "object" && !Array.isArray(nestedError) ? nestedError.code : undefined;
79145
+ const record8 = body !== null && typeof body === "object" && !Array.isArray(body) ? body : undefined;
79146
+ const nestedError = record8?.error;
79147
+ const code = typeof record8?.code === "string" ? record8.code : nestedError !== null && typeof nestedError === "object" && !Array.isArray(nestedError) ? nestedError.code : undefined;
78022
79148
  if (lookup.status === 404 && code === "SKILL_NOT_FOUND")
78023
79149
  return;
78024
79150
  if (lookup.status < 200 || lookup.status >= 300) {
78025
79151
  throw new PushSkillError(`Publishing was refused because the current skill lookup failed: HTTP ${lookup.status}.`, ["Only an explicit SKILL_NOT_FOUND response establishes an initial publish. Check access and server compatibility before retrying."]);
78026
79152
  }
78027
- const revision = record7?.revisionId;
78028
- if (record7?.publicationState === "catalogue-only") {
78029
- if (record7.name === slug && (record7.slug === undefined || record7.slug === slug) && revision === null)
79153
+ const revision = record8?.revisionId;
79154
+ if (record8?.publicationState === "catalogue-only") {
79155
+ if (record8.name === slug && (record8.slug === undefined || record8.slug === slug) && revision === null)
78030
79156
  return;
78031
79157
  throw new PushSkillError("Publishing was refused because the catalogue-only response had contradictory identity or revision state.", ["Check the configured instance and server compatibility. No upload was attempted."]);
78032
79158
  }
78033
- if (record7?.slug !== slug || typeof revision !== "string" || revision.length === 0 || revision !== revision.trim() || !/^[\x21-\x7e]+$/.test(revision)) {
79159
+ if (record8?.slug !== slug || typeof revision !== "string" || revision.length === 0 || revision !== revision.trim() || !/^[\x21-\x7e]+$/.test(revision)) {
78034
79160
  throw new PushSkillError("Publishing was refused because the current skill response did not contain a matching slug and valid revision.", ["Check the configured instance and server compatibility. No upload was attempted."]);
78035
79161
  }
78036
79162
  return revision;
@@ -78191,7 +79317,7 @@ async function readCode() {
78191
79317
  function promptCode() {
78192
79318
  const { stdin, stderr: output } = process;
78193
79319
  const { isRaw: wasRaw, readableFlowing: wasFlowing } = stdin;
78194
- return new Promise((resolve5) => {
79320
+ return new Promise((resolve6) => {
78195
79321
  let value = "", settled = false;
78196
79322
  const finish = (answer) => {
78197
79323
  if (settled)
@@ -78208,7 +79334,7 @@ function promptCode() {
78208
79334
  `);
78209
79335
  if (answer === null)
78210
79336
  process.exitCode = 130;
78211
- resolve5(answer);
79337
+ resolve6(answer);
78212
79338
  };
78213
79339
  const cancel = () => finish(null);
78214
79340
  const keypress = (text2, key) => {
@@ -78248,6 +79374,81 @@ var init_customer_verification = __esm(() => {
78248
79374
  };
78249
79375
  });
78250
79376
 
79377
+ // src/cli/commands/private-publications.ts
79378
+ var exports_private_publications = {};
79379
+ __export(exports_private_publications, {
79380
+ registerPrivatePublications: () => registerPrivatePublications
79381
+ });
79382
+ import { resolve as resolve6 } from "path";
79383
+ function verifyOptions(options, action) {
79384
+ if (options.userId === undefined !== (options.membershipId === undefined) || options.userId !== undefined && (!publicationUuid(options.userId) || !publicationUuid(options.membershipId)))
79385
+ throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide both observed --user-id and --membership-id UUIDs.");
79386
+ if ((action === "publish" || action === "resume" || action === "cancel") && options.confirm !== true)
79387
+ throw new PrivatePublicationError("PUBLICATION_CONFIRM_REQUIRED", "Use --confirm to approve this publication action.");
79388
+ if (action === "publish" && (!publicationUuid(options.skillId) || Boolean(options.expectEmpty) === (options.expectedCurrentVersion !== undefined) || options.expectedCurrentVersion !== undefined && !publicationUuid(options.expectedCurrentVersion) || options.idempotencyKey !== undefined && !publicationUuid(options.idempotencyKey)))
79389
+ throw new PrivatePublicationError("INVALID_PUBLICATION_INPUT", "Provide --skill-id and exactly one of --expect-empty or --expected-current-version; optional --idempotency-key must be a UUID.");
79390
+ if (options.waitSeconds !== undefined && (!/^(?:0|[1-9]\d{0,2})$/.test(options.waitSeconds) || Number(options.waitSeconds) > 300))
79391
+ throw new PrivatePublicationError("INVALID_PUBLICATION_INPUT", "Use --wait-seconds from 0 to 300.");
79392
+ if (!options.codeStdin && (options.json || !process.stdin.isTTY || !process.stderr.isTTY))
79393
+ throw new PrivatePublicationError("PUBLICATION_CODE_REQUIRED", "Use --code-stdin with a fresh code for noninteractive publishing.");
79394
+ }
79395
+ function print2(result2, json) {
79396
+ if (json)
79397
+ console.log(JSON.stringify(result2));
79398
+ else {
79399
+ console.log(`Publication: ${result2.state}`);
79400
+ console.log(`Recovery: ${result2.recoveryDirectory}`);
79401
+ console.log(result2.nextAction);
79402
+ }
79403
+ if (!result2.committed && !["cancelled", "expired"].includes(result2.state))
79404
+ process.exitCode = 2;
79405
+ }
79406
+ function registerPrivatePublications(parent) {
79407
+ const group = parent.command("publication").description("Publish and reconcile private source versions on a compatible hosted server; execution is separate");
79408
+ const publishCommand = group.command("publish").allowExcessArguments(false).description("Publish private source with explicit current-version comparison").requiredOption("--recovery-dir <directory>", "New recovery directory for publish; existing directory for other actions").requiredOption("--email <email>", "Account email for fresh interactive verification").option("--user-id <uuid>", "Observed account UUID (required without an enrolled named profile)").option("--membership-id <uuid>", "Observed workspace membership UUID").option("--code-stdin", "Read a fresh six-digit verification code from stdin").option("--json", "Output a safe result without session credentials or upload URLs").option("--confirm", "Explicitly approve this action").argument("<directory>", "Validated local skill directory").requiredOption("--skill-id <uuid>", "Existing private/team skill UUID").option("--expected-current-version <uuid>", "Compare against the observed current version UUID").option("--expect-empty", "Require the skill to have no current version").option("--idempotency-key <uuid>", "Stable intent key; generated and saved before begin when omitted").option("--wait-seconds <seconds>", "Bounded status wait from 0 to 300", "60");
79409
+ const statusCommand = group.command("status").allowExcessArguments(false).description("Inspect the exact saved publication").requiredOption("--recovery-dir <directory>", "New recovery directory for publish; existing directory for other actions").requiredOption("--email <email>", "Account email for fresh interactive verification").option("--user-id <uuid>", "Observed account UUID (required without an enrolled named profile)").option("--membership-id <uuid>", "Observed workspace membership UUID").option("--code-stdin", "Read a fresh six-digit verification code from stdin").option("--json", "Output a safe result without session credentials or upload URLs");
79410
+ const resumeCommand = group.command("resume").allowExcessArguments(false).description("Reconcile the saved intent without creating a replacement").requiredOption("--recovery-dir <directory>", "New recovery directory for publish; existing directory for other actions").requiredOption("--email <email>", "Account email for fresh interactive verification").option("--user-id <uuid>", "Observed account UUID (required without an enrolled named profile)").option("--membership-id <uuid>", "Observed workspace membership UUID").option("--code-stdin", "Read a fresh six-digit verification code from stdin").option("--json", "Output a safe result without session credentials or upload URLs").option("--confirm", "Explicitly approve this action").option("--wait-seconds <seconds>", "Bounded status wait from 0 to 300", "60");
79411
+ const cancelCommand = group.command("cancel").allowExcessArguments(false).description("Cancel the exact saved publication").requiredOption("--recovery-dir <directory>", "New recovery directory for publish; existing directory for other actions").requiredOption("--email <email>", "Account email for fresh interactive verification").option("--user-id <uuid>", "Observed account UUID (required without an enrolled named profile)").option("--membership-id <uuid>", "Observed workspace membership UUID").option("--code-stdin", "Read a fresh six-digit verification code from stdin").option("--json", "Output a safe result without session credentials or upload URLs").option("--confirm", "Explicitly approve this action");
79412
+ for (const [action, command] of [["publish", publishCommand], ["status", statusCommand], ["resume", resumeCommand], ["cancel", cancelCommand]]) {
79413
+ command.action(async (...args2) => {
79414
+ const options = action === "publish" ? args2[1] : args2[0];
79415
+ try {
79416
+ verifyOptions(options, action);
79417
+ let code;
79418
+ if (options.codeStdin)
79419
+ code = await readCode();
79420
+ else {
79421
+ await new RemoteSkillsAuthClient(getApiUrl("Publish private skills")).requestCode(options.email);
79422
+ code = await promptCode();
79423
+ }
79424
+ if (code === null)
79425
+ return;
79426
+ const context = options.userId ? { userId: options.userId, membershipId: options.membershipId } : undefined;
79427
+ const client = await privatePublicationSession(options.email, code, context), directory = resolve6(options.recoveryDir);
79428
+ if (action === "publish")
79429
+ await preparePrivatePublication(client, resolve6(args2[0]), directory, { skillId: options.skillId, expectedCurrentVersionId: options.expectedCurrentVersion ?? null, idempotencyKey: options.idempotencyKey });
79430
+ const result2 = action === "publish" || action === "resume" ? await continuePrivatePublication(client, directory, { confirm: true, waitMs: Number(options.waitSeconds) * 1000 }) : await inspectPrivatePublication(client, directory, action === "cancel");
79431
+ print2(result2, options.json);
79432
+ } catch (error2) {
79433
+ const result2 = privatePublicationCustomerError(error2);
79434
+ if (options.json)
79435
+ console.log(JSON.stringify(result2));
79436
+ else
79437
+ console.error(result2.error);
79438
+ process.exitCode = 1;
79439
+ }
79440
+ });
79441
+ }
79442
+ }
79443
+ var init_private_publications = __esm(() => {
79444
+ init_remote_auth();
79445
+ init_auth_store();
79446
+ init_private_publication_customer();
79447
+ init_private_publication_recovery();
79448
+ init_remote_private_publications();
79449
+ init_customer_verification();
79450
+ });
79451
+
78251
79452
  // src/cli/commands/workspace-selection.ts
78252
79453
  async function codeFor(client, options) {
78253
79454
  if (!options.email?.includes("@"))
@@ -78326,7 +79527,7 @@ __export(exports_auth, {
78326
79527
  import { createInterface as createInterface2 } from "readline";
78327
79528
  function prompt(question) {
78328
79529
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
78329
- return new Promise((resolve5) => {
79530
+ return new Promise((resolve7) => {
78330
79531
  let settled = false;
78331
79532
  const finish = (answer) => {
78332
79533
  if (settled)
@@ -78335,7 +79536,7 @@ function prompt(question) {
78335
79536
  rl.close();
78336
79537
  if (answer === null)
78337
79538
  process.exitCode = 130;
78338
- resolve5(answer);
79539
+ resolve7(answer);
78339
79540
  };
78340
79541
  rl.once("SIGINT", () => finish(null));
78341
79542
  rl.once("close", () => finish(null));
@@ -78437,7 +79638,7 @@ function printWhoami(payload) {
78437
79638
  console.log(source_default.dim("(offline \u2014 showing cached info)"));
78438
79639
  }
78439
79640
  function sleep(ms) {
78440
- return new Promise((resolve5) => setTimeout(resolve5, ms));
79641
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
78441
79642
  }
78442
79643
  function browserCommand(url) {
78443
79644
  if (process.platform === "darwin")
@@ -78930,7 +80131,7 @@ function promptInvitationProof(kind) {
78930
80131
  const label = kind === "token" ? "invitation token" : "recovery code";
78931
80132
  const character = kind === "token" ? /^[A-Za-z0-9_-]$/ : /^\d$/;
78932
80133
  const { stdin, stderr: output } = process, wasRaw = stdin.isRaw, wasFlowing = stdin.readableFlowing;
78933
- return new Promise((resolve5) => {
80134
+ return new Promise((resolve7) => {
78934
80135
  let value = "", settled = false, overflow = false;
78935
80136
  const finish = (answer) => {
78936
80137
  if (settled)
@@ -78947,7 +80148,7 @@ function promptInvitationProof(kind) {
78947
80148
  `);
78948
80149
  if (answer === null)
78949
80150
  process.exitCode = 130;
78950
- resolve5(answer);
80151
+ resolve7(answer);
78951
80152
  };
78952
80153
  const cancel = () => finish(null);
78953
80154
  const keypress = (text2, key) => {
@@ -79519,8 +80720,8 @@ var init_storage = __esm(() => {
79519
80720
  });
79520
80721
 
79521
80722
  // src/lib/registry-reconcile.ts
79522
- import { existsSync as existsSync33, readFileSync as readFileSync31, statSync as statSync20, writeFileSync as writeFileSync20 } from "fs";
79523
- import { join as join39 } from "path";
80723
+ import { existsSync as existsSync33, readFileSync as readFileSync31, statSync as statSync20, writeFileSync as writeFileSync21 } from "fs";
80724
+ import { join as join40 } from "path";
79524
80725
  function isDirectory2(path) {
79525
80726
  try {
79526
80727
  return statSync20(path).isDirectory();
@@ -79531,11 +80732,11 @@ function isDirectory2(path) {
79531
80732
  function migrationNeeded(options) {
79532
80733
  if (options.rootDir)
79533
80734
  return false;
79534
- const appDir = options.homeDir ? join39(options.homeDir, ".hasna", "skills") : getDataDir();
79535
- return !(isOwnerLayoutMigrated(appDir) && isDirectory2(join39(appDir, SKILLS_CACHE_DIRNAME)));
80735
+ const appDir = options.homeDir ? join40(options.homeDir, ".hasna", "skills") : getDataDir();
80736
+ return !(isOwnerLayoutMigrated(appDir) && isDirectory2(join40(appDir, SKILLS_CACHE_DIRNAME)));
79536
80737
  }
79537
80738
  function readBaseline(skillDir) {
79538
- const markerPath = join39(skillDir, PULL_MARKER_FILE);
80739
+ const markerPath = join40(skillDir, PULL_MARKER_FILE);
79539
80740
  if (!existsSync33(markerPath))
79540
80741
  return;
79541
80742
  try {
@@ -79551,7 +80752,7 @@ function readBaseline(skillDir) {
79551
80752
  }
79552
80753
  }
79553
80754
  function readCursor(root) {
79554
- const path = join39(root, SYNC_CURSOR_FILE);
80755
+ const path = join40(root, SYNC_CURSOR_FILE);
79555
80756
  if (!existsSync33(path))
79556
80757
  return { runCount: 0 };
79557
80758
  try {
@@ -79564,21 +80765,21 @@ function readCursor(root) {
79564
80765
  function resolveCorpusRootReadOnly(options) {
79565
80766
  if (options.rootDir)
79566
80767
  return { root: options.rootDir, migrationPending: false };
79567
- const appDir = options.homeDir ? join39(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
79568
- const cache3 = join39(appDir, SKILLS_CACHE_DIRNAME);
80768
+ const appDir = options.homeDir ? join40(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
80769
+ const cache3 = join40(appDir, SKILLS_CACHE_DIRNAME);
79569
80770
  if (isOwnerLayoutMigrated(appDir) && isDirectory2(cache3)) {
79570
80771
  return { root: cache3, migrationPending: false };
79571
80772
  }
79572
- return { root: join39(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
80773
+ return { root: join40(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
79573
80774
  }
79574
- function remoteRowToSkill(record7) {
79575
- const slug = typeof record7.slug === "string" ? record7.slug : typeof record7.name === "string" ? record7.name : undefined;
80775
+ function remoteRowToSkill(record8) {
80776
+ const slug = typeof record8.slug === "string" ? record8.slug : typeof record8.name === "string" ? record8.name : undefined;
79576
80777
  if (!slug)
79577
80778
  return;
79578
80779
  return {
79579
80780
  slug,
79580
- version: typeof record7.version === "string" ? record7.version : undefined,
79581
- sha256: typeof record7.bundleSha256 === "string" && record7.bundleSha256 ? record7.bundleSha256 : undefined
80781
+ version: typeof record8.version === "string" ? record8.version : undefined,
80782
+ sha256: typeof record8.bundleSha256 === "string" && record8.bundleSha256 ? record8.bundleSha256 : undefined
79582
80783
  };
79583
80784
  }
79584
80785
  function recheckLocalSide(plannedLocal, localDir, ops = {
@@ -79703,7 +80904,7 @@ async function reconcileRegistry(options = {}) {
79703
80904
  for (const slug of allSlugs) {
79704
80905
  const local = locals.get(slug);
79705
80906
  const remote = remotes.get(slug);
79706
- const baseline = local ? readBaseline(join39(root, slug)) : undefined;
80907
+ const baseline = local ? readBaseline(join40(root, slug)) : undefined;
79707
80908
  const { state, reason } = classifySkill(local, remote, baseline);
79708
80909
  let { action, reason: actionReason } = resolveAction(state, direction, conflict);
79709
80910
  if (state === "remote-only" && isDigestless(remote)) {
@@ -79762,7 +80963,7 @@ async function reconcileRegistry(options = {}) {
79762
80963
  try {
79763
80964
  await pushSkill(slug, { rootDir: root, client });
79764
80965
  const pushed = locals.get(slug);
79765
- writePullMarker(join39(root, slug), {
80966
+ writePullMarker(join40(root, slug), {
79766
80967
  skill: slug,
79767
80968
  ...pushed?.version ? { version: pushed.version } : {},
79768
80969
  ...pushed?.sha256 ? { contentHash: pushed.sha256 } : {},
@@ -79834,7 +81035,7 @@ async function reconcileRegistry(options = {}) {
79834
81035
  runCount: readCursor(root).runCount + 1,
79835
81036
  summary
79836
81037
  };
79837
- writeFileSync20(join39(root, SYNC_CURSOR_FILE), `${JSON.stringify(cursor2, null, 2)}
81038
+ writeFileSync21(join40(root, SYNC_CURSOR_FILE), `${JSON.stringify(cursor2, null, 2)}
79838
81039
  `);
79839
81040
  return {
79840
81041
  corpusRoot: root,
@@ -84984,6 +86185,7 @@ import { spawn } from "child_process";
84984
86185
  import { request as nodeHttpRequest } from "http";
84985
86186
  import { request as nodeHttpsRequest } from "https";
84986
86187
  import { randomUUID as randomUUID2 } from "crypto";
86188
+ import { createRequire } from "module";
84987
86189
  function getPathValue(input, path) {
84988
86190
  return path.split(".").reduce((value, part) => {
84989
86191
  if (value && typeof value === "object" && part in value) {
@@ -86081,6 +87283,22 @@ function setPath(input, path, replacement) {
86081
87283
  if (last2 && last2 in cursor)
86082
87284
  cursor[last2] = replacement;
86083
87285
  }
87286
+ var MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
87287
+ var INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
87288
+ var CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
87289
+ var AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
87290
+ var SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
87291
+ var requireSecretsSdk = createRequire(import.meta.url);
87292
+ var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
87293
+ var AUTHORITY_OVERRIDE_HEADERS = new Set([
87294
+ "host",
87295
+ ":authority",
87296
+ "forwarded",
87297
+ "x-forwarded-host",
87298
+ "x-original-host"
87299
+ ]);
87300
+ var MAX_ENVELOPE_BYTES = 256 * 1024;
87301
+ var MAX_REQUEST_BYTES = MAX_ENVELOPE_BYTES * 2 + 8192;
86084
87302
  function createEvent(input) {
86085
87303
  return {
86086
87304
  id: input.id ?? randomUUID2(),
@@ -87780,6 +88998,8 @@ registerPull2(program2);
87780
88998
  registerVersions2(program2);
87781
88999
  var { registerPublish: registerPublish2 } = await Promise.resolve().then(() => (init_publish(), exports_publish));
87782
89000
  registerPublish2(program2);
89001
+ var { registerPrivatePublications: registerPrivatePublications2 } = await Promise.resolve().then(() => (init_private_publications(), exports_private_publications));
89002
+ registerPrivatePublications2(program2);
87783
89003
  var { registerAuth: registerAuth2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
87784
89004
  registerAuth2(program2);
87785
89005
  var { registerCustomerProfileCommands: registerCustomerProfileCommands2 } = await Promise.resolve().then(() => (init_customer_profile(), exports_customer_profile));