@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.
- package/dist/caplet-source.js +12 -12
- package/dist/cli/auth.d.ts +1 -0
- package/dist/cli/commands.d.ts +6 -1
- package/dist/cli/doctor.d.ts +1 -0
- package/dist/cli/vault.d.ts +7 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cloud/client.d.ts +59 -0
- package/dist/{completion-De4t5MtT.js → completion-CFOJucl5.js} +24 -8
- package/dist/config-runtime.js +1 -1
- package/dist/config.d.ts +39 -6
- package/dist/daemon/host-path.d.ts +8 -0
- package/dist/engine.d.ts +6 -2
- package/dist/errors.d.ts +1 -1
- package/dist/index.js +1239 -174
- package/dist/native.js +1 -1
- package/dist/project-binding/errors.d.ts +1 -1
- package/dist/remote/profile-store.d.ts +2 -0
- package/dist/remote/profiles.d.ts +2 -0
- package/dist/remote/server-credential-store.d.ts +100 -1
- package/dist/remote/server-credentials.d.ts +18 -0
- package/dist/remote-control/types.d.ts +1 -1
- package/dist/serve/http.d.ts +5 -0
- package/dist/{service-Ut6dN9M8.js → service-aBIn4nrw.js} +809 -85
- package/dist/{validation-C4tYXw6G.js → validation-GD2x5HW1.js} +1 -0
- package/dist/vault/access.d.ts +5 -0
- package/dist/vault/crypto.d.ts +19 -0
- package/dist/vault/index.d.ts +40 -0
- package/dist/vault/keys.d.ts +15 -0
- package/dist/vault/store.d.ts +4 -0
- package/dist/vault/types.d.ts +68 -0
- package/package.json +1 -1
|
@@ -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-
|
|
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 && !
|
|
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 && !
|
|
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 && !
|
|
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 && !
|
|
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 && !
|
|
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 && !
|
|
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 && !
|
|
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 && !
|
|
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 && !
|
|
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) ||
|
|
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
|
|
26635
|
-
return /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$env:[A-Za-z_][A-Za-z0-9_]
|
|
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) ||
|
|
26755
|
+
if (!value || isAbsolute(value) || hasInterpolationReference$1(value)) return value;
|
|
26758
26756
|
return join(baseDir, value);
|
|
26759
26757
|
}
|
|
26760
|
-
function
|
|
26761
|
-
return /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$env:[A-Za-z_][A-Za-z0-9_]
|
|
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
|
|
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 =
|
|
27569
|
-
const
|
|
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
|
|
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
|
|
27595
|
-
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
|
27651
|
-
const
|
|
27652
|
-
|
|
27653
|
-
|
|
27654
|
-
|
|
27655
|
-
|
|
27656
|
-
|
|
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
|
|
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) ||
|
|
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
|
|
27949
|
-
return
|
|
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.
|
|
63333
|
-
|
|
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-
|
|
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.
|
|
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
|
|
81703
|
+
if (isPermanentRemoteCredentialsCode(candidate?.projectBindingCode)) return true;
|
|
81024
81704
|
if (isPlainObject(candidate?.details)) {
|
|
81025
81705
|
const code = candidate.details.code;
|
|
81026
|
-
if (code
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
82644
|
+
let status = await getCloudProfileStatusForSelection(store, {
|
|
81945
82645
|
hostUrl: normalizedRemoteUrl,
|
|
81946
82646
|
workspace: profileWorkspace
|
|
81947
82647
|
});
|
|
81948
|
-
if (!status && profileWorkspace) status = await store
|
|
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 {
|
|
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 };
|