@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/mcp.js CHANGED
@@ -5586,7 +5586,7 @@ var package_default;
5586
5586
  var init_package = __esm(() => {
5587
5587
  package_default = {
5588
5588
  name: "@hasna/skills",
5589
- version: "0.5.3",
5589
+ version: "0.5.5",
5590
5590
  description: "Skills library for AI coding agents",
5591
5591
  type: "module",
5592
5592
  bin: {
@@ -6507,6 +6507,7 @@ __export(exports_fleet_credentials, {
6507
6507
  skillsCredentialOrReason: () => skillsCredentialOrReason,
6508
6508
  skillsCredentialFiles: () => skillsCredentialFiles,
6509
6509
  skillsCredentialFilePath: () => skillsCredentialFilePath,
6510
+ skillsApiRequestUrl: () => skillsApiRequestUrl,
6510
6511
  selectsSkillsLocalMode: () => selectsSkillsLocalMode,
6511
6512
  resolveSkillsFleet: () => resolveSkillsFleet,
6512
6513
  resolveSkillsConnection: () => resolveSkillsConnection,
@@ -6547,7 +6548,9 @@ function normalizeSkillsApiOrigin(apiUrl) {
6547
6548
  throw new SkillsFleetCredentialError("A Skills API URL must use HTTPS (or loopback HTTP), without credentials, query or fragment", "INVALID_API_URL");
6548
6549
  }
6549
6550
  const pathname = url.pathname.replace(/\/+$/, "");
6550
- if (pathname === "/api" || pathname === "/api/v1") {
6551
+ if (url.origin === "https://api.hasna.com" && pathname === "/skills/v1") {
6552
+ url.pathname = "/skills";
6553
+ } else if (pathname === "/api" || pathname === "/api/v1") {
6551
6554
  url.pathname = "/";
6552
6555
  } else if (pathname.endsWith("/api/v1")) {
6553
6556
  url.pathname = pathname.slice(0, -"/api/v1".length) || "/";
@@ -6556,6 +6559,21 @@ function normalizeSkillsApiOrigin(apiUrl) {
6556
6559
  }
6557
6560
  return url.toString().replace(/\/+$/, "");
6558
6561
  }
6562
+ function skillsApiRequestUrl(apiUrl, route) {
6563
+ const origin = normalizeSkillsApiOrigin(apiUrl);
6564
+ if (!route.startsWith("/api/") || route.includes("#") || route.includes("\\")) {
6565
+ throw new SkillsFleetCredentialError("Invalid Skills API route", "INVALID_API_URL");
6566
+ }
6567
+ if (origin === "https://api.hasna.com/skills") {
6568
+ if (route === "/api/auth/whoami")
6569
+ return `${origin}/v1/auth/whoami`;
6570
+ if (!route.startsWith("/api/v1/")) {
6571
+ throw new SkillsFleetCredentialError("The internal Skills gateway has no established login contract yet. Select an explicitly configured instance with supported authentication.", "GATEWAY_AUTH_UNAVAILABLE");
6572
+ }
6573
+ return `${origin}${route.slice("/api".length)}`;
6574
+ }
6575
+ return `${origin}${route}`;
6576
+ }
6559
6577
  function configuredSkillsApiUrl(env = process.env, keychain, profile) {
6560
6578
  const declared = SKILLS_API_URL_ENV_KEYS.filter((key) => env[key] !== undefined).map((key) => ({ key, value: env[key] }));
6561
6579
  for (const entry of declared) {
@@ -23273,6 +23291,186 @@ function hashBundleFiles(files) {
23273
23291
  hash.update(part);
23274
23292
  return hash.digest("hex");
23275
23293
  }
23294
+ async function hashBundleFilesCooperatively(files, check2) {
23295
+ const hash = createHash(CONTENT_HASH_ALGORITHM);
23296
+ let bytesSinceYield = 0;
23297
+ for (const part of bundleHashParts(files)) {
23298
+ for (let offset = 0;offset < part.byteLength; offset += 64 * 1024) {
23299
+ check2();
23300
+ const chunk = part.subarray(offset, offset + 64 * 1024);
23301
+ hash.update(chunk);
23302
+ bytesSinceYield += chunk.byteLength;
23303
+ if (bytesSinceYield >= 256 * 1024) {
23304
+ await new Promise((resolve2) => setImmediate(resolve2));
23305
+ bytesSinceYield = 0;
23306
+ }
23307
+ }
23308
+ }
23309
+ check2();
23310
+ return hash.digest("hex");
23311
+ }
23312
+ function invalidContent(message = "Invalid content hash input") {
23313
+ throw new ContentHashInputError("CONTENT_HASH_INVALID", message);
23314
+ }
23315
+ function contentLimit(message) {
23316
+ throw new ContentHashInputError("CONTENT_HASH_LIMIT", message);
23317
+ }
23318
+ function contentRecord(value, allowed) {
23319
+ if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
23320
+ invalidContent();
23321
+ const result = Object.create(null);
23322
+ for (const key of Reflect.ownKeys(value)) {
23323
+ if (typeof key !== "string" || !allowed.includes(key))
23324
+ invalidContent();
23325
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
23326
+ if (!descriptor || !("value" in descriptor))
23327
+ invalidContent("Accessor content hash input is unsupported");
23328
+ result[key] = descriptor.value;
23329
+ }
23330
+ return result;
23331
+ }
23332
+ function contentOptions(options) {
23333
+ const record3 = contentRecord(options, ["limits", "signal"]);
23334
+ const limits = { ...CONTENT_HASH_LIMITS };
23335
+ if (record3.limits !== undefined) {
23336
+ const supplied = contentRecord(record3.limits, Object.keys(limits));
23337
+ for (const key of Object.keys(supplied)) {
23338
+ const value = supplied[key];
23339
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0 || value > limits[key])
23340
+ contentLimit("Invalid content hash limit");
23341
+ limits[key] = value;
23342
+ }
23343
+ }
23344
+ if (record3.signal !== undefined && !(record3.signal instanceof AbortSignal))
23345
+ invalidContent("Invalid content hash signal");
23346
+ return { limits, signal: record3.signal };
23347
+ }
23348
+ function snapshotContentEntries(entries, limits, check2) {
23349
+ if (!Array.isArray(entries))
23350
+ invalidContent("Content hash entries must be an array");
23351
+ if (entries.length > limits.entries)
23352
+ contentLimit("Content hash entry limit exceeded");
23353
+ if (Reflect.ownKeys(entries).length !== entries.length + 1)
23354
+ invalidContent("Invalid content hash entry array");
23355
+ const snapshot = [];
23356
+ const paths = new SkillEntryPaths;
23357
+ let rawBytes = 0;
23358
+ for (let index = 0;index < entries.length; index++) {
23359
+ check2();
23360
+ const descriptor = Object.getOwnPropertyDescriptor(entries, String(index));
23361
+ if (!descriptor || !("value" in descriptor))
23362
+ invalidContent("Invalid content hash entry array");
23363
+ const entry = contentRecord(descriptor.value, ["path", "bytes", "mode"]);
23364
+ if (typeof entry.path !== "string" || typeof entry.mode !== "number" || !Number.isInteger(entry.mode) || entry.mode < 0 || entry.mode > 511)
23365
+ invalidContent("Invalid regular-file content hash entry");
23366
+ paths.add(entry.path, limits.pathBytes, invalidContent, () => contentLimit("Content hash path limit exceeded"));
23367
+ if (!(entry.bytes instanceof Uint8Array) || !ArrayBuffer.isView(entry.bytes))
23368
+ invalidContent("Content hash entry requires bytes");
23369
+ const size = byteLengthOf.call(entry.bytes);
23370
+ if (!(bufferOf.call(entry.bytes) instanceof ArrayBuffer))
23371
+ invalidContent("Shared content hash bytes are unsupported");
23372
+ if (size > limits.fileBytes || rawBytes + size > limits.rawBytes)
23373
+ contentLimit("Content hash raw byte limit exceeded");
23374
+ if (entry.path === "skill.json" && size > limits.manifestBytes)
23375
+ contentLimit("Content hash manifest byte limit exceeded");
23376
+ rawBytes += size;
23377
+ const bytes = new Uint8Array(new ArrayBuffer(size));
23378
+ bytes.set(entry.bytes);
23379
+ snapshot.push({ path: entry.path, bytes, mode: entry.mode });
23380
+ }
23381
+ check2();
23382
+ return snapshot;
23383
+ }
23384
+ function coveredContentPath(path) {
23385
+ const segments = path.split("/");
23386
+ if (!HASH_COVERAGE.includes(segments[0]))
23387
+ return false;
23388
+ return !segments.slice(1).some((segment, index) => excludedHashEntry(segment, index < segments.length - 2));
23389
+ }
23390
+ function boundedManifest(raw, maxDepth) {
23391
+ let parsed;
23392
+ try {
23393
+ parsed = JSON.parse(raw);
23394
+ } catch {
23395
+ return;
23396
+ }
23397
+ const pending = [{ value: parsed, depth: 1 }];
23398
+ while (pending.length) {
23399
+ const { value, depth } = pending.pop();
23400
+ if (!value || typeof value !== "object")
23401
+ continue;
23402
+ if (depth > maxDepth)
23403
+ contentLimit("Content hash manifest depth limit exceeded");
23404
+ for (const child of Object.values(value))
23405
+ pending.push({ value: child, depth: depth + 1 });
23406
+ }
23407
+ return parsed;
23408
+ }
23409
+ async function hashContentEntries(entries, options) {
23410
+ const { limits, signal } = contentOptions(options);
23411
+ const deadline = performance.now() + limits.timeoutMs;
23412
+ let terminal;
23413
+ const abort = () => {
23414
+ terminal ??= new ContentHashInputError("CONTENT_HASH_ABORTED", "Content hashing aborted");
23415
+ };
23416
+ const timer = setTimeout(() => {
23417
+ terminal ??= new ContentHashInputError("CONTENT_HASH_TIMEOUT", "Content hashing deadline exceeded");
23418
+ }, limits.timeoutMs);
23419
+ const check2 = () => {
23420
+ if (signal?.aborted)
23421
+ abort();
23422
+ if (terminal)
23423
+ throw terminal;
23424
+ if (performance.now() >= deadline)
23425
+ throw new ContentHashInputError("CONTENT_HASH_TIMEOUT", "Content hashing deadline exceeded");
23426
+ };
23427
+ try {
23428
+ signal?.addEventListener("abort", abort, { once: true });
23429
+ check2();
23430
+ const snapshot = snapshotContentEntries(entries, limits, check2);
23431
+ const normalized = [];
23432
+ let normalizedBytes = 0;
23433
+ let manifest;
23434
+ await new Promise((resolve2) => setImmediate(resolve2));
23435
+ for (const entry of snapshot) {
23436
+ check2();
23437
+ if (!coveredContentPath(entry.path))
23438
+ continue;
23439
+ if (entry.path === "skill.json")
23440
+ manifest = boundedManifest(new TextDecoder().decode(entry.bytes), limits.manifestDepth);
23441
+ const file = normalizeBundleFile(entry.path, entry.bytes);
23442
+ check2();
23443
+ if (file.content.byteLength > limits.normalizedFileBytes || normalizedBytes + file.content.byteLength > limits.normalizedBytes)
23444
+ contentLimit("Content hash normalized byte limit exceeded");
23445
+ normalizedBytes += file.content.byteLength;
23446
+ normalized.push(file);
23447
+ await new Promise((resolve2) => setImmediate(resolve2));
23448
+ }
23449
+ normalized.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
23450
+ check2();
23451
+ return { hash: await hashBundleFilesCooperatively(normalized, check2), manifest };
23452
+ } catch (error2) {
23453
+ if (error2 instanceof ContentHashInputError)
23454
+ throw error2;
23455
+ throw new ContentHashInputError("CONTENT_HASH_INVALID", "Invalid content hash input");
23456
+ } finally {
23457
+ clearTimeout(timer);
23458
+ signal?.removeEventListener("abort", abort);
23459
+ }
23460
+ }
23461
+ async function verifyContentHashFromEntries(entries, options = {}) {
23462
+ const { hash, manifest } = await hashContentEntries(entries, options);
23463
+ const provenance = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest.provenance : undefined;
23464
+ const value = provenance && typeof provenance === "object" && !Array.isArray(provenance) ? provenance.content_hash : undefined;
23465
+ if (value !== undefined && typeof value !== "string")
23466
+ invalidContent("Invalid content hash declaration");
23467
+ const declaredHash = value?.trim() || undefined;
23468
+ if (!declaredHash)
23469
+ return { declared: false, valid: false };
23470
+ if (!/^[a-f0-9]{64}$/.test(declaredHash))
23471
+ return { declared: true, valid: false, declaredHash };
23472
+ return { declared: true, valid: hash === declaredHash, declaredHash, computedHash: hash };
23473
+ }
23276
23474
  function verifyContentHash(skillPath, manifest) {
23277
23475
  const declaredHash = manifest?.provenance?.content_hash?.trim() || undefined;
23278
23476
  if (!declaredHash)
@@ -23288,7 +23486,7 @@ function verifyContentHash(skillPath, manifest) {
23288
23486
  computedHash
23289
23487
  };
23290
23488
  }
23291
- var CONTENT_HASH_ALGORITHM = "sha256", HASH_EXCLUDE_DIRS, HASH_COVERAGE, CONTENT_HASH_LIMITS, typedArrayPrototype, byteLengthOf, bufferOf;
23489
+ var CONTENT_HASH_ALGORITHM = "sha256", HASH_EXCLUDE_DIRS, HASH_COVERAGE, CONTENT_HASH_LIMITS, ContentHashInputError, typedArrayPrototype, byteLengthOf, bufferOf;
23292
23490
  var init_skill_hash = __esm(() => {
23293
23491
  HASH_EXCLUDE_DIRS = new Set([".git", "node_modules", "dist", "build", ".turbo"]);
23294
23492
  HASH_COVERAGE = [
@@ -23313,6 +23511,14 @@ var init_skill_hash = __esm(() => {
23313
23511
  manifestDepth: 64,
23314
23512
  timeoutMs: 5000
23315
23513
  });
23514
+ ContentHashInputError = class ContentHashInputError extends Error {
23515
+ code;
23516
+ constructor(code, message) {
23517
+ super(message);
23518
+ this.code = code;
23519
+ this.name = "ContentHashInputError";
23520
+ }
23521
+ };
23316
23522
  typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
23317
23523
  byteLengthOf = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength").get;
23318
23524
  bufferOf = Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
@@ -24898,7 +25104,14 @@ function getConfiguredApiUrl(env = process.env) {
24898
25104
  }
24899
25105
  function buildSkillsApiUrl(apiUrl, endpoint = "/skills") {
24900
25106
  const url = new URL(apiUrl);
25107
+ if (url.origin === "https://api.hasna.com" && /^\/skills\/(?:api\/)?v1\/skills\/?$/.test(url.pathname)) {
25108
+ url.pathname = "/skills";
25109
+ apiUrl = url.toString();
25110
+ }
24901
25111
  const cleanEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
25112
+ if (normalizeSkillsApiOrigin(apiUrl) === "https://api.hasna.com/skills") {
25113
+ return skillsApiRequestUrl(apiUrl, `/api/v1${cleanEndpoint}`);
25114
+ }
24902
25115
  const pathname = url.pathname.replace(/\/+$/, "");
24903
25116
  const apiBase = /\/api(?:\/v1)?\/skills$/.test(pathname) ? pathname.slice(0, -"/skills".length) : pathname;
24904
25117
  if (/\/api(?:\/v1)?$/.test(apiBase)) {
@@ -25800,7 +26013,7 @@ var MCP_CONTRACT_SCHEMA_VERSION = 1, stringSchema = (description) => ({
25800
26013
  type: "array",
25801
26014
  items,
25802
26015
  ...description ? { description } : {}
25803
- }), skillNameInput, optionalAgentInput, scopeInput, runInputSchema, runArgsSchema, errorSchema, skillSummarySchema, toolPrimitiveSummarySchema, skillToolDependencySchema, validationMessageSchema, validationOutputSchema, installOutputSchema, runOutputSchema, toolContracts, remoteCustomerContracts, contracts, resourceContracts;
26016
+ }), skillNameInput, optionalAgentInput, scopeInput, runInputSchema, runArgsSchema, errorSchema, skillSummarySchema, toolPrimitiveSummarySchema, skillToolDependencySchema, validationMessageSchema, validationOutputSchema, installOutputSchema, runOutputSchema, toolContracts, remoteCustomerContracts, publicationUuidSchema, publicationVerification, privatePublicationContracts, contracts, resourceContracts;
25804
26017
  var init_mcp_contracts = __esm(() => {
25805
26018
  init_remote_customer_operations();
25806
26019
  skillNameInput = stringSchema("skill name or alias.");
@@ -26306,7 +26519,7 @@ var init_mcp_contracts = __esm(() => {
26306
26519
  name: "run_skill",
26307
26520
  title: "Run Skill",
26308
26521
  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.",
26309
- params: ["name", "input?", "args?", "detail?", "remote?", "maxCredits?", "maxCostCents?", "idempotency_key?", "files?"],
26522
+ params: ["name", "input?", "args?", "detail?", "remote?", "maxCredits?", "maxCostCents?", "quoteReceipt?", "idempotency_key?", "files?"],
26310
26523
  category: "execution",
26311
26524
  sideEffects: "local-process-or-remote-run",
26312
26525
  stable: true,
@@ -26318,6 +26531,7 @@ var init_mcp_contracts = __esm(() => {
26318
26531
  remote: { type: "boolean", description: "Use the configured server catalog." },
26319
26532
  maxCredits: { type: "integer", minimum: 0, description: "Maximum explicitly approved integer credits; omitted permits only free remote runs." },
26320
26533
  maxCostCents: { type: "integer", minimum: 0, description: "Legacy alias for maxCredits; both must agree." },
26534
+ 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." },
26321
26535
  idempotency_key: { type: "string", pattern: "^[A-Za-z0-9._:-]{1,128}$", description: "Stable retry key for the same remote submission." },
26322
26536
  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." }
26323
26537
  }, ["name"]),
@@ -26601,12 +26815,17 @@ var init_mcp_contracts = __esm(() => {
26601
26815
  name: "quote_skill",
26602
26816
  title: "Quote Remote Skill",
26603
26817
  description: "Get a server credit quote without submitting a run.",
26604
- params: ["name", "input?", "args?"],
26818
+ params: ["name", "input?", "args?", "files?"],
26605
26819
  category: "execution",
26606
26820
  sideEffects: "none",
26607
26821
  stable: true,
26608
- inputSchema: objectSchema({ name: skillNameInput, input: runInputSchema, args: runArgsSchema }, ["name"]),
26609
- outputSchema: objectSchema({ skill: stringSchema("Canonical server skill."), pricing: objectSchema({}, [], "Quoted integer credits.", true) }, ["skill", "pricing"], undefined, true)
26822
+ inputSchema: objectSchema({
26823
+ name: skillNameInput,
26824
+ input: runInputSchema,
26825
+ args: runArgsSchema,
26826
+ 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." }
26827
+ }, ["name"]),
26828
+ 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)
26610
26829
  });
26611
26830
  remoteCustomerContracts.push({
26612
26831
  name: "download_run_artifact",
@@ -26619,7 +26838,47 @@ var init_mcp_contracts = __esm(() => {
26619
26838
  inputSchema: objectSchema({ run_id: stringSchema("Run identifier."), artifact_id: stringSchema("Artifact identifier.") }, ["run_id", "artifact_id"]),
26620
26839
  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"])
26621
26840
  });
26622
- contracts = [...toolContracts, ...remoteCustomerContracts].sort((a, b) => a.name.localeCompare(b.name));
26841
+ publicationUuidSchema = { type: "string", pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" };
26842
+ publicationVerification = {
26843
+ email: { type: "string", format: "email", maxLength: 254 },
26844
+ code: { type: "string", pattern: "^\\d{6}$" },
26845
+ userId: publicationUuidSchema,
26846
+ membershipId: publicationUuidSchema,
26847
+ recoveryDirectory: { type: "string", maxLength: 4096, description: "Absolute host-local recovery directory without symbolic links." }
26848
+ };
26849
+ privatePublicationContracts = [
26850
+ { name: "publish_private_skill", title: "Publish private skill", extras: {
26851
+ directory: { type: "string", maxLength: 4096, description: "Absolute local skill source directory." },
26852
+ skillId: publicationUuidSchema,
26853
+ expectedCurrentVersionId: { oneOf: [publicationUuidSchema, { type: "null" }] },
26854
+ idempotencyKey: publicationUuidSchema,
26855
+ confirm: { const: true },
26856
+ waitMs: { type: "integer", minimum: 0, maximum: 300000 }
26857
+ }, required: ["directory", "skillId", "expectedCurrentVersionId", "confirm"] },
26858
+ { name: "get_private_publication", title: "Get private publication", extras: {}, required: [] },
26859
+ { name: "resume_private_publication", title: "Resume private publication", extras: { confirm: { const: true }, waitMs: { type: "integer", minimum: 0, maximum: 300000 } }, required: ["confirm"] },
26860
+ { name: "cancel_private_publication", title: "Cancel private publication", extras: { confirm: { const: true } }, required: ["confirm"] }
26861
+ ].map((operation) => ({
26862
+ name: operation.name,
26863
+ title: operation.title,
26864
+ 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.",
26865
+ params: [...Object.keys(publicationVerification), ...Object.keys(operation.extras)],
26866
+ category: "storage",
26867
+ sideEffects: "filesystem",
26868
+ stable: true,
26869
+ inputSchema: objectSchema({ ...publicationVerification, ...operation.extras }, [...Object.keys(publicationVerification), ...operation.required]),
26870
+ outputSchema: objectSchema({
26871
+ recoveryDirectory: { type: "string" },
26872
+ skillId: publicationUuidSchema,
26873
+ intentId: { oneOf: [publicationUuidSchema, { type: "null" }] },
26874
+ state: { type: "string" },
26875
+ versionId: { oneOf: [publicationUuidSchema, { type: "null" }] },
26876
+ committed: { type: "boolean" },
26877
+ executionEnabled: { const: false },
26878
+ nextAction: { type: "string" }
26879
+ }, ["recoveryDirectory", "skillId", "intentId", "state", "versionId", "committed", "executionEnabled", "nextAction"])
26880
+ }));
26881
+ contracts = [...toolContracts, ...remoteCustomerContracts, ...privatePublicationContracts].sort((a, b) => a.name.localeCompare(b.name));
26623
26882
  resourceContracts = [
26624
26883
  {
26625
26884
  uri: "skills://mcp/contracts",
@@ -28321,8 +28580,17 @@ function creditCount(value) {
28321
28580
  }
28322
28581
  return value;
28323
28582
  }
28583
+ function runQuoteReceipt(value) {
28584
+ if (value === undefined)
28585
+ return;
28586
+ if (typeof value !== "string" || !value.length || Buffer.byteLength(value, "utf8") > 4096) {
28587
+ throw new Error("Invalid quote receipt");
28588
+ }
28589
+ return value;
28590
+ }
28324
28591
  function parseRemoteRunQuote(value) {
28325
28592
  const quote = object4(value);
28593
+ runQuoteReceipt(quote.quoteReceipt);
28326
28594
  const pricing = object4(quote.pricing);
28327
28595
  if (typeof quote.skill !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(quote.skill))
28328
28596
  throw new Error("Invalid quoted skill");
@@ -28424,6 +28692,72 @@ function parseUpdatedWorkspace(value) {
28424
28692
  return { organization: { id: organization2.id, slug: organization2.slug, name: organization2.name } };
28425
28693
  }
28426
28694
 
28695
+ // src/lib/remote-quote-errors.ts
28696
+ async function readQuoteUnavailableCode(response) {
28697
+ const maximum = 16 * 1024;
28698
+ const length = response.headers.get("content-length");
28699
+ if (response.status !== 503 || response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() !== "application/json" || length !== null && (!/^\d+$/.test(length) || Number(length) > maximum)) {
28700
+ response.body?.cancel().catch(() => {});
28701
+ return null;
28702
+ }
28703
+ const reader = response.body?.getReader();
28704
+ if (!reader)
28705
+ return null;
28706
+ let timer;
28707
+ let deadlineExceeded = false;
28708
+ const expired = Symbol("quote body deadline");
28709
+ const deadline = new Promise((resolve2) => {
28710
+ timer = setTimeout(() => {
28711
+ deadlineExceeded = true;
28712
+ resolve2(expired);
28713
+ reader.cancel().catch(() => {});
28714
+ }, 1500);
28715
+ });
28716
+ const chunks = [];
28717
+ let size = 0;
28718
+ try {
28719
+ while (true) {
28720
+ const next = await Promise.race([reader.read(), deadline]);
28721
+ if (next === expired || deadlineExceeded)
28722
+ return null;
28723
+ if (next.done)
28724
+ break;
28725
+ size += next.value.byteLength;
28726
+ if (size > maximum)
28727
+ return null;
28728
+ chunks.push(next.value);
28729
+ }
28730
+ const bytes = new Uint8Array(size);
28731
+ let offset = 0;
28732
+ for (const chunk of chunks) {
28733
+ bytes.set(chunk, offset);
28734
+ offset += chunk.byteLength;
28735
+ }
28736
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
28737
+ if (!value || typeof value !== "object" || Array.isArray(value))
28738
+ return null;
28739
+ const code = value.code;
28740
+ return typeof code === "string" && Object.hasOwn(quoteUnavailableMessages, code) ? code : null;
28741
+ } catch {
28742
+ return null;
28743
+ } finally {
28744
+ clearTimeout(timer);
28745
+ reader.cancel().catch(() => {});
28746
+ reader.releaseLock();
28747
+ }
28748
+ }
28749
+ var quoteUnavailableMessages;
28750
+ var init_remote_quote_errors = __esm(() => {
28751
+ quoteUnavailableMessages = Object.freeze({
28752
+ HOSTED_PROVIDER_UNAVAILABLE: "Hosted execution is temporarily unavailable on this Skills instance.",
28753
+ HOSTED_CONNECTORS_UNAVAILABLE: "Hosted connector execution is unavailable on this Skills instance.",
28754
+ SKILL_IMPLEMENTATION_UNAVAILABLE: "This skill has no hosted execution implementation.",
28755
+ HOSTED_PRICING_UNAVAILABLE: "Hosted execution is unavailable while this skill's pricing is reviewed.",
28756
+ RUNTIME_ALLOWLIST_REQUIRED: "Hosted execution is unavailable until this Skills instance enables its skill catalog.",
28757
+ RUNTIME_SKILL_NOT_ALLOWED: "This skill is not enabled for hosted execution on this Skills instance."
28758
+ });
28759
+ });
28760
+
28427
28761
  // src/lib/remote-client.ts
28428
28762
  var exports_remote_client = {};
28429
28763
  __export(exports_remote_client, {
@@ -28434,6 +28768,7 @@ __export(exports_remote_client, {
28434
28768
  RemoteSkillsClient: () => RemoteSkillsClient,
28435
28769
  RemoteRouteUnsupportedError: () => RemoteRouteUnsupportedError,
28436
28770
  RemoteRequestError: () => RemoteRequestError,
28771
+ RemoteQuoteUnavailableError: () => RemoteQuoteUnavailableError,
28437
28772
  RemoteCapabilityUnavailableError: () => RemoteCapabilityUnavailableError
28438
28773
  });
28439
28774
 
@@ -28446,7 +28781,7 @@ class RemoteSkillsClient {
28446
28781
  this.apiUrl = normalizeSkillsApiOrigin(apiUrl);
28447
28782
  }
28448
28783
  async request(path, options) {
28449
- return fetch(`${this.apiUrl}${path}`, {
28784
+ return fetch(skillsApiRequestUrl(this.apiUrl, path), {
28450
28785
  ...options,
28451
28786
  redirect: "error",
28452
28787
  credentials: "omit",
@@ -28469,6 +28804,11 @@ class RemoteSkillsClient {
28469
28804
  throw new RemoteRouteUnsupportedError(routePath, response.status, this.apiUrl);
28470
28805
  }
28471
28806
  if (!response.ok) {
28807
+ if (opts.quoteRefusal && options?.method === "POST" && /^\/api\/v1\/skills\/[^/?#]+\/quote$/.test(routePath) && response.status === 503) {
28808
+ const code = await readQuoteUnavailableCode(response);
28809
+ if (code)
28810
+ throw new RemoteQuoteUnavailableError(routePath, code);
28811
+ }
28472
28812
  if (path === "/api/v1/billing/checkout" && options?.method === "POST" && response.status === 503 && await responseBodyCarriesCode(response, ["SUBSCRIPTION_CHECKOUT_UNAVAILABLE"])) {
28473
28813
  throw new RemoteCapabilityUnavailableError;
28474
28814
  }
@@ -28501,6 +28841,7 @@ class RemoteSkillsClient {
28501
28841
  return { status: res.status, body };
28502
28842
  }
28503
28843
  async submitRun(slug, input, args, approval = {}) {
28844
+ const quoteReceipt = runQuoteReceipt(approval.quoteReceipt);
28504
28845
  if (approval.idempotencyKey !== undefined && !/^[A-Za-z0-9._:-]{1,128}$/.test(approval.idempotencyKey))
28505
28846
  throw new Error("Idempotency key must be 1-128 URL-safe characters");
28506
28847
  if (approval.maxCostCents !== undefined)
@@ -28517,16 +28858,21 @@ class RemoteSkillsClient {
28517
28858
  ...approval.maxCredits !== undefined ? { maxCredits: approval.maxCredits } : {},
28518
28859
  ...approval.maxCostCents !== undefined ? { maxCostCents: approval.maxCostCents } : {},
28519
28860
  ...approval.idempotencyKey !== undefined ? { idempotencyKey: approval.idempotencyKey } : {},
28520
- ...approval.inputFiles !== undefined ? { files: approval.inputFiles } : {}
28861
+ ...approval.inputFiles !== undefined ? { files: approval.inputFiles } : {},
28862
+ ...quoteReceipt !== undefined ? { quoteReceipt } : {}
28521
28863
  })
28522
28864
  });
28865
+ if (!res.ok) {
28866
+ res.body?.cancel().catch(() => {});
28867
+ throw new RemoteRequestError(`/api/v1/runs/${encodeURIComponent(slug)}`, res.status);
28868
+ }
28523
28869
  return normalizeRemoteSkillRunContract(await res.json(), slug);
28524
28870
  }
28525
- async quoteRun(slug, input = {}, args = []) {
28871
+ async quoteRun(slug, input = {}, args = [], files) {
28526
28872
  const response = await this.requestNewRoute(`/api/v1/skills/${encodeURIComponent(slug)}/quote`, {
28527
28873
  method: "POST",
28528
- body: JSON.stringify({ input, args })
28529
- });
28874
+ body: JSON.stringify({ input, args, ...files === undefined ? {} : { files } })
28875
+ }, { quoteRefusal: true });
28530
28876
  return parseRemoteRunQuote(await response.json());
28531
28877
  }
28532
28878
  getCapabilities() {
@@ -28541,17 +28887,24 @@ class RemoteSkillsClient {
28541
28887
  return this.capabilities;
28542
28888
  }
28543
28889
  async submitQuotedRun(slug, input = {}, args = [], approval = {}) {
28890
+ runQuoteReceipt(approval.quoteReceipt);
28891
+ ({ input, args, approval } = JSON.parse(JSON.stringify({ input, args, approval })));
28544
28892
  const maximum = creditCount(approval.maxCredits ?? approval.maxCostCents ?? 0);
28545
28893
  if (approval.maxCostCents !== undefined && approval.maxCostCents !== maximum)
28546
28894
  throw new Error("Credit approval fields disagree");
28547
- const quote = await this.quoteRun(slug, input, args);
28548
- if (quote.pricing.costCents > maximum)
28895
+ const quote = approval.quoteReceipt === undefined ? await this.quoteRun(slug, input, args, approval.inputFiles?.length ? approval.inputFiles : undefined) : undefined;
28896
+ if (quote && quote.pricing.costCents > maximum)
28549
28897
  throw new RemoteCreditApprovalError(quote.pricing.costCents, maximum);
28550
28898
  const capabilities = await this.getCapabilities();
28551
28899
  if (!capabilities.capabilities.includes("runs.submit") || capabilities.billing?.boundedRunApproval !== true || capabilities.billing.unit !== "credits") {
28552
28900
  throw new Error("The configured server does not support bounded credit approval; refusing remote submission");
28553
28901
  }
28554
- return this.submitRun(quote.skill, input, args, { ...approval, maxCredits: maximum, maxCostCents: maximum });
28902
+ return this.submitRun(quote?.skill ?? slug, input, args, {
28903
+ ...approval,
28904
+ maxCredits: maximum,
28905
+ maxCostCents: maximum,
28906
+ ...(quote?.quoteReceipt ?? approval.quoteReceipt) === undefined ? {} : { quoteReceipt: quote?.quoteReceipt ?? approval.quoteReceipt }
28907
+ });
28555
28908
  }
28556
28909
  async getIdentity() {
28557
28910
  return (await this.requestNewRoute("/api/auth/whoami")).json();
@@ -28839,6 +29192,10 @@ class RemoteSkillsClient {
28839
29192
  return { id: artifactId, fileName: String(artifact.fileName ?? artifactId), bytes, byteSize: bytes.byteLength, sha256: artifact.sha256 };
28840
29193
  }
28841
29194
  async submitQuotedRunWithFiles(slug, input, args, files, approval = {}) {
29195
+ runQuoteReceipt(approval.quoteReceipt);
29196
+ ({ input, args, approval } = JSON.parse(JSON.stringify({ input, args, approval })));
29197
+ describeRemoteFiles(files);
29198
+ files = files.map((file) => ({ name: file.name, contentType: file.contentType, bytes: new Uint8Array(file.bytes) }));
28842
29199
  const inputFiles = describeRemoteFiles(files);
28843
29200
  if (files.length && !(await this.getCapabilities()).capabilities.includes("runs.uploads"))
28844
29201
  throw new Error("The configured server does not support input uploads");
@@ -28902,7 +29259,7 @@ class RemoteSkillsClient {
28902
29259
  const headers = { Authorization: `Bearer ${this.apiKey}` };
28903
29260
  if (ifMatch)
28904
29261
  headers["If-Match"] = ifMatch;
28905
- return fetch(`${this.apiUrl}/api/v1/skills`, {
29262
+ return fetch(skillsApiRequestUrl(this.apiUrl, "/api/v1/skills"), {
28906
29263
  method: "POST",
28907
29264
  headers,
28908
29265
  body: form,
@@ -29125,7 +29482,7 @@ async function createRemoteSkillsClient(env = process.env) {
29125
29482
  function createRemoteSkillsClientReadOnly(env = process.env) {
29126
29483
  return createRemoteSkillsClient(env);
29127
29484
  }
29128
- var RemoteRouteUnsupportedError, RemoteRequestError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
29485
+ var RemoteRouteUnsupportedError, RemoteRequestError, RemoteQuoteUnavailableError, RemoteWorkspaceMemberError, RemoteWorkspaceSelectionError, RemoteCapabilityUnavailableError, INVALID_SKILL_VERSION_RESPONSE = "Remote skill version payload did not match the expected contract.";
29129
29486
  var init_remote_client = __esm(() => {
29130
29487
  init_remote_invitations();
29131
29488
  init_remote_workspace_leave();
@@ -29136,6 +29493,7 @@ var init_remote_client = __esm(() => {
29136
29493
  init_fleet_credentials();
29137
29494
  init_remote_account();
29138
29495
  init_remote_files();
29496
+ init_remote_quote_errors();
29139
29497
  RemoteRouteUnsupportedError = class RemoteRouteUnsupportedError extends Error {
29140
29498
  path;
29141
29499
  status;
@@ -29158,6 +29516,17 @@ var init_remote_client = __esm(() => {
29158
29516
  this.name = "RemoteRequestError";
29159
29517
  }
29160
29518
  };
29519
+ RemoteQuoteUnavailableError = class RemoteQuoteUnavailableError extends RemoteRequestError {
29520
+ code;
29521
+ constructor(path, code) {
29522
+ super(path, 503);
29523
+ this.code = code;
29524
+ if (!Object.hasOwn(quoteUnavailableMessages, code))
29525
+ throw new Error("Unknown quote refusal code");
29526
+ this.name = "RemoteQuoteUnavailableError";
29527
+ this.message = quoteUnavailableMessages[code];
29528
+ }
29529
+ };
29161
29530
  RemoteWorkspaceMemberError = class RemoteWorkspaceMemberError extends RemoteRequestError {
29162
29531
  code;
29163
29532
  constructor(path, code) {
@@ -29395,11 +29764,12 @@ function registerOperationTools(server) {
29395
29764
  detail: exports_external.boolean().optional(),
29396
29765
  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"),
29397
29766
  maxCredits: exports_external.number().int().min(0).max(2147483647).optional(),
29767
+ 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."),
29398
29768
  remote: exports_external.boolean().optional().describe("Use the configured server catalog, including skills not installed locally"),
29399
29769
  idempotency_key: exports_external.string().regex(/^[A-Za-z0-9._:-]{1,128}$/).optional().describe("Reuse for the same approved submission after an interrupted response"),
29400
29770
  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")
29401
29771
  }
29402
- }, async ({ name, input, args, detail, maxCostCents, maxCredits, remote, idempotency_key, files }) => {
29772
+ }, async ({ name, input, args, detail, maxCostCents, maxCredits, quoteReceipt, remote, idempotency_key, files }) => {
29403
29773
  const skill = remote ? { name, serverOwned: true } : getSkill(name);
29404
29774
  if (!skill) {
29405
29775
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
@@ -29440,7 +29810,7 @@ function registerOperationTools(server) {
29440
29810
  try {
29441
29811
  const { RemoteSkillsClient: RemoteSkillsClient2 } = await Promise.resolve().then(() => (init_remote_client(), exports_remote_client));
29442
29812
  const client = new RemoteSkillsClient2(routing.apiKey, routing.apiOrigin);
29443
- const run = await client.submitQuotedRunWithFiles(skillName, runInput, runArgs, inputFiles, { maxCredits, maxCostCents, idempotencyKey: idempotency_key ?? runContext.record.id });
29813
+ const run = await client.submitQuotedRunWithFiles(skillName, runInput, runArgs, inputFiles, { maxCredits, maxCostCents, quoteReceipt, idempotencyKey: idempotency_key ?? runContext.record.id });
29444
29814
  if (run.error) {
29445
29815
  writeRunLogs(runContext, "", String(run.error) + `
29446
29816
  `);
@@ -30434,7 +30804,7 @@ async function requestInvitationEmail(origin, action, input) {
30434
30804
  }
30435
30805
  const body = JSON.stringify({ invitationId: value.invitationId, token: value.token, challengeId: value.challengeId, ...action === "accept" ? { code: value.code } : {} });
30436
30806
  try {
30437
- const response = await fetch(`${target}/api/v1/account/invitations/email-${action}`, {
30807
+ const response = await fetch(skillsApiRequestUrl(target, `/api/v1/account/invitations/email-${action}`), {
30438
30808
  method: "POST",
30439
30809
  headers: { "Content-Type": "application/json" },
30440
30810
  body,
@@ -30509,14 +30879,318 @@ var init_remote_invitation_recovery = __esm(() => {
30509
30879
  };
30510
30880
  });
30511
30881
 
30882
+ // src/lib/remote-private-publications.ts
30883
+ import { createHash as createHash5 } from "crypto";
30884
+ function checkedPublicationDeclaration(value) {
30885
+ 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)
30886
+ return bad();
30887
+ try {
30888
+ const manifest = JSON.parse(value.manifestText);
30889
+ if (!record7(manifest) || manifest.version !== value.version || validatePortableManifestContract(manifest, { strict: true }).length || !hash(manifest.provenance?.content_hash))
30890
+ return bad();
30891
+ } catch {
30892
+ return bad();
30893
+ }
30894
+ return Object.freeze({ ...value });
30895
+ }
30896
+ function checkedPublicationView(value, skillId, intentId, declaration) {
30897
+ 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)
30898
+ return invalid3();
30899
+ if (declaration && ["version", "expectedCurrentVersionId", "archiveSha256", "archiveByteSize"].some((k) => value[k] !== declaration[k]))
30900
+ return invalid3();
30901
+ return Object.freeze({ ...value });
30902
+ }
30903
+ async function boundedJson(url, init, token, mutation, budget = 15000, signal) {
30904
+ const controller = new AbortController;
30905
+ let reader;
30906
+ let response;
30907
+ const timeout = new PrivatePublicationError("PUBLICATION_UNCONFIRMED", "No confirmed publication result. Inspect the saved intent before retrying.", mutation);
30908
+ let timer;
30909
+ let abort;
30910
+ try {
30911
+ return await Promise.race([(async () => {
30912
+ if (signal?.aborted)
30913
+ throw timeout;
30914
+ response = await fetch(url, {
30915
+ ...init,
30916
+ redirect: "error",
30917
+ credentials: "omit",
30918
+ signal: controller.signal,
30919
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }
30920
+ });
30921
+ const length = response.headers.get("content-length");
30922
+ if (length !== null && (!/^\d+$/.test(length) || Number(length) > 65536))
30923
+ return invalid3();
30924
+ reader = response.body?.getReader();
30925
+ const chunks = [];
30926
+ let size = 0;
30927
+ if (reader)
30928
+ while (true) {
30929
+ const part = await reader.read();
30930
+ if (part.done)
30931
+ break;
30932
+ size += part.value.byteLength;
30933
+ if (size > 65536)
30934
+ return invalid3();
30935
+ chunks.push(part.value);
30936
+ }
30937
+ if (controller.signal.aborted)
30938
+ throw timeout;
30939
+ const bytes = Buffer.concat(chunks);
30940
+ let body;
30941
+ try {
30942
+ body = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
30943
+ } catch {
30944
+ return invalid3();
30945
+ }
30946
+ if (!response.ok) {
30947
+ const code = record7(body) && typeof body.code === "string" && Object.hasOwn(failures, body.code) ? body.code : null;
30948
+ if (code && failures[code][0] === response.status)
30949
+ throw new PrivatePublicationError(code, failures[code][1], mutation && code === "PUBLICATION_UNCERTAIN", response.status);
30950
+ throw new PrivatePublicationError("PUBLICATION_REQUEST_FAILED", "The publication request was refused. Inspect its status before retrying.", mutation && response.status >= 500, response.status);
30951
+ }
30952
+ return body;
30953
+ })(), new Promise((_, reject) => {
30954
+ abort = () => {
30955
+ controller.abort();
30956
+ reject(timeout);
30957
+ };
30958
+ timer = setTimeout(abort, Math.max(1, Math.min(15000, budget)));
30959
+ signal?.addEventListener("abort", abort, { once: true });
30960
+ })]);
30961
+ } catch (error2) {
30962
+ if (error2 instanceof PrivatePublicationError) {
30963
+ if (mutation && error2.code === "INVALID_PUBLICATION_RESPONSE")
30964
+ throw timeout;
30965
+ throw error2;
30966
+ }
30967
+ throw timeout;
30968
+ } finally {
30969
+ if (timer)
30970
+ clearTimeout(timer);
30971
+ if (abort)
30972
+ signal?.removeEventListener("abort", abort);
30973
+ controller.abort();
30974
+ if (reader)
30975
+ reader.cancel().catch(() => {});
30976
+ else
30977
+ response?.body?.cancel().catch(() => {});
30978
+ }
30979
+ }
30980
+
30981
+ class RemotePrivatePublicationsClient {
30982
+ apiOrigin;
30983
+ organizationId;
30984
+ userId;
30985
+ membershipId;
30986
+ #token;
30987
+ constructor(apiUrl, session) {
30988
+ const checked = parseWorkspaceSession(session, { userId: session?.user?.id, membershipId: session?.user?.membershipId });
30989
+ if (checked.user.role === "viewer")
30990
+ throw new PrivatePublicationError("PUBLICATION_FORBIDDEN", failures.PUBLICATION_FORBIDDEN[1]);
30991
+ this.apiOrigin = normalizeSkillsApiOrigin(apiUrl);
30992
+ this.#token = checked.token;
30993
+ this.organizationId = checked.organization.id;
30994
+ this.userId = checked.user.id;
30995
+ this.membershipId = checked.user.membershipId;
30996
+ Object.freeze(this);
30997
+ }
30998
+ async getCapability(options = {}) {
30999
+ if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 15000))
31000
+ return bad();
31001
+ const response = await boundedJson(skillsApiRequestUrl(this.apiOrigin, "/api/v1/capabilities"), {}, this.#token, false, options.timeoutMs, options.signal);
31002
+ const p = record7(response) && response.privatePublishing;
31003
+ 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)
31004
+ throw new PrivatePublicationError("PUBLICATION_CONTRACT_UNAVAILABLE", "This server does not support the hosted private publication contract.");
31005
+ return Object.freeze({ ...p });
31006
+ }
31007
+ async#gate(enabled, options = {}) {
31008
+ if (!(await this.getCapability(options)).enabled && enabled)
31009
+ throw new PrivatePublicationError("PUBLICATION_CAPABILITY_UNAVAILABLE", failures.PUBLICATION_CAPABILITY_UNAVAILABLE[1]);
31010
+ }
31011
+ #path(skillId, intentId) {
31012
+ if (!publicationUuid(skillId) || intentId !== undefined && !publicationUuid(intentId))
31013
+ return bad();
31014
+ return skillsApiRequestUrl(this.apiOrigin, `/api/v1/skills/${skillId}/publication-uploads${intentId ? `/${intentId}` : ""}`);
31015
+ }
31016
+ async#view(path, method, skillId, intentId, declaration, options = {}) {
31017
+ const value = await boundedJson(path, { method, ...method === "GET" ? {} : { body: JSON.stringify(declaration ?? {}) } }, this.#token, method !== "GET", options.timeoutMs, options.signal);
31018
+ try {
31019
+ if (!record7(value) || !Object.keys(value).every((k) => k === "upload" || k === "changed") || value.changed !== undefined && typeof value.changed !== "boolean")
31020
+ return invalid3();
31021
+ return checkedPublicationView(value.upload, skillId, intentId, declaration);
31022
+ } catch (error2) {
31023
+ if (method !== "GET")
31024
+ throw new PrivatePublicationError("PUBLICATION_UNCONFIRMED", "The publication result could not be confirmed. Reconcile the saved intent before another action.", true);
31025
+ throw error2;
31026
+ }
31027
+ }
31028
+ async begin(skillId, input) {
31029
+ const path = this.#path(skillId), declaration = checkedPublicationDeclaration(input);
31030
+ await this.#gate(true);
31031
+ return this.#view(path, "POST", skillId, undefined, declaration);
31032
+ }
31033
+ async get(skillId, intentId, options = {}) {
31034
+ const path = this.#path(skillId, intentId), until = Date.now() + (options.timeoutMs ?? 15000);
31035
+ await this.#gate(false, options);
31036
+ return this.#view(path, "GET", skillId, intentId, undefined, { ...options, timeoutMs: Math.max(1, until - Date.now()) });
31037
+ }
31038
+ async finalize(skillId, intentId) {
31039
+ const path = this.#path(skillId, intentId);
31040
+ await this.#gate(true);
31041
+ return this.#view(`${path}/finalize`, "POST", skillId, intentId);
31042
+ }
31043
+ async cancel(skillId, intentId) {
31044
+ const path = this.#path(skillId, intentId);
31045
+ await this.#gate(false);
31046
+ return this.#view(path, "DELETE", skillId, intentId);
31047
+ }
31048
+ async upload(skillId, intent, bytes) {
31049
+ const captured = checkedPublicationView(intent, skillId), path = this.#path(skillId, captured.id);
31050
+ if (!(bytes instanceof Uint8Array) || bytes.byteLength !== captured.archiveByteSize)
31051
+ return bad();
31052
+ const owned = Buffer.from(bytes);
31053
+ if (captured.state !== "awaiting_upload" || owned.byteLength !== captured.archiveByteSize || publicationSha256(owned) !== captured.archiveSha256)
31054
+ return bad();
31055
+ await this.#gate(true);
31056
+ const value = await boundedJson(`${path}/upload-url`, { method: "POST", body: "{}" }, this.#token, false);
31057
+ const upload = this.#upload(value, captured);
31058
+ const controller = new AbortController;
31059
+ let timer;
31060
+ 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);
31061
+ try {
31062
+ await Promise.race([(async () => {
31063
+ const response = await fetch(upload.uploadUrl, { method: "PUT", headers: upload.headers, body: owned, redirect: "error", credentials: "omit", signal: controller.signal });
31064
+ response.body?.cancel().catch(() => {});
31065
+ if (response.status !== 200 || controller.signal.aborted)
31066
+ throw uncertain();
31067
+ })(), new Promise((_, reject) => {
31068
+ timer = setTimeout(() => {
31069
+ controller.abort();
31070
+ reject(uncertain());
31071
+ }, 30000);
31072
+ })]);
31073
+ } catch {
31074
+ throw uncertain();
31075
+ } finally {
31076
+ if (timer)
31077
+ clearTimeout(timer);
31078
+ controller.abort();
31079
+ }
31080
+ }
31081
+ #upload(value, intent) {
31082
+ if (!exact(value, ["upload"]) || !exact(value.upload, ["method", "uploadUrl", "headers", "expiresAt"]))
31083
+ return invalid3();
31084
+ const p = value.upload;
31085
+ 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"]))
31086
+ return invalid3();
31087
+ let url;
31088
+ try {
31089
+ url = new URL(p.uploadUrl);
31090
+ } catch {
31091
+ return invalid3();
31092
+ }
31093
+ 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") ?? ""))
31094
+ return invalid3();
31095
+ const queryKeys = ["X-Amz-Algorithm", "X-Amz-Credential", "X-Amz-Date", "X-Amz-Expires", "X-Amz-Security-Token", "X-Amz-Signature", "X-Amz-SignedHeaders"];
31096
+ if ([...url.searchParams.keys()].sort().join(",") !== queryKeys.sort().join(","))
31097
+ return invalid3();
31098
+ const issued = url.searchParams.get("X-Amz-Date"), ttl = url.searchParams.get("X-Amz-Expires"), credential = url.searchParams.get("X-Amz-Credential");
31099
+ const timestamp3 = /^(\d{4})(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)Z$/.exec(issued);
31100
+ const region = url.hostname.split(".s3.")[1].split(".amazonaws.com")[0];
31101
+ 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")))
31102
+ return invalid3();
31103
+ const issuedAt = Date.parse(`${timestamp3[1]}-${timestamp3[2]}-${timestamp3[3]}T${timestamp3[4]}:${timestamp3[5]}:${timestamp3[6]}Z`);
31104
+ if (!Number.isFinite(issuedAt) || issuedAt > Date.now() + 1000 || issuedAt + Number(ttl) * 1000 !== Date.parse(p.expiresAt))
31105
+ return invalid3();
31106
+ return { method: "PUT", uploadUrl: url.href, expiresAt: p.expiresAt, headers: Object.freeze({ ...p.headers }) };
31107
+ }
31108
+ async wait(skillId, intentId, options = {}) {
31109
+ const timeout = options.timeoutMs ?? 60000;
31110
+ if (!Number.isSafeInteger(timeout) || timeout < 0 || timeout > 300000)
31111
+ return bad();
31112
+ const until = Date.now() + timeout;
31113
+ let previous;
31114
+ while (true) {
31115
+ if (options.signal?.aborted)
31116
+ throw new PrivatePublicationError("PUBLICATION_WAIT_ABORTED", "Stopped waiting. The server publication continues; inspect the saved intent.");
31117
+ let view;
31118
+ try {
31119
+ view = await this.get(skillId, intentId, { timeoutMs: timeout === 0 ? 15000 : Math.max(1, Math.min(15000, until - Date.now())), signal: options.signal });
31120
+ } catch (error2) {
31121
+ if (previous && Date.now() >= until && !options.signal?.aborted)
31122
+ return previous;
31123
+ throw error2;
31124
+ }
31125
+ previous = view;
31126
+ if (!["queued", "verifying"].includes(view.state) || Date.now() >= until)
31127
+ return view;
31128
+ await new Promise((resolve2) => {
31129
+ const timer = setTimeout(done, Math.min(1000, until - Date.now()));
31130
+ function done() {
31131
+ clearTimeout(timer);
31132
+ options.signal?.removeEventListener("abort", done);
31133
+ resolve2();
31134
+ }
31135
+ options.signal?.addEventListener("abort", done, { once: true });
31136
+ });
31137
+ }
31138
+ }
31139
+ }
31140
+ var PRIVATE_PUBLICATION_MAX_BYTES, failures, PrivatePublicationError, bad = () => {
31141
+ throw new PrivatePublicationError("INVALID_PUBLICATION_INPUT", "Invalid publication input or recovery data.");
31142
+ }, invalid3 = () => {
31143
+ throw new PrivatePublicationError("INVALID_PUBLICATION_RESPONSE", "The server returned an invalid publication result.");
31144
+ }, 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, keys) => record7(v) && Object.keys(v).sort().join(",") === keys.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) => createHash5("sha256").update(bytes).digest("hex");
31145
+ var init_remote_private_publications = __esm(() => {
31146
+ init_fleet_credentials();
31147
+ init_remote_workspace_selection();
31148
+ init_skill_contract();
31149
+ PRIVATE_PUBLICATION_MAX_BYTES = 16 * 1024 * 1024;
31150
+ failures = {
31151
+ INVALID_REQUEST: [400, "The publication request is invalid."],
31152
+ SESSION_EXPIRED: [401, "Sign in again to manage this publication."],
31153
+ ACCOUNT_UNAVAILABLE: [403, "The account is unavailable."],
31154
+ INTERACTIVE_SESSION_REQUIRED: [403, "An interactive account session is required."],
31155
+ PUBLICATION_FORBIDDEN: [403, "This session cannot manage the publication."],
31156
+ PUBLICATION_ENTITLEMENT_REQUIRED: [403, "The workspace is not entitled to publish private skills."],
31157
+ PUBLICATION_UNAVAILABLE: [404, "The publication is unavailable to this session."],
31158
+ MANIFEST_NAME_MISMATCH: [409, "The manifest name does not match the selected skill."],
31159
+ IDEMPOTENCY_CONFLICT: [409, "This request key already identifies different publication bytes. Use the saved recovery directory."],
31160
+ CURRENT_VERSION_CHANGED: [409, "The current version changed. Inspect it before explicitly starting another publication."],
31161
+ VERSION_EXISTS: [409, "This version already exists."],
31162
+ VERSION_RESERVED: [409, "This version is reserved by another publication."],
31163
+ PUBLICATION_COMMITTED: [409, "The publication is already committed."],
31164
+ PUBLICATION_UPLOAD_UNAVAILABLE: [409, "This publication cannot receive another upload."],
31165
+ PUBLICATION_LIMIT: [429, "The workspace publication limit has been reached."],
31166
+ PUBLICATION_BUSY: [503, "The publication is busy. Reconcile the saved intent before retrying."],
31167
+ PUBLICATION_UNCERTAIN: [503, "The publication outcome is uncertain. Reconcile the saved intent."],
31168
+ PUBLICATION_CAPABILITY_UNAVAILABLE: [503, "Private publishing is not enabled on this server."],
31169
+ PUBLICATION_SIGNING_UNAVAILABLE: [503, "Upload authorization is temporarily unavailable. Keep the same intent."]
31170
+ };
31171
+ PrivatePublicationError = class PrivatePublicationError extends Error {
31172
+ code;
31173
+ uncertain;
31174
+ status;
31175
+ constructor(code, message, uncertain = false, status) {
31176
+ super(message);
31177
+ this.code = code;
31178
+ this.uncertain = uncertain;
31179
+ this.status = status;
31180
+ this.name = "PrivatePublicationError";
31181
+ }
31182
+ };
31183
+ });
31184
+
30512
31185
  // src/lib/remote-auth.ts
30513
31186
  async function requestAuthApi(instance, path, options) {
30514
31187
  const url = normalizeSkillsApiOrigin(instance);
30515
31188
  const safeUrl = url;
30516
- const endpoint = `${(options?.method || "GET").toUpperCase()} ${safeUrl}${path}`;
31189
+ const requestUrl = skillsApiRequestUrl(url, path);
31190
+ const endpoint = `${(options?.method || "GET").toUpperCase()} ${requestUrl}`;
30517
31191
  let res;
30518
31192
  try {
30519
- res = await fetch(`${url}${path}`, {
31193
+ res = await fetch(requestUrl, {
30520
31194
  ...options,
30521
31195
  redirect: "error",
30522
31196
  signal: options?.signal ?? AbortSignal.timeout(15000),
@@ -30531,10 +31205,10 @@ async function requestAuthApi(instance, path, options) {
30531
31205
  const text2 = await res.text();
30532
31206
  const body = text2 ? parseJsonBody(text2) : {};
30533
31207
  if (!res.ok) {
30534
- const record7 = isRecord5(body) ? body : {};
30535
- const detail = typeof record7.detail === "string" ? record7.detail : undefined;
30536
- const error2 = typeof record7.error === "string" ? record7.error : undefined;
30537
- const code = typeof record7.code === "string" ? record7.code : undefined;
31208
+ const record8 = isRecord5(body) ? body : {};
31209
+ const detail = typeof record8.detail === "string" ? record8.detail : undefined;
31210
+ const error2 = typeof record8.error === "string" ? record8.error : undefined;
31211
+ const code = typeof record8.code === "string" ? record8.code : undefined;
30538
31212
  throw new HostedApiError(detail || error2 || `${res.status} ${res.statusText}`, {
30539
31213
  status: res.status,
30540
31214
  code,
@@ -30568,6 +31242,10 @@ class RemoteSkillsAuthClient {
30568
31242
  constructor(apiUrl) {
30569
31243
  this.apiOrigin = normalizeSkillsApiOrigin(apiUrl);
30570
31244
  }
31245
+ async openPrivatePublications(email3, code, context) {
31246
+ const origin = this.apiOrigin, captured = workspaceContext(context);
31247
+ return new RemotePrivatePublicationsClient(origin, await this.switchWorkspace(email3, code, captured));
31248
+ }
30571
31249
  requestInvitationEmailChallenge(input) {
30572
31250
  return requestInvitationEmail(this.apiOrigin, "challenge", input);
30573
31251
  }
@@ -30615,9 +31293,10 @@ class RemoteSkillsAuthClient {
30615
31293
  const apiOrigin = this.apiOrigin;
30616
31294
  if (typeof email3 !== "string" || !email3.includes("@") || typeof code !== "string" || !/^\d{6}$/.test(code))
30617
31295
  throw new Error("Fresh email and six-digit verification code are required to manage this account");
31296
+ const requestUrl = skillsApiRequestUrl(apiOrigin, "/api/auth/verify");
30618
31297
  let response;
30619
31298
  try {
30620
- response = await fetch(`${apiOrigin}/api/auth/verify`, {
31299
+ response = await fetch(requestUrl, {
30621
31300
  method: "POST",
30622
31301
  redirect: "error",
30623
31302
  credentials: "omit",
@@ -30711,6 +31390,7 @@ class RemoteSkillsAuthClient {
30711
31390
  var MAX_ERROR_DETAIL_LENGTH = 200, HostedApiError;
30712
31391
  var init_remote_auth = __esm(() => {
30713
31392
  init_remote_invitation_recovery();
31393
+ init_remote_private_publications();
30714
31394
  init_remote_invitations();
30715
31395
  init_remote_workspace_leave();
30716
31396
  init_remote_workspace_selection();
@@ -30913,9 +31593,824 @@ var init_remote_invitation_tools = __esm(() => {
30913
31593
  init_helpers();
30914
31594
  });
30915
31595
 
31596
+ // src/lib/private-publication-customer.ts
31597
+ async function privatePublicationSession(email3, code, requested) {
31598
+ const target = await captureProfileWorkspace("Manage private publications");
31599
+ const context = requested ?? target.context;
31600
+ if (!context || !publicationUuid(context.userId) || !publicationUuid(context.membershipId) || target.context && (context.userId !== target.context.userId || context.membershipId !== target.context.membershipId))
31601
+ throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide the observed user and membership IDs, or select an enrolled workspace profile.");
31602
+ target.unchanged();
31603
+ const client = await new RemoteSkillsAuthClient(target.origin).openPrivatePublications(email3, code, context);
31604
+ target.unchanged();
31605
+ return client;
31606
+ }
31607
+ function privatePublicationCustomerError(error2) {
31608
+ 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 };
31609
+ }
31610
+ var init_private_publication_customer = __esm(() => {
31611
+ init_remote_auth();
31612
+ init_remote_private_publications();
31613
+ init_workspace_profile();
31614
+ });
31615
+
31616
+ // src/lib/skill-bundle.ts
31617
+ import { createHash as createHash6 } from "crypto";
31618
+ import { readFileSync as readFileSync16, readdirSync as readdirSync10, statSync as statSync11 } from "fs";
31619
+ import { join as join19, relative as relative4 } from "path";
31620
+ import { createGunzip } from "zlib";
31621
+ function isDotenvFile(lower) {
31622
+ if (lower === ".env" || lower.startsWith(".env."))
31623
+ return true;
31624
+ if (lower === "env")
31625
+ return true;
31626
+ if (lower.startsWith("env.")) {
31627
+ const extension = lower.slice(lower.lastIndexOf(".") + 1);
31628
+ return !NON_DOTENV_EXTENSIONS.has(extension);
31629
+ }
31630
+ return false;
31631
+ }
31632
+ function isCredentialFile(name) {
31633
+ const lower = name.toLowerCase();
31634
+ if (ENV_TEMPLATE_NAMES.has(lower))
31635
+ return false;
31636
+ if (isDotenvFile(lower))
31637
+ return true;
31638
+ if (CREDENTIAL_FILENAMES.has(lower))
31639
+ return true;
31640
+ return CREDENTIAL_EXTENSIONS.some((extension) => lower.endsWith(extension));
31641
+ }
31642
+ function ownBytes(view) {
31643
+ const source = view instanceof ArrayBuffer ? new Uint8Array(view) : view;
31644
+ const out = new Uint8Array(new ArrayBuffer(source.byteLength));
31645
+ out.set(source);
31646
+ return out;
31647
+ }
31648
+ function sha256Hex(bytes) {
31649
+ return createHash6("sha256").update(bytes).digest("hex");
31650
+ }
31651
+ function collectSkillBundleEntries(dir) {
31652
+ const entries = [];
31653
+ walk(dir, dir, entries);
31654
+ return entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
31655
+ }
31656
+ function walk(root, current, out) {
31657
+ for (const entry of readdirSync10(current, { withFileTypes: true })) {
31658
+ const absolute = join19(current, entry.name);
31659
+ const rel = relative4(root, absolute).split("\\").join("/");
31660
+ const isRootLevel = !rel.includes("/");
31661
+ if (ANY_SEGMENT_EXCLUDES.has(entry.name.toLowerCase()))
31662
+ continue;
31663
+ if (isRootLevel && ROOT_EXCLUDES.has(entry.name.toLowerCase()))
31664
+ continue;
31665
+ if (isRootLevel && TOOL_SIDECAR_FILENAMES.has(entry.name.toLowerCase()))
31666
+ continue;
31667
+ if (entry.name.startsWith("._"))
31668
+ continue;
31669
+ if (entry.isSymbolicLink())
31670
+ continue;
31671
+ if (entry.isDirectory()) {
31672
+ walk(root, absolute, out);
31673
+ continue;
31674
+ }
31675
+ if (!entry.isFile())
31676
+ continue;
31677
+ if (isCredentialFile(entry.name))
31678
+ continue;
31679
+ const stats = statSync11(absolute);
31680
+ out.push({
31681
+ path: rel,
31682
+ bytes: ownBytes(readFileSync16(absolute)),
31683
+ mode: stats.mode & 64 ? 493 : 420
31684
+ });
31685
+ }
31686
+ }
31687
+ function packSkillBundle(dir, options = {}) {
31688
+ const entries = collectSkillBundleEntries(dir);
31689
+ if (entries.length === 0) {
31690
+ throw new Error(`Nothing to pack: ${dir} contains no files after exclusions (.git, node_modules, dist, .env)`);
31691
+ }
31692
+ const unpackedByteSize = entries.reduce((sum, entry) => sum + entry.bytes.byteLength, 0);
31693
+ const max = options.maxUnpackedBytes ?? 0;
31694
+ if (max > 0 && unpackedByteSize > max) {
31695
+ throw new Error(`Skill sources are ${unpackedByteSize} bytes, over the ${max} byte limit. Remove build output or large fixtures.`);
31696
+ }
31697
+ const tar = writeTar(entries);
31698
+ const bytes = canonicalGzip(tar);
31699
+ return {
31700
+ bytes,
31701
+ sha256: sha256Hex(bytes),
31702
+ fileCount: entries.length,
31703
+ unpackedByteSize,
31704
+ paths: entries.map((entry) => entry.path)
31705
+ };
31706
+ }
31707
+ function canonicalGzip(tar) {
31708
+ const bytes = ownBytes(Bun.gzipSync(tar, { level: 6 }));
31709
+ if (bytes.byteLength >= 10) {
31710
+ bytes[4] = 0;
31711
+ bytes[5] = 0;
31712
+ bytes[6] = 0;
31713
+ bytes[7] = 0;
31714
+ bytes[9] = 255;
31715
+ }
31716
+ return bytes;
31717
+ }
31718
+ function writeTar(entries) {
31719
+ const blocks = [];
31720
+ for (const entry of entries) {
31721
+ blocks.push(ustarHeader(entry));
31722
+ blocks.push(entry.bytes);
31723
+ const remainder = entry.bytes.byteLength % BLOCK;
31724
+ if (remainder !== 0)
31725
+ blocks.push(new Uint8Array(new ArrayBuffer(BLOCK - remainder)));
31726
+ }
31727
+ blocks.push(new Uint8Array(new ArrayBuffer(BLOCK * 2)));
31728
+ return concat(blocks);
31729
+ }
31730
+ function ustarHeader(entry) {
31731
+ const header = new Uint8Array(new ArrayBuffer(BLOCK));
31732
+ const encoder = new TextEncoder;
31733
+ const put = (offset, length, value) => {
31734
+ const encoded = encoder.encode(value);
31735
+ if (encoded.byteLength > length) {
31736
+ throw new Error(`Cannot pack '${entry.path}': field does not fit in a ustar header (${encoded.byteLength} > ${length})`);
31737
+ }
31738
+ header.set(encoded, offset);
31739
+ };
31740
+ if (encoder.encode(entry.path).byteLength > 100) {
31741
+ throw new Error(`Cannot pack '${entry.path}': path is longer than the 100 bytes a ustar header holds`);
31742
+ }
31743
+ put(0, 100, entry.path);
31744
+ put(100, 8, `${entry.mode.toString(8).padStart(7, "0")}\x00`);
31745
+ put(108, 8, "0000000\x00");
31746
+ put(116, 8, "0000000\x00");
31747
+ put(124, 12, `${entry.bytes.byteLength.toString(8).padStart(11, "0")}\x00`);
31748
+ put(136, 12, `${0 .toString(8).padStart(11, "0")}\x00`);
31749
+ put(148, 8, " ");
31750
+ put(156, 1, "0");
31751
+ put(257, 6, "ustar\x00");
31752
+ put(263, 2, "00");
31753
+ let checksum = 0;
31754
+ for (const byte of header)
31755
+ checksum += byte;
31756
+ put(148, 8, `${checksum.toString(8).padStart(6, "0")}\x00 `);
31757
+ return header;
31758
+ }
31759
+ function concat(chunks) {
31760
+ const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
31761
+ const merged = new Uint8Array(new ArrayBuffer(total));
31762
+ let offset = 0;
31763
+ for (const chunk of chunks) {
31764
+ merged.set(chunk, offset);
31765
+ offset += chunk.byteLength;
31766
+ }
31767
+ return merged;
31768
+ }
31769
+ function invalidBundle(message) {
31770
+ throw new SkillBundleInspectionError("BUNDLE_INVALID", message);
31771
+ }
31772
+ function inspectionLimits(options) {
31773
+ const limits = { ...SKILL_BUNDLE_INSPECTION_LIMITS };
31774
+ for (const key of Object.keys(options.limits ?? {})) {
31775
+ if (!Object.hasOwn(limits, key))
31776
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Unknown bundle limit");
31777
+ const field = key;
31778
+ const value = options.limits[field];
31779
+ if (!Number.isSafeInteger(value) || value <= 0 || value > limits[field]) {
31780
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle limits must be positive integers within the hard ceilings");
31781
+ }
31782
+ limits[field] = value;
31783
+ }
31784
+ return limits;
31785
+ }
31786
+ async function inspectSkillBundle(bundle, options = {}) {
31787
+ const signal = options.signal;
31788
+ const limits = inspectionLimits(options);
31789
+ const deadline = performance.now() + limits.timeoutMs;
31790
+ const check2 = () => {
31791
+ if (signal?.aborted)
31792
+ throw new SkillBundleInspectionError("BUNDLE_ABORTED", "Bundle inspection aborted");
31793
+ if (performance.now() >= deadline)
31794
+ throw new SkillBundleInspectionError("BUNDLE_TIMEOUT", "Bundle inspection deadline exceeded");
31795
+ };
31796
+ check2();
31797
+ if (bundle.byteLength > limits.compressedBytes)
31798
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Compressed bundle exceeds byte limit");
31799
+ const snapshot = ownBytes(bundle);
31800
+ check2();
31801
+ const sha2562 = sha256Hex(snapshot);
31802
+ check2();
31803
+ const parser = new BoundedTarReader(limits, check2);
31804
+ const streamOptions = { chunkSize: 16 * 1024, highWaterMark: 16 * 1024 };
31805
+ const decoder = createGunzip(streamOptions);
31806
+ let terminalError;
31807
+ const stop = (code) => {
31808
+ terminalError ??= new SkillBundleInspectionError(code, code === "BUNDLE_ABORTED" ? "Bundle inspection aborted" : "Bundle inspection deadline exceeded");
31809
+ decoder.destroy(terminalError);
31810
+ };
31811
+ const onAbort = () => stop("BUNDLE_ABORTED");
31812
+ const timer = setTimeout(() => stop("BUNDLE_TIMEOUT"), Math.max(1, deadline - performance.now()));
31813
+ signal?.addEventListener("abort", onAbort, { once: true });
31814
+ let decompressedByteSize = 0;
31815
+ let bytesSinceYield = 0;
31816
+ try {
31817
+ check2();
31818
+ decoder.end(snapshot);
31819
+ for await (const chunk of decoder) {
31820
+ check2();
31821
+ decompressedByteSize += chunk.byteLength;
31822
+ if (decompressedByteSize > limits.decompressedBytes)
31823
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Decompressed bundle exceeds byte limit");
31824
+ parser.push(chunk);
31825
+ bytesSinceYield += chunk.byteLength;
31826
+ if (bytesSinceYield >= 256 * 1024) {
31827
+ await new Promise((resolve2) => setTimeout(resolve2, 0));
31828
+ bytesSinceYield = 0;
31829
+ check2();
31830
+ }
31831
+ }
31832
+ check2();
31833
+ const entries = parser.finish();
31834
+ return {
31835
+ entries,
31836
+ sha256: sha2562,
31837
+ compressedByteSize: snapshot.byteLength,
31838
+ decompressedByteSize,
31839
+ unpackedByteSize: parser.fileBytes,
31840
+ fileCount: entries.length
31841
+ };
31842
+ } catch (error2) {
31843
+ if (terminalError)
31844
+ throw terminalError;
31845
+ if (error2 instanceof SkillBundleInspectionError)
31846
+ throw error2;
31847
+ throw new SkillBundleInspectionError("BUNDLE_INVALID", "Invalid or truncated gzip bundle");
31848
+ } finally {
31849
+ clearTimeout(timer);
31850
+ signal?.removeEventListener("abort", onAbort);
31851
+ decoder.destroy();
31852
+ }
31853
+ }
31854
+
31855
+ class BoundedTarReader {
31856
+ limits;
31857
+ check;
31858
+ header = new Uint8Array(BLOCK);
31859
+ headerOffset = 0;
31860
+ pending;
31861
+ bodyOffset = 0;
31862
+ padding = 0;
31863
+ zeroBlocks = 0;
31864
+ entries = [];
31865
+ paths = new SkillEntryPaths;
31866
+ fileBytes = 0;
31867
+ constructor(limits, check2) {
31868
+ this.limits = limits;
31869
+ this.check = check2;
31870
+ }
31871
+ push(chunk) {
31872
+ let offset = 0;
31873
+ while (offset < chunk.byteLength) {
31874
+ this.check();
31875
+ if (this.pending) {
31876
+ const count = Math.min(this.pending.bytes.byteLength - this.bodyOffset, chunk.byteLength - offset);
31877
+ this.pending.bytes.set(chunk.subarray(offset, offset + count), this.bodyOffset);
31878
+ offset += count;
31879
+ this.bodyOffset += count;
31880
+ if (this.bodyOffset === this.pending.bytes.byteLength) {
31881
+ this.entries.push(this.pending);
31882
+ this.pending = undefined;
31883
+ }
31884
+ } else if (this.padding) {
31885
+ const count = Math.min(this.padding, chunk.byteLength - offset);
31886
+ if (chunk.subarray(offset, offset + count).some((byte) => byte !== 0))
31887
+ invalidBundle("Nonzero tar body padding");
31888
+ offset += count;
31889
+ this.padding -= count;
31890
+ } else {
31891
+ const count = Math.min(BLOCK - this.headerOffset, chunk.byteLength - offset);
31892
+ this.header.set(chunk.subarray(offset, offset + count), this.headerOffset);
31893
+ offset += count;
31894
+ this.headerOffset += count;
31895
+ if (this.headerOffset === BLOCK) {
31896
+ this.readHeader();
31897
+ this.headerOffset = 0;
31898
+ }
31899
+ }
31900
+ }
31901
+ }
31902
+ finish() {
31903
+ this.check();
31904
+ if (this.pending || this.padding || this.headerOffset || this.zeroBlocks < 2)
31905
+ invalidBundle("Truncated tar bundle");
31906
+ return this.entries;
31907
+ }
31908
+ readHeader() {
31909
+ this.check();
31910
+ const h = this.header;
31911
+ if (h.every((byte) => byte === 0)) {
31912
+ this.zeroBlocks++;
31913
+ return;
31914
+ }
31915
+ if (this.zeroBlocks)
31916
+ invalidBundle("Nonzero tar data after terminator");
31917
+ if (this.entries.length >= this.limits.entries)
31918
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle entry limit exceeded");
31919
+ let checksum = 0;
31920
+ for (let i = 0;i < BLOCK; i++)
31921
+ checksum += i >= 148 && i < 156 ? 32 : h[i];
31922
+ if (tarOctal(h.subarray(148, 156)) !== checksum)
31923
+ invalidBundle("Invalid tar header checksum");
31924
+ if (new TextDecoder().decode(h.subarray(257, 265)) !== "ustar\x00" + "00")
31925
+ invalidBundle("Unsupported tar format");
31926
+ if (h[156] !== 48 && h[156] !== 0 || h.subarray(157, 257).some((b) => b !== 0) || h.subarray(345).some((b) => b !== 0))
31927
+ invalidBundle("Unsupported tar entry or path prefix");
31928
+ const mode = tarOctal(h.subarray(100, 108));
31929
+ if (mode > 511)
31930
+ invalidBundle("Unsupported tar permission bits");
31931
+ tarOctal(h.subarray(108, 116));
31932
+ tarOctal(h.subarray(116, 124));
31933
+ tarOctal(h.subarray(136, 148));
31934
+ const size = tarOctal(h.subarray(124, 136));
31935
+ if (size > this.limits.fileBytes || this.fileBytes + size > this.limits.decompressedBytes) {
31936
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle file byte limit exceeded");
31937
+ }
31938
+ const name = h.subarray(0, 100);
31939
+ const end = name.indexOf(0);
31940
+ if (end !== -1 && name.subarray(end).some((b) => b !== 0))
31941
+ invalidBundle("Invalid tar path padding");
31942
+ const raw = end === -1 ? name : name.subarray(0, end);
31943
+ if (raw.byteLength > this.limits.pathBytes)
31944
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle path byte limit exceeded");
31945
+ let path;
31946
+ try {
31947
+ path = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(raw);
31948
+ } catch {
31949
+ return invalidBundle("Invalid UTF-8 bundle path");
31950
+ }
31951
+ this.paths.add(path, this.limits.pathBytes, invalidBundle, () => {
31952
+ throw new SkillBundleInspectionError("BUNDLE_LIMIT", "Bundle path byte limit exceeded");
31953
+ });
31954
+ this.fileBytes += size;
31955
+ this.pending = { path, mode, bytes: new Uint8Array(new ArrayBuffer(size)) };
31956
+ this.bodyOffset = 0;
31957
+ this.padding = (BLOCK - size % BLOCK) % BLOCK;
31958
+ if (!size) {
31959
+ this.entries.push(this.pending);
31960
+ this.pending = undefined;
31961
+ }
31962
+ }
31963
+ }
31964
+ function tarOctal(field) {
31965
+ const text2 = new TextDecoder().decode(field);
31966
+ if (!/^[0-7]+[\0 ]*$/.test(text2))
31967
+ invalidBundle("Invalid tar octal field");
31968
+ const value = Number.parseInt(text2, 8);
31969
+ if (!Number.isSafeInteger(value))
31970
+ invalidBundle("Tar integer is out of range");
31971
+ return value;
31972
+ }
31973
+ var BLOCK = 512, ANY_SEGMENT_EXCLUDES, ROOT_EXCLUDES, TOOL_SIDECAR_FILENAMES, CREDENTIAL_FILENAMES, CREDENTIAL_EXTENSIONS, ENV_TEMPLATE_NAMES, NON_DOTENV_EXTENSIONS, SKILL_BUNDLE_INSPECTION_LIMITS, SkillBundleInspectionError;
31974
+ var init_skill_bundle = __esm(() => {
31975
+ ANY_SEGMENT_EXCLUDES = new Set([
31976
+ ".git",
31977
+ ".ds_store",
31978
+ ".system",
31979
+ "node_modules",
31980
+ ".aws",
31981
+ ".ssh",
31982
+ ".gnupg",
31983
+ ".docker"
31984
+ ]);
31985
+ ROOT_EXCLUDES = new Set(["dist", "build", ".turbo"]);
31986
+ TOOL_SIDECAR_FILENAMES = new Set([".hasna-skills.json"]);
31987
+ CREDENTIAL_FILENAMES = new Set([
31988
+ ".npmrc",
31989
+ ".pypirc",
31990
+ ".netrc",
31991
+ ".envrc",
31992
+ ".pgpass",
31993
+ ".git-credentials",
31994
+ "credentials",
31995
+ "id_rsa",
31996
+ "id_dsa",
31997
+ "id_ecdsa",
31998
+ "id_ed25519"
31999
+ ]);
32000
+ CREDENTIAL_EXTENSIONS = [".pem", ".key", ".p12", ".pfx", ".keystore", ".jks"];
32001
+ ENV_TEMPLATE_NAMES = new Set([".env.example", ".env.sample", ".env.template", ".env.dist"]);
32002
+ NON_DOTENV_EXTENSIONS = new Set([
32003
+ "ts",
32004
+ "tsx",
32005
+ "mts",
32006
+ "cts",
32007
+ "js",
32008
+ "jsx",
32009
+ "mjs",
32010
+ "cjs",
32011
+ "json",
32012
+ "jsonc",
32013
+ "json5",
32014
+ "py",
32015
+ "rb",
32016
+ "go",
32017
+ "rs",
32018
+ "java",
32019
+ "kt",
32020
+ "kts",
32021
+ "swift",
32022
+ "c",
32023
+ "h",
32024
+ "cc",
32025
+ "cpp",
32026
+ "hpp",
32027
+ "cs",
32028
+ "php",
32029
+ "sh",
32030
+ "bash",
32031
+ "zsh",
32032
+ "fish",
32033
+ "ps1",
32034
+ "bat",
32035
+ "cmd",
32036
+ "lua",
32037
+ "pl",
32038
+ "pm",
32039
+ "r",
32040
+ "jl",
32041
+ "dart",
32042
+ "ex",
32043
+ "exs",
32044
+ "scala",
32045
+ "clj",
32046
+ "cljs",
32047
+ "groovy",
32048
+ "gradle",
32049
+ "vb",
32050
+ "fs",
32051
+ "yaml",
32052
+ "yml",
32053
+ "toml",
32054
+ "ini",
32055
+ "xml",
32056
+ "csv",
32057
+ "tsv",
32058
+ "html",
32059
+ "htm",
32060
+ "css",
32061
+ "scss",
32062
+ "sass",
32063
+ "less",
32064
+ "md",
32065
+ "mdx",
32066
+ "txt",
32067
+ "rst",
32068
+ "adoc",
32069
+ "sql",
32070
+ "graphql",
32071
+ "gql",
32072
+ "proto",
32073
+ "d",
32074
+ "png",
32075
+ "jpg",
32076
+ "jpeg",
32077
+ "gif",
32078
+ "svg",
32079
+ "webp",
32080
+ "ico",
32081
+ "bmp",
32082
+ "pdf",
32083
+ "woff",
32084
+ "woff2",
32085
+ "ttf",
32086
+ "otf",
32087
+ "eot",
32088
+ "mp3",
32089
+ "mp4",
32090
+ "wav",
32091
+ "webm",
32092
+ "zip",
32093
+ "gz",
32094
+ "tar",
32095
+ "wasm"
32096
+ ]);
32097
+ SKILL_BUNDLE_INSPECTION_LIMITS = Object.freeze({
32098
+ compressedBytes: 16 * 1024 * 1024,
32099
+ decompressedBytes: 64 * 1024 * 1024,
32100
+ entries: 1024,
32101
+ fileBytes: 16 * 1024 * 1024,
32102
+ pathBytes: 100,
32103
+ timeoutMs: 5000
32104
+ });
32105
+ SkillBundleInspectionError = class SkillBundleInspectionError extends Error {
32106
+ code;
32107
+ constructor(code, message) {
32108
+ super(message);
32109
+ this.code = code;
32110
+ this.name = "SkillBundleInspectionError";
32111
+ }
32112
+ };
32113
+ });
32114
+
32115
+ // src/lib/private-publication-recovery.ts
32116
+ import { constants as constants3, closeSync as closeSync4, fsyncSync, fstatSync as fstatSync4, lstatSync as lstatSync5, mkdirSync as mkdirSync11, openSync as openSync4, readSync as readSync2, realpathSync as realpathSync2, renameSync as renameSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "fs";
32117
+ import { dirname as dirname8, isAbsolute as isAbsolute4, join as join20, resolve as resolve2 } from "path";
32118
+ import { randomUUID } from "crypto";
32119
+ function safeDirectory(directory) {
32120
+ if (!isAbsolute4(directory) || directory !== resolve2(directory) || realpathSync2(directory) !== directory)
32121
+ return fail2();
32122
+ for (let path = directory;; path = dirname8(path)) {
32123
+ const stat2 = lstatSync5(path);
32124
+ if (!stat2.isDirectory() || stat2.isSymbolicLink())
32125
+ return fail2();
32126
+ if (path === dirname8(path))
32127
+ break;
32128
+ }
32129
+ const own = lstatSync5(directory);
32130
+ if ((own.mode & 63) !== 0 || process.getuid && own.uid !== process.getuid())
32131
+ return fail2();
32132
+ return { dev: own.dev, ino: own.ino };
32133
+ }
32134
+ function unchangedDirectory(directory, identity) {
32135
+ const now = safeDirectory(directory);
32136
+ if (now.dev !== identity.dev || now.ino !== identity.ino)
32137
+ return fail2();
32138
+ }
32139
+ function readOwned(directory, name, max) {
32140
+ const identity = safeDirectory(directory), file = join20(directory, name);
32141
+ const fd = openSync4(file, constants3.O_RDONLY | constants3.O_NOFOLLOW);
32142
+ try {
32143
+ const stat2 = fstatSync4(fd);
32144
+ if (!stat2.isFile() || stat2.nlink !== 1 || stat2.size > max || stat2.size < 1 || (stat2.mode & 63) !== 0 || process.getuid && stat2.uid !== process.getuid())
32145
+ return fail2();
32146
+ const buffer = Buffer.alloc(Math.min(max, stat2.size) + 1);
32147
+ let length = 0;
32148
+ while (length < buffer.length) {
32149
+ const count = readSync2(fd, buffer, length, buffer.length - length, length);
32150
+ if (count === 0)
32151
+ break;
32152
+ length += count;
32153
+ }
32154
+ const bytes = buffer.subarray(0, length), after = fstatSync4(fd);
32155
+ unchangedDirectory(directory, identity);
32156
+ if (bytes.length !== stat2.size || stat2.size !== after.size || stat2.mtimeMs !== after.mtimeMs || stat2.ctimeMs !== after.ctimeMs)
32157
+ return fail2();
32158
+ return bytes;
32159
+ } finally {
32160
+ closeSync4(fd);
32161
+ }
32162
+ }
32163
+ function writeOwned(directory, name, bytes) {
32164
+ const fd = openSync4(join20(directory, name), constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | constants3.O_NOFOLLOW, 384);
32165
+ try {
32166
+ writeFileSync10(fd, bytes);
32167
+ fsyncSync(fd);
32168
+ } finally {
32169
+ closeSync4(fd);
32170
+ }
32171
+ }
32172
+ function save(directory, value) {
32173
+ const identity = safeDirectory(directory), temporary = `.receipt-${randomUUID()}.json`;
32174
+ writeOwned(directory, temporary, JSON.stringify(value) + `
32175
+ `);
32176
+ unchangedDirectory(directory, identity);
32177
+ renameSync4(join20(directory, temporary), join20(directory, "receipt.json"));
32178
+ const fd = openSync4(directory, constants3.O_RDONLY | constants3.O_NOFOLLOW);
32179
+ try {
32180
+ fsyncSync(fd);
32181
+ } finally {
32182
+ closeSync4(fd);
32183
+ }
32184
+ }
32185
+ function bind(client, receipt) {
32186
+ if (["apiOrigin", "organizationId", "userId", "membershipId"].some((k) => client[k] !== receipt[k]))
32187
+ throw new PrivatePublicationError("PUBLICATION_IDENTITY_CHANGED", "The fresh session does not match the recovery directory's server, account and membership.");
32188
+ }
32189
+ async function locked(directory, action) {
32190
+ const identity = safeDirectory(directory), file = join20(directory, "operation.lock");
32191
+ let fd;
32192
+ try {
32193
+ fd = openSync4(file, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | constants3.O_NOFOLLOW, 384);
32194
+ } catch {
32195
+ 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.");
32196
+ }
32197
+ const lock = fstatSync4(fd);
32198
+ try {
32199
+ writeFileSync10(fd, JSON.stringify({ pid: process.pid }) + `
32200
+ `);
32201
+ fsyncSync(fd);
32202
+ return await action();
32203
+ } finally {
32204
+ closeSync4(fd);
32205
+ unchangedDirectory(directory, identity);
32206
+ const now = lstatSync5(file);
32207
+ if (now.dev !== lock.dev || now.ino !== lock.ino || !now.isFile() || now.isSymbolicLink())
32208
+ fail2();
32209
+ unlinkSync2(file);
32210
+ }
32211
+ }
32212
+ function readPrivatePublicationRecovery(directory) {
32213
+ try {
32214
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(readOwned(directory, "receipt.json", 65536)));
32215
+ 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))
32216
+ return fail2();
32217
+ value.declaration = checkedPublicationDeclaration(value.declaration);
32218
+ if (value.intent !== null)
32219
+ value.intent = checkedPublicationView(value.intent, value.skillId, undefined, value.declaration);
32220
+ if (value.intent === null && !["prepared", "begin_uncertain"].includes(value.phase))
32221
+ return fail2();
32222
+ const bytes = readOwned(directory, "bundle.tgz", PRIVATE_PUBLICATION_MAX_BYTES);
32223
+ if (bytes.length !== value.declaration.archiveByteSize || publicationSha256(bytes) !== value.declaration.archiveSha256)
32224
+ return fail2();
32225
+ return { receipt: value, bytes };
32226
+ } catch {
32227
+ return fail2();
32228
+ }
32229
+ }
32230
+ async function preparePrivatePublication(client, sourceDirectory, recoveryDirectory, input) {
32231
+ if (!publicationUuid(input.skillId) || !(input.expectedCurrentVersionId === null || publicationUuid(input.expectedCurrentVersionId)) || input.idempotencyKey !== undefined && !publicationUuid(input.idempotencyKey))
32232
+ return fail2();
32233
+ const packed = packSkillBundle(sourceDirectory, { maxUnpackedBytes: 32 * 1024 * 1024 });
32234
+ const inspected = await inspectSkillBundle(packed.bytes, { limits: { compressedBytes: PRIVATE_PUBLICATION_MAX_BYTES } });
32235
+ const manifest = inspected.entries.find((entry) => entry.path === "skill.json");
32236
+ if (!manifest || manifest.bytes.length > 16384 || !(await verifyContentHashFromEntries(inspected.entries)).valid)
32237
+ throw new PrivatePublicationError("PUBLICATION_BUNDLE_INVALID", "The skill must have a valid skill.json with its current content hash. Validate the skill before publishing.");
32238
+ const manifestText = new TextDecoder("utf-8", { fatal: true }).decode(manifest.bytes);
32239
+ const declaration = checkedPublicationDeclaration({
32240
+ idempotencyKey: input.idempotencyKey ?? randomUUID(),
32241
+ version: JSON.parse(manifestText).version,
32242
+ expectedCurrentVersionId: input.expectedCurrentVersionId,
32243
+ manifestText,
32244
+ archiveSha256: inspected.sha256,
32245
+ archiveByteSize: packed.bytes.length
32246
+ });
32247
+ const receipt = {
32248
+ contractVersion: 1,
32249
+ apiOrigin: client.apiOrigin,
32250
+ organizationId: client.organizationId,
32251
+ userId: client.userId,
32252
+ membershipId: client.membershipId,
32253
+ skillId: input.skillId,
32254
+ declaration,
32255
+ phase: "prepared",
32256
+ intent: null
32257
+ };
32258
+ if (!isAbsolute4(recoveryDirectory) || resolve2(recoveryDirectory) !== recoveryDirectory || realpathSync2(dirname8(recoveryDirectory)) !== dirname8(recoveryDirectory))
32259
+ return fail2();
32260
+ mkdirSync11(recoveryDirectory, { mode: 448 });
32261
+ safeDirectory(recoveryDirectory);
32262
+ const parent = openSync4(dirname8(recoveryDirectory), constants3.O_RDONLY | constants3.O_NOFOLLOW);
32263
+ try {
32264
+ fsyncSync(parent);
32265
+ } finally {
32266
+ closeSync4(parent);
32267
+ }
32268
+ writeOwned(recoveryDirectory, "bundle.tgz", packed.bytes);
32269
+ save(recoveryDirectory, receipt);
32270
+ return receipt;
32271
+ }
32272
+ function privatePublicationResult(directory, receipt) {
32273
+ const state = receipt.intent?.state ?? receipt.phase, committed = state === "committed";
32274
+ 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.";
32275
+ return {
32276
+ recoveryDirectory: directory,
32277
+ skillId: receipt.skillId,
32278
+ intentId: receipt.intent?.id ?? null,
32279
+ state,
32280
+ versionId: receipt.intent?.versionId ?? null,
32281
+ committed,
32282
+ executionEnabled: false,
32283
+ nextAction
32284
+ };
32285
+ }
32286
+ async function continuePrivatePublication(client, directory, options) {
32287
+ if (options.confirm !== true)
32288
+ throw new PrivatePublicationError("PUBLICATION_CONFIRM_REQUIRED", "Explicit upload confirmation is required.");
32289
+ if (options.waitMs !== undefined && (!Number.isSafeInteger(options.waitMs) || options.waitMs < 0 || options.waitMs > 300000))
32290
+ return fail2();
32291
+ return locked(directory, () => continueLocked(client, directory, options));
32292
+ }
32293
+ async function continueLocked(client, directory, options) {
32294
+ const { receipt, bytes } = readPrivatePublicationRecovery(directory);
32295
+ bind(client, receipt);
32296
+ if (!receipt.intent) {
32297
+ receipt.phase = "begin_uncertain";
32298
+ save(directory, receipt);
32299
+ receipt.intent = await client.begin(receipt.skillId, receipt.declaration);
32300
+ receipt.phase = "awaiting_upload";
32301
+ save(directory, receipt);
32302
+ } else {
32303
+ receipt.intent = checkedPublicationView(await client.get(receipt.skillId, receipt.intent.id), receipt.skillId, receipt.intent.id, receipt.declaration);
32304
+ save(directory, receipt);
32305
+ }
32306
+ if (receipt.intent.state === "awaiting_upload") {
32307
+ if (receipt.phase === "awaiting_upload") {
32308
+ receipt.phase = "upload_uncertain";
32309
+ save(directory, receipt);
32310
+ try {
32311
+ await client.upload(receipt.skillId, receipt.intent, bytes);
32312
+ } catch (error2) {
32313
+ if (error2 instanceof PrivatePublicationError && !error2.uncertain) {
32314
+ receipt.phase = "awaiting_upload";
32315
+ save(directory, receipt);
32316
+ }
32317
+ throw error2;
32318
+ }
32319
+ receipt.phase = "uploaded";
32320
+ save(directory, receipt);
32321
+ }
32322
+ receipt.phase = "finalize_uncertain";
32323
+ save(directory, receipt);
32324
+ receipt.intent = checkedPublicationView(await client.finalize(receipt.skillId, receipt.intent.id), receipt.skillId, receipt.intent.id, receipt.declaration);
32325
+ receipt.phase = "observed";
32326
+ save(directory, receipt);
32327
+ }
32328
+ if (options.waitMs !== undefined && options.waitMs > 0 && ["queued", "verifying"].includes(receipt.intent.state)) {
32329
+ receipt.intent = checkedPublicationView(await client.wait(receipt.skillId, receipt.intent.id, { timeoutMs: options.waitMs }), receipt.skillId, receipt.intent.id, receipt.declaration);
32330
+ receipt.phase = "observed";
32331
+ save(directory, receipt);
32332
+ }
32333
+ return privatePublicationResult(directory, receipt);
32334
+ }
32335
+ async function inspectPrivatePublication(client, directory, cancel = false) {
32336
+ return locked(directory, () => inspectLocked(client, directory, cancel));
32337
+ }
32338
+ async function inspectLocked(client, directory, cancel) {
32339
+ const { receipt } = readPrivatePublicationRecovery(directory);
32340
+ bind(client, receipt);
32341
+ if (receipt.intent) {
32342
+ 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);
32343
+ save(directory, receipt);
32344
+ } else if (cancel)
32345
+ throw new PrivatePublicationError("PUBLICATION_INTENT_UNKNOWN", "Reconcile the saved begin request with publication resume before cancelling its intent.");
32346
+ return privatePublicationResult(directory, receipt);
32347
+ }
32348
+ var fail2 = () => {
32349
+ 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.");
32350
+ };
32351
+ var init_private_publication_recovery = __esm(() => {
32352
+ init_skill_bundle();
32353
+ init_skill_hash();
32354
+ init_remote_private_publications();
32355
+ init_fleet_credentials();
32356
+ });
32357
+
32358
+ // src/mcp/private-publication-tools.ts
32359
+ import { isAbsolute as isAbsolute5 } from "path";
32360
+ function registerPrivatePublicationTools(server) {
32361
+ 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}$/);
32362
+ const directory = exports_external.string().max(4096).refine(isAbsolute5, "Use an absolute local directory");
32363
+ const verification = { email: exports_external.string().email().max(254), code: exports_external.string().regex(/^\d{6}$/), userId: uuid6, membershipId: uuid6, recoveryDirectory: directory };
32364
+ 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.";
32365
+ const handler = (action) => async (value) => {
32366
+ try {
32367
+ const client = await privatePublicationSession(String(value.email), String(value.code), { userId: String(value.userId), membershipId: String(value.membershipId) });
32368
+ const recovery = String(value.recoveryDirectory);
32369
+ if (action === "publish")
32370
+ await preparePrivatePublication(client, String(value.directory), recovery, { skillId: String(value.skillId), expectedCurrentVersionId: value.expectedCurrentVersionId, idempotencyKey: value.idempotencyKey });
32371
+ const result = action === "publish" || action === "resume" ? await continuePrivatePublication(client, recovery, { confirm: true, waitMs: value.waitMs }) : await inspectPrivatePublication(client, recovery, action === "cancel");
32372
+ return mcpJson(result);
32373
+ } catch (error2) {
32374
+ const result = privatePublicationCustomerError(error2);
32375
+ return mcpError(result.code, result.error);
32376
+ }
32377
+ };
32378
+ server.registerTool("publish_private_skill", {
32379
+ title: "Publish private skill",
32380
+ description,
32381
+ annotations: { destructiveHint: true, readOnlyHint: false, idempotentHint: false },
32382
+ 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()
32383
+ }, handler("publish"));
32384
+ server.registerTool("get_private_publication", {
32385
+ title: "Get private publication",
32386
+ description,
32387
+ annotations: { destructiveHint: false, readOnlyHint: true, idempotentHint: true },
32388
+ inputSchema: exports_external.object(verification).strict()
32389
+ }, handler("get"));
32390
+ server.registerTool("resume_private_publication", {
32391
+ title: "Resume private publication",
32392
+ description,
32393
+ annotations: { destructiveHint: true, readOnlyHint: false, idempotentHint: true },
32394
+ inputSchema: exports_external.object({ ...verification, confirm: exports_external.literal(true), waitMs: exports_external.number().int().min(0).max(300000).optional() }).strict()
32395
+ }, handler("resume"));
32396
+ server.registerTool("cancel_private_publication", {
32397
+ title: "Cancel private publication",
32398
+ description,
32399
+ annotations: { destructiveHint: true, readOnlyHint: false, idempotentHint: true },
32400
+ inputSchema: exports_external.object({ ...verification, confirm: exports_external.literal(true) }).strict()
32401
+ }, handler("cancel"));
32402
+ }
32403
+ var init_private_publication_tools = __esm(() => {
32404
+ init_zod();
32405
+ init_private_publication_customer();
32406
+ init_private_publication_recovery();
32407
+ init_helpers();
32408
+ });
32409
+
30916
32410
  // src/mcp/remote-customer-tools.ts
30917
32411
  function registerRemoteCustomerTools(server) {
30918
32412
  registerRemoteInvitationTools(server);
32413
+ registerPrivatePublicationTools(server);
30919
32414
  const memberRole = exports_external.enum(["owner", "admin", "member", "viewer"]);
30920
32415
  const memberInput = {
30921
32416
  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}$/),
@@ -31033,8 +32528,16 @@ function registerRemoteCustomerTools(server) {
31033
32528
  server.registerTool("quote_skill", {
31034
32529
  title: "Quote Remote Skill",
31035
32530
  description: "Get the configured server's credit quote without submitting a run.",
31036
- inputSchema: { name: exports_external.string(), input: exports_external.record(exports_external.string(), exports_external.unknown()).optional(), args: exports_external.array(exports_external.string()).optional() }
31037
- }, ({ name, input, args }) => callRemote((client) => client.quoteRun(name, input, args)));
32531
+ inputSchema: {
32532
+ name: exports_external.string(),
32533
+ input: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
32534
+ args: exports_external.array(exports_external.string()).optional(),
32535
+ 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.")
32536
+ }
32537
+ }, ({ name, input, args, files }) => callRemote((client) => {
32538
+ const descriptors = describeRemoteFiles(decodeRemoteFiles(files ?? []));
32539
+ return client.quoteRun(name, input, args, descriptors.length ? descriptors : undefined);
32540
+ }));
31038
32541
  server.registerTool("download_run_artifact", {
31039
32542
  title: "Download Verified Run Artifact",
31040
32543
  description: "Return verified artifact bytes as base64 (at most 1 MiB); use the CLI for larger files.",
@@ -31068,6 +32571,7 @@ async function freshAccount(action, operation) {
31068
32571
  }
31069
32572
  var init_remote_customer_tools = __esm(() => {
31070
32573
  init_remote_invitation_tools();
32574
+ init_private_publication_tools();
31071
32575
  init_zod();
31072
32576
  init_remote_auth();
31073
32577
  init_workspace_profile();
@@ -31075,6 +32579,7 @@ var init_remote_customer_tools = __esm(() => {
31075
32579
  init_remote_client();
31076
32580
  init_remote_workspace_leave();
31077
32581
  init_helpers();
32582
+ init_remote_files();
31078
32583
  });
31079
32584
 
31080
32585
  // src/mcp/server.ts
@@ -31488,7 +32993,7 @@ var RequestError, toRequestError = (e) => {
31488
32993
  });
31489
32994
  if (!chunk) {
31490
32995
  if (i === 1) {
31491
- await new Promise((resolve2) => setTimeout(resolve2));
32996
+ await new Promise((resolve3) => setTimeout(resolve3));
31492
32997
  maxReadCount = 3;
31493
32998
  continue;
31494
32999
  }
@@ -32323,9 +33828,9 @@ data:
32323
33828
  const initRequest = messages.find((m) => isInitializeRequest(m));
32324
33829
  const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
32325
33830
  if (this._enableJsonResponse) {
32326
- return new Promise((resolve2) => {
33831
+ return new Promise((resolve3) => {
32327
33832
  this._streamMapping.set(streamId, {
32328
- resolveJson: resolve2,
33833
+ resolveJson: resolve3,
32329
33834
  cleanup: () => {
32330
33835
  this._streamMapping.delete(streamId);
32331
33836
  }
@@ -32694,19 +34199,19 @@ async function connectMcpForNode() {
32694
34199
  return { server: server2, transport };
32695
34200
  }
32696
34201
  function readBody(req) {
32697
- return new Promise((resolve2, reject) => {
34202
+ return new Promise((resolve3, reject) => {
32698
34203
  const chunks = [];
32699
34204
  req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
32700
34205
  req.on("end", () => {
32701
34206
  const raw = Buffer.concat(chunks).toString("utf-8");
32702
34207
  if (!raw.trim()) {
32703
- resolve2(undefined);
34208
+ resolve3(undefined);
32704
34209
  return;
32705
34210
  }
32706
34211
  try {
32707
- resolve2(JSON.parse(raw));
34212
+ resolve3(JSON.parse(raw));
32708
34213
  } catch {
32709
- resolve2(undefined);
34214
+ resolve3(undefined);
32710
34215
  }
32711
34216
  });
32712
34217
  req.on("error", reject);
@@ -32748,9 +34253,9 @@ async function startSkillsMcpHttpServer(options = {}) {
32748
34253
  }
32749
34254
  }
32750
34255
  });
32751
- await new Promise((resolve2, reject) => {
34256
+ await new Promise((resolve3, reject) => {
32752
34257
  httpServer.once("error", reject);
32753
- httpServer.listen(port, hostname2, () => resolve2());
34258
+ httpServer.listen(port, hostname2, () => resolve3());
32754
34259
  });
32755
34260
  const address = httpServer.address();
32756
34261
  const listenPort = typeof address === "object" && address ? address.port : port;