@caplets/core 0.18.9 → 0.19.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 (82) hide show
  1. package/dist/attach/options.d.ts +10 -0
  2. package/dist/attach/server.d.ts +5 -0
  3. package/dist/caplet-files-bundle.d.ts +415 -0
  4. package/dist/caplet-files.d.ts +3 -266
  5. package/dist/caplet-source/bundle.d.ts +7 -0
  6. package/dist/caplet-source/filesystem.d.ts +7 -0
  7. package/dist/caplet-source/filesystem.js +2 -0
  8. package/dist/caplet-source/index.d.ts +4 -0
  9. package/dist/caplet-source/parse.d.ts +35 -0
  10. package/dist/caplet-source/types.d.ts +9 -0
  11. package/dist/caplet-source.js +11231 -0
  12. package/dist/cli/commands.d.ts +6 -1
  13. package/dist/cli/doctor.d.ts +18 -0
  14. package/dist/cli/setup-caplet.d.ts +12 -0
  15. package/dist/cli/setup.d.ts +4 -0
  16. package/dist/cli.d.ts +7 -1
  17. package/dist/cloud/apply.d.ts +36 -0
  18. package/dist/cloud/client.d.ts +30 -0
  19. package/dist/cloud/presence.d.ts +29 -0
  20. package/dist/cloud/project-root.d.ts +2 -0
  21. package/dist/cloud/runtime-adapter.d.ts +23 -0
  22. package/dist/cloud/runtime-http.d.ts +6 -0
  23. package/dist/cloud/sync.d.ts +10 -0
  24. package/dist/cloud-auth/client.d.ts +42 -0
  25. package/dist/cloud-auth/errors.d.ts +11 -0
  26. package/dist/cloud-auth/open-url.d.ts +7 -0
  27. package/dist/cloud-auth/store.d.ts +35 -0
  28. package/dist/cloud-auth/types.d.ts +66 -0
  29. package/dist/{completion-RqzHpHRY.js → completion-brgziz4L.js} +17 -1
  30. package/dist/config-runtime.d.ts +174 -0
  31. package/dist/config-runtime.js +392 -0
  32. package/dist/config.d.ts +42 -0
  33. package/dist/filesystem-Kkg32TOJ.js +66 -0
  34. package/dist/generated-tool-input-schema.js +161 -1
  35. package/dist/index.d.ts +35 -0
  36. package/dist/index.js +2999 -266
  37. package/dist/native/options.d.ts +22 -3
  38. package/dist/native/remote.d.ts +2 -1
  39. package/dist/native/service.d.ts +7 -3
  40. package/dist/native.js +2 -430
  41. package/dist/project-binding/attach.d.ts +46 -0
  42. package/dist/project-binding/errors.d.ts +17 -0
  43. package/dist/project-binding/gitignore.d.ts +5 -0
  44. package/dist/project-binding/mutagen.d.ts +65 -0
  45. package/dist/project-binding/routes.d.ts +9 -0
  46. package/dist/project-binding/session.d.ts +82 -0
  47. package/dist/project-binding/sync-filter.d.ts +19 -0
  48. package/dist/project-binding/sync-size.d.ts +27 -0
  49. package/dist/project-binding/transport.d.ts +21 -0
  50. package/dist/project-binding/types.d.ts +31 -0
  51. package/dist/project-binding/workspaces.d.ts +60 -0
  52. package/dist/remote/options.d.ts +42 -0
  53. package/dist/remote/selection.d.ts +26 -0
  54. package/dist/runtime-plan/features.d.ts +7 -0
  55. package/dist/runtime-plan/index.d.ts +4 -0
  56. package/dist/runtime-plan/planner.d.ts +5 -0
  57. package/dist/runtime-plan/resources.d.ts +11 -0
  58. package/dist/runtime-plan/types.d.ts +82 -0
  59. package/dist/runtime-plan.js +275 -0
  60. package/dist/{generated-tool-input-schema--kVuUNc5.js → schemas-BZ6BBrh7.js} +1 -161
  61. package/dist/serve/daemon/config.d.ts +8 -0
  62. package/dist/serve/daemon/index.d.ts +16 -0
  63. package/dist/serve/daemon/paths.d.ts +3 -0
  64. package/dist/serve/daemon/platform-darwin.d.ts +2 -0
  65. package/dist/serve/daemon/platform-linux.d.ts +2 -0
  66. package/dist/serve/daemon/platform-windows.d.ts +2 -0
  67. package/dist/serve/daemon/platform.d.ts +9 -0
  68. package/dist/serve/daemon/process.d.ts +5 -0
  69. package/dist/serve/daemon/types.d.ts +86 -0
  70. package/dist/serve/http.d.ts +8 -0
  71. package/dist/serve/index.d.ts +5 -1
  72. package/dist/serve/native-session.d.ts +19 -0
  73. package/dist/serve/options.d.ts +1 -0
  74. package/dist/server/options.d.ts +1 -1
  75. package/dist/{options-DnOUjft1.js → service-BXcE4Rv8.js} +9891 -2182
  76. package/dist/setup/hash.d.ts +3 -0
  77. package/dist/setup/local-store.d.ts +34 -0
  78. package/dist/setup/runner.d.ts +40 -0
  79. package/dist/setup/types.d.ts +52 -0
  80. package/dist/tools.d.ts +16 -1
  81. package/dist/validation-BBG4skZf.js +153 -0
  82. package/package.json +20 -4
package/dist/index.js CHANGED
@@ -1,17 +1,22 @@
1
- import { $ as assertCompleteRequestPrompt, A as CreateMessageResultSchema, At as hasRenderableStructuredContent, B as JSONRPCMessageSchema, C as AjvJsonSchemaValidator, Ct as resolveCapletsRoot, D as CallToolRequestSchema, Dt as discoverCapletFiles, E as toJsonSchemaCompat, Et as resolveProjectConfigPath, F as EmptyResultSchema, Ft as redactSecrets, G as ListRootsResultSchema, H as ListPromptsRequestSchema, I as ErrorCode, It as toSafeError, J as McpError, K as ListToolsRequestSchema, L as GetPromptRequestSchema, M as CreateTaskResultSchema, Mt as markdownStructuredContent, Nt as CAPLETS_ERROR_CODES, O as CallToolResultSchema, Ot as validateCapletFile, P as ElicitResultSchema, Pt as CapletsError, R as InitializeRequestSchema, S as assertToolsCallTaskCapability, T as mergeCapabilities, Tt as resolveProjectCapletsRoot, U as ListResourceTemplatesRequestSchema, V as LATEST_PROTOCOL_VERSION, W as ListResourcesRequestSchema, X as SUPPORTED_PROTOCOL_VERSIONS, Y as ReadResourceRequestSchema, Z as SetLevelRequestSchema, _t as loadGlobalConfig, a as resolveCapletsServer, at as getLiteralValue, bt as parseConfig, c as ServerRegistry, ct as getSchemaDescription, d as runOAuthFlow, dt as normalizeObjectSchema, et as assertCompleteRequestResourceTemplate, f as startGenericOAuthFlow, ft as objectFromShape, g as readTokenBundle, gt as loadConfigWithSources, h as isTokenBundleExpired, ht as loadConfig, i as resolveCapletsMode, it as isJSONRPCResultResponse, j as CreateMessageResultWithToolsSchema, jt as markdownCallToolResultContent, k as CompleteRequestSchema, kt as SERVER_ID_PATTERN, l as capabilityDescription, lt as isSchemaOptional, m as deleteTokenBundle, mt as safeParseAsync, nt as isJSONRPCErrorResponse, o as CapletsEngine, ot as getObjectShape, p as startOAuthFlow, pt as safeParse, q as LoggingLevelSchema, r as parseServerBaseUrl, rt as isJSONRPCRequest, s as handleServerTool, st as getParseErrorMessage, t as controlUrlForBase, tt as isInitializeRequest, u as runGenericOAuthFlow, ut as isZ4Schema, v as ReadBuffer, vt as loadLocalOverlayConfigWithSources, w as Protocol, wt as resolveConfigPath, x as assertClientRequestTaskCapability, y as serializeMessage, yt as loadProjectConfig, z as InitializedNotificationSchema } from "./options-DnOUjft1.js";
2
- import { A as url, C as object, D as string, b as literal, d as ZodOptional, o as generatedToolInputSchema, s as generatedToolInputSchemaForCaplet } from "./generated-tool-input-schema--kVuUNc5.js";
3
- import { a as formatCapletList, c as resolveCliConfigPaths, l as cliCommands, n as completionScript, o as formatConfigPaths, s as listCaplets, t as completeCliWords, u as completionShells } from "./completion-RqzHpHRY.js";
1
+ import { $ as ElicitResultSchema, $t as markdownStructuredContent, A as capabilityDescription, At as objectFromShape, B as assertClientRequestTaskCapability, C as migrateCredentials, Ct as getLiteralValue, D as CapletsEngine, Dt as isSchemaOptional, E as fingerprintProjectRoot, Et as getSchemaDescription, F as deleteTokenBundle, Ft as loadGlobalConfig, G as toJsonSchemaCompat, Gt as resolveConfigPath, H as AjvJsonSchemaValidator, Ht as defaultConfigBaseDir, I as isTokenBundleExpired, It as loadLocalOverlayConfigWithSources, J as CompleteRequestSchema, Jt as discoverCapletFiles, K as CallToolRequestSchema, Kt as resolveProjectCapletsRoot, L as readTokenBundle, Lt as loadProjectConfig, M as runOAuthFlow, Mt as safeParseAsync, N as startGenericOAuthFlow, Nt as loadConfig, O as handleServerTool, Ot as isZ4Schema, P as startOAuthFlow, Pt as loadConfigWithSources, Qt as markdownCallToolResultContent, R as ReadBuffer, Rt as parseConfig, S as cloudAuthPath, St as isJSONRPCResultResponse, T as findProjectRoot, Tt as getParseErrorMessage, U as Protocol, Ut as defaultStateBaseDir, V as assertToolsCallTaskCapability, Vt as defaultCacheBaseDir, W as mergeCapabilities, Wt as resolveCapletsRoot, X as CreateMessageResultWithToolsSchema, Xt as loadCapletFilesFromMap, Y as CreateMessageResultSchema, Yt as validateCapletFile, Z as CreateTaskResultSchema, Zt as hasRenderableStructuredContent, _ as controlUrlForBase, _t as assertCompleteRequestPrompt, a as createSdkRemoteCapletsClient, at as JSONRPCMessageSchema, b as resolveCapletsServer, bt as isJSONRPCErrorResponse, ct as ListResourceTemplatesRequestSchema, d as PROJECT_BINDING_ERROR_CODES, dt as ListToolsRequestSchema, et as EmptyResultSchema, f as ProjectBindingError, ft as LoggingLevelSchema, g as resolveCapletsRemote, gt as SetLevelRequestSchema, h as CloudAuthClient, ht as SUPPORTED_PROTOCOL_VERSIONS, it as InitializedNotificationSchema, j as runGenericOAuthFlow, jt as safeParse, k as ServerRegistry, kt as normalizeObjectSchema, lt as ListResourcesRequestSchema, m as projectBindingRecovery, mt as ReadResourceRequestSchema, n as buildProjectSyncManifest, nt as GetPromptRequestSchema, ot as LATEST_PROTOCOL_VERSION, p as projectBindingError, pt as McpError, q as CallToolResultSchema, qt as resolveProjectConfigPath, rt as InitializeRequestSchema, st as ListPromptsRequestSchema, t as createNativeCapletsService, tt as ErrorCode, u as resolveRemoteSelection, ut as ListRootsResultSchema, v as parseServerBaseUrl, vt as assertCompleteRequestResourceTemplate, w as redactedCloudAuthStatus, wt as getObjectShape, x as CloudAuthStore, xt as isJSONRPCRequest, y as resolveCapletsMode, yt as isInitializeRequest, z as serializeMessage } from "./service-BXcE4Rv8.js";
2
+ import { f as redactSecrets, i as SERVER_ID_PATTERN, l as CAPLETS_ERROR_CODES, p as toSafeError, u as CapletsError } from "./validation-BBG4skZf.js";
3
+ import { _ as record, b as unknown, d as literal, m as object, n as ZodOptional, o as array, p as number, r as _enum, s as boolean, v as string, x as url } from "./schemas-BZ6BBrh7.js";
4
+ import { generatedToolInputSchema, generatedToolInputSchemaForCaplet } from "./generated-tool-input-schema.js";
5
+ import { a as formatCapletList, c as resolveCliConfigPaths, l as cliCommands$1, n as completionScript, o as formatConfigPaths, s as listCaplets, t as completeCliWords, u as completionShells } from "./completion-brgziz4L.js";
6
+ import { n as normalizeCapletSourcePath, t as FilesystemCapletSource } from "./filesystem-Kkg32TOJ.js";
7
+ import { parseConfig as parseConfig$1 } from "./config-runtime.js";
4
8
  import fs, { accessSync, chmodSync, closeSync, constants, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
5
- import path, { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
6
- import childProcess, { execFile, execFileSync } from "node:child_process";
9
+ import path, { basename, dirname, isAbsolute, join, parse, posix, relative, resolve, sep, win32 } from "node:path";
10
+ import childProcess, { execFile, execFileSync, spawn } from "node:child_process";
11
+ import { homedir, tmpdir } from "node:os";
7
12
  import process$1, { stdin, stdout } from "node:process";
8
- import { tmpdir } from "node:os";
9
13
  import { Readable } from "node:stream";
10
14
  import { STATUS_CODES, createServer } from "node:http";
11
- import { randomUUID, timingSafeEqual } from "node:crypto";
15
+ import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
12
16
  import { EventEmitter } from "node:events";
13
17
  import { promisify, stripVTControlCharacters } from "node:util";
14
18
  import { createInterface } from "node:readline/promises";
19
+ import { Buffer as Buffer$1 } from "node:buffer";
15
20
  import { Http2ServerRequest, constants as constants$1 } from "node:http2";
16
21
  //#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
17
22
  /**
@@ -1322,7 +1327,7 @@ const EMPTY_COMPLETION_RESULT = { completion: {
1322
1327
  } };
1323
1328
  //#endregion
1324
1329
  //#region package.json
1325
- var version = "0.18.9";
1330
+ var version = "0.19.0";
1326
1331
  //#endregion
1327
1332
  //#region src/serve/session.ts
1328
1333
  var CapletsMcpSession = class {
@@ -4741,11 +4746,14 @@ function renderLocalPaths(fields, outputDir) {
4741
4746
  });
4742
4747
  }
4743
4748
  function localPathRelativeToOutput(path, outputDir) {
4744
- const absolutePath = resolve(path);
4745
- const rendered = relative(outputDir, resolve(path));
4749
+ const absolutePath = displayPath(resolve(path));
4750
+ const rendered = relative(outputDir, absolutePath);
4746
4751
  if (rendered.startsWith("../..") || rendered.startsWith("..\\..")) return absolutePath;
4747
4752
  return rendered === "" ? "." : rendered;
4748
4753
  }
4754
+ function displayPath(path) {
4755
+ return path;
4756
+ }
4749
4757
  function rejectUnsafeDestinationParents(path) {
4750
4758
  const parent = dirname(resolve(path));
4751
4759
  const root = parse(parent).root;
@@ -4755,10 +4763,22 @@ function rejectUnsafeDestinationParents(path) {
4755
4763
  current = join(current, segment);
4756
4764
  const stats = lstatIfExists$1(current);
4757
4765
  if (!stats) return;
4758
- if (stats.isSymbolicLink()) throw new CapletsError("CONFIG_EXISTS", `Output parent path ${current} is a symlink; remove it before writing`);
4766
+ if (stats.isSymbolicLink()) {
4767
+ if (isDarwinSystemAliasSymlink$1(current)) continue;
4768
+ throw new CapletsError("CONFIG_EXISTS", `Output parent path ${current} is a symlink; remove it before writing`);
4769
+ }
4759
4770
  if (!stats.isDirectory()) throw new CapletsError("CONFIG_EXISTS", `Output parent path ${current} is not a directory; choose a file path`);
4760
4771
  }
4761
4772
  }
4773
+ function isDarwinSystemAliasSymlink$1(path) {
4774
+ if (process.platform !== "darwin") return false;
4775
+ if (path !== "/var" && path !== "/tmp") return false;
4776
+ try {
4777
+ return realpathSync(path) === `/private${path}`;
4778
+ } catch {
4779
+ return false;
4780
+ }
4781
+ }
4762
4782
  function lstatIfExists$1(path) {
4763
4783
  try {
4764
4784
  return lstatSync(path);
@@ -5009,6 +5029,344 @@ function starterConfig() {
5009
5029
  }, null, 2);
5010
5030
  }
5011
5031
  //#endregion
5032
+ //#region src/project-binding/workspaces.ts
5033
+ const DEFAULT_STALE_LEASE_TTL_MS = 120 * 1e3;
5034
+ const DEFAULT_INACTIVE_WORKSPACE_TTL_MS = 720 * 60 * 60 * 1e3;
5035
+ const DEFAULT_SOFT_DISK_CAP_BYTES = 10 * 1024 * 1024 * 1024;
5036
+ function projectBindingWorkspaceRoot(options = {}) {
5037
+ if (options.root) return options.root;
5038
+ const platform = options.platform ?? process.platform;
5039
+ const home = options.homedir ?? homedir();
5040
+ const env = options.env ?? process.env;
5041
+ if (platform === "win32") {
5042
+ const base = env.LOCALAPPDATA && win32.isAbsolute(env.LOCALAPPDATA) ? env.LOCALAPPDATA : win32.join(home, "AppData", "Local");
5043
+ return win32.join(base, "Caplets", "State", "workspaces");
5044
+ }
5045
+ const base = env.XDG_STATE_HOME && posix.isAbsolute(env.XDG_STATE_HOME) ? env.XDG_STATE_HOME : posix.join(home, ".local", "state");
5046
+ return posix.join(base, "caplets", "workspaces");
5047
+ }
5048
+ function projectBindingWorkspacePaths(projectFingerprint, options = {}) {
5049
+ assertPathSegment(projectFingerprint, "project fingerprint");
5050
+ const pathJoin = pathJoinFor(options.platform);
5051
+ const root = pathJoin(projectBindingWorkspaceRoot(options), projectFingerprint);
5052
+ const leases = pathJoin(root, "leases");
5053
+ const setup = pathJoin(root, "setup");
5054
+ return {
5055
+ projectFingerprint,
5056
+ root,
5057
+ project: pathJoin(root, "project"),
5058
+ metadata: pathJoin(root, "metadata.json"),
5059
+ leases,
5060
+ setup,
5061
+ setupReceipts: pathJoin(setup, "receipts.json"),
5062
+ lease(bindingId) {
5063
+ assertPathSegment(bindingId, "binding ID");
5064
+ return pathJoin(leases, `${bindingId}.json`);
5065
+ }
5066
+ };
5067
+ }
5068
+ var ProjectBindingWorkspaceStore = class {
5069
+ options;
5070
+ root;
5071
+ now;
5072
+ staleLeaseTtlMs;
5073
+ inactiveWorkspaceTtlMs;
5074
+ softDiskCapBytes;
5075
+ workspaceSizeBytes;
5076
+ constructor(options = {}) {
5077
+ this.options = options;
5078
+ this.root = projectBindingWorkspaceRoot(options);
5079
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
5080
+ this.staleLeaseTtlMs = options.staleLeaseTtlMs ?? DEFAULT_STALE_LEASE_TTL_MS;
5081
+ this.inactiveWorkspaceTtlMs = options.inactiveWorkspaceTtlMs ?? DEFAULT_INACTIVE_WORKSPACE_TTL_MS;
5082
+ this.softDiskCapBytes = options.softDiskCapBytes ?? DEFAULT_SOFT_DISK_CAP_BYTES;
5083
+ this.workspaceSizeBytes = options.workspaceSizeBytes ?? ((paths) => directorySize(paths.root));
5084
+ }
5085
+ paths(projectFingerprint) {
5086
+ return projectBindingWorkspacePaths(projectFingerprint, {
5087
+ ...this.options,
5088
+ root: this.root
5089
+ });
5090
+ }
5091
+ async ensureWorkspace(input) {
5092
+ const paths = this.paths(input.projectFingerprint);
5093
+ const now = this.now().toISOString();
5094
+ const existing = this.readMetadata(input.projectFingerprint);
5095
+ const metadata = {
5096
+ projectFingerprint: input.projectFingerprint,
5097
+ projectRoot: input.projectRoot,
5098
+ createdAt: input.createdAt ?? existing?.createdAt ?? now,
5099
+ lastActiveAt: input.lastActiveAt ?? now
5100
+ };
5101
+ mkdirSync(paths.project, { recursive: true });
5102
+ mkdirSync(paths.leases, { recursive: true });
5103
+ mkdirSync(paths.setup, { recursive: true });
5104
+ writeJson$1(paths.metadata, metadata);
5105
+ return paths;
5106
+ }
5107
+ async writeLease(lease) {
5108
+ const paths = this.paths(lease.projectFingerprint);
5109
+ mkdirSync(paths.leases, { recursive: true });
5110
+ writeJson$1(paths.lease(lease.bindingId), lease);
5111
+ if (lease.active) {
5112
+ const metadata = this.readMetadata(lease.projectFingerprint);
5113
+ if (metadata) writeJson$1(paths.metadata, {
5114
+ ...metadata,
5115
+ lastActiveAt: lease.updatedAt
5116
+ });
5117
+ }
5118
+ }
5119
+ async listLeases(projectFingerprint) {
5120
+ return this.leasesFor(this.paths(projectFingerprint)).map((entry) => entry.lease);
5121
+ }
5122
+ async writeSetupReceipts(projectFingerprint, receipts) {
5123
+ const paths = this.paths(projectFingerprint);
5124
+ mkdirSync(paths.setup, { recursive: true });
5125
+ writeJson$1(paths.setupReceipts, receipts);
5126
+ }
5127
+ async cleanup() {
5128
+ const expiredLeases = [];
5129
+ const deletedWorkspaces = [];
5130
+ const retainedWorkspaces = [];
5131
+ const candidates = [];
5132
+ for (const paths of this.workspacePaths()) {
5133
+ const leases = this.leasesFor(paths);
5134
+ for (const entry of leases) if (!entry.lease.active && this.isStaleLease(entry.lease)) {
5135
+ rmSync(entry.path, { force: true });
5136
+ expiredLeases.push(entry.path);
5137
+ }
5138
+ if (this.leasesFor(paths).some((entry) => entry.lease.active)) {
5139
+ retainedWorkspaces.push(paths.root);
5140
+ continue;
5141
+ }
5142
+ const metadata = this.readMetadata(paths.projectFingerprint);
5143
+ const lastActiveMs = metadata ? Date.parse(metadata.lastActiveAt) : workspaceMtime(paths.root);
5144
+ const sizeBytes = this.workspaceSizeBytes(paths);
5145
+ candidates.push({
5146
+ paths,
5147
+ lastActiveMs,
5148
+ sizeBytes
5149
+ });
5150
+ }
5151
+ for (const candidate of candidates) if (this.isInactiveWorkspace(candidate.lastActiveMs)) {
5152
+ rmSync(candidate.paths.root, {
5153
+ recursive: true,
5154
+ force: true
5155
+ });
5156
+ deletedWorkspaces.push(candidate.paths.root);
5157
+ }
5158
+ const remaining = candidates.filter((candidate) => !deletedWorkspaces.includes(candidate.paths.root)).sort((first, second) => first.lastActiveMs - second.lastActiveMs);
5159
+ let totalBytes = remaining.reduce((sum, candidate) => sum + candidate.sizeBytes, 0);
5160
+ for (const candidate of remaining) {
5161
+ if (totalBytes <= this.softDiskCapBytes) {
5162
+ retainedWorkspaces.push(candidate.paths.root);
5163
+ continue;
5164
+ }
5165
+ rmSync(candidate.paths.root, {
5166
+ recursive: true,
5167
+ force: true
5168
+ });
5169
+ deletedWorkspaces.push(candidate.paths.root);
5170
+ totalBytes -= candidate.sizeBytes;
5171
+ }
5172
+ return {
5173
+ expiredLeases,
5174
+ deletedWorkspaces,
5175
+ retainedWorkspaces
5176
+ };
5177
+ }
5178
+ readMetadata(projectFingerprint) {
5179
+ const path = this.paths(projectFingerprint).metadata;
5180
+ if (!existsSync(path)) return void 0;
5181
+ return JSON.parse(readFileSync(path, "utf8"));
5182
+ }
5183
+ workspacePaths() {
5184
+ if (!existsSync(this.root)) return [];
5185
+ return readdirSync(this.root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => this.paths(entry.name));
5186
+ }
5187
+ leasesFor(paths) {
5188
+ if (!existsSync(paths.leases)) return [];
5189
+ return readdirSync(paths.leases, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => {
5190
+ const path = join(paths.leases, entry.name);
5191
+ return {
5192
+ path,
5193
+ lease: JSON.parse(readFileSync(path, "utf8"))
5194
+ };
5195
+ });
5196
+ }
5197
+ isStaleLease(lease) {
5198
+ const parsedExpiresAt = lease.expiresAt ? Date.parse(lease.expiresAt) : NaN;
5199
+ return (Number.isFinite(parsedExpiresAt) ? parsedExpiresAt : Date.parse(lease.updatedAt) + this.staleLeaseTtlMs) <= this.now().getTime();
5200
+ }
5201
+ isInactiveWorkspace(lastActiveMs) {
5202
+ return lastActiveMs + this.inactiveWorkspaceTtlMs <= this.now().getTime();
5203
+ }
5204
+ };
5205
+ function writeJson$1(path, value) {
5206
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
5207
+ }
5208
+ function directorySize(path) {
5209
+ if (!existsSync(path)) return 0;
5210
+ const stat = statSync(path);
5211
+ if (stat.isFile()) return stat.size;
5212
+ if (!stat.isDirectory()) return 0;
5213
+ return readdirSync(path).reduce((sum, entry) => sum + directorySize(join(path, entry)), 0);
5214
+ }
5215
+ function workspaceMtime(path) {
5216
+ return existsSync(path) ? statSync(path).mtimeMs : 0;
5217
+ }
5218
+ function pathJoinFor(platform = process.platform) {
5219
+ return platform === "win32" ? win32.join : join;
5220
+ }
5221
+ function assertPathSegment(value, label) {
5222
+ if (!value || value.includes("/") || value.includes("\\") || value.includes("\0") || value === "." || value === "..") throw new Error(`Invalid ${label}: ${value}`);
5223
+ }
5224
+ //#endregion
5225
+ //#region src/cli/doctor.ts
5226
+ async function doctorJsonReport(options = {}) {
5227
+ const env = options.env ?? process.env;
5228
+ const root = findProjectRoot(options.cwd ?? process.cwd());
5229
+ const projectFingerprint = fingerprintProjectRoot(root);
5230
+ const paths = projectBindingWorkspacePaths(projectFingerprint, { env });
5231
+ const server = resolveServerSection(env);
5232
+ const remote = resolveRemoteSection(env);
5233
+ const credentials = await (options.cloudAuthStore ?? new CloudAuthStore({ env })).load();
5234
+ return {
5235
+ server,
5236
+ remote,
5237
+ projectBinding: {
5238
+ state: "not_attached",
5239
+ projectRoot: root,
5240
+ projectFingerprint,
5241
+ workspacePath: paths.project,
5242
+ authMode: credentials ? "hosted_cloud" : remote.configured ? "self_hosted_remote" : "unconfigured",
5243
+ selectedWorkspace: credentials?.workspaceSlug ?? credentials?.workspaceId ?? remote.workspace ?? null,
5244
+ webSocketUrl: remote.webSocketUrl,
5245
+ lease: null,
5246
+ lastUpgradeError: null,
5247
+ recoveryCommand: credentials || remote.configured ? "caplets attach --once" : "caplets cloud auth login"
5248
+ },
5249
+ sync: {
5250
+ state: options.syncStatus?.state ?? "idle",
5251
+ diagnosticCode: options.syncStatus?.diagnosticCode ?? null,
5252
+ mutagenBinary: options.syncStatus?.mutagenBinary ?? "mutagen",
5253
+ mutagenVersion: options.syncStatus?.mutagenVersion ?? null,
5254
+ lastCommand: options.syncStatus?.lastCommand ?? null
5255
+ },
5256
+ daemon: {
5257
+ configured: false,
5258
+ running: false
5259
+ },
5260
+ cloudAuth: redactedCloudAuthStatus(credentials)
5261
+ };
5262
+ }
5263
+ async function formatDoctorReport(options = {}) {
5264
+ const report = await doctorJsonReport(options);
5265
+ return `${[
5266
+ "Server hosting",
5267
+ ` Configured: ${yesNo(Boolean(report.server.configured))}`,
5268
+ ...report.server.configured ? [` Base URL: ${report.server.baseUrl}`] : [],
5269
+ "",
5270
+ "Remote client",
5271
+ ` Configured: ${yesNo(Boolean(report.remote.configured))}`,
5272
+ ...report.remote.configured ? [
5273
+ ` MCP URL: ${report.remote.mcpUrl}`,
5274
+ ` Control URL: ${report.remote.controlUrl}`,
5275
+ ` Health URL: ${report.remote.healthUrl}`,
5276
+ ` WebSocket URL: ${report.remote.webSocketUrl}`,
5277
+ ` Auth: ${report.remote.auth}`
5278
+ ] : [],
5279
+ "",
5280
+ "Project Binding",
5281
+ ` State: ${report.projectBinding.state}`,
5282
+ ` Project root: ${report.projectBinding.projectRoot}`,
5283
+ ` Project fingerprint: ${report.projectBinding.projectFingerprint}`,
5284
+ ` Workspace path: ${report.projectBinding.workspacePath}`,
5285
+ ` Auth mode: ${report.projectBinding.authMode}`,
5286
+ ` Selected Workspace: ${report.projectBinding.selectedWorkspace ?? "none"}`,
5287
+ ` Binding Session: ${report.projectBinding.state}`,
5288
+ ` Recovery: ${report.projectBinding.recoveryCommand}`,
5289
+ "",
5290
+ "Project sync",
5291
+ ` State: ${report.sync.state}`,
5292
+ ` Mutagen: ${report.sync.mutagenVersion ?? report.sync.mutagenBinary}`,
5293
+ ...report.sync.diagnosticCode ? [` Diagnostic: ${report.sync.diagnosticCode}`] : [],
5294
+ "",
5295
+ "Daemon",
5296
+ ` Running: ${yesNo(Boolean(report.daemon.running))}`,
5297
+ "",
5298
+ "Cloud Auth",
5299
+ ` Authenticated: ${yesNo(Boolean(report.cloudAuth.authenticated))}`,
5300
+ ...report.cloudAuth.cloudUrl ? [` Cloud URL: ${report.cloudAuth.cloudUrl}`] : [],
5301
+ ...report.cloudAuth.workspaceSlug || report.cloudAuth.workspaceId ? [` Selected Workspace: ${report.cloudAuth.workspaceSlug ?? report.cloudAuth.workspaceId}`] : []
5302
+ ].join("\n")}\n`;
5303
+ }
5304
+ function resolveServerSection(env) {
5305
+ try {
5306
+ const server = resolveCapletsServer({}, env);
5307
+ return {
5308
+ configured: true,
5309
+ baseUrl: server.baseUrl.href,
5310
+ mcpUrl: server.mcpUrl.href,
5311
+ controlUrl: server.controlUrl.href,
5312
+ healthUrl: server.healthUrl.href,
5313
+ auth: server.auth.enabled ? "basic" : "none"
5314
+ };
5315
+ } catch {
5316
+ return { configured: false };
5317
+ }
5318
+ }
5319
+ function resolveRemoteSection(env) {
5320
+ try {
5321
+ const remote = resolveCapletsRemote({}, env);
5322
+ return {
5323
+ configured: true,
5324
+ baseUrl: remote.baseUrl.href,
5325
+ mcpUrl: remote.mcpUrl.href,
5326
+ controlUrl: remote.controlUrl.href,
5327
+ healthUrl: remote.healthUrl.href,
5328
+ webSocketUrl: remote.projectBindingWebSocketUrl.href,
5329
+ auth: remote.auth.type,
5330
+ tokenPresent: remote.auth.type === "bearer",
5331
+ workspace: remote.workspace ?? null
5332
+ };
5333
+ } catch {
5334
+ return { configured: false };
5335
+ }
5336
+ }
5337
+ function yesNo(value) {
5338
+ return value ? "yes" : "no";
5339
+ }
5340
+ //#endregion
5341
+ //#region src/cloud-auth/open-url.ts
5342
+ async function openBrowserUrl(url, options = {}) {
5343
+ if (options.opener) return await options.opener(url);
5344
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
5345
+ const args = process.platform === "win32" ? [
5346
+ "/c",
5347
+ "start",
5348
+ "",
5349
+ url
5350
+ ] : [url];
5351
+ return await new Promise((resolve) => {
5352
+ const child = spawn(command, args, {
5353
+ detached: true,
5354
+ stdio: "ignore"
5355
+ });
5356
+ child.once("error", () => resolve({
5357
+ opened: false,
5358
+ command
5359
+ }));
5360
+ child.once("spawn", () => {
5361
+ child.unref();
5362
+ resolve({
5363
+ opened: true,
5364
+ command
5365
+ });
5366
+ });
5367
+ });
5368
+ }
5369
+ //#endregion
5012
5370
  //#region src/cli/install.ts
5013
5371
  function installCaplets(repo, options = {}) {
5014
5372
  const source = resolveInstallSource(repo);
@@ -5118,10 +5476,22 @@ function rejectUnsafeInstallParents(path) {
5118
5476
  current = join(current, segment);
5119
5477
  const stats = lstatIfExists(current);
5120
5478
  if (!stats) return;
5121
- if (stats.isSymbolicLink()) throw new CapletsError("CONFIG_EXISTS", `Install destination parent ${current} is a symlink; remove it before installing`);
5479
+ if (stats.isSymbolicLink()) {
5480
+ if (isDarwinSystemAliasSymlink(current)) continue;
5481
+ throw new CapletsError("CONFIG_EXISTS", `Install destination parent ${current} is a symlink; remove it before installing`);
5482
+ }
5122
5483
  if (!stats.isDirectory()) throw new CapletsError("CONFIG_EXISTS", `Install destination parent ${current} is not a directory; choose another destination`);
5123
5484
  }
5124
5485
  }
5486
+ function isDarwinSystemAliasSymlink(path) {
5487
+ if (process.platform !== "darwin") return false;
5488
+ if (path !== "/var" && path !== "/tmp") return false;
5489
+ try {
5490
+ return realpathSync(path) === `/private${path}`;
5491
+ } catch {
5492
+ return false;
5493
+ }
5494
+ }
5125
5495
  function rejectUnsafeInstallDestination(plan, force) {
5126
5496
  const stats = lstatIfExists(plan.destination);
5127
5497
  if (!stats) return;
@@ -5270,6 +5640,348 @@ function nearestExistingParent(path) {
5270
5640
  return nearestExistingParent(parent);
5271
5641
  }
5272
5642
  //#endregion
5643
+ //#region src/setup/hash.ts
5644
+ function capletSetupContentHash(caplet) {
5645
+ return createHash("sha256").update(stableJson(stableCapletForHash(caplet))).digest("hex");
5646
+ }
5647
+ function stableJson(value) {
5648
+ return JSON.stringify(sortJson(value));
5649
+ }
5650
+ function stableCapletForHash(caplet) {
5651
+ return {
5652
+ server: caplet.server,
5653
+ name: caplet.name,
5654
+ description: caplet.description,
5655
+ backend: caplet.backend,
5656
+ tags: caplet.tags,
5657
+ body: caplet.body,
5658
+ setup: caplet.setup,
5659
+ backendConfig: Object.fromEntries(Object.entries(caplet).filter(([key]) => ![
5660
+ "server",
5661
+ "name",
5662
+ "description",
5663
+ "backend",
5664
+ "tags",
5665
+ "body",
5666
+ "setup",
5667
+ "disabled"
5668
+ ].includes(key)))
5669
+ };
5670
+ }
5671
+ function sortJson(value) {
5672
+ if (Array.isArray(value)) return value.map(sortJson);
5673
+ if (!value || typeof value !== "object") return value;
5674
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, sortJson(entry)]));
5675
+ }
5676
+ //#endregion
5677
+ //#region src/setup/types.ts
5678
+ const setupTargetKinds = [
5679
+ "local_host",
5680
+ "remote_host",
5681
+ "hosted_sandbox"
5682
+ ];
5683
+ function isSetupTargetKind(value) {
5684
+ return setupTargetKinds.includes(value);
5685
+ }
5686
+ //#endregion
5687
+ //#region src/setup/local-store.ts
5688
+ const DEFAULT_PROJECT_FINGERPRINT = "default";
5689
+ var LocalSetupStore = class {
5690
+ root;
5691
+ now;
5692
+ maxAttempts;
5693
+ retentionDays;
5694
+ constructor(options = {}) {
5695
+ this.root = options.baseDir ?? join(defaultCacheBaseDir(), "caplets", "setup");
5696
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
5697
+ this.maxAttempts = options.maxAttempts ?? 3;
5698
+ this.retentionDays = options.retentionDays ?? 7;
5699
+ }
5700
+ async getApproval(...args) {
5701
+ const [projectFingerprint, capletId, contentHash, targetKind] = args.length === 3 ? [
5702
+ DEFAULT_PROJECT_FINGERPRINT,
5703
+ args[0],
5704
+ args[1],
5705
+ args[2]
5706
+ ] : args;
5707
+ assertSetupTargetKind$1(targetKind);
5708
+ return this.approvals().find((approval) => approval.projectFingerprint === projectFingerprint && approval.capletId === capletId && approval.contentHash === contentHash && approval.targetKind === targetKind);
5709
+ }
5710
+ async approve(input) {
5711
+ const approval = {
5712
+ ...input,
5713
+ projectFingerprint: input.projectFingerprint ?? DEFAULT_PROJECT_FINGERPRINT
5714
+ };
5715
+ assertSetupTargetKind$1(approval.targetKind);
5716
+ const approvals = this.approvals().filter((existing) => existing.projectFingerprint !== approval.projectFingerprint || existing.capletId !== approval.capletId || existing.contentHash !== approval.contentHash || existing.targetKind !== approval.targetKind);
5717
+ approvals.push(approval);
5718
+ mkdirSync(this.root, { recursive: true });
5719
+ writeFileSync(this.approvalsPath(), `${JSON.stringify(approvals, null, 2)}\n`, { mode: 384 });
5720
+ return approval;
5721
+ }
5722
+ async recordAttempt(attempt) {
5723
+ assertSetupTargetKind$1(attempt.targetKind);
5724
+ const projectFingerprint = attempt.projectFingerprint ?? DEFAULT_PROJECT_FINGERPRINT;
5725
+ const attempts = this.prunedAttempts([...this.attempts(projectFingerprint, attempt.capletId), {
5726
+ ...attempt,
5727
+ projectFingerprint
5728
+ }]);
5729
+ mkdirSync(this.attemptsDir(projectFingerprint), { recursive: true });
5730
+ writeFileSync(this.attemptsPath(projectFingerprint, attempt.capletId), attempts.map((entry) => JSON.stringify(entry)).join("\n") + "\n", { mode: 384 });
5731
+ }
5732
+ async listAttempts(...args) {
5733
+ const [projectFingerprint, capletId] = args.length === 1 ? [DEFAULT_PROJECT_FINGERPRINT, args[0]] : args;
5734
+ return this.attempts(projectFingerprint, capletId);
5735
+ }
5736
+ retention() {
5737
+ return {
5738
+ maxAttempts: this.maxAttempts,
5739
+ days: this.retentionDays
5740
+ };
5741
+ }
5742
+ approvals() {
5743
+ const path = this.approvalsPath();
5744
+ if (!existsSync(path)) return [];
5745
+ return JSON.parse(readFileSync(path, "utf8")).map((approval) => ({
5746
+ ...approval,
5747
+ projectFingerprint: approval.projectFingerprint ?? DEFAULT_PROJECT_FINGERPRINT
5748
+ }));
5749
+ }
5750
+ prunedAttempts(attempts) {
5751
+ const cutoffMs = this.now().getTime() - this.retentionDays * 24 * 60 * 60 * 1e3;
5752
+ return attempts.filter((attempt) => new Date(attempt.finishedAt).getTime() >= cutoffMs).slice(-this.maxAttempts);
5753
+ }
5754
+ approvalsPath() {
5755
+ return join(this.root, "approvals.json");
5756
+ }
5757
+ attempts(projectFingerprint, capletId) {
5758
+ const path = this.attemptsPath(projectFingerprint, capletId);
5759
+ if (!existsSync(path)) return [];
5760
+ return readFileSync(path, "utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
5761
+ }
5762
+ attemptsDir(projectFingerprint) {
5763
+ return join(this.root, "projects", safeFileName(projectFingerprint), "attempts");
5764
+ }
5765
+ attemptsPath(projectFingerprint, capletId) {
5766
+ return join(this.attemptsDir(projectFingerprint), `${safeFileName(capletId)}.jsonl`);
5767
+ }
5768
+ };
5769
+ function safeFileName(value) {
5770
+ return value.replace(/[^a-zA-Z0-9._-]/gu, "_");
5771
+ }
5772
+ function assertSetupTargetKind$1(value) {
5773
+ if (!isSetupTargetKind(value)) throw new CapletsError("REQUEST_INVALID", "setup target must be one of: local_host, remote_host, hosted_sandbox");
5774
+ }
5775
+ //#endregion
5776
+ //#region src/setup/runner.ts
5777
+ const DEFAULT_TIMEOUT_MS = 12e4;
5778
+ const DEFAULT_MAX_OUTPUT_BYTES = 2e5;
5779
+ async function runCapletSetup(options) {
5780
+ assertSetupTargetKind(options.targetKind);
5781
+ if (!options.approved) throw new CapletsError("REQUEST_INVALID", "Setup approval is required before commands run");
5782
+ const attempts = [];
5783
+ const commands = options.setup.commands ?? [];
5784
+ const verify = options.setup.verify ?? [];
5785
+ for (const phase of ["commands", "verify"]) for (const command of phase === "commands" ? commands : verify) {
5786
+ const attempt = await runSetupCommand(options, phase, command);
5787
+ attempts.push(attempt);
5788
+ await options.store.recordAttempt(attempt);
5789
+ if (attempt.status !== "succeeded") return attempts;
5790
+ }
5791
+ return attempts;
5792
+ }
5793
+ async function runSetupCommand(options, phase, command) {
5794
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
5795
+ const startedAt = now();
5796
+ const argv = [command.command, ...command.args ?? []];
5797
+ const env = {
5798
+ ...process.env,
5799
+ ...command.env
5800
+ };
5801
+ const timeoutMs = command.timeoutMs ?? DEFAULT_TIMEOUT_MS;
5802
+ const maxOutputBytes = command.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
5803
+ const cwd = resolveCwd(command.cwd);
5804
+ assertProjectWorkspaceSetupAllowed({
5805
+ cwd,
5806
+ projectBindingRequired: options.projectBindingRequired === true,
5807
+ projectWorkspacePath: options.projectWorkspacePath
5808
+ });
5809
+ const result = await (options.spawn ?? spawnCommand)(command.command, command.args ?? [], {
5810
+ cwd,
5811
+ env,
5812
+ timeoutMs,
5813
+ maxOutputBytes
5814
+ });
5815
+ const finishedAt = now();
5816
+ const redacted = redactsSecrets(command.env);
5817
+ const stdout = redactOutput(result.stdout, command.env);
5818
+ const stderr = redactOutput(result.stderr, command.env);
5819
+ return {
5820
+ attemptId: randomUUID(),
5821
+ projectFingerprint: options.projectFingerprint ?? "default",
5822
+ capletId: options.capletId,
5823
+ contentHash: options.contentHash,
5824
+ ...options.setupHash === void 0 ? {} : { setupHash: options.setupHash },
5825
+ targetKind: options.targetKind,
5826
+ ...options.runtimeFeatures === void 0 ? {} : { runtimeFeatures: options.runtimeFeatures },
5827
+ actor: options.actor,
5828
+ status: result.exitCode === 0 && !result.signal ? "succeeded" : "failed",
5829
+ phase,
5830
+ commandLabel: command.label,
5831
+ argv,
5832
+ ...result.exitCode === void 0 ? {} : { exitCode: result.exitCode },
5833
+ ...result.signal === void 0 ? {} : { signal: result.signal },
5834
+ durationMs: result.durationMs,
5835
+ startedAt: startedAt.toISOString(),
5836
+ finishedAt: finishedAt.toISOString(),
5837
+ stdout: capBytes(stdout, maxOutputBytes),
5838
+ stderr: capBytes(stderr, maxOutputBytes),
5839
+ redacted,
5840
+ retention: options.store.retention()
5841
+ };
5842
+ }
5843
+ function assertProjectWorkspaceSetupAllowed(input) {
5844
+ if (input.projectBindingRequired || !input.cwd || !input.projectWorkspacePath) return;
5845
+ const workspacePath = resolve(input.projectWorkspacePath);
5846
+ const cwd = resolve(input.cwd);
5847
+ if (cwd === workspacePath || cwd.startsWith(`${workspacePath}/`)) throw new CapletsError("REQUEST_INVALID", "Non-project setup cannot run inside project workspace without projectBinding.required");
5848
+ }
5849
+ async function spawnCommand(command, args, options) {
5850
+ const startedAt = Date.now();
5851
+ return await new Promise((resolvePromise, reject) => {
5852
+ const child = spawn(command, args, {
5853
+ cwd: options.cwd,
5854
+ env: options.env,
5855
+ shell: false,
5856
+ stdio: [
5857
+ "ignore",
5858
+ "pipe",
5859
+ "pipe"
5860
+ ]
5861
+ });
5862
+ const chunks = {
5863
+ stdout: "",
5864
+ stderr: ""
5865
+ };
5866
+ const timer = setTimeout(() => {
5867
+ child.kill("SIGTERM");
5868
+ }, options.timeoutMs);
5869
+ child.on("error", (error) => {
5870
+ clearTimeout(timer);
5871
+ reject(error);
5872
+ });
5873
+ child.stdout?.setEncoding("utf8");
5874
+ child.stderr?.setEncoding("utf8");
5875
+ child.stdout?.on("data", (chunk) => {
5876
+ chunks.stdout = capBytes(chunks.stdout + chunk, options.maxOutputBytes);
5877
+ });
5878
+ child.stderr?.on("data", (chunk) => {
5879
+ chunks.stderr = capBytes(chunks.stderr + chunk, options.maxOutputBytes);
5880
+ });
5881
+ child.on("close", (exitCode, signal) => {
5882
+ clearTimeout(timer);
5883
+ resolvePromise({
5884
+ exitCode: exitCode ?? void 0,
5885
+ signal: signal ?? void 0,
5886
+ stdout: chunks.stdout,
5887
+ stderr: chunks.stderr,
5888
+ durationMs: Date.now() - startedAt
5889
+ });
5890
+ });
5891
+ });
5892
+ }
5893
+ function resolveCwd(cwd) {
5894
+ if (!cwd) return void 0;
5895
+ if (!isAbsolute(cwd)) throw new CapletsError("CONFIG_INVALID", "Setup command cwd must be absolute");
5896
+ return resolve(cwd);
5897
+ }
5898
+ function redactsSecrets(env) {
5899
+ return Object.entries(env ?? {}).some(([key, value]) => isSecretKey(key) && value.length > 0);
5900
+ }
5901
+ function redactOutput(output, env) {
5902
+ let redacted = output;
5903
+ for (const [key, value] of Object.entries(env ?? {})) {
5904
+ if (!isSecretKey(key) || !value) continue;
5905
+ redacted = redacted.split(value).join("[REDACTED]");
5906
+ }
5907
+ return redacted;
5908
+ }
5909
+ function isSecretKey(key) {
5910
+ return /TOKEN|SECRET|PASSWORD|KEY|AUTH/iu.test(key);
5911
+ }
5912
+ function capBytes(value, maxBytes) {
5913
+ if (Buffer.byteLength(value) <= maxBytes) return value;
5914
+ return Buffer.from(value).subarray(0, maxBytes).toString("utf8");
5915
+ }
5916
+ function assertSetupTargetKind(value) {
5917
+ if (!isSetupTargetKind(value)) throw new CapletsError("REQUEST_INVALID", "setup target must be one of: local_host, remote_host, hosted_sandbox");
5918
+ }
5919
+ //#endregion
5920
+ //#region src/cli/setup-caplet.ts
5921
+ async function runCapletSetupCli(capletId, options = {}) {
5922
+ const targetKind = resolveSetupTarget(options);
5923
+ if (targetKind === "hosted_sandbox") throw new CapletsError("REQUEST_INVALID", "Cloud setup runs through the Caplets Cloud API, not the local CLI runner");
5924
+ const config = loadConfig(options.configPath ?? resolveConfigPath(), options.projectConfigPath ?? resolveProjectConfigPath());
5925
+ const caplet = Object.values({
5926
+ ...config.mcpServers,
5927
+ ...config.openapiEndpoints,
5928
+ ...config.graphqlEndpoints,
5929
+ ...config.httpApis,
5930
+ ...config.cliTools,
5931
+ ...config.capletSets
5932
+ }).find((entry) => entry.server === capletId);
5933
+ if (!caplet) throw new CapletsError("CONFIG_INVALID", `Unknown Caplet ID: ${capletId}`);
5934
+ if (!caplet.setup || !caplet.setup.commands?.length && !caplet.setup.verify?.length) return `No setup metadata is defined for ${caplet.name} (${caplet.server}).\n`;
5935
+ const contentHash = capletSetupContentHash(caplet);
5936
+ const projectFingerprint = "default";
5937
+ const store = new LocalSetupStore(options.baseDir ? { baseDir: options.baseDir } : {});
5938
+ const existingApproval = await store.getApproval(projectFingerprint, caplet.server, contentHash, targetKind);
5939
+ const actor = options.yes ? "cli-yes" : "cli-interactive";
5940
+ if (!existingApproval && !options.yes) return [
5941
+ `Setup approval required for ${caplet.name} (${caplet.server}).`,
5942
+ `Content hash: ${contentHash}`,
5943
+ `Target: ${targetKind}`,
5944
+ "",
5945
+ "Commands:",
5946
+ ...formatCommands(caplet.setup.commands ?? []),
5947
+ "Verify:",
5948
+ ...formatCommands(caplet.setup.verify ?? []),
5949
+ "",
5950
+ `Run caplets setup ${caplet.server} --yes to approve and execute these exact steps.`,
5951
+ ""
5952
+ ].join("\n");
5953
+ if (options.yes && !existingApproval) await store.approve({
5954
+ projectFingerprint,
5955
+ capletId: caplet.server,
5956
+ contentHash,
5957
+ targetKind,
5958
+ approvedAt: (/* @__PURE__ */ new Date()).toISOString(),
5959
+ actor
5960
+ });
5961
+ const attempts = await runCapletSetup({
5962
+ projectFingerprint,
5963
+ capletId: caplet.server,
5964
+ contentHash,
5965
+ targetKind,
5966
+ setup: caplet.setup,
5967
+ actor,
5968
+ approved: true,
5969
+ store,
5970
+ ...options.spawn ? { spawn: options.spawn } : {}
5971
+ });
5972
+ const failed = attempts.find((attempt) => attempt.status === "failed");
5973
+ if (failed) throw new CapletsError("SERVER_UNAVAILABLE", `Setup failed for ${caplet.server}: ${failed.commandLabel}`, { attempts });
5974
+ return `Completed setup for ${caplet.name} (${caplet.server}).\n`;
5975
+ }
5976
+ function resolveSetupTarget(options) {
5977
+ if (options.target) return options.target;
5978
+ return options.remote ? "remote_host" : "local_host";
5979
+ }
5980
+ function formatCommands(commands) {
5981
+ if (commands.length === 0) return [" none"];
5982
+ return commands.map((command) => ` - ${command.label}: ${[command.command, ...command.args ?? []].join(" ")}`);
5983
+ }
5984
+ //#endregion
5273
5985
  //#region src/cli/setup.ts
5274
5986
  const execFileAsync = promisify(execFile);
5275
5987
  const setupIntegrationIds = [
@@ -5307,6 +6019,11 @@ function formatSetupMenu() {
5307
6019
  ].join("\n");
5308
6020
  }
5309
6021
  async function runSetup(integration, options = {}) {
6022
+ if (!setupIntegrationIds.includes(integration)) return await runCapletSetupCli(integration, {
6023
+ ...options.yes === void 0 ? {} : { yes: options.yes },
6024
+ target: resolveSetupTargetKind(options),
6025
+ ...options.remote === void 0 ? {} : { remote: options.remote }
6026
+ });
5310
6027
  const result = await executeSetup(integration, options);
5311
6028
  if (options.format === "json") return `${JSON.stringify(result, null, 2)}\n`;
5312
6029
  return formatSetupResult(result);
@@ -5350,6 +6067,7 @@ async function executeSetup(integration, options) {
5350
6067
  integration: id,
5351
6068
  name: definition.name,
5352
6069
  mode: options.remote ? "remote" : "local",
6070
+ targetKind: resolveSetupTargetKind(options),
5353
6071
  dryRun: Boolean(options.dryRun),
5354
6072
  actions,
5355
6073
  nextSteps: definition.nextSteps
@@ -5497,7 +6215,7 @@ async function defaultSetupCommandRunner(command, args) {
5497
6215
  };
5498
6216
  }
5499
6217
  function formatSetupResult(result) {
5500
- const lines = [`${result.dryRun ? "Dry run" : "Completed"} ${result.name} setup (${result.mode})`, ""];
6218
+ const lines = [`${result.dryRun ? "Dry run" : "Completed"} ${result.name} setup (${result.mode}, ${result.targetKind})`, ""];
5501
6219
  for (const action of result.actions) {
5502
6220
  if (action.command) lines.push(`- ${action.status}: ${action.command}`);
5503
6221
  if (action.path) lines.push(`- ${action.status}: wrote ${action.path}`);
@@ -5516,97 +6234,174 @@ function nonEmpty$1(value) {
5516
6234
  const trimmed = value?.trim();
5517
6235
  return trimmed ? trimmed : void 0;
5518
6236
  }
5519
- //#endregion
5520
- //#region src/remote-control/client.ts
5521
- var RemoteControlClient = class {
5522
- #baseUrl;
5523
- #requestInit;
5524
- #fetch;
5525
- constructor(options) {
5526
- this.#baseUrl = options.baseUrl;
5527
- this.#requestInit = options.requestInit;
5528
- this.#fetch = options.fetch ?? fetch;
6237
+ function resolveSetupTargetKind(options) {
6238
+ if (options.target !== void 0) {
6239
+ if (isSetupTargetKind(options.target)) return options.target;
6240
+ if (options.target === "local") return "local_host";
6241
+ if (options.target === "remote") return "remote_host";
6242
+ if (options.target === "cloud" || options.target === "hosted_worker") return "hosted_sandbox";
6243
+ throw new CapletsError("REQUEST_INVALID", "setup target must be one of: local_host, remote_host, hosted_sandbox");
5529
6244
  }
5530
- async request(command, args) {
5531
- const controlUrl = controlUrlForBase(this.#baseUrl);
5532
- let response;
5533
- try {
5534
- response = await this.#fetch(controlUrl, {
5535
- ...this.#requestInit,
5536
- method: "POST",
5537
- headers: mergeJsonHeaders(this.#requestInit.headers),
5538
- body: JSON.stringify({
5539
- command,
5540
- arguments: args
5541
- })
5542
- });
5543
- } catch (error) {
5544
- throw new CapletsError("SERVER_UNAVAILABLE", `Could not connect to Caplets server at ${safeBaseUrl(this.#baseUrl)}.`, toSafeError(error, "SERVER_UNAVAILABLE"));
5545
- }
5546
- if (response.status === 401 || response.status === 403) throw new CapletsError("AUTH_FAILED", "Caplets server authentication failed. Check CAPLETS_SERVER_USER and CAPLETS_SERVER_PASSWORD.");
5547
- if (!response.ok) throw new CapletsError("SERVER_UNAVAILABLE", `Caplets server at ${safeBaseUrl(this.#baseUrl)} returned HTTP ${response.status}.`);
5548
- const payload = await parseRemoteCliResponse(response);
5549
- if (!payload.ok) throw new CapletsError(payload.error.code, redactRemoteMessage(payload.error.message), payload.error.nextAction === void 0 ? void 0 : { nextAction: payload.error.nextAction });
5550
- return payload.result;
6245
+ return options.remote ? "remote_host" : "local_host";
6246
+ }
6247
+ //#endregion
6248
+ //#region src/serve/options.ts
6249
+ const HTTP_ONLY_OPTIONS = [
6250
+ "host",
6251
+ "port",
6252
+ "path",
6253
+ "user",
6254
+ "password",
6255
+ "allowUnauthenticatedHttp",
6256
+ "trustProxy"
6257
+ ];
6258
+ function resolveServeOptions(raw, env = process.env) {
6259
+ const transport = parseTransport(raw.transport ?? "stdio");
6260
+ if (transport === "stdio") {
6261
+ const invalid = HTTP_ONLY_OPTIONS.filter((key) => raw[key] !== void 0);
6262
+ if (invalid.length > 0) throw new CapletsError("REQUEST_INVALID", `${invalid.map((key) => `--${key}`).join(", ")} ${invalid.length === 1 ? "is" : "are"} only valid with --transport http`);
6263
+ return { transport };
5551
6264
  }
5552
- };
5553
- function mergeJsonHeaders(headers) {
5554
- const merged = new Headers(headers);
5555
- merged.set("content-type", "application/json");
5556
- return merged;
6265
+ const serverUrl = env.CAPLETS_SERVER_URL ? parseServeServerUrl(nonEmpty(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL")) : void 0;
6266
+ const host = nonEmpty(raw.host, "--host") ?? serverUrlHost(serverUrl) ?? "127.0.0.1";
6267
+ const port = parsePort(raw.port ?? (serverUrl?.port ? Number(serverUrl.port) : 5387));
6268
+ const path = normalizeHttpPath(raw.path ?? serverUrl?.pathname ?? "/");
6269
+ const userWasExplicit = raw.user !== void 0 || hasEnv(env.CAPLETS_SERVER_USER);
6270
+ const user = nonEmpty(raw.user, "--user") ?? nonEmpty(env.CAPLETS_SERVER_USER, "CAPLETS_SERVER_USER") ?? "caplets";
6271
+ const password = nonEmpty(raw.password, "--password") ?? nonEmpty(env.CAPLETS_SERVER_PASSWORD, "CAPLETS_SERVER_PASSWORD");
6272
+ if (userWasExplicit && password === void 0) throw new CapletsError("REQUEST_INVALID", "HTTP Basic Auth requires a password; pass --password or set CAPLETS_SERVER_PASSWORD.");
6273
+ const loopback = isLoopbackHost(host);
6274
+ const auth = password === void 0 ? {
6275
+ enabled: false,
6276
+ user
6277
+ } : {
6278
+ enabled: true,
6279
+ user,
6280
+ password
6281
+ };
6282
+ if (!loopback && !auth.enabled && raw.allowUnauthenticatedHttp !== true) throw new CapletsError("REQUEST_INVALID", "Unauthenticated HTTP serving on non-loopback hosts requires --allow-unauthenticated-http.");
6283
+ return {
6284
+ transport,
6285
+ host,
6286
+ port,
6287
+ path,
6288
+ ...serverUrl ? { publicOrigin: serverUrl.origin } : {},
6289
+ auth,
6290
+ warnUnauthenticatedNetwork: !loopback && !auth.enabled,
6291
+ loopback,
6292
+ trustProxy: raw.trustProxy === true
6293
+ };
5557
6294
  }
5558
- function safeBaseUrl(baseUrl) {
5559
- const safe = new URL(baseUrl.href);
5560
- safe.username = "";
5561
- safe.password = "";
5562
- safe.search = "";
5563
- safe.hash = "";
5564
- return safe.toString();
6295
+ function resolveDaemonServeOptions(raw, env = process.env) {
6296
+ if (raw.transport !== void 0 && raw.transport !== "http") throw new CapletsError("REQUEST_INVALID", "Daemonized serve requires --transport http.");
6297
+ return resolveServeOptions({
6298
+ ...raw,
6299
+ transport: "http"
6300
+ }, env);
5565
6301
  }
5566
- async function parseRemoteCliResponse(response) {
5567
- let payload;
6302
+ function isLoopbackHost(host) {
6303
+ const normalized = host.toLocaleLowerCase();
6304
+ return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
6305
+ }
6306
+ function parseServeServerUrl(value) {
5568
6307
  try {
5569
- payload = await response.json();
6308
+ return parseServerBaseUrl(value);
5570
6309
  } catch (error) {
5571
- throw invalidRemoteControlResponse(error);
5572
- }
5573
- if (!isRecord(payload)) throw invalidRemoteControlResponse();
5574
- if (payload.ok === true) {
5575
- if (!("result" in payload)) throw invalidRemoteControlResponse();
5576
- return {
5577
- ok: true,
5578
- result: payload.result
5579
- };
5580
- }
5581
- if (payload.ok === false) {
5582
- const error = payload.error;
5583
- if (!isRecord(error) || typeof error.code !== "string" || typeof error.message !== "string") throw invalidRemoteControlResponse();
5584
- if ("nextAction" in error && error.nextAction !== void 0 && typeof error.nextAction !== "string") throw invalidRemoteControlResponse();
5585
- const errorResponse = {
5586
- ok: false,
5587
- error: {
5588
- code: isCapletsErrorCode(error.code) ? error.code : "DOWNSTREAM_TOOL_ERROR",
5589
- message: error.message
5590
- }
5591
- };
5592
- if (typeof error.nextAction === "string") errorResponse.error.nextAction = error.nextAction;
5593
- return errorResponse;
6310
+ if (error instanceof CapletsError && error.message.includes("must use https except loopback development URLs")) throw new CapletsError("REQUEST_INVALID", "CAPLETS_SERVER_URL must use https except loopback development URLs; use --host, --port, and --path separately for non-loopback HTTP bind addresses.");
6311
+ throw error;
5594
6312
  }
5595
- throw invalidRemoteControlResponse();
5596
6313
  }
5597
- function invalidRemoteControlResponse(cause) {
5598
- return new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Caplets server returned an invalid remote control response.", cause === void 0 ? void 0 : toSafeError(cause, "DOWNSTREAM_PROTOCOL_ERROR"));
6314
+ function parseTransport(value) {
6315
+ if (value === "stdio" || value === "http") return value;
6316
+ throw new CapletsError("REQUEST_INVALID", `Expected --transport to be stdio or http, got ${value}`);
5599
6317
  }
5600
- function isRecord(value) {
5601
- return value !== null && typeof value === "object" && !Array.isArray(value);
6318
+ function parsePort(value) {
6319
+ const parsed = typeof value === "number" ? value : Number(value);
6320
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) throw new CapletsError("REQUEST_INVALID", `Expected --port to be a valid TCP port, got ${value}`);
6321
+ return parsed;
5602
6322
  }
5603
- function isCapletsErrorCode(value) {
5604
- return CAPLETS_ERROR_CODES.includes(value);
6323
+ function normalizeHttpPath(value) {
6324
+ if (!value.startsWith("/")) throw new CapletsError("REQUEST_INVALID", "HTTP --path must start with /");
6325
+ if (value.includes("?") || value.includes("#")) throw new CapletsError("REQUEST_INVALID", "HTTP --path must not include a query string or fragment");
6326
+ return value === "/" ? value : value.replace(/\/+$/u, "");
5605
6327
  }
5606
- function redactRemoteMessage(message) {
5607
- return String(redactSecrets(message)).replace(/\b(authorization\s*:\s*(?:basic|bearer)\s+)[^\s,;]+/giu, "$1[REDACTED]").replace(/\b((?:access_)?token=)[^\s,;]+/giu, "$1[REDACTED]").replace(/\b((?:token|secret|authorization|auth|api[-_]?key|password|credential|clientsecret|client_secret|code|refresh(?:_token)?)\s*[=:]\s*)[^\s,;]+/giu, "$1[REDACTED]");
6328
+ function serverUrlHost(url) {
6329
+ return url?.hostname.replace(/^\[(.*)\]$/u, "$1");
6330
+ }
6331
+ function nonEmpty(value, label) {
6332
+ if (value === void 0) return;
6333
+ const trimmed = value.trim();
6334
+ if (!trimmed) throw new CapletsError("REQUEST_INVALID", `${label} must not be empty`);
6335
+ return trimmed;
6336
+ }
6337
+ function hasEnv(value) {
6338
+ return value !== void 0 && value.trim() !== "";
6339
+ }
6340
+ //#endregion
6341
+ //#region src/attach/options.ts
6342
+ async function resolveAttachServeOptions(raw = {}, env = process.env) {
6343
+ const selection = await resolveRemoteSelection(raw, env);
6344
+ return {
6345
+ ...resolveServeOptions(raw, env),
6346
+ projectRoot: raw.projectRoot ?? process.cwd(),
6347
+ selection
6348
+ };
5608
6349
  }
5609
6350
  //#endregion
6351
+ //#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
6352
+ /**
6353
+ * Server transport for stdio: this communicates with an MCP client by reading from the current process' stdin and writing to stdout.
6354
+ *
6355
+ * This transport is only available in Node.js environments.
6356
+ */
6357
+ var StdioServerTransport = class {
6358
+ constructor(_stdin = process$1.stdin, _stdout = process$1.stdout) {
6359
+ this._stdin = _stdin;
6360
+ this._stdout = _stdout;
6361
+ this._readBuffer = new ReadBuffer();
6362
+ this._started = false;
6363
+ this._ondata = (chunk) => {
6364
+ this._readBuffer.append(chunk);
6365
+ this.processReadBuffer();
6366
+ };
6367
+ this._onerror = (error) => {
6368
+ this.onerror?.(error);
6369
+ };
6370
+ }
6371
+ /**
6372
+ * Starts listening for messages on stdin.
6373
+ */
6374
+ async start() {
6375
+ if (this._started) throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
6376
+ this._started = true;
6377
+ this._stdin.on("data", this._ondata);
6378
+ this._stdin.on("error", this._onerror);
6379
+ }
6380
+ processReadBuffer() {
6381
+ while (true) try {
6382
+ const message = this._readBuffer.readMessage();
6383
+ if (message === null) break;
6384
+ this.onmessage?.(message);
6385
+ } catch (error) {
6386
+ this.onerror?.(error);
6387
+ }
6388
+ }
6389
+ async close() {
6390
+ this._stdin.off("data", this._ondata);
6391
+ this._stdin.off("error", this._onerror);
6392
+ if (this._stdin.listenerCount("data") === 0) this._stdin.pause();
6393
+ this._readBuffer.clear();
6394
+ this.onclose?.();
6395
+ }
6396
+ send(message) {
6397
+ return new Promise((resolve) => {
6398
+ const json = serializeMessage(message);
6399
+ if (this._stdout.write(json)) resolve();
6400
+ else this._stdout.once("drain", resolve);
6401
+ });
6402
+ }
6403
+ };
6404
+ //#endregion
5610
6405
  //#region ../../node_modules/.pnpm/hono@4.12.23/node_modules/hono/dist/compose.js
5611
6406
  var compose = (middleware, onError, onNotFound) => {
5612
6407
  return (context, next) => {
@@ -9337,7 +10132,7 @@ function createHttpServeApp(options, engine, io = {}) {
9337
10132
  id: null
9338
10133
  }, 400);
9339
10134
  const nextSessionId = randomUUID();
9340
- const session = await createHttpSession(engine, nextSessionId, options, async (closedSessionId) => {
10135
+ const session = await createHttpSession(io.sessionFactory ?? (() => new CapletsMcpSession(engine)), nextSessionId, options, async (closedSessionId) => {
9341
10136
  const closed = sessions.get(closedSessionId);
9342
10137
  sessions.delete(closedSessionId);
9343
10138
  if (closed) await closed.server.close();
@@ -9363,6 +10158,11 @@ function createHttpServeApp(options, engine, io = {}) {
9363
10158
  }
9364
10159
  return c.json(await dispatchRemoteCliRequest(request, controlContext(io, writeErr, authFlowStore, c.req.url, paths.control, options.publicOrigin, options.trustProxy, (name) => c.req.header(name))));
9365
10160
  });
10161
+ app.get(routePath(paths.control, "project-bindings/connect"), basicAuth(options.auth), (c) => c.json({ error: "websocket_upgrade_required" }, 426));
10162
+ app.get(routePath(paths.control, "project-bindings/:bindingId/status"), basicAuth(options.auth), (c) => c.json({
10163
+ bindingId: c.req.param("bindingId"),
10164
+ state: "not_attached"
10165
+ }));
9366
10166
  app.get(routePath(paths.control, "auth/callback/:flowId"), async (c) => {
9367
10167
  const flowId = c.req.param("flowId");
9368
10168
  const result = await dispatchRemoteCliRequest({
@@ -9428,6 +10228,28 @@ async function serveHttp(options, engineOptions = {}, writeErr = (value) => proc
9428
10228
  writeErr(`Basic Auth: ${options.auth.enabled ? `enabled (user: ${options.auth.user})` : "disabled"}\n`);
9429
10229
  }), app, engine, writeErr);
9430
10230
  }
10231
+ async function serveHttpWithSessionFactory(options, createSession, writeErr = (value) => process.stderr.write(value)) {
10232
+ const engine = new CapletsEngine({});
10233
+ const app = createHttpServeApp(options, engine, {
10234
+ writeErr,
10235
+ sessionFactory: createSession,
10236
+ control: { projectCapletsRoot: resolveProjectCapletsRoot() }
10237
+ });
10238
+ const paths = servicePaths(options.path);
10239
+ const origin = `http://${formatHost(options.host)}:${options.port}`;
10240
+ const baseUrl = `${origin}${paths.base === "/" ? "" : paths.base}`;
10241
+ installHttpSignalHandlers(serve({
10242
+ fetch: app.fetch,
10243
+ hostname: options.host,
10244
+ port: options.port
10245
+ }, () => {
10246
+ writeErr(`Caplets HTTP service listening on ${baseUrl}\n`);
10247
+ writeErr(`MCP endpoint: ${origin}${paths.mcp}\n`);
10248
+ writeErr(`Control endpoint: ${origin}${paths.control}\n`);
10249
+ writeErr(`Health check: ${origin}${paths.health}\n`);
10250
+ writeErr(`Basic Auth: ${options.auth.enabled ? `enabled (user: ${options.auth.user})` : "disabled"}\n`);
10251
+ }), app, engine, writeErr);
10252
+ }
9431
10253
  function projectCapletsRootForEngineOptions(engineOptions) {
9432
10254
  return engineOptions.projectConfigPath ? resolveProjectCapletsRootForConfigPath(engineOptions.projectConfigPath) : resolveProjectCapletsRoot();
9433
10255
  }
@@ -9445,13 +10267,13 @@ function servicePaths(base) {
9445
10267
  health: routePath(base, "healthz")
9446
10268
  };
9447
10269
  }
9448
- async function createHttpSession(engine, sessionId, options, onClose) {
10270
+ async function createHttpSession(createServer, sessionId, options, onClose) {
9449
10271
  const transport = new StreamableHTTPTransport({
9450
10272
  sessionIdGenerator: () => sessionId,
9451
10273
  onsessionclosed: onClose,
9452
10274
  ...options.loopback ? dnsRebindingOptions(options) : {}
9453
10275
  });
9454
- const server = new CapletsMcpSession(engine);
10276
+ const server = await createServer();
9455
10277
  await server.connect(transport);
9456
10278
  return {
9457
10279
  server,
@@ -9517,145 +10339,657 @@ function formatHost(host) {
9517
10339
  return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
9518
10340
  }
9519
10341
  //#endregion
9520
- //#region src/serve/options.ts
9521
- const HTTP_ONLY_OPTIONS = [
9522
- "host",
9523
- "port",
9524
- "path",
9525
- "user",
9526
- "password",
9527
- "allowUnauthenticatedHttp",
9528
- "trustProxy"
9529
- ];
9530
- function resolveServeOptions(raw, env = process.env) {
9531
- const transport = parseTransport(raw.transport ?? "stdio");
9532
- if (transport === "stdio") {
9533
- const invalid = HTTP_ONLY_OPTIONS.filter((key) => raw[key] !== void 0);
9534
- if (invalid.length > 0) throw new CapletsError("REQUEST_INVALID", `${invalid.map((key) => `--${key}`).join(", ")} ${invalid.length === 1 ? "is" : "are"} only valid with --transport http`);
9535
- return { transport };
10342
+ //#region src/serve/native-session.ts
10343
+ var NativeCapletsMcpSession = class {
10344
+ service;
10345
+ server;
10346
+ tools = /* @__PURE__ */ new Map();
10347
+ unsubscribe;
10348
+ closed = false;
10349
+ constructor(service, options = {}) {
10350
+ this.service = service;
10351
+ this.server = options.server ?? new McpServer({
10352
+ name: "caplets",
10353
+ version
10354
+ });
10355
+ this.unsubscribe = service.onToolsChanged((tools) => this.reconcileTools(tools));
10356
+ this.reconcileTools(service.listTools());
9536
10357
  }
9537
- const serverUrl = env.CAPLETS_SERVER_URL ? parseServeServerUrl(nonEmpty(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL")) : void 0;
9538
- const host = nonEmpty(raw.host, "--host") ?? serverUrlHost(serverUrl) ?? "127.0.0.1";
9539
- const port = parsePort(raw.port ?? (serverUrl?.port ? Number(serverUrl.port) : 5387));
9540
- const path = normalizeHttpPath(raw.path ?? serverUrl?.pathname ?? "/");
9541
- const userWasExplicit = raw.user !== void 0 || hasEnv(env.CAPLETS_SERVER_USER);
9542
- const user = nonEmpty(raw.user, "--user") ?? nonEmpty(env.CAPLETS_SERVER_USER, "CAPLETS_SERVER_USER") ?? "caplets";
9543
- const password = nonEmpty(raw.password, "--password") ?? nonEmpty(env.CAPLETS_SERVER_PASSWORD, "CAPLETS_SERVER_PASSWORD");
9544
- if (userWasExplicit && password === void 0) throw new CapletsError("REQUEST_INVALID", "HTTP Basic Auth requires a password; pass --password or set CAPLETS_SERVER_PASSWORD.");
9545
- const loopback = isLoopbackHost(host);
9546
- const auth = password === void 0 ? {
9547
- enabled: false,
9548
- user
9549
- } : {
10358
+ async connect(transport) {
10359
+ await this.server.connect(transport);
10360
+ }
10361
+ async close() {
10362
+ if (this.closed) return;
10363
+ this.closed = true;
10364
+ this.unsubscribe();
10365
+ this.tools.clear();
10366
+ await this.server.close();
10367
+ await this.service.close();
10368
+ }
10369
+ reconcileTools(next) {
10370
+ const enabled = new Map(next.map((tool) => [tool.caplet, tool]));
10371
+ for (const [id, registered] of this.tools) {
10372
+ const tool = enabled.get(id);
10373
+ if (!tool) {
10374
+ registered.remove();
10375
+ this.tools.delete(id);
10376
+ continue;
10377
+ }
10378
+ registered.update(this.definition(tool));
10379
+ }
10380
+ for (const tool of enabled.values()) if (!this.tools.has(tool.caplet)) this.tools.set(tool.caplet, this.server.registerTool(tool.caplet, this.definition(tool), async (request) => await this.service.execute(tool.caplet, request)));
10381
+ }
10382
+ definition(tool) {
10383
+ return {
10384
+ title: tool.title,
10385
+ description: tool.description,
10386
+ inputSchema: isRecord$2(tool.inputSchema) ? jsonSchemaToZodShape(tool.inputSchema) : void 0
10387
+ };
10388
+ }
10389
+ };
10390
+ function jsonSchemaToZodShape(schema) {
10391
+ const properties = isRecord$2(schema.properties) ? schema.properties : {};
10392
+ const shape = {};
10393
+ for (const [key, value] of Object.entries(properties)) shape[key] = jsonSchemaPropertyToZod(value);
10394
+ return shape;
10395
+ }
10396
+ function jsonSchemaPropertyToZod(value) {
10397
+ if (!isRecord$2(value)) return unknown().optional();
10398
+ if (Array.isArray(value.enum) && value.enum.every((item) => typeof item === "string")) {
10399
+ const values = value.enum;
10400
+ if (values.length > 0) return _enum(values).optional();
10401
+ }
10402
+ switch (value.type) {
10403
+ case "string": return string().optional();
10404
+ case "number":
10405
+ case "integer": return number().optional();
10406
+ case "boolean": return boolean().optional();
10407
+ case "array": return array(unknown()).optional();
10408
+ case "object": return record(string(), unknown()).optional();
10409
+ default: return unknown().optional();
10410
+ }
10411
+ }
10412
+ function isRecord$2(value) {
10413
+ return value !== null && typeof value === "object" && !Array.isArray(value);
10414
+ }
10415
+ //#endregion
10416
+ //#region src/attach/server.ts
10417
+ async function attachResolvedCaplets(options, io = {}) {
10418
+ if (options.transport === "stdio") {
10419
+ const service = createAttachNativeService(options, io);
10420
+ const session = new NativeCapletsMcpSession(service);
10421
+ await service.reload();
10422
+ await session.connect(new StdioServerTransport());
10423
+ return;
10424
+ }
10425
+ await serveHttpWithSessionFactory(options, async () => {
10426
+ const service = createAttachNativeService(options, io);
10427
+ await service.reload();
10428
+ return new NativeCapletsMcpSession(service);
10429
+ }, io.writeErr);
10430
+ }
10431
+ function createAttachNativeService(options, io) {
10432
+ return createNativeCapletsService({
10433
+ mode: options.selection.kind === "hosted_cloud" ? "cloud" : "remote",
10434
+ server: {
10435
+ url: options.selection.remote.baseUrl.toString(),
10436
+ ...options.selection.remote.fetch ? { fetch: options.selection.remote.fetch } : {}
10437
+ },
10438
+ remote: {
10439
+ ...options.selection.remote.fetch ? { fetch: options.selection.remote.fetch } : {},
10440
+ ...options.selection.kind === "hosted_cloud" ? { cloud: {
10441
+ url: options.selection.cloudPresence.url.toString(),
10442
+ accessToken: options.selection.cloudPresence.accessToken,
10443
+ workspaceId: options.selection.cloudPresence.workspaceId,
10444
+ projectRoot: options.projectRoot
10445
+ } } : {}
10446
+ },
10447
+ remoteClientFactory: (resolved) => createSdkRemoteCapletsClient({
10448
+ ...resolved,
10449
+ requestInit: options.selection.remote.requestInit,
10450
+ auth: nativeAuthFromRemoteAuth(options.selection.remote.auth),
10451
+ url: options.selection.remote.mcpUrl,
10452
+ ...options.selection.remote.fetch ? { fetch: options.selection.remote.fetch } : {}
10453
+ }),
10454
+ ...io.writeErr ? { writeErr: io.writeErr } : {}
10455
+ });
10456
+ }
10457
+ function nativeAuthFromRemoteAuth(auth) {
10458
+ if (auth.type === "basic") return {
9550
10459
  enabled: true,
9551
- user,
9552
- password
10460
+ user: auth.user,
10461
+ password: auth.password
10462
+ };
10463
+ if (auth.type === "none") return {
10464
+ enabled: false,
10465
+ user: auth.user
9553
10466
  };
9554
- if (!loopback && !auth.enabled && raw.allowUnauthenticatedHttp !== true) throw new CapletsError("REQUEST_INVALID", "Unauthenticated HTTP serving on non-loopback hosts requires --allow-unauthenticated-http.");
9555
10467
  return {
9556
- transport,
9557
- host,
9558
- port,
9559
- path,
9560
- ...serverUrl ? { publicOrigin: serverUrl.origin } : {},
9561
- auth,
9562
- warnUnauthenticatedNetwork: !loopback && !auth.enabled,
9563
- loopback,
9564
- trustProxy: raw.trustProxy === true
10468
+ enabled: false,
10469
+ user: "caplets"
9565
10470
  };
9566
10471
  }
9567
- function isLoopbackHost(host) {
9568
- const normalized = host.toLocaleLowerCase();
9569
- return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
9570
- }
9571
- function parseServeServerUrl(value) {
9572
- try {
9573
- return parseServerBaseUrl(value);
9574
- } catch (error) {
9575
- if (error instanceof CapletsError && error.message.includes("must use https except loopback development URLs")) throw new CapletsError("REQUEST_INVALID", "CAPLETS_SERVER_URL must use https except loopback development URLs; use --host, --port, and --path separately for non-loopback HTTP bind addresses.");
9576
- throw error;
10472
+ //#endregion
10473
+ //#region src/project-binding/gitignore.ts
10474
+ const CAPLETS_GITIGNORE_CONTENT = "*\n!.gitignore\n";
10475
+ function bootstrapProjectBindingGitignore(projectRoot) {
10476
+ const capletsDir = join(projectRoot, ".caplets");
10477
+ const gitignorePath = join(capletsDir, ".gitignore");
10478
+ if (!existsSync(projectRoot)) return {
10479
+ path: gitignorePath,
10480
+ changed: false
10481
+ };
10482
+ mkdirSync(capletsDir, { recursive: true });
10483
+ if (!existsSync(gitignorePath)) {
10484
+ writeFileSync(gitignorePath, CAPLETS_GITIGNORE_CONTENT, { mode: 384 });
10485
+ return {
10486
+ path: gitignorePath,
10487
+ changed: true
10488
+ };
9577
10489
  }
10490
+ const existing = readFileSync(gitignorePath, "utf8");
10491
+ if (existing.startsWith(CAPLETS_GITIGNORE_CONTENT)) return {
10492
+ path: gitignorePath,
10493
+ changed: false
10494
+ };
10495
+ writeFileSync(gitignorePath, `${CAPLETS_GITIGNORE_CONTENT}${existing}`, { mode: 384 });
10496
+ return {
10497
+ path: gitignorePath,
10498
+ changed: true
10499
+ };
9578
10500
  }
9579
- function parseTransport(value) {
9580
- if (value === "stdio" || value === "http") return value;
9581
- throw new CapletsError("REQUEST_INVALID", `Expected --transport to be stdio or http, got ${value}`);
9582
- }
9583
- function parsePort(value) {
9584
- const parsed = typeof value === "number" ? value : Number(value);
9585
- if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) throw new CapletsError("REQUEST_INVALID", `Expected --port to be a valid TCP port, got ${value}`);
9586
- return parsed;
9587
- }
9588
- function normalizeHttpPath(value) {
9589
- if (!value.startsWith("/")) throw new CapletsError("REQUEST_INVALID", "HTTP --path must start with /");
9590
- if (value.includes("?") || value.includes("#")) throw new CapletsError("REQUEST_INVALID", "HTTP --path must not include a query string or fragment");
9591
- return value === "/" ? value : value.replace(/\/+$/u, "");
9592
- }
9593
- function serverUrlHost(url) {
9594
- return url?.hostname.replace(/^\[(.*)\]$/u, "$1");
9595
- }
9596
- function nonEmpty(value, label) {
9597
- if (value === void 0) return;
9598
- const trimmed = value.trim();
9599
- if (!trimmed) throw new CapletsError("REQUEST_INVALID", `${label} must not be empty`);
9600
- return trimmed;
9601
- }
9602
- function hasEnv(value) {
9603
- return value !== void 0 && value.trim() !== "";
10501
+ //#endregion
10502
+ //#region src/project-binding/transport.ts
10503
+ function defaultProjectBindingWebSocketFactory(url, protocols) {
10504
+ const WebSocketCtor = globalThis.WebSocket;
10505
+ if (!WebSocketCtor) throw new Error("WebSocket is not available in this runtime.");
10506
+ return new WebSocketCtor(url, protocols);
9604
10507
  }
9605
10508
  //#endregion
9606
- //#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
9607
- /**
9608
- * Server transport for stdio: this communicates with an MCP client by reading from the current process' stdin and writing to stdout.
9609
- *
9610
- * This transport is only available in Node.js environments.
9611
- */
9612
- var StdioServerTransport = class {
9613
- constructor(_stdin = process$1.stdin, _stdout = process$1.stdout) {
9614
- this._stdin = _stdin;
9615
- this._stdout = _stdout;
9616
- this._readBuffer = new ReadBuffer();
9617
- this._started = false;
9618
- this._ondata = (chunk) => {
9619
- this._readBuffer.append(chunk);
9620
- this.processReadBuffer();
10509
+ //#region src/project-binding/session.ts
10510
+ async function runProjectBindingSession(input) {
10511
+ const fetchImpl = input.fetch ?? input.remote.fetch ?? fetch;
10512
+ const webSocketFactory = input.webSocketFactory ?? defaultProjectBindingWebSocketFactory;
10513
+ const heartbeatIntervalMs = input.heartbeatIntervalMs ?? 15e3;
10514
+ const projectFingerprint = fingerprintProjectRoot(input.projectRoot);
10515
+ const requestInit = input.remote.requestInit;
10516
+ input.onEvent?.({
10517
+ type: "state",
10518
+ state: "attaching"
10519
+ });
10520
+ const created = await postJson(fetchImpl, sessionUrl(input.remote), requestInit, {
10521
+ projectRoot: input.projectRoot,
10522
+ projectFingerprint,
10523
+ workspaceId: input.remote.workspace ?? "default"
10524
+ });
10525
+ const bindingId = created.binding.bindingId;
10526
+ const sessionId = created.sessionId;
10527
+ let state = created.binding.state ?? "attaching";
10528
+ let syncState = created.binding.syncState ?? "pending";
10529
+ let ended = false;
10530
+ let heartbeatTimer;
10531
+ const publicWebSocketUrl = input.remote.projectBindingWebSocketUrl.toString();
10532
+ const socketUrl = bindingSocketUrl(input.remote, bindingId, sessionId, projectFingerprint);
10533
+ const socketProtocols = bindingSocketProtocols(input.remote);
10534
+ const emitReady = (requestId) => {
10535
+ input.onEvent?.({
10536
+ type: "ready",
10537
+ bindingId,
10538
+ sessionId,
10539
+ projectRoot: input.projectRoot,
10540
+ projectFingerprint,
10541
+ webSocketUrl: publicWebSocketUrl,
10542
+ ...requestId ? { requestId } : {}
10543
+ });
10544
+ };
10545
+ const heartbeat = async (socket) => {
10546
+ const payload = {
10547
+ type: "heartbeat",
10548
+ bindingId,
10549
+ sessionId,
10550
+ state,
10551
+ syncState
9621
10552
  };
9622
- this._onerror = (error) => {
9623
- this.onerror?.(error);
10553
+ if (socket?.readyState === 1) socket.send(JSON.stringify(payload));
10554
+ await postJson(fetchImpl, heartbeatUrl(input.remote, bindingId), requestInit, {
10555
+ sessionId,
10556
+ state,
10557
+ syncState
10558
+ }).catch(() => void 0);
10559
+ input.onEvent?.({
10560
+ type: "heartbeat",
10561
+ bindingId,
10562
+ sessionId,
10563
+ state
10564
+ });
10565
+ };
10566
+ const connect = async (attempt) => {
10567
+ const socket = webSocketFactory(socketUrl, socketProtocols);
10568
+ await waitForOpen(socket, input.signal);
10569
+ if (input.signal?.aborted) {
10570
+ closeSocket(socket, 1e3, "aborted");
10571
+ return;
10572
+ }
10573
+ const closePromise = new Promise((resolve) => {
10574
+ listen(socket, "message", (event) => {
10575
+ const message = parseSocketMessage(event.data);
10576
+ if (!message) return;
10577
+ if (message.type === "state") {
10578
+ state = message.state;
10579
+ syncState = message.syncState;
10580
+ input.onEvent?.({
10581
+ type: "state",
10582
+ state,
10583
+ ...message.requestId ? { requestId: message.requestId } : {}
10584
+ });
10585
+ return;
10586
+ }
10587
+ if (message.type === "ready") {
10588
+ state = "ready";
10589
+ syncState = message.syncState;
10590
+ emitReady(message.requestId);
10591
+ return;
10592
+ }
10593
+ if (message.type === "blocked") {
10594
+ state = "blocked";
10595
+ input.onEvent?.({
10596
+ type: "ended",
10597
+ bindingId,
10598
+ sessionId,
10599
+ reason: message.reason
10600
+ });
10601
+ resolve({
10602
+ reconnect: false,
10603
+ reason: message.reason.message
10604
+ });
10605
+ return;
10606
+ }
10607
+ input.onEvent?.({
10608
+ type: "ended",
10609
+ bindingId,
10610
+ sessionId,
10611
+ reason: message.reason
10612
+ });
10613
+ resolve({
10614
+ reconnect: false,
10615
+ reason: message.reason.message
10616
+ });
10617
+ });
10618
+ listen(socket, "close", (event) => {
10619
+ resolve({
10620
+ reconnect: !input.signal?.aborted && attempt < 1,
10621
+ reason: event.reason ?? `WebSocket closed${event.code ? ` (${event.code})` : ""}.`
10622
+ });
10623
+ });
10624
+ listen(socket, "error", () => {
10625
+ resolve({
10626
+ reconnect: !input.signal?.aborted && attempt < 1,
10627
+ reason: "WebSocket error."
10628
+ });
10629
+ });
10630
+ });
10631
+ heartbeatTimer = setInterval(() => {
10632
+ heartbeat(socket);
10633
+ }, heartbeatIntervalMs);
10634
+ await heartbeat(socket);
10635
+ const closed = await Promise.race([closePromise, waitForAbort(input.signal)]);
10636
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
10637
+ heartbeatTimer = void 0;
10638
+ closeSocket(socket, 1e3, "Binding Session closed.");
10639
+ if (closed === "abort") return;
10640
+ if (closed.reconnect) {
10641
+ input.onEvent?.({
10642
+ type: "reconnecting",
10643
+ bindingId,
10644
+ sessionId,
10645
+ attempt: attempt + 1,
10646
+ reason: closed.reason
10647
+ });
10648
+ await connect(attempt + 1);
10649
+ }
10650
+ };
10651
+ try {
10652
+ await connect(0);
10653
+ } finally {
10654
+ ended = true;
10655
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
10656
+ const reason = input.signal?.aborted ? {
10657
+ code: "interrupted",
10658
+ message: "Binding Session ended."
10659
+ } : {
10660
+ code: "completed",
10661
+ message: "Binding Session completed."
9624
10662
  };
10663
+ await endRemoteSession(fetchImpl, input.remote, requestInit, bindingId, sessionId, reason);
10664
+ input.onEvent?.({
10665
+ type: "ended",
10666
+ bindingId,
10667
+ sessionId,
10668
+ reason
10669
+ });
9625
10670
  }
9626
- /**
9627
- * Starts listening for messages on stdin.
9628
- */
9629
- async start() {
9630
- if (this._started) throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
9631
- this._started = true;
9632
- this._stdin.on("data", this._ondata);
9633
- this._stdin.on("error", this._onerror);
10671
+ return {
10672
+ ok: true,
10673
+ bindingId,
10674
+ sessionId,
10675
+ projectRoot: input.projectRoot,
10676
+ projectFingerprint,
10677
+ webSocketUrl: publicWebSocketUrl,
10678
+ ended
10679
+ };
10680
+ }
10681
+ async function postJson(fetchImpl, url, requestInit, body) {
10682
+ const headers = new Headers(requestInit.headers);
10683
+ headers.set("content-type", "application/json");
10684
+ const response = await fetchImpl(url, {
10685
+ ...requestInit,
10686
+ method: "POST",
10687
+ headers,
10688
+ body: JSON.stringify(body)
10689
+ });
10690
+ if (!response.ok) throw new CapletsError("SERVER_UNAVAILABLE", `Project Binding request failed (${response.status}).`);
10691
+ return await response.json().catch(() => ({}));
10692
+ }
10693
+ async function endRemoteSession(fetchImpl, remote, requestInit, bindingId, sessionId, reason) {
10694
+ const headers = new Headers(requestInit.headers);
10695
+ headers.set("content-type", "application/json");
10696
+ await fetchImpl(endSessionUrl(remote, bindingId), {
10697
+ ...requestInit,
10698
+ method: "DELETE",
10699
+ headers,
10700
+ body: JSON.stringify({
10701
+ sessionId,
10702
+ terminalReason: reason
10703
+ })
10704
+ }).catch(() => void 0);
10705
+ }
10706
+ function sessionUrl(remote) {
10707
+ return controlProjectBindingUrl(remote, "sessions");
10708
+ }
10709
+ function heartbeatUrl(remote, bindingId) {
10710
+ return controlProjectBindingUrl(remote, `${encodeURIComponent(bindingId)}/heartbeat`);
10711
+ }
10712
+ function endSessionUrl(remote, bindingId) {
10713
+ return controlProjectBindingUrl(remote, `${encodeURIComponent(bindingId)}/session`);
10714
+ }
10715
+ function controlProjectBindingUrl(remote, suffix) {
10716
+ const url = new URL(remote.projectBindingWebSocketUrl);
10717
+ if (url.protocol === "wss:") url.protocol = "https:";
10718
+ if (url.protocol === "ws:") url.protocol = "http:";
10719
+ url.pathname = url.pathname.replace(/\/connect$/u, `/${suffix}`);
10720
+ url.search = "";
10721
+ url.hash = "";
10722
+ return url;
10723
+ }
10724
+ function bindingSocketUrl(remote, bindingId, sessionId, projectFingerprint) {
10725
+ const url = new URL(remote.projectBindingWebSocketUrl);
10726
+ url.searchParams.set("bindingId", bindingId);
10727
+ url.searchParams.set("sessionId", sessionId);
10728
+ url.searchParams.set("projectFingerprint", projectFingerprint);
10729
+ return url.toString();
10730
+ }
10731
+ function bindingSocketProtocols(remote) {
10732
+ if (remote.auth.type !== "bearer") return void 0;
10733
+ return ["caplets.project-binding.v1", `caplets.bearer.${Buffer$1.from(remote.auth.token).toString("base64url")}`];
10734
+ }
10735
+ function parseSocketMessage(data) {
10736
+ const text = typeof data === "string" ? data : data instanceof ArrayBuffer ? new TextDecoder().decode(data) : void 0;
10737
+ if (!text) return void 0;
10738
+ const parsed = JSON.parse(text);
10739
+ return typeof parsed.type === "string" ? parsed : void 0;
10740
+ }
10741
+ async function waitForOpen(socket, signal) {
10742
+ if (socket.readyState === 1) return;
10743
+ await Promise.race([new Promise((resolve, reject) => {
10744
+ listen(socket, "open", () => resolve(), { once: true });
10745
+ listen(socket, "error", () => reject(new CapletsError("SERVER_UNAVAILABLE", "Project Binding WebSocket failed to open.")), { once: true });
10746
+ }), waitForAbort(signal).then(() => void 0)]);
10747
+ }
10748
+ function waitForAbort(signal) {
10749
+ if (signal?.aborted) return Promise.resolve("abort");
10750
+ return new Promise((resolve) => {
10751
+ signal?.addEventListener("abort", () => resolve("abort"), { once: true });
10752
+ });
10753
+ }
10754
+ function listen(socket, type, listener, options) {
10755
+ if (socket.addEventListener) {
10756
+ socket.addEventListener(type, listener, options);
10757
+ return;
9634
10758
  }
9635
- processReadBuffer() {
9636
- while (true) try {
9637
- const message = this._readBuffer.readMessage();
9638
- if (message === null) break;
9639
- this.onmessage?.(message);
10759
+ const key = `on${type}`;
10760
+ const existing = socket[key];
10761
+ const wrapper = (event) => {
10762
+ existing?.(event);
10763
+ listener(event);
10764
+ if (options?.once && socket[key] === wrapper) socket[key] = existing ?? null;
10765
+ };
10766
+ socket[key] = wrapper;
10767
+ }
10768
+ function closeSocket(socket, code, reason) {
10769
+ try {
10770
+ socket.close(code, reason);
10771
+ } catch {}
10772
+ }
10773
+ //#endregion
10774
+ //#region src/project-binding/sync-size.ts
10775
+ const DEFAULT_SYNC_LIMITS = {
10776
+ free: {
10777
+ maxSingleFileBytes: 25 * 1024 * 1024,
10778
+ maxProjectBytes: 250 * 1024 * 1024
10779
+ },
10780
+ plus: {
10781
+ maxSingleFileBytes: 100 * 1024 * 1024,
10782
+ maxProjectBytes: 1024 * 1024 * 1024
10783
+ },
10784
+ pro: {
10785
+ maxSingleFileBytes: 250 * 1024 * 1024,
10786
+ maxProjectBytes: 5 * 1024 * 1024 * 1024
10787
+ },
10788
+ enterprise: {
10789
+ maxSingleFileBytes: 250 * 1024 * 1024,
10790
+ maxProjectBytes: 5 * 1024 * 1024 * 1024
10791
+ },
10792
+ self_hosted: {
10793
+ maxSingleFileBytes: 250 * 1024 * 1024,
10794
+ maxProjectBytes: 5 * 1024 * 1024 * 1024
10795
+ }
10796
+ };
10797
+ function enforceProjectSyncSizeLimits(input) {
10798
+ const limits = {
10799
+ ...DEFAULT_SYNC_LIMITS[input.tier],
10800
+ ...input.limits
10801
+ };
10802
+ const totalBytes = input.files.reduce((total, file) => total + file.sizeBytes, 0);
10803
+ const largestFileBytes = Math.max(0, ...input.files.map((file) => file.sizeBytes));
10804
+ if (largestFileBytes > limits.maxSingleFileBytes || totalBytes > limits.maxProjectBytes) return {
10805
+ ok: false,
10806
+ code: "sync_size_limit_exceeded",
10807
+ totalBytes,
10808
+ maxSingleFileBytes: limits.maxSingleFileBytes,
10809
+ maxProjectBytes: limits.maxProjectBytes,
10810
+ largestFileBytes,
10811
+ recoveryCommand: "Add exclusions to .capletsignore or upgrade the workspace plan."
10812
+ };
10813
+ return {
10814
+ ok: true,
10815
+ totalBytes,
10816
+ maxSingleFileBytes: limits.maxSingleFileBytes,
10817
+ maxProjectBytes: limits.maxProjectBytes
10818
+ };
10819
+ }
10820
+ //#endregion
10821
+ //#region src/project-binding/attach.ts
10822
+ async function resolveAttachOptions(raw = {}, env = process.env) {
10823
+ return await resolveAttachOptionsForRun(raw, env);
10824
+ }
10825
+ async function resolveAttachOptionsForRun(raw = {}, env = process.env) {
10826
+ const selection = await resolveRemoteSelection({
10827
+ ...raw.remoteUrl !== void 0 ? { remoteUrl: raw.remoteUrl } : {},
10828
+ ...raw.user !== void 0 ? { user: raw.user } : {},
10829
+ ...raw.password !== void 0 ? { password: raw.password } : {},
10830
+ ...raw.token !== void 0 ? { token: raw.token } : {},
10831
+ ...raw.workspace !== void 0 ? { workspace: raw.workspace } : {},
10832
+ ...raw.fetch !== void 0 ? { fetch: raw.fetch } : {}
10833
+ }, env);
10834
+ return {
10835
+ projectRoot: raw.projectRoot ?? process.cwd(),
10836
+ json: raw.json === true,
10837
+ verbose: raw.verbose === true,
10838
+ once: raw.once === true,
10839
+ remote: selection.remote,
10840
+ authMode: selection.kind,
10841
+ ...selection.kind === "hosted_cloud" ? { selectedWorkspace: selection.selectedWorkspace } : raw.workspace ? { selectedWorkspace: raw.workspace } : {}
10842
+ };
10843
+ }
10844
+ async function attachProjectOnce(raw = {}, env = process.env) {
10845
+ const resolved = await resolveAttachOptionsForRun({
10846
+ ...raw,
10847
+ once: true
10848
+ }, env);
10849
+ bootstrapProjectBindingGitignore(resolved.projectRoot);
10850
+ preflightProjectSync(resolved.projectRoot, hostedTier(env));
10851
+ const response = await (resolved.remote.fetch ?? fetch)(projectBindingProbeUrl(resolved.remote), {
10852
+ ...resolved.remote.requestInit,
10853
+ method: "GET"
10854
+ });
10855
+ if (response.status !== 101 && !await isWebSocketUpgradeRequired(response)) throw new CapletsError("SERVER_UNAVAILABLE", `Project Binding WebSocket unavailable at ${resolved.remote.projectBindingWebSocketUrl}. Run caplets doctor for details.`);
10856
+ return {
10857
+ ok: true,
10858
+ projectRoot: resolved.projectRoot,
10859
+ webSocketUrl: resolved.remote.projectBindingWebSocketUrl.toString()
10860
+ };
10861
+ }
10862
+ async function attachProjectSession(raw = {}, env = process.env, options = {}) {
10863
+ const resolved = await resolveAttachOptionsForRun(raw, env);
10864
+ bootstrapProjectBindingGitignore(resolved.projectRoot);
10865
+ preflightProjectSync(resolved.projectRoot, hostedTier(env));
10866
+ return await runProjectBindingSession({
10867
+ projectRoot: resolved.projectRoot,
10868
+ remote: resolved.remote,
10869
+ fetch: resolved.remote.fetch,
10870
+ signal: options.signal,
10871
+ heartbeatIntervalMs: options.heartbeatIntervalMs,
10872
+ webSocketFactory: options.webSocketFactory,
10873
+ onEvent: options.onEvent
10874
+ });
10875
+ }
10876
+ function projectBindingProbeUrl(remote) {
10877
+ const url = new URL(remote.projectBindingWebSocketUrl);
10878
+ if (url.protocol === "wss:") url.protocol = "https:";
10879
+ if (url.protocol === "ws:") url.protocol = "http:";
10880
+ return url;
10881
+ }
10882
+ async function isWebSocketUpgradeRequired(response) {
10883
+ if (response.status !== 426) return false;
10884
+ if (!(response.headers.get("content-type") ?? "").includes("application/json")) return false;
10885
+ return (await response.json().catch(() => void 0))?.error === "websocket_upgrade_required";
10886
+ }
10887
+ function preflightProjectSync(projectRoot, tier) {
10888
+ if (!existsSync(projectRoot)) return;
10889
+ const size = enforceProjectSyncSizeLimits({
10890
+ tier,
10891
+ files: buildProjectSyncManifest({ projectRoot }).files
10892
+ });
10893
+ if (!size.ok) throw new ProjectBindingError({
10894
+ code: size.code,
10895
+ message: "Project sync size exceeds the selected workspace policy.",
10896
+ recoveryCommand: size.recoveryCommand
10897
+ });
10898
+ }
10899
+ function hostedTier(env) {
10900
+ const value = env.CAPLETS_CLOUD_TIER?.toLowerCase();
10901
+ return value === "plus" || value === "pro" || value === "enterprise" ? value : "free";
10902
+ }
10903
+ //#endregion
10904
+ //#region src/remote-control/client.ts
10905
+ var RemoteControlClient = class {
10906
+ #baseUrl;
10907
+ #requestInit;
10908
+ #fetch;
10909
+ constructor(options) {
10910
+ this.#baseUrl = options.baseUrl;
10911
+ this.#requestInit = options.requestInit;
10912
+ this.#fetch = options.fetch ?? fetch;
10913
+ }
10914
+ async request(command, args) {
10915
+ const controlUrl = controlUrlForBase(this.#baseUrl);
10916
+ let response;
10917
+ try {
10918
+ response = await this.#fetch(controlUrl, {
10919
+ ...this.#requestInit,
10920
+ method: "POST",
10921
+ headers: mergeJsonHeaders(this.#requestInit.headers),
10922
+ body: JSON.stringify({
10923
+ command,
10924
+ arguments: args
10925
+ })
10926
+ });
9640
10927
  } catch (error) {
9641
- this.onerror?.(error);
10928
+ throw new CapletsError("SERVER_UNAVAILABLE", `Could not connect to Caplets server at ${safeBaseUrl(this.#baseUrl)}.`, toSafeError(error, "SERVER_UNAVAILABLE"));
9642
10929
  }
10930
+ if (response.status === 401 || response.status === 403) throw new CapletsError("AUTH_FAILED", "Caplets remote authentication failed. Check CAPLETS_REMOTE_USER and CAPLETS_REMOTE_PASSWORD.");
10931
+ if (!response.ok) throw new CapletsError("SERVER_UNAVAILABLE", `Caplets server at ${safeBaseUrl(this.#baseUrl)} returned HTTP ${response.status}.`);
10932
+ const payload = await parseRemoteCliResponse(response);
10933
+ if (!payload.ok) throw new CapletsError(payload.error.code, redactRemoteMessage(payload.error.message), payload.error.nextAction === void 0 ? void 0 : { nextAction: payload.error.nextAction });
10934
+ return payload.result;
9643
10935
  }
9644
- async close() {
9645
- this._stdin.off("data", this._ondata);
9646
- this._stdin.off("error", this._onerror);
9647
- if (this._stdin.listenerCount("data") === 0) this._stdin.pause();
9648
- this._readBuffer.clear();
9649
- this.onclose?.();
10936
+ };
10937
+ function mergeJsonHeaders(headers) {
10938
+ const merged = new Headers(headers);
10939
+ merged.set("content-type", "application/json");
10940
+ return merged;
10941
+ }
10942
+ function safeBaseUrl(baseUrl) {
10943
+ const safe = new URL(baseUrl.href);
10944
+ safe.username = "";
10945
+ safe.password = "";
10946
+ safe.search = "";
10947
+ safe.hash = "";
10948
+ return safe.toString();
10949
+ }
10950
+ async function parseRemoteCliResponse(response) {
10951
+ let payload;
10952
+ try {
10953
+ payload = await response.json();
10954
+ } catch (error) {
10955
+ throw invalidRemoteControlResponse(error);
9650
10956
  }
9651
- send(message) {
9652
- return new Promise((resolve) => {
9653
- const json = serializeMessage(message);
9654
- if (this._stdout.write(json)) resolve();
9655
- else this._stdout.once("drain", resolve);
9656
- });
10957
+ if (!isRecord$1(payload)) throw invalidRemoteControlResponse();
10958
+ if (payload.ok === true) {
10959
+ if (!("result" in payload)) throw invalidRemoteControlResponse();
10960
+ return {
10961
+ ok: true,
10962
+ result: payload.result
10963
+ };
9657
10964
  }
9658
- };
10965
+ if (payload.ok === false) {
10966
+ const error = payload.error;
10967
+ if (!isRecord$1(error) || typeof error.code !== "string" || typeof error.message !== "string") throw invalidRemoteControlResponse();
10968
+ if ("nextAction" in error && error.nextAction !== void 0 && typeof error.nextAction !== "string") throw invalidRemoteControlResponse();
10969
+ const errorResponse = {
10970
+ ok: false,
10971
+ error: {
10972
+ code: isCapletsErrorCode(error.code) ? error.code : "DOWNSTREAM_TOOL_ERROR",
10973
+ message: error.message
10974
+ }
10975
+ };
10976
+ if (typeof error.nextAction === "string") errorResponse.error.nextAction = error.nextAction;
10977
+ return errorResponse;
10978
+ }
10979
+ throw invalidRemoteControlResponse();
10980
+ }
10981
+ function invalidRemoteControlResponse(cause) {
10982
+ return new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Caplets server returned an invalid remote control response.", cause === void 0 ? void 0 : toSafeError(cause, "DOWNSTREAM_PROTOCOL_ERROR"));
10983
+ }
10984
+ function isRecord$1(value) {
10985
+ return value !== null && typeof value === "object" && !Array.isArray(value);
10986
+ }
10987
+ function isCapletsErrorCode(value) {
10988
+ return CAPLETS_ERROR_CODES.includes(value);
10989
+ }
10990
+ function redactRemoteMessage(message) {
10991
+ return String(redactSecrets(message)).replace(/\b(authorization\s*:\s*(?:basic|bearer)\s+)[^\s,;]+/giu, "$1[REDACTED]").replace(/\b((?:access_)?token=)[^\s,;]+/giu, "$1[REDACTED]").replace(/\b((?:token|secret|authorization|auth|api[-_]?key|password|credential|clientsecret|client_secret|code|refresh(?:_token)?)\s*[=:]\s*)[^\s,;]+/giu, "$1[REDACTED]");
10992
+ }
9659
10993
  //#endregion
9660
10994
  //#region src/serve/stdio.ts
9661
10995
  async function serveStdio(options = {}) {
@@ -9671,20 +11005,402 @@ async function serveStdio(options = {}) {
9671
11005
  await engine.close();
9672
11006
  }
9673
11007
  };
9674
- let sigintHandler;
9675
- let sigtermHandler;
9676
- if (options.signalHandling !== false) {
9677
- sigintHandler = () => void close().finally(() => process.exit(130));
9678
- sigtermHandler = () => void close().finally(() => process.exit(143));
9679
- process.once("SIGINT", sigintHandler);
9680
- process.once("SIGTERM", sigtermHandler);
9681
- }
11008
+ let sigintHandler;
11009
+ let sigtermHandler;
11010
+ if (options.signalHandling !== false) {
11011
+ sigintHandler = () => void close().finally(() => process.exit(130));
11012
+ sigtermHandler = () => void close().finally(() => process.exit(143));
11013
+ process.once("SIGINT", sigintHandler);
11014
+ process.once("SIGTERM", sigtermHandler);
11015
+ }
11016
+ try {
11017
+ await session.connect(new StdioServerTransport());
11018
+ } finally {
11019
+ if (sigintHandler) process.off("SIGINT", sigintHandler);
11020
+ if (sigtermHandler) process.off("SIGTERM", sigtermHandler);
11021
+ await close();
11022
+ }
11023
+ }
11024
+ //#endregion
11025
+ //#region src/serve/daemon/config.ts
11026
+ function readDaemonConfig(paths) {
11027
+ return readJson(paths.configFile);
11028
+ }
11029
+ function writeDaemonConfig(paths, serve, command, now = /* @__PURE__ */ new Date()) {
11030
+ const config = {
11031
+ instance: "default",
11032
+ serve,
11033
+ command,
11034
+ paths,
11035
+ updatedAt: now.toISOString()
11036
+ };
11037
+ writeJson(paths.configFile, config);
11038
+ return config;
11039
+ }
11040
+ function readDaemonState(paths) {
11041
+ return readJson(paths.stateFile);
11042
+ }
11043
+ function writeDaemonState(paths, state, now = /* @__PURE__ */ new Date()) {
11044
+ const next = {
11045
+ instance: "default",
11046
+ ...state,
11047
+ updatedAt: state.updatedAt ?? now.toISOString()
11048
+ };
11049
+ writeJson(paths.stateFile, next);
11050
+ return next;
11051
+ }
11052
+ function redactDaemonStatus(status) {
11053
+ return redactDaemonValue(status);
11054
+ }
11055
+ function redactDaemonValue(value) {
11056
+ if (Array.isArray(value)) return redactDaemonArray(value);
11057
+ if (!value || typeof value !== "object") return value;
11058
+ const redacted = {};
11059
+ for (const [key, nested] of Object.entries(value)) redacted[key] = /password|token|secret|authorization|credential/iu.test(key) ? "[REDACTED]" : redactDaemonValue(nested);
11060
+ return redacted;
11061
+ }
11062
+ function redactDaemonArray(value) {
11063
+ const redacted = [];
11064
+ let redactNext = false;
11065
+ for (const item of value) {
11066
+ if (redactNext) {
11067
+ redacted.push("[REDACTED]");
11068
+ redactNext = false;
11069
+ continue;
11070
+ }
11071
+ redacted.push(redactDaemonValue(item));
11072
+ if (typeof item === "string" && /--(?:password|token|secret|authorization|credential)$/iu.test(item)) redactNext = true;
11073
+ }
11074
+ return redacted;
11075
+ }
11076
+ function readJson(path) {
11077
+ if (!existsSync(path)) return void 0;
11078
+ return JSON.parse(readFileSync(path, "utf8"));
11079
+ }
11080
+ function writeJson(path, value) {
11081
+ mkdirSync(dirname(path), { recursive: true });
11082
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
11083
+ }
11084
+ //#endregion
11085
+ //#region src/serve/daemon/platform-darwin.ts
11086
+ function buildLaunchdUserAgentDescriptor(paths, command) {
11087
+ const label = "dev.caplets.serve.default";
11088
+ return {
11089
+ kind: "launchd-user-agent",
11090
+ label,
11091
+ path: `${paths.configFile}.plist`,
11092
+ plist: [
11093
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
11094
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">",
11095
+ "<plist version=\"1.0\">",
11096
+ "<dict>",
11097
+ " <key>Label</key>",
11098
+ ` <string>${label}</string>`,
11099
+ " <key>ProgramArguments</key>",
11100
+ " <array>",
11101
+ ` <string>${escapeXml(command.executable)}</string>`,
11102
+ ...command.args.map((arg) => ` <string>${escapeXml(arg)}</string>`),
11103
+ " </array>",
11104
+ " <key>RunAtLoad</key>",
11105
+ " <true/>",
11106
+ " <key>KeepAlive</key>",
11107
+ " <true/>",
11108
+ " <key>StandardOutPath</key>",
11109
+ ` <string>${escapeXml(paths.stdoutLog)}</string>`,
11110
+ " <key>StandardErrorPath</key>",
11111
+ ` <string>${escapeXml(paths.stderrLog)}</string>`,
11112
+ "</dict>",
11113
+ "</plist>",
11114
+ ""
11115
+ ].join("\n")
11116
+ };
11117
+ }
11118
+ //#endregion
11119
+ //#region src/serve/daemon/platform-linux.ts
11120
+ function buildLinuxServiceDescriptor(paths, command, serviceAvailable = true) {
11121
+ if (!serviceAvailable) return {
11122
+ kind: "manual",
11123
+ reason: "Linux systemd user service is not available; run the daemon command manually.",
11124
+ command
11125
+ };
11126
+ return {
11127
+ kind: "systemd-user",
11128
+ unitName: "caplets-serve-default.service",
11129
+ path: `${paths.configFile}.service`,
11130
+ unit: [
11131
+ "[Unit]",
11132
+ "Description=Caplets HTTP daemon (default)",
11133
+ "After=network.target",
11134
+ "",
11135
+ "[Service]",
11136
+ "Type=simple",
11137
+ `ExecStart=${shellJoin([command.executable, ...command.args])}`,
11138
+ "Restart=on-failure",
11139
+ `StandardOutput=append:${paths.stdoutLog}`,
11140
+ `StandardError=append:${paths.stderrLog}`,
11141
+ "",
11142
+ "[Install]",
11143
+ "WantedBy=default.target",
11144
+ ""
11145
+ ].join("\n")
11146
+ };
11147
+ }
11148
+ function shellJoin(args) {
11149
+ return args.map((arg) => /^[A-Za-z0-9_./:=@-]+$/u.test(arg) ? arg : `'${arg.replaceAll("'", "'\\''")}'`).join(" ");
11150
+ }
11151
+ //#endregion
11152
+ //#region src/serve/daemon/platform-windows.ts
11153
+ function buildWindowsScheduledTaskDescriptor(command) {
11154
+ const taskName = "Caplets Serve Default";
11155
+ return {
11156
+ kind: "windows-scheduled-task",
11157
+ taskName,
11158
+ commands: {
11159
+ register: `schtasks /Create /TN "${taskName}" /SC ONLOGON /TR "${commandLine([command.executable, ...command.args])}" /F`,
11160
+ unregister: `schtasks /Delete /TN "${taskName}" /F`,
11161
+ query: `schtasks /Query /TN "${taskName}"`
11162
+ }
11163
+ };
11164
+ }
11165
+ function commandLine(args) {
11166
+ return args.map((arg) => arg.includes(" ") ? `\\"${arg}\\"` : arg).join(" ");
11167
+ }
11168
+ //#endregion
11169
+ //#region src/serve/daemon/platform.ts
11170
+ function buildDaemonPlatformDescriptor(options) {
11171
+ const platform = options.platform ?? process.platform;
11172
+ if (platform === "darwin") return buildLaunchdUserAgentDescriptor(options.paths, options.command);
11173
+ if (platform === "linux") return buildLinuxServiceDescriptor(options.paths, options.command, options.serviceAvailable);
11174
+ if (platform === "win32") return buildWindowsScheduledTaskDescriptor(options.command);
11175
+ return {
11176
+ kind: "manual",
11177
+ reason: `Automatic user service descriptors are not available on ${platform}.`,
11178
+ command: options.command
11179
+ };
11180
+ }
11181
+ function escapeXml(value) {
11182
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
11183
+ }
11184
+ //#endregion
11185
+ //#region src/serve/daemon/process.ts
11186
+ function daemonServeCommand(options) {
11187
+ return {
11188
+ executable: process.argv[1] ?? "caplets",
11189
+ args: daemonServeArgs(options)
11190
+ };
11191
+ }
11192
+ function daemonServeArgs(options) {
11193
+ const args = [
11194
+ "serve",
11195
+ "--transport",
11196
+ "http",
11197
+ "--host",
11198
+ options.host,
11199
+ "--port",
11200
+ String(options.port),
11201
+ "--path",
11202
+ options.path,
11203
+ "--user",
11204
+ options.auth.user
11205
+ ];
11206
+ if (options.auth.enabled) args.push("--password", options.auth.password);
11207
+ if (options.warnUnauthenticatedNetwork) args.push("--allow-unauthenticated-http");
11208
+ if (options.trustProxy) args.push("--trust-proxy");
11209
+ return args;
11210
+ }
11211
+ function createNodeDaemonProcessRunner() {
11212
+ return {
11213
+ async isRunning(pid) {
11214
+ try {
11215
+ process.kill(pid, 0);
11216
+ return true;
11217
+ } catch {
11218
+ return false;
11219
+ }
11220
+ },
11221
+ async start(command) {
11222
+ mkdirSync(dirname(command.stdoutLog), { recursive: true });
11223
+ mkdirSync(dirname(command.stderrLog), { recursive: true });
11224
+ const stdout = openSync(command.stdoutLog, "a");
11225
+ const stderr = openSync(command.stderrLog, "a");
11226
+ try {
11227
+ const child = spawn(process.execPath, [process.argv[1] ?? "caplets", ...command.args], {
11228
+ detached: true,
11229
+ stdio: [
11230
+ "ignore",
11231
+ stdout,
11232
+ stderr
11233
+ ],
11234
+ env: process.env
11235
+ });
11236
+ child.unref();
11237
+ return child.pid ?? 0;
11238
+ } finally {
11239
+ closeSync(stdout);
11240
+ closeSync(stderr);
11241
+ }
11242
+ },
11243
+ async stop(pid) {
11244
+ try {
11245
+ process.kill(pid, "SIGTERM");
11246
+ } catch {
11247
+ return;
11248
+ }
11249
+ }
11250
+ };
11251
+ }
11252
+ //#endregion
11253
+ //#region src/serve/daemon/paths.ts
11254
+ function resolveServeDaemonPaths(options = {}) {
11255
+ const platform = options.platform ?? process.platform;
11256
+ const home = options.home ?? homedir();
11257
+ const env = options.env ?? process.env;
11258
+ const path = platform === "win32" ? win32 : posix;
11259
+ const configBase = defaultConfigBaseDir(env, home, platform);
11260
+ const stateBase = defaultStateBaseDir(env, home, platform);
11261
+ if (platform === "win32") {
11262
+ const stateDir = path.join(stateBase, "Caplets", "State", "serve", "default");
11263
+ const logDir = path.join(stateDir, "logs");
11264
+ return {
11265
+ instance: "default",
11266
+ stateDir,
11267
+ logDir,
11268
+ stateFile: path.join(stateDir, "state.json"),
11269
+ pidFile: path.join(stateDir, "server.pid"),
11270
+ stdoutLog: path.join(logDir, "stdout.log"),
11271
+ stderrLog: path.join(logDir, "stderr.log"),
11272
+ configFile: path.join(configBase, "Caplets", "serve", "default.json")
11273
+ };
11274
+ }
11275
+ const stateDir = path.join(stateBase, "caplets", "serve", "default");
11276
+ const logDir = path.join(stateDir, "logs");
11277
+ return {
11278
+ instance: "default",
11279
+ stateDir,
11280
+ logDir,
11281
+ stateFile: path.join(stateDir, "state.json"),
11282
+ pidFile: path.join(stateDir, "server.pid"),
11283
+ stdoutLog: path.join(logDir, "stdout.log"),
11284
+ stderrLog: path.join(logDir, "stderr.log"),
11285
+ configFile: path.join(configBase, "caplets", "serve", "default.json")
11286
+ };
11287
+ }
11288
+ //#endregion
11289
+ //#region src/serve/daemon/index.ts
11290
+ async function startDaemon(raw = {}, options = {}) {
11291
+ const paths = resolveServeDaemonPaths(options);
11292
+ const processRunner = options.process ?? createNodeDaemonProcessRunner();
11293
+ const existing = await daemonStatus({
11294
+ ...options,
11295
+ process: processRunner
11296
+ });
11297
+ if (existing.running) throw new CapletsError("REQUEST_INVALID", "Caplets HTTP daemon is already running.");
11298
+ const serve = resolveDaemonServeOptions(raw, options.env ?? process.env);
11299
+ const command = daemonServeCommand(serve);
11300
+ mkdirSync(paths.logDir, { recursive: true });
11301
+ const config = writeDaemonConfig(paths, serve, command);
11302
+ const pid = await processRunner.start({
11303
+ args: command.args,
11304
+ stdoutLog: paths.stdoutLog,
11305
+ stderrLog: paths.stderrLog,
11306
+ configFile: paths.configFile
11307
+ });
11308
+ writeFileSync(paths.pidFile, `${pid}\n`);
11309
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11310
+ return { status: redactDaemonStatus({
11311
+ ...writeDaemonState(paths, {
11312
+ running: true,
11313
+ pid,
11314
+ startedAt: now,
11315
+ enabled: existing.enabled,
11316
+ updatedAt: now
11317
+ }),
11318
+ paths,
11319
+ config
11320
+ }) };
11321
+ }
11322
+ async function stopDaemon(options = {}) {
11323
+ const paths = resolveServeDaemonPaths(options);
11324
+ const processRunner = options.process ?? createNodeDaemonProcessRunner();
11325
+ const existing = await daemonStatus({
11326
+ ...options,
11327
+ process: processRunner
11328
+ });
11329
+ if (existing.running && existing.pid !== void 0) await processRunner.stop(existing.pid);
11330
+ rmSync(paths.pidFile, { force: true });
11331
+ return { status: redactDaemonStatus({
11332
+ ...writeDaemonState(paths, {
11333
+ running: false,
11334
+ enabled: existing.enabled
11335
+ }),
11336
+ paths,
11337
+ ...configProperty(readDaemonConfig(paths))
11338
+ }) };
11339
+ }
11340
+ async function restartDaemon(raw = {}, options = {}) {
11341
+ await stopDaemon(options);
11342
+ return startDaemon(raw, options);
11343
+ }
11344
+ async function daemonStatus(options = {}) {
11345
+ const paths = resolveServeDaemonPaths(options);
11346
+ const processRunner = options.process ?? createNodeDaemonProcessRunner();
11347
+ const config = readDaemonConfig(paths);
11348
+ const storedState = readDaemonState(paths);
11349
+ const pid = readPid(paths.pidFile) ?? storedState?.pid;
11350
+ const running = pid === void 0 ? false : await processRunner.isRunning(pid);
11351
+ if (!running) rmSync(paths.pidFile, { force: true });
11352
+ return redactDaemonStatus({
11353
+ ...writeDaemonState(paths, {
11354
+ running,
11355
+ ...running && pid !== void 0 ? { pid } : {},
11356
+ ...running && storedState?.startedAt ? { startedAt: storedState.startedAt } : {},
11357
+ enabled: storedState?.enabled ?? false
11358
+ }),
11359
+ paths,
11360
+ ...config ? { config } : {}
11361
+ });
11362
+ }
11363
+ async function enableDaemon(options = {}) {
11364
+ return setDaemonEnabled(true, options);
11365
+ }
11366
+ async function disableDaemon(options = {}) {
11367
+ return setDaemonEnabled(false, options);
11368
+ }
11369
+ async function setDaemonEnabled(enabled, options) {
11370
+ const paths = resolveServeDaemonPaths(options);
11371
+ const config = readDaemonConfig(paths);
11372
+ const command = config?.command ?? daemonServeCommand(resolveDaemonServeOptions({}, options.env));
11373
+ const descriptor = buildDaemonPlatformDescriptor({
11374
+ ...options.platform !== void 0 ? { platform: options.platform } : {},
11375
+ ...options.serviceAvailable !== void 0 ? { serviceAvailable: options.serviceAvailable } : {},
11376
+ paths,
11377
+ command
11378
+ });
11379
+ const current = await daemonStatus(options);
11380
+ return {
11381
+ enabled,
11382
+ descriptor,
11383
+ status: redactDaemonStatus({
11384
+ ...writeDaemonState(paths, {
11385
+ running: current.running,
11386
+ ...current.running && current.pid !== void 0 ? { pid: current.pid } : {},
11387
+ ...current.running && current.startedAt ? { startedAt: current.startedAt } : {},
11388
+ enabled
11389
+ }),
11390
+ paths,
11391
+ ...configProperty(config)
11392
+ })
11393
+ };
11394
+ }
11395
+ function configProperty(config) {
11396
+ return config ? { config } : {};
11397
+ }
11398
+ function readPid(path) {
9682
11399
  try {
9683
- await session.connect(new StdioServerTransport());
9684
- } finally {
9685
- if (sigintHandler) process.off("SIGINT", sigintHandler);
9686
- if (sigtermHandler) process.off("SIGTERM", sigtermHandler);
9687
- await close();
11400
+ const value = Number(readFileSync(path, "utf8").trim());
11401
+ return Number.isInteger(value) && value > 0 ? value : void 0;
11402
+ } catch {
11403
+ return;
9688
11404
  }
9689
11405
  }
9690
11406
  //#endregion
@@ -9730,6 +11446,61 @@ async function runCli(args, io = {}) {
9730
11446
  function normalizeCompletionWords(words) {
9731
11447
  return words.map((word) => word === "__CAPLETS_TRAILING_SPACE__" ? "" : word);
9732
11448
  }
11449
+ function addServeDaemonCommand(parent, name, description, action) {
11450
+ parent.command(name).description(description).option("--transport <transport>", "server transport: http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--user <user>", "HTTP Basic Auth username").option("--password <password>", "HTTP Basic Auth password").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").option("--json", "print JSON output").action(function(options) {
11451
+ return action({
11452
+ ...this.parent?.opts(),
11453
+ ...options
11454
+ });
11455
+ });
11456
+ }
11457
+ function cloudAuthStore(env) {
11458
+ return new CloudAuthStore({ env });
11459
+ }
11460
+ function cloudAuthStatus(credentials) {
11461
+ return redactedCloudAuthStatus(credentials);
11462
+ }
11463
+ function isProjectBindingWebSocketUnavailable(error) {
11464
+ return error instanceof CapletsError && error.code === "SERVER_UNAVAILABLE" && error.message.includes("Project Binding WebSocket unavailable");
11465
+ }
11466
+ function isProjectBindingCliError(error) {
11467
+ return error instanceof ProjectBindingError;
11468
+ }
11469
+ function serveRawOptions(options) {
11470
+ return {
11471
+ ...options.transport !== void 0 ? { transport: options.transport } : {},
11472
+ ...options.host !== void 0 ? { host: options.host } : {},
11473
+ ...options.port !== void 0 ? { port: options.port } : {},
11474
+ ...options.path !== void 0 ? { path: options.path } : {},
11475
+ ...options.user !== void 0 ? { user: options.user } : {},
11476
+ ...options.password !== void 0 ? { password: options.password } : {},
11477
+ ...options.allowUnauthenticatedHttp !== void 0 ? { allowUnauthenticatedHttp: options.allowUnauthenticatedHttp } : {},
11478
+ ...options.trustProxy !== void 0 ? { trustProxy: options.trustProxy } : {}
11479
+ };
11480
+ }
11481
+ async function waitForCloudLogin(client, loginId, env) {
11482
+ const timeoutMs = numberEnv(env.CAPLETS_CLOUD_AUTH_TIMEOUT_MS, 12e4);
11483
+ const intervalMs = numberEnv(env.CAPLETS_CLOUD_AUTH_POLL_INTERVAL_MS, 1500);
11484
+ const started = Date.now();
11485
+ while (Date.now() - started <= timeoutMs) {
11486
+ const result = await client.pollLogin(loginId);
11487
+ if (result.status !== "pending" && result.status !== "workspace_selection_required") return result;
11488
+ await sleep(intervalMs);
11489
+ }
11490
+ return {
11491
+ status: "expired",
11492
+ message: "Cloud Auth login timed out."
11493
+ };
11494
+ }
11495
+ function numberEnv(value, fallback) {
11496
+ if (value === void 0) return fallback;
11497
+ const parsed = Number(value);
11498
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
11499
+ }
11500
+ async function sleep(ms) {
11501
+ if (ms <= 0) return;
11502
+ await new Promise((resolve) => setTimeout(resolve, ms));
11503
+ }
9733
11504
  function createProgram(io = {}) {
9734
11505
  const writeOut = io.writeOut ?? ((value) => process.stdout.write(value));
9735
11506
  const writeErr = io.writeErr ?? ((value) => process.stderr.write(value));
@@ -9744,11 +11515,11 @@ function createProgram(io = {}) {
9744
11515
  writeErr,
9745
11516
  outputError: (value, write) => write(value)
9746
11517
  });
9747
- program.command(cliCommands.completion).description("Print a shell completion script.").argument("<shell>", "completion shell: bash, zsh, fish, powershell, or cmd").action((shell) => {
11518
+ program.command(cliCommands$1.completion).description("Print a shell completion script.").argument("<shell>", "completion shell: bash, zsh, fish, powershell, or cmd").action((shell) => {
9748
11519
  if (!completionShells.includes(shell)) throw new CapletsError("REQUEST_INVALID", "completion shell must be bash, zsh, fish, powershell, or cmd");
9749
11520
  writeOut(completionScript(shell));
9750
11521
  });
9751
- program.command(cliCommands.completeHidden, { hidden: true }).description("Internal shell completion endpoint.").option("--shell <shell>", "completion shell").allowUnknownOption(true).argument("[words...]", "words to complete").action(async (words, options) => {
11522
+ program.command(cliCommands$1.completeHidden, { hidden: true }).description("Internal shell completion endpoint.").option("--shell <shell>", "completion shell").allowUnknownOption(true).argument("[words...]", "words to complete").action(async (words, options) => {
9752
11523
  const shell = completionShells.includes(options.shell) ? options.shell : "bash";
9753
11524
  const remote = remoteClientForCli(io);
9754
11525
  const configPath = currentConfigPath();
@@ -9781,7 +11552,7 @@ function createProgram(io = {}) {
9781
11552
  }
9782
11553
  if (suggestions.length > 0) writeOut(`${suggestions.join("\n")}\n`);
9783
11554
  });
9784
- program.command(cliCommands.serve).description("Serve configured Caplets as an MCP server.").option("--transport <transport>", "server transport: stdio or http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--user <user>", "HTTP Basic Auth username").option("--password <password>", "HTTP Basic Auth password").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").action(async (options) => {
11555
+ const serve = program.command(cliCommands$1.serve).description("Serve configured Caplets as an MCP server.").option("--transport <transport>", "server transport: stdio or http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--user <user>", "HTTP Basic Auth username").option("--password <password>", "HTTP Basic Auth password").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").action(async (options) => {
9785
11556
  const resolved = resolveServeOptions(options);
9786
11557
  const configPath = currentConfigPath();
9787
11558
  await (io.serve ?? ((serveOptions) => serveResolvedCaplets(serveOptions, {
@@ -9789,7 +11560,233 @@ function createProgram(io = {}) {
9789
11560
  ...io.authDir ? { authDir: io.authDir } : {}
9790
11561
  }, writeErr)))(resolved);
9791
11562
  });
9792
- program.command(cliCommands.init).description("Create a starter Caplets config file.").option("--project", "create the project Caplets config").option("-g, --global", "create the user Caplets config").option("--remote", "create the remote Caplets config").option("--force", "overwrite an existing config file").action(async (options) => {
11563
+ const daemonOptions = () => ({
11564
+ env,
11565
+ ...io.daemon
11566
+ });
11567
+ addServeDaemonCommand(serve, "start", "Start the default Caplets HTTP daemon.", async (options) => {
11568
+ const result = await startDaemon(serveRawOptions(options), daemonOptions());
11569
+ const serveConfig = result.status.config?.serve;
11570
+ writeOut(`Started Caplets HTTP daemon on ${serveConfig?.host ?? "127.0.0.1"}:${serveConfig?.port ?? 5387}.\n`);
11571
+ if (options.json) writeOut(`${JSON.stringify(result.status, null, 2)}\n`);
11572
+ });
11573
+ addServeDaemonCommand(serve, "stop", "Stop the default Caplets HTTP daemon.", async (options) => {
11574
+ const result = await stopDaemon(daemonOptions());
11575
+ if (options.json) {
11576
+ writeOut(`${JSON.stringify(result.status, null, 2)}\n`);
11577
+ return;
11578
+ }
11579
+ writeOut("Stopped Caplets HTTP daemon.\n");
11580
+ });
11581
+ addServeDaemonCommand(serve, "status", "Show the default Caplets HTTP daemon status.", async (options) => {
11582
+ const status = await daemonStatus(daemonOptions());
11583
+ if (options.json) {
11584
+ writeOut(`${JSON.stringify(status, null, 2)}\n`);
11585
+ return;
11586
+ }
11587
+ writeOut(status.running ? `Caplets HTTP daemon is running${status.pid ? ` (pid ${status.pid})` : ""}.\n` : "Caplets HTTP daemon is stopped.\n");
11588
+ });
11589
+ addServeDaemonCommand(serve, "restart", "Restart the default Caplets HTTP daemon.", async (options) => {
11590
+ const result = await restartDaemon(serveRawOptions(options), daemonOptions());
11591
+ if (options.json) {
11592
+ writeOut(`${JSON.stringify(result.status, null, 2)}\n`);
11593
+ return;
11594
+ }
11595
+ const serveConfig = result.status.config?.serve;
11596
+ writeOut(`Restarted Caplets HTTP daemon on ${serveConfig?.host ?? "127.0.0.1"}:${serveConfig?.port ?? 5387}.\n`);
11597
+ });
11598
+ addServeDaemonCommand(serve, "enable", "Enable the default Caplets HTTP daemon at login.", async (options) => {
11599
+ const result = await enableDaemon(daemonOptions());
11600
+ if (options.json) {
11601
+ writeOut(`${JSON.stringify(result, null, 2)}\n`);
11602
+ return;
11603
+ }
11604
+ writeOut(`Enabled Caplets HTTP daemon at login (${result.descriptor.kind}).\n`);
11605
+ });
11606
+ addServeDaemonCommand(serve, "disable", "Disable the default Caplets HTTP daemon at login.", async (options) => {
11607
+ const result = await disableDaemon(daemonOptions());
11608
+ if (options.json) {
11609
+ writeOut(`${JSON.stringify(result, null, 2)}\n`);
11610
+ return;
11611
+ }
11612
+ writeOut(`Disabled Caplets HTTP daemon at login (${result.descriptor.kind}).\n`);
11613
+ });
11614
+ program.command(cliCommands$1.attach).description("Start a remote-backed Caplets MCP server.").option("--transport <transport>", "server transport: stdio or http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--remote-url <url>", "remote Caplets service base URL").option("--user <user>", "remote Basic Auth username").option("--password <password>", "remote Basic Auth password").option("--token <token>", "remote bearer token").option("--workspace <workspace>", "hosted Cloud workspace ID or slug").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").option("--json", "print JSON status events").option("--verbose", "print detailed attach diagnostics").option("--once", "validate Project Binding once and exit").option("--project-root <path>", "test-only project root override").action(async (options) => {
11615
+ try {
11616
+ const attachOptions = {
11617
+ ...options,
11618
+ ...io.fetch ? { fetch: io.fetch } : {}
11619
+ };
11620
+ if (!options.once) {
11621
+ const resolved = await resolveAttachServeOptions(attachOptions, env);
11622
+ await (io.attachServe ?? ((serveOptions) => attachResolvedCaplets(serveOptions, { writeErr })))(resolved);
11623
+ return;
11624
+ }
11625
+ const result = await attachProjectOnce(attachOptions, env);
11626
+ if (options.json) {
11627
+ writeOut(`${JSON.stringify(result, null, 2)}\n`);
11628
+ return;
11629
+ }
11630
+ writeOut(`Project Binding available at ${result.webSocketUrl}.\n`);
11631
+ } catch (error) {
11632
+ if (options.json && isProjectBindingWebSocketUnavailable(error)) {
11633
+ writeOut(`${JSON.stringify({
11634
+ ok: false,
11635
+ error: {
11636
+ code: "PROJECT_BINDING_WEBSOCKET_UNAVAILABLE",
11637
+ message: error instanceof Error ? error.message : String(error)
11638
+ }
11639
+ }, null, 2)}\n`);
11640
+ setExitCode(1);
11641
+ return;
11642
+ }
11643
+ if (options.json && isProjectBindingCliError(error)) {
11644
+ writeOut(`${JSON.stringify({
11645
+ ok: false,
11646
+ error: {
11647
+ code: error.projectBindingCode,
11648
+ message: error.message,
11649
+ recoveryCommand: error.recoveryCommand,
11650
+ requestId: error.requestId
11651
+ }
11652
+ }, null, 2)}\n`);
11653
+ setExitCode(1);
11654
+ return;
11655
+ }
11656
+ if (options.json && error instanceof CapletsError) {
11657
+ writeOut(`${JSON.stringify({
11658
+ ok: false,
11659
+ error: {
11660
+ code: error.code,
11661
+ message: error.message
11662
+ }
11663
+ }, null, 2)}\n`);
11664
+ setExitCode(1);
11665
+ return;
11666
+ }
11667
+ throw error;
11668
+ }
11669
+ });
11670
+ const cloudAuth = program.command(cliCommands$1.cloud).description("Manage hosted Caplets Cloud.").command("auth").description("Authenticate this Caplets client to hosted Caplets Cloud.");
11671
+ cloudAuth.command("login").description("Log in to hosted Caplets Cloud.").option("--cloud-url <url>", "hosted Caplets Cloud URL").option("--workspace <workspace>", "workspace ID or slug to select").option("--device-name <name>", "device label for this Cloud Auth credential").option("--no-open", "print the login URL without opening a browser").option("--json", "print JSON output").action(async (options) => {
11672
+ const client = new CloudAuthClient({
11673
+ cloudUrl: options.cloudUrl ?? env.CAPLETS_CLOUD_URL ?? "https://cloud.caplets.dev",
11674
+ ...io.fetch ? { fetch: io.fetch } : {}
11675
+ });
11676
+ const started = await client.startLogin({
11677
+ requestedWorkspace: options.workspace,
11678
+ deviceName: options.deviceName ?? env.CAPLETS_DEVICE_NAME ?? "Caplets CLI"
11679
+ });
11680
+ if (options.open !== false) await openBrowserUrl(started.loginUrl);
11681
+ if (!options.json) {
11682
+ writeOut(`Open ${started.loginUrl}\n`);
11683
+ writeOut(`Enter code ${started.userCode} if prompted.\n`);
11684
+ }
11685
+ const completed = await waitForCloudLogin(client, started.loginId, env);
11686
+ if (completed.status !== "completed") throw new CapletsError("AUTH_FAILED", `Cloud Auth login ${completed.status}.`);
11687
+ const exchanged = await client.exchangeToken({
11688
+ loginId: started.loginId,
11689
+ oneTimeCode: completed.oneTimeCode
11690
+ });
11691
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11692
+ const credentials = {
11693
+ version: 2,
11694
+ cloudUrl: exchanged.cloudUrl,
11695
+ workspaceId: exchanged.workspaceId,
11696
+ ...exchanged.workspaceSlug ? { workspaceSlug: exchanged.workspaceSlug } : {},
11697
+ accessToken: exchanged.accessToken,
11698
+ refreshToken: exchanged.refreshToken ?? "",
11699
+ expiresAt: exchanged.expiresAt,
11700
+ scope: exchanged.scope,
11701
+ tokenType: exchanged.tokenType,
11702
+ credentialFamilyId: exchanged.credentialFamilyId,
11703
+ deviceName: exchanged.deviceName ?? options.deviceName ?? "Caplets CLI",
11704
+ createdAt: now,
11705
+ lastRefreshAt: now
11706
+ };
11707
+ await cloudAuthStore(env).save(credentials);
11708
+ const status = cloudAuthStatus(credentials);
11709
+ if (options.json) {
11710
+ writeOut(`${JSON.stringify(status, null, 2)}\n`);
11711
+ return;
11712
+ }
11713
+ writeOut(`Authenticated to ${credentials.cloudUrl} as workspace ${credentials.workspaceSlug ?? credentials.workspaceId}.\n`);
11714
+ });
11715
+ cloudAuth.command("status").description("Show hosted Caplets Cloud authentication status.").option("--json", "print JSON output").action(async (options) => {
11716
+ const credentials = await cloudAuthStore(env).load();
11717
+ if (options.json) {
11718
+ writeOut(`${JSON.stringify(cloudAuthStatus(credentials), null, 2)}\n`);
11719
+ return;
11720
+ }
11721
+ writeOut(credentials ? `Authenticated to ${credentials.cloudUrl} as workspace ${credentials.workspaceSlug ?? credentials.workspaceId}.\n` : "Not authenticated to hosted Caplets Cloud.\n");
11722
+ });
11723
+ cloudAuth.command("logout").description("Log out of hosted Caplets Cloud.").action(async () => {
11724
+ const store = cloudAuthStore(env);
11725
+ const credentials = await store.load();
11726
+ if (credentials?.refreshToken) await new CloudAuthClient({
11727
+ cloudUrl: credentials.cloudUrl,
11728
+ ...io.fetch ? { fetch: io.fetch } : {}
11729
+ }).logout(credentials.refreshToken).catch(() => void 0);
11730
+ await store.clear();
11731
+ writeOut("Logged out of hosted Caplets Cloud.\n");
11732
+ });
11733
+ cloudAuth.command("workspaces").description("List hosted Caplets Cloud workspaces.").option("--json", "print JSON output").action(async (options) => {
11734
+ const credentials = await cloudAuthStore(env).load();
11735
+ const workspaces = credentials?.accessToken ? (await new CloudAuthClient({
11736
+ cloudUrl: credentials.cloudUrl,
11737
+ ...io.fetch ? { fetch: io.fetch } : {}
11738
+ }).workspaces(credentials.accessToken).catch(() => ({ workspaces: [{
11739
+ workspaceId: credentials.workspaceId,
11740
+ ...credentials.workspaceSlug ? { slug: credentials.workspaceSlug } : {}
11741
+ }] }))).workspaces.map((workspace) => ({
11742
+ ...workspace,
11743
+ selected: workspace.workspaceId === credentials.workspaceId || workspace.slug === credentials.workspaceSlug
11744
+ })) : [];
11745
+ if (options.json) {
11746
+ writeOut(`${JSON.stringify({ workspaces }, null, 2)}\n`);
11747
+ return;
11748
+ }
11749
+ if (workspaces.length === 0) {
11750
+ writeOut("No hosted Caplets Cloud workspaces available. Run caplets cloud auth login.\n");
11751
+ return;
11752
+ }
11753
+ for (const workspace of workspaces) writeOut(`${workspace.selected ? "* " : " "}${workspace.slug ?? workspace.workspaceId}\n`);
11754
+ });
11755
+ cloudAuth.command("switch").description("Switch the hosted Caplets Cloud Selected Workspace.").argument("<workspace>", "workspace ID or slug").option("--json", "print JSON output").action(async (workspace, options) => {
11756
+ const store = cloudAuthStore(env);
11757
+ const credentials = await store.load();
11758
+ if (!credentials) throw new CapletsError("AUTH_REQUIRED", "Run caplets cloud auth login first.");
11759
+ const switched = await new CloudAuthClient({
11760
+ cloudUrl: credentials.cloudUrl,
11761
+ ...io.fetch ? { fetch: io.fetch } : {}
11762
+ }).switchWorkspace({
11763
+ accessToken: credentials.accessToken,
11764
+ refreshToken: credentials.refreshToken,
11765
+ workspace,
11766
+ deviceName: credentials.deviceName
11767
+ });
11768
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11769
+ const next = {
11770
+ ...credentials,
11771
+ workspaceId: switched.workspaceId,
11772
+ workspaceSlug: switched.workspaceSlug,
11773
+ accessToken: switched.accessToken,
11774
+ refreshToken: switched.refreshToken ?? credentials.refreshToken,
11775
+ expiresAt: switched.expiresAt,
11776
+ scope: switched.scope,
11777
+ tokenType: switched.tokenType,
11778
+ credentialFamilyId: switched.credentialFamilyId,
11779
+ lastRefreshAt: now,
11780
+ selectedWorkspaceSwitchedAt: now
11781
+ };
11782
+ await store.save(next);
11783
+ if (options.json) {
11784
+ writeOut(`${JSON.stringify(cloudAuthStatus(next), null, 2)}\n`);
11785
+ return;
11786
+ }
11787
+ writeOut(`Selected workspace ${next.workspaceSlug ?? next.workspaceId}.\n`);
11788
+ });
11789
+ program.command(cliCommands$1.init).description("Create a starter Caplets config file.").option("--project", "create the project Caplets config").option("-g, --global", "create the user Caplets config").option("--remote", "create the remote Caplets config").option("--force", "overwrite an existing config file").action(async (options) => {
9793
11790
  const target = parseMutationTarget(options);
9794
11791
  if (target === "remote") {
9795
11792
  writeOut(`Created remote Caplets config at ${(await requireRemoteClientForTarget(io).request("init", { force: Boolean(options.force) })).path}\n`);
@@ -9801,7 +11798,7 @@ function createProgram(io = {}) {
9801
11798
  });
9802
11799
  writeOut(`Created ${localMutationTargetLabel(target, io)}Caplets config at ${path}\n`);
9803
11800
  });
9804
- program.command(cliCommands.setup).description("Install or configure an agent integration for Caplets.").argument("[integration]", "integration: codex, claude-code, opencode, pi, or mcp-client").option("--remote", "configure for a remote Caplets server").option("--server-url <url>", "remote Caplets service base URL").option("--output <path>", "config path to write for generic MCP setup").option("--dry-run", "print actions without running commands or writing files").option("--format <format>", "output format: plain or json", parseSetupFormat).action(async (integration, options) => {
11801
+ program.command(cliCommands$1.setup).description("Install or configure an agent integration for Caplets.").argument("[integration]", "integration: codex, claude-code, opencode, pi, or mcp-client").option("--remote", "configure for a remote Caplets server").option("--server-url <url>", "remote Caplets service base URL").option("--output <path>", "config path to write for generic MCP setup").option("--dry-run", "print actions without running commands or writing files").option("--yes", "approve Caplet setup commands for the exact current content hash").option("--target <target>", "Caplet setup target: local, remote, or cloud", parseSetupTarget).option("--format <format>", "output format: plain or json", parseSetupFormat).action(async (integration, options) => {
9805
11802
  if (!integration) {
9806
11803
  writeOut(formatSetupMenu());
9807
11804
  return;
@@ -9813,7 +11810,14 @@ function createProgram(io = {}) {
9813
11810
  if (io.runSetupCommand) setupOptions.runCommand = io.runSetupCommand;
9814
11811
  writeOut(await runSetup(integration, setupOptions));
9815
11812
  });
9816
- program.command(cliCommands.list).description("List configured Caplets.").option("--all", "include disabled Caplets").option("--json", "print JSON output").option("--format <format>", "output format: plain, markdown, md, or json", parseOutputFormat).action(async (options) => {
11813
+ program.command(cliCommands$1.doctor).description("Diagnose Caplets local, remote, and project-sync configuration.").option("--json", "print JSON output").action(async (options) => {
11814
+ if (options.json) {
11815
+ writeOut(`${JSON.stringify(await doctorJsonReport({ env }), null, 2)}\n`);
11816
+ return;
11817
+ }
11818
+ writeOut(await formatDoctorReport({ env }));
11819
+ });
11820
+ program.command(cliCommands$1.list).description("List configured Caplets.").option("--all", "include disabled Caplets").option("--json", "print JSON output").option("--format <format>", "output format: plain, markdown, md, or json", parseOutputFormat).action(async (options) => {
9817
11821
  const includeDisabled = Boolean(options.all);
9818
11822
  const remote = remoteClientForCli(io);
9819
11823
  if (remote) {
@@ -9835,7 +11839,7 @@ function createProgram(io = {}) {
9835
11839
  }
9836
11840
  writeOut(formatCapletList(rows, options.format ?? "plain"));
9837
11841
  });
9838
- program.command(cliCommands.install).description("Install Caplets from a repo's caplets directory.").argument("<repo>", "local repo path, Git URL, or GitHub owner/repo").argument("[caplets...]", "optional Caplet IDs to install").option("--project", "install to the project Caplets root").option("-g, --global", "install to the user Caplets root").option("--remote", "install through remote control").option("--force", "overwrite installed Caplets").action(async (repo, capletIds, options) => {
11842
+ program.command(cliCommands$1.install).description("Install Caplets from a repo's caplets directory.").argument("<repo>", "local repo path, Git URL, or GitHub owner/repo").argument("[caplets...]", "optional Caplet IDs to install").option("--project", "install to the project Caplets root").option("-g, --global", "install to the user Caplets root").option("--remote", "install through remote control").option("--force", "overwrite installed Caplets").action(async (repo, capletIds, options) => {
9839
11843
  const target = parseMutationTarget(options);
9840
11844
  if (target === "remote") {
9841
11845
  const result = await requireRemoteClientForTarget(io).request("install", {
@@ -9853,7 +11857,7 @@ function createProgram(io = {}) {
9853
11857
  });
9854
11858
  for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to ${localMutationTargetLabel(target, io)}${caplet.destination}\n`);
9855
11859
  });
9856
- const add = program.command(cliCommands.add).description("Add generated Caplet files.");
11860
+ const add = program.command(cliCommands$1.add).description("Add generated Caplet files.");
9857
11861
  add.command("cli").description("Add a CLI tools Caplet.").argument("<id>", "Caplet ID/display seed").option("--repo <path>", "repository path to inspect").option("--include <items>", "comma-separated generators to include: git,gh,package").option("--command <name>", "single CLI command template to generate").option("--project", "write to the project Caplets root").option("-g, --global", "write to the user Caplets root").option("--remote", "add through remote control").option("--print", "print generated Caplet text without writing a file").option("--output <path>", "output path").option("--force", "overwrite an existing destination file").action(async (id, options) => {
9858
11862
  const target = parseMutationTarget(options);
9859
11863
  if (target === "remote") {
@@ -9938,7 +11942,7 @@ function createProgram(io = {}) {
9938
11942
  });
9939
11943
  writeAddResult(writeOut, `${localMutationTargetLabel(target, io)}HTTP`, result);
9940
11944
  });
9941
- program.command(cliCommands.inspect).description("Print a configured Caplet card.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
11945
+ program.command(cliCommands$1.inspect).description("Print a configured Caplet card.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
9942
11946
  await executeOperation(caplet, { operation: "inspect" }, {
9943
11947
  writeOut,
9944
11948
  writeErr,
@@ -9949,7 +11953,7 @@ function createProgram(io = {}) {
9949
11953
  format: options.format
9950
11954
  });
9951
11955
  });
9952
- program.command(cliCommands.checkBackend).description("Check backend availability for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
11956
+ program.command(cliCommands$1.checkBackend).description("Check backend availability for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
9953
11957
  await executeOperation(caplet, { operation: "check_backend" }, {
9954
11958
  writeOut,
9955
11959
  writeErr,
@@ -9960,7 +11964,7 @@ function createProgram(io = {}) {
9960
11964
  format: options.format
9961
11965
  });
9962
11966
  });
9963
- program.command(cliCommands.listTools).description("List downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
11967
+ program.command(cliCommands$1.listTools).description("List downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
9964
11968
  await executeOperation(caplet, { operation: "list_tools" }, {
9965
11969
  writeOut,
9966
11970
  writeErr,
@@ -9971,7 +11975,7 @@ function createProgram(io = {}) {
9971
11975
  format: options.format
9972
11976
  });
9973
11977
  });
9974
- program.command(cliCommands.searchTools).description("Search downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").argument("<query>", "search query").option("--limit <n>", "maximum number of tools to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => {
11978
+ program.command(cliCommands$1.searchTools).description("Search downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").argument("<query>", "search query").option("--limit <n>", "maximum number of tools to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => {
9975
11979
  await executeOperation(caplet, options.limit === void 0 ? {
9976
11980
  operation: "search_tools",
9977
11981
  query
@@ -9989,7 +11993,7 @@ function createProgram(io = {}) {
9989
11993
  format: options.format
9990
11994
  });
9991
11995
  });
9992
- program.command(cliCommands.getTool).description("Print one downstream tool schema.").argument("<caplet-or-target>", "Caplet ID or qualified <caplet.tool> target").argument("[tool]", "downstream tool name when caplet is provided separately").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, toolArgument, options) => {
11996
+ program.command(cliCommands$1.getTool).description("Print one downstream tool schema.").argument("<caplet-or-target>", "Caplet ID or qualified <caplet.tool> target").argument("[tool]", "downstream tool name when caplet is provided separately").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, toolArgument, options) => {
9993
11997
  const { caplet, tool } = parseQualifiedTarget(capletOrTarget, toolArgument);
9994
11998
  await executeOperation(caplet, {
9995
11999
  operation: "get_tool",
@@ -10004,7 +12008,7 @@ function createProgram(io = {}) {
10004
12008
  format: options.format
10005
12009
  });
10006
12010
  });
10007
- program.command(cliCommands.callTool).description("Call one downstream tool.").argument("<caplet-or-target>", "Caplet ID or qualified <caplet.tool> target").argument("[tool]", "downstream tool name when caplet is provided separately").option("--args <json-object>", "JSON object of downstream tool arguments").option("--field <path>", "project a field from structured output", collect, []).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, toolArgument, options) => {
12011
+ program.command(cliCommands$1.callTool).description("Call one downstream tool.").argument("<caplet-or-target>", "Caplet ID or qualified <caplet.tool> target").argument("[tool]", "downstream tool name when caplet is provided separately").option("--args <json-object>", "JSON object of downstream tool arguments").option("--field <path>", "project a field from structured output", collect, []).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, toolArgument, options) => {
10008
12012
  const { caplet, tool } = parseQualifiedTarget(capletOrTarget, toolArgument);
10009
12013
  await executeOperation(caplet, {
10010
12014
  operation: "call_tool",
@@ -10021,7 +12025,7 @@ function createProgram(io = {}) {
10021
12025
  format: options.format
10022
12026
  });
10023
12027
  });
10024
- program.command(cliCommands.listResources).description("List MCP resources for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of resources to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_resources" } : {
12028
+ program.command(cliCommands$1.listResources).description("List MCP resources for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of resources to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_resources" } : {
10025
12029
  operation: "list_resources",
10026
12030
  limit: options.limit
10027
12031
  }, {
@@ -10033,7 +12037,7 @@ function createProgram(io = {}) {
10033
12037
  remote: remoteClientForCli(io),
10034
12038
  format: options.format
10035
12039
  }));
10036
- program.command(cliCommands.searchResources).description("Search MCP resources and resource templates for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of matches to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
12040
+ program.command(cliCommands$1.searchResources).description("Search MCP resources and resource templates for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of matches to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
10037
12041
  operation: "search_resources",
10038
12042
  query
10039
12043
  } : {
@@ -10049,7 +12053,7 @@ function createProgram(io = {}) {
10049
12053
  remote: remoteClientForCli(io),
10050
12054
  format: options.format
10051
12055
  }));
10052
- program.command(cliCommands.listResourceTemplates).description("List MCP resource templates for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of templates to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_resource_templates" } : {
12056
+ program.command(cliCommands$1.listResourceTemplates).description("List MCP resource templates for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of templates to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_resource_templates" } : {
10053
12057
  operation: "list_resource_templates",
10054
12058
  limit: options.limit
10055
12059
  }, {
@@ -10061,7 +12065,7 @@ function createProgram(io = {}) {
10061
12065
  remote: remoteClientForCli(io),
10062
12066
  format: options.format
10063
12067
  }));
10064
- program.command(cliCommands.readResource).description("Read one MCP resource by URI.").argument("<caplet>").argument("<uri>").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, uri, options) => executeOperation(caplet, {
12068
+ program.command(cliCommands$1.readResource).description("Read one MCP resource by URI.").argument("<caplet>").argument("<uri>").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, uri, options) => executeOperation(caplet, {
10065
12069
  operation: "read_resource",
10066
12070
  uri
10067
12071
  }, {
@@ -10073,7 +12077,7 @@ function createProgram(io = {}) {
10073
12077
  remote: remoteClientForCli(io),
10074
12078
  format: options.format
10075
12079
  }));
10076
- program.command(cliCommands.listPrompts).description("List MCP prompts for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_prompts" } : {
12080
+ program.command(cliCommands$1.listPrompts).description("List MCP prompts for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_prompts" } : {
10077
12081
  operation: "list_prompts",
10078
12082
  limit: options.limit
10079
12083
  }, {
@@ -10085,7 +12089,7 @@ function createProgram(io = {}) {
10085
12089
  remote: remoteClientForCli(io),
10086
12090
  format: options.format
10087
12091
  }));
10088
- program.command(cliCommands.searchPrompts).description("Search MCP prompts for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
12092
+ program.command(cliCommands$1.searchPrompts).description("Search MCP prompts for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
10089
12093
  operation: "search_prompts",
10090
12094
  query
10091
12095
  } : {
@@ -10101,7 +12105,7 @@ function createProgram(io = {}) {
10101
12105
  remote: remoteClientForCli(io),
10102
12106
  format: options.format
10103
12107
  }));
10104
- program.command(cliCommands.getPrompt).description("Get one MCP prompt by name.").argument("<caplet-or-target>", "MCP Caplet ID or qualified <caplet.prompt> target").argument("[prompt]", "prompt name when caplet is provided separately").option("--args <json-object>", "JSON object of prompt arguments").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, promptArgument, options) => {
12108
+ program.command(cliCommands$1.getPrompt).description("Get one MCP prompt by name.").argument("<caplet-or-target>", "MCP Caplet ID or qualified <caplet.prompt> target").argument("[prompt]", "prompt name when caplet is provided separately").option("--args <json-object>", "JSON object of prompt arguments").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, promptArgument, options) => {
10105
12109
  const { caplet, tool: prompt } = parseQualifiedTarget(capletOrTarget, promptArgument);
10106
12110
  await executeOperation(caplet, {
10107
12111
  operation: "get_prompt",
@@ -10117,7 +12121,7 @@ function createProgram(io = {}) {
10117
12121
  format: options.format
10118
12122
  });
10119
12123
  });
10120
- program.command(cliCommands.complete).description("Complete an MCP prompt or resource-template argument.").argument("<caplet>").requiredOption("--argument <name>", "argument name").option("--value <value>", "argument prefix", "").option("--prompt <name>", "prompt name to complete").option("--resource-template <uri-template>", "resource template URI to complete").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, {
12124
+ program.command(cliCommands$1.complete).description("Complete an MCP prompt or resource-template argument.").argument("<caplet>").requiredOption("--argument <name>", "argument name").option("--value <value>", "argument prefix", "").option("--prompt <name>", "prompt name to complete").option("--resource-template <uri-template>", "resource template URI to complete").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, {
10121
12125
  operation: "complete",
10122
12126
  ref: completionRefFromOptions(options),
10123
12127
  argument: {
@@ -10133,7 +12137,7 @@ function createProgram(io = {}) {
10133
12137
  remote: remoteClientForCli(io),
10134
12138
  format: options.format
10135
12139
  }));
10136
- const config = program.command(cliCommands.config).description("Inspect Caplets config locations.");
12140
+ const config = program.command(cliCommands$1.config).description("Inspect Caplets config locations.");
10137
12141
  config.command("path").description("Print the effective user config path.").action(() => {
10138
12142
  writeOut(`${resolveConfigPath(currentConfigPath())}\n`);
10139
12143
  });
@@ -10145,7 +12149,7 @@ function createProgram(io = {}) {
10145
12149
  }
10146
12150
  writeOut(formatConfigPaths(paths, options.format ?? "plain"));
10147
12151
  });
10148
- const auth = program.command(cliCommands.auth).description("Manage OAuth credentials for remote servers.");
12152
+ const auth = program.command(cliCommands$1.auth).description("Manage OAuth credentials for remote servers.");
10149
12153
  auth.command("login").description("Authenticate a configured remote OAuth server.").argument("<server>", "configured server ID").option("--project", "authenticate using the project Caplets config").option("-g, --global", "authenticate using the user Caplets config").option("--remote", "authenticate using the remote server auth store").option("--no-open", "print the authorization URL without opening a browser").action(async (serverId, options) => {
10150
12154
  const target = await resolveAuthTarget(serverId, options, io);
10151
12155
  if (target === "remote") {
@@ -10348,6 +12352,10 @@ function parseSetupFormat(value) {
10348
12352
  if (value === "plain" || value === "json") return value;
10349
12353
  throw new CapletsError("REQUEST_INVALID", "setup format must be plain or json");
10350
12354
  }
12355
+ function parseSetupTarget(value) {
12356
+ if (value === "local" || value === "remote" || value === "cloud") return value;
12357
+ throw new CapletsError("REQUEST_INVALID", "setup target must be local, remote, or cloud");
12358
+ }
10351
12359
  function parseQualifiedTarget(capletOrTarget, toolArgument) {
10352
12360
  if (toolArgument !== void 0) {
10353
12361
  if (capletOrTarget.length === 0 || toolArgument.length === 0) throw new CapletsError("REQUEST_INVALID", "Expected target in the form <caplet> <tool> or <caplet>.<tool>");
@@ -10385,22 +12393,22 @@ function localShadowedCompletionTarget(words, config) {
10385
12393
  const target = words[1];
10386
12394
  if (!command || !target || target.startsWith("-")) return;
10387
12395
  const qualifiedCommands = new Set([
10388
- cliCommands.getTool,
10389
- cliCommands.callTool,
10390
- cliCommands.getPrompt
12396
+ cliCommands$1.getTool,
12397
+ cliCommands$1.callTool,
12398
+ cliCommands$1.getPrompt
10391
12399
  ]);
10392
12400
  const capletCommands = new Set([
10393
- cliCommands.inspect,
10394
- cliCommands.checkBackend,
10395
- cliCommands.listTools,
10396
- cliCommands.searchTools,
10397
- cliCommands.listResources,
10398
- cliCommands.searchResources,
10399
- cliCommands.listResourceTemplates,
10400
- cliCommands.readResource,
10401
- cliCommands.listPrompts,
10402
- cliCommands.searchPrompts,
10403
- cliCommands.complete
12401
+ cliCommands$1.inspect,
12402
+ cliCommands$1.checkBackend,
12403
+ cliCommands$1.listTools,
12404
+ cliCommands$1.searchTools,
12405
+ cliCommands$1.listResources,
12406
+ cliCommands$1.searchResources,
12407
+ cliCommands$1.listResourceTemplates,
12408
+ cliCommands$1.readResource,
12409
+ cliCommands$1.listPrompts,
12410
+ cliCommands$1.searchPrompts,
12411
+ cliCommands$1.complete
10404
12412
  ]);
10405
12413
  const caplet = qualifiedCommands.has(command) ? target.slice(0, target.includes(".") ? target.indexOf(".") : target.length) : capletCommands.has(command) ? target : void 0;
10406
12414
  return caplet && hasEnabledCaplet(config, caplet) ? caplet : void 0;
@@ -10940,4 +12948,729 @@ function writeAddResult(writeOut, label, result) {
10940
12948
  writeOut(result.text);
10941
12949
  }
10942
12950
  //#endregion
10943
- export { CapletsRuntime, ServerRegistry, capabilityDescription, createProgram, generatedToolInputSchema, handleServerTool, hasRenderableStructuredContent, loadConfig, markdownCallToolResultContent, markdownStructuredContent, parseConfig, runCli, serveCaplets, serveHttp, serveResolvedCaplets, serveStdio };
12951
+ //#region src/caplet-source/bundle.ts
12952
+ var BundleCapletSource = class {
12953
+ files;
12954
+ constructor(files) {
12955
+ this.files = /* @__PURE__ */ new Map();
12956
+ for (const file of files) {
12957
+ const path = normalizeCapletSourcePath(file.path);
12958
+ if (!path) throw new CapletsError("CONFIG_INVALID", `Invalid bundle file path ${file.path}`);
12959
+ if (this.files.has(path)) throw new CapletsError("CONFIG_INVALID", `Duplicate bundle file path ${path}`);
12960
+ this.files.set(path, {
12961
+ path,
12962
+ content: file.content
12963
+ });
12964
+ }
12965
+ }
12966
+ async listFiles() {
12967
+ return [...this.files.values()].sort((left, right) => left.path.localeCompare(right.path));
12968
+ }
12969
+ async readFile(path) {
12970
+ const normalized = normalizeCapletSourcePath(path);
12971
+ if (!normalized) return;
12972
+ return this.files.get(normalized);
12973
+ }
12974
+ };
12975
+ //#endregion
12976
+ //#region src/runtime-plan/features.ts
12977
+ function inferRuntimeFeatures(caplet) {
12978
+ const provenance = [];
12979
+ for (const feature of explicitFeatures(caplet)) provenance.push({
12980
+ feature,
12981
+ source: "explicit",
12982
+ matched: "runtime.features"
12983
+ });
12984
+ for (const command of commandRecords(caplet)) {
12985
+ const text = [command.command, ...command.args].join(" ");
12986
+ const dockerMatch = matchDocker(command.command, command.args);
12987
+ if (dockerMatch) provenance.push({
12988
+ feature: "docker",
12989
+ source: command.source,
12990
+ matched: dockerMatch,
12991
+ command: text
12992
+ });
12993
+ const browserMatch = matchBrowser(command.command, command.args);
12994
+ if (browserMatch) provenance.push({
12995
+ feature: "browser",
12996
+ source: command.source,
12997
+ matched: browserMatch,
12998
+ command: text
12999
+ });
13000
+ }
13001
+ return {
13002
+ features: orderedFeatures([...new Set(provenance.map((entry) => entry.feature))]),
13003
+ provenance
13004
+ };
13005
+ }
13006
+ function explicitFeatures(caplet) {
13007
+ const runtime = caplet.runtime;
13008
+ if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) return [];
13009
+ const features = runtime.features;
13010
+ return Array.isArray(features) ? features.filter((feature) => feature === "docker" || feature === "browser") : [];
13011
+ }
13012
+ function commandRecords(caplet) {
13013
+ return [
13014
+ ...setupCommands(caplet.setup, "setup.commands"),
13015
+ ...setupCommands(caplet.setup, "setup.verify", true),
13016
+ ...mcpCommands(caplet),
13017
+ ...cliCommands(caplet)
13018
+ ];
13019
+ }
13020
+ function setupCommands(setup, source, verify = false) {
13021
+ if (!setup || typeof setup !== "object" || Array.isArray(setup)) return [];
13022
+ const values = setup[verify ? "verify" : "commands"];
13023
+ if (!Array.isArray(values)) return [];
13024
+ return values.flatMap((value) => commandRecordFrom(value, source));
13025
+ }
13026
+ function mcpCommands(caplet) {
13027
+ if (caplet.backend !== "mcp" || typeof caplet.command !== "string") return [];
13028
+ return [{
13029
+ source: "mcp.command",
13030
+ command: caplet.command,
13031
+ args: stringArray(caplet.args)
13032
+ }];
13033
+ }
13034
+ function cliCommands(caplet) {
13035
+ if (caplet.backend !== "cli") return [];
13036
+ const records = [];
13037
+ if (typeof caplet.command === "string") records.push({
13038
+ source: "cli.command",
13039
+ command: caplet.command,
13040
+ args: stringArray(caplet.args)
13041
+ });
13042
+ const actions = caplet.actions;
13043
+ if (actions && typeof actions === "object" && !Array.isArray(actions)) for (const action of Object.values(actions)) records.push(...commandRecordFrom(action, "cli.action"));
13044
+ return records;
13045
+ }
13046
+ function commandRecordFrom(value, source) {
13047
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [];
13048
+ const command = value.command;
13049
+ if (typeof command !== "string") return [];
13050
+ return [{
13051
+ source,
13052
+ command,
13053
+ args: stringArray(value.args)
13054
+ }];
13055
+ }
13056
+ function stringArray(value) {
13057
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
13058
+ }
13059
+ function matchDocker(command, args) {
13060
+ const text = [command, ...args].join(" ").toLowerCase();
13061
+ if (text.includes("docker-mcp")) return "docker-mcp";
13062
+ if (/\bdocker(?:-compose)?\b/u.test(text)) return command === "docker" ? command : text;
13063
+ return text.includes("docker mcp") ? "docker mcp" : void 0;
13064
+ }
13065
+ function matchBrowser(command, args) {
13066
+ const text = [command, ...args].join(" ").toLowerCase();
13067
+ if (text.includes("@playwright/mcp")) return "@playwright/mcp";
13068
+ if (text.includes("playwright install")) return "playwright install";
13069
+ if (text.includes("playwright")) return "playwright";
13070
+ if (text.includes("browser-use")) return "browser-use";
13071
+ if (text.includes("puppeteer")) return "puppeteer";
13072
+ if (text.includes("chromium")) return "chromium";
13073
+ return /\bchrome\b/u.test(text) ? "chrome" : void 0;
13074
+ }
13075
+ function orderedFeatures(features) {
13076
+ return ["docker", "browser"].filter((feature) => features.includes(feature));
13077
+ }
13078
+ //#endregion
13079
+ //#region src/runtime-plan/resources.ts
13080
+ const defaults = {
13081
+ small: {
13082
+ class: "small",
13083
+ cpu: 1,
13084
+ memoryMb: 1024,
13085
+ diskMb: 4096
13086
+ },
13087
+ medium: {
13088
+ class: "medium",
13089
+ cpu: 2,
13090
+ memoryMb: 4096,
13091
+ diskMb: 8192
13092
+ },
13093
+ standard: {
13094
+ class: "standard",
13095
+ cpu: 2,
13096
+ memoryMb: 4096,
13097
+ diskMb: 8192
13098
+ },
13099
+ large: {
13100
+ class: "large",
13101
+ cpu: 4,
13102
+ memoryMb: 8192,
13103
+ diskMb: 20480
13104
+ },
13105
+ heavy: {
13106
+ class: "heavy",
13107
+ cpu: 8,
13108
+ memoryMb: 16384,
13109
+ diskMb: 40960
13110
+ }
13111
+ };
13112
+ const rank = {
13113
+ small: 0,
13114
+ standard: 1,
13115
+ medium: 1,
13116
+ large: 2,
13117
+ heavy: 3
13118
+ };
13119
+ function resolveRuntimeResources(inputOrCaplet, features, policy) {
13120
+ const caplet = inputOrCaplet;
13121
+ const input = features === void 0 ? inputOrCaplet : {
13122
+ backend: typeof caplet.backend === "string" ? caplet.backend : void 0,
13123
+ features,
13124
+ explicitClass: explicitResourceClass$1(caplet),
13125
+ setupRequired: Boolean(caplet.setup),
13126
+ policy
13127
+ };
13128
+ const requested = input.explicitClass ?? defaultResourceClass(input);
13129
+ const capped = capClass(requested, input.policy?.maxClass);
13130
+ return {
13131
+ ...defaults[capped] ?? defaults.standard,
13132
+ ...requested !== capped ? { cappedByPolicy: input.policy?.maxClass } : {}
13133
+ };
13134
+ }
13135
+ function defaultResourceClass(input) {
13136
+ const hasDocker = input.features.includes("docker");
13137
+ const hasBrowser = input.features.includes("browser");
13138
+ if (hasDocker && hasBrowser) return "heavy";
13139
+ if (hasDocker || hasBrowser) return "large";
13140
+ if (input.backend === "cli" || input.backend === "mcp" || input.setupRequired) return "medium";
13141
+ return "small";
13142
+ }
13143
+ function explicitResourceClass$1(caplet) {
13144
+ const runtime = caplet.runtime;
13145
+ if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) return void 0;
13146
+ const resources = runtime.resources;
13147
+ if (!resources || typeof resources !== "object" || Array.isArray(resources)) return void 0;
13148
+ const value = resources.class;
13149
+ return typeof value === "string" ? value : void 0;
13150
+ }
13151
+ function capClass(requested, maxClass) {
13152
+ if (!maxClass) return requested;
13153
+ return (rank[requested] ?? 0) > (rank[maxClass] ?? 0) ? maxClass : requested;
13154
+ }
13155
+ //#endregion
13156
+ //#region src/runtime-plan/planner.ts
13157
+ function planCapletRuntimeRoutes(caplets, options = {}) {
13158
+ return caplets.map((caplet) => planSingleCaplet(caplet, options));
13159
+ }
13160
+ function planCapletRuntimeRoute(caplet, options = {}) {
13161
+ return planCapletRuntimeRoutes([caplet], options)[0] ?? planSingleCaplet(caplet, options);
13162
+ }
13163
+ function planSingleCaplet(caplet, options) {
13164
+ const route = classifyCapletRuntimeRoute(caplet);
13165
+ const setupRequired = Boolean(caplet.setup);
13166
+ const projectBindingRequired = projectBindingRequiredFor(caplet);
13167
+ const features = inferRuntimeFeatures(caplet);
13168
+ const runtime = {
13169
+ features: features.features,
13170
+ featureProvenance: features.provenance,
13171
+ resources: resolveRuntimeResources({
13172
+ backend: typeof caplet.backend === "string" ? caplet.backend : void 0,
13173
+ features: features.features,
13174
+ explicitClass: explicitResourceClass(caplet),
13175
+ policy: options.resourcePolicy,
13176
+ setupRequired
13177
+ })
13178
+ };
13179
+ return {
13180
+ id: String(caplet.server ?? ""),
13181
+ backend: typeof caplet.backend === "string" ? caplet.backend : "unknown",
13182
+ route,
13183
+ setupRequired,
13184
+ authRequired: authRequired$1("auth" in caplet ? caplet.auth : void 0),
13185
+ ...route === "process" || route === "project_bound_process" ? { setupTarget: setupTargetFor(options.deployment) } : {},
13186
+ projectBindingRequired,
13187
+ runtime,
13188
+ caplet
13189
+ };
13190
+ }
13191
+ function classifyCapletRuntimeRoute(caplet) {
13192
+ if (projectBindingRequiredFor(caplet)) return "project_bound_process";
13193
+ if (caplet.setup) return "process";
13194
+ if (caplet.backend === "cli") return "process";
13195
+ if (caplet.backend === "mcp") return caplet.transport === "stdio" || Boolean(caplet.command) ? "process" : "worker_safe";
13196
+ if (caplet.backend === "openapi" || caplet.backend === "graphql" || caplet.backend === "http") return "worker_safe";
13197
+ if (caplet.backend === "caplets") return "worker_safe";
13198
+ return "local_only";
13199
+ }
13200
+ function setupTargetFor(deployment) {
13201
+ if (deployment === "local") return "local_host";
13202
+ return deployment === "self_hosted" ? "remote_host" : "hosted_sandbox";
13203
+ }
13204
+ function authRequired$1(auth) {
13205
+ return auth !== null && typeof auth === "object" && "type" in auth && auth.type !== "none";
13206
+ }
13207
+ function projectBindingRequiredFor(caplet) {
13208
+ const projectBinding = caplet.projectBinding;
13209
+ return Boolean(projectBinding) && typeof projectBinding === "object" && !Array.isArray(projectBinding) && projectBinding.required === true;
13210
+ }
13211
+ function explicitResourceClass(caplet) {
13212
+ const runtime = caplet.runtime;
13213
+ if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) return void 0;
13214
+ const resources = runtime.resources;
13215
+ if (!resources || typeof resources !== "object" || Array.isArray(resources)) return void 0;
13216
+ const value = resources.class;
13217
+ return value === "small" || value === "medium" || value === "standard" || value === "large" || value === "heavy" ? value : void 0;
13218
+ }
13219
+ //#endregion
13220
+ //#region src/caplet-source/parse.ts
13221
+ async function parseCapletSource(source) {
13222
+ const files = await source.listFiles();
13223
+ let loaded;
13224
+ try {
13225
+ loaded = loadCapletFilesFromMap({ files });
13226
+ } catch (error) {
13227
+ return {
13228
+ ok: false,
13229
+ resolvedCaplets: [],
13230
+ warnings: [],
13231
+ errors: [{ message: errorMessage(error) }]
13232
+ };
13233
+ }
13234
+ if (!loaded) return {
13235
+ ok: false,
13236
+ resolvedCaplets: [],
13237
+ warnings: [],
13238
+ errors: [{ message: "Caplet source must include at least one CAPLET.md or top-level Markdown Caplet file." }]
13239
+ };
13240
+ let config;
13241
+ try {
13242
+ config = parseConfig$1({
13243
+ version: 1,
13244
+ ...loaded.config
13245
+ });
13246
+ } catch (error) {
13247
+ return {
13248
+ ok: false,
13249
+ resolvedCaplets: [],
13250
+ warnings: [],
13251
+ errors: [{ message: errorMessage(error) }]
13252
+ };
13253
+ }
13254
+ const configCaplets = capletsFromConfig(config);
13255
+ const plansById = new Map(planCapletRuntimeRoutes(configCaplets, { deployment: "hosted" }).map((plan) => [plan.id, plan]));
13256
+ const caplets = configCaplets.map((caplet) => {
13257
+ const plan = plansById.get(caplet.server);
13258
+ return {
13259
+ id: caplet.server,
13260
+ name: caplet.name,
13261
+ description: caplet.description,
13262
+ backend: caplet.backend,
13263
+ sourcePath: loaded.paths[caplet.server] ?? "CAPLET.md",
13264
+ setupRequired: Boolean(caplet.setup),
13265
+ authRequired: authRequired("auth" in caplet ? caplet.auth : void 0),
13266
+ projectBindingRequired: plan?.projectBindingRequired ?? false,
13267
+ runtime: {
13268
+ ...plan?.runtime ?? {
13269
+ features: [],
13270
+ featureProvenance: [],
13271
+ resources: {
13272
+ class: "standard",
13273
+ cpu: 2,
13274
+ memoryMb: 4096,
13275
+ diskMb: 8192
13276
+ }
13277
+ },
13278
+ route: plan?.route ?? "local_only",
13279
+ ...plan?.setupTarget === void 0 ? {} : { setupTarget: plan.setupTarget }
13280
+ },
13281
+ localReferences: localReferencePaths(caplet).map((path) => ({
13282
+ path,
13283
+ exists: false
13284
+ })),
13285
+ config: caplet
13286
+ };
13287
+ });
13288
+ for (const caplet of caplets) for (const reference of caplet.localReferences) reference.exists = Boolean(await source.readFile(reference.path));
13289
+ const errors = caplets.flatMap((caplet) => caplet.localReferences.filter((reference) => !reference.exists).map((reference) => ({
13290
+ path: caplet.sourcePath,
13291
+ message: `Referenced file ${reference.path} was not found.`
13292
+ })));
13293
+ return {
13294
+ ok: errors.length === 0,
13295
+ config,
13296
+ resolvedCaplets: errors.length === 0 ? caplets : [],
13297
+ warnings: [],
13298
+ errors
13299
+ };
13300
+ }
13301
+ function capletsFromConfig(config) {
13302
+ return [
13303
+ ...Object.values(config.mcpServers),
13304
+ ...Object.values(config.openapiEndpoints),
13305
+ ...Object.values(config.graphqlEndpoints),
13306
+ ...Object.values(config.httpApis),
13307
+ ...Object.values(config.cliTools),
13308
+ ...Object.values(config.capletSets)
13309
+ ];
13310
+ }
13311
+ function localReferencePaths(caplet) {
13312
+ if (caplet.backend === "openapi") return filterLocalReferences([caplet.specPath]);
13313
+ if (caplet.backend === "graphql") return filterLocalReferences([caplet.schemaPath, ...Object.values(caplet.operations ?? {}).map((operation) => operation.documentPath)]);
13314
+ if (caplet.backend === "caplets") return filterLocalReferences([caplet.configPath]);
13315
+ return [];
13316
+ }
13317
+ function filterLocalReferences(values) {
13318
+ return values.filter((value) => typeof value === "string" && value.length > 0 && !hasEnvReference(value) && !/^[a-z][a-z0-9+.-]*:/iu.test(value));
13319
+ }
13320
+ function authRequired(auth) {
13321
+ return auth !== null && typeof auth === "object" && "type" in auth && auth.type !== "none";
13322
+ }
13323
+ function hasEnvReference(value) {
13324
+ return /\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$env:[A-Za-z_][A-Za-z0-9_]*/u.test(value);
13325
+ }
13326
+ function errorMessage(error) {
13327
+ return error instanceof Error ? error.message : String(error);
13328
+ }
13329
+ //#endregion
13330
+ //#region src/project-binding/types.ts
13331
+ const PROJECT_BINDING_STATES = [
13332
+ "not_attached",
13333
+ "attaching",
13334
+ "syncing",
13335
+ "ready",
13336
+ "degraded",
13337
+ "blocked",
13338
+ "offline",
13339
+ "cleaning_up",
13340
+ "ended",
13341
+ "expired"
13342
+ ];
13343
+ const PROJECT_BINDING_SYNC_STATES = [
13344
+ "not_started",
13345
+ "pending",
13346
+ "syncing",
13347
+ "idle",
13348
+ "failed"
13349
+ ];
13350
+ //#endregion
13351
+ //#region src/project-binding/routes.ts
13352
+ const PROJECT_BINDINGS_CONTROL_PATH = "/control/project-bindings";
13353
+ const PROJECT_BINDING_CONNECT_PATH = `${PROJECT_BINDINGS_CONTROL_PATH}/connect`;
13354
+ function projectBindingConnectPath() {
13355
+ return PROJECT_BINDING_CONNECT_PATH;
13356
+ }
13357
+ function projectBindingStatusPath(bindingId) {
13358
+ return `${PROJECT_BINDINGS_CONTROL_PATH}/${encodeURIComponent(bindingId)}/status`;
13359
+ }
13360
+ function projectBindingConnectUrl(baseUrl) {
13361
+ return withBasePath(baseUrl, projectBindingConnectPath()).toString();
13362
+ }
13363
+ function projectBindingStatusUrl(baseUrl, bindingId) {
13364
+ return withBasePath(baseUrl, projectBindingStatusPath(bindingId)).toString();
13365
+ }
13366
+ function withBasePath(baseUrl, path) {
13367
+ const url = new URL(baseUrl);
13368
+ url.pathname = `${trimTrailingSlash(url.pathname)}${path}`;
13369
+ url.search = "";
13370
+ url.hash = "";
13371
+ return url;
13372
+ }
13373
+ function trimTrailingSlash(pathname) {
13374
+ if (pathname === "/") return "";
13375
+ return pathname.replace(/\/+$/u, "");
13376
+ }
13377
+ //#endregion
13378
+ //#region src/project-binding/mutagen.ts
13379
+ const readyStatuses = new Set([
13380
+ "watching",
13381
+ "ready",
13382
+ "ok"
13383
+ ]);
13384
+ const syncingStatuses = new Set([
13385
+ "connecting",
13386
+ "halted on root",
13387
+ "reconciling",
13388
+ "scanning",
13389
+ "staging",
13390
+ "syncing",
13391
+ "transitioning",
13392
+ "watching for changes"
13393
+ ]);
13394
+ function planMutagenVersionCommand(mutagenBinary = "mutagen") {
13395
+ return {
13396
+ command: mutagenBinary,
13397
+ args: ["version"]
13398
+ };
13399
+ }
13400
+ function planMutagenSyncCreateCommand(input, mutagenBinary = "mutagen") {
13401
+ return {
13402
+ command: mutagenBinary,
13403
+ args: [
13404
+ "sync",
13405
+ "create",
13406
+ input.localProjectRoot,
13407
+ input.serverProjectRoot,
13408
+ "--name",
13409
+ mutagenSyncName(input.bindingId)
13410
+ ]
13411
+ };
13412
+ }
13413
+ function planMutagenSyncListCommand(mutagenBinary = "mutagen") {
13414
+ return {
13415
+ command: mutagenBinary,
13416
+ args: [
13417
+ "sync",
13418
+ "list",
13419
+ "--template",
13420
+ "json"
13421
+ ]
13422
+ };
13423
+ }
13424
+ function planMutagenSyncTerminateCommand(bindingId, mutagenBinary = "mutagen") {
13425
+ return {
13426
+ command: mutagenBinary,
13427
+ args: [
13428
+ "sync",
13429
+ "terminate",
13430
+ mutagenSyncName(bindingId)
13431
+ ]
13432
+ };
13433
+ }
13434
+ function mutagenSyncName(bindingId) {
13435
+ return `caplets-${bindingId}`;
13436
+ }
13437
+ var ManagedMutagenProjectSync = class {
13438
+ mutagenBinary;
13439
+ #runner;
13440
+ #snapshot;
13441
+ constructor(options = {}) {
13442
+ this.mutagenBinary = options.mutagenBinary ?? "mutagen";
13443
+ this.#runner = options.runner ?? defaultMutagenProcessRunner;
13444
+ this.#snapshot = {
13445
+ state: "idle",
13446
+ publicMessage: "Project sync is idle.",
13447
+ mutagenBinary: this.mutagenBinary
13448
+ };
13449
+ }
13450
+ async start(input) {
13451
+ this.#setState({
13452
+ state: "starting",
13453
+ bindingId: input.bindingId,
13454
+ publicMessage: "Project sync is starting."
13455
+ });
13456
+ const versionResult = await this.#run(planMutagenVersionCommand(this.mutagenBinary));
13457
+ if (versionResult.blocked) return this.snapshot();
13458
+ this.#snapshot.mutagenVersion = parseMutagenVersionOutput(versionResult.result.stdout ?? "").version;
13459
+ if ((await this.#run(planMutagenSyncCreateCommand(input, this.mutagenBinary))).blocked) return this.snapshot();
13460
+ this.#setState({
13461
+ state: "syncing",
13462
+ bindingId: input.bindingId,
13463
+ publicMessage: "Project sync is starting."
13464
+ });
13465
+ return this.snapshot();
13466
+ }
13467
+ async refresh(input) {
13468
+ const listResult = await this.#run(planMutagenSyncListCommand(this.mutagenBinary), input.bindingId);
13469
+ if (listResult.blocked) return this.snapshot();
13470
+ let session;
13471
+ try {
13472
+ session = findMutagenSyncSession(listResult.result.stdout, mutagenSyncName(input.bindingId));
13473
+ } catch {
13474
+ this.#block(input.bindingId, "project_sync_status_unavailable");
13475
+ return this.snapshot();
13476
+ }
13477
+ if (!session) {
13478
+ this.#block(input.bindingId, "project_sync_status_unavailable");
13479
+ return this.snapshot();
13480
+ }
13481
+ const normalizedStatus = session.status.toLocaleLowerCase();
13482
+ if (readyStatuses.has(normalizedStatus)) {
13483
+ this.#setState({
13484
+ state: "ready",
13485
+ bindingId: input.bindingId,
13486
+ publicMessage: "Project sync is ready."
13487
+ });
13488
+ return this.snapshot();
13489
+ }
13490
+ if (syncingStatuses.has(normalizedStatus)) {
13491
+ this.#setState({
13492
+ state: "syncing",
13493
+ bindingId: input.bindingId,
13494
+ publicMessage: "Project sync is catching up."
13495
+ });
13496
+ return this.snapshot();
13497
+ }
13498
+ this.#block(input.bindingId, mapTextToDiagnosticCode(session.status));
13499
+ return this.snapshot();
13500
+ }
13501
+ async stop(input) {
13502
+ if ((await this.#run(planMutagenSyncTerminateCommand(input.bindingId, this.mutagenBinary), input.bindingId)).blocked) return this.snapshot();
13503
+ this.#setState({
13504
+ state: "stopped",
13505
+ bindingId: input.bindingId,
13506
+ publicMessage: "Project sync has stopped."
13507
+ });
13508
+ return this.snapshot();
13509
+ }
13510
+ snapshot() {
13511
+ const snapshot = { ...this.#snapshot };
13512
+ if (this.#snapshot.lastCommand) snapshot.lastCommand = {
13513
+ ...this.#snapshot.lastCommand,
13514
+ args: [...this.#snapshot.lastCommand.args]
13515
+ };
13516
+ return snapshot;
13517
+ }
13518
+ async #run(plan, bindingId = this.#snapshot.bindingId) {
13519
+ try {
13520
+ const result = normalizeProcessResult(await this.#runner(plan.command, [...plan.args]));
13521
+ this.#snapshot.lastCommand = {
13522
+ ...plan,
13523
+ ...result,
13524
+ args: [...plan.args]
13525
+ };
13526
+ if (result.exitCode !== 0) {
13527
+ this.#block(bindingId, "project_sync_process_exit");
13528
+ return { blocked: true };
13529
+ }
13530
+ return {
13531
+ blocked: false,
13532
+ result
13533
+ };
13534
+ } catch (error) {
13535
+ const errorResult = {
13536
+ stdout: "",
13537
+ stderr: error instanceof Error ? error.message : String(error)
13538
+ };
13539
+ const exitCode = errorExitCode(error);
13540
+ if (exitCode !== void 0) errorResult.exitCode = exitCode;
13541
+ this.#snapshot.lastCommand = commandStatus(plan, errorResult);
13542
+ this.#block(bindingId, mapErrorToDiagnosticCode(error));
13543
+ return { blocked: true };
13544
+ }
13545
+ }
13546
+ #block(bindingId, diagnosticCode) {
13547
+ this.#setState({
13548
+ state: "blocked",
13549
+ bindingId,
13550
+ diagnosticCode,
13551
+ publicMessage: "Project sync is blocked."
13552
+ });
13553
+ }
13554
+ #setState(next) {
13555
+ const snapshot = {
13556
+ ...this.#snapshot,
13557
+ state: next.state,
13558
+ publicMessage: next.publicMessage,
13559
+ mutagenBinary: this.mutagenBinary
13560
+ };
13561
+ if (next.bindingId !== void 0) snapshot.bindingId = next.bindingId;
13562
+ if (next.diagnosticCode !== void 0) snapshot.diagnosticCode = next.diagnosticCode;
13563
+ else delete snapshot.diagnosticCode;
13564
+ this.#snapshot = snapshot;
13565
+ }
13566
+ };
13567
+ function mutagenProjectSyncDoctorData(snapshot) {
13568
+ const doctorData = { state: snapshot.state };
13569
+ if (snapshot.diagnosticCode !== void 0) doctorData.diagnosticCode = snapshot.diagnosticCode;
13570
+ if (snapshot.mutagenBinary !== void 0) doctorData.mutagenBinary = snapshot.mutagenBinary;
13571
+ if (snapshot.mutagenVersion !== void 0) doctorData.mutagenVersion = snapshot.mutagenVersion;
13572
+ if (snapshot.lastCommand !== void 0) doctorData.lastCommand = snapshot.lastCommand;
13573
+ return doctorData;
13574
+ }
13575
+ function parseMutagenVersionOutput(output) {
13576
+ return { version: output.match(/Mutagen version\s+([^\s]+)/u)?.[1] ?? "unknown" };
13577
+ }
13578
+ async function defaultMutagenProcessRunner(command, args) {
13579
+ return new Promise((resolve, reject) => {
13580
+ const child = spawn(command, args, { stdio: [
13581
+ "ignore",
13582
+ "pipe",
13583
+ "pipe"
13584
+ ] });
13585
+ let stdout = "";
13586
+ let stderr = "";
13587
+ child.stdout.setEncoding("utf8");
13588
+ child.stderr.setEncoding("utf8");
13589
+ child.stdout.on("data", (chunk) => {
13590
+ stdout += chunk;
13591
+ });
13592
+ child.stderr.on("data", (chunk) => {
13593
+ stderr += chunk;
13594
+ });
13595
+ child.on("error", reject);
13596
+ child.on("close", (exitCode) => {
13597
+ resolve(exitCode === null ? {
13598
+ stdout,
13599
+ stderr
13600
+ } : {
13601
+ stdout,
13602
+ stderr,
13603
+ exitCode
13604
+ });
13605
+ });
13606
+ });
13607
+ }
13608
+ function normalizeProcessResult(result) {
13609
+ return {
13610
+ stdout: result.stdout ?? "",
13611
+ stderr: result.stderr ?? "",
13612
+ exitCode: result.exitCode ?? 0
13613
+ };
13614
+ }
13615
+ function commandStatus(plan, result) {
13616
+ const status = {
13617
+ ...plan,
13618
+ args: [...plan.args],
13619
+ stdout: result.stdout ?? "",
13620
+ stderr: result.stderr ?? ""
13621
+ };
13622
+ if (result.exitCode !== void 0) status.exitCode = result.exitCode;
13623
+ return status;
13624
+ }
13625
+ function mapErrorToDiagnosticCode(error) {
13626
+ const text = errorText(error);
13627
+ if (errorCode(error) === "ENOENT" || /\bnot found\b|enoent|no such file/u.test(text)) return "project_sync_binary_missing";
13628
+ return mapTextToDiagnosticCode(text, errorExitCode(error));
13629
+ }
13630
+ function mapTextToDiagnosticCode(text, exitCode) {
13631
+ const normalized = text.toLocaleLowerCase();
13632
+ if (/auth|credential|permission denied|unauthorized|forbidden/u.test(normalized)) return "project_sync_auth_failed";
13633
+ if (/already exists|conflict|duplicate|in use/u.test(normalized)) return "project_sync_conflict";
13634
+ if (exitCode !== void 0 || /exit|failed|terminated/u.test(normalized)) return "project_sync_process_exit";
13635
+ return "project_sync_status_unavailable";
13636
+ }
13637
+ function errorText(error) {
13638
+ return error instanceof Error ? error.message.toLocaleLowerCase() : String(error).toLocaleLowerCase();
13639
+ }
13640
+ function errorCode(error) {
13641
+ return typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
13642
+ }
13643
+ function errorExitCode(error) {
13644
+ return typeof error === "object" && error !== null && "exitCode" in error ? error.exitCode : void 0;
13645
+ }
13646
+ function findMutagenSyncSession(stdout, name) {
13647
+ const parsed = JSON.parse(stdout);
13648
+ for (const entry of collectCandidateSessions(parsed)) if (entry.name === name) return entry;
13649
+ }
13650
+ function collectCandidateSessions(value) {
13651
+ if (Array.isArray(value)) return value.flatMap(collectCandidateSessions);
13652
+ if (!isRecord(value)) return [];
13653
+ const ownSession = sessionFromRecord(value);
13654
+ const nested = [
13655
+ "synchronizations",
13656
+ "sessions",
13657
+ "syncs"
13658
+ ].flatMap((key) => collectCandidateSessions(value[key]));
13659
+ return ownSession ? [ownSession, ...nested] : nested;
13660
+ }
13661
+ function sessionFromRecord(value) {
13662
+ const name = stringProperty(value, "name") ?? stringProperty(value, "Name");
13663
+ const status = stringProperty(value, "status") ?? stringProperty(value, "Status") ?? stringProperty(value, "sessionStatus") ?? stringProperty(value, "SessionStatus");
13664
+ return name && status ? {
13665
+ name,
13666
+ status
13667
+ } : void 0;
13668
+ }
13669
+ function stringProperty(value, key) {
13670
+ return typeof value[key] === "string" ? value[key] : void 0;
13671
+ }
13672
+ function isRecord(value) {
13673
+ return typeof value === "object" && value !== null;
13674
+ }
13675
+ //#endregion
13676
+ export { BundleCapletSource, CapletsRuntime, CloudAuthClient, CloudAuthStore, DEFAULT_SYNC_LIMITS, FilesystemCapletSource, LocalSetupStore, ManagedMutagenProjectSync, PROJECT_BINDINGS_CONTROL_PATH, PROJECT_BINDING_CONNECT_PATH, PROJECT_BINDING_ERROR_CODES, PROJECT_BINDING_STATES, PROJECT_BINDING_SYNC_STATES, ProjectBindingError, ProjectBindingWorkspaceStore, ServerRegistry, attachProjectOnce, attachProjectSession, buildProjectSyncManifest, capabilityDescription, capletSetupContentHash, classifyCapletRuntimeRoute, cloudAuthPath, createProgram, defaultProjectBindingWebSocketFactory, enforceProjectSyncSizeLimits, generatedToolInputSchema, handleServerTool, hasRenderableStructuredContent, loadConfig, markdownCallToolResultContent, markdownStructuredContent, migrateCredentials, mutagenProjectSyncDoctorData, mutagenSyncName, openBrowserUrl, parseCapletSource, parseConfig, parseMutagenVersionOutput as parseManagedMutagenVersionOutput, planCapletRuntimeRoute, planCapletRuntimeRoutes, planMutagenSyncCreateCommand, planMutagenSyncListCommand, planMutagenSyncTerminateCommand, planMutagenVersionCommand, projectBindingConnectPath, projectBindingConnectUrl, projectBindingError, projectBindingRecovery, projectBindingStatusPath, projectBindingStatusUrl, projectBindingWorkspacePaths, projectBindingWorkspaceRoot, redactedCloudAuthStatus, resolveAttachOptions, runCapletSetup, runCli, runProjectBindingSession, serveCaplets, serveHttp, serveResolvedCaplets, serveStdio, stableJson };