@caplets/core 0.24.0 → 0.25.0
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/attach/options.d.ts +1 -0
- package/dist/attach/server.d.ts +3 -0
- package/dist/cli/commands.d.ts +10 -2
- package/dist/cli/doctor.d.ts +4 -1
- package/dist/cli.d.ts +3 -2
- package/dist/{completion-CzHdM9B_.js → completion-DrPr2vYw.js} +27 -5
- package/dist/config.d.ts +1 -0
- package/dist/daemon/config.d.ts +8 -0
- package/dist/daemon/env.d.ts +2 -0
- package/dist/daemon/index.d.ts +18 -0
- package/dist/daemon/logs.d.ts +15 -0
- package/dist/daemon/manager.d.ts +2 -0
- package/dist/daemon/paths.d.ts +2 -0
- package/dist/daemon/platform-darwin.d.ts +3 -0
- package/dist/daemon/platform-linux.d.ts +3 -0
- package/dist/daemon/platform-windows.d.ts +3 -0
- package/dist/daemon/process.d.ts +11 -0
- package/dist/daemon/shell.d.ts +20 -0
- package/dist/daemon/types.d.ts +178 -0
- package/dist/daemon/validation.d.ts +13 -0
- package/dist/daemon/xml.d.ts +1 -0
- package/dist/index.js +5857 -3604
- package/dist/native/options.d.ts +5 -2
- package/dist/native/remote.d.ts +6 -1
- package/dist/native.js +1 -1
- package/dist/project-binding/attach.d.ts +1 -3
- package/dist/project-binding/session.d.ts +1 -0
- package/dist/remote/credential-store.d.ts +12 -0
- package/dist/remote/options.d.ts +2 -7
- package/dist/remote/pairing.d.ts +13 -0
- package/dist/remote/profile-store.d.ts +94 -0
- package/dist/remote/profiles.d.ts +47 -0
- package/dist/remote/selection.d.ts +4 -4
- package/dist/remote/server-credential-store.d.ts +84 -0
- package/dist/remote/server-credentials.d.ts +21 -0
- package/dist/remote-control/client.d.ts +4 -1
- package/dist/serve/http.d.ts +5 -0
- package/dist/serve/index.d.ts +1 -3
- package/dist/serve/options.d.ts +7 -12
- package/dist/server/options.d.ts +2 -9
- package/dist/{service-BCT_Ls_3.js → service-DjwB8aiW.js} +1165 -262
- package/package.json +1 -1
- package/dist/serve/daemon/config.d.ts +0 -8
- package/dist/serve/daemon/index.d.ts +0 -16
- package/dist/serve/daemon/paths.d.ts +0 -3
- package/dist/serve/daemon/platform-darwin.d.ts +0 -2
- package/dist/serve/daemon/platform-linux.d.ts +0 -2
- package/dist/serve/daemon/platform-windows.d.ts +0 -2
- package/dist/serve/daemon/platform.d.ts +0 -9
- package/dist/serve/daemon/process.d.ts +0 -5
- package/dist/serve/daemon/types.d.ts +0 -86
|
@@ -14,7 +14,6 @@ import { homedir } from "node:os";
|
|
|
14
14
|
import { readFile } from "node:fs/promises";
|
|
15
15
|
import ts from "typescript";
|
|
16
16
|
import { getQuickJS, shouldInterruptAfterDeadline } from "quickjs-emscripten";
|
|
17
|
-
import { Buffer as Buffer$1 } from "node:buffer";
|
|
18
17
|
//#region \0rolldown/runtime.js
|
|
19
18
|
var __create = Object.create;
|
|
20
19
|
var __defProp = Object.defineProperty;
|
|
@@ -18627,8 +18626,8 @@ function compactToolSafetyHints(tool) {
|
|
|
18627
18626
|
};
|
|
18628
18627
|
}
|
|
18629
18628
|
function compactToolSchemaHints(tool) {
|
|
18630
|
-
const schema = isRecord$
|
|
18631
|
-
const properties = isRecord$
|
|
18629
|
+
const schema = isRecord$7(tool.inputSchema) ? tool.inputSchema : void 0;
|
|
18630
|
+
const properties = isRecord$7(schema?.properties) ? schema.properties : {};
|
|
18632
18631
|
const acceptedArgs = Object.keys(properties).sort();
|
|
18633
18632
|
const requiredArgs = Array.isArray(schema?.required) ? schema.required.filter((value) => typeof value === "string").sort() : [];
|
|
18634
18633
|
const argsTemplate = compactArgsTemplate(properties, requiredArgs, acceptedArgs);
|
|
@@ -18652,7 +18651,7 @@ function compactArgsTemplate(properties, requiredArgs, acceptedArgs) {
|
|
|
18652
18651
|
if (templateArgs.length === 0 || templateArgs.length > 4) return void 0;
|
|
18653
18652
|
if (requiredArgs.length === 0 && acceptedArgs.length > 3) return void 0;
|
|
18654
18653
|
const entries = templateArgs.flatMap((name) => {
|
|
18655
|
-
const value = placeholderForSchema(isRecord$
|
|
18654
|
+
const value = placeholderForSchema(isRecord$7(properties[name]) ? properties[name] : void 0);
|
|
18656
18655
|
return value === void 0 ? [] : [[name, value]];
|
|
18657
18656
|
});
|
|
18658
18657
|
return entries.length === templateArgs.length ? Object.fromEntries(entries) : void 0;
|
|
@@ -18672,13 +18671,13 @@ function placeholderForSchema(schema) {
|
|
|
18672
18671
|
}
|
|
18673
18672
|
}
|
|
18674
18673
|
function compactToolSelectionHints(tool) {
|
|
18675
|
-
if (!isRecord$
|
|
18674
|
+
if (!isRecord$7(tool)) return {};
|
|
18676
18675
|
return {
|
|
18677
18676
|
...typeof tool.useWhen === "string" && tool.useWhen.trim() ? { useWhen: tool.useWhen.trim() } : {},
|
|
18678
18677
|
...typeof tool.avoidWhen === "string" && tool.avoidWhen.trim() ? { avoidWhen: tool.avoidWhen.trim() } : {}
|
|
18679
18678
|
};
|
|
18680
18679
|
}
|
|
18681
|
-
function isRecord$
|
|
18680
|
+
function isRecord$7(value) {
|
|
18682
18681
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18683
18682
|
}
|
|
18684
18683
|
function sameServerConfig(left, right) {
|
|
@@ -18753,7 +18752,7 @@ function markdownCallToolResultContent(result, context = {}) {
|
|
|
18753
18752
|
return textContent(renderStructuredMarkdown(result, context));
|
|
18754
18753
|
}
|
|
18755
18754
|
function hasRenderableStructuredContent(value) {
|
|
18756
|
-
if (!isRecord$
|
|
18755
|
+
if (!isRecord$6(value)) return false;
|
|
18757
18756
|
return Object.keys(value).some((key) => key !== "caplets" && key !== "elapsedMs");
|
|
18758
18757
|
}
|
|
18759
18758
|
function renderStructuredMarkdown(value, context) {
|
|
@@ -18887,13 +18886,13 @@ function renderErrorMarkdown(value, title) {
|
|
|
18887
18886
|
].join("\n");
|
|
18888
18887
|
}
|
|
18889
18888
|
function isDiscoveryWrapper(value) {
|
|
18890
|
-
return isRecord$
|
|
18889
|
+
return isRecord$6(value) && "result" in value;
|
|
18891
18890
|
}
|
|
18892
18891
|
function isErrorStructuredContent(value) {
|
|
18893
|
-
return isRecord$
|
|
18892
|
+
return isRecord$6(value) && "error" in value;
|
|
18894
18893
|
}
|
|
18895
18894
|
function isHttpLikeResult(value) {
|
|
18896
|
-
return isRecord$
|
|
18895
|
+
return isRecord$6(value) && ("status" in value || "statusText" in value || "body" in value);
|
|
18897
18896
|
}
|
|
18898
18897
|
function isGraphQlHttpResult(value) {
|
|
18899
18898
|
if (!isHttpLikeResult(value)) return false;
|
|
@@ -18901,7 +18900,7 @@ function isGraphQlHttpResult(value) {
|
|
|
18901
18900
|
return Boolean(body && ("data" in body || "errors" in body));
|
|
18902
18901
|
}
|
|
18903
18902
|
function isCliResult(value) {
|
|
18904
|
-
return isRecord$
|
|
18903
|
+
return isRecord$6(value) && ("exitCode" in value || "stdout" in value || "stderr" in value);
|
|
18905
18904
|
}
|
|
18906
18905
|
function renderBodyValue(value) {
|
|
18907
18906
|
if (value === void 0) return "_No response body._";
|
|
@@ -18969,8 +18968,8 @@ function compactListHints(record) {
|
|
|
18969
18968
|
const acceptedArgs = stringArrayValue(record.acceptedArgs);
|
|
18970
18969
|
if (requiredArgs.length > 0) hints.push(`required args: ${requiredArgs.join(", ")}`);
|
|
18971
18970
|
else if (acceptedArgs.length > 0) hints.push(`args: ${acceptedArgs.join(", ")}`);
|
|
18972
|
-
if (isRecord$
|
|
18973
|
-
if (isRecord$
|
|
18971
|
+
if (isRecord$6(record.argsTemplate)) hints.push(`args template: ${compactJsonText(record.argsTemplate, 160)}`);
|
|
18972
|
+
if (isRecord$6(record.callTemplate)) hints.push(`call: ${compactJsonText(record.callTemplate, 220)}`);
|
|
18974
18973
|
if (record.supportsFields === true) hints.push("supports fields");
|
|
18975
18974
|
if (record.readOnlyHint === true) hints.push("read-only");
|
|
18976
18975
|
if (record.destructiveHint === true) hints.push("destructive");
|
|
@@ -19040,9 +19039,9 @@ function humanizeKey(key) {
|
|
|
19040
19039
|
return key.replace(/([A-Z])/gu, " $1").replace(/^./u, (char) => char.toUpperCase());
|
|
19041
19040
|
}
|
|
19042
19041
|
function asRecord$3(value) {
|
|
19043
|
-
return isRecord$
|
|
19042
|
+
return isRecord$6(value) ? value : void 0;
|
|
19044
19043
|
}
|
|
19045
|
-
function isRecord$
|
|
19044
|
+
function isRecord$6(value) {
|
|
19046
19045
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
19047
19046
|
}
|
|
19048
19047
|
//#endregion
|
|
@@ -27102,6 +27101,16 @@ const publicCapletSetSchema = object$1({
|
|
|
27102
27101
|
});
|
|
27103
27102
|
});
|
|
27104
27103
|
const normalizedCapletSetSchema = publicCapletSetSchema.extend({ body: string().optional() });
|
|
27104
|
+
const CAPLET_BACKEND_KEYS = [
|
|
27105
|
+
"mcpServers",
|
|
27106
|
+
"openapiEndpoints",
|
|
27107
|
+
"googleDiscoveryApis",
|
|
27108
|
+
"graphqlEndpoints",
|
|
27109
|
+
"httpApis",
|
|
27110
|
+
"cliTools",
|
|
27111
|
+
"capletSets"
|
|
27112
|
+
];
|
|
27113
|
+
const CAPLET_BACKEND_KEY_SET = new Set(CAPLET_BACKEND_KEYS);
|
|
27105
27114
|
function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, googleDiscoveryApiValueSchema, graphQlEndpointValueSchema, httpApiValueSchema, cliToolsValueSchema, capletSetValueSchema) {
|
|
27106
27115
|
return object$1({
|
|
27107
27116
|
$schema: string().optional().describe("Optional JSON Schema for editor validation."),
|
|
@@ -27555,8 +27564,11 @@ function loadLocalOverlayConfigWithSources(path = resolveConfigPath(), projectPa
|
|
|
27555
27564
|
}
|
|
27556
27565
|
function readBestEffortConfigInput(path, kind, warnings, transform) {
|
|
27557
27566
|
try {
|
|
27558
|
-
const
|
|
27559
|
-
|
|
27567
|
+
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));
|
|
27570
|
+
if (!parsed.success) throw new CapletsError("CONFIG_INVALID", `Caplets config at ${path} is invalid`, parsed.error.issues);
|
|
27571
|
+
return filtered;
|
|
27560
27572
|
} catch (error) {
|
|
27561
27573
|
warnings.push({
|
|
27562
27574
|
kind,
|
|
@@ -27566,6 +27578,55 @@ function readBestEffortConfigInput(path, kind, warnings, transform) {
|
|
|
27566
27578
|
return;
|
|
27567
27579
|
}
|
|
27568
27580
|
}
|
|
27581
|
+
function readBestEffortJsonConfigInput(path) {
|
|
27582
|
+
try {
|
|
27583
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
27584
|
+
} catch (error) {
|
|
27585
|
+
throw new CapletsError("CONFIG_INVALID", `Caplets config at ${path} is not valid JSON`, redactSecrets(error));
|
|
27586
|
+
}
|
|
27587
|
+
}
|
|
27588
|
+
function quarantineMissingEnvCaplets(input, kind, sourcePath, warnings) {
|
|
27589
|
+
let filtered = input;
|
|
27590
|
+
for (const backend of CAPLET_BACKEND_KEYS) {
|
|
27591
|
+
const caplets = filtered[backend];
|
|
27592
|
+
if (!isPlainObject$5(caplets)) continue;
|
|
27593
|
+
for (const [id, caplet] of Object.entries(caplets)) {
|
|
27594
|
+
const missing = missingEnvReferences(caplet, [backend, id]);
|
|
27595
|
+
if (missing.length === 0) continue;
|
|
27596
|
+
filtered = removeCapletBackendId(filtered, backend, id);
|
|
27597
|
+
warnings.push({
|
|
27598
|
+
kind,
|
|
27599
|
+
path: typeof sourcePath === "function" ? sourcePath(id) : sourcePath,
|
|
27600
|
+
message: formatMissingEnvWarning(id, missing),
|
|
27601
|
+
recoverable: true
|
|
27602
|
+
});
|
|
27603
|
+
}
|
|
27604
|
+
}
|
|
27605
|
+
return filtered;
|
|
27606
|
+
}
|
|
27607
|
+
function missingEnvReferences(value, path) {
|
|
27608
|
+
if (isPublicMetadataPath(path)) return [];
|
|
27609
|
+
if (typeof value === "string") return missingEnvReferencesInString(value, path.join("."));
|
|
27610
|
+
if (Array.isArray(value)) return value.flatMap((item, index) => missingEnvReferences(item, [...path, String(index)]));
|
|
27611
|
+
if (isPlainObject$5(value)) return Object.entries(value).flatMap(([key, nested]) => missingEnvReferences(nested, [...path, key]));
|
|
27612
|
+
return [];
|
|
27613
|
+
}
|
|
27614
|
+
function missingEnvReferencesInString(value, path) {
|
|
27615
|
+
const missing = [];
|
|
27616
|
+
for (const match of value.matchAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$env:([A-Za-z_][A-Za-z0-9_]*)/g)) {
|
|
27617
|
+
const name = match[1] ?? match[2];
|
|
27618
|
+
if (name && process.env[name] === void 0) missing.push({
|
|
27619
|
+
name,
|
|
27620
|
+
path
|
|
27621
|
+
});
|
|
27622
|
+
}
|
|
27623
|
+
return missing;
|
|
27624
|
+
}
|
|
27625
|
+
function formatMissingEnvWarning(id, missing) {
|
|
27626
|
+
const names = [...new Set(missing.map((reference) => reference.name))];
|
|
27627
|
+
const paths = [...new Set(missing.map((reference) => reference.path))];
|
|
27628
|
+
return `Caplet ${id} references missing ${names.length === 1 ? "environment variable" : "environment variables"} ${names.join(", ")} at ${paths.join(", ")}; skipping Caplet ${id}.`;
|
|
27629
|
+
}
|
|
27569
27630
|
function loadBestEffortCapletFiles(root, kind, warnings) {
|
|
27570
27631
|
const result = loadCapletFilesWithPathsBestEffort(root);
|
|
27571
27632
|
if (!result) return;
|
|
@@ -27574,9 +27635,11 @@ function loadBestEffortCapletFiles(root, kind, warnings) {
|
|
|
27574
27635
|
path: warning.path ?? root,
|
|
27575
27636
|
message: warning.message
|
|
27576
27637
|
});
|
|
27638
|
+
const config = quarantineMissingEnvCaplets(result.config, kind, (id) => result.paths[id] ?? root, warnings);
|
|
27639
|
+
const retainedIds = new Set(capletIds(config));
|
|
27577
27640
|
return {
|
|
27578
|
-
config
|
|
27579
|
-
paths: result.paths
|
|
27641
|
+
config,
|
|
27642
|
+
paths: Object.fromEntries(Object.entries(result.paths).filter(([id]) => retainedIds.has(id)))
|
|
27580
27643
|
};
|
|
27581
27644
|
}
|
|
27582
27645
|
function errorMessage$3(error) {
|
|
@@ -27737,6 +27800,15 @@ function mergeConfigInputsWithSources(...inputs) {
|
|
|
27737
27800
|
shadows
|
|
27738
27801
|
};
|
|
27739
27802
|
}
|
|
27803
|
+
function removeCapletBackendId(input, backend, id) {
|
|
27804
|
+
const caplets = input[backend];
|
|
27805
|
+
if (!isPlainObject$5(caplets)) return input;
|
|
27806
|
+
const { [id]: _removed, ...remaining } = caplets;
|
|
27807
|
+
return {
|
|
27808
|
+
...input,
|
|
27809
|
+
[backend]: remaining
|
|
27810
|
+
};
|
|
27811
|
+
}
|
|
27740
27812
|
function removeCapletId(input, id) {
|
|
27741
27813
|
const { [id]: _mcpServer, ...mcpServers } = input.mcpServers ?? {};
|
|
27742
27814
|
const { [id]: _openapiEndpoint, ...openapiEndpoints } = input.openapiEndpoints ?? {};
|
|
@@ -27867,7 +27939,7 @@ function interpolateConfig(value, path = []) {
|
|
|
27867
27939
|
return value;
|
|
27868
27940
|
}
|
|
27869
27941
|
function isPublicMetadataPath(path) {
|
|
27870
|
-
if (path.length < 3 || path[0]
|
|
27942
|
+
if (path.length < 3 || !CAPLET_BACKEND_KEY_SET.has(path[0] ?? "")) return false;
|
|
27871
27943
|
return NON_INTERPOLATED_SERVER_FIELDS.has(path[2] ?? "");
|
|
27872
27944
|
}
|
|
27873
27945
|
function isPlainObject$5(value) {
|
|
@@ -27944,17 +28016,17 @@ function googleDiscoveryScopesForOperations(operations) {
|
|
|
27944
28016
|
return [...new Set(operations.flatMap((operation) => operation.scopes))].sort();
|
|
27945
28017
|
}
|
|
27946
28018
|
function validateGoogleDiscoveryDocument(value) {
|
|
27947
|
-
if (!isRecord$
|
|
28019
|
+
if (!isRecord$5(value)) throw new Error("Invalid Google Discovery document: expected an object");
|
|
27948
28020
|
if (value.kind !== void 0 && value.kind !== "discovery#restDescription") throw new Error("Invalid Google Discovery document: expected kind discovery#restDescription");
|
|
27949
|
-
if (value.resources !== void 0 && !isRecord$
|
|
27950
|
-
if (value.methods !== void 0 && !isRecord$
|
|
27951
|
-
if (!isRecord$
|
|
27952
|
-
if (value.schemas !== void 0 && !isRecord$
|
|
27953
|
-
if (value.parameters !== void 0 && !isRecord$
|
|
28021
|
+
if (value.resources !== void 0 && !isRecord$5(value.resources)) throw new Error("Invalid Google Discovery document: expected resources object");
|
|
28022
|
+
if (value.methods !== void 0 && !isRecord$5(value.methods)) throw new Error("Invalid Google Discovery document: expected methods object");
|
|
28023
|
+
if (!isRecord$5(value.resources) && !isRecord$5(value.methods)) throw new Error("Invalid Google Discovery document: expected resources or methods object");
|
|
28024
|
+
if (value.schemas !== void 0 && !isRecord$5(value.schemas)) throw new Error("Invalid Google Discovery document: expected schemas object");
|
|
28025
|
+
if (value.parameters !== void 0 && !isRecord$5(value.parameters)) throw new Error("Invalid Google Discovery document: expected parameters object");
|
|
27954
28026
|
return value;
|
|
27955
28027
|
}
|
|
27956
28028
|
function collectDocumentMethods(document) {
|
|
27957
|
-
return [...Object.entries(document.methods ?? {}).filter((entry) => isRecord$
|
|
28029
|
+
return [...Object.entries(document.methods ?? {}).filter((entry) => isRecord$5(entry[1])).map(([methodKey, method]) => ({
|
|
27958
28030
|
resourcePath: [],
|
|
27959
28031
|
methodKey,
|
|
27960
28032
|
method
|
|
@@ -27963,9 +28035,9 @@ function collectDocumentMethods(document) {
|
|
|
27963
28035
|
function collectMethods(resources, resourcePath = []) {
|
|
27964
28036
|
const entries = [];
|
|
27965
28037
|
for (const [resourceName, resource] of Object.entries(resources)) {
|
|
27966
|
-
if (!isRecord$
|
|
28038
|
+
if (!isRecord$5(resource)) continue;
|
|
27967
28039
|
const nextPath = [...resourcePath, resourceName];
|
|
27968
|
-
for (const [methodKey, method] of Object.entries(resource.methods ?? {})) if (isRecord$
|
|
28040
|
+
for (const [methodKey, method] of Object.entries(resource.methods ?? {})) if (isRecord$5(method)) entries.push({
|
|
27969
28041
|
resourcePath: nextPath,
|
|
27970
28042
|
methodKey,
|
|
27971
28043
|
method
|
|
@@ -28138,7 +28210,7 @@ function globMatches(pattern, name) {
|
|
|
28138
28210
|
function isJsonSchemaObject(value) {
|
|
28139
28211
|
return value.type === "object" || "properties" in value || "additionalProperties" in value;
|
|
28140
28212
|
}
|
|
28141
|
-
function isRecord$
|
|
28213
|
+
function isRecord$5(value) {
|
|
28142
28214
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28143
28215
|
}
|
|
28144
28216
|
function collapseWhitespace(value) {
|
|
@@ -63362,7 +63434,7 @@ var CapletsEngine = class {
|
|
|
63362
63434
|
}
|
|
63363
63435
|
}
|
|
63364
63436
|
async completeCliWords(words) {
|
|
63365
|
-
const { completeCliWords } = await import("./completion-
|
|
63437
|
+
const { completeCliWords } = await import("./completion-DrPr2vYw.js").then((n) => n.r);
|
|
63366
63438
|
return await completeCliWords(words, {
|
|
63367
63439
|
config: this.registry.config,
|
|
63368
63440
|
managers: {
|
|
@@ -63652,7 +63724,7 @@ function annotateDirectResult(result, caplet, operation) {
|
|
|
63652
63724
|
return {
|
|
63653
63725
|
...result,
|
|
63654
63726
|
_meta: {
|
|
63655
|
-
...isRecord$
|
|
63727
|
+
...isRecord$4(existingMeta) ? existingMeta : {},
|
|
63656
63728
|
caplets: {
|
|
63657
63729
|
capletId: caplet.server,
|
|
63658
63730
|
backend: caplet.backend,
|
|
@@ -63665,7 +63737,7 @@ function annotateDirectResult(result, caplet, operation) {
|
|
|
63665
63737
|
function isUnsupportedCapability(error) {
|
|
63666
63738
|
return error instanceof CapletsError && error.code === "UNSUPPORTED_CAPABILITY";
|
|
63667
63739
|
}
|
|
63668
|
-
function isRecord$
|
|
63740
|
+
function isRecord$4(value) {
|
|
63669
63741
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
63670
63742
|
}
|
|
63671
63743
|
//#endregion
|
|
@@ -79662,32 +79734,18 @@ function nativeCapletToolDescription(toolName, caplet) {
|
|
|
79662
79734
|
}
|
|
79663
79735
|
//#endregion
|
|
79664
79736
|
//#region src/server/options.ts
|
|
79665
|
-
const DEFAULT_SERVER_USER = "caplets";
|
|
79666
79737
|
function resolveCapletsServer(input = {}, env = process.env) {
|
|
79667
79738
|
const rawUrl = nonEmpty$1(input.url, "url") ?? nonEmpty$1(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL");
|
|
79668
79739
|
if (rawUrl === void 0) throw new CapletsError("REQUEST_INVALID", "CAPLETS_SERVER_URL or url is required.");
|
|
79669
79740
|
const baseUrl = parseServerBaseUrl(rawUrl);
|
|
79670
|
-
const userWasExplicit = input.user !== void 0 || hasEnv$1(env.CAPLETS_SERVER_USER);
|
|
79671
|
-
const user = nonEmpty$1(input.user, "user") ?? nonEmpty$1(env.CAPLETS_SERVER_USER, "CAPLETS_SERVER_USER") ?? DEFAULT_SERVER_USER;
|
|
79672
|
-
const password = nonEmpty$1(input.password, "password") ?? nonEmpty$1(env.CAPLETS_SERVER_PASSWORD, "CAPLETS_SERVER_PASSWORD");
|
|
79673
|
-
if (userWasExplicit && password === void 0) throw new CapletsError("REQUEST_INVALID", "Caplets server Basic Auth requires a password; set CAPLETS_SERVER_PASSWORD or password.");
|
|
79674
|
-
const auth = password === void 0 ? {
|
|
79675
|
-
enabled: false,
|
|
79676
|
-
user
|
|
79677
|
-
} : {
|
|
79678
|
-
enabled: true,
|
|
79679
|
-
user,
|
|
79680
|
-
password
|
|
79681
|
-
};
|
|
79682
|
-
const requestInit = auth.enabled ? { headers: { Authorization: basicAuthHeader$1(auth.user, auth.password) } } : {};
|
|
79683
79741
|
return {
|
|
79684
79742
|
baseUrl,
|
|
79685
79743
|
mcpUrl: mcpUrlForBase(baseUrl),
|
|
79686
79744
|
attachUrl: attachUrlForBase(baseUrl),
|
|
79687
79745
|
controlUrl: controlUrlForBase(baseUrl),
|
|
79688
79746
|
healthUrl: healthUrlForBase(baseUrl),
|
|
79689
|
-
auth,
|
|
79690
|
-
requestInit,
|
|
79747
|
+
auth: { type: "none" },
|
|
79748
|
+
requestInit: {},
|
|
79691
79749
|
...input.fetch ? { fetch: input.fetch } : {}
|
|
79692
79750
|
};
|
|
79693
79751
|
}
|
|
@@ -79724,18 +79782,12 @@ function isLoopbackHost(host) {
|
|
|
79724
79782
|
const normalized = host.toLocaleLowerCase();
|
|
79725
79783
|
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
|
|
79726
79784
|
}
|
|
79727
|
-
function basicAuthHeader$1(user, password) {
|
|
79728
|
-
return `Basic ${Buffer$1.from(`${user}:${password}`).toString("base64")}`;
|
|
79729
|
-
}
|
|
79730
79785
|
function nonEmpty$1(value, label) {
|
|
79731
79786
|
if (value === void 0) return;
|
|
79732
79787
|
const trimmed = value.trim();
|
|
79733
79788
|
if (!trimmed) throw new CapletsError("REQUEST_INVALID", `${label} must not be empty`);
|
|
79734
79789
|
return trimmed;
|
|
79735
79790
|
}
|
|
79736
|
-
function hasEnv$1(value) {
|
|
79737
|
-
return value !== void 0 && value.trim() !== "";
|
|
79738
|
-
}
|
|
79739
79791
|
//#endregion
|
|
79740
79792
|
//#region src/remote/options.ts
|
|
79741
79793
|
const DEFAULT_REMOTE_USER = "caplets";
|
|
@@ -79759,25 +79811,16 @@ function resolveCapletsRemote(input = {}, env = process.env) {
|
|
|
79759
79811
|
const rawUrl = nonEmpty(input.url, "url") ?? nonEmpty(env.CAPLETS_REMOTE_URL, "CAPLETS_REMOTE_URL");
|
|
79760
79812
|
if (rawUrl === void 0) throw new CapletsError("REQUEST_INVALID", "CAPLETS_REMOTE_URL or url is required.");
|
|
79761
79813
|
const baseUrl = parseServerBaseUrl(rawUrl);
|
|
79762
|
-
const token = nonEmpty(input.token, "token")
|
|
79763
|
-
const userWasExplicit = input.user !== void 0 || hasEnv(env.CAPLETS_REMOTE_USER);
|
|
79764
|
-
const user = nonEmpty(input.user, "user") ?? nonEmpty(env.CAPLETS_REMOTE_USER, "CAPLETS_REMOTE_USER") ?? DEFAULT_REMOTE_USER;
|
|
79765
|
-
const password = nonEmpty(input.password, "password") ?? nonEmpty(env.CAPLETS_REMOTE_PASSWORD, "CAPLETS_REMOTE_PASSWORD");
|
|
79814
|
+
const token = nonEmpty(input.token, "token");
|
|
79766
79815
|
const workspace = nonEmpty(input.workspace, "workspace") ?? nonEmpty(env.CAPLETS_REMOTE_WORKSPACE, "CAPLETS_REMOTE_WORKSPACE");
|
|
79767
|
-
if (token && password) throw new CapletsError("REQUEST_INVALID", "Use either CAPLETS_REMOTE_TOKEN or CAPLETS_REMOTE_PASSWORD, not both.");
|
|
79768
|
-
if (!token && userWasExplicit && password === void 0) throw new CapletsError("REQUEST_INVALID", "Remote Caplets Basic Auth requires a password; set CAPLETS_REMOTE_PASSWORD or password.");
|
|
79769
79816
|
const auth = token ? {
|
|
79770
79817
|
type: "bearer",
|
|
79771
79818
|
token
|
|
79772
|
-
} : password === void 0 ? {
|
|
79773
|
-
type: "none",
|
|
79774
|
-
user
|
|
79775
79819
|
} : {
|
|
79776
|
-
type: "
|
|
79777
|
-
user
|
|
79778
|
-
password
|
|
79820
|
+
type: "none",
|
|
79821
|
+
user: DEFAULT_REMOTE_USER
|
|
79779
79822
|
};
|
|
79780
|
-
const requestInit = auth.type === "bearer" ? { headers: { Authorization: `Bearer ${auth.token}` } } :
|
|
79823
|
+
const requestInit = auth.type === "bearer" ? { headers: { Authorization: `Bearer ${auth.token}` } } : {};
|
|
79781
79824
|
return {
|
|
79782
79825
|
baseUrl,
|
|
79783
79826
|
mcpUrl: appendBasePath(baseUrl, "v1/mcp"),
|
|
@@ -79797,7 +79840,7 @@ function resolveHostedCloudRemote(input = {}, env = process.env) {
|
|
|
79797
79840
|
const cloud = parseHostedCloudRemoteUrl(rawUrl);
|
|
79798
79841
|
const workspace = cloud.workspace ?? nonEmpty(input.workspace, "workspace") ?? nonEmpty(env.CAPLETS_REMOTE_WORKSPACE, "CAPLETS_REMOTE_WORKSPACE");
|
|
79799
79842
|
if (!workspace) throw new CapletsError("REQUEST_INVALID", "Caplets Cloud remote URL requires a selected workspace.");
|
|
79800
|
-
const token = nonEmpty(input.token, "token")
|
|
79843
|
+
const token = nonEmpty(input.token, "token");
|
|
79801
79844
|
const auth = token ? {
|
|
79802
79845
|
type: "bearer",
|
|
79803
79846
|
token
|
|
@@ -79827,6 +79870,11 @@ function hostedCloudWorkspaceFromRemoteUrl(value) {
|
|
|
79827
79870
|
return;
|
|
79828
79871
|
}
|
|
79829
79872
|
}
|
|
79873
|
+
function normalizeRemoteProfileHostUrl(value) {
|
|
79874
|
+
const url = parseServerBaseUrl(value);
|
|
79875
|
+
if (isCapletsCloudUrl(url.toString())) return `${url.origin}/`;
|
|
79876
|
+
return url.toString();
|
|
79877
|
+
}
|
|
79830
79878
|
function projectBindingWebSocketUrlForBase(baseUrl) {
|
|
79831
79879
|
return webSocketUrl(appendBasePath(baseUrl, "v1/attach/project-bindings/connect"));
|
|
79832
79880
|
}
|
|
@@ -79864,18 +79912,12 @@ function parseCapletsMode(value) {
|
|
|
79864
79912
|
if (value === "auto" || value === "local" || value === "remote" || value === "cloud") return value;
|
|
79865
79913
|
throw new CapletsError("REQUEST_INVALID", `Expected CAPLETS_MODE to be auto, local, remote, or cloud, got ${value}`);
|
|
79866
79914
|
}
|
|
79867
|
-
function basicAuthHeader(user, password) {
|
|
79868
|
-
return `Basic ${Buffer$1.from(`${user}:${password}`).toString("base64")}`;
|
|
79869
|
-
}
|
|
79870
79915
|
function nonEmpty(value, label) {
|
|
79871
79916
|
if (value === void 0) return void 0;
|
|
79872
79917
|
const trimmed = value.trim();
|
|
79873
79918
|
if (!trimmed) throw new CapletsError("REQUEST_INVALID", `${label} must not be empty`);
|
|
79874
79919
|
return trimmed;
|
|
79875
79920
|
}
|
|
79876
|
-
function hasEnv(value) {
|
|
79877
|
-
return value !== void 0 && value.trim() !== "";
|
|
79878
|
-
}
|
|
79879
79921
|
//#endregion
|
|
79880
79922
|
//#region src/native/options.ts
|
|
79881
79923
|
const DEFAULT_POLL_INTERVAL_MS = 3e4;
|
|
@@ -79887,7 +79929,11 @@ function resolveNativeCapletsServiceOptions(input = {}, env = process.env) {
|
|
|
79887
79929
|
}, env);
|
|
79888
79930
|
if (mode.mode === "local") return { mode: "local" };
|
|
79889
79931
|
const remoteFetch = input.remote?.fetch;
|
|
79890
|
-
const server = mode.mode === "cloud" ?
|
|
79932
|
+
const server = mode.mode === "cloud" ? resolveNativeHostedCloudRemoteOrPlaceholder(input.remote?.url ?? env.CAPLETS_REMOTE_URL ?? "", optionalWorkspace(input, env).workspace, remoteFetch) : resolveCapletsRemote({
|
|
79933
|
+
...input.remote?.url ? { url: input.remote.url } : {},
|
|
79934
|
+
...input.remote?.workspace ? { workspace: input.remote.workspace } : {},
|
|
79935
|
+
...remoteFetch ? { fetch: remoteFetch } : {}
|
|
79936
|
+
}, env);
|
|
79891
79937
|
const cloud = resolveNativeCloudPresence(input.remote?.cloud, env);
|
|
79892
79938
|
return {
|
|
79893
79939
|
mode: mode.mode,
|
|
@@ -79908,16 +79954,19 @@ function resolveNativeHostedCloudRemote(url, workspace, fetch) {
|
|
|
79908
79954
|
...fetch ? { fetch } : {}
|
|
79909
79955
|
});
|
|
79910
79956
|
}
|
|
79957
|
+
function resolveNativeHostedCloudRemoteOrPlaceholder(url, workspace, fetch) {
|
|
79958
|
+
if (!isCapletsCloudUrl(url)) throw new CapletsError("REQUEST_INVALID", "CAPLETS_MODE=cloud requires Caplets Cloud.");
|
|
79959
|
+
if (workspace) return resolveNativeHostedCloudRemote(url, workspace, fetch);
|
|
79960
|
+
return resolveCapletsRemote({
|
|
79961
|
+
url,
|
|
79962
|
+
...fetch ? { fetch } : {}
|
|
79963
|
+
}, {});
|
|
79964
|
+
}
|
|
79911
79965
|
function optionalWorkspace(input, env) {
|
|
79912
79966
|
const workspace = input.remote?.cloud?.workspaceId ?? input.remote?.workspace ?? env.CAPLETS_REMOTE_WORKSPACE ?? env.CAPLETS_CLOUD_WORKSPACE_ID;
|
|
79913
79967
|
return workspace ? { workspace } : {};
|
|
79914
79968
|
}
|
|
79915
79969
|
function nativeAuthFromRemoteAuth$1(auth) {
|
|
79916
|
-
if (auth.type === "basic") return {
|
|
79917
|
-
enabled: true,
|
|
79918
|
-
user: auth.user,
|
|
79919
|
-
password: auth.password
|
|
79920
|
-
};
|
|
79921
79970
|
if (auth.type === "none") return {
|
|
79922
79971
|
enabled: false,
|
|
79923
79972
|
user: auth.user
|
|
@@ -80241,45 +80290,79 @@ function projectSyncFiles(projectRoot) {
|
|
|
80241
80290
|
//#endregion
|
|
80242
80291
|
//#region src/native/remote.ts
|
|
80243
80292
|
function createSdkRemoteCapletsClient(options) {
|
|
80244
|
-
const fetchImpl = options.fetch ?? fetch;
|
|
80245
80293
|
const listeners = /* @__PURE__ */ new Set();
|
|
80246
80294
|
let manifest;
|
|
80247
80295
|
let exportByName = /* @__PURE__ */ new Map();
|
|
80248
80296
|
let eventsAbort;
|
|
80297
|
+
let eventsStartInFlight;
|
|
80249
80298
|
let eventsReconnectTimer;
|
|
80299
|
+
let closed = false;
|
|
80300
|
+
const resolveRuntimeOptions = async () => {
|
|
80301
|
+
return options.resolveRuntimeOptions ? await options.resolveRuntimeOptions() : options;
|
|
80302
|
+
};
|
|
80303
|
+
const fetchFor = (runtimeOptions) => runtimeOptions.fetch ?? fetch;
|
|
80304
|
+
const fetchCurrentManifest = async () => {
|
|
80305
|
+
const runtimeOptions = await resolveRuntimeOptions();
|
|
80306
|
+
return await fetchAttachManifest(runtimeOptions.url, runtimeOptions.requestInit, fetchFor(runtimeOptions));
|
|
80307
|
+
};
|
|
80308
|
+
const invokeCurrentExport = async (body) => {
|
|
80309
|
+
const runtimeOptions = await resolveRuntimeOptions();
|
|
80310
|
+
return await invokeAttachExport(runtimeOptions.url, runtimeOptions.requestInit, fetchFor(runtimeOptions), body);
|
|
80311
|
+
};
|
|
80250
80312
|
const clearEventsReconnectTimer = () => {
|
|
80251
80313
|
if (eventsReconnectTimer) {
|
|
80252
80314
|
clearTimeout(eventsReconnectTimer);
|
|
80253
80315
|
eventsReconnectTimer = void 0;
|
|
80254
80316
|
}
|
|
80255
80317
|
};
|
|
80318
|
+
const scheduleEventsReconnect = () => {
|
|
80319
|
+
if (closed || listeners.size === 0) return;
|
|
80320
|
+
clearEventsReconnectTimer();
|
|
80321
|
+
eventsReconnectTimer = setTimeout(() => {
|
|
80322
|
+
eventsReconnectTimer = void 0;
|
|
80323
|
+
startEvents();
|
|
80324
|
+
}, 1e3);
|
|
80325
|
+
};
|
|
80326
|
+
const startEventsNow = async () => {
|
|
80327
|
+
try {
|
|
80328
|
+
const runtimeOptions = await resolveRuntimeOptions();
|
|
80329
|
+
if (closed || eventsAbort || listeners.size === 0) return;
|
|
80330
|
+
eventsAbort = startAttachEvents(runtimeOptions.url, runtimeOptions.requestInit, fetchFor(runtimeOptions), listeners, (closedAbort, retry) => {
|
|
80331
|
+
if (eventsAbort !== closedAbort) return;
|
|
80332
|
+
eventsAbort = void 0;
|
|
80333
|
+
if (!retry || closedAbort.signal.aborted || listeners.size === 0) return;
|
|
80334
|
+
scheduleEventsReconnect();
|
|
80335
|
+
});
|
|
80336
|
+
} catch (error) {
|
|
80337
|
+
if (isPermanentRemoteCredentialsError(error)) {
|
|
80338
|
+
options.writeErr?.(`${remoteAuthError(options.authKind ?? "self_hosted_remote").message}\n`);
|
|
80339
|
+
return;
|
|
80340
|
+
}
|
|
80341
|
+
scheduleEventsReconnect();
|
|
80342
|
+
}
|
|
80343
|
+
};
|
|
80256
80344
|
const startEvents = () => {
|
|
80257
|
-
if (eventsAbort || listeners.size === 0) return;
|
|
80258
|
-
|
|
80259
|
-
|
|
80260
|
-
|
|
80261
|
-
if (
|
|
80262
|
-
clearEventsReconnectTimer();
|
|
80263
|
-
eventsReconnectTimer = setTimeout(() => {
|
|
80264
|
-
eventsReconnectTimer = void 0;
|
|
80265
|
-
startEvents();
|
|
80266
|
-
}, 1e3);
|
|
80345
|
+
if (closed || eventsAbort || eventsStartInFlight || listeners.size === 0) return;
|
|
80346
|
+
const start = startEventsNow();
|
|
80347
|
+
eventsStartInFlight = start;
|
|
80348
|
+
start.finally(() => {
|
|
80349
|
+
if (eventsStartInFlight === start) eventsStartInFlight = void 0;
|
|
80267
80350
|
});
|
|
80268
80351
|
};
|
|
80269
80352
|
return {
|
|
80270
80353
|
async listTools() {
|
|
80271
|
-
manifest = await
|
|
80354
|
+
manifest = await fetchCurrentManifest();
|
|
80272
80355
|
exportByName = exportMapFor(manifest);
|
|
80273
80356
|
return toolsFromManifest(manifest);
|
|
80274
80357
|
},
|
|
80275
80358
|
async callTool(name, args) {
|
|
80276
80359
|
if (!manifest) {
|
|
80277
|
-
manifest = await
|
|
80360
|
+
manifest = await fetchCurrentManifest();
|
|
80278
80361
|
exportByName = exportMapFor(manifest);
|
|
80279
80362
|
}
|
|
80280
80363
|
const invokeWithStaleRetry = async (entry, input) => {
|
|
80281
80364
|
try {
|
|
80282
|
-
return await
|
|
80365
|
+
return await invokeCurrentExport({
|
|
80283
80366
|
revision: manifest.revision,
|
|
80284
80367
|
kind: entry.kind,
|
|
80285
80368
|
exportId: entry.exportId,
|
|
@@ -80287,12 +80370,12 @@ function createSdkRemoteCapletsClient(options) {
|
|
|
80287
80370
|
});
|
|
80288
80371
|
} catch (error) {
|
|
80289
80372
|
if (!isAttachManifestStale(error)) throw error;
|
|
80290
|
-
const nextManifest = await
|
|
80373
|
+
const nextManifest = await fetchCurrentManifest();
|
|
80291
80374
|
const nextEntry = compatibleExport(nextManifest, entry);
|
|
80292
80375
|
manifest = nextManifest;
|
|
80293
80376
|
exportByName = exportMapFor(nextManifest);
|
|
80294
80377
|
if (!nextEntry) throw new CapletsError("ATTACH_EXPORT_NOT_FOUND", "Attach export changed after manifest refresh; refetch the manifest before retrying.");
|
|
80295
|
-
return await
|
|
80378
|
+
return await invokeCurrentExport({
|
|
80296
80379
|
revision: nextManifest.revision,
|
|
80297
80380
|
kind: nextEntry.kind,
|
|
80298
80381
|
exportId: nextEntry.exportId,
|
|
@@ -80321,6 +80404,7 @@ function createSdkRemoteCapletsClient(options) {
|
|
|
80321
80404
|
};
|
|
80322
80405
|
},
|
|
80323
80406
|
async close() {
|
|
80407
|
+
closed = true;
|
|
80324
80408
|
clearEventsReconnectTimer();
|
|
80325
80409
|
eventsAbort?.abort();
|
|
80326
80410
|
eventsAbort = void 0;
|
|
@@ -80917,7 +81001,7 @@ function errorMessage$1(error) {
|
|
|
80917
81001
|
return error instanceof Error ? error.message : String(error);
|
|
80918
81002
|
}
|
|
80919
81003
|
function remoteAuthError(kind) {
|
|
80920
|
-
return new CapletsError("AUTH_FAILED", kind === "hosted_cloud" ? "Caplets Cloud authentication failed; run caplets
|
|
81004
|
+
return new CapletsError("AUTH_FAILED", kind === "hosted_cloud" ? "Caplets Cloud authentication failed; run caplets remote login <cloud-url>." : "Remote Caplets authentication failed; run caplets remote login <url>.");
|
|
80921
81005
|
}
|
|
80922
81006
|
function isSessionFailure(error) {
|
|
80923
81007
|
const candidate = error;
|
|
@@ -80934,6 +81018,15 @@ function isAuthFailure(error) {
|
|
|
80934
81018
|
if (status === 401 || status === 403 || statusCode === 401 || statusCode === 403 || code === 401 || code === 403) return true;
|
|
80935
81019
|
return /\b(401|403|unauthorized|forbidden)\b/iu.test(errorMessage$1(error));
|
|
80936
81020
|
}
|
|
81021
|
+
function isPermanentRemoteCredentialsError(error) {
|
|
81022
|
+
const candidate = error;
|
|
81023
|
+
if (candidate?.projectBindingCode === "remote_credentials_required" || candidate?.projectBindingCode === "remote_auth_failed") return true;
|
|
81024
|
+
if (isPlainObject(candidate?.details)) {
|
|
81025
|
+
const code = candidate.details.code;
|
|
81026
|
+
if (code === "remote_credentials_required" || code === "remote_auth_failed") return true;
|
|
81027
|
+
}
|
|
81028
|
+
return isAuthFailure(error);
|
|
81029
|
+
}
|
|
80937
81030
|
//#endregion
|
|
80938
81031
|
//#region src/cloud-auth/errors.ts
|
|
80939
81032
|
const SECRET_PATTERN = /(cap_access_[a-z0-9._~+/=-]+|cap_refresh_[a-z0-9._~+/=-]+|one_time_code_[a-z0-9._~+/=-]+|Bearer\s+)[^\s"]*/giu;
|
|
@@ -81034,7 +81127,7 @@ var CloudAuthClient = class {
|
|
|
81034
81127
|
throw new CapletsError("AUTH_FAILED", message, redactCloudAuthSecrets({
|
|
81035
81128
|
code,
|
|
81036
81129
|
message,
|
|
81037
|
-
recoveryCommand: code === "workspace_switch_required" ? "caplets cloud
|
|
81130
|
+
recoveryCommand: code === "workspace_switch_required" ? "caplets remote login <cloud-url> --workspace <workspace>" : "caplets remote login <cloud-url>",
|
|
81038
81131
|
requestId
|
|
81039
81132
|
}));
|
|
81040
81133
|
}
|
|
@@ -81074,6 +81167,78 @@ function normalizeCredentials(response, fallbackCloudUrl) {
|
|
|
81074
81167
|
};
|
|
81075
81168
|
}
|
|
81076
81169
|
//#endregion
|
|
81170
|
+
//#region src/project-binding/errors.ts
|
|
81171
|
+
const PROJECT_BINDING_ERROR_CODES = [
|
|
81172
|
+
"cloud_auth_required",
|
|
81173
|
+
"cloud_auth_expired",
|
|
81174
|
+
"cloud_auth_revoked",
|
|
81175
|
+
"workspace_selection_required",
|
|
81176
|
+
"workspace_switch_required",
|
|
81177
|
+
"workspace_forbidden",
|
|
81178
|
+
"project_binding_forbidden",
|
|
81179
|
+
"endpoint_unavailable",
|
|
81180
|
+
"websocket_upgrade_required",
|
|
81181
|
+
"sync_required",
|
|
81182
|
+
"sync_failed",
|
|
81183
|
+
"sync_size_limit_exceeded",
|
|
81184
|
+
"lease_conflict",
|
|
81185
|
+
"lease_expired",
|
|
81186
|
+
"policy_denied",
|
|
81187
|
+
"usage_limit_reached",
|
|
81188
|
+
"billing_required",
|
|
81189
|
+
"subscription_past_due",
|
|
81190
|
+
"email_verification_required",
|
|
81191
|
+
"remote_credentials_required",
|
|
81192
|
+
"remote_auth_failed"
|
|
81193
|
+
];
|
|
81194
|
+
var ProjectBindingError = class extends CapletsError {
|
|
81195
|
+
projectBindingCode;
|
|
81196
|
+
recoveryCommand;
|
|
81197
|
+
requestId;
|
|
81198
|
+
constructor(input) {
|
|
81199
|
+
super("SERVER_UNAVAILABLE", input.message, input);
|
|
81200
|
+
this.name = "ProjectBindingError";
|
|
81201
|
+
this.projectBindingCode = input.code;
|
|
81202
|
+
this.recoveryCommand = input.recoveryCommand;
|
|
81203
|
+
this.requestId = input.requestId;
|
|
81204
|
+
}
|
|
81205
|
+
};
|
|
81206
|
+
function projectBindingRecovery(code, message = defaultProjectBindingMessage(code)) {
|
|
81207
|
+
return {
|
|
81208
|
+
code,
|
|
81209
|
+
message,
|
|
81210
|
+
recoveryCommand: recoveryCommandFor(code)
|
|
81211
|
+
};
|
|
81212
|
+
}
|
|
81213
|
+
function projectBindingError(code, message) {
|
|
81214
|
+
return new ProjectBindingError(projectBindingRecovery(code, message));
|
|
81215
|
+
}
|
|
81216
|
+
function recoveryCommandFor(code) {
|
|
81217
|
+
switch (code) {
|
|
81218
|
+
case "cloud_auth_required":
|
|
81219
|
+
case "cloud_auth_expired":
|
|
81220
|
+
case "cloud_auth_revoked":
|
|
81221
|
+
case "workspace_selection_required": return "caplets remote login <cloud-url>";
|
|
81222
|
+
case "workspace_switch_required": return "caplets remote login <cloud-url> --workspace <workspace>";
|
|
81223
|
+
case "sync_size_limit_exceeded": return "Add exclusions to .capletsignore or upgrade the workspace plan.";
|
|
81224
|
+
case "remote_credentials_required":
|
|
81225
|
+
case "remote_auth_failed": return "caplets remote login <url>";
|
|
81226
|
+
case "endpoint_unavailable":
|
|
81227
|
+
case "websocket_upgrade_required": return "caplets doctor";
|
|
81228
|
+
default: return;
|
|
81229
|
+
}
|
|
81230
|
+
}
|
|
81231
|
+
function defaultProjectBindingMessage(code) {
|
|
81232
|
+
switch (code) {
|
|
81233
|
+
case "sync_size_limit_exceeded": return "Project sync size exceeds the selected workspace policy.";
|
|
81234
|
+
case "workspace_switch_required": return "The requested workspace differs from the saved Selected Workspace.";
|
|
81235
|
+
case "cloud_auth_required": return "Hosted Project Binding requires Remote Login.";
|
|
81236
|
+
case "endpoint_unavailable":
|
|
81237
|
+
case "websocket_upgrade_required": return "Project Binding endpoint is unavailable.";
|
|
81238
|
+
default: return code.replace(/_/gu, " ");
|
|
81239
|
+
}
|
|
81240
|
+
}
|
|
81241
|
+
//#endregion
|
|
81077
81242
|
//#region src/cloud-auth/store.ts
|
|
81078
81243
|
var CloudAuthStore = class {
|
|
81079
81244
|
path;
|
|
@@ -81093,7 +81258,7 @@ var CloudAuthStore = class {
|
|
|
81093
81258
|
}
|
|
81094
81259
|
};
|
|
81095
81260
|
function migrateCredentials(value) {
|
|
81096
|
-
const record = isRecord$
|
|
81261
|
+
const record = isRecord$3(value) ? value : {};
|
|
81097
81262
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
81098
81263
|
return {
|
|
81099
81264
|
version: 2,
|
|
@@ -81143,7 +81308,7 @@ function cloudAuthPath(options = {}) {
|
|
|
81143
81308
|
if (platform === "win32") return win32.join(defaultConfigBaseDir(env, home, platform), "Caplets", "cloud-auth.json");
|
|
81144
81309
|
return posix.join(defaultConfigBaseDir(env, home, platform), "caplets", "cloud-auth.json");
|
|
81145
81310
|
}
|
|
81146
|
-
function isRecord$
|
|
81311
|
+
function isRecord$3(value) {
|
|
81147
81312
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
81148
81313
|
}
|
|
81149
81314
|
function stringValue(value) {
|
|
@@ -81154,79 +81319,567 @@ function arrayValue(value) {
|
|
|
81154
81319
|
if (typeof value === "string") return value.split(/\s+/u).filter(Boolean);
|
|
81155
81320
|
}
|
|
81156
81321
|
//#endregion
|
|
81157
|
-
//#region src/
|
|
81158
|
-
|
|
81159
|
-
|
|
81160
|
-
|
|
81161
|
-
|
|
81162
|
-
|
|
81163
|
-
|
|
81164
|
-
|
|
81165
|
-
|
|
81166
|
-
|
|
81167
|
-
|
|
81168
|
-
|
|
81169
|
-
|
|
81170
|
-
|
|
81171
|
-
|
|
81172
|
-
|
|
81173
|
-
|
|
81174
|
-
|
|
81175
|
-
|
|
81176
|
-
|
|
81177
|
-
|
|
81178
|
-
|
|
81179
|
-
|
|
81180
|
-
|
|
81181
|
-
|
|
81182
|
-
|
|
81183
|
-
|
|
81184
|
-
|
|
81185
|
-
|
|
81186
|
-
|
|
81187
|
-
|
|
81188
|
-
|
|
81189
|
-
|
|
81190
|
-
|
|
81322
|
+
//#region src/remote/credential-store.ts
|
|
81323
|
+
var FileRemoteCredentialStore = class {
|
|
81324
|
+
root;
|
|
81325
|
+
constructor(options = {}) {
|
|
81326
|
+
this.root = options.root ?? join(DEFAULT_AUTH_DIR, "remote-credentials");
|
|
81327
|
+
}
|
|
81328
|
+
pathForKey(key) {
|
|
81329
|
+
return join(this.root, `${encodeURIComponent(key)}.json`);
|
|
81330
|
+
}
|
|
81331
|
+
async load(key) {
|
|
81332
|
+
const path = this.pathForKey(key);
|
|
81333
|
+
if (!existsSync(path)) return void 0;
|
|
81334
|
+
return parseRemoteProfileCredential(JSON.parse(readFileSync(path, "utf8")));
|
|
81335
|
+
}
|
|
81336
|
+
async save(key, credential) {
|
|
81337
|
+
mkdirSync(this.root, {
|
|
81338
|
+
recursive: true,
|
|
81339
|
+
mode: 448
|
|
81340
|
+
});
|
|
81341
|
+
try {
|
|
81342
|
+
chmodSync(this.root, 448);
|
|
81343
|
+
} catch {}
|
|
81344
|
+
const path = this.pathForKey(key);
|
|
81345
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
81346
|
+
writeFileSync(tempPath, `${JSON.stringify(credential, null, 2)}\n`, { mode: 384 });
|
|
81347
|
+
try {
|
|
81348
|
+
chmodSync(tempPath, 384);
|
|
81349
|
+
} catch {}
|
|
81350
|
+
renameSync(tempPath, path);
|
|
81351
|
+
}
|
|
81352
|
+
async delete(key) {
|
|
81353
|
+
const path = this.pathForKey(key);
|
|
81354
|
+
if (!existsSync(path)) return false;
|
|
81355
|
+
rmSync(path, { force: true });
|
|
81356
|
+
return true;
|
|
81191
81357
|
}
|
|
81192
81358
|
};
|
|
81193
|
-
function
|
|
81359
|
+
function parseRemoteProfileCredential(value) {
|
|
81360
|
+
if (!isRecord$2(value)) return void 0;
|
|
81194
81361
|
return {
|
|
81195
|
-
|
|
81196
|
-
|
|
81197
|
-
|
|
81362
|
+
...typeof value.accessToken === "string" ? { accessToken: value.accessToken } : {},
|
|
81363
|
+
...typeof value.refreshToken === "string" ? { refreshToken: value.refreshToken } : {},
|
|
81364
|
+
...typeof value.tokenType === "string" ? { tokenType: value.tokenType } : {},
|
|
81365
|
+
...typeof value.expiresAt === "string" ? { expiresAt: value.expiresAt } : {},
|
|
81366
|
+
...Array.isArray(value.scope) ? { scope: value.scope.filter((entry) => typeof entry === "string") } : {},
|
|
81367
|
+
...typeof value.clientSecret === "string" ? { clientSecret: value.clientSecret } : {},
|
|
81368
|
+
...typeof value.pairingCode === "string" ? { pairingCode: value.pairingCode } : {}
|
|
81198
81369
|
};
|
|
81199
81370
|
}
|
|
81200
|
-
function
|
|
81201
|
-
return
|
|
81371
|
+
function isRecord$2(value) {
|
|
81372
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
81202
81373
|
}
|
|
81203
|
-
|
|
81204
|
-
|
|
81205
|
-
|
|
81206
|
-
|
|
81207
|
-
|
|
81208
|
-
|
|
81209
|
-
|
|
81210
|
-
|
|
81211
|
-
|
|
81212
|
-
|
|
81213
|
-
|
|
81214
|
-
|
|
81215
|
-
|
|
81216
|
-
|
|
81374
|
+
//#endregion
|
|
81375
|
+
//#region src/remote/profiles.ts
|
|
81376
|
+
function remoteProfileKey(input) {
|
|
81377
|
+
const hostUrl = normalizeRemoteProfileHostUrl(input.hostUrl);
|
|
81378
|
+
if (input.kind === "cloud") {
|
|
81379
|
+
const workspace = input.workspace ?? hostedCloudWorkspaceFromRemoteUrl(input.hostUrl);
|
|
81380
|
+
if (!workspace) throw new CapletsError("REQUEST_INVALID", "Cloud Remote Profile requires a workspace.");
|
|
81381
|
+
return `cloud:${hostUrl}:${workspace}`;
|
|
81382
|
+
}
|
|
81383
|
+
return `self-hosted:${hostUrl}`;
|
|
81384
|
+
}
|
|
81385
|
+
function selectedWorkspaceKey(hostUrl) {
|
|
81386
|
+
return `cloud:${normalizeRemoteProfileHostUrl(hostUrl)}:selected-workspace`;
|
|
81387
|
+
}
|
|
81388
|
+
function remoteProfileStatus(input) {
|
|
81389
|
+
const hostUrl = normalizeRemoteProfileHostUrl(input.hostUrl);
|
|
81390
|
+
const key = input.key ?? remoteProfileKey({
|
|
81391
|
+
kind: input.kind,
|
|
81392
|
+
hostUrl,
|
|
81393
|
+
workspace: input.workspaceSlug ?? input.workspaceId
|
|
81394
|
+
});
|
|
81395
|
+
const expiresAt = input.credential?.expiresAt;
|
|
81396
|
+
const expired = Number.isFinite(Date.parse(expiresAt ?? "")) ? Date.parse(expiresAt ?? "") <= Date.now() : false;
|
|
81397
|
+
return {
|
|
81398
|
+
authenticated: Boolean(input.credential?.accessToken) && !expired,
|
|
81399
|
+
kind: input.kind,
|
|
81400
|
+
key,
|
|
81401
|
+
hostUrl,
|
|
81402
|
+
...input.workspaceId ? { workspaceId: input.workspaceId } : {},
|
|
81403
|
+
...input.workspaceSlug ? { workspaceSlug: input.workspaceSlug } : {},
|
|
81404
|
+
...input.clientId ? { clientId: input.clientId } : {},
|
|
81405
|
+
selected: Boolean(input.selected),
|
|
81406
|
+
...input.clientLabel ? { clientLabel: input.clientLabel } : {},
|
|
81407
|
+
...input.createdAt ? { createdAt: input.createdAt } : {},
|
|
81408
|
+
...input.updatedAt ? { updatedAt: input.updatedAt } : {},
|
|
81409
|
+
...expiresAt ? { expiresAt } : {},
|
|
81410
|
+
...input.credential?.scope ? { scope: input.credential.scope } : {},
|
|
81411
|
+
...input.credential?.tokenType ? { tokenType: input.credential.tokenType } : {}
|
|
81412
|
+
};
|
|
81217
81413
|
}
|
|
81218
|
-
|
|
81219
|
-
|
|
81220
|
-
|
|
81221
|
-
|
|
81222
|
-
|
|
81223
|
-
|
|
81224
|
-
|
|
81225
|
-
|
|
81414
|
+
//#endregion
|
|
81415
|
+
//#region src/remote/profile-store.ts
|
|
81416
|
+
const PROFILE_LOCK_DIR = "remote-profiles.lock";
|
|
81417
|
+
const PROFILE_REFRESH_LOCK_DIR = "remote-profile-refresh-locks";
|
|
81418
|
+
const PROFILE_LOCK_TIMEOUT_MS = 2e4;
|
|
81419
|
+
function createRemoteProfileStore(options = {}) {
|
|
81420
|
+
const env = options.env ?? process.env;
|
|
81421
|
+
return new FileRemoteProfileStore({
|
|
81422
|
+
root: join(options.authDir ?? (env.CAPLETS_CLOUD_AUTH_PATH ? dirname(env.CAPLETS_CLOUD_AUTH_PATH) : void 0) ?? DEFAULT_AUTH_DIR, "remote-profiles"),
|
|
81423
|
+
legacyCloudAuthStore: options.legacyCloudAuthStore ?? new CloudAuthStore(options.authDir ? { path: join(options.authDir, "cloud-auth.json") } : { env })
|
|
81424
|
+
});
|
|
81425
|
+
}
|
|
81426
|
+
function cloudCredentialsFromRemoteProfile(status, credential) {
|
|
81427
|
+
return {
|
|
81428
|
+
version: 2,
|
|
81429
|
+
cloudUrl: status.hostUrl,
|
|
81430
|
+
workspaceId: status.workspaceId ?? "",
|
|
81431
|
+
...status.workspaceSlug ? { workspaceSlug: status.workspaceSlug } : {},
|
|
81432
|
+
accessToken: credential.accessToken ?? "",
|
|
81433
|
+
refreshToken: credential.refreshToken ?? "",
|
|
81434
|
+
expiresAt: credential.expiresAt ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
81435
|
+
scope: credential.scope,
|
|
81436
|
+
tokenType: credential.tokenType,
|
|
81437
|
+
deviceName: status.clientLabel,
|
|
81438
|
+
createdAt: status.createdAt,
|
|
81439
|
+
lastRefreshAt: status.updatedAt
|
|
81440
|
+
};
|
|
81441
|
+
}
|
|
81442
|
+
var FileRemoteProfileStore = class {
|
|
81443
|
+
root;
|
|
81444
|
+
credentials;
|
|
81445
|
+
legacyCloudAuthStore;
|
|
81446
|
+
constructor(options = {}) {
|
|
81447
|
+
this.root = options.root ?? join(DEFAULT_AUTH_DIR, "remote-profiles");
|
|
81448
|
+
this.credentials = options.credentials ?? new FileRemoteCredentialStore({ root: join(this.root, "credentials") });
|
|
81449
|
+
this.legacyCloudAuthStore = options.legacyCloudAuthStore;
|
|
81450
|
+
}
|
|
81451
|
+
async saveSelfHostedProfile(input) {
|
|
81452
|
+
return await this.withMutationLock(async () => await this.writeSelfHostedProfile(input));
|
|
81226
81453
|
}
|
|
81454
|
+
async getSelfHostedProfileStatus(input) {
|
|
81455
|
+
const key = remoteProfileKey({
|
|
81456
|
+
kind: "self-hosted",
|
|
81457
|
+
hostUrl: normalizeRemoteProfileHostUrl(input.hostUrl)
|
|
81458
|
+
});
|
|
81459
|
+
return this.statusByKey(key, false);
|
|
81460
|
+
}
|
|
81461
|
+
async logoutSelfHostedProfile(input) {
|
|
81462
|
+
return await this.withMutationLock(async () => {
|
|
81463
|
+
const key = remoteProfileKey({
|
|
81464
|
+
kind: "self-hosted",
|
|
81465
|
+
hostUrl: normalizeRemoteProfileHostUrl(input.hostUrl)
|
|
81466
|
+
});
|
|
81467
|
+
if (!this.readProfile(key)) return false;
|
|
81468
|
+
await this.credentials.delete(key);
|
|
81469
|
+
rmSync(this.profilePath(key), { force: true });
|
|
81470
|
+
return true;
|
|
81471
|
+
});
|
|
81472
|
+
}
|
|
81473
|
+
async refreshSelfHostedProfileIfNeeded(input) {
|
|
81474
|
+
const key = remoteProfileKey({
|
|
81475
|
+
kind: "self-hosted",
|
|
81476
|
+
hostUrl: normalizeRemoteProfileHostUrl(input.hostUrl)
|
|
81477
|
+
});
|
|
81478
|
+
return await this.withRefreshLock(key, async () => {
|
|
81479
|
+
const snapshot = await this.withMutationLock(async () => this.selfHostedRefreshSnapshot(key, input));
|
|
81480
|
+
if (!snapshot || !snapshot.needsRefresh) return snapshot?.result;
|
|
81481
|
+
const refreshed = await input.refresh(snapshot.result.status, snapshot.result.credential);
|
|
81482
|
+
return await this.withMutationLock(async () => {
|
|
81483
|
+
const current = await this.selfHostedRefreshSnapshot(key, input);
|
|
81484
|
+
if (!current || !current.needsRefresh) return current?.result;
|
|
81485
|
+
const refreshedStatus = await this.writeSelfHostedProfile(refreshed);
|
|
81486
|
+
const refreshedCredential = await this.credentials.load(refreshedStatus.key);
|
|
81487
|
+
if (!refreshedCredential?.accessToken) return void 0;
|
|
81488
|
+
return {
|
|
81489
|
+
status: refreshedStatus,
|
|
81490
|
+
credential: refreshedCredential
|
|
81491
|
+
};
|
|
81492
|
+
});
|
|
81493
|
+
});
|
|
81494
|
+
}
|
|
81495
|
+
async refreshCloudProfileIfNeeded(input) {
|
|
81496
|
+
const snapshot = await this.withMutationLock(async () => this.cloudRefreshSnapshot(input));
|
|
81497
|
+
if (!snapshot || !snapshot.needsRefresh) return snapshot?.result;
|
|
81498
|
+
return await this.withRefreshLock(snapshot.result.status.key, async () => {
|
|
81499
|
+
const lockedSnapshot = await this.withMutationLock(async () => this.cloudRefreshSnapshot(input));
|
|
81500
|
+
if (!lockedSnapshot || !lockedSnapshot.needsRefresh) return lockedSnapshot?.result;
|
|
81501
|
+
const refreshed = await input.refresh(lockedSnapshot.result.status, lockedSnapshot.result.credential);
|
|
81502
|
+
return await this.withMutationLock(async () => {
|
|
81503
|
+
const current = await this.cloudRefreshSnapshot(input);
|
|
81504
|
+
if (!current || !current.needsRefresh) return current?.result;
|
|
81505
|
+
const refreshedStatus = await this.writeCloudProfile(refreshed, { select: current.result.status.selected });
|
|
81506
|
+
const refreshedCredential = await this.credentials.load(refreshedStatus.key);
|
|
81507
|
+
if (!refreshedCredential?.accessToken) return void 0;
|
|
81508
|
+
return {
|
|
81509
|
+
status: refreshedStatus,
|
|
81510
|
+
credential: refreshedCredential
|
|
81511
|
+
};
|
|
81512
|
+
});
|
|
81513
|
+
});
|
|
81514
|
+
}
|
|
81515
|
+
async saveCloudProfile(input) {
|
|
81516
|
+
return await this.withMutationLock(async () => await this.writeCloudProfile(input));
|
|
81517
|
+
}
|
|
81518
|
+
async getCloudProfileStatus(input) {
|
|
81519
|
+
const hostUrl = normalizeRemoteProfileHostUrl(input.hostUrl);
|
|
81520
|
+
const workspace = input.workspace ?? hostedCloudWorkspaceFromRemoteUrl(input.hostUrl);
|
|
81521
|
+
if (workspace) {
|
|
81522
|
+
const found = await this.findCloudStatus(hostUrl, workspace);
|
|
81523
|
+
if (found) return found;
|
|
81524
|
+
return this.migrateLegacyCloudProfile(hostUrl, workspace);
|
|
81525
|
+
}
|
|
81526
|
+
const selected = this.readSelectedWorkspace(hostUrl);
|
|
81527
|
+
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.");
|
|
81529
|
+
return this.migrateLegacyCloudProfile(hostUrl);
|
|
81530
|
+
}
|
|
81531
|
+
async listCloudProfileStatuses(hostUrlInput) {
|
|
81532
|
+
const hostUrl = normalizeRemoteProfileHostUrl(hostUrlInput);
|
|
81533
|
+
const selected = this.readSelectedWorkspace(hostUrl)?.profileKey;
|
|
81534
|
+
return (await Promise.all(this.listProfilesForHost(hostUrl).map(async (profile) => this.statusFor(profile, void 0, profile.key === selected)))).sort((left, right) => (left.workspaceSlug ?? left.workspaceId ?? "").localeCompare(right.workspaceSlug ?? right.workspaceId ?? ""));
|
|
81535
|
+
}
|
|
81536
|
+
async listProfileStatuses() {
|
|
81537
|
+
const dir = this.profilesDir();
|
|
81538
|
+
if (!existsSync(dir)) return [];
|
|
81539
|
+
return (await Promise.all(readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => this.readProfileFile(join(dir, entry.name))).filter((profile) => Boolean(profile)).map(async (profile) => {
|
|
81540
|
+
const selected = profile.kind === "cloud" ? this.readSelectedWorkspace(profile.hostUrl)?.profileKey === profile.key : false;
|
|
81541
|
+
return await this.statusFor(profile, void 0, selected);
|
|
81542
|
+
}))).sort((left, right) => {
|
|
81543
|
+
const host = left.hostUrl.localeCompare(right.hostUrl);
|
|
81544
|
+
if (host !== 0) return host;
|
|
81545
|
+
return (left.workspaceSlug ?? left.workspaceId ?? "").localeCompare(right.workspaceSlug ?? right.workspaceId ?? "");
|
|
81546
|
+
});
|
|
81547
|
+
}
|
|
81548
|
+
async logoutCloudProfile(input) {
|
|
81549
|
+
return await this.withMutationLock(async () => {
|
|
81550
|
+
const hostUrl = normalizeRemoteProfileHostUrl(input.hostUrl);
|
|
81551
|
+
const workspace = input.workspace ?? hostedCloudWorkspaceFromRemoteUrl(input.hostUrl);
|
|
81552
|
+
const selected = this.readSelectedWorkspace(hostUrl);
|
|
81553
|
+
let key;
|
|
81554
|
+
if (workspace) key = this.listProfilesForHost(hostUrl).find((profile) => profileMatchesWorkspace(profile, workspace))?.key;
|
|
81555
|
+
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.");
|
|
81557
|
+
if (!key) return false;
|
|
81558
|
+
const profile = this.readProfile(key);
|
|
81559
|
+
await this.credentials.delete(key);
|
|
81560
|
+
rmSync(this.profilePath(key), { force: true });
|
|
81561
|
+
if (selected?.profileKey === key) rmSync(this.selectedWorkspacePath(hostUrl), { force: true });
|
|
81562
|
+
await this.clearMatchingLegacyCloudAuth(hostUrl, profile);
|
|
81563
|
+
return true;
|
|
81564
|
+
});
|
|
81565
|
+
}
|
|
81566
|
+
async clearSelectedCloudWorkspace(hostUrlInput) {
|
|
81567
|
+
return await this.withMutationLock(async () => {
|
|
81568
|
+
const path = this.selectedWorkspacePath(normalizeRemoteProfileHostUrl(hostUrlInput));
|
|
81569
|
+
if (!existsSync(path)) return false;
|
|
81570
|
+
rmSync(path, { force: true });
|
|
81571
|
+
return true;
|
|
81572
|
+
});
|
|
81573
|
+
}
|
|
81574
|
+
async selfHostedRefreshSnapshot(key, input) {
|
|
81575
|
+
const profile = this.readProfile(key);
|
|
81576
|
+
if (!profile) return void 0;
|
|
81577
|
+
const credential = await this.credentials.load(key);
|
|
81578
|
+
if (!credential?.accessToken) return void 0;
|
|
81579
|
+
const status = await this.statusFor(profile, credential, false);
|
|
81580
|
+
return {
|
|
81581
|
+
needsRefresh: input.needsRefresh(credential),
|
|
81582
|
+
result: {
|
|
81583
|
+
status,
|
|
81584
|
+
credential
|
|
81585
|
+
}
|
|
81586
|
+
};
|
|
81587
|
+
}
|
|
81588
|
+
async cloudRefreshSnapshot(input) {
|
|
81589
|
+
const hostUrl = normalizeRemoteProfileHostUrl(input.hostUrl);
|
|
81590
|
+
const workspace = input.workspace ?? hostedCloudWorkspaceFromRemoteUrl(input.hostUrl);
|
|
81591
|
+
let status;
|
|
81592
|
+
if (workspace) status = await this.findCloudStatus(hostUrl, workspace);
|
|
81593
|
+
else {
|
|
81594
|
+
const selected = this.readSelectedWorkspace(hostUrl);
|
|
81595
|
+
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.");
|
|
81597
|
+
}
|
|
81598
|
+
if (!status) return void 0;
|
|
81599
|
+
const credential = await this.credentials.load(status.key);
|
|
81600
|
+
if (!credential?.accessToken) return void 0;
|
|
81601
|
+
return {
|
|
81602
|
+
needsRefresh: input.needsRefresh(credential),
|
|
81603
|
+
result: {
|
|
81604
|
+
status,
|
|
81605
|
+
credential
|
|
81606
|
+
}
|
|
81607
|
+
};
|
|
81608
|
+
}
|
|
81609
|
+
async findCloudStatus(hostUrl, workspace) {
|
|
81610
|
+
const selected = this.readSelectedWorkspace(hostUrl)?.profileKey;
|
|
81611
|
+
const profile = this.listProfilesForHost(hostUrl).find((candidate) => profileMatchesWorkspace(candidate, workspace));
|
|
81612
|
+
if (!profile) return void 0;
|
|
81613
|
+
return this.statusFor(profile, void 0, profile.key === selected);
|
|
81614
|
+
}
|
|
81615
|
+
async migrateLegacyCloudProfile(hostUrl, workspace) {
|
|
81616
|
+
const legacy = await this.legacyCloudAuthStore?.load();
|
|
81617
|
+
if (!legacy) return void 0;
|
|
81618
|
+
if (normalizeRemoteProfileHostUrl(legacy.cloudUrl) !== hostUrl) return void 0;
|
|
81619
|
+
if (workspace && workspace !== legacy.workspaceSlug && workspace !== legacy.workspaceId) return;
|
|
81620
|
+
return this.saveCloudProfile({
|
|
81621
|
+
hostUrl,
|
|
81622
|
+
workspaceId: legacy.workspaceId,
|
|
81623
|
+
...legacy.workspaceSlug ? { workspaceSlug: legacy.workspaceSlug } : {},
|
|
81624
|
+
clientLabel: legacy.deviceName,
|
|
81625
|
+
credentials: legacyCredential(legacy),
|
|
81626
|
+
now: legacy.createdAt ? new Date(legacy.createdAt) : void 0
|
|
81627
|
+
});
|
|
81628
|
+
}
|
|
81629
|
+
async clearMatchingLegacyCloudAuth(hostUrl, profile) {
|
|
81630
|
+
const legacy = await this.legacyCloudAuthStore?.load();
|
|
81631
|
+
if (!legacy) return;
|
|
81632
|
+
if (normalizeRemoteProfileHostUrl(legacy.cloudUrl) !== hostUrl) return;
|
|
81633
|
+
if (!profile) return;
|
|
81634
|
+
const workspaceIdMatches = legacy.workspaceId === profile.workspaceId;
|
|
81635
|
+
const workspaceSlugMatches = Boolean(legacy.workspaceSlug && profile.workspaceSlug && legacy.workspaceSlug === profile.workspaceSlug);
|
|
81636
|
+
if (!workspaceIdMatches && !workspaceSlugMatches) return;
|
|
81637
|
+
await this.legacyCloudAuthStore?.clear();
|
|
81638
|
+
}
|
|
81639
|
+
async statusByKey(key, selected) {
|
|
81640
|
+
const profile = this.readProfile(key);
|
|
81641
|
+
if (!profile) return void 0;
|
|
81642
|
+
return this.statusFor(profile, void 0, selected);
|
|
81643
|
+
}
|
|
81644
|
+
async statusFor(profile, credential, selected) {
|
|
81645
|
+
const build = (loadedCredential) => remoteProfileStatus({
|
|
81646
|
+
kind: profile.kind,
|
|
81647
|
+
key: profile.key,
|
|
81648
|
+
hostUrl: profile.hostUrl,
|
|
81649
|
+
workspaceId: profile.workspaceId,
|
|
81650
|
+
workspaceSlug: profile.workspaceSlug,
|
|
81651
|
+
clientId: profile.clientId,
|
|
81652
|
+
clientLabel: profile.clientLabel,
|
|
81653
|
+
createdAt: profile.createdAt,
|
|
81654
|
+
updatedAt: profile.updatedAt,
|
|
81655
|
+
selected,
|
|
81656
|
+
credential: loadedCredential
|
|
81657
|
+
});
|
|
81658
|
+
if (credential !== void 0) return build(credential);
|
|
81659
|
+
return build(await this.credentials.load(profile.key));
|
|
81660
|
+
}
|
|
81661
|
+
async writeSelfHostedProfile(input) {
|
|
81662
|
+
const hostUrl = normalizeRemoteProfileHostUrl(input.hostUrl);
|
|
81663
|
+
const key = remoteProfileKey({
|
|
81664
|
+
kind: "self-hosted",
|
|
81665
|
+
hostUrl
|
|
81666
|
+
});
|
|
81667
|
+
const now = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
81668
|
+
const existing = this.readProfile(key);
|
|
81669
|
+
const profile = {
|
|
81670
|
+
version: 1,
|
|
81671
|
+
kind: "self-hosted",
|
|
81672
|
+
key,
|
|
81673
|
+
hostUrl,
|
|
81674
|
+
clientId: input.clientId,
|
|
81675
|
+
...input.clientLabel ? { clientLabel: input.clientLabel } : {},
|
|
81676
|
+
createdAt: existing?.createdAt ?? now,
|
|
81677
|
+
updatedAt: now
|
|
81678
|
+
};
|
|
81679
|
+
await this.credentials.save(key, input.credentials);
|
|
81680
|
+
this.writeJson(this.profilePath(key), profile);
|
|
81681
|
+
return await this.statusFor(profile, input.credentials, false);
|
|
81682
|
+
}
|
|
81683
|
+
async writeCloudProfile(input, options = {}) {
|
|
81684
|
+
const hostUrl = normalizeRemoteProfileHostUrl(input.hostUrl);
|
|
81685
|
+
const workspace = cloudWorkspace(input);
|
|
81686
|
+
const key = remoteProfileKey({
|
|
81687
|
+
kind: "cloud",
|
|
81688
|
+
hostUrl,
|
|
81689
|
+
workspace
|
|
81690
|
+
});
|
|
81691
|
+
const now = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
81692
|
+
const existing = this.readProfile(key);
|
|
81693
|
+
const select = options.select ?? true;
|
|
81694
|
+
const profile = {
|
|
81695
|
+
version: 1,
|
|
81696
|
+
kind: "cloud",
|
|
81697
|
+
key,
|
|
81698
|
+
hostUrl,
|
|
81699
|
+
workspaceId: input.workspaceId,
|
|
81700
|
+
...input.workspaceSlug ? { workspaceSlug: input.workspaceSlug } : {},
|
|
81701
|
+
...input.clientLabel ? { clientLabel: input.clientLabel } : {},
|
|
81702
|
+
createdAt: existing?.createdAt ?? now,
|
|
81703
|
+
updatedAt: now
|
|
81704
|
+
};
|
|
81705
|
+
await this.credentials.save(key, input.credentials);
|
|
81706
|
+
this.writeJson(this.profilePath(key), profile);
|
|
81707
|
+
if (select) this.writeJson(this.selectedWorkspacePath(hostUrl), {
|
|
81708
|
+
version: 1,
|
|
81709
|
+
hostUrl,
|
|
81710
|
+
workspace,
|
|
81711
|
+
profileKey: key,
|
|
81712
|
+
selectedAt: now
|
|
81713
|
+
});
|
|
81714
|
+
return await this.statusFor(profile, input.credentials, select);
|
|
81715
|
+
}
|
|
81716
|
+
listProfilesForHost(hostUrl) {
|
|
81717
|
+
const dir = this.profilesDir();
|
|
81718
|
+
if (!existsSync(dir)) return [];
|
|
81719
|
+
return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => this.readProfileFile(join(dir, entry.name))).filter((profile) => Boolean(profile)).filter((profile) => profile.kind === "cloud" && profile.hostUrl === hostUrl);
|
|
81720
|
+
}
|
|
81721
|
+
readProfile(key) {
|
|
81722
|
+
return this.readProfileFile(this.profilePath(key));
|
|
81723
|
+
}
|
|
81724
|
+
readProfileFile(path) {
|
|
81725
|
+
if (!existsSync(path)) return void 0;
|
|
81726
|
+
return parseStoredRemoteProfile(JSON.parse(readFileSync(path, "utf8")));
|
|
81727
|
+
}
|
|
81728
|
+
readSelectedWorkspace(hostUrl) {
|
|
81729
|
+
const path = this.selectedWorkspacePath(hostUrl);
|
|
81730
|
+
if (!existsSync(path)) return void 0;
|
|
81731
|
+
return parseSelectedCloudWorkspace(JSON.parse(readFileSync(path, "utf8")), hostUrl);
|
|
81732
|
+
}
|
|
81733
|
+
profilePath(key) {
|
|
81734
|
+
return join(this.profilesDir(), `${encodeURIComponent(key)}.json`);
|
|
81735
|
+
}
|
|
81736
|
+
selectedWorkspacePath(hostUrl) {
|
|
81737
|
+
return join(this.selectionsDir(), `${encodeURIComponent(selectedWorkspaceKey(hostUrl))}.json`);
|
|
81738
|
+
}
|
|
81739
|
+
profilesDir() {
|
|
81740
|
+
return join(this.root, "profiles");
|
|
81741
|
+
}
|
|
81742
|
+
selectionsDir() {
|
|
81743
|
+
return join(this.root, "selections");
|
|
81744
|
+
}
|
|
81745
|
+
writeJson(path, value) {
|
|
81746
|
+
const directory = dirname(path);
|
|
81747
|
+
mkdirSync(directory, {
|
|
81748
|
+
recursive: true,
|
|
81749
|
+
mode: 448
|
|
81750
|
+
});
|
|
81751
|
+
try {
|
|
81752
|
+
chmodSync(directory, 448);
|
|
81753
|
+
} catch {}
|
|
81754
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
81755
|
+
writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
|
|
81756
|
+
try {
|
|
81757
|
+
chmodSync(tempPath, 384);
|
|
81758
|
+
} catch {}
|
|
81759
|
+
renameSync(tempPath, path);
|
|
81760
|
+
}
|
|
81761
|
+
async withMutationLock(operation) {
|
|
81762
|
+
await this.acquireLock(this.lockPath(), "Remote Profile store is locked.");
|
|
81763
|
+
try {
|
|
81764
|
+
return await operation();
|
|
81765
|
+
} finally {
|
|
81766
|
+
this.releaseLock(this.lockPath());
|
|
81767
|
+
}
|
|
81768
|
+
}
|
|
81769
|
+
async withRefreshLock(key, operation) {
|
|
81770
|
+
const lockPath = this.refreshLockPath(key);
|
|
81771
|
+
await this.acquireLock(lockPath, "Remote Profile refresh is locked.");
|
|
81772
|
+
try {
|
|
81773
|
+
return await operation();
|
|
81774
|
+
} finally {
|
|
81775
|
+
this.releaseLock(lockPath);
|
|
81776
|
+
}
|
|
81777
|
+
}
|
|
81778
|
+
async acquireLock(lockPath, message) {
|
|
81779
|
+
mkdirSync(this.root, {
|
|
81780
|
+
recursive: true,
|
|
81781
|
+
mode: 448
|
|
81782
|
+
});
|
|
81783
|
+
mkdirSync(dirname(lockPath), {
|
|
81784
|
+
recursive: true,
|
|
81785
|
+
mode: 448
|
|
81786
|
+
});
|
|
81787
|
+
const started = Date.now();
|
|
81788
|
+
while (true) try {
|
|
81789
|
+
mkdirSync(lockPath, {
|
|
81790
|
+
recursive: false,
|
|
81791
|
+
mode: 448
|
|
81792
|
+
});
|
|
81793
|
+
return;
|
|
81794
|
+
} catch (error) {
|
|
81795
|
+
if (isFileExistsError(error) && this.clearStaleLock(lockPath)) continue;
|
|
81796
|
+
if (!isFileExistsError(error) || Date.now() - started >= PROFILE_LOCK_TIMEOUT_MS) throw new CapletsError("SERVER_UNAVAILABLE", message);
|
|
81797
|
+
await sleep(10);
|
|
81798
|
+
}
|
|
81799
|
+
}
|
|
81800
|
+
releaseLock(lockPath) {
|
|
81801
|
+
rmSync(lockPath, {
|
|
81802
|
+
recursive: true,
|
|
81803
|
+
force: true
|
|
81804
|
+
});
|
|
81805
|
+
}
|
|
81806
|
+
clearStaleLock(lockPath) {
|
|
81807
|
+
try {
|
|
81808
|
+
if (Date.now() - statSync(lockPath).mtimeMs < PROFILE_LOCK_TIMEOUT_MS) return false;
|
|
81809
|
+
rmSync(lockPath, {
|
|
81810
|
+
recursive: true,
|
|
81811
|
+
force: true
|
|
81812
|
+
});
|
|
81813
|
+
return true;
|
|
81814
|
+
} catch {
|
|
81815
|
+
return false;
|
|
81816
|
+
}
|
|
81817
|
+
}
|
|
81818
|
+
lockPath() {
|
|
81819
|
+
return join(this.root, PROFILE_LOCK_DIR);
|
|
81820
|
+
}
|
|
81821
|
+
refreshLockPath(key) {
|
|
81822
|
+
return join(this.root, PROFILE_REFRESH_LOCK_DIR, `${encodeURIComponent(key)}.lock`);
|
|
81823
|
+
}
|
|
81824
|
+
};
|
|
81825
|
+
async function sleep(ms) {
|
|
81826
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
81827
|
+
}
|
|
81828
|
+
function isFileExistsError(error) {
|
|
81829
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
81830
|
+
}
|
|
81831
|
+
function cloudWorkspace(input) {
|
|
81832
|
+
return input.workspaceSlug ?? input.workspaceId ?? hostedCloudWorkspaceFromRemoteUrl(input.hostUrl) ?? "";
|
|
81833
|
+
}
|
|
81834
|
+
function profileMatchesWorkspace(profile, workspace) {
|
|
81835
|
+
return profile.workspaceSlug === workspace || profile.workspaceId === workspace;
|
|
81836
|
+
}
|
|
81837
|
+
function legacyCredential(credentials) {
|
|
81838
|
+
return {
|
|
81839
|
+
accessToken: credentials.accessToken,
|
|
81840
|
+
refreshToken: credentials.refreshToken,
|
|
81841
|
+
expiresAt: credentials.expiresAt,
|
|
81842
|
+
...credentials.scope ? { scope: credentials.scope } : {},
|
|
81843
|
+
...credentials.tokenType ? { tokenType: credentials.tokenType } : {}
|
|
81844
|
+
};
|
|
81845
|
+
}
|
|
81846
|
+
function parseStoredRemoteProfile(value) {
|
|
81847
|
+
if (!isRecord$1(value)) return void 0;
|
|
81848
|
+
if (value.version !== 1) return void 0;
|
|
81849
|
+
if (value.kind !== "cloud" && value.kind !== "self-hosted") return void 0;
|
|
81850
|
+
if (typeof value.key !== "string" || typeof value.hostUrl !== "string" || typeof value.createdAt !== "string" || typeof value.updatedAt !== "string") return;
|
|
81851
|
+
if (value.kind === "cloud" && typeof value.workspaceId !== "string") return void 0;
|
|
81852
|
+
if (value.kind === "self-hosted" && typeof value.clientId !== "string") return void 0;
|
|
81853
|
+
return {
|
|
81854
|
+
version: 1,
|
|
81855
|
+
kind: value.kind,
|
|
81856
|
+
key: value.key,
|
|
81857
|
+
hostUrl: value.hostUrl,
|
|
81858
|
+
...typeof value.workspaceId === "string" ? { workspaceId: value.workspaceId } : {},
|
|
81859
|
+
...typeof value.workspaceSlug === "string" ? { workspaceSlug: value.workspaceSlug } : {},
|
|
81860
|
+
...typeof value.clientId === "string" ? { clientId: value.clientId } : {},
|
|
81861
|
+
...typeof value.clientLabel === "string" ? { clientLabel: value.clientLabel } : {},
|
|
81862
|
+
createdAt: value.createdAt,
|
|
81863
|
+
updatedAt: value.updatedAt
|
|
81864
|
+
};
|
|
81865
|
+
}
|
|
81866
|
+
function parseSelectedCloudWorkspace(value, expectedHostUrl) {
|
|
81867
|
+
if (!isRecord$1(value)) return void 0;
|
|
81868
|
+
if (value.version !== 1 || value.hostUrl !== expectedHostUrl || typeof value.workspace !== "string" || typeof value.profileKey !== "string" || typeof value.selectedAt !== "string") return;
|
|
81869
|
+
return {
|
|
81870
|
+
version: 1,
|
|
81871
|
+
hostUrl: value.hostUrl,
|
|
81872
|
+
workspace: value.workspace,
|
|
81873
|
+
profileKey: value.profileKey,
|
|
81874
|
+
selectedAt: value.selectedAt
|
|
81875
|
+
};
|
|
81876
|
+
}
|
|
81877
|
+
function isRecord$1(value) {
|
|
81878
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
81227
81879
|
}
|
|
81228
81880
|
//#endregion
|
|
81229
81881
|
//#region src/remote/selection.ts
|
|
81882
|
+
const SELF_HOSTED_REFRESH_TIMEOUT_MS = 15e3;
|
|
81230
81883
|
async function resolveRemoteSelection(input = {}, env = process.env) {
|
|
81231
81884
|
const modeValue = input.mode ?? env.CAPLETS_MODE;
|
|
81232
81885
|
const mode = resolveRemoteMode({
|
|
@@ -81234,43 +81887,112 @@ async function resolveRemoteSelection(input = {}, env = process.env) {
|
|
|
81234
81887
|
...input.remoteUrl !== void 0 ? { remoteUrl: input.remoteUrl } : {}
|
|
81235
81888
|
}, env);
|
|
81236
81889
|
if (mode.mode === "local") throw new CapletsError("REQUEST_INVALID", "caplets attach requires a remote upstream; set CAPLETS_REMOTE_URL or use caplets serve for local-only MCP.");
|
|
81237
|
-
if (mode.mode === "remote")
|
|
81238
|
-
|
|
81239
|
-
|
|
81240
|
-
|
|
81241
|
-
|
|
81242
|
-
|
|
81243
|
-
|
|
81244
|
-
|
|
81245
|
-
|
|
81246
|
-
|
|
81247
|
-
|
|
81248
|
-
|
|
81249
|
-
|
|
81250
|
-
|
|
81251
|
-
|
|
81252
|
-
|
|
81253
|
-
|
|
81254
|
-
|
|
81255
|
-
|
|
81256
|
-
|
|
81257
|
-
|
|
81258
|
-
|
|
81259
|
-
|
|
81260
|
-
|
|
81261
|
-
|
|
81262
|
-
|
|
81890
|
+
if (mode.mode === "remote") {
|
|
81891
|
+
const remoteUrl = input.remoteUrl ?? env.CAPLETS_REMOTE_URL;
|
|
81892
|
+
if (!remoteUrl) throw new CapletsError("REQUEST_INVALID", "CAPLETS_REMOTE_URL or remoteUrl is required.");
|
|
81893
|
+
const credential = (await createRemoteProfileStore({
|
|
81894
|
+
authDir: input.authDir,
|
|
81895
|
+
env
|
|
81896
|
+
}).refreshSelfHostedProfileIfNeeded({
|
|
81897
|
+
hostUrl: remoteUrl,
|
|
81898
|
+
needsRefresh: (credential) => credentialsNeedRefresh({ expiresAt: credential.expiresAt ?? "" }),
|
|
81899
|
+
refresh: async (status, credential) => {
|
|
81900
|
+
if (!credential.refreshToken || !status.clientId) throw remoteLoginRequired(remoteUrl);
|
|
81901
|
+
const refreshed = await refreshSelfHostedCredentials(remoteUrl, credential.refreshToken, input.fetch ? { fetch: input.fetch } : {});
|
|
81902
|
+
return {
|
|
81903
|
+
hostUrl: refreshed.hostUrl ?? remoteUrl,
|
|
81904
|
+
clientId: refreshed.clientId,
|
|
81905
|
+
clientLabel: refreshed.clientLabel ?? status.clientLabel,
|
|
81906
|
+
credentials: {
|
|
81907
|
+
accessToken: refreshed.accessToken,
|
|
81908
|
+
refreshToken: refreshed.refreshToken,
|
|
81909
|
+
expiresAt: refreshed.expiresAt,
|
|
81910
|
+
tokenType: refreshed.tokenType
|
|
81911
|
+
}
|
|
81912
|
+
};
|
|
81913
|
+
}
|
|
81914
|
+
}))?.credential;
|
|
81915
|
+
if (!credential?.accessToken) {
|
|
81916
|
+
const normalizedUrl = normalizeRemoteProfileHostUrl(remoteUrl);
|
|
81917
|
+
throw new ProjectBindingError({
|
|
81918
|
+
code: "remote_credentials_required",
|
|
81919
|
+
message: `Remote Login required for ${normalizedUrl}.`,
|
|
81920
|
+
recoveryCommand: `caplets remote login ${normalizedUrl}`
|
|
81921
|
+
});
|
|
81922
|
+
}
|
|
81923
|
+
return {
|
|
81924
|
+
kind: "self_hosted_remote",
|
|
81925
|
+
remote: resolveCapletsRemote({
|
|
81926
|
+
url: remoteUrl,
|
|
81927
|
+
token: credential.accessToken,
|
|
81928
|
+
...input.workspace !== void 0 ? { workspace: input.workspace } : {},
|
|
81929
|
+
...input.fetch !== void 0 ? { fetch: input.fetch } : {}
|
|
81930
|
+
}, env),
|
|
81931
|
+
...credential.expiresAt ? { credentialExpiresAt: credential.expiresAt } : {}
|
|
81263
81932
|
};
|
|
81264
|
-
await store.save(credentials);
|
|
81265
81933
|
}
|
|
81266
|
-
const
|
|
81267
|
-
|
|
81934
|
+
const store = createRemoteProfileStore({
|
|
81935
|
+
authDir: input.authDir,
|
|
81936
|
+
env
|
|
81937
|
+
});
|
|
81268
81938
|
const remoteUrl = input.remoteUrl ?? env.CAPLETS_REMOTE_URL;
|
|
81269
81939
|
if (!remoteUrl) throw new CapletsError("REQUEST_INVALID", "CAPLETS_MODE=cloud requires CAPLETS_REMOTE_URL or remoteUrl.");
|
|
81270
81940
|
const workspaceFromRemoteUrl = hostedCloudWorkspaceFromRemoteUrl(remoteUrl);
|
|
81941
|
+
const explicitWorkspace = input.workspace ?? (workspaceFromRemoteUrl ? void 0 : env.CAPLETS_REMOTE_WORKSPACE);
|
|
81942
|
+
const profileWorkspace = workspaceFromRemoteUrl ?? explicitWorkspace;
|
|
81943
|
+
const normalizedRemoteUrl = normalizeRemoteProfileHostUrl(remoteUrl);
|
|
81944
|
+
let status = await store.getCloudProfileStatus({
|
|
81945
|
+
hostUrl: normalizedRemoteUrl,
|
|
81946
|
+
workspace: profileWorkspace
|
|
81947
|
+
});
|
|
81948
|
+
if (!status && profileWorkspace) status = await store.getCloudProfileStatus({ hostUrl: normalizedRemoteUrl });
|
|
81949
|
+
let credential = status ? await store.credentials.load(status.key) : void 0;
|
|
81950
|
+
if (!status || !credential?.accessToken) throw projectBindingError("cloud_auth_required");
|
|
81951
|
+
let credentials = cloudCredentialsFromRemoteProfile(status, credential);
|
|
81952
|
+
if (credentialsNeedRefresh(credentials)) {
|
|
81953
|
+
const refreshed = await store.refreshCloudProfileIfNeeded({
|
|
81954
|
+
hostUrl: normalizedRemoteUrl,
|
|
81955
|
+
workspace: profileWorkspace,
|
|
81956
|
+
needsRefresh: (candidate) => credentialsNeedRefresh({ expiresAt: candidate.expiresAt ?? "" }),
|
|
81957
|
+
refresh: async (candidateStatus, candidateCredential) => {
|
|
81958
|
+
const candidateCredentials = cloudCredentialsFromRemoteProfile(candidateStatus, candidateCredential);
|
|
81959
|
+
if (!candidateCredentials.refreshToken) throw projectBindingError("cloud_auth_required");
|
|
81960
|
+
const refreshedCredentials = await new CloudAuthClient({
|
|
81961
|
+
cloudUrl: candidateCredentials.cloudUrl,
|
|
81962
|
+
...input.fetch !== void 0 ? { fetch: input.fetch } : {}
|
|
81963
|
+
}).refresh({ refreshToken: candidateCredentials.refreshToken });
|
|
81964
|
+
const nextCredentials = {
|
|
81965
|
+
...candidateCredentials,
|
|
81966
|
+
...refreshedCredentials,
|
|
81967
|
+
refreshToken: refreshedCredentials.refreshToken ?? candidateCredentials.refreshToken,
|
|
81968
|
+
createdAt: candidateCredentials.createdAt,
|
|
81969
|
+
lastRefreshAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
81970
|
+
};
|
|
81971
|
+
return {
|
|
81972
|
+
hostUrl: nextCredentials.cloudUrl,
|
|
81973
|
+
workspaceId: nextCredentials.workspaceId,
|
|
81974
|
+
...nextCredentials.workspaceSlug ? { workspaceSlug: nextCredentials.workspaceSlug } : {},
|
|
81975
|
+
clientLabel: nextCredentials.deviceName,
|
|
81976
|
+
credentials: {
|
|
81977
|
+
accessToken: nextCredentials.accessToken,
|
|
81978
|
+
refreshToken: nextCredentials.refreshToken,
|
|
81979
|
+
expiresAt: nextCredentials.expiresAt,
|
|
81980
|
+
scope: nextCredentials.scope,
|
|
81981
|
+
tokenType: nextCredentials.tokenType
|
|
81982
|
+
}
|
|
81983
|
+
};
|
|
81984
|
+
}
|
|
81985
|
+
});
|
|
81986
|
+
if (!refreshed?.credential?.accessToken) throw projectBindingError("cloud_auth_required");
|
|
81987
|
+
status = refreshed.status;
|
|
81988
|
+
credential = refreshed.credential;
|
|
81989
|
+
credentials = cloudCredentialsFromRemoteProfile(status, credential);
|
|
81990
|
+
}
|
|
81991
|
+
const selectedWorkspace = credentials.workspaceSlug ?? credentials.workspaceId;
|
|
81992
|
+
if (explicitWorkspace && explicitWorkspace !== credentials.workspaceId && explicitWorkspace !== credentials.workspaceSlug) throw projectBindingError("workspace_switch_required", `Requested workspace ${explicitWorkspace} differs from saved Selected Workspace ${selectedWorkspace}.`);
|
|
81271
81993
|
if (workspaceFromRemoteUrl && workspaceFromRemoteUrl !== credentials.workspaceSlug && workspaceFromRemoteUrl !== credentials.workspaceId) throw projectBindingError("workspace_switch_required", `Requested workspace ${workspaceFromRemoteUrl} differs from saved Selected Workspace ${selectedWorkspace}.`);
|
|
81272
81994
|
const missingScope = requiredHostedCloudAttachScopes().find((scope) => !credentials.scope?.includes(scope));
|
|
81273
|
-
if (missingScope) throw projectBindingError("cloud_auth_required", `Hosted Cloud attach requires Cloud Auth scope ${missingScope}. Run caplets
|
|
81995
|
+
if (missingScope) throw projectBindingError("cloud_auth_required", `Hosted Cloud attach requires Cloud Auth scope ${missingScope}. Run caplets remote login ${credentials.cloudUrl} again.`);
|
|
81274
81996
|
const remote = resolveHostedCloudRemote({
|
|
81275
81997
|
url: remoteUrl,
|
|
81276
81998
|
token: credentials.accessToken,
|
|
@@ -81282,6 +82004,7 @@ async function resolveRemoteSelection(input = {}, env = process.env) {
|
|
|
81282
82004
|
remote,
|
|
81283
82005
|
selectedWorkspace,
|
|
81284
82006
|
credentials,
|
|
82007
|
+
credentialExpiresAt: credentials.expiresAt,
|
|
81285
82008
|
cloudPresence: {
|
|
81286
82009
|
url: remote.baseUrl,
|
|
81287
82010
|
accessToken: credentials.accessToken,
|
|
@@ -81293,6 +82016,74 @@ function credentialsNeedRefresh(credentials) {
|
|
|
81293
82016
|
const expiresAt = Date.parse(credentials.expiresAt);
|
|
81294
82017
|
return Number.isFinite(expiresAt) && expiresAt <= Date.now() + 6e4;
|
|
81295
82018
|
}
|
|
82019
|
+
async function refreshSelfHostedCredentials(remoteUrl, refreshToken, options) {
|
|
82020
|
+
const response = await fetchSelfHostedRefresh(appendBasePath(new URL(normalizeRemoteProfileHostUrl(remoteUrl)), "v1/remote/refresh"), refreshToken, options);
|
|
82021
|
+
if (!response.ok) throw await selfHostedRefreshError(remoteUrl, response);
|
|
82022
|
+
return parseSelfHostedRefreshCredentials(response);
|
|
82023
|
+
}
|
|
82024
|
+
async function selfHostedRefreshError(remoteUrl, response) {
|
|
82025
|
+
const summary = await parseSelfHostedRefreshError(response);
|
|
82026
|
+
if (response.status === 401 || summary?.code === "AUTH_FAILED") return remoteLoginRequired(remoteUrl);
|
|
82027
|
+
if (response.status === 503 || summary?.code === "SERVER_UNAVAILABLE") return new CapletsError("SERVER_UNAVAILABLE", summary?.message ?? "Remote credential refresh is temporarily unavailable.");
|
|
82028
|
+
return new CapletsError("AUTH_REFRESH_FAILED", summary?.message ?? `Remote credential refresh failed with HTTP ${response.status}.`);
|
|
82029
|
+
}
|
|
82030
|
+
async function parseSelfHostedRefreshError(response) {
|
|
82031
|
+
const parsed = await response.clone().json().catch(() => void 0);
|
|
82032
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
82033
|
+
const error = parsed.error;
|
|
82034
|
+
if (!error || typeof error !== "object" || Array.isArray(error)) return void 0;
|
|
82035
|
+
const record = error;
|
|
82036
|
+
return {
|
|
82037
|
+
...typeof record.code === "string" ? { code: record.code } : {},
|
|
82038
|
+
...typeof record.message === "string" ? { message: record.message } : {}
|
|
82039
|
+
};
|
|
82040
|
+
}
|
|
82041
|
+
async function fetchSelfHostedRefresh(refreshUrl, refreshToken, options) {
|
|
82042
|
+
const controller = new AbortController();
|
|
82043
|
+
let timeout;
|
|
82044
|
+
try {
|
|
82045
|
+
const refresh = (options.fetch ?? fetch)(refreshUrl, {
|
|
82046
|
+
method: "POST",
|
|
82047
|
+
headers: { "content-type": "application/json" },
|
|
82048
|
+
body: JSON.stringify({ refreshToken }),
|
|
82049
|
+
signal: controller.signal
|
|
82050
|
+
});
|
|
82051
|
+
const timedOut = new Promise((_resolve, reject) => {
|
|
82052
|
+
timeout = setTimeout(() => {
|
|
82053
|
+
controller.abort();
|
|
82054
|
+
reject(new CapletsError("SERVER_UNAVAILABLE", "Remote credential refresh timed out."));
|
|
82055
|
+
}, SELF_HOSTED_REFRESH_TIMEOUT_MS);
|
|
82056
|
+
});
|
|
82057
|
+
return await Promise.race([refresh, timedOut]);
|
|
82058
|
+
} catch (error) {
|
|
82059
|
+
if (error instanceof CapletsError) throw error;
|
|
82060
|
+
throw new CapletsError("SERVER_UNAVAILABLE", "Remote credential refresh failed.");
|
|
82061
|
+
} finally {
|
|
82062
|
+
if (timeout) clearTimeout(timeout);
|
|
82063
|
+
}
|
|
82064
|
+
}
|
|
82065
|
+
function remoteLoginRequired(remoteUrl) {
|
|
82066
|
+
return new ProjectBindingError({
|
|
82067
|
+
code: "remote_credentials_required",
|
|
82068
|
+
message: `Remote Login required for ${normalizeRemoteProfileHostUrl(remoteUrl)}.`,
|
|
82069
|
+
recoveryCommand: `caplets remote login ${normalizeRemoteProfileHostUrl(remoteUrl)}`
|
|
82070
|
+
});
|
|
82071
|
+
}
|
|
82072
|
+
async function parseSelfHostedRefreshCredentials(response) {
|
|
82073
|
+
const parsed = await response.json();
|
|
82074
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Remote refresh response must be an object.");
|
|
82075
|
+
const record = parsed;
|
|
82076
|
+
if (typeof record.clientId !== "string" || typeof record.accessToken !== "string" || typeof record.refreshToken !== "string") throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Remote refresh response is missing credentials.");
|
|
82077
|
+
return {
|
|
82078
|
+
...typeof record.hostUrl === "string" ? { hostUrl: record.hostUrl } : {},
|
|
82079
|
+
clientId: record.clientId,
|
|
82080
|
+
...typeof record.clientLabel === "string" ? { clientLabel: record.clientLabel } : {},
|
|
82081
|
+
accessToken: record.accessToken,
|
|
82082
|
+
refreshToken: record.refreshToken,
|
|
82083
|
+
...typeof record.tokenType === "string" ? { tokenType: record.tokenType } : {},
|
|
82084
|
+
...typeof record.expiresAt === "string" ? { expiresAt: record.expiresAt } : {}
|
|
82085
|
+
};
|
|
82086
|
+
}
|
|
81296
82087
|
function requiredHostedCloudAttachScopes() {
|
|
81297
82088
|
return HOSTED_CLOUD_AUTH_SCOPES.filter((scope) => scope !== "mcp:tools");
|
|
81298
82089
|
}
|
|
@@ -81303,21 +82094,24 @@ let hasWarnedRemoteProjectBindingFallback = false;
|
|
|
81303
82094
|
function createNativeCapletsService(options = {}) {
|
|
81304
82095
|
const resolved = resolveNativeCapletsServiceOptions(options);
|
|
81305
82096
|
if (resolved.mode === "remote") {
|
|
81306
|
-
|
|
81307
|
-
|
|
81308
|
-
|
|
81309
|
-
|
|
81310
|
-
|
|
81311
|
-
|
|
81312
|
-
|
|
82097
|
+
if (options.remoteClientFactory) {
|
|
82098
|
+
const local = createLocalOverlayService(options);
|
|
82099
|
+
try {
|
|
82100
|
+
return createCompositeRemoteService(resolved.remote, local, options, "self_hosted_remote");
|
|
82101
|
+
} catch (error) {
|
|
82102
|
+
if (options.mode !== "remote") {
|
|
82103
|
+
warnRemoteProjectBindingFallback(options);
|
|
82104
|
+
return local;
|
|
82105
|
+
}
|
|
82106
|
+
local.close().catch((closeError) => {
|
|
82107
|
+
writeErr(options, `Could not close local overlay Caplets service: ${errorMessage(closeError)}\n`);
|
|
82108
|
+
});
|
|
82109
|
+
throw error;
|
|
81313
82110
|
}
|
|
81314
|
-
local.close().catch((closeError) => {
|
|
81315
|
-
writeErr(options, `Could not close local overlay Caplets service: ${errorMessage(closeError)}\n`);
|
|
81316
|
-
});
|
|
81317
|
-
throw error;
|
|
81318
82111
|
}
|
|
82112
|
+
return new ProfileBackedNativeCapletsService(options, resolved.remote, "self_hosted_remote");
|
|
81319
82113
|
}
|
|
81320
|
-
if (resolved.mode === "cloud") return new
|
|
82114
|
+
if (resolved.mode === "cloud") return new ProfileBackedNativeCapletsService(options, resolved.remote, "hosted_cloud");
|
|
81321
82115
|
return new DefaultNativeCapletsService(options);
|
|
81322
82116
|
}
|
|
81323
82117
|
var DefaultNativeCapletsService = class {
|
|
@@ -81678,31 +82472,55 @@ function createLocalOverlayService(options) {
|
|
|
81678
82472
|
return (options.localServiceFactory ?? createDefaultNativeCapletsService)(localOptions);
|
|
81679
82473
|
}
|
|
81680
82474
|
function createCompositeRemoteService(remoteOptions, local, options, authKind) {
|
|
81681
|
-
|
|
81682
|
-
|
|
81683
|
-
|
|
82475
|
+
const { remote, presence } = createCompositeRemoteParts(remoteOptions, local, options, authKind);
|
|
82476
|
+
return new CompositeNativeCapletsService(remote, local, options, presence);
|
|
82477
|
+
}
|
|
82478
|
+
function createCompositeRemoteParts(remoteOptions, local, options, authKind, resolveRuntimeRemoteOptions) {
|
|
82479
|
+
const remote = new RemoteNativeCapletsService({
|
|
82480
|
+
client: createRemoteClient(remoteOptions, options, authKind, resolveRuntimeRemoteOptions),
|
|
82481
|
+
clientFactory: () => createRemoteClient(remoteOptions, options, authKind, resolveRuntimeRemoteOptions),
|
|
81684
82482
|
pollIntervalMs: remoteOptions.pollIntervalMs,
|
|
81685
82483
|
authKind,
|
|
81686
82484
|
...options.writeErr ? { writeErr: options.writeErr } : {}
|
|
81687
|
-
})
|
|
82485
|
+
});
|
|
82486
|
+
const presence = createProjectBindingSessionManager(remoteOptions.cloud, local, options);
|
|
82487
|
+
return {
|
|
82488
|
+
remote,
|
|
82489
|
+
...presence ? { presence } : {}
|
|
82490
|
+
};
|
|
81688
82491
|
}
|
|
81689
|
-
|
|
82492
|
+
function createRemoteClient(remoteOptions, options, authKind, resolveRuntimeRemoteOptions) {
|
|
82493
|
+
if (options.remoteClientFactory) return options.remoteClientFactory(remoteOptions);
|
|
82494
|
+
return createSdkRemoteCapletsClient({
|
|
82495
|
+
...remoteOptions,
|
|
82496
|
+
authKind,
|
|
82497
|
+
...options.writeErr ? { writeErr: options.writeErr } : {},
|
|
82498
|
+
...resolveRuntimeRemoteOptions ? { resolveRuntimeOptions: resolveRuntimeRemoteOptions } : {}
|
|
82499
|
+
});
|
|
82500
|
+
}
|
|
82501
|
+
var ProfileBackedNativeCapletsService = class {
|
|
81690
82502
|
options;
|
|
81691
82503
|
baseRemote;
|
|
82504
|
+
authKind;
|
|
81692
82505
|
local;
|
|
81693
82506
|
listeners = /* @__PURE__ */ new Set();
|
|
81694
82507
|
delegate;
|
|
81695
82508
|
unsubscribeDelegate;
|
|
82509
|
+
remoteSignature;
|
|
82510
|
+
credentialExpiresAt;
|
|
82511
|
+
ensureDelegateCurrentInFlight;
|
|
81696
82512
|
closed = false;
|
|
81697
|
-
constructor(options, baseRemote) {
|
|
82513
|
+
constructor(options, baseRemote, authKind) {
|
|
81698
82514
|
this.options = options;
|
|
81699
82515
|
this.baseRemote = baseRemote;
|
|
82516
|
+
this.authKind = authKind;
|
|
81700
82517
|
this.local = createLocalOverlayService(options);
|
|
81701
82518
|
}
|
|
81702
82519
|
listTools() {
|
|
81703
82520
|
return this.delegate?.listTools() ?? this.local.listTools();
|
|
81704
82521
|
}
|
|
81705
82522
|
async execute(capletId, request) {
|
|
82523
|
+
if (!this.delegate || nativeCredentialsNeedRefresh(this.credentialExpiresAt)) await this.ensureDelegateCurrent();
|
|
81706
82524
|
return await (this.delegate ?? this.local).execute(capletId, request);
|
|
81707
82525
|
}
|
|
81708
82526
|
codeModeService() {
|
|
@@ -81710,42 +82528,8 @@ var CloudNativeCapletsService = class {
|
|
|
81710
82528
|
}
|
|
81711
82529
|
async reload() {
|
|
81712
82530
|
if (this.closed) return false;
|
|
81713
|
-
|
|
81714
|
-
|
|
81715
|
-
const remoteUrl = this.options.remote?.url ?? this.baseRemote.url.toString().replace(/\/mcp$/u, "");
|
|
81716
|
-
const selection = await resolveRemoteSelection({
|
|
81717
|
-
mode: "cloud",
|
|
81718
|
-
remoteUrl,
|
|
81719
|
-
...cloudFetch ? { fetch: cloudFetch } : {}
|
|
81720
|
-
}, {
|
|
81721
|
-
...process.env,
|
|
81722
|
-
CAPLETS_MODE: "cloud",
|
|
81723
|
-
CAPLETS_REMOTE_URL: remoteUrl
|
|
81724
|
-
});
|
|
81725
|
-
if (selection.kind !== "hosted_cloud") throw new CapletsError("REQUEST_INVALID", "CAPLETS_MODE=cloud requires Caplets Cloud.");
|
|
81726
|
-
const cloudPresence = {
|
|
81727
|
-
url: selection.cloudPresence.url,
|
|
81728
|
-
accessToken: selection.cloudPresence.accessToken,
|
|
81729
|
-
workspaceId: selection.cloudPresence.workspaceId,
|
|
81730
|
-
...this.options.remote?.cloud?.projectRoot ? { projectRoot: this.options.remote.cloud.projectRoot } : {},
|
|
81731
|
-
heartbeatIntervalMs: this.options.remote?.cloud?.heartbeatIntervalMs ?? this.baseRemote.cloud?.heartbeatIntervalMs ?? 3e4
|
|
81732
|
-
};
|
|
81733
|
-
const remoteOptions = {
|
|
81734
|
-
...this.baseRemote,
|
|
81735
|
-
url: selection.remote.attachUrl,
|
|
81736
|
-
auth: nativeAuthFromRemoteAuth(selection.remote.auth),
|
|
81737
|
-
requestInit: selection.remote.requestInit,
|
|
81738
|
-
...selection.remote.fetch ? { fetch: selection.remote.fetch } : {},
|
|
81739
|
-
cloud: cloudPresence
|
|
81740
|
-
};
|
|
81741
|
-
this.delegate = createCompositeRemoteService(remoteOptions, this.local, this.options, "hosted_cloud");
|
|
81742
|
-
this.unsubscribeDelegate = this.delegate.onToolsChanged((tools) => this.emit(tools));
|
|
81743
|
-
} catch (error) {
|
|
81744
|
-
if (this.options.mode === "cloud") throw error;
|
|
81745
|
-
warnRemoteProjectBindingFallback(this.options);
|
|
81746
|
-
return await this.local.reload();
|
|
81747
|
-
}
|
|
81748
|
-
return await this.delegate.reload();
|
|
82531
|
+
await this.ensureDelegateCurrent();
|
|
82532
|
+
return await (this.delegate ?? this.local).reload();
|
|
81749
82533
|
}
|
|
81750
82534
|
onToolsChanged(listener) {
|
|
81751
82535
|
this.listeners.add(listener);
|
|
@@ -81761,13 +82545,110 @@ var CloudNativeCapletsService = class {
|
|
|
81761
82545
|
emit(tools) {
|
|
81762
82546
|
for (const listener of this.listeners) listener(tools);
|
|
81763
82547
|
}
|
|
82548
|
+
async ensureDelegateCurrent() {
|
|
82549
|
+
if (this.ensureDelegateCurrentInFlight) {
|
|
82550
|
+
await this.ensureDelegateCurrentInFlight;
|
|
82551
|
+
return;
|
|
82552
|
+
}
|
|
82553
|
+
const refresh = this.ensureDelegateCurrentNow();
|
|
82554
|
+
this.ensureDelegateCurrentInFlight = refresh;
|
|
82555
|
+
try {
|
|
82556
|
+
await refresh;
|
|
82557
|
+
} finally {
|
|
82558
|
+
if (this.ensureDelegateCurrentInFlight === refresh) this.ensureDelegateCurrentInFlight = void 0;
|
|
82559
|
+
}
|
|
82560
|
+
}
|
|
82561
|
+
async ensureDelegateCurrentNow() {
|
|
82562
|
+
try {
|
|
82563
|
+
const remoteOptions = await this.resolveProfileRemoteOptions();
|
|
82564
|
+
if (this.closed) return;
|
|
82565
|
+
const signature = remoteOptionsSignature(remoteOptions);
|
|
82566
|
+
if (!this.delegate) {
|
|
82567
|
+
const { remote, presence } = createCompositeRemoteParts(remoteOptions, this.local, this.options, this.authKind, () => this.resolveProfileRemoteOptions());
|
|
82568
|
+
this.delegate = new CompositeNativeCapletsService(remote, this.local, this.options, presence);
|
|
82569
|
+
this.unsubscribeDelegate = this.delegate.onToolsChanged((tools) => this.emit(tools));
|
|
82570
|
+
this.remoteSignature = signature;
|
|
82571
|
+
this.credentialExpiresAt = remoteOptions.credentialExpiresAt;
|
|
82572
|
+
return;
|
|
82573
|
+
}
|
|
82574
|
+
if (signature === this.remoteSignature) return;
|
|
82575
|
+
const { remote, presence } = createCompositeRemoteParts(remoteOptions, this.local, this.options, this.authKind, () => this.resolveProfileRemoteOptions());
|
|
82576
|
+
if (!await remote.reload()) {
|
|
82577
|
+
await Promise.all([remote.close(), presence?.close()]);
|
|
82578
|
+
return;
|
|
82579
|
+
}
|
|
82580
|
+
await this.delegate.replaceRemote(remote, presence);
|
|
82581
|
+
this.remoteSignature = signature;
|
|
82582
|
+
this.credentialExpiresAt = remoteOptions.credentialExpiresAt;
|
|
82583
|
+
} catch (error) {
|
|
82584
|
+
if (this.options.mode === "cloud" || this.options.mode === "remote") {
|
|
82585
|
+
if (this.delegate) throw error;
|
|
82586
|
+
await this.close().catch((closeError) => {
|
|
82587
|
+
writeErr(this.options, `Could not close local overlay Caplets service: ${errorMessage(closeError)}\n`);
|
|
82588
|
+
});
|
|
82589
|
+
throw error;
|
|
82590
|
+
}
|
|
82591
|
+
warnRemoteProjectBindingFallback(this.options);
|
|
82592
|
+
if (!this.delegate) await this.local.reload();
|
|
82593
|
+
}
|
|
82594
|
+
}
|
|
82595
|
+
async resolveProfileRemoteOptions() {
|
|
82596
|
+
const cloudFetch = this.options.remote?.fetch;
|
|
82597
|
+
const remoteUrl = this.options.remote?.url ?? process.env.CAPLETS_REMOTE_URL ?? this.baseRemote.url.toString().replace(/\/v1(?:\/ws\/[^/]+)?\/attach$/u, "");
|
|
82598
|
+
const selection = await resolveRemoteSelection({
|
|
82599
|
+
mode: this.authKind === "hosted_cloud" ? "cloud" : "remote",
|
|
82600
|
+
remoteUrl,
|
|
82601
|
+
...this.options.remote?.workspace ? { workspace: this.options.remote.workspace } : {},
|
|
82602
|
+
...this.options.authDir ? { authDir: this.options.authDir } : {},
|
|
82603
|
+
...cloudFetch ? { fetch: cloudFetch } : {}
|
|
82604
|
+
}, {
|
|
82605
|
+
...process.env,
|
|
82606
|
+
CAPLETS_MODE: this.authKind === "hosted_cloud" ? "cloud" : "remote",
|
|
82607
|
+
CAPLETS_REMOTE_URL: remoteUrl
|
|
82608
|
+
});
|
|
82609
|
+
if (this.authKind === "hosted_cloud" && selection.kind !== "hosted_cloud") throw new CapletsError("REQUEST_INVALID", "CAPLETS_MODE=cloud requires Caplets Cloud.");
|
|
82610
|
+
if (this.authKind === "self_hosted_remote" && selection.kind !== "self_hosted_remote") throw new CapletsError("REQUEST_INVALID", "CAPLETS_MODE=remote requires self-hosted Caplets.");
|
|
82611
|
+
const cloudPresence = selection.kind === "hosted_cloud" ? {
|
|
82612
|
+
url: selection.cloudPresence.url,
|
|
82613
|
+
accessToken: selection.cloudPresence.accessToken,
|
|
82614
|
+
workspaceId: selection.cloudPresence.workspaceId,
|
|
82615
|
+
...this.options.remote?.cloud?.projectRoot ? { projectRoot: this.options.remote.cloud.projectRoot } : {},
|
|
82616
|
+
heartbeatIntervalMs: this.options.remote?.cloud?.heartbeatIntervalMs ?? this.baseRemote.cloud?.heartbeatIntervalMs ?? 3e4
|
|
82617
|
+
} : void 0;
|
|
82618
|
+
return remoteOptionsFromSelection(selection, this.baseRemote, cloudPresence);
|
|
82619
|
+
}
|
|
81764
82620
|
};
|
|
81765
|
-
function
|
|
81766
|
-
|
|
81767
|
-
|
|
81768
|
-
|
|
81769
|
-
|
|
82621
|
+
function remoteOptionsFromSelection(selection, baseRemote, cloudPresence) {
|
|
82622
|
+
return {
|
|
82623
|
+
...baseRemote,
|
|
82624
|
+
url: selection.remote.attachUrl,
|
|
82625
|
+
auth: nativeAuthFromRemoteAuth(selection.remote.auth),
|
|
82626
|
+
requestInit: selection.remote.requestInit,
|
|
82627
|
+
...selection.remote.fetch ? { fetch: selection.remote.fetch } : {},
|
|
82628
|
+
...cloudPresence ? { cloud: cloudPresence } : {},
|
|
82629
|
+
...selection.credentialExpiresAt ? { credentialExpiresAt: selection.credentialExpiresAt } : {}
|
|
81770
82630
|
};
|
|
82631
|
+
}
|
|
82632
|
+
function remoteOptionsSignature(remoteOptions) {
|
|
82633
|
+
return JSON.stringify({
|
|
82634
|
+
url: remoteOptions.url.toString(),
|
|
82635
|
+
requestInit: remoteOptions.requestInit,
|
|
82636
|
+
credentialExpiresAt: remoteOptions.credentialExpiresAt,
|
|
82637
|
+
cloud: remoteOptions.cloud ? {
|
|
82638
|
+
url: remoteOptions.cloud.url.toString(),
|
|
82639
|
+
accessToken: remoteOptions.cloud.accessToken,
|
|
82640
|
+
workspaceId: remoteOptions.cloud.workspaceId,
|
|
82641
|
+
projectRoot: remoteOptions.cloud.projectRoot,
|
|
82642
|
+
heartbeatIntervalMs: remoteOptions.cloud.heartbeatIntervalMs
|
|
82643
|
+
} : void 0
|
|
82644
|
+
});
|
|
82645
|
+
}
|
|
82646
|
+
function nativeCredentialsNeedRefresh(expiresAt) {
|
|
82647
|
+
if (!expiresAt) return false;
|
|
82648
|
+
const parsed = Date.parse(expiresAt);
|
|
82649
|
+
return Number.isFinite(parsed) && parsed <= Date.now() + 6e4;
|
|
82650
|
+
}
|
|
82651
|
+
function nativeAuthFromRemoteAuth(auth) {
|
|
81771
82652
|
if (auth.type === "none") return {
|
|
81772
82653
|
enabled: false,
|
|
81773
82654
|
user: auth.user
|
|
@@ -81783,7 +82664,8 @@ var CompositeNativeCapletsService = class {
|
|
|
81783
82664
|
options;
|
|
81784
82665
|
presence;
|
|
81785
82666
|
listeners = /* @__PURE__ */ new Set();
|
|
81786
|
-
|
|
82667
|
+
unsubscribeRemote;
|
|
82668
|
+
unsubscribeLocal;
|
|
81787
82669
|
warnedShadowedLocalCaplets = /* @__PURE__ */ new Set();
|
|
81788
82670
|
tools = [];
|
|
81789
82671
|
closed = false;
|
|
@@ -81794,11 +82676,10 @@ var CompositeNativeCapletsService = class {
|
|
|
81794
82676
|
this.local = local;
|
|
81795
82677
|
this.options = options;
|
|
81796
82678
|
this.presence = presence;
|
|
81797
|
-
this.
|
|
82679
|
+
this.unsubscribeRemote = this.remote.onToolsChanged(() => this.updateMergedTools());
|
|
82680
|
+
this.unsubscribeLocal = this.local.onToolsChanged(() => this.updateMergedTools());
|
|
81798
82681
|
this.tools = this.mergeTools();
|
|
81799
|
-
this.
|
|
81800
|
-
writeErr(options, `Could not register Caplets Cloud Project Binding: ${errorMessage(error)}\n`);
|
|
81801
|
-
});
|
|
82682
|
+
this.startPresence();
|
|
81802
82683
|
}
|
|
81803
82684
|
listTools() {
|
|
81804
82685
|
return [...this.tools];
|
|
@@ -81836,7 +82717,8 @@ var CompositeNativeCapletsService = class {
|
|
|
81836
82717
|
async close() {
|
|
81837
82718
|
if (this.closed) return;
|
|
81838
82719
|
this.closed = true;
|
|
81839
|
-
|
|
82720
|
+
this.unsubscribeRemote();
|
|
82721
|
+
this.unsubscribeLocal();
|
|
81840
82722
|
this.listeners.clear();
|
|
81841
82723
|
this.codeModeSessions.close();
|
|
81842
82724
|
await Promise.all([
|
|
@@ -81845,6 +82727,22 @@ var CompositeNativeCapletsService = class {
|
|
|
81845
82727
|
this.presence?.close()
|
|
81846
82728
|
]);
|
|
81847
82729
|
}
|
|
82730
|
+
async replaceRemote(remote, presence) {
|
|
82731
|
+
if (this.closed) {
|
|
82732
|
+
await Promise.all([remote.close(), presence?.close()]);
|
|
82733
|
+
return;
|
|
82734
|
+
}
|
|
82735
|
+
const previousRemote = this.remote;
|
|
82736
|
+
const previousPresence = this.presence;
|
|
82737
|
+
this.unsubscribeRemote();
|
|
82738
|
+
this.remote = remote;
|
|
82739
|
+
this.presence = presence;
|
|
82740
|
+
this.unsubscribeRemote = this.remote.onToolsChanged(() => this.updateMergedTools());
|
|
82741
|
+
await Promise.all([previousRemote.close(), previousPresence?.close()]);
|
|
82742
|
+
if (this.closed) return;
|
|
82743
|
+
this.startPresence();
|
|
82744
|
+
this.updateMergedTools();
|
|
82745
|
+
}
|
|
81848
82746
|
updateMergedTools() {
|
|
81849
82747
|
if (this.closed || this.batchingReload) return;
|
|
81850
82748
|
const tools = this.mergeTools();
|
|
@@ -81889,6 +82787,11 @@ var CompositeNativeCapletsService = class {
|
|
|
81889
82787
|
return;
|
|
81890
82788
|
}
|
|
81891
82789
|
}
|
|
82790
|
+
startPresence() {
|
|
82791
|
+
this.presence?.start().catch((error) => {
|
|
82792
|
+
writeErr(this.options, `Could not register Caplets Cloud Project Binding: ${errorMessage(error)}\n`);
|
|
82793
|
+
});
|
|
82794
|
+
}
|
|
81892
82795
|
};
|
|
81893
82796
|
function remoteCodeModeCallableNativeTools(tools) {
|
|
81894
82797
|
return codeModeCallableNativeTools(tools, { fallbackToVisible: true });
|
|
@@ -81942,9 +82845,9 @@ function createLocalOverlayConfigLoader(options) {
|
|
|
81942
82845
|
return parseConfig({});
|
|
81943
82846
|
}
|
|
81944
82847
|
for (const warning of result.warnings) writeErr(options, `Caplets local overlay warning${typeof warning.path === "string" ? ` at ${warning.path}` : ""}: ${warning.message}\n`);
|
|
81945
|
-
const
|
|
81946
|
-
if (hasLoaded && [...
|
|
81947
|
-
previousWarnings =
|
|
82848
|
+
const fatalWarnings = new Set(result.warnings.filter((warning) => !warning.recoverable).map(warningKey));
|
|
82849
|
+
if (hasLoaded && [...fatalWarnings].some((warning) => !previousWarnings.has(warning))) throw new CapletsError("CONFIG_INVALID", "Caplets local overlay reload produced new warnings; keeping last known-good config.");
|
|
82850
|
+
previousWarnings = fatalWarnings;
|
|
81948
82851
|
hasLoaded = true;
|
|
81949
82852
|
return result.config;
|
|
81950
82853
|
};
|
|
@@ -81964,4 +82867,4 @@ function errorMessage(error) {
|
|
|
81964
82867
|
return error instanceof Error ? error.message : String(error);
|
|
81965
82868
|
}
|
|
81966
82869
|
//#endregion
|
|
81967
|
-
export {
|
|
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 };
|