@caplets/core 0.24.1 → 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.
Files changed (50) hide show
  1. package/dist/attach/options.d.ts +1 -0
  2. package/dist/attach/server.d.ts +3 -0
  3. package/dist/cli/commands.d.ts +10 -2
  4. package/dist/cli/doctor.d.ts +4 -1
  5. package/dist/cli.d.ts +3 -2
  6. package/dist/{completion-BeVXdm9q.js → completion-DrPr2vYw.js} +27 -5
  7. package/dist/daemon/config.d.ts +8 -0
  8. package/dist/daemon/env.d.ts +2 -0
  9. package/dist/daemon/index.d.ts +18 -0
  10. package/dist/daemon/logs.d.ts +15 -0
  11. package/dist/daemon/manager.d.ts +2 -0
  12. package/dist/daemon/paths.d.ts +2 -0
  13. package/dist/daemon/platform-darwin.d.ts +3 -0
  14. package/dist/daemon/platform-linux.d.ts +3 -0
  15. package/dist/daemon/platform-windows.d.ts +3 -0
  16. package/dist/daemon/process.d.ts +11 -0
  17. package/dist/daemon/shell.d.ts +20 -0
  18. package/dist/daemon/types.d.ts +178 -0
  19. package/dist/daemon/validation.d.ts +13 -0
  20. package/dist/daemon/xml.d.ts +1 -0
  21. package/dist/index.js +5857 -3604
  22. package/dist/native/options.d.ts +5 -2
  23. package/dist/native/remote.d.ts +6 -1
  24. package/dist/native.js +1 -1
  25. package/dist/project-binding/attach.d.ts +1 -3
  26. package/dist/project-binding/session.d.ts +1 -0
  27. package/dist/remote/credential-store.d.ts +12 -0
  28. package/dist/remote/options.d.ts +2 -7
  29. package/dist/remote/pairing.d.ts +13 -0
  30. package/dist/remote/profile-store.d.ts +94 -0
  31. package/dist/remote/profiles.d.ts +47 -0
  32. package/dist/remote/selection.d.ts +4 -4
  33. package/dist/remote/server-credential-store.d.ts +84 -0
  34. package/dist/remote/server-credentials.d.ts +21 -0
  35. package/dist/remote-control/client.d.ts +4 -1
  36. package/dist/serve/http.d.ts +5 -0
  37. package/dist/serve/index.d.ts +1 -3
  38. package/dist/serve/options.d.ts +7 -12
  39. package/dist/server/options.d.ts +2 -9
  40. package/dist/{service-Cvnuu9wr.js → service-DjwB8aiW.js} +1084 -254
  41. package/package.json +1 -1
  42. package/dist/serve/daemon/config.d.ts +0 -8
  43. package/dist/serve/daemon/index.d.ts +0 -16
  44. package/dist/serve/daemon/paths.d.ts +0 -3
  45. package/dist/serve/daemon/platform-darwin.d.ts +0 -2
  46. package/dist/serve/daemon/platform-linux.d.ts +0 -2
  47. package/dist/serve/daemon/platform-windows.d.ts +0 -2
  48. package/dist/serve/daemon/platform.d.ts +0 -9
  49. package/dist/serve/daemon/process.d.ts +0 -5
  50. 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$5(tool.inputSchema) ? tool.inputSchema : void 0;
18631
- const properties = isRecord$5(schema?.properties) ? schema.properties : {};
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$5(properties[name]) ? properties[name] : void 0);
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$5(tool)) return {};
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$5(value) {
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$4(value)) return false;
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$4(value) && "result" in value;
18889
+ return isRecord$6(value) && "result" in value;
18891
18890
  }
18892
18891
  function isErrorStructuredContent(value) {
18893
- return isRecord$4(value) && "error" in value;
18892
+ return isRecord$6(value) && "error" in value;
18894
18893
  }
18895
18894
  function isHttpLikeResult(value) {
18896
- return isRecord$4(value) && ("status" in value || "statusText" in value || "body" in value);
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$4(value) && ("exitCode" in value || "stdout" in value || "stderr" in value);
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$4(record.argsTemplate)) hints.push(`args template: ${compactJsonText(record.argsTemplate, 160)}`);
18973
- if (isRecord$4(record.callTemplate)) hints.push(`call: ${compactJsonText(record.callTemplate, 220)}`);
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$4(value) ? value : void 0;
19042
+ return isRecord$6(value) ? value : void 0;
19044
19043
  }
19045
- function isRecord$4(value) {
19044
+ function isRecord$6(value) {
19046
19045
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
19047
19046
  }
19048
19047
  //#endregion
@@ -28017,17 +28016,17 @@ function googleDiscoveryScopesForOperations(operations) {
28017
28016
  return [...new Set(operations.flatMap((operation) => operation.scopes))].sort();
28018
28017
  }
28019
28018
  function validateGoogleDiscoveryDocument(value) {
28020
- if (!isRecord$3(value)) throw new Error("Invalid Google Discovery document: expected an object");
28019
+ if (!isRecord$5(value)) throw new Error("Invalid Google Discovery document: expected an object");
28021
28020
  if (value.kind !== void 0 && value.kind !== "discovery#restDescription") throw new Error("Invalid Google Discovery document: expected kind discovery#restDescription");
28022
- if (value.resources !== void 0 && !isRecord$3(value.resources)) throw new Error("Invalid Google Discovery document: expected resources object");
28023
- if (value.methods !== void 0 && !isRecord$3(value.methods)) throw new Error("Invalid Google Discovery document: expected methods object");
28024
- if (!isRecord$3(value.resources) && !isRecord$3(value.methods)) throw new Error("Invalid Google Discovery document: expected resources or methods object");
28025
- if (value.schemas !== void 0 && !isRecord$3(value.schemas)) throw new Error("Invalid Google Discovery document: expected schemas object");
28026
- if (value.parameters !== void 0 && !isRecord$3(value.parameters)) throw new Error("Invalid Google Discovery document: expected parameters object");
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");
28027
28026
  return value;
28028
28027
  }
28029
28028
  function collectDocumentMethods(document) {
28030
- return [...Object.entries(document.methods ?? {}).filter((entry) => isRecord$3(entry[1])).map(([methodKey, method]) => ({
28029
+ return [...Object.entries(document.methods ?? {}).filter((entry) => isRecord$5(entry[1])).map(([methodKey, method]) => ({
28031
28030
  resourcePath: [],
28032
28031
  methodKey,
28033
28032
  method
@@ -28036,9 +28035,9 @@ function collectDocumentMethods(document) {
28036
28035
  function collectMethods(resources, resourcePath = []) {
28037
28036
  const entries = [];
28038
28037
  for (const [resourceName, resource] of Object.entries(resources)) {
28039
- if (!isRecord$3(resource)) continue;
28038
+ if (!isRecord$5(resource)) continue;
28040
28039
  const nextPath = [...resourcePath, resourceName];
28041
- for (const [methodKey, method] of Object.entries(resource.methods ?? {})) if (isRecord$3(method)) entries.push({
28040
+ for (const [methodKey, method] of Object.entries(resource.methods ?? {})) if (isRecord$5(method)) entries.push({
28042
28041
  resourcePath: nextPath,
28043
28042
  methodKey,
28044
28043
  method
@@ -28211,7 +28210,7 @@ function globMatches(pattern, name) {
28211
28210
  function isJsonSchemaObject(value) {
28212
28211
  return value.type === "object" || "properties" in value || "additionalProperties" in value;
28213
28212
  }
28214
- function isRecord$3(value) {
28213
+ function isRecord$5(value) {
28215
28214
  return typeof value === "object" && value !== null && !Array.isArray(value);
28216
28215
  }
28217
28216
  function collapseWhitespace(value) {
@@ -63435,7 +63434,7 @@ var CapletsEngine = class {
63435
63434
  }
63436
63435
  }
63437
63436
  async completeCliWords(words) {
63438
- const { completeCliWords } = await import("./completion-BeVXdm9q.js").then((n) => n.r);
63437
+ const { completeCliWords } = await import("./completion-DrPr2vYw.js").then((n) => n.r);
63439
63438
  return await completeCliWords(words, {
63440
63439
  config: this.registry.config,
63441
63440
  managers: {
@@ -63725,7 +63724,7 @@ function annotateDirectResult(result, caplet, operation) {
63725
63724
  return {
63726
63725
  ...result,
63727
63726
  _meta: {
63728
- ...isRecord$2(existingMeta) ? existingMeta : {},
63727
+ ...isRecord$4(existingMeta) ? existingMeta : {},
63729
63728
  caplets: {
63730
63729
  capletId: caplet.server,
63731
63730
  backend: caplet.backend,
@@ -63738,7 +63737,7 @@ function annotateDirectResult(result, caplet, operation) {
63738
63737
  function isUnsupportedCapability(error) {
63739
63738
  return error instanceof CapletsError && error.code === "UNSUPPORTED_CAPABILITY";
63740
63739
  }
63741
- function isRecord$2(value) {
63740
+ function isRecord$4(value) {
63742
63741
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
63743
63742
  }
63744
63743
  //#endregion
@@ -79735,32 +79734,18 @@ function nativeCapletToolDescription(toolName, caplet) {
79735
79734
  }
79736
79735
  //#endregion
79737
79736
  //#region src/server/options.ts
79738
- const DEFAULT_SERVER_USER = "caplets";
79739
79737
  function resolveCapletsServer(input = {}, env = process.env) {
79740
79738
  const rawUrl = nonEmpty$1(input.url, "url") ?? nonEmpty$1(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL");
79741
79739
  if (rawUrl === void 0) throw new CapletsError("REQUEST_INVALID", "CAPLETS_SERVER_URL or url is required.");
79742
79740
  const baseUrl = parseServerBaseUrl(rawUrl);
79743
- const userWasExplicit = input.user !== void 0 || hasEnv$1(env.CAPLETS_SERVER_USER);
79744
- const user = nonEmpty$1(input.user, "user") ?? nonEmpty$1(env.CAPLETS_SERVER_USER, "CAPLETS_SERVER_USER") ?? DEFAULT_SERVER_USER;
79745
- const password = nonEmpty$1(input.password, "password") ?? nonEmpty$1(env.CAPLETS_SERVER_PASSWORD, "CAPLETS_SERVER_PASSWORD");
79746
- if (userWasExplicit && password === void 0) throw new CapletsError("REQUEST_INVALID", "Caplets server Basic Auth requires a password; set CAPLETS_SERVER_PASSWORD or password.");
79747
- const auth = password === void 0 ? {
79748
- enabled: false,
79749
- user
79750
- } : {
79751
- enabled: true,
79752
- user,
79753
- password
79754
- };
79755
- const requestInit = auth.enabled ? { headers: { Authorization: basicAuthHeader$1(auth.user, auth.password) } } : {};
79756
79741
  return {
79757
79742
  baseUrl,
79758
79743
  mcpUrl: mcpUrlForBase(baseUrl),
79759
79744
  attachUrl: attachUrlForBase(baseUrl),
79760
79745
  controlUrl: controlUrlForBase(baseUrl),
79761
79746
  healthUrl: healthUrlForBase(baseUrl),
79762
- auth,
79763
- requestInit,
79747
+ auth: { type: "none" },
79748
+ requestInit: {},
79764
79749
  ...input.fetch ? { fetch: input.fetch } : {}
79765
79750
  };
79766
79751
  }
@@ -79797,18 +79782,12 @@ function isLoopbackHost(host) {
79797
79782
  const normalized = host.toLocaleLowerCase();
79798
79783
  return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
79799
79784
  }
79800
- function basicAuthHeader$1(user, password) {
79801
- return `Basic ${Buffer$1.from(`${user}:${password}`).toString("base64")}`;
79802
- }
79803
79785
  function nonEmpty$1(value, label) {
79804
79786
  if (value === void 0) return;
79805
79787
  const trimmed = value.trim();
79806
79788
  if (!trimmed) throw new CapletsError("REQUEST_INVALID", `${label} must not be empty`);
79807
79789
  return trimmed;
79808
79790
  }
79809
- function hasEnv$1(value) {
79810
- return value !== void 0 && value.trim() !== "";
79811
- }
79812
79791
  //#endregion
79813
79792
  //#region src/remote/options.ts
79814
79793
  const DEFAULT_REMOTE_USER = "caplets";
@@ -79832,25 +79811,16 @@ function resolveCapletsRemote(input = {}, env = process.env) {
79832
79811
  const rawUrl = nonEmpty(input.url, "url") ?? nonEmpty(env.CAPLETS_REMOTE_URL, "CAPLETS_REMOTE_URL");
79833
79812
  if (rawUrl === void 0) throw new CapletsError("REQUEST_INVALID", "CAPLETS_REMOTE_URL or url is required.");
79834
79813
  const baseUrl = parseServerBaseUrl(rawUrl);
79835
- const token = nonEmpty(input.token, "token") ?? nonEmpty(env.CAPLETS_REMOTE_TOKEN, "CAPLETS_REMOTE_TOKEN");
79836
- const userWasExplicit = input.user !== void 0 || hasEnv(env.CAPLETS_REMOTE_USER);
79837
- const user = nonEmpty(input.user, "user") ?? nonEmpty(env.CAPLETS_REMOTE_USER, "CAPLETS_REMOTE_USER") ?? DEFAULT_REMOTE_USER;
79838
- const password = nonEmpty(input.password, "password") ?? nonEmpty(env.CAPLETS_REMOTE_PASSWORD, "CAPLETS_REMOTE_PASSWORD");
79814
+ const token = nonEmpty(input.token, "token");
79839
79815
  const workspace = nonEmpty(input.workspace, "workspace") ?? nonEmpty(env.CAPLETS_REMOTE_WORKSPACE, "CAPLETS_REMOTE_WORKSPACE");
79840
- if (token && password) throw new CapletsError("REQUEST_INVALID", "Use either CAPLETS_REMOTE_TOKEN or CAPLETS_REMOTE_PASSWORD, not both.");
79841
- if (!token && userWasExplicit && password === void 0) throw new CapletsError("REQUEST_INVALID", "Remote Caplets Basic Auth requires a password; set CAPLETS_REMOTE_PASSWORD or password.");
79842
79816
  const auth = token ? {
79843
79817
  type: "bearer",
79844
79818
  token
79845
- } : password === void 0 ? {
79846
- type: "none",
79847
- user
79848
79819
  } : {
79849
- type: "basic",
79850
- user,
79851
- password
79820
+ type: "none",
79821
+ user: DEFAULT_REMOTE_USER
79852
79822
  };
79853
- const requestInit = auth.type === "bearer" ? { headers: { Authorization: `Bearer ${auth.token}` } } : auth.type === "basic" ? { headers: { Authorization: basicAuthHeader(auth.user, auth.password) } } : {};
79823
+ const requestInit = auth.type === "bearer" ? { headers: { Authorization: `Bearer ${auth.token}` } } : {};
79854
79824
  return {
79855
79825
  baseUrl,
79856
79826
  mcpUrl: appendBasePath(baseUrl, "v1/mcp"),
@@ -79870,7 +79840,7 @@ function resolveHostedCloudRemote(input = {}, env = process.env) {
79870
79840
  const cloud = parseHostedCloudRemoteUrl(rawUrl);
79871
79841
  const workspace = cloud.workspace ?? nonEmpty(input.workspace, "workspace") ?? nonEmpty(env.CAPLETS_REMOTE_WORKSPACE, "CAPLETS_REMOTE_WORKSPACE");
79872
79842
  if (!workspace) throw new CapletsError("REQUEST_INVALID", "Caplets Cloud remote URL requires a selected workspace.");
79873
- const token = nonEmpty(input.token, "token") ?? nonEmpty(env.CAPLETS_REMOTE_TOKEN, "CAPLETS_REMOTE_TOKEN");
79843
+ const token = nonEmpty(input.token, "token");
79874
79844
  const auth = token ? {
79875
79845
  type: "bearer",
79876
79846
  token
@@ -79900,6 +79870,11 @@ function hostedCloudWorkspaceFromRemoteUrl(value) {
79900
79870
  return;
79901
79871
  }
79902
79872
  }
79873
+ function normalizeRemoteProfileHostUrl(value) {
79874
+ const url = parseServerBaseUrl(value);
79875
+ if (isCapletsCloudUrl(url.toString())) return `${url.origin}/`;
79876
+ return url.toString();
79877
+ }
79903
79878
  function projectBindingWebSocketUrlForBase(baseUrl) {
79904
79879
  return webSocketUrl(appendBasePath(baseUrl, "v1/attach/project-bindings/connect"));
79905
79880
  }
@@ -79937,18 +79912,12 @@ function parseCapletsMode(value) {
79937
79912
  if (value === "auto" || value === "local" || value === "remote" || value === "cloud") return value;
79938
79913
  throw new CapletsError("REQUEST_INVALID", `Expected CAPLETS_MODE to be auto, local, remote, or cloud, got ${value}`);
79939
79914
  }
79940
- function basicAuthHeader(user, password) {
79941
- return `Basic ${Buffer$1.from(`${user}:${password}`).toString("base64")}`;
79942
- }
79943
79915
  function nonEmpty(value, label) {
79944
79916
  if (value === void 0) return void 0;
79945
79917
  const trimmed = value.trim();
79946
79918
  if (!trimmed) throw new CapletsError("REQUEST_INVALID", `${label} must not be empty`);
79947
79919
  return trimmed;
79948
79920
  }
79949
- function hasEnv(value) {
79950
- return value !== void 0 && value.trim() !== "";
79951
- }
79952
79921
  //#endregion
79953
79922
  //#region src/native/options.ts
79954
79923
  const DEFAULT_POLL_INTERVAL_MS = 3e4;
@@ -79960,7 +79929,11 @@ function resolveNativeCapletsServiceOptions(input = {}, env = process.env) {
79960
79929
  }, env);
79961
79930
  if (mode.mode === "local") return { mode: "local" };
79962
79931
  const remoteFetch = input.remote?.fetch;
79963
- const server = mode.mode === "cloud" ? resolveNativeHostedCloudRemote(input.remote?.url ?? env.CAPLETS_REMOTE_URL ?? "", optionalWorkspace(input, env).workspace, remoteFetch) : resolveCapletsRemote(input.remote, env);
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);
79964
79937
  const cloud = resolveNativeCloudPresence(input.remote?.cloud, env);
79965
79938
  return {
79966
79939
  mode: mode.mode,
@@ -79981,16 +79954,19 @@ function resolveNativeHostedCloudRemote(url, workspace, fetch) {
79981
79954
  ...fetch ? { fetch } : {}
79982
79955
  });
79983
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
+ }
79984
79965
  function optionalWorkspace(input, env) {
79985
79966
  const workspace = input.remote?.cloud?.workspaceId ?? input.remote?.workspace ?? env.CAPLETS_REMOTE_WORKSPACE ?? env.CAPLETS_CLOUD_WORKSPACE_ID;
79986
79967
  return workspace ? { workspace } : {};
79987
79968
  }
79988
79969
  function nativeAuthFromRemoteAuth$1(auth) {
79989
- if (auth.type === "basic") return {
79990
- enabled: true,
79991
- user: auth.user,
79992
- password: auth.password
79993
- };
79994
79970
  if (auth.type === "none") return {
79995
79971
  enabled: false,
79996
79972
  user: auth.user
@@ -80314,45 +80290,79 @@ function projectSyncFiles(projectRoot) {
80314
80290
  //#endregion
80315
80291
  //#region src/native/remote.ts
80316
80292
  function createSdkRemoteCapletsClient(options) {
80317
- const fetchImpl = options.fetch ?? fetch;
80318
80293
  const listeners = /* @__PURE__ */ new Set();
80319
80294
  let manifest;
80320
80295
  let exportByName = /* @__PURE__ */ new Map();
80321
80296
  let eventsAbort;
80297
+ let eventsStartInFlight;
80322
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
+ };
80323
80312
  const clearEventsReconnectTimer = () => {
80324
80313
  if (eventsReconnectTimer) {
80325
80314
  clearTimeout(eventsReconnectTimer);
80326
80315
  eventsReconnectTimer = void 0;
80327
80316
  }
80328
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
+ };
80329
80344
  const startEvents = () => {
80330
- if (eventsAbort || listeners.size === 0) return;
80331
- eventsAbort = startAttachEvents(options.url, options.requestInit, fetchImpl, listeners, (closedAbort, retry) => {
80332
- if (eventsAbort !== closedAbort) return;
80333
- eventsAbort = void 0;
80334
- if (!retry || closedAbort.signal.aborted || listeners.size === 0) return;
80335
- clearEventsReconnectTimer();
80336
- eventsReconnectTimer = setTimeout(() => {
80337
- eventsReconnectTimer = void 0;
80338
- startEvents();
80339
- }, 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;
80340
80350
  });
80341
80351
  };
80342
80352
  return {
80343
80353
  async listTools() {
80344
- manifest = await fetchAttachManifest(options.url, options.requestInit, fetchImpl);
80354
+ manifest = await fetchCurrentManifest();
80345
80355
  exportByName = exportMapFor(manifest);
80346
80356
  return toolsFromManifest(manifest);
80347
80357
  },
80348
80358
  async callTool(name, args) {
80349
80359
  if (!manifest) {
80350
- manifest = await fetchAttachManifest(options.url, options.requestInit, fetchImpl);
80360
+ manifest = await fetchCurrentManifest();
80351
80361
  exportByName = exportMapFor(manifest);
80352
80362
  }
80353
80363
  const invokeWithStaleRetry = async (entry, input) => {
80354
80364
  try {
80355
- return await invokeAttachExport(options.url, options.requestInit, fetchImpl, {
80365
+ return await invokeCurrentExport({
80356
80366
  revision: manifest.revision,
80357
80367
  kind: entry.kind,
80358
80368
  exportId: entry.exportId,
@@ -80360,12 +80370,12 @@ function createSdkRemoteCapletsClient(options) {
80360
80370
  });
80361
80371
  } catch (error) {
80362
80372
  if (!isAttachManifestStale(error)) throw error;
80363
- const nextManifest = await fetchAttachManifest(options.url, options.requestInit, fetchImpl);
80373
+ const nextManifest = await fetchCurrentManifest();
80364
80374
  const nextEntry = compatibleExport(nextManifest, entry);
80365
80375
  manifest = nextManifest;
80366
80376
  exportByName = exportMapFor(nextManifest);
80367
80377
  if (!nextEntry) throw new CapletsError("ATTACH_EXPORT_NOT_FOUND", "Attach export changed after manifest refresh; refetch the manifest before retrying.");
80368
- return await invokeAttachExport(options.url, options.requestInit, fetchImpl, {
80378
+ return await invokeCurrentExport({
80369
80379
  revision: nextManifest.revision,
80370
80380
  kind: nextEntry.kind,
80371
80381
  exportId: nextEntry.exportId,
@@ -80394,6 +80404,7 @@ function createSdkRemoteCapletsClient(options) {
80394
80404
  };
80395
80405
  },
80396
80406
  async close() {
80407
+ closed = true;
80397
80408
  clearEventsReconnectTimer();
80398
80409
  eventsAbort?.abort();
80399
80410
  eventsAbort = void 0;
@@ -80990,7 +81001,7 @@ function errorMessage$1(error) {
80990
81001
  return error instanceof Error ? error.message : String(error);
80991
81002
  }
80992
81003
  function remoteAuthError(kind) {
80993
- return new CapletsError("AUTH_FAILED", kind === "hosted_cloud" ? "Caplets Cloud authentication failed; run caplets cloud auth login." : "Remote Caplets authentication failed; check CAPLETS_REMOTE_TOKEN or CAPLETS_REMOTE_USER and CAPLETS_REMOTE_PASSWORD.");
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>.");
80994
81005
  }
80995
81006
  function isSessionFailure(error) {
80996
81007
  const candidate = error;
@@ -81007,6 +81018,15 @@ function isAuthFailure(error) {
81007
81018
  if (status === 401 || status === 403 || statusCode === 401 || statusCode === 403 || code === 401 || code === 403) return true;
81008
81019
  return /\b(401|403|unauthorized|forbidden)\b/iu.test(errorMessage$1(error));
81009
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
+ }
81010
81030
  //#endregion
81011
81031
  //#region src/cloud-auth/errors.ts
81012
81032
  const SECRET_PATTERN = /(cap_access_[a-z0-9._~+/=-]+|cap_refresh_[a-z0-9._~+/=-]+|one_time_code_[a-z0-9._~+/=-]+|Bearer\s+)[^\s"]*/giu;
@@ -81107,7 +81127,7 @@ var CloudAuthClient = class {
81107
81127
  throw new CapletsError("AUTH_FAILED", message, redactCloudAuthSecrets({
81108
81128
  code,
81109
81129
  message,
81110
- recoveryCommand: code === "workspace_switch_required" ? "caplets cloud auth switch <workspace>" : "caplets cloud auth login",
81130
+ recoveryCommand: code === "workspace_switch_required" ? "caplets remote login <cloud-url> --workspace <workspace>" : "caplets remote login <cloud-url>",
81111
81131
  requestId
81112
81132
  }));
81113
81133
  }
@@ -81147,6 +81167,78 @@ function normalizeCredentials(response, fallbackCloudUrl) {
81147
81167
  };
81148
81168
  }
81149
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
81150
81242
  //#region src/cloud-auth/store.ts
81151
81243
  var CloudAuthStore = class {
81152
81244
  path;
@@ -81166,7 +81258,7 @@ var CloudAuthStore = class {
81166
81258
  }
81167
81259
  };
81168
81260
  function migrateCredentials(value) {
81169
- const record = isRecord$1(value) ? value : {};
81261
+ const record = isRecord$3(value) ? value : {};
81170
81262
  const now = (/* @__PURE__ */ new Date()).toISOString();
81171
81263
  return {
81172
81264
  version: 2,
@@ -81216,7 +81308,7 @@ function cloudAuthPath(options = {}) {
81216
81308
  if (platform === "win32") return win32.join(defaultConfigBaseDir(env, home, platform), "Caplets", "cloud-auth.json");
81217
81309
  return posix.join(defaultConfigBaseDir(env, home, platform), "caplets", "cloud-auth.json");
81218
81310
  }
81219
- function isRecord$1(value) {
81311
+ function isRecord$3(value) {
81220
81312
  return typeof value === "object" && value !== null && !Array.isArray(value);
81221
81313
  }
81222
81314
  function stringValue(value) {
@@ -81227,79 +81319,567 @@ function arrayValue(value) {
81227
81319
  if (typeof value === "string") return value.split(/\s+/u).filter(Boolean);
81228
81320
  }
81229
81321
  //#endregion
81230
- //#region src/project-binding/errors.ts
81231
- const PROJECT_BINDING_ERROR_CODES = [
81232
- "cloud_auth_required",
81233
- "cloud_auth_expired",
81234
- "cloud_auth_revoked",
81235
- "workspace_selection_required",
81236
- "workspace_switch_required",
81237
- "workspace_forbidden",
81238
- "project_binding_forbidden",
81239
- "endpoint_unavailable",
81240
- "websocket_upgrade_required",
81241
- "sync_required",
81242
- "sync_failed",
81243
- "sync_size_limit_exceeded",
81244
- "lease_conflict",
81245
- "lease_expired",
81246
- "policy_denied",
81247
- "usage_limit_reached",
81248
- "billing_required",
81249
- "subscription_past_due",
81250
- "email_verification_required",
81251
- "remote_credentials_required",
81252
- "remote_auth_failed"
81253
- ];
81254
- var ProjectBindingError = class extends CapletsError {
81255
- projectBindingCode;
81256
- recoveryCommand;
81257
- requestId;
81258
- constructor(input) {
81259
- super("SERVER_UNAVAILABLE", input.message, input);
81260
- this.name = "ProjectBindingError";
81261
- this.projectBindingCode = input.code;
81262
- this.recoveryCommand = input.recoveryCommand;
81263
- this.requestId = input.requestId;
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;
81264
81357
  }
81265
81358
  };
81266
- function projectBindingRecovery(code, message = defaultProjectBindingMessage(code)) {
81359
+ function parseRemoteProfileCredential(value) {
81360
+ if (!isRecord$2(value)) return void 0;
81267
81361
  return {
81268
- code,
81269
- message,
81270
- recoveryCommand: recoveryCommandFor(code)
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 } : {}
81271
81369
  };
81272
81370
  }
81273
- function projectBindingError(code, message) {
81274
- return new ProjectBindingError(projectBindingRecovery(code, message));
81371
+ function isRecord$2(value) {
81372
+ return typeof value === "object" && value !== null && !Array.isArray(value);
81275
81373
  }
81276
- function recoveryCommandFor(code) {
81277
- switch (code) {
81278
- case "cloud_auth_required":
81279
- case "cloud_auth_expired":
81280
- case "cloud_auth_revoked":
81281
- case "workspace_selection_required": return "caplets cloud auth login";
81282
- case "workspace_switch_required": return "caplets cloud auth switch <workspace>";
81283
- case "sync_size_limit_exceeded": return "Add exclusions to .capletsignore or upgrade the workspace plan.";
81284
- case "remote_credentials_required":
81285
- case "remote_auth_failed": return "Set CAPLETS_REMOTE_URL and remote credentials.";
81286
- case "endpoint_unavailable":
81287
- case "websocket_upgrade_required": return "caplets doctor";
81288
- default: return;
81289
- }
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
+ };
81290
81413
  }
81291
- function defaultProjectBindingMessage(code) {
81292
- switch (code) {
81293
- case "sync_size_limit_exceeded": return "Project sync size exceeds the selected workspace policy.";
81294
- case "workspace_switch_required": return "The requested workspace differs from the saved Selected Workspace.";
81295
- case "cloud_auth_required": return "Hosted Project Binding requires Cloud Auth.";
81296
- case "endpoint_unavailable":
81297
- case "websocket_upgrade_required": return "Project Binding endpoint is unavailable.";
81298
- default: return code.replace(/_/gu, " ");
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));
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`);
81299
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);
81300
81879
  }
81301
81880
  //#endregion
81302
81881
  //#region src/remote/selection.ts
81882
+ const SELF_HOSTED_REFRESH_TIMEOUT_MS = 15e3;
81303
81883
  async function resolveRemoteSelection(input = {}, env = process.env) {
81304
81884
  const modeValue = input.mode ?? env.CAPLETS_MODE;
81305
81885
  const mode = resolveRemoteMode({
@@ -81307,43 +81887,112 @@ async function resolveRemoteSelection(input = {}, env = process.env) {
81307
81887
  ...input.remoteUrl !== void 0 ? { remoteUrl: input.remoteUrl } : {}
81308
81888
  }, env);
81309
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.");
81310
- if (mode.mode === "remote") return {
81311
- kind: "self_hosted_remote",
81312
- remote: resolveCapletsRemote({
81313
- ...input.remoteUrl !== void 0 ? { url: input.remoteUrl } : {},
81314
- ...input.user !== void 0 ? { user: input.user } : {},
81315
- ...input.password !== void 0 ? { password: input.password } : {},
81316
- ...input.token !== void 0 ? { token: input.token } : {},
81317
- ...input.workspace !== void 0 ? { workspace: input.workspace } : {},
81318
- ...input.fetch !== void 0 ? { fetch: input.fetch } : {}
81319
- }, env)
81320
- };
81321
- const store = new CloudAuthStore({ env });
81322
- let credentials = await store.load();
81323
- if (!credentials?.accessToken) throw projectBindingError("cloud_auth_required");
81324
- if (credentialsNeedRefresh(credentials)) {
81325
- if (!credentials.refreshToken) throw projectBindingError("cloud_auth_required");
81326
- const refreshed = await new CloudAuthClient({
81327
- cloudUrl: credentials.cloudUrl,
81328
- ...input.fetch !== void 0 ? { fetch: input.fetch } : {}
81329
- }).refresh({ refreshToken: credentials.refreshToken });
81330
- credentials = {
81331
- ...credentials,
81332
- ...refreshed,
81333
- refreshToken: refreshed.refreshToken ?? credentials.refreshToken,
81334
- createdAt: credentials.createdAt,
81335
- lastRefreshAt: (/* @__PURE__ */ new Date()).toISOString()
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 } : {}
81336
81932
  };
81337
- await store.save(credentials);
81338
81933
  }
81339
- const selectedWorkspace = credentials.workspaceSlug ?? credentials.workspaceId;
81340
- if (input.workspace && input.workspace !== credentials.workspaceId && input.workspace !== credentials.workspaceSlug) throw projectBindingError("workspace_switch_required", `Requested workspace ${input.workspace} differs from saved Selected Workspace ${selectedWorkspace}.`);
81934
+ const store = createRemoteProfileStore({
81935
+ authDir: input.authDir,
81936
+ env
81937
+ });
81341
81938
  const remoteUrl = input.remoteUrl ?? env.CAPLETS_REMOTE_URL;
81342
81939
  if (!remoteUrl) throw new CapletsError("REQUEST_INVALID", "CAPLETS_MODE=cloud requires CAPLETS_REMOTE_URL or remoteUrl.");
81343
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}.`);
81344
81993
  if (workspaceFromRemoteUrl && workspaceFromRemoteUrl !== credentials.workspaceSlug && workspaceFromRemoteUrl !== credentials.workspaceId) throw projectBindingError("workspace_switch_required", `Requested workspace ${workspaceFromRemoteUrl} differs from saved Selected Workspace ${selectedWorkspace}.`);
81345
81994
  const missingScope = requiredHostedCloudAttachScopes().find((scope) => !credentials.scope?.includes(scope));
81346
- if (missingScope) throw projectBindingError("cloud_auth_required", `Hosted Cloud attach requires Cloud Auth scope ${missingScope}. Run caplets cloud auth login again.`);
81995
+ if (missingScope) throw projectBindingError("cloud_auth_required", `Hosted Cloud attach requires Cloud Auth scope ${missingScope}. Run caplets remote login ${credentials.cloudUrl} again.`);
81347
81996
  const remote = resolveHostedCloudRemote({
81348
81997
  url: remoteUrl,
81349
81998
  token: credentials.accessToken,
@@ -81355,6 +82004,7 @@ async function resolveRemoteSelection(input = {}, env = process.env) {
81355
82004
  remote,
81356
82005
  selectedWorkspace,
81357
82006
  credentials,
82007
+ credentialExpiresAt: credentials.expiresAt,
81358
82008
  cloudPresence: {
81359
82009
  url: remote.baseUrl,
81360
82010
  accessToken: credentials.accessToken,
@@ -81366,6 +82016,74 @@ function credentialsNeedRefresh(credentials) {
81366
82016
  const expiresAt = Date.parse(credentials.expiresAt);
81367
82017
  return Number.isFinite(expiresAt) && expiresAt <= Date.now() + 6e4;
81368
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
+ }
81369
82087
  function requiredHostedCloudAttachScopes() {
81370
82088
  return HOSTED_CLOUD_AUTH_SCOPES.filter((scope) => scope !== "mcp:tools");
81371
82089
  }
@@ -81376,21 +82094,24 @@ let hasWarnedRemoteProjectBindingFallback = false;
81376
82094
  function createNativeCapletsService(options = {}) {
81377
82095
  const resolved = resolveNativeCapletsServiceOptions(options);
81378
82096
  if (resolved.mode === "remote") {
81379
- const local = createLocalOverlayService(options);
81380
- try {
81381
- return createCompositeRemoteService(resolved.remote, local, options, "self_hosted_remote");
81382
- } catch (error) {
81383
- if (options.mode !== "remote") {
81384
- warnRemoteProjectBindingFallback(options);
81385
- return local;
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;
81386
82110
  }
81387
- local.close().catch((closeError) => {
81388
- writeErr(options, `Could not close local overlay Caplets service: ${errorMessage(closeError)}\n`);
81389
- });
81390
- throw error;
81391
82111
  }
82112
+ return new ProfileBackedNativeCapletsService(options, resolved.remote, "self_hosted_remote");
81392
82113
  }
81393
- if (resolved.mode === "cloud") return new CloudNativeCapletsService(options, resolved.remote);
82114
+ if (resolved.mode === "cloud") return new ProfileBackedNativeCapletsService(options, resolved.remote, "hosted_cloud");
81394
82115
  return new DefaultNativeCapletsService(options);
81395
82116
  }
81396
82117
  var DefaultNativeCapletsService = class {
@@ -81751,31 +82472,55 @@ function createLocalOverlayService(options) {
81751
82472
  return (options.localServiceFactory ?? createDefaultNativeCapletsService)(localOptions);
81752
82473
  }
81753
82474
  function createCompositeRemoteService(remoteOptions, local, options, authKind) {
81754
- return new CompositeNativeCapletsService(new RemoteNativeCapletsService({
81755
- client: (options.remoteClientFactory ?? createSdkRemoteCapletsClient)(remoteOptions),
81756
- clientFactory: () => (options.remoteClientFactory ?? createSdkRemoteCapletsClient)(remoteOptions),
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),
81757
82482
  pollIntervalMs: remoteOptions.pollIntervalMs,
81758
82483
  authKind,
81759
82484
  ...options.writeErr ? { writeErr: options.writeErr } : {}
81760
- }), local, options, createProjectBindingSessionManager(remoteOptions.cloud, local, options));
82485
+ });
82486
+ const presence = createProjectBindingSessionManager(remoteOptions.cloud, local, options);
82487
+ return {
82488
+ remote,
82489
+ ...presence ? { presence } : {}
82490
+ };
82491
+ }
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
+ });
81761
82500
  }
81762
- var CloudNativeCapletsService = class {
82501
+ var ProfileBackedNativeCapletsService = class {
81763
82502
  options;
81764
82503
  baseRemote;
82504
+ authKind;
81765
82505
  local;
81766
82506
  listeners = /* @__PURE__ */ new Set();
81767
82507
  delegate;
81768
82508
  unsubscribeDelegate;
82509
+ remoteSignature;
82510
+ credentialExpiresAt;
82511
+ ensureDelegateCurrentInFlight;
81769
82512
  closed = false;
81770
- constructor(options, baseRemote) {
82513
+ constructor(options, baseRemote, authKind) {
81771
82514
  this.options = options;
81772
82515
  this.baseRemote = baseRemote;
82516
+ this.authKind = authKind;
81773
82517
  this.local = createLocalOverlayService(options);
81774
82518
  }
81775
82519
  listTools() {
81776
82520
  return this.delegate?.listTools() ?? this.local.listTools();
81777
82521
  }
81778
82522
  async execute(capletId, request) {
82523
+ if (!this.delegate || nativeCredentialsNeedRefresh(this.credentialExpiresAt)) await this.ensureDelegateCurrent();
81779
82524
  return await (this.delegate ?? this.local).execute(capletId, request);
81780
82525
  }
81781
82526
  codeModeService() {
@@ -81783,42 +82528,8 @@ var CloudNativeCapletsService = class {
81783
82528
  }
81784
82529
  async reload() {
81785
82530
  if (this.closed) return false;
81786
- if (!this.delegate) try {
81787
- const cloudFetch = this.options.remote?.fetch;
81788
- const remoteUrl = this.options.remote?.url ?? this.baseRemote.url.toString().replace(/\/mcp$/u, "");
81789
- const selection = await resolveRemoteSelection({
81790
- mode: "cloud",
81791
- remoteUrl,
81792
- ...cloudFetch ? { fetch: cloudFetch } : {}
81793
- }, {
81794
- ...process.env,
81795
- CAPLETS_MODE: "cloud",
81796
- CAPLETS_REMOTE_URL: remoteUrl
81797
- });
81798
- if (selection.kind !== "hosted_cloud") throw new CapletsError("REQUEST_INVALID", "CAPLETS_MODE=cloud requires Caplets Cloud.");
81799
- const cloudPresence = {
81800
- url: selection.cloudPresence.url,
81801
- accessToken: selection.cloudPresence.accessToken,
81802
- workspaceId: selection.cloudPresence.workspaceId,
81803
- ...this.options.remote?.cloud?.projectRoot ? { projectRoot: this.options.remote.cloud.projectRoot } : {},
81804
- heartbeatIntervalMs: this.options.remote?.cloud?.heartbeatIntervalMs ?? this.baseRemote.cloud?.heartbeatIntervalMs ?? 3e4
81805
- };
81806
- const remoteOptions = {
81807
- ...this.baseRemote,
81808
- url: selection.remote.attachUrl,
81809
- auth: nativeAuthFromRemoteAuth(selection.remote.auth),
81810
- requestInit: selection.remote.requestInit,
81811
- ...selection.remote.fetch ? { fetch: selection.remote.fetch } : {},
81812
- cloud: cloudPresence
81813
- };
81814
- this.delegate = createCompositeRemoteService(remoteOptions, this.local, this.options, "hosted_cloud");
81815
- this.unsubscribeDelegate = this.delegate.onToolsChanged((tools) => this.emit(tools));
81816
- } catch (error) {
81817
- if (this.options.mode === "cloud") throw error;
81818
- warnRemoteProjectBindingFallback(this.options);
81819
- return await this.local.reload();
81820
- }
81821
- return await this.delegate.reload();
82531
+ await this.ensureDelegateCurrent();
82532
+ return await (this.delegate ?? this.local).reload();
81822
82533
  }
81823
82534
  onToolsChanged(listener) {
81824
82535
  this.listeners.add(listener);
@@ -81834,13 +82545,110 @@ var CloudNativeCapletsService = class {
81834
82545
  emit(tools) {
81835
82546
  for (const listener of this.listeners) listener(tools);
81836
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
+ }
81837
82620
  };
81838
- function nativeAuthFromRemoteAuth(auth) {
81839
- if (auth.type === "basic") return {
81840
- enabled: true,
81841
- user: auth.user,
81842
- password: auth.password
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 } : {}
81843
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) {
81844
82652
  if (auth.type === "none") return {
81845
82653
  enabled: false,
81846
82654
  user: auth.user
@@ -81856,7 +82664,8 @@ var CompositeNativeCapletsService = class {
81856
82664
  options;
81857
82665
  presence;
81858
82666
  listeners = /* @__PURE__ */ new Set();
81859
- unsubscribers;
82667
+ unsubscribeRemote;
82668
+ unsubscribeLocal;
81860
82669
  warnedShadowedLocalCaplets = /* @__PURE__ */ new Set();
81861
82670
  tools = [];
81862
82671
  closed = false;
@@ -81867,11 +82676,10 @@ var CompositeNativeCapletsService = class {
81867
82676
  this.local = local;
81868
82677
  this.options = options;
81869
82678
  this.presence = presence;
81870
- this.unsubscribers = [this.remote.onToolsChanged(() => this.updateMergedTools()), this.local.onToolsChanged(() => this.updateMergedTools())];
82679
+ this.unsubscribeRemote = this.remote.onToolsChanged(() => this.updateMergedTools());
82680
+ this.unsubscribeLocal = this.local.onToolsChanged(() => this.updateMergedTools());
81871
82681
  this.tools = this.mergeTools();
81872
- this.presence?.start().catch((error) => {
81873
- writeErr(options, `Could not register Caplets Cloud Project Binding: ${errorMessage(error)}\n`);
81874
- });
82682
+ this.startPresence();
81875
82683
  }
81876
82684
  listTools() {
81877
82685
  return [...this.tools];
@@ -81909,7 +82717,8 @@ var CompositeNativeCapletsService = class {
81909
82717
  async close() {
81910
82718
  if (this.closed) return;
81911
82719
  this.closed = true;
81912
- for (const unsubscribe of this.unsubscribers.splice(0)) unsubscribe();
82720
+ this.unsubscribeRemote();
82721
+ this.unsubscribeLocal();
81913
82722
  this.listeners.clear();
81914
82723
  this.codeModeSessions.close();
81915
82724
  await Promise.all([
@@ -81918,6 +82727,22 @@ var CompositeNativeCapletsService = class {
81918
82727
  this.presence?.close()
81919
82728
  ]);
81920
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
+ }
81921
82746
  updateMergedTools() {
81922
82747
  if (this.closed || this.batchingReload) return;
81923
82748
  const tools = this.mergeTools();
@@ -81962,6 +82787,11 @@ var CompositeNativeCapletsService = class {
81962
82787
  return;
81963
82788
  }
81964
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
+ }
81965
82795
  };
81966
82796
  function remoteCodeModeCallableNativeTools(tools) {
81967
82797
  return codeModeCallableNativeTools(tools, { fallbackToVisible: true });
@@ -82037,4 +82867,4 @@ function errorMessage(error) {
82037
82867
  return error instanceof Error ? error.message : String(error);
82038
82868
  }
82039
82869
  //#endregion
82040
- export { GoogleDiscoveryManager as $, ListPromptsRequestSchema as $t, emptyCodeModeRunMeta as A, ReadBuffer as At, codeModeDeclarationHash as B, CompleteRequestSchema as Bt, nativeCapletToolDescription as C, safeParse as Cn, defaultCacheBaseDir as Ct, nativeCodeModeToolName as D, resolveConfigPath as Dt, nativeCodeModeToolId as E, resolveCapletsRoot as Et, createCodeModeCapletsApi as F, Protocol as Ft, resolveExposure as G, ElicitResultSchema as Gt, generateCodeModeRunToolDescription as H, CreateMessageResultWithToolsSchema as Ht, listCodeModeCallableCaplets as I, mergeCapabilities as It, findProjectRoot as J, GetPromptRequestSchema as Jt, decodeDirectResourceUri as K, EmptyResultSchema as Kt, CodeModeJournalStore as L, toJsonSchemaCompat as Lt, CodeModeSessionManager as M, assertClientRequestTaskCapability as Mt, QuickJsCodeModeSandbox as N, assertToolsCallTaskCapability as Nt, codeModeRunInputSchema as O, resolveProjectCapletsRoot as Ot, diagnoseCodeModeTypeScript as P, AjvJsonSchemaValidator as Pt, capabilityDescription as Q, LATEST_PROTOCOL_VERSION as Qt, CodeModeLogStore as R, CallToolRequestSchema as Rt, nativeCapletPromptGuidance as S, objectFromShape as Sn, DEFAULT_OBSERVED_OUTPUT_SHAPE_CACHE_DIR as St, nativeCapletsSystemGuidance as T, __exportAll as Tn, defaultStateBaseDir as Tt, minifyCodeModeDeclarationText as U, CreateTaskResultSchema as Ut, generateCodeModeDeclarations as V, CreateMessageResultSchema as Vt, CapletsEngine as W, DEFAULT_NEGOTIATED_PROTOCOL_VERSION as Wt, handleServerTool as X, InitializedNotificationSchema as Xt, fingerprintProjectRoot as Y, InitializeRequestSchema as Yt, ServerRegistry as Z, JSONRPCMessageSchema as Zt, resolveHostedCloudRemote as _, getParseErrorMessage as _n, deleteTokenBundle as _t, projectBindingError as a, McpError as an, parseConfig as at, parseServerBaseUrl as b, isZ4Schema as bn, DEFAULT_AUTH_DIR as bt, cloudAuthPath as c, SetLevelRequestSchema as cn, loadCapletFilesFromMap as ct, CloudAuthClient as d, isInitializeRequest as dn, markdownStructuredContent as dt, ListResourceTemplatesRequestSchema as en, loadConfig as et, RemoteNativeCapletsService as f, isJSONRPCErrorResponse as fn, refreshOAuthTokenBundle as ft, resolveCapletsRemote as g, getObjectShape as gn, startOAuthFlow as gt, resolveNativeCapletsServiceOptions as h, getLiteralValue as hn, startGenericOAuthFlow as ht, ProjectBindingError as i, LoggingLevelSchema as in, loadProjectConfig as it, runCodeMode as j, serializeMessage as jt, codeModeRunParamsSchema as k, resolveProjectConfigPath as kt, migrateCredentials as l, assertCompleteRequestPrompt as ln, hasRenderableStructuredContent as lt, buildProjectSyncManifest as m, isJSONRPCResultResponse as mn, runOAuthFlow as mt, resolveRemoteSelection as n, ListRootsResultSchema as nn, loadGlobalConfig as nt, projectBindingRecovery as o, ReadResourceRequestSchema as on, discoverCapletFiles as ot, createSdkRemoteCapletsClient as p, isJSONRPCRequest as pn, runGenericOAuthFlow as pt, directResourceUriMatchesTemplate as q, ErrorCode as qt, PROJECT_BINDING_ERROR_CODES as r, ListToolsRequestSchema as rn, loadLocalOverlayConfigWithSources as rt, CloudAuthStore as s, SUPPORTED_PROTOCOL_VERSIONS as sn, validateCapletFile as st, createNativeCapletsService as t, ListResourcesRequestSchema as tn, loadConfigWithSources as tt, redactedCloudAuthStatus as u, assertCompleteRequestResourceTemplate as un, markdownCallToolResultContent as ut, resolveRemoteMode as v, getSchemaDescription as vn, isTokenBundleExpired as vt, nativeCapletToolName as w, safeParseAsync as wn, defaultConfigBaseDir as wt, resolveCapletsServer as x, normalizeObjectSchema as xn, DEFAULT_COMPLETION_CACHE_DIR as xt, controlUrlForBase as y, isSchemaOptional as yn, readTokenBundle as yt, redactCodeModeLogText as z, CallToolResultSchema as zt };
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 };