@caplets/core 0.25.1 → 0.26.1

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.
@@ -1,5 +1,5 @@
1
1
  import { A as safeParseAsync$1, C as toJSONSchema, D as parse$3, E as $ZodType, F as NEVER, M as defineLazy, N as normalizeParams, O as parseAsync, P as $constructor, S as datetime, T as $ZodObject, _ as record, a as any, b as unknown, c as custom, d as literal, f as looseObject, g as preprocess, h as optional, i as _null, j as clone, k as safeParse$1, l as discriminatedUnion, m as object$1, o as array, p as number$1, r as _enum, s as boolean, t as ZodNumber$1, u as intersection, v as string, w as _coercedNumber, x as url, y as union } from "./schemas-BoqMu4MG.js";
2
- import { a as isAllowedHttpBaseUrl, c as validateHttpActionHeaders, d as errorResult, f as redactSecrets, i as SERVER_ID_PATTERN, n as HEADER_NAME_PATTERN, o as isAllowedRemoteUrl, p as toSafeError, r as HTTP_BASE_URL_PATTERN, s as isUrl, t as FORBIDDEN_HEADERS, u as CapletsError } from "./validation-C4tYXw6G.js";
2
+ import { a as isAllowedHttpBaseUrl, c as validateHttpActionHeaders, d as errorResult, f as redactSecrets, i as SERVER_ID_PATTERN, n as HEADER_NAME_PATTERN, o as isAllowedRemoteUrl, p as toSafeError, r as HTTP_BASE_URL_PATTERN, s as isUrl, t as FORBIDDEN_HEADERS, u as CapletsError } from "./validation-GD2x5HW1.js";
3
3
  import { generatedToolInputJsonSchema, generatedToolInputJsonSchemaForCaplet, generatedToolInputSchemaForCaplet, mcpOperations, operations } from "./generated-tool-input-schema.js";
4
4
  import { f as observedOutputShapeKey, i as observeOutputShape, r as normalizedObservableValue, t as usefulOutputSchema, u as FileObservedOutputShapeStore } from "./observed-output-shapes-DuP7mJQf.js";
5
5
  import { createRequire } from "node:module";
@@ -9,8 +9,9 @@ import { spawn } from "node:child_process";
9
9
  import process$1 from "node:process";
10
10
  import { PassThrough } from "node:stream";
11
11
  import { createServer } from "node:http";
12
- import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
12
+ import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
13
13
  import { homedir } from "node:os";
14
+ import { Buffer as Buffer$1 } from "node:buffer";
14
15
  import { readFile } from "node:fs/promises";
15
16
  import ts from "typescript";
16
17
  import { getQuickJS, shouldInterruptAfterDeadline } from "quickjs-emscripten";
@@ -26078,7 +26079,7 @@ const capletMcpServerSchema = object$1({
26078
26079
  path: ["url"],
26079
26080
  message: "remote servers require url"
26080
26081
  });
26081
- if (server.url && !hasEnvReference$2(server.url) && !isAllowedRemoteUrl(server.url)) ctx.addIssue({
26082
+ if (server.url && !hasInterpolationReference$2(server.url) && !isAllowedRemoteUrl(server.url)) ctx.addIssue({
26082
26083
  code: "custom",
26083
26084
  path: ["url"],
26084
26085
  message: "remote url must use https except loopback development urls"
@@ -26091,7 +26092,7 @@ const capletMcpServerSchema = object$1({
26091
26092
  "redirectUri"
26092
26093
  ]) {
26093
26094
  const value = server.auth[field];
26094
- if (value && !hasEnvReference$2(value) && !isUrl(value)) ctx.addIssue({
26095
+ if (value && !hasInterpolationReference$2(value) && !isUrl(value)) ctx.addIssue({
26095
26096
  code: "custom",
26096
26097
  path: ["auth", field],
26097
26098
  message: `${field} must be a URL or environment reference`
@@ -26125,12 +26126,12 @@ const capletOpenApiEndpointSchema = object$1({
26125
26126
  code: "custom",
26126
26127
  message: "openapiEndpoint must define exactly one spec source: specPath or specUrl"
26127
26128
  });
26128
- if (endpoint.specUrl && !hasEnvReference$2(endpoint.specUrl) && !isAllowedRemoteUrl(endpoint.specUrl)) ctx.addIssue({
26129
+ if (endpoint.specUrl && !hasInterpolationReference$2(endpoint.specUrl) && !isAllowedRemoteUrl(endpoint.specUrl)) ctx.addIssue({
26129
26130
  code: "custom",
26130
26131
  path: ["specUrl"],
26131
26132
  message: "OpenAPI specUrl must use https except loopback development urls"
26132
26133
  });
26133
- if (endpoint.baseUrl && !hasEnvReference$2(endpoint.baseUrl) && !isAllowedRemoteUrl(endpoint.baseUrl)) ctx.addIssue({
26134
+ if (endpoint.baseUrl && !hasInterpolationReference$2(endpoint.baseUrl) && !isAllowedRemoteUrl(endpoint.baseUrl)) ctx.addIssue({
26134
26135
  code: "custom",
26135
26136
  path: ["baseUrl"],
26136
26137
  message: "OpenAPI baseUrl must use https except loopback development urls"
@@ -26155,12 +26156,12 @@ const capletGoogleDiscoveryApiSchema = object$1({
26155
26156
  code: "custom",
26156
26157
  message: "googleDiscoveryApi must define exactly one discovery source: discoveryPath or discoveryUrl"
26157
26158
  });
26158
- if (api.discoveryUrl && !hasEnvReference$2(api.discoveryUrl) && !isAllowedRemoteUrl(api.discoveryUrl)) ctx.addIssue({
26159
+ if (api.discoveryUrl && !hasInterpolationReference$2(api.discoveryUrl) && !isAllowedRemoteUrl(api.discoveryUrl)) ctx.addIssue({
26159
26160
  code: "custom",
26160
26161
  path: ["discoveryUrl"],
26161
26162
  message: "Google Discovery discoveryUrl must use https except loopback development urls"
26162
26163
  });
26163
- if (api.baseUrl && !hasEnvReference$2(api.baseUrl) && !isAllowedHttpBaseUrl(api.baseUrl)) ctx.addIssue({
26164
+ if (api.baseUrl && !hasInterpolationReference$2(api.baseUrl) && !isAllowedHttpBaseUrl(api.baseUrl)) ctx.addIssue({
26164
26165
  code: "custom",
26165
26166
  path: ["baseUrl"],
26166
26167
  message: "Google Discovery baseUrl must use https except loopback development urls and must not include credentials, query, or fragment"
@@ -26197,12 +26198,12 @@ const capletGraphQlEndpointSchema = object$1({
26197
26198
  code: "custom",
26198
26199
  message: "graphqlEndpoint must define exactly one schema source: schemaPath, schemaUrl, or introspection"
26199
26200
  });
26200
- if (endpoint.endpointUrl && !hasEnvReference$2(endpoint.endpointUrl) && !isAllowedRemoteUrl(endpoint.endpointUrl)) ctx.addIssue({
26201
+ if (endpoint.endpointUrl && !hasInterpolationReference$2(endpoint.endpointUrl) && !isAllowedRemoteUrl(endpoint.endpointUrl)) ctx.addIssue({
26201
26202
  code: "custom",
26202
26203
  path: ["endpointUrl"],
26203
26204
  message: "GraphQL endpointUrl must use https except loopback development urls"
26204
26205
  });
26205
- if (endpoint.schemaUrl && !hasEnvReference$2(endpoint.schemaUrl) && !isAllowedRemoteUrl(endpoint.schemaUrl)) ctx.addIssue({
26206
+ if (endpoint.schemaUrl && !hasInterpolationReference$2(endpoint.schemaUrl) && !isAllowedRemoteUrl(endpoint.schemaUrl)) ctx.addIssue({
26206
26207
  code: "custom",
26207
26208
  path: ["schemaUrl"],
26208
26209
  message: "GraphQL schemaUrl must use https except loopback development urls"
@@ -26246,7 +26247,7 @@ const capletHttpApiSchema = object$1({
26246
26247
  projectBinding: capletProjectBindingSchema.optional(),
26247
26248
  runtime: capletRuntimeRequirementsSchema.optional()
26248
26249
  }).strict().superRefine((api, ctx) => {
26249
- if (api.baseUrl && !hasEnvReference$2(api.baseUrl) && !isAllowedHttpBaseUrl(api.baseUrl)) ctx.addIssue({
26250
+ if (api.baseUrl && !hasInterpolationReference$2(api.baseUrl) && !isAllowedHttpBaseUrl(api.baseUrl)) ctx.addIssue({
26250
26251
  code: "custom",
26251
26252
  path: ["baseUrl"],
26252
26253
  message: "HTTP API baseUrl must use https except loopback development urls and must not include credentials, query, or fragment"
@@ -26445,7 +26446,7 @@ function discoverCapletFileMapCandidates(paths) {
26445
26446
  });
26446
26447
  }
26447
26448
  return candidates.map(({ id, path }) => {
26448
- validateCapletId(id, path);
26449
+ validateCapletId$1(id, path);
26449
26450
  return {
26450
26451
  id,
26451
26452
  path
@@ -26551,7 +26552,7 @@ function normalizeGraphQlOperations(operations, baseDir, normalizePath) {
26551
26552
  }]));
26552
26553
  }
26553
26554
  function normalizeBundleLocalPath(value, baseDir) {
26554
- if (!value || isMapAbsolutePath(value) || hasEnvReference$2(value)) return value;
26555
+ if (!value || isMapAbsolutePath(value) || hasInterpolationReference$2(value)) return value;
26555
26556
  const parts = [...baseDir ? baseDir.split("/") : [], ...value.split("/")];
26556
26557
  const normalized = [];
26557
26558
  for (const part of parts) {
@@ -26625,21 +26626,18 @@ function parseFrontmatter(text, path) {
26625
26626
  function isPlainObject$6(value) {
26626
26627
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
26627
26628
  }
26628
- function validateCapletId(id, path) {
26629
+ function validateCapletId$1(id, path) {
26629
26630
  if (!SERVER_ID_PATTERN.test(id)) throw new CapletsError("CONFIG_INVALID", `Caplet file at ${path} derives invalid ID ${id}; ID must match ^[a-zA-Z0-9_-]{1,64}$`);
26630
26631
  }
26631
26632
  function errorMessage$4(error) {
26632
26633
  return error instanceof Error ? error.message : String(error);
26633
26634
  }
26634
- function hasEnvReference$2(value) {
26635
- return /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$env:[A-Za-z_][A-Za-z0-9_]*/.test(value);
26635
+ function hasInterpolationReference$2(value) {
26636
+ return /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$env:[A-Za-z_][A-Za-z0-9_]*|\$\{vault:[^}]+\}|\$vault:[A-Za-z0-9_-]+/.test(value);
26636
26637
  }
26637
26638
  //#endregion
26638
26639
  //#region src/caplet-files.ts
26639
26640
  const MAX_CAPLET_FILE_BYTES = 128 * 1024;
26640
- function loadCapletFiles(root) {
26641
- return loadCapletFilesWithPaths(root)?.config;
26642
- }
26643
26641
  function loadCapletFilesWithPaths(root) {
26644
26642
  if (!existsSync(root)) return;
26645
26643
  return buildCapletFileLoadResultFromEntries(root, discoverCapletFiles(root), (path) => readCapletFile(path));
@@ -26653,7 +26651,7 @@ function discoverCapletFiles(root) {
26653
26651
  const entries = readdirSync(root, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
26654
26652
  const candidates = [];
26655
26653
  function addCandidate(id, path) {
26656
- validateCapletId(id, path);
26654
+ validateCapletId$1(id, path);
26657
26655
  candidates.push({
26658
26656
  id,
26659
26657
  path
@@ -26679,7 +26677,7 @@ function discoverCapletFilesBestEffort(root, warnings) {
26679
26677
  const duplicateIds = /* @__PURE__ */ new Set();
26680
26678
  function addCandidate(id, path, isDirectoryCaplet) {
26681
26679
  try {
26682
- validateCapletId(id, path);
26680
+ validateCapletId$1(id, path);
26683
26681
  } catch (error) {
26684
26682
  warnings.push({
26685
26683
  path,
@@ -26754,11 +26752,420 @@ function validateCapletFile(path) {
26754
26752
  readCapletFile(path);
26755
26753
  }
26756
26754
  function normalizeLocalPath$1(value, baseDir) {
26757
- if (!value || isAbsolute(value) || hasEnvReference$1(value)) return value;
26755
+ if (!value || isAbsolute(value) || hasInterpolationReference$1(value)) return value;
26758
26756
  return join(baseDir, value);
26759
26757
  }
26760
- function hasEnvReference$1(value) {
26761
- return /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$env:[A-Za-z_][A-Za-z0-9_]*/.test(value);
26758
+ function hasInterpolationReference$1(value) {
26759
+ return /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$env:[A-Za-z_][A-Za-z0-9_]*|\$\{vault:[^}]+\}|\$vault:[A-Za-z0-9_-]+/.test(value);
26760
+ }
26761
+ //#endregion
26762
+ //#region src/vault/crypto.ts
26763
+ const NONCE_BYTES = 12;
26764
+ function encryptVaultValue(input) {
26765
+ const nonce = randomBytes(NONCE_BYTES);
26766
+ const cipher = createCipheriv("aes-256-gcm", input.key, nonce);
26767
+ const ciphertext = Buffer$1.concat([cipher.update(input.plaintext, "utf8"), cipher.final()]);
26768
+ const authTag = cipher.getAuthTag();
26769
+ const timestamp = input.now.toISOString();
26770
+ return {
26771
+ version: 1,
26772
+ algorithm: "aes-256-gcm",
26773
+ nonce: nonce.toString("base64url"),
26774
+ ciphertext: ciphertext.toString("base64url"),
26775
+ authTag: authTag.toString("base64url"),
26776
+ valueBytes: Buffer$1.byteLength(input.plaintext, "utf8"),
26777
+ createdAt: input.existing?.createdAt ?? timestamp,
26778
+ updatedAt: timestamp
26779
+ };
26780
+ }
26781
+ function decryptVaultValue(record, key) {
26782
+ const parsed = parseEncryptedRecord(record);
26783
+ try {
26784
+ const decipher = createDecipheriv("aes-256-gcm", key, Buffer$1.from(parsed.nonce, "base64url"));
26785
+ decipher.setAuthTag(Buffer$1.from(parsed.authTag, "base64url"));
26786
+ return Buffer$1.concat([decipher.update(Buffer$1.from(parsed.ciphertext, "base64url")), decipher.final()]).toString("utf8");
26787
+ } catch {
26788
+ throw new CapletsError("CONFIG_INVALID", "Vault encrypted record could not be decrypted.");
26789
+ }
26790
+ }
26791
+ function parseEncryptedRecord(record) {
26792
+ if (!record || typeof record !== "object" || Array.isArray(record)) throw new CapletsError("CONFIG_INVALID", "Vault encrypted record must be an object.");
26793
+ const value = record;
26794
+ if (value.version !== 1 || value.algorithm !== "aes-256-gcm") throw new CapletsError("CONFIG_INVALID", "Vault encrypted record version is unsupported.");
26795
+ if (typeof value.nonce !== "string" || typeof value.ciphertext !== "string" || typeof value.authTag !== "string" || typeof value.valueBytes !== "number" || typeof value.createdAt !== "string" || typeof value.updatedAt !== "string") throw new CapletsError("CONFIG_INVALID", "Vault encrypted record is malformed.");
26796
+ return value;
26797
+ }
26798
+ //#endregion
26799
+ //#region src/vault/store.ts
26800
+ function ensurePrivateDir(path) {
26801
+ mkdirSync(path, {
26802
+ recursive: true,
26803
+ mode: 448
26804
+ });
26805
+ try {
26806
+ chmodSync(path, 448);
26807
+ } catch {}
26808
+ }
26809
+ function writePrivateFileAtomic(path, contents) {
26810
+ ensurePrivateDir(dirname(path));
26811
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
26812
+ writeFileSync(tempPath, contents, { mode: 384 });
26813
+ try {
26814
+ chmodSync(tempPath, 384);
26815
+ } catch {}
26816
+ renameSync(tempPath, path);
26817
+ }
26818
+ function readJsonFile(path, fallback) {
26819
+ if (!existsSync(path)) return fallback;
26820
+ return JSON.parse(readFileSync(path, "utf8"));
26821
+ }
26822
+ function deleteFile(path) {
26823
+ if (!existsSync(path)) return false;
26824
+ rmSync(path, { force: true });
26825
+ return true;
26826
+ }
26827
+ //#endregion
26828
+ //#region src/vault/keys.ts
26829
+ const VAULT_KEY_PATTERN = /^[A-Z_][A-Z0-9_]{0,127}$/;
26830
+ const KEY_FILE_PREFIX = "caplets-vault-key-v1.";
26831
+ const KEY_BYTES = 32;
26832
+ function validateVaultKeyName(name) {
26833
+ if (!VAULT_KEY_PATTERN.test(name)) throw new CapletsError("REQUEST_INVALID", "Vault key names must match ^[A-Z_][A-Z0-9_]{0,127}$");
26834
+ return name;
26835
+ }
26836
+ function loadVaultKey(input) {
26837
+ const envKey = input.env?.CAPLETS_ENCRYPTION_KEY;
26838
+ if (envKey !== void 0) return decodeExactKey(envKey, "CAPLETS_ENCRYPTION_KEY");
26839
+ const status = vaultKeySourceStatus(input);
26840
+ if (!status.available) throw new CapletsError("CONFIG_INVALID", `Vault key source is unavailable: ${"reason" in status ? status.reason : "invalid"}`);
26841
+ return parseKeyFile(readFileSync(input.keyFile, "utf8"));
26842
+ }
26843
+ function ensureVaultKey(input) {
26844
+ const envKey = input.env?.CAPLETS_ENCRYPTION_KEY;
26845
+ if (envKey !== void 0) return decodeExactKey(envKey, "CAPLETS_ENCRYPTION_KEY");
26846
+ if (!existsSync(input.keyFile)) {
26847
+ ensurePrivateDir(dirname(input.keyFile));
26848
+ const encoded = randomBytes(KEY_BYTES).toString("base64url");
26849
+ writePrivateFileAtomic(input.keyFile, `${KEY_FILE_PREFIX}${encoded}\n`);
26850
+ try {
26851
+ chmodSync(input.keyFile, 384);
26852
+ } catch {}
26853
+ }
26854
+ return loadVaultKey(input);
26855
+ }
26856
+ function vaultKeySourceStatus(input) {
26857
+ const envKey = input.env?.CAPLETS_ENCRYPTION_KEY;
26858
+ if (envKey !== void 0) try {
26859
+ decodeExactKey(envKey, "CAPLETS_ENCRYPTION_KEY");
26860
+ return {
26861
+ available: true,
26862
+ source: "env"
26863
+ };
26864
+ } catch {
26865
+ return {
26866
+ available: false,
26867
+ source: "env",
26868
+ reason: "invalid"
26869
+ };
26870
+ }
26871
+ if (!existsSync(input.keyFile)) return {
26872
+ available: false,
26873
+ source: "file",
26874
+ reason: "missing",
26875
+ keyFile: input.keyFile
26876
+ };
26877
+ let mode;
26878
+ try {
26879
+ mode = statSync(input.keyFile).mode;
26880
+ } catch (error) {
26881
+ return unavailableKeyFileStatus(input.keyFile, error);
26882
+ }
26883
+ if (process.platform !== "win32" && (mode & 63) !== 0) return {
26884
+ available: false,
26885
+ source: "file",
26886
+ reason: "wrong-permissions",
26887
+ keyFile: input.keyFile
26888
+ };
26889
+ let contents;
26890
+ try {
26891
+ contents = readFileSync(input.keyFile, "utf8");
26892
+ } catch (error) {
26893
+ return unavailableKeyFileStatus(input.keyFile, error);
26894
+ }
26895
+ try {
26896
+ parseKeyFile(contents);
26897
+ return {
26898
+ available: true,
26899
+ source: "file",
26900
+ keyFile: input.keyFile
26901
+ };
26902
+ } catch (error) {
26903
+ return {
26904
+ available: false,
26905
+ source: "file",
26906
+ reason: error instanceof CapletsError && error.message.includes("unsupported") ? "unsupported-version" : "invalid",
26907
+ keyFile: input.keyFile
26908
+ };
26909
+ }
26910
+ }
26911
+ function unavailableKeyFileStatus(keyFile, error) {
26912
+ return {
26913
+ available: false,
26914
+ source: "file",
26915
+ reason: (error && typeof error === "object" && "code" in error ? String(error.code) : "") === "ENOENT" ? "missing" : "unreadable",
26916
+ keyFile
26917
+ };
26918
+ }
26919
+ function parseKeyFile(contents) {
26920
+ const trimmed = contents.trim();
26921
+ if (!trimmed.startsWith(KEY_FILE_PREFIX)) throw new CapletsError("CONFIG_INVALID", "Vault key file has an unsupported format version.");
26922
+ return decodeExactKey(trimmed.slice(21), "Vault key file");
26923
+ }
26924
+ function decodeExactKey(encoded, label) {
26925
+ let decoded;
26926
+ try {
26927
+ decoded = Buffer$1.from(encoded, "base64url");
26928
+ } catch {
26929
+ throw new CapletsError("REQUEST_INVALID", `${label} must be a base64url-encoded 32-byte key.`);
26930
+ }
26931
+ if (decoded.length !== KEY_BYTES || decoded.toString("base64url") !== encoded.replace(/=+$/u, "")) throw new CapletsError("REQUEST_INVALID", `${label} must be a base64url-encoded 32-byte key.`);
26932
+ return decoded;
26933
+ }
26934
+ //#endregion
26935
+ //#region src/vault/access.ts
26936
+ function normalizeVaultGrant(input) {
26937
+ const now = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
26938
+ return {
26939
+ storedKey: validateVaultKeyName(input.storedKey),
26940
+ referenceName: validateVaultKeyName(input.referenceName),
26941
+ capletId: validateCapletId(input.capletId),
26942
+ origin: normalizeOrigin(input.origin),
26943
+ createdAt: now,
26944
+ updatedAt: now
26945
+ };
26946
+ }
26947
+ function upsertVaultGrant(grants, input) {
26948
+ const next = normalizeVaultGrant(input);
26949
+ return [...grants.filter((grant) => !sameGrantIdentity(grant, next)), {
26950
+ ...next,
26951
+ createdAt: grants.find((grant) => sameGrantIdentity(grant, next))?.createdAt ?? next.createdAt
26952
+ }].sort(compareGrants);
26953
+ }
26954
+ function filterVaultGrants(grants, filter = {}) {
26955
+ return grants.filter((grant) => {
26956
+ if (filter.storedKey !== void 0 && grant.storedKey !== filter.storedKey) return false;
26957
+ if (filter.referenceName !== void 0 && grant.referenceName !== filter.referenceName) return false;
26958
+ if (filter.capletId !== void 0 && grant.capletId !== filter.capletId) return false;
26959
+ if (filter.origin !== void 0 && !sameOrigin(grant.origin, filter.origin)) return false;
26960
+ return true;
26961
+ });
26962
+ }
26963
+ function sameOrigin(left, right) {
26964
+ return left.kind === right.kind && left.path === right.path;
26965
+ }
26966
+ function sameGrantIdentity(left, right) {
26967
+ return left.referenceName === right.referenceName && left.capletId === right.capletId && sameOrigin(left.origin, right.origin);
26968
+ }
26969
+ function normalizeOrigin(origin) {
26970
+ if (!origin.path) throw new CapletsError("REQUEST_INVALID", "Vault access grants require a config origin path.");
26971
+ return {
26972
+ kind: origin.kind,
26973
+ path: origin.path
26974
+ };
26975
+ }
26976
+ function validateCapletId(capletId) {
26977
+ if (!/^[a-zA-Z0-9_-]{1,64}$/u.test(capletId)) throw new CapletsError("REQUEST_INVALID", "Vault access grants require a valid Caplet ID.");
26978
+ return capletId;
26979
+ }
26980
+ function compareGrants(left, right) {
26981
+ return left.capletId.localeCompare(right.capletId) || left.referenceName.localeCompare(right.referenceName) || left.storedKey.localeCompare(right.storedKey) || left.origin.kind.localeCompare(right.origin.kind) || left.origin.path.localeCompare(right.origin.path);
26982
+ }
26983
+ //#endregion
26984
+ //#region src/vault/types.ts
26985
+ const VAULT_MAX_VALUE_BYTES = 64 * 1024;
26986
+ //#endregion
26987
+ //#region src/vault/index.ts
26988
+ var FileVaultStore = class {
26989
+ root;
26990
+ env;
26991
+ paths;
26992
+ constructor(options = {}) {
26993
+ this.root = options.root ?? join(defaultStateBaseDir(options.env), "caplets", "vault");
26994
+ this.env = options.env ?? process.env;
26995
+ this.paths = {
26996
+ keyFile: join(this.root, "vault-key"),
26997
+ valuesDir: join(this.root, "values"),
26998
+ grantsFile: join(this.root, "access-grants.json")
26999
+ };
27000
+ }
27001
+ valuePath(key) {
27002
+ return join(this.paths.valuesDir, `${encodeURIComponent(validateVaultKeyName(key))}.json`);
27003
+ }
27004
+ set(key, value, options = {}) {
27005
+ const normalizedKey = validateVaultKeyName(key);
27006
+ if (Buffer$1.byteLength(value, "utf8") > 65536) throw new CapletsError("REQUEST_INVALID", `Vault values must be ${VAULT_MAX_VALUE_BYTES} bytes or smaller.`);
27007
+ const path = this.valuePath(normalizedKey);
27008
+ const existing = this.loadValueRecord(normalizedKey);
27009
+ if (existing && !options.force) throw new CapletsError("CONFIG_EXISTS", `Vault key ${normalizedKey} already exists.`);
27010
+ ensurePrivateDir(this.root);
27011
+ ensurePrivateDir(this.paths.valuesDir);
27012
+ const encrypted = encryptVaultValue({
27013
+ plaintext: value,
27014
+ key: ensureVaultKey({
27015
+ keyFile: this.paths.keyFile,
27016
+ env: this.env
27017
+ }),
27018
+ now: options.now ?? /* @__PURE__ */ new Date(),
27019
+ ...existing ? { existing } : {}
27020
+ });
27021
+ writePrivateFileAtomic(path, `${JSON.stringify(encrypted, null, 2)}\n`);
27022
+ return this.statusForRecord(normalizedKey, encrypted);
27023
+ }
27024
+ getStatus(key) {
27025
+ const normalizedKey = validateVaultKeyName(key);
27026
+ const record = this.loadValueRecord(normalizedKey);
27027
+ return record ? this.statusForRecord(normalizedKey, record) : {
27028
+ key: normalizedKey,
27029
+ present: false
27030
+ };
27031
+ }
27032
+ listValues() {
27033
+ if (!existsSync(this.paths.valuesDir)) return [];
27034
+ return readdirSync(this.paths.valuesDir).filter((entry) => entry.endsWith(".json")).map((entry) => decodeURIComponent(basename(entry, ".json"))).map((key) => this.getStatus(key)).filter((status) => status.present).sort((left, right) => left.key.localeCompare(right.key));
27035
+ }
27036
+ resolveValue(key) {
27037
+ const normalizedKey = validateVaultKeyName(key);
27038
+ const record = this.loadValueRecord(normalizedKey);
27039
+ if (!record) throw new CapletsError("CONFIG_INVALID", `Vault key ${normalizedKey} is missing.`);
27040
+ return decryptVaultValue(record, loadVaultKey({
27041
+ keyFile: this.paths.keyFile,
27042
+ env: this.env
27043
+ }));
27044
+ }
27045
+ delete(key) {
27046
+ const normalizedKey = validateVaultKeyName(key);
27047
+ return {
27048
+ key: normalizedKey,
27049
+ deleted: deleteFile(this.valuePath(normalizedKey)),
27050
+ grantsRetained: this.listAccess({ storedKey: normalizedKey }).length
27051
+ };
27052
+ }
27053
+ keySourceStatus() {
27054
+ return vaultKeySourceStatus({
27055
+ keyFile: this.paths.keyFile,
27056
+ env: this.env
27057
+ });
27058
+ }
27059
+ grantAccess(input) {
27060
+ const next = normalizeVaultGrant(input);
27061
+ const grants = upsertVaultGrant(this.loadAccessGrants(), input);
27062
+ this.saveAccessGrants(grants);
27063
+ return grants.find((grant) => grant.storedKey === next.storedKey && grant.referenceName === next.referenceName && grant.capletId === next.capletId && sameOrigin(grant.origin, next.origin));
27064
+ }
27065
+ listAccess(filter = {}) {
27066
+ return filterVaultGrants(this.loadAccessGrants(), filter);
27067
+ }
27068
+ revokeAccess(filter) {
27069
+ const removed = this.listAccess(filter);
27070
+ if (removed.length === 0) return [];
27071
+ const removedKeys = new Set(removed.map(accessGrantIdentity));
27072
+ const remaining = this.loadAccessGrants().filter((grant) => !removedKeys.has(accessGrantIdentity(grant)));
27073
+ this.saveAccessGrants(remaining);
27074
+ return removed;
27075
+ }
27076
+ resolveGrantedValue(input) {
27077
+ const referenceName = validateVaultKeyName(input.referenceName);
27078
+ const grant = this.listAccess({
27079
+ referenceName,
27080
+ capletId: input.capletId,
27081
+ origin: input.origin
27082
+ })[0];
27083
+ if (!grant) return {
27084
+ reason: "ungranted",
27085
+ referenceName,
27086
+ capletId: input.capletId,
27087
+ origin: input.origin
27088
+ };
27089
+ if (!existsSync(this.valuePath(grant.storedKey))) return {
27090
+ reason: "missing",
27091
+ storedKey: grant.storedKey,
27092
+ referenceName,
27093
+ capletId: input.capletId,
27094
+ origin: input.origin
27095
+ };
27096
+ return {
27097
+ storedKey: grant.storedKey,
27098
+ value: this.resolveValue(grant.storedKey)
27099
+ };
27100
+ }
27101
+ loadValueRecord(key) {
27102
+ const path = this.valuePath(key);
27103
+ if (!existsSync(path)) return void 0;
27104
+ let raw;
27105
+ try {
27106
+ raw = readJsonFile(path, {});
27107
+ } catch {
27108
+ throw new CapletsError("CONFIG_INVALID", `Vault value record for ${key} is not valid JSON.`);
27109
+ }
27110
+ return parseEncryptedRecord(raw);
27111
+ }
27112
+ statusForRecord(key, record) {
27113
+ return {
27114
+ key,
27115
+ present: true,
27116
+ valueBytes: record.valueBytes,
27117
+ createdAt: record.createdAt,
27118
+ updatedAt: record.updatedAt
27119
+ };
27120
+ }
27121
+ loadAccessGrants() {
27122
+ let raw;
27123
+ try {
27124
+ raw = readJsonFile(this.paths.grantsFile, []);
27125
+ } catch {
27126
+ throw new CapletsError("CONFIG_INVALID", "Vault access grants file is not valid JSON.");
27127
+ }
27128
+ if (!Array.isArray(raw)) throw new CapletsError("CONFIG_INVALID", "Vault access grants file must contain an array.");
27129
+ return raw.map(parseStoredGrant);
27130
+ }
27131
+ saveAccessGrants(grants) {
27132
+ ensurePrivateDir(this.root);
27133
+ writePrivateFileAtomic(this.paths.grantsFile, `${JSON.stringify(grants, null, 2)}\n`);
27134
+ }
27135
+ };
27136
+ function accessGrantIdentity(grant) {
27137
+ return [
27138
+ grant.storedKey,
27139
+ grant.referenceName,
27140
+ grant.capletId,
27141
+ grant.origin.kind,
27142
+ grant.origin.path
27143
+ ].join("\0");
27144
+ }
27145
+ function parseStoredGrant(value) {
27146
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new CapletsError("CONFIG_INVALID", "Vault access grant must be an object.");
27147
+ const record = value;
27148
+ if (typeof record.storedKey !== "string" || typeof record.referenceName !== "string" || typeof record.capletId !== "string" || typeof record.createdAt !== "string" || typeof record.updatedAt !== "string" || !record.origin || typeof record.origin !== "object" || Array.isArray(record.origin)) throw new CapletsError("CONFIG_INVALID", "Vault access grant is malformed.");
27149
+ const originRecord = record.origin;
27150
+ if (typeof originRecord.kind !== "string" || typeof originRecord.path !== "string" || ![
27151
+ "global-config",
27152
+ "global-file",
27153
+ "project-config",
27154
+ "project-file"
27155
+ ].includes(originRecord.kind)) throw new CapletsError("CONFIG_INVALID", "Vault access grant origin is malformed.");
27156
+ return {
27157
+ ...normalizeVaultGrant({
27158
+ storedKey: record.storedKey,
27159
+ referenceName: record.referenceName,
27160
+ capletId: record.capletId,
27161
+ origin: {
27162
+ kind: originRecord.kind,
27163
+ path: originRecord.path
27164
+ }
27165
+ }),
27166
+ createdAt: record.createdAt,
27167
+ updatedAt: record.updatedAt
27168
+ };
26762
27169
  }
26763
27170
  //#endregion
26764
27171
  //#region src/config.ts
@@ -26768,6 +27175,7 @@ const NON_INTERPOLATED_SERVER_FIELDS = /* @__PURE__ */ new Set([
26768
27175
  "tags",
26769
27176
  "body"
26770
27177
  ]);
27178
+ const VAULT_BARE_REFERENCE = "[A-Za-z0-9_-]+";
26771
27179
  const remoteAuthSchema = discriminatedUnion("type", [
26772
27180
  object$1({ type: literal("none") }).strict(),
26773
27181
  object$1({
@@ -27430,10 +27838,10 @@ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, googleDi
27430
27838
  }
27431
27839
  const configFileSchema = configSchemaFor(publicServerSchema, publicOpenApiEndpointSchema, publicGoogleDiscoveryApiSchema, publicGraphQlEndpointSchema, publicHttpApiSchema, publicCliToolsSchema, publicCapletSetSchema);
27432
27840
  const normalizedConfigFileSchema = configSchemaFor(normalizedServerSchema, normalizedOpenApiEndpointSchema, normalizedGoogleDiscoveryApiSchema, normalizedGraphQlEndpointSchema, normalizedHttpApiSchema, normalizedCliToolsSchema, normalizedCapletSetSchema);
27433
- function loadConfig(path = resolveConfigPath(), projectPath = resolveProjectConfigPath()) {
27434
- return loadConfigWithSources(path, projectPath).config;
27841
+ function loadConfig(path = resolveConfigPath(), projectPath = resolveProjectConfigPath(), options = {}) {
27842
+ return loadConfigWithSources(path, projectPath, options).config;
27435
27843
  }
27436
- function loadConfigWithSources(path = resolveConfigPath(), projectPath = resolveProjectConfigPath()) {
27844
+ function loadConfigWithSources(path = resolveConfigPath(), projectPath = resolveProjectConfigPath(), options = {}) {
27437
27845
  const hasUserConfig = existsSync(path);
27438
27846
  const hasProjectConfig = existsSync(projectPath);
27439
27847
  const userConfig = hasUserConfig ? readPublicConfigInput(path) : void 0;
@@ -27470,9 +27878,9 @@ function loadConfigWithSources(path = resolveConfigPath(), projectPath = resolve
27470
27878
  path: projectCaplets.paths
27471
27879
  }
27472
27880
  } : void 0
27473
- ], `Caplets config not found at ${path} or ${projectPath}`, "Caplets config must define at least one MCP server, OpenAPI endpoint, Google Discovery API, GraphQL endpoint, HTTP API, CLI tools backend, or Caplet set");
27881
+ ], `Caplets config not found at ${path} or ${projectPath}`, "Caplets config must define at least one MCP server, OpenAPI endpoint, Google Discovery API, GraphQL endpoint, HTTP API, CLI tools backend, or Caplet set", options);
27474
27882
  }
27475
- function loadGlobalConfig(path = resolveConfigPath()) {
27883
+ function loadGlobalConfig(path = resolveConfigPath(), options = {}) {
27476
27884
  const userConfig = existsSync(path) ? readPublicConfigInput(path) : void 0;
27477
27885
  const userCaplets = loadCapletFilesWithPaths(resolveCapletsRoot(path));
27478
27886
  return buildConfigWithSources([{
@@ -27487,9 +27895,9 @@ function loadGlobalConfig(path = resolveConfigPath()) {
27487
27895
  kind: "global-file",
27488
27896
  path: userCaplets.paths
27489
27897
  }
27490
- } : void 0], `Caplets user config not found at ${path}`, void 0).config;
27898
+ } : void 0], `Caplets user config not found at ${path}`, void 0, options).config;
27491
27899
  }
27492
- function loadProjectConfig(projectPath = resolveProjectConfigPath()) {
27900
+ function loadProjectConfig(projectPath = resolveProjectConfigPath(), options = {}) {
27493
27901
  const projectConfig = existsSync(projectPath) ? rejectProjectConfigExecutableBackendMaps(readPublicConfigInput(projectPath), projectPath) : void 0;
27494
27902
  const projectCapletsRoot = resolveProjectCapletsRootForConfigPath(projectPath);
27495
27903
  const projectCaplets = projectCapletsRoot ? loadCapletFilesWithPaths(projectCapletsRoot) : void 0;
@@ -27505,13 +27913,16 @@ function loadProjectConfig(projectPath = resolveProjectConfigPath()) {
27505
27913
  kind: "project-file",
27506
27914
  path: projectCaplets.paths
27507
27915
  }
27508
- } : void 0], `Caplets project config not found at ${projectPath}`, void 0).config;
27916
+ } : void 0], `Caplets project config not found at ${projectPath}`, void 0, options).config;
27509
27917
  }
27510
- function buildConfigWithSources(inputs, notFoundMessage, emptyMessage) {
27918
+ function buildConfigWithSources(inputs, notFoundMessage, emptyMessage, options = {}) {
27511
27919
  if (!inputs.some((entry) => entry?.input !== void 0)) throw new CapletsError("CONFIG_NOT_FOUND", notFoundMessage);
27512
27920
  try {
27513
27921
  const { input, sources, shadows } = mergeConfigInputsWithSources(...inputs);
27514
- const config = parseConfig(input);
27922
+ const config = parseConfig(input, {
27923
+ sources,
27924
+ vaultResolver: options.vaultResolver ?? defaultVaultResolver()
27925
+ });
27515
27926
  if (emptyMessage && Object.keys(config.mcpServers).length === 0 && Object.keys(config.openapiEndpoints).length === 0 && Object.keys(config.googleDiscoveryApis).length === 0 && Object.keys(config.graphqlEndpoints).length === 0 && Object.keys(config.httpApis).length === 0 && Object.keys(config.cliTools).length === 0 && Object.keys(config.capletSets).length === 0) throw new CapletsError("CONFIG_INVALID", emptyMessage);
27516
27927
  return {
27517
27928
  config,
@@ -27523,13 +27934,18 @@ function buildConfigWithSources(inputs, notFoundMessage, emptyMessage) {
27523
27934
  throw new CapletsError("CONFIG_INVALID", "Caplets config is not valid JSON", redactSecrets(error));
27524
27935
  }
27525
27936
  }
27526
- function loadLocalOverlayConfigWithSources(path = resolveConfigPath(), projectPath = resolveProjectConfigPath()) {
27937
+ function loadLocalOverlayConfigWithSources(path = resolveConfigPath(), projectPath = resolveProjectConfigPath(), options = {}) {
27938
+ const parseOptions = {
27939
+ vaultResolver: options.vaultResolver ?? defaultVaultResolver(),
27940
+ vaultRecoveryTarget: options.vaultRecoveryTarget
27941
+ };
27527
27942
  const warnings = [];
27528
- const userConfig = existsSync(path) ? readBestEffortConfigInput(path, "global-config", warnings) : void 0;
27529
- const userCaplets = loadBestEffortCapletFiles(resolveCapletsRoot(path), "global-file", warnings);
27530
- const projectConfig = existsSync(projectPath) ? readBestEffortConfigInput(projectPath, "project-config", warnings, (input) => rejectProjectConfigExecutableBackendMaps(input, projectPath)) : void 0;
27943
+ const userConfig = existsSync(path) ? readBestEffortConfigInput(path, "global-config", warnings, void 0, parseOptions) : void 0;
27944
+ const userCaplets = loadBestEffortCapletFiles(resolveCapletsRoot(path), "global-file", warnings, parseOptions);
27945
+ const projectConfig = existsSync(projectPath) ? readBestEffortConfigInput(projectPath, "project-config", warnings, (input) => rejectProjectConfigExecutableBackendMaps(input, projectPath), parseOptions) : void 0;
27531
27946
  const projectCapletsRoot = resolveProjectCapletsRootForConfigPath(projectPath);
27532
- const projectCaplets = projectCapletsRoot ? loadBestEffortCapletFiles(projectCapletsRoot, "project-file", warnings) : void 0;
27947
+ const projectCaplets = projectCapletsRoot ? loadBestEffortCapletFiles(projectCapletsRoot, "project-file", warnings, parseOptions) : void 0;
27948
+ const sourceFound = Boolean(userConfig || userCaplets || projectConfig || projectCaplets);
27533
27949
  const { input, sources, shadows } = mergeConfigInputsWithSources({
27534
27950
  input: userConfig,
27535
27951
  source: {
@@ -27556,17 +27972,40 @@ function loadLocalOverlayConfigWithSources(path = resolveConfigPath(), projectPa
27556
27972
  }
27557
27973
  } : void 0);
27558
27974
  return {
27559
- config: parseConfig(input),
27975
+ config: parseConfig(input, {
27976
+ sources,
27977
+ vaultResolver: parseOptions.vaultResolver
27978
+ }),
27560
27979
  sources,
27561
27980
  shadows,
27562
- warnings
27981
+ warnings,
27982
+ sourceFound
27563
27983
  };
27564
27984
  }
27565
- function readBestEffortConfigInput(path, kind, warnings, transform) {
27985
+ function loadLocalRuntimeConfig(path = resolveConfigPath(), projectPath = resolveProjectConfigPath(), options = {}) {
27986
+ const overlay = loadLocalOverlayConfigWithSources(path, projectPath, {
27987
+ vaultResolver: options.vaultResolver,
27988
+ vaultRecoveryTarget: options.vaultRecoveryTarget
27989
+ });
27990
+ for (const warning of overlay.warnings) options.writeWarning?.(warning);
27991
+ const blockingWarning = overlay.warnings.find((warning) => !warning.recoverable && (warning.kind === "global-config" || warning.kind === "project-config"));
27992
+ if (blockingWarning) throw new CapletsError("CONFIG_INVALID", blockingWarning.message);
27993
+ if (!overlay.sourceFound) throw new CapletsError("CONFIG_NOT_FOUND", `Caplets config not found at ${path} or ${projectPath}`);
27994
+ if (!configHasAnyCaplets(overlay.config) && !overlay.warnings.some((warning) => warning.recoverable)) throw new CapletsError("CONFIG_INVALID", "Caplets config must define at least one MCP server, OpenAPI endpoint, Google Discovery API, GraphQL endpoint, HTTP API, CLI tools backend, or Caplet set");
27995
+ return overlay.config;
27996
+ }
27997
+ function readBestEffortConfigInput(path, kind, warnings, transform, options = {}) {
27566
27998
  try {
27567
27999
  const normalized = normalizeLocalPaths(readBestEffortJsonConfigInput(path), dirname(path));
27568
- const filtered = quarantineMissingEnvCaplets(transform ? transform(normalized) : normalized, kind, path, warnings);
27569
- const parsed = configFileSchema.safeParse(interpolateConfig(filtered));
28000
+ const filtered = quarantineUnresolvedReferenceCaplets(transform ? transform(normalized) : normalized, kind, path, warnings, options);
28001
+ const validationOptions = {
28002
+ ...options,
28003
+ sources: Object.fromEntries(capletIds(filtered).map((id) => [id, {
28004
+ kind,
28005
+ path
28006
+ }]))
28007
+ };
28008
+ const parsed = configFileSchema.safeParse(interpolateConfig(filtered, [], validationOptions));
27570
28009
  if (!parsed.success) throw new CapletsError("CONFIG_INVALID", `Caplets config at ${path} is invalid`, parsed.error.issues);
27571
28010
  return filtered;
27572
28011
  } catch (error) {
@@ -27585,21 +28024,35 @@ function readBestEffortJsonConfigInput(path) {
27585
28024
  throw new CapletsError("CONFIG_INVALID", `Caplets config at ${path} is not valid JSON`, redactSecrets(error));
27586
28025
  }
27587
28026
  }
27588
- function quarantineMissingEnvCaplets(input, kind, sourcePath, warnings) {
28027
+ function quarantineUnresolvedReferenceCaplets(input, kind, sourcePath, warnings, options = {}) {
27589
28028
  let filtered = input;
27590
28029
  for (const backend of CAPLET_BACKEND_KEYS) {
27591
28030
  const caplets = filtered[backend];
27592
28031
  if (!isPlainObject$5(caplets)) continue;
27593
28032
  for (const [id, caplet] of Object.entries(caplets)) {
27594
- const missing = missingEnvReferences(caplet, [backend, id]);
27595
- if (missing.length === 0) continue;
28033
+ const envMissing = missingEnvReferences(caplet, [backend, id]);
28034
+ const capletSourcePath = typeof sourcePath === "function" ? sourcePath(id) : sourcePath;
28035
+ const vaultIssues = unresolvedVaultReferences(caplet, [backend, id], {
28036
+ capletId: id,
28037
+ origin: {
28038
+ kind,
28039
+ path: capletSourcePath
28040
+ }
28041
+ }, options);
28042
+ if (envMissing.length === 0 && vaultIssues.length === 0) continue;
27596
28043
  filtered = removeCapletBackendId(filtered, backend, id);
27597
- warnings.push({
28044
+ for (const missing of groupMissingEnvReferences(envMissing)) warnings.push({
27598
28045
  kind,
27599
- path: typeof sourcePath === "function" ? sourcePath(id) : sourcePath,
28046
+ path: capletSourcePath,
27600
28047
  message: formatMissingEnvWarning(id, missing),
27601
28048
  recoverable: true
27602
28049
  });
28050
+ for (const issue of vaultIssues) warnings.push({
28051
+ kind,
28052
+ path: capletSourcePath,
28053
+ message: formatVaultReferenceWarning(id, issue, options.vaultRecoveryTarget),
28054
+ recoverable: true
28055
+ });
27603
28056
  }
27604
28057
  }
27605
28058
  return filtered;
@@ -27627,7 +28080,90 @@ function formatMissingEnvWarning(id, missing) {
27627
28080
  const paths = [...new Set(missing.map((reference) => reference.path))];
27628
28081
  return `Caplet ${id} references missing ${names.length === 1 ? "environment variable" : "environment variables"} ${names.join(", ")} at ${paths.join(", ")}; skipping Caplet ${id}.`;
27629
28082
  }
27630
- function loadBestEffortCapletFiles(root, kind, warnings) {
28083
+ function groupMissingEnvReferences(missing) {
28084
+ return missing.length === 0 ? [] : [missing];
28085
+ }
28086
+ function configHasAnyCaplets(config) {
28087
+ return Object.keys(config.mcpServers).length > 0 || Object.keys(config.openapiEndpoints).length > 0 || Object.keys(config.googleDiscoveryApis).length > 0 || Object.keys(config.graphqlEndpoints).length > 0 || Object.keys(config.httpApis).length > 0 || Object.keys(config.cliTools).length > 0 || Object.keys(config.capletSets).length > 0;
28088
+ }
28089
+ function unresolvedVaultReferences(value, path, context, options) {
28090
+ if (isPublicMetadataPath(path)) return [];
28091
+ if (typeof value === "string") return unresolvedVaultReferencesInString(value, path.join("."), context, options);
28092
+ if (Array.isArray(value)) return value.flatMap((item, index) => unresolvedVaultReferences(item, [...path, String(index)], context, options));
28093
+ if (isPlainObject$5(value)) return Object.entries(value).flatMap(([key, nested]) => unresolvedVaultReferences(nested, [...path, key], context, options));
28094
+ return [];
28095
+ }
28096
+ function unresolvedVaultReferencesInString(value, path, context, options) {
28097
+ const issues = [];
28098
+ for (const match of value.matchAll(VAULT_REFERENCE_PATTERN)) {
28099
+ const name = match[1] ?? match[2];
28100
+ if (!name) continue;
28101
+ try {
28102
+ validateVaultKeyName(name);
28103
+ } catch {
28104
+ issues.push({
28105
+ name,
28106
+ path,
28107
+ reason: "invalid-key-source"
28108
+ });
28109
+ continue;
28110
+ }
28111
+ const resolution = options.vaultResolver?.({
28112
+ referenceName: name,
28113
+ capletId: context.capletId,
28114
+ origin: context.origin,
28115
+ path
28116
+ });
28117
+ if (!resolution || !("value" in resolution)) {
28118
+ const reason = resolution && "reason" in resolution ? resolution.reason : "unavailable";
28119
+ issues.push({
28120
+ name,
28121
+ path,
28122
+ reason,
28123
+ ...resolution && "storedKey" in resolution && resolution.storedKey ? { storedKey: resolution.storedKey } : {}
28124
+ });
28125
+ }
28126
+ }
28127
+ return issues;
28128
+ }
28129
+ function formatVaultReferenceWarning(id, issue, target) {
28130
+ const key = issue.storedKey ?? issue.name;
28131
+ const targetFlag = target === "remote" ? " --remote" : "";
28132
+ if (issue.reason === "invalid-key-source") return `Caplet ${id} references invalid-key-source Vault key ${key} at ${issue.path}; run \`caplets doctor\` for key-source details, then reload Caplets; skipping Caplet ${id}.`;
28133
+ if (issue.reason === "missing") return `Caplet ${id} references missing Vault key ${key} at ${issue.path}; run \`caplets vault set ${key}${targetFlag}\`, then reload Caplets; skipping Caplet ${id}.`;
28134
+ const grantCommand = `caplets vault access grant ${key} ${id}${targetFlag}${key !== issue.name ? ` --as ${issue.name}` : ""}`;
28135
+ return `Caplet ${id} references ${issue.reason} Vault key ${key} at ${issue.path}; run \`${grantCommand}\` after setting the value, then reload Caplets; skipping Caplet ${id}.`;
28136
+ }
28137
+ function defaultVaultResolver(store = new FileVaultStore()) {
28138
+ return (reference) => {
28139
+ try {
28140
+ return store.resolveGrantedValue(reference);
28141
+ } catch (error) {
28142
+ return {
28143
+ reason: error instanceof CapletsError ? "invalid-key-source" : "unavailable",
28144
+ referenceName: reference.referenceName,
28145
+ capletId: reference.capletId,
28146
+ origin: reference.origin
28147
+ };
28148
+ }
28149
+ };
28150
+ }
28151
+ function vaultStoreForAuthDir(authDir) {
28152
+ return new FileVaultStore(authDir ? { root: join(authDir, "vault") } : {});
28153
+ }
28154
+ function vaultResolverForAuthDir(authDir) {
28155
+ return defaultVaultResolver(vaultStoreForAuthDir(authDir));
28156
+ }
28157
+ const vaultBootstrapResolver = (reference) => ({
28158
+ storedKey: reference.referenceName,
28159
+ value: vaultBootstrapPlaceholderValue(reference.path)
28160
+ });
28161
+ function vaultBootstrapPlaceholderValue(path) {
28162
+ const leaf = path.split(".").at(-1)?.toLowerCase() ?? "";
28163
+ if (leaf.endsWith("url") || leaf.endsWith("uri") || leaf === "issuer") return "https://caplets.local/vault-placeholder";
28164
+ return "caplets-vault-placeholder";
28165
+ }
28166
+ function loadBestEffortCapletFiles(root, kind, warnings, options = {}) {
27631
28167
  const result = loadCapletFilesWithPathsBestEffort(root);
27632
28168
  if (!result) return;
27633
28169
  for (const warning of result.warnings) warnings.push({
@@ -27635,7 +28171,7 @@ function loadBestEffortCapletFiles(root, kind, warnings) {
27635
28171
  path: warning.path ?? root,
27636
28172
  message: warning.message
27637
28173
  });
27638
- const config = quarantineMissingEnvCaplets(result.config, kind, (id) => result.paths[id] ?? root, warnings);
28174
+ const config = quarantineUnresolvedReferenceCaplets(result.config, kind, (id) => result.paths[id] ?? root, warnings, options);
27639
28175
  const retainedIds = new Set(capletIds(config));
27640
28176
  return {
27641
28177
  config,
@@ -27647,14 +28183,44 @@ function errorMessage$3(error) {
27647
28183
  }
27648
28184
  function loadIsolatedConfig(options) {
27649
28185
  if (!options.configPath && !options.capletsRoot) throw new CapletsError("CONFIG_INVALID", "Nested Caplet set must define at least one source: configPath or capletsRoot");
27650
- const configInput = options.configPath ? readPublicConfigInput(options.configPath) : void 0;
27651
- const capletInput = options.capletsRoot ? loadCapletFiles(options.capletsRoot) : void 0;
27652
- if (!configInput && !capletInput) throw new CapletsError("CONFIG_NOT_FOUND", `Nested Caplet set sources not found: ${[options.configPath, options.capletsRoot].filter(Boolean).join(", ")}`);
27653
- const config = parseConfig(mergeConfigInputs(configInput, capletInput, {
27654
- version: 1,
27655
- defaultSearchLimit: options.defaultSearchLimit,
27656
- maxSearchLimit: options.maxSearchLimit
27657
- }));
28186
+ const warnings = [];
28187
+ const parseOptions = {
28188
+ vaultResolver: options.vaultResolver ?? defaultVaultResolver(),
28189
+ vaultRecoveryTarget: options.vaultRecoveryTarget
28190
+ };
28191
+ const configExists = Boolean(options.configPath && existsSync(options.configPath));
28192
+ const configInput = configExists ? readBestEffortConfigInput(options.configPath, "global-config", warnings, void 0, parseOptions) : void 0;
28193
+ const capletInput = options.capletsRoot ? loadBestEffortCapletFiles(options.capletsRoot, "global-file", warnings, parseOptions) : void 0;
28194
+ if (!configExists && !capletInput) throw new CapletsError("CONFIG_NOT_FOUND", `Nested Caplet set sources not found: ${[options.configPath, options.capletsRoot].filter(Boolean).join(", ")}`);
28195
+ const blockingWarning = warnings.find((warning) => !warning.recoverable);
28196
+ if (blockingWarning) throw new CapletsError("CONFIG_INVALID", blockingWarning.message);
28197
+ const { input, sources } = mergeConfigInputsWithSources({
28198
+ input: configInput,
28199
+ source: {
28200
+ kind: "global-config",
28201
+ path: options.configPath ?? ""
28202
+ }
28203
+ }, capletInput ? {
28204
+ input: capletInput.config,
28205
+ source: {
28206
+ kind: "global-file",
28207
+ path: capletInput.paths
28208
+ }
28209
+ } : void 0, {
28210
+ input: {
28211
+ version: 1,
28212
+ defaultSearchLimit: options.defaultSearchLimit,
28213
+ maxSearchLimit: options.maxSearchLimit
28214
+ },
28215
+ source: {
28216
+ kind: "global-config",
28217
+ path: options.configPath ?? ""
28218
+ }
28219
+ });
28220
+ const config = parseConfig(input, {
28221
+ sources,
28222
+ vaultResolver: parseOptions.vaultResolver
28223
+ });
27658
28224
  if (Object.keys(config.mcpServers).length === 0 && Object.keys(config.openapiEndpoints).length === 0 && Object.keys(config.googleDiscoveryApis).length === 0 && Object.keys(config.graphqlEndpoints).length === 0 && Object.keys(config.httpApis).length === 0 && Object.keys(config.cliTools).length === 0 && Object.keys(config.capletSets).length === 0) throw new CapletsError("CONFIG_INVALID", "Nested Caplet set must define at least one Caplet");
27659
28225
  return config;
27660
28226
  }
@@ -27665,7 +28231,14 @@ function resolveProjectCapletsRootForConfigPath(projectPath) {
27665
28231
  function readPublicConfigInput(path) {
27666
28232
  try {
27667
28233
  const normalized = normalizeLocalPaths(JSON.parse(readFileSync(path, "utf8")), dirname(path));
27668
- const parsed = configFileSchema.safeParse(interpolateConfig(normalized));
28234
+ const validationOptions = {
28235
+ sources: Object.fromEntries(capletIds(normalized).map((id) => [id, {
28236
+ kind: "global-config",
28237
+ path
28238
+ }])),
28239
+ vaultResolver: vaultBootstrapResolver
28240
+ };
28241
+ const parsed = configFileSchema.safeParse(interpolateConfig(normalized, [], validationOptions));
27669
28242
  if (!parsed.success) throw new CapletsError("CONFIG_INVALID", `Caplets config at ${path} is invalid`, parsed.error.issues);
27670
28243
  return normalized;
27671
28244
  } catch (error) {
@@ -27729,7 +28302,7 @@ function normalizeCapletSetPaths(endpoint, baseDir) {
27729
28302
  };
27730
28303
  }
27731
28304
  function normalizeLocalPath(value, baseDir) {
27732
- if (typeof value !== "string" || !value || isAbsolute(value) || hasEnvReference(value)) return value;
28305
+ if (typeof value !== "string" || !value || isAbsolute(value) || hasInterpolationReference(value)) return value;
27733
28306
  return join(baseDir, value);
27734
28307
  }
27735
28308
  function rejectProjectConfigExecutableBackendMaps(input, path) {
@@ -27845,8 +28418,8 @@ function sourceForId(source, id) {
27845
28418
  path: typeof source.path === "string" ? source.path : source.path[id] ?? ""
27846
28419
  };
27847
28420
  }
27848
- function parseConfig(input) {
27849
- const parsed = normalizedConfigFileSchema.safeParse(interpolateConfig(input));
28421
+ function parseConfig(input, options = {}) {
28422
+ const parsed = normalizedConfigFileSchema.safeParse(interpolateConfig(input, [], options));
27850
28423
  if (!parsed.success) throw new CapletsError("CONFIG_INVALID", "Caplets config is invalid", parsed.error.issues);
27851
28424
  const servers = {};
27852
28425
  for (const [server, raw] of Object.entries(parsed.data.mcpServers)) {
@@ -27931,11 +28504,11 @@ function validateEndpointAuthHeaders(auth, ctx, path) {
27931
28504
  function stripUndefined(value) {
27932
28505
  return Object.fromEntries(Object.entries(value).filter(([, nested]) => nested !== void 0));
27933
28506
  }
27934
- function interpolateConfig(value, path = []) {
28507
+ function interpolateConfig(value, path = [], options = {}) {
27935
28508
  if (isPublicMetadataPath(path)) return value;
27936
- if (typeof value === "string") return interpolateEnv(value);
27937
- if (Array.isArray(value)) return value.map((item, index) => interpolateConfig(item, [...path, String(index)]));
27938
- if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([nestedKey, nested]) => [nestedKey, interpolateConfig(nested, [...path, nestedKey])]));
28509
+ if (typeof value === "string") return interpolateVault(interpolateEnv(value), path, options);
28510
+ if (Array.isArray(value)) return value.map((item, index) => interpolateConfig(item, [...path, String(index)], options));
28511
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([nestedKey, nested]) => [nestedKey, interpolateConfig(nested, [...path, nestedKey], options)]));
27939
28512
  return value;
27940
28513
  }
27941
28514
  function isPublicMetadataPath(path) {
@@ -27945,8 +28518,28 @@ function isPublicMetadataPath(path) {
27945
28518
  function isPlainObject$5(value) {
27946
28519
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
27947
28520
  }
27948
- function hasEnvReference(value) {
27949
- return /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$env:[A-Za-z_][A-Za-z0-9_]*/.test(value);
28521
+ function hasInterpolationReference(value) {
28522
+ return new RegExp(`\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}|\\$env:[A-Za-z_][A-Za-z0-9_]*|\\$\\{vault:[^}]+\\}|\\$vault:${VAULT_BARE_REFERENCE}`).test(value);
28523
+ }
28524
+ const VAULT_REFERENCE_PATTERN = new RegExp(`\\$\\{vault:([^}]+)\\}|\\$vault:(${VAULT_BARE_REFERENCE})`, "g");
28525
+ function interpolateVault(value, path, options) {
28526
+ if (!options.vaultResolver) return value;
28527
+ const backend = path[0];
28528
+ const capletId = path[1];
28529
+ if (!backend || !capletId || !CAPLET_BACKEND_KEY_SET.has(backend)) return value;
28530
+ const origin = options.sources?.[capletId];
28531
+ if (!origin) return value;
28532
+ return value.replace(VAULT_REFERENCE_PATTERN, (_match, braced, bare) => {
28533
+ const referenceName = validateVaultKeyName(braced ?? bare);
28534
+ const resolution = options.vaultResolver?.({
28535
+ referenceName,
28536
+ capletId,
28537
+ origin,
28538
+ path: path.join(".")
28539
+ });
28540
+ if (!resolution || !("value" in resolution)) throw new CapletsError("CONFIG_INVALID", `Vault key ${referenceName} is unresolved for Caplet ${capletId}`);
28541
+ return resolution.value;
28542
+ });
27950
28543
  }
27951
28544
  function interpolateEnv(value) {
27952
28545
  return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_match, name) => process.env[name] ?? "").replace(/\$env:([A-Za-z_][A-Za-z0-9_]*)/g, (_match, name) => process.env[name] ?? "");
@@ -62987,7 +63580,8 @@ var CapletSetManager = class CapletSetManager {
62987
63580
  ...config.configPath ? { configPath: config.configPath } : {},
62988
63581
  ...config.capletsRoot ? { capletsRoot: config.capletsRoot } : {},
62989
63582
  defaultSearchLimit: config.defaultSearchLimit,
62990
- maxSearchLimit: config.maxSearchLimit
63583
+ maxSearchLimit: config.maxSearchLimit,
63584
+ vaultResolver: vaultResolverForAuthDir(this.options.authDir)
62991
63585
  }));
62992
63586
  const sharedOptions = {
62993
63587
  ...this.options.authDir ? { authDir: this.options.authDir } : {},
@@ -63329,8 +63923,9 @@ var CapletsEngine = class {
63329
63923
  configPath: resolveConfigPath(options.configPath),
63330
63924
  projectConfigPath: options.projectConfigPath ?? resolveProjectConfigPath()
63331
63925
  };
63332
- this.configLoader = options.configLoader ?? loadConfig;
63333
- const config = this.configLoader(this.paths.configPath, this.paths.projectConfigPath);
63926
+ this.writeErr = options.writeErr ?? ((value) => process.stderr.write(value));
63927
+ this.configLoader = options.configLoader ?? runtimeConfigLoader(options.authDir, options.vaultRecoveryTarget);
63928
+ const config = this.loadConfigWithWarnings();
63334
63929
  this.registry = new ServerRegistry(config);
63335
63930
  this.downstream = new DownstreamManager(this.registry, selectAuthOptions(options.authDir));
63336
63931
  this.openapi = new OpenApiManager(this.registry, selectHttpLikeOptions(options));
@@ -63341,7 +63936,6 @@ var CapletsEngine = class {
63341
63936
  this.capletSets = new CapletSetManager(this.registry, selectHttpLikeOptions(options));
63342
63937
  this.watchDebounceMs = options.watchDebounceMs ?? 250;
63343
63938
  this.watchEnabled = options.watch ?? true;
63344
- this.writeErr = options.writeErr ?? ((value) => process.stderr.write(value));
63345
63939
  this.observedOutputShapeStore = options.observedOutputShapeStore ?? new FileObservedOutputShapeStore(options.observedOutputShapeCacheDir ?? DEFAULT_OBSERVED_OUTPUT_SHAPE_CACHE_DIR);
63346
63940
  this.observedOutputShapeScope = options.observedOutputShapeScope ?? "local";
63347
63941
  this.projectFingerprint = options.projectFingerprint ?? safeProjectFingerprint();
@@ -63434,7 +64028,7 @@ var CapletsEngine = class {
63434
64028
  }
63435
64029
  }
63436
64030
  async completeCliWords(words) {
63437
- const { completeCliWords } = await import("./completion-De4t5MtT.js").then((n) => n.r);
64031
+ const { completeCliWords } = await import("./completion-CFOJucl5.js").then((n) => n.r);
63438
64032
  return await completeCliWords(words, {
63439
64033
  config: this.registry.config,
63440
64034
  managers: {
@@ -63508,7 +64102,7 @@ var CapletsEngine = class {
63508
64102
  if (this.closed) return false;
63509
64103
  let nextConfig;
63510
64104
  try {
63511
- nextConfig = this.configLoader(this.paths.configPath, this.paths.projectConfigPath);
64105
+ nextConfig = this.loadConfigWithWarnings();
63512
64106
  } catch (error) {
63513
64107
  this.writeErr(`Caplets config reload failed; keeping last known-good config.\n`);
63514
64108
  this.writeErr(`${JSON.stringify(toSafeError(error, "CONFIG_INVALID"), null, 2)}\n`);
@@ -63542,6 +64136,11 @@ var CapletsEngine = class {
63542
64136
  });
63543
64137
  return invalidated;
63544
64138
  }
64139
+ loadConfigWithWarnings() {
64140
+ return this.configLoader(this.paths.configPath, this.paths.projectConfigPath, { writeWarning: (warning) => {
64141
+ this.writeErr(`Warning: ${warning.kind} at ${warning.path}: ${warning.message}\n`);
64142
+ } });
64143
+ }
63545
64144
  async reloadUntilSettled() {
63546
64145
  let succeeded = true;
63547
64146
  do {
@@ -63631,6 +64230,14 @@ var CapletsEngine = class {
63631
64230
  }, this.watchDebounceMs);
63632
64231
  }
63633
64232
  };
64233
+ function runtimeConfigLoader(authDir, vaultRecoveryTarget) {
64234
+ const vaultResolver = vaultResolverForAuthDir(authDir);
64235
+ return (configPath, projectConfigPath, options) => loadLocalRuntimeConfig(configPath, projectConfigPath, {
64236
+ ...options,
64237
+ vaultResolver,
64238
+ vaultRecoveryTarget
64239
+ });
64240
+ }
63634
64241
  function selectAuthOptions(authDir) {
63635
64242
  return authDir ? { authDir } : {};
63636
64243
  }
@@ -80061,6 +80668,79 @@ var CapletsCloudClient = class {
80061
80668
  });
80062
80669
  if (!response.ok) throw new Error(`Caplets Cloud Project Binding update failed: HTTP ${response.status}`);
80063
80670
  }
80671
+ async setVaultValue(input) {
80672
+ const response = await this.fetchImpl(this.endpoint(`api/workspaces/${encodeURIComponent(input.workspace)}/vault/values/${encodeURIComponent(input.name)}`), {
80673
+ method: "PUT",
80674
+ headers: this.headers({ json: true }),
80675
+ body: JSON.stringify({
80676
+ value: input.value,
80677
+ force: Boolean(input.force),
80678
+ ...input.grant ? { grant: input.grant } : {},
80679
+ ...input.referenceName ? { referenceName: input.referenceName } : {}
80680
+ })
80681
+ });
80682
+ if (!response.ok) throw new Error(`Caplets Cloud Vault set failed: HTTP ${response.status}`);
80683
+ return await response.json();
80684
+ }
80685
+ async getVaultValue(input) {
80686
+ const url = this.endpoint(`api/workspaces/${encodeURIComponent(input.workspace)}/vault/values/${encodeURIComponent(input.name)}`);
80687
+ if (input.reveal) {
80688
+ url.searchParams.set("reveal", "true");
80689
+ url.searchParams.set("revealContext", input.revealContext ?? "human-cli");
80690
+ }
80691
+ const response = await this.fetchImpl(url, {
80692
+ method: "GET",
80693
+ headers: this.headers()
80694
+ });
80695
+ if (!response.ok) throw new Error(`Caplets Cloud Vault get failed: HTTP ${response.status}`);
80696
+ return await response.json();
80697
+ }
80698
+ async listVaultValues(input) {
80699
+ const response = await this.fetchImpl(this.endpoint(`api/workspaces/${encodeURIComponent(input.workspace)}/vault/values`), {
80700
+ method: "GET",
80701
+ headers: this.headers()
80702
+ });
80703
+ if (!response.ok) throw new Error(`Caplets Cloud Vault list failed: HTTP ${response.status}`);
80704
+ return await response.json();
80705
+ }
80706
+ async deleteVaultValue(input) {
80707
+ const response = await this.fetchImpl(this.endpoint(`api/workspaces/${encodeURIComponent(input.workspace)}/vault/values/${encodeURIComponent(input.name)}`), {
80708
+ method: "DELETE",
80709
+ headers: this.headers()
80710
+ });
80711
+ if (!response.ok) throw new Error(`Caplets Cloud Vault delete failed: HTTP ${response.status}`);
80712
+ return await response.json();
80713
+ }
80714
+ async grantVaultAccess(input) {
80715
+ const response = await this.fetchImpl(this.endpoint(`api/workspaces/${encodeURIComponent(input.workspace)}/vault/access/${encodeURIComponent(input.name)}/${encodeURIComponent(input.capletId)}`), {
80716
+ method: "PUT",
80717
+ headers: this.headers({ json: true }),
80718
+ body: JSON.stringify({ referenceName: input.referenceName ?? input.name })
80719
+ });
80720
+ if (!response.ok) throw new Error(`Caplets Cloud Vault access grant failed: HTTP ${response.status}`);
80721
+ return await response.json();
80722
+ }
80723
+ async listVaultAccess(input) {
80724
+ const url = this.endpoint(`api/workspaces/${encodeURIComponent(input.workspace)}/vault/access`);
80725
+ if (input.name) url.searchParams.set("name", input.name);
80726
+ if (input.capletId) url.searchParams.set("capletId", input.capletId);
80727
+ const response = await this.fetchImpl(url, {
80728
+ method: "GET",
80729
+ headers: this.headers()
80730
+ });
80731
+ if (!response.ok) throw new Error(`Caplets Cloud Vault access list failed: HTTP ${response.status}`);
80732
+ return await response.json();
80733
+ }
80734
+ async revokeVaultAccess(input) {
80735
+ const url = this.endpoint(`api/workspaces/${encodeURIComponent(input.workspace)}/vault/access/${encodeURIComponent(input.name)}/${encodeURIComponent(input.capletId)}`);
80736
+ if (input.referenceName) url.searchParams.set("referenceName", input.referenceName);
80737
+ const response = await this.fetchImpl(url, {
80738
+ method: "DELETE",
80739
+ headers: this.headers()
80740
+ });
80741
+ if (!response.ok) throw new Error(`Caplets Cloud Vault access revoke failed: HTTP ${response.status}`);
80742
+ return await response.json();
80743
+ }
80064
80744
  headers(options = {}) {
80065
80745
  const headers = new Headers();
80066
80746
  headers.set("authorization", `Bearer ${this.options.accessToken}`);
@@ -81020,13 +81700,16 @@ function isAuthFailure(error) {
81020
81700
  }
81021
81701
  function isPermanentRemoteCredentialsError(error) {
81022
81702
  const candidate = error;
81023
- if (candidate?.projectBindingCode === "remote_credentials_required" || candidate?.projectBindingCode === "remote_auth_failed") return true;
81703
+ if (isPermanentRemoteCredentialsCode(candidate?.projectBindingCode)) return true;
81024
81704
  if (isPlainObject(candidate?.details)) {
81025
81705
  const code = candidate.details.code;
81026
- if (code === "remote_credentials_required" || code === "remote_auth_failed") return true;
81706
+ if (isPermanentRemoteCredentialsCode(code)) return true;
81027
81707
  }
81028
81708
  return isAuthFailure(error);
81029
81709
  }
81710
+ function isPermanentRemoteCredentialsCode(code) {
81711
+ return code === "remote_credentials_required" || code === "remote_credentials_revoked" || code === "remote_auth_failed";
81712
+ }
81030
81713
  //#endregion
81031
81714
  //#region src/cloud-auth/errors.ts
81032
81715
  const SECRET_PATTERN = /(cap_access_[a-z0-9._~+/=-]+|cap_refresh_[a-z0-9._~+/=-]+|one_time_code_[a-z0-9._~+/=-]+|Bearer\s+)[^\s"]*/giu;
@@ -81189,6 +81872,7 @@ const PROJECT_BINDING_ERROR_CODES = [
81189
81872
  "subscription_past_due",
81190
81873
  "email_verification_required",
81191
81874
  "remote_credentials_required",
81875
+ "remote_credentials_revoked",
81192
81876
  "remote_auth_failed"
81193
81877
  ];
81194
81878
  var ProjectBindingError = class extends CapletsError {
@@ -81222,6 +81906,7 @@ function recoveryCommandFor(code) {
81222
81906
  case "workspace_switch_required": return "caplets remote login <cloud-url> --workspace <workspace>";
81223
81907
  case "sync_size_limit_exceeded": return "Add exclusions to .capletsignore or upgrade the workspace plan.";
81224
81908
  case "remote_credentials_required":
81909
+ case "remote_credentials_revoked":
81225
81910
  case "remote_auth_failed": return "caplets remote login <url>";
81226
81911
  case "endpoint_unavailable":
81227
81912
  case "websocket_upgrade_required": return "caplets doctor";
@@ -81399,6 +82084,7 @@ function remoteProfileStatus(input) {
81399
82084
  kind: input.kind,
81400
82085
  key,
81401
82086
  hostUrl,
82087
+ ...input.hostIdentity ? { hostIdentity: input.hostIdentity } : {},
81402
82088
  ...input.workspaceId ? { workspaceId: input.workspaceId } : {},
81403
82089
  ...input.workspaceSlug ? { workspaceSlug: input.workspaceSlug } : {},
81404
82090
  ...input.clientId ? { clientId: input.clientId } : {},
@@ -81456,7 +82142,9 @@ var FileRemoteProfileStore = class {
81456
82142
  kind: "self-hosted",
81457
82143
  hostUrl: normalizeRemoteProfileHostUrl(input.hostUrl)
81458
82144
  });
81459
- return this.statusByKey(key, false);
82145
+ const status = await this.statusByKey(key, false);
82146
+ assertHostIdentityMatches(status, input.hostIdentity);
82147
+ return status;
81460
82148
  }
81461
82149
  async logoutSelfHostedProfile(input) {
81462
82150
  return await this.withMutationLock(async () => {
@@ -81525,7 +82213,7 @@ var FileRemoteProfileStore = class {
81525
82213
  }
81526
82214
  const selected = this.readSelectedWorkspace(hostUrl);
81527
82215
  if (selected) return this.statusByKey(selected.profileKey, true);
81528
- if (this.listProfilesForHost(hostUrl).length > 0) throw new CapletsError("REQUEST_INVALID", "Cloud Remote Profile requires a selected or explicit workspace.");
82216
+ if (this.listProfilesForHost(hostUrl).length > 0) throw cloudWorkspaceAmbiguityError();
81529
82217
  return this.migrateLegacyCloudProfile(hostUrl);
81530
82218
  }
81531
82219
  async listCloudProfileStatuses(hostUrlInput) {
@@ -81553,7 +82241,7 @@ var FileRemoteProfileStore = class {
81553
82241
  let key;
81554
82242
  if (workspace) key = this.listProfilesForHost(hostUrl).find((profile) => profileMatchesWorkspace(profile, workspace))?.key;
81555
82243
  else if (selected) key = selected.profileKey;
81556
- else if (this.listProfilesForHost(hostUrl).length > 0) throw new CapletsError("REQUEST_INVALID", "Cloud Remote Profile requires a selected or explicit workspace.");
82244
+ else if (this.listProfilesForHost(hostUrl).length > 0) throw cloudWorkspaceAmbiguityError();
81557
82245
  if (!key) return false;
81558
82246
  const profile = this.readProfile(key);
81559
82247
  await this.credentials.delete(key);
@@ -81593,7 +82281,7 @@ var FileRemoteProfileStore = class {
81593
82281
  else {
81594
82282
  const selected = this.readSelectedWorkspace(hostUrl);
81595
82283
  if (selected) status = await this.statusByKey(selected.profileKey, true);
81596
- else if (this.listProfilesForHost(hostUrl).length > 0) throw new CapletsError("REQUEST_INVALID", "Cloud Remote Profile requires a selected or explicit workspace.");
82284
+ else if (this.listProfilesForHost(hostUrl).length > 0) throw cloudWorkspaceAmbiguityError();
81597
82285
  }
81598
82286
  if (!status) return void 0;
81599
82287
  const credential = await this.credentials.load(status.key);
@@ -81646,6 +82334,7 @@ var FileRemoteProfileStore = class {
81646
82334
  kind: profile.kind,
81647
82335
  key: profile.key,
81648
82336
  hostUrl: profile.hostUrl,
82337
+ hostIdentity: profile.hostIdentity,
81649
82338
  workspaceId: profile.workspaceId,
81650
82339
  workspaceSlug: profile.workspaceSlug,
81651
82340
  clientId: profile.clientId,
@@ -81666,11 +82355,13 @@ var FileRemoteProfileStore = class {
81666
82355
  });
81667
82356
  const now = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
81668
82357
  const existing = this.readProfile(key);
82358
+ const hostIdentity = input.hostIdentity ?? existing?.hostIdentity;
81669
82359
  const profile = {
81670
82360
  version: 1,
81671
82361
  kind: "self-hosted",
81672
82362
  key,
81673
82363
  hostUrl,
82364
+ ...hostIdentity ? { hostIdentity } : {},
81674
82365
  clientId: input.clientId,
81675
82366
  ...input.clientLabel ? { clientLabel: input.clientLabel } : {},
81676
82367
  createdAt: existing?.createdAt ?? now,
@@ -81843,6 +82534,14 @@ function legacyCredential(credentials) {
81843
82534
  ...credentials.tokenType ? { tokenType: credentials.tokenType } : {}
81844
82535
  };
81845
82536
  }
82537
+ function assertHostIdentityMatches(status, expectedHostIdentity) {
82538
+ if (!status || !expectedHostIdentity || !status.hostIdentity) return;
82539
+ if (status.hostIdentity === expectedHostIdentity) return;
82540
+ throw new CapletsError("AUTH_FAILED", "Remote Profile belongs to a different host identity.");
82541
+ }
82542
+ function cloudWorkspaceAmbiguityError() {
82543
+ return new CapletsError("REQUEST_INVALID", "Cloud Remote Profile requires a selected or explicit workspace.", { reason: "cloud_workspace_ambiguous" });
82544
+ }
81846
82545
  function parseStoredRemoteProfile(value) {
81847
82546
  if (!isRecord$1(value)) return void 0;
81848
82547
  if (value.version !== 1) return void 0;
@@ -81855,6 +82554,7 @@ function parseStoredRemoteProfile(value) {
81855
82554
  kind: value.kind,
81856
82555
  key: value.key,
81857
82556
  hostUrl: value.hostUrl,
82557
+ ...typeof value.hostIdentity === "string" ? { hostIdentity: value.hostIdentity } : {},
81858
82558
  ...typeof value.workspaceId === "string" ? { workspaceId: value.workspaceId } : {},
81859
82559
  ...typeof value.workspaceSlug === "string" ? { workspaceSlug: value.workspaceSlug } : {},
81860
82560
  ...typeof value.clientId === "string" ? { clientId: value.clientId } : {},
@@ -81941,11 +82641,11 @@ async function resolveRemoteSelection(input = {}, env = process.env) {
81941
82641
  const explicitWorkspace = input.workspace ?? (workspaceFromRemoteUrl ? void 0 : env.CAPLETS_REMOTE_WORKSPACE);
81942
82642
  const profileWorkspace = workspaceFromRemoteUrl ?? explicitWorkspace;
81943
82643
  const normalizedRemoteUrl = normalizeRemoteProfileHostUrl(remoteUrl);
81944
- let status = await store.getCloudProfileStatus({
82644
+ let status = await getCloudProfileStatusForSelection(store, {
81945
82645
  hostUrl: normalizedRemoteUrl,
81946
82646
  workspace: profileWorkspace
81947
82647
  });
81948
- if (!status && profileWorkspace) status = await store.getCloudProfileStatus({ hostUrl: normalizedRemoteUrl });
82648
+ if (!status && profileWorkspace) status = await getCloudProfileStatusForSelection(store, { hostUrl: normalizedRemoteUrl });
81949
82649
  let credential = status ? await store.credentials.load(status.key) : void 0;
81950
82650
  if (!status || !credential?.accessToken) throw projectBindingError("cloud_auth_required");
81951
82651
  let credentials = cloudCredentialsFromRemoteProfile(status, credential);
@@ -82023,7 +82723,7 @@ async function refreshSelfHostedCredentials(remoteUrl, refreshToken, options) {
82023
82723
  }
82024
82724
  async function selfHostedRefreshError(remoteUrl, response) {
82025
82725
  const summary = await parseSelfHostedRefreshError(response);
82026
- if (response.status === 401 || summary?.code === "AUTH_FAILED") return remoteLoginRequired(remoteUrl);
82726
+ if (response.status === 401 || summary?.code === "AUTH_FAILED" || summary?.code === "REMOTE_CREDENTIALS_REVOKED") return selfHostedRefreshLooksRevoked(summary) ? remoteLoginRevoked(remoteUrl) : remoteLoginRequired(remoteUrl);
82027
82727
  if (response.status === 503 || summary?.code === "SERVER_UNAVAILABLE") return new CapletsError("SERVER_UNAVAILABLE", summary?.message ?? "Remote credential refresh is temporarily unavailable.");
82028
82728
  return new CapletsError("AUTH_REFRESH_FAILED", summary?.message ?? `Remote credential refresh failed with HTTP ${response.status}.`);
82029
82729
  }
@@ -82069,6 +82769,30 @@ function remoteLoginRequired(remoteUrl) {
82069
82769
  recoveryCommand: `caplets remote login ${normalizeRemoteProfileHostUrl(remoteUrl)}`
82070
82770
  });
82071
82771
  }
82772
+ function remoteLoginRevoked(remoteUrl) {
82773
+ const normalizedUrl = normalizeRemoteProfileHostUrl(remoteUrl);
82774
+ return new ProjectBindingError({
82775
+ code: "remote_credentials_revoked",
82776
+ message: `Remote credentials for ${normalizedUrl} were revoked or rejected. Run Remote Login again and ask the server operator to approve the pending login.`,
82777
+ recoveryCommand: `caplets remote login ${normalizedUrl}`
82778
+ });
82779
+ }
82780
+ function selfHostedRefreshLooksRevoked(summary) {
82781
+ if (summary?.code === "REMOTE_CREDENTIALS_REVOKED") return true;
82782
+ return /revoked|rejected|stale/iu.test(summary?.message ?? "");
82783
+ }
82784
+ async function getCloudProfileStatusForSelection(store, input) {
82785
+ try {
82786
+ return await store.getCloudProfileStatus(input);
82787
+ } catch (error) {
82788
+ if (isCloudWorkspaceAmbiguity(error)) throw projectBindingError("workspace_switch_required", "Cloud Remote Profile requires a selected or explicit workspace.");
82789
+ throw error;
82790
+ }
82791
+ }
82792
+ function isCloudWorkspaceAmbiguity(error) {
82793
+ const details = error instanceof CapletsError ? error.details : void 0;
82794
+ return error instanceof CapletsError && error.code === "REQUEST_INVALID" && typeof details === "object" && details !== null && !Array.isArray(details) && details.reason === "cloud_workspace_ambiguous";
82795
+ }
82072
82796
  async function parseSelfHostedRefreshCredentials(response) {
82073
82797
  const parsed = await response.json();
82074
82798
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Remote refresh response must be an object.");
@@ -82867,4 +83591,4 @@ function errorMessage(error) {
82867
83591
  return error instanceof Error ? error.message : String(error);
82868
83592
  }
82869
83593
  //#endregion
82870
- export { decodeDirectResourceUri as $, ElicitResultSchema as $t, nativeCapletToolDescription as A, objectFromShape as An, defaultCacheBaseDir as At, QuickJsCodeModeSandbox as B, assertClientRequestTaskCapability as Bt, resolveRemoteMode as C, getLiteralValue as Cn, startOAuthFlow as Ct, parseServerBaseUrl as D, isSchemaOptional as Dn, DEFAULT_AUTH_DIR as Dt, isLoopbackHost as E, getSchemaDescription as En, readTokenBundle as Et, codeModeRunInputSchema as F, resolveConfigPath as Ft, CodeModeLogStore as G, toJsonSchemaCompat as Gt, createCodeModeCapletsApi as H, AjvJsonSchemaValidator as Ht, codeModeRunParamsSchema as I, resolveProjectCapletsRoot as It, generateCodeModeDeclarations as J, CompleteRequestSchema as Jt, redactCodeModeLogText as K, CallToolRequestSchema as Kt, emptyCodeModeRunMeta as L, resolveProjectConfigPath as Lt, nativeCapletsSystemGuidance as M, safeParseAsync as Mn, defaultConfigPath as Mt, nativeCodeModeToolId as N, __exportAll as Nn, defaultStateBaseDir as Nt, resolveCapletsServer as O, isZ4Schema as On, DEFAULT_COMPLETION_CACHE_DIR as Ot, nativeCodeModeToolName as P, resolveCapletsRoot as Pt, resolveExposure as Q, DEFAULT_NEGOTIATED_PROTOCOL_VERSION as Qt, runCodeMode as R, ReadBuffer as Rt, resolveHostedCloudRemote as S, isJSONRPCResultResponse as Sn, startGenericOAuthFlow as St, controlUrlForBase as T, getParseErrorMessage as Tn, isTokenBundleExpired as Tt, listCodeModeCallableCaplets as U, Protocol as Ut, diagnoseCodeModeTypeScript as V, assertToolsCallTaskCapability as Vt, CodeModeJournalStore as W, mergeCapabilities as Wt, minifyCodeModeDeclarationText as X, CreateMessageResultWithToolsSchema as Xt, generateCodeModeRunToolDescription as Y, CreateMessageResultSchema as Yt, CapletsEngine as Z, CreateTaskResultSchema as Zt, resolveNativeCapletsServiceOptions as _, assertCompleteRequestPrompt as _n, markdownCallToolResultContent as _t, CloudAuthStore as a, JSONRPCMessageSchema as an, capabilityDescription as at, normalizeRemoteProfileHostUrl as b, isJSONRPCErrorResponse as bn, runGenericOAuthFlow as bt, redactedCloudAuthStatus as c, ListResourceTemplatesRequestSchema as cn, loadConfigWithSources as ct, projectBindingError as d, ListToolsRequestSchema as dn, loadProjectConfig as dt, EmptyResultSchema as en, directResourceUriMatchesTemplate as et, projectBindingRecovery as f, LoggingLevelSchema as fn, parseConfig as ft, buildProjectSyncManifest as g, SetLevelRequestSchema as gn, hasRenderableStructuredContent as gt, createSdkRemoteCapletsClient as h, SUPPORTED_PROTOCOL_VERSIONS as hn, loadCapletFilesFromMap as ht, createRemoteProfileStore as i, InitializedNotificationSchema as in, ServerRegistry as it, nativeCapletToolName as j, safeParse as jn, defaultConfigBaseDir as jt, nativeCapletPromptGuidance as k, normalizeObjectSchema as kn, DEFAULT_OBSERVED_OUTPUT_SHAPE_CACHE_DIR as kt, PROJECT_BINDING_ERROR_CODES as l, ListResourcesRequestSchema as ln, loadGlobalConfig as lt, RemoteNativeCapletsService as m, ReadResourceRequestSchema as mn, validateCapletFile as mt, resolveRemoteSelection as n, GetPromptRequestSchema as nn, fingerprintProjectRoot as nt, cloudAuthPath as o, LATEST_PROTOCOL_VERSION as on, GoogleDiscoveryManager as ot, CloudAuthClient as p, McpError as pn, discoverCapletFiles as pt, codeModeDeclarationHash as q, CallToolResultSchema as qt, cloudCredentialsFromRemoteProfile as r, InitializeRequestSchema as rn, handleServerTool as rt, migrateCredentials as s, ListPromptsRequestSchema as sn, loadConfig as st, createNativeCapletsService as t, ErrorCode as tn, findProjectRoot as tt, ProjectBindingError as u, ListRootsResultSchema as un, loadLocalOverlayConfigWithSources as ut, hostedCloudWorkspaceFromRemoteUrl as v, assertCompleteRequestResourceTemplate as vn, markdownStructuredContent as vt, appendBasePath as w, getObjectShape as wn, deleteTokenBundle as wt, resolveCapletsRemote as x, isJSONRPCRequest as xn, runOAuthFlow as xt, isCapletsCloudUrl as y, isInitializeRequest as yn, refreshOAuthTokenBundle as yt, CodeModeSessionManager as z, serializeMessage as zt };
83594
+ export { resolveExposure as $, mergeCapabilities as $t, nativeCapletPromptGuidance as A, isJSONRPCRequest as An, runOAuthFlow as At, CodeModeSessionManager as B, safeParse as Bn, defaultConfigBaseDir as Bt, resolveHostedCloudRemote as C, ReadResourceRequestSchema as Cn, validateCapletFile as Ct, isLoopbackHost as D, assertCompleteRequestResourceTemplate as Dn, markdownStructuredContent as Dt, controlUrlForBase as E, assertCompleteRequestPrompt as En, markdownCallToolResultContent as Et, nativeCodeModeToolName as F, getSchemaDescription as Fn, readTokenBundle as Ft, CodeModeJournalStore as G, resolveProjectCapletsRoot as Gt, diagnoseCodeModeTypeScript as H, __exportAll as Hn, defaultStateBaseDir as Ht, codeModeRunInputSchema as I, isSchemaOptional as In, DEFAULT_AUTH_DIR as It, codeModeDeclarationHash as J, serializeMessage as Jt, CodeModeLogStore as K, resolveProjectConfigPath as Kt, codeModeRunParamsSchema as L, isZ4Schema as Ln, DEFAULT_COMPLETION_CACHE_DIR as Lt, nativeCapletToolName as M, getLiteralValue as Mn, startOAuthFlow as Mt, nativeCapletsSystemGuidance as N, getObjectShape as Nn, deleteTokenBundle as Nt, parseServerBaseUrl as O, isInitializeRequest as On, refreshOAuthTokenBundle as Ot, nativeCodeModeToolId as P, getParseErrorMessage as Pn, isTokenBundleExpired as Pt, CapletsEngine as Q, Protocol as Qt, emptyCodeModeRunMeta as R, normalizeObjectSchema as Rn, DEFAULT_OBSERVED_OUTPUT_SHAPE_CACHE_DIR as Rt, resolveCapletsRemote as S, McpError as Sn, discoverCapletFiles as St, appendBasePath as T, SetLevelRequestSchema as Tn, hasRenderableStructuredContent as Tt, createCodeModeCapletsApi as U, resolveCapletsRoot as Ut, QuickJsCodeModeSandbox as V, safeParseAsync as Vn, defaultConfigPath as Vt, listCodeModeCallableCaplets as W, resolveConfigPath as Wt, generateCodeModeRunToolDescription as X, assertToolsCallTaskCapability as Xt, generateCodeModeDeclarations as Y, assertClientRequestTaskCapability as Yt, minifyCodeModeDeclarationText as Z, AjvJsonSchemaValidator as Zt, CapletsCloudClient as _, ListResourceTemplatesRequestSchema as _n, FileVaultStore as _t, CloudAuthStore as a, CreateMessageResultWithToolsSchema as an, ServerRegistry as at, isCapletsCloudUrl as b, ListToolsRequestSchema as bn, decryptVaultValue as bt, redactedCloudAuthStatus as c, ElicitResultSchema as cn, loadConfig as ct, projectBindingError as d, GetPromptRequestSchema as dn, loadLocalOverlayConfigWithSources as dt, toJsonSchemaCompat as en, decodeDirectResourceUri as et, projectBindingRecovery as f, InitializeRequestSchema as fn, loadProjectConfig as ft, buildProjectSyncManifest as g, ListPromptsRequestSchema as gn, vaultStoreForAuthDir as gt, createSdkRemoteCapletsClient as h, LATEST_PROTOCOL_VERSION as hn, vaultResolverForAuthDir as ht, createRemoteProfileStore as i, CreateMessageResultSchema as in, handleServerTool as it, nativeCapletToolDescription as j, isJSONRPCResultResponse as jn, startGenericOAuthFlow as jt, resolveCapletsServer as k, isJSONRPCErrorResponse as kn, runGenericOAuthFlow as kt, PROJECT_BINDING_ERROR_CODES as l, EmptyResultSchema as ln, loadConfigWithSources as lt, RemoteNativeCapletsService as m, JSONRPCMessageSchema as mn, vaultBootstrapResolver as mt, resolveRemoteSelection as n, CallToolResultSchema as nn, findProjectRoot as nt, cloudAuthPath as o, CreateTaskResultSchema as on, capabilityDescription as ot, CloudAuthClient as p, InitializedNotificationSchema as pn, parseConfig as pt, redactCodeModeLogText as q, ReadBuffer as qt, cloudCredentialsFromRemoteProfile as r, CompleteRequestSchema as rn, fingerprintProjectRoot as rt, migrateCredentials as s, DEFAULT_NEGOTIATED_PROTOCOL_VERSION as sn, GoogleDiscoveryManager as st, createNativeCapletsService as t, CallToolRequestSchema as tn, directResourceUriMatchesTemplate as tt, ProjectBindingError as u, ErrorCode as un, loadGlobalConfig as ut, resolveNativeCapletsServiceOptions as v, ListResourcesRequestSchema as vn, VAULT_MAX_VALUE_BYTES as vt, resolveRemoteMode as w, SUPPORTED_PROTOCOL_VERSIONS as wn, loadCapletFilesFromMap as wt, normalizeRemoteProfileHostUrl as x, LoggingLevelSchema as xn, encryptVaultValue as xt, hostedCloudWorkspaceFromRemoteUrl as y, ListRootsResultSchema as yn, validateVaultKeyName as yt, runCodeMode as z, objectFromShape as zn, defaultCacheBaseDir as zt };