@mycadet/cli 0.12.7 → 0.12.9-dev.202610131452.9acab64

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 (185) hide show
  1. package/README.md +2 -2
  2. package/knip.json +1 -1
  3. package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
  4. package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
  5. package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
  6. package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
  7. package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
  8. package/node_modules/@mycadet/assistant-api/events/show-cloud-login.ts +18 -0
  9. package/node_modules/@mycadet/assistant-api/index.ts +11 -11
  10. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +2 -2
  11. package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
  12. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  13. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  14. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  15. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  16. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  17. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  18. package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
  19. package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
  20. package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
  21. package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
  22. package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  23. package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  24. package/node_modules/@mycadet/environments/src/types.ts +6 -6
  25. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
  26. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
  27. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  28. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  29. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
  30. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +2 -2
  31. package/node_modules/@mycadet/{assistant-api/node_modules/@mycadet/service-contracts/src/platform-credential.ts → local-mode/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
  32. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  33. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  34. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  35. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  36. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  37. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  38. package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
  39. package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
  40. package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
  41. package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
  42. package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
  43. package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
  44. package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
  45. package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
  46. package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
  47. package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
  48. package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
  49. package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
  50. package/node_modules/@mycadet/service-contracts/package.json +2 -2
  51. package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
  52. package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  53. package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  54. package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  55. package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  56. package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  57. package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  58. package/package.json +6 -4
  59. package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
  60. package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
  61. package/src/__tests__/assistant-client-refresh.test.ts +2 -2
  62. package/src/__tests__/backup.test.ts +86 -91
  63. package/src/__tests__/client-token.test.ts +1 -1
  64. package/src/__tests__/client-tui-refresh.test.ts +1 -1
  65. package/src/__tests__/client-web-config.test.ts +1 -2
  66. package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
  67. package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +28 -18
  68. package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
  69. package/src/__tests__/connect-import.test.ts +55 -39
  70. package/src/__tests__/debug.test.ts +38 -38
  71. package/src/__tests__/ensure-binary.test.ts +182 -0
  72. package/src/__tests__/guardian-token.test.ts +149 -56
  73. package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
  74. package/src/__tests__/helpers/guardian-token.ts +22 -0
  75. package/src/__tests__/helpers/http.ts +10 -0
  76. package/src/__tests__/nginx-ingress.test.ts +15 -20
  77. package/src/__tests__/ngrok-spawn.test.ts +49 -0
  78. package/src/__tests__/orphan-detection.test.ts +1 -1
  79. package/src/__tests__/pair.test.ts +149 -85
  80. package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +80 -26
  81. package/src/__tests__/ps-table.test.ts +79 -0
  82. package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
  83. package/src/__tests__/retire.test.ts +58 -199
  84. package/src/__tests__/root-process-lockfile.test.ts +99 -0
  85. package/src/__tests__/sleep.test.ts +19 -19
  86. package/src/__tests__/statefulset.test.ts +8 -8
  87. package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +2 -2
  88. package/src/__tests__/teleport-backup.test.ts +3 -3
  89. package/src/__tests__/teleport.test.ts +180 -211
  90. package/src/__tests__/tunnel.test.ts +490 -270
  91. package/src/__tests__/uninstall.test.ts +150 -9
  92. package/src/__tests__/upgrade-local.test.ts +3 -3
  93. package/src/__tests__/upgrade-replay-env.test.ts +12 -12
  94. package/src/__tests__/wake.test.ts +260 -43
  95. package/src/__tests__/workos-pkce.test.ts +2 -2
  96. package/src/commands/backup.ts +24 -24
  97. package/src/commands/client.ts +76 -78
  98. package/src/commands/completions.test.ts +104 -0
  99. package/src/commands/completions.ts +87 -0
  100. package/src/commands/debug.ts +20 -20
  101. package/src/commands/exec.ts +12 -12
  102. package/src/commands/flags.ts +2 -2
  103. package/src/commands/hatch.ts +27 -24
  104. package/src/commands/login.ts +22 -22
  105. package/src/commands/logs.ts +8 -8
  106. package/src/commands/pair.ts +140 -37
  107. package/src/commands/ps.ts +232 -86
  108. package/src/commands/restart.ts +30 -0
  109. package/src/commands/restore.ts +140 -22
  110. package/src/commands/retire.ts +48 -150
  111. package/src/commands/rollback.ts +20 -26
  112. package/src/commands/setup.ts +2 -2
  113. package/src/commands/sleep.ts +2 -2
  114. package/src/commands/teleport.ts +110 -129
  115. package/src/commands/terminal.ts +158 -18
  116. package/src/commands/tunnel.ts +156 -175
  117. package/src/commands/uninstall.ts +137 -54
  118. package/src/commands/unpair.ts +3 -3
  119. package/src/commands/upgrade.ts +30 -54
  120. package/src/commands/use.ts +1 -1
  121. package/src/commands/wake.ts +166 -50
  122. package/src/index.ts +18 -9
  123. package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
  124. package/src/lib/__tests__/docker.test.ts +168 -20
  125. package/src/lib/__tests__/job-polling.test.ts +2 -2
  126. package/src/lib/__tests__/local-gateway-cli.test.ts +42 -0
  127. package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
  128. package/src/lib/__tests__/runtime-url.test.ts +1 -1
  129. package/src/lib/assistant-api-key-resolution.ts +5 -5
  130. package/src/lib/assistant-client.ts +8 -8
  131. package/src/lib/assistant-config.ts +58 -12
  132. package/src/lib/{platform-client.ts → cloud-client.ts} +105 -99
  133. package/src/lib/cloud-login.ts +23 -24
  134. package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
  135. package/src/lib/cloud-values.ts +3 -0
  136. package/src/lib/color.ts +4 -0
  137. package/src/lib/config-utils.ts +1 -1
  138. package/src/lib/detached-process.ts +1 -1
  139. package/src/lib/docker.ts +300 -111
  140. package/src/lib/ensure-binary.ts +171 -0
  141. package/src/lib/environments/__tests__/paths.test.ts +29 -3
  142. package/src/lib/environments/__tests__/resolve.test.ts +26 -26
  143. package/src/lib/environments/paths.ts +16 -8
  144. package/src/lib/environments/resolve.ts +10 -12
  145. package/src/lib/flag-args.test.ts +1 -1
  146. package/src/lib/flag-args.ts +1 -1
  147. package/src/lib/guardian-token.ts +37 -5
  148. package/src/lib/hatch-local.ts +32 -29
  149. package/src/lib/hatch-next-steps.ts +5 -9
  150. package/src/lib/health-check.ts +6 -6
  151. package/src/lib/ingress-config.ts +8 -35
  152. package/src/lib/job-polling.ts +2 -2
  153. package/src/lib/live-voice/connection.ts +12 -12
  154. package/src/lib/live-voice/tunnel.test.ts +11 -11
  155. package/src/lib/live-voice/tunnel.ts +15 -15
  156. package/src/lib/local-runtime-client.ts +20 -20
  157. package/src/lib/local.ts +46 -34
  158. package/src/lib/nginx-ingress.ts +40 -15
  159. package/src/lib/pair.ts +1 -1
  160. package/src/lib/process.ts +1 -1
  161. package/src/lib/provider-secrets.ts +3 -3
  162. package/src/lib/retire-apple-container.ts +1 -1
  163. package/src/lib/runtime-url.ts +8 -8
  164. package/src/lib/statefulset.ts +2 -2
  165. package/src/lib/step-runner.ts +6 -1
  166. package/src/lib/sync-cloud-assistants.ts +19 -19
  167. package/src/lib/teleport-backup.ts +4 -4
  168. package/src/lib/terminal-client.ts +17 -17
  169. package/src/lib/terminal-session.ts +24 -24
  170. package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
  171. package/src/lib/tunnels/cadet.ts +29 -0
  172. package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +36 -15
  173. package/src/lib/tunnels/gateway-notify.ts +61 -0
  174. package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +350 -89
  175. package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +53 -9
  176. package/src/lib/upgrade-lifecycle.ts +8 -21
  177. package/src/lib/upgrade-preflight.ts +2 -2
  178. package/src/lib/workos-pkce.ts +5 -5
  179. package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
  180. package/node_modules/@mycadet/assistant-api/events/show-platform-login.ts +0 -20
  181. package/src/commands/recover.ts +0 -131
  182. package/src/commands/roadmap.ts +0 -459
  183. package/src/commands/ssh.ts +0 -144
  184. package/src/commands/workflows.ts +0 -305
  185. package/src/lib/managed-cloud.ts +0 -3
package/src/lib/local.ts CHANGED
@@ -74,11 +74,10 @@ import {
74
74
  executableName,
75
75
  isProcessAlive,
76
76
  pathListDelimiter,
77
- readProcessCommandLine,
78
77
  resolveProcessState,
79
- stopProcess,
80
78
  stopProcessByPidFile,
81
79
  } from "./process.js";
80
+ import { stopTunnelProcess } from "./tunnel.js";
82
81
  import { stripVersionPrefix } from "./version-compat.js";
83
82
  import { openLogFile, pipeToLogFile } from "./xdg-log.js";
84
83
 
@@ -699,7 +698,7 @@ function sleep(ms: number): Promise<void> {
699
698
  * A readiness answer proves only that something listens on the port: a foreign
700
699
  * listener holding it answers in the daemon's place, so a daemon aborting over
701
700
  * the collision still reads as "up" or "migrating". Ownership is confirmed
702
- * from the listening PID where the platform can report it. Otherwise the spawn
701
+ * from the listening PID where the cloud can report it. Otherwise the spawn
703
702
  * gets a short settle window, which is long enough because transports bind
704
703
  * before migrations run, so an address collision aborts the daemon early.
705
704
  */
@@ -938,7 +937,7 @@ async function startDaemonWatchFromSource(
938
937
  }
939
938
  writeFileSync(pidFile, String(daemonPid), "utf-8");
940
939
 
941
- console.log(" Assistant started in watch mode (bun --watch)");
940
+ console.log(" Assistant started in watch mode with bun --watch");
942
941
  return trackDaemonSpawn(child);
943
942
  }
944
943
 
@@ -985,6 +984,41 @@ function resolveGatewayDir(resources?: LocalInstanceResources): string {
985
984
  }
986
985
  }
987
986
 
987
+ /**
988
+ * How to invoke a `gateway <subcommand>` CLI process so it resolves the same
989
+ * workspace AND gateway security dir as the locally spawned gateway server:
990
+ * run the gateway package's CLI entry through bun with the spawn state root
991
+ * as cwd and the instance-pinned CADET_WORKSPACE_DIR / GATEWAY_SECURITY_DIR
992
+ * the server was given (the file-backed pairing store must resolve
993
+ * identically in both processes). Callers pass the resolved assistant
994
+ * entry's `resources` (undefined for the default instance, where both
995
+ * processes fall back to the same cwd-relative paths).
996
+ */
997
+ export function gatewayCliInvocation(resources?: LocalInstanceResources): {
998
+ command: string;
999
+ args: string[];
1000
+ cwd: string;
1001
+ env: Record<string, string | undefined>;
1002
+ } {
1003
+ return {
1004
+ command: resolveBunExecutable(),
1005
+ args: ["run", join(resolveGatewayDir(resources), "src/cli/index.ts")],
1006
+ cwd: spawnStateRoot(),
1007
+ env: {
1008
+ ...process.env,
1009
+ ...(resources
1010
+ ? {
1011
+ CADET_WORKSPACE_DIR: join(resources.instanceDir, "workspace"),
1012
+ GATEWAY_SECURITY_DIR: join(
1013
+ resources.instanceDir,
1014
+ "gateway-security",
1015
+ ),
1016
+ }
1017
+ : {}),
1018
+ },
1019
+ };
1020
+ }
1021
+
988
1022
  function resolveCesDir(resources?: LocalInstanceResources): string {
989
1023
  const runtimeCesDir = localRuntimeCesDir(resources);
990
1024
  if (runtimeCesDir) return runtimeCesDir;
@@ -1119,9 +1153,7 @@ export async function startCes(
1119
1153
  // Source tree / bunx: run the CES entry point via bun.
1120
1154
  const cesDir = runtimeCesDir ?? resolveCesDir(resources);
1121
1155
  const cesEntry = join(cesDir, "src/main.ts");
1122
- const bunArgs = watch
1123
- ? ["--watch", "run", cesEntry]
1124
- : ["run", cesEntry];
1156
+ const bunArgs = watch ? ["--watch", "run", cesEntry] : ["run", cesEntry];
1125
1157
  const cesLogFd = openLogFile("hatch.log");
1126
1158
  ces = spawn(resolveBunExecutable(), bunArgs, {
1127
1159
  cwd: spawnStateRoot(),
@@ -1632,7 +1664,7 @@ export async function startLocalDaemon(
1632
1664
  "ANTHROPIC_API_KEY",
1633
1665
  "APP_VERSION",
1634
1666
  "CADET_ENVIRONMENT",
1635
- "CADET_PLATFORM_URL",
1667
+ "CADET_CLOUD_URL",
1636
1668
  "QDRANT_HTTP_PORT",
1637
1669
  "QDRANT_URL",
1638
1670
  "RUNTIME_HTTP_PORT",
@@ -1642,7 +1674,7 @@ export async function startLocalDaemon(
1642
1674
  "CADET_DEBUG",
1643
1675
  "CADET_DEV",
1644
1676
  "CADET_DESKTOP_APP",
1645
- "CADET_DISABLE_PLATFORM",
1677
+ "CADET_DISABLE_CLOUD",
1646
1678
  "CADET_MIGRATION_EXPORT_ALLOWED_HOSTS",
1647
1679
  "CADET_MIGRATION_IMPORT_ALLOWED_HOSTS",
1648
1680
  "CADET_WORKSPACE_DIR",
@@ -1931,16 +1963,6 @@ export async function startGateway(
1931
1963
  return gatewayUrl;
1932
1964
  }
1933
1965
 
1934
- /** Check whether a PID belongs to an ngrok process via its command line. */
1935
- function isNgrokProcess(pid: number): boolean {
1936
- try {
1937
- const output = readProcessCommandLine(pid);
1938
- return /ngrok/i.test(output);
1939
- } catch {
1940
- return false;
1941
- }
1942
- }
1943
-
1944
1966
  /**
1945
1967
  * Stop any locally-running daemon and gateway processes
1946
1968
  * and clean up PID files. Called when hatch fails partway through
@@ -1969,22 +1991,12 @@ export async function stopLocalProcesses(
1969
1991
  const cesPidFile = join(cadetDir, "ces.pid");
1970
1992
  await stopProcessByPidFile(cesPidFile, "credential-executor");
1971
1993
 
1972
- // Kill ngrok directly by PID rather than using stopProcessByPidFile, because
1973
- // isCadetProcess() won't match the ngrok binary — resulting in a no-op that
1974
- // leaves ngrok running. Verify the PID still belongs to ngrok before killing
1975
- // to avoid hitting an unrelated process if the OS has reused the PID.
1976
- const ngrokPidFile = join(cadetDir, "ngrok.pid");
1977
- if (existsSync(ngrokPidFile)) {
1978
- try {
1979
- const pid = parseInt(readFileSync(ngrokPidFile, "utf-8").trim(), 10);
1980
- if (!isNaN(pid) && isNgrokProcess(pid)) {
1981
- await stopProcess(pid, "ngrok");
1982
- }
1983
- unlinkSync(ngrokPidFile);
1984
- } catch {}
1985
- }
1994
+ // Stop the tunnel agent this CLI spawned (root lockfile `tunnel` entry),
1995
+ // verifying the pid still belongs to the provider's binary before signaling
1996
+ // it — PID reuse must never send an unrelated process a signal.
1997
+ await stopTunnelProcess();
1986
1998
 
1987
1999
  // Stop the nginx ingress if one is fronting this gateway (it guards against
1988
- // PID reuse itself, mirroring the ngrok handling above).
2000
+ // PID reuse itself).
1989
2001
  await stopIngressNginx(join(cadetDir, "workspace"));
1990
2002
  }
@@ -19,10 +19,13 @@ import { join } from "node:path";
19
19
  import { cloudAssistantHubUrl } from "@mycadet/environments";
20
20
 
21
21
  import {
22
+ clearRootProcess,
22
23
  getAssistantDisplayName,
23
24
  lookupAssistantByIdentifier,
25
+ stampRootProcess,
24
26
  type AssistantEntry,
25
27
  } from "./assistant-config.js";
28
+ import type { RemoteHost } from "./constants.js";
26
29
  import { getCurrentEnvironment } from "./environments/resolve.js";
27
30
  import { waitForDaemonReady } from "./http-client.js";
28
31
  import {
@@ -159,6 +162,12 @@ export function cloudWebHubUrl(env: string | undefined): string {
159
162
  return cloudAssistantHubUrl(env);
160
163
  }
161
164
 
165
+ // The edge serves remote browsers, so the bundle's remote-gateway machinery
166
+ // must always run: it is reached through this self-hosted edge however the
167
+ // cadet itself is hosted. "cloud" is never stamped (the cloud serves its
168
+ // own bundles).
169
+ const EDGE_SERVED_REMOTE: RemoteHost = "custom";
170
+
162
171
  function remoteWebIngressConfig(
163
172
  opts: Pick<
164
173
  RemoteWebIngressOptions,
@@ -166,10 +175,8 @@ function remoteWebIngressConfig(
166
175
  >,
167
176
  ): Record<string, unknown> {
168
177
  return {
169
- mode: "remote-gateway",
178
+ remote: EDGE_SERVED_REMOTE,
170
179
  apiBaseUrl: "/v1",
171
- platformDisabled: true,
172
- disablePlatform: true,
173
180
  ...(opts.assistantName ? { assistantName: opts.assistantName } : {}),
174
181
  ...(opts.assistantId ? { assistantId: opts.assistantId } : {}),
175
182
  ...(opts.hubUrl ? { hubUrl: opts.hubUrl } : {}),
@@ -182,7 +189,7 @@ function remoteWebIngressConfig(
182
189
  * fingerprint matches, so this must change whenever the generated index or
183
190
  * nginx template does.
184
191
  */
185
- export const EDGE_TEMPLATE_VERSION = 8;
192
+ export const EDGE_TEMPLATE_VERSION = 10;
186
193
 
187
194
  /**
188
195
  * Stable fingerprint of the SPA config injected into the served index and
@@ -353,7 +360,7 @@ http {
353
360
  # remote client cannot smuggle one. Every caller reaches this loopback-only
354
361
  # listener through the TLS-terminating front (tunnel agent), so the raw peer
355
362
  # is always 127.0.0.1; the front records the real client as the RIGHTMOST
356
- # X-Forwarded-For entry (ngrok/cloudflared append, tailscale serve sets it),
363
+ # X-Forwarded-For entry (tunnel providers append one; tailscale serve sets it),
357
364
  # which the remote client cannot control. Fall back to the raw peer when the
358
365
  # front sets no X-Forwarded-For.
359
366
  map $http_x_forwarded_for $cadet_edge_client_ip {
@@ -487,7 +494,8 @@ export function getNginxVersion(): string | null {
487
494
  * process.ts helpers: stopProcessByPidFile's isCadetProcess() guard only
488
495
  * matches command lines containing a cadet path, which fails for a custom
489
496
  * CADET_WORKSPACE_DIR and would silently leave nginx running (the same
490
- * reason local.ts kills ngrok directly). This module is also imported by
497
+ * reason local.ts stops the tunnel agent directly). This module is also
498
+ * imported by
491
499
  * sleep/retire, whose tests mock.module() process.js process-globally —
492
500
  * depending on it here would couple this lib's behavior to those mocks.
493
501
  */
@@ -618,13 +626,15 @@ function clearIngressState(workspaceDir: string): void {
618
626
  function clearStoppedIngress(workspaceDir: string, pidPath: string): void {
619
627
  clearIngressState(workspaceDir);
620
628
  rmSync(pidPath, { force: true });
629
+ // The edge is gone; drop the root lockfile's process entry with it.
630
+ clearRootProcess("app");
621
631
  }
622
632
 
623
633
  /**
624
- * Write the nginx config and spawn nginx detached (same idiom as the ngrok
625
- * spawn in ngrok.ts: stdout/stderr to a log file, fd closed after spawn,
626
- * caller unrefs). nginx runs with `daemon off` so the spawned process is the
627
- * master; it writes its pid to nginx.pid under the prefix dir.
634
+ * Write the nginx config and spawn nginx detached (stdout/stderr to a log
635
+ * file, fd closed after spawn, caller unrefs). nginx runs with `daemon off`
636
+ * so the spawned process is the master; it writes its pid to nginx.pid under
637
+ * the prefix dir.
628
638
  */
629
639
  export function startIngressNginx(opts: {
630
640
  workspaceDir: string;
@@ -1028,8 +1038,22 @@ export async function stopContainerTunnelEdge(
1028
1038
  return stopIngressNginx(workspaceDir);
1029
1039
  }
1030
1040
 
1031
- /** Resolved edge a tunnel (or bring-your-own HTTPS front) should target. */
1032
- export interface TunnelEdge {
1041
+ /** Record the running edge's pid in the root lockfile for `cadet ps`. */
1042
+ function stampAppProcessEntry(
1043
+ workspaceDir: string,
1044
+ assistantId?: string,
1045
+ ): void {
1046
+ const pid = getIngressPid(workspaceDir);
1047
+ if (pid !== null) {
1048
+ stampRootProcess("app", {
1049
+ pid,
1050
+ startedAt: new Date().toISOString(),
1051
+ assistantId,
1052
+ });
1053
+ }
1054
+ }
1055
+
1056
+ /** Resolved edge a tunnel (or bring-your-own HTTPS front) should target. */ export interface TunnelEdge {
1033
1057
  /** Loopback listen port the HTTPS front should forward to. */
1034
1058
  port: number;
1035
1059
  /** True when this call started the edge, false when a running one was reused. */
@@ -1081,7 +1105,6 @@ export async function ensureTunnelEdge(opts: {
1081
1105
  const assistantName = opts.assistantId
1082
1106
  ? lockfileAssistantName(opts.assistantId)
1083
1107
  : undefined;
1084
-
1085
1108
  const result = await startRemoteWebIngress({
1086
1109
  workspaceDir: opts.workspaceDir,
1087
1110
  gatewayPort: opts.gatewayPort,
@@ -1093,6 +1116,7 @@ export async function ensureTunnelEdge(opts: {
1093
1116
 
1094
1117
  switch (result.status) {
1095
1118
  case "started":
1119
+ stampAppProcessEntry(opts.workspaceDir, opts.assistantId);
1096
1120
  return {
1097
1121
  port: result.listenPort,
1098
1122
  started: true,
@@ -1126,6 +1150,7 @@ export async function ensureTunnelEdge(opts: {
1126
1150
  stuckEdgeRecoveryHint(opts.workspaceDir),
1127
1151
  );
1128
1152
  }
1153
+ stampAppProcessEntry(opts.workspaceDir, opts.assistantId);
1129
1154
  return {
1130
1155
  port: result.listenPort,
1131
1156
  started: false,
@@ -1135,13 +1160,13 @@ export async function ensureTunnelEdge(opts: {
1135
1160
  case "nginx-missing":
1136
1161
  throw new Error(
1137
1162
  "nginx is not installed, so the tunnel edge cannot start. " +
1138
- "Install it (macOS: `brew install nginx`, Linux: `sudo apt install nginx`) " +
1163
+ "See https://nginx.org/en/docs/install.html for how to install, " +
1139
1164
  "or point NGINX_BIN at an existing binary.",
1140
1165
  );
1141
1166
  case "web-dist-missing":
1142
1167
  throw new Error(
1143
1168
  "Unable to locate built web assets for the remote web edge. " +
1144
- "Build the SPA (`cd clients/web && VITE_PLATFORM_MODE=false bun run build`) " +
1169
+ "Build the SPA with `cd clients/web && VITE_CLOUD_MODE=false bun run build`, " +
1145
1170
  "or install @mycadet/web so its packaged dist directory is available.",
1146
1171
  );
1147
1172
  case "unreachable":
package/src/lib/pair.ts CHANGED
@@ -50,6 +50,6 @@ export function formatWebApproveFailure(
50
50
  return [
51
51
  `${rejection} ${gatewayUrl} (assistant "${assistantReference}", environment "${envName}").`,
52
52
  `Codes are minted by the gateway that serves the pair page, expire after ${CODE_TTL_MINUTES} minutes, and are single-use.`,
53
- "If the pair page came from a different assistant or environment (e.g. the desktop app's), re-run with that environment's CADET_ENVIRONMENT and that assistant's name.",
53
+ "If the pair page came from a different assistant or environment, such as the desktop app's, re-run with that environment's CADET_ENVIRONMENT and that assistant's name.",
54
54
  ].join("\n");
55
55
  }
@@ -76,7 +76,7 @@ export function windowsCommandLineLookupArgs(pid: number): string[] {
76
76
  ];
77
77
  }
78
78
 
79
- /** Read a process command line using the platform's process inspection tool. */
79
+ /** Read a process command line using the cloud's process inspection tool. */
80
80
  export function readProcessCommandLine(
81
81
  pid: number,
82
82
  hostPlatform: NodeJS.Platform = platform(),
@@ -86,7 +86,7 @@ const PROVIDER_LABELS: Record<LlmProviderId, string> = {
86
86
  typesafe: "TypeSafe",
87
87
  };
88
88
 
89
- // Ranked for the hatch prompt: Cadet Cloud first (the managed connection —
89
+ // Ranked for the hatch prompt: Cadet Cloud first (the cloud connection —
90
90
  // no key to collect), then the hosted-inference gateways, then the BYO-key
91
91
  // incumbents, then the rest, with the endpoint-supplied catalogs last.
92
92
  export const HATCH_PROVIDER_CHOICES: readonly LlmProviderId[] = [
@@ -681,11 +681,11 @@ export async function ensureProviderApiKey(
681
681
 
682
682
  if (!envVarName) {
683
683
  // Managed-connection provider (e.g. Cadet Cloud): authenticated through
684
- // the platform, not a user-supplied API key. Checked before the
684
+ // the cloud, not a user-supplied API key. Checked before the
685
685
  // supported-provider guard so the keyless flow stays reachable.
686
686
  return {
687
687
  status: "skipped",
688
- message: `${providerName} authenticates through the managed connection; no API key needed.`,
688
+ message: `${providerName} authenticates through the cloud connection; no API key needed.`,
689
689
  };
690
690
  }
691
691
 
@@ -7,7 +7,7 @@ import type { AssistantEntry } from "./assistant-config.js";
7
7
  * Retire an Apple Container assistant by sending a retire command to the
8
8
  * macOS app via the management socket. The app handles the full lifecycle:
9
9
  * stop the pod, archive the instance directory, remove the guardian token,
10
- * deregister from the platform, and remove the lockfile entry.
10
+ * deregister from the cloud, and remove the lockfile entry.
11
11
  */
12
12
  export async function retireAppleContainer(
13
13
  name: string,
@@ -1,4 +1,4 @@
1
- import { isManagedCloud } from "./managed-cloud.js";
1
+ import { isCloudHome } from "./cloud-values.js";
2
2
  import { hostname } from "node:os";
3
3
 
4
4
  import { getLocalLanIPv4 } from "./local";
@@ -11,12 +11,12 @@ import type { AssistantEntry } from "./assistant-config.js";
11
11
  * - For local/docker assistants, `runtimeUrl` is the loopback gateway and
12
12
  * the runtime serves `/v1/migrations/<subpath>` directly. The CLI hits
13
13
  * that path with guardian-token bearer auth.
14
- * - For platform-managed (cloud="cadet") assistants, `runtimeUrl` is the
15
- * platform host (e.g. `https://cloud.mycadet.ai`). The platform's
14
+ * - For cloud-managed (cloud="cadet") assistants, `runtimeUrl` is the
15
+ * platform host (e.g. `https://cloud.mycadet.ai`). The cloud's
16
16
  * `MigrationViewSet` does NOT expose `export-to-gcs` or arbitrary runtime
17
17
  * migration paths under `/v1/migrations/...`. The wildcard runtime proxy
18
18
  * at `/v1/assistants/<id>/<path:rest>` is what forwards arbitrary runtime
19
- * paths to the managed runtime — vembda's unified proxy bootstraps the
19
+ * paths to the cloud runtime — vembda's unified proxy bootstraps the
20
20
  * guardian token internally for the runtime call. From the CLI side it's
21
21
  * user-session auth.
22
22
  *
@@ -27,7 +27,7 @@ export function resolveRuntimeMigrationUrl(
27
27
  entry: Pick<AssistantEntry, "cloud" | "runtimeUrl" | "assistantId">,
28
28
  subpath: string,
29
29
  ): string {
30
- if (isManagedCloud(entry.cloud)) {
30
+ if (isCloudHome(entry.cloud)) {
31
31
  return `${entry.runtimeUrl}/v1/assistants/${entry.assistantId}/migrations/${subpath}`;
32
32
  }
33
33
  return `${entry.runtimeUrl}/v1/migrations/${subpath}`;
@@ -39,9 +39,9 @@ export function resolveRuntimeMigrationUrl(
39
39
  *
40
40
  * - For local/docker assistants, `runtimeUrl` is the loopback gateway and
41
41
  * the runtime serves `/v1/<subpath>` directly.
42
- * - For platform-managed (cloud="cadet") assistants the path is rewritten
42
+ * - For cloud-managed (cloud="cadet") assistants the path is rewritten
43
43
  * to the wildcard runtime proxy:
44
- * `{platformUrl}/v1/assistants/<assistantId>/<subpath>`.
44
+ * `{cloudUrl}/v1/assistants/<assistantId>/<subpath>`.
45
45
  *
46
46
  * The `subpath` is appended verbatim (e.g. `"identity"`).
47
47
  */
@@ -49,7 +49,7 @@ export function resolveRuntimeUrl(
49
49
  entry: Pick<AssistantEntry, "cloud" | "runtimeUrl" | "assistantId">,
50
50
  subpath: string,
51
51
  ): string {
52
- if (isManagedCloud(entry.cloud)) {
52
+ if (isCloudHome(entry.cloud)) {
53
53
  return `${entry.runtimeUrl}/v1/assistants/${entry.assistantId}/${subpath}`;
54
54
  }
55
55
  return `${entry.runtimeUrl}/v1/${subpath}`;
@@ -199,7 +199,7 @@ export const DOCKER_STATEFUL_SET_SPEC: DockerStatefulSetSpec = {
199
199
  (v): HostEnv => ({ kind: "host", name: v }),
200
200
  ),
201
201
  { kind: "host", name: "CADET_ENVIRONMENT" },
202
- { kind: "host", name: "CADET_PLATFORM_URL" },
202
+ { kind: "host", name: "CADET_CLOUD_URL" },
203
203
  ],
204
204
  volumeMounts: [
205
205
  { volumeName: "assistant-workspace", mountPath: "/workspace" },
@@ -227,7 +227,7 @@ export const DOCKER_STATEFUL_SET_SPEC: DockerStatefulSetSpec = {
227
227
  { kind: "secret", name: "ACTOR_TOKEN_SIGNING_KEY", secret: "signingKey" },
228
228
  { kind: "secret", name: "GUARDIAN_BOOTSTRAP_SECRET", secret: "bootstrapSecret" },
229
229
  { kind: "host", name: "CADET_ENVIRONMENT" },
230
- { kind: "host", name: "CADET_PLATFORM_URL" },
230
+ { kind: "host", name: "CADET_CLOUD_URL" },
231
231
  { kind: "host", name: "TUNNEL_BASE_URL" },
232
232
  ],
233
233
  volumeMounts: [
@@ -112,11 +112,16 @@ export function execWithStdin(
112
112
  export function execOutput(
113
113
  command: string,
114
114
  args: string[],
115
- options: { cwd?: string; timeoutMs?: number } = {},
115
+ options: {
116
+ cwd?: string;
117
+ timeoutMs?: number;
118
+ env?: Record<string, string | undefined>;
119
+ } = {},
116
120
  ): Promise<string> {
117
121
  return new Promise((resolve, reject) => {
118
122
  const child = spawn(command, args, {
119
123
  cwd: options.cwd,
124
+ env: options.env,
120
125
  stdio: ["pipe", "pipe", "pipe"],
121
126
  windowsHide: true,
122
127
  });
@@ -2,7 +2,7 @@
2
2
  * Sync cloud-managed (platform) assistants into the local lockfile.
3
3
  *
4
4
  * - Adds new platform assistants that aren't in the lockfile yet.
5
- * - Removes lockfile entries whose IDs are no longer returned by the platform
5
+ * - Removes lockfile entries whose IDs are no longer returned by the cloud
6
6
  * (e.g. retired assistants).
7
7
  *
8
8
  * Used by both `cadet login` and `cadet ps` to keep the lockfile fresh.
@@ -10,10 +10,10 @@
10
10
  * **Contract:** callers must verify the user is logged in (i.e. a non-empty
11
11
  * platform token exists) before invoking this helper. The "is there a token?"
12
12
  * decision belongs at the command level so commands can render the right
13
- * "Platform: …" status without ever entering the platform fetch path.
13
+ * "Platform: …" status without ever entering the cloud fetch path.
14
14
  */
15
15
 
16
- import { isManagedCloud } from "./managed-cloud.js";
16
+ import { isCloudHome } from "./cloud-values.js";
17
17
  import {
18
18
  loadAllAssistants,
19
19
  removeAssistantEntry,
@@ -21,9 +21,9 @@ import {
21
21
  } from "./assistant-config.js";
22
22
  import {
23
23
  fetchCurrentUser,
24
- fetchPlatformAssistants,
25
- getPlatformUrl,
26
- } from "./platform-client.js";
24
+ fetchCloudAssistants,
25
+ getCloudUrl,
26
+ } from "./cloud-client.js";
27
27
 
28
28
  export type SyncLogger = (message: string) => void;
29
29
 
@@ -58,12 +58,12 @@ export async function syncCloudAssistants(
58
58
  ): Promise<SyncResult | null> {
59
59
  if (!token) {
60
60
  throw new Error(
61
- "syncCloudAssistants called without a token. Callers must check `readPlatformToken()` first.",
61
+ "syncCloudAssistants called without a token. Callers must check `readCloudToken()` first.",
62
62
  );
63
63
  }
64
64
  const log = options?.log;
65
- const platformUrl = getPlatformUrl();
66
- log?.(`Platform URL: ${platformUrl}`);
65
+ const cloudUrl = getCloudUrl();
66
+ log?.(`Platform URL: ${cloudUrl}`);
67
67
  log?.(`Token found (${token.length} chars, prefix: ${token.slice(0, 6)}…)`);
68
68
 
69
69
  // Fetch user info for the login status line
@@ -83,12 +83,12 @@ export async function syncCloudAssistants(
83
83
  }
84
84
  }
85
85
 
86
- let platformAssistants: { id: string; name: string; status: string }[];
86
+ let cloudAssistants: { id: string; name: string; status: string }[];
87
87
  try {
88
88
  log?.("Fetching Cadet Cloud assistants…");
89
- platformAssistants = await fetchPlatformAssistants(token);
89
+ cloudAssistants = await fetchCloudAssistants(token);
90
90
  log?.(
91
- `Cadet Cloud returned ${platformAssistants.length} assistant(s): ${platformAssistants.map((a) => a.name || a.id).join(", ") || "(none)"}`,
91
+ `Cadet Cloud returned ${cloudAssistants.length} ${cloudAssistants.length === 1 ? "assistant" : "assistants"}: ${cloudAssistants.map((a) => a.name || a.id).join(", ") || "(none)"}`,
92
92
  );
93
93
  } catch (err) {
94
94
  const msg = err instanceof Error ? err.message : String(err);
@@ -101,29 +101,29 @@ export async function syncCloudAssistants(
101
101
  return null;
102
102
  }
103
103
 
104
- if (platformAssistants.length === 0) {
104
+ if (cloudAssistants.length === 0) {
105
105
  log?.(
106
106
  "Cadet Cloud returned 0 assistants — this may mean the API returned a non-ok status (check token validity)",
107
107
  );
108
108
  }
109
109
 
110
- const platformIds = new Set(platformAssistants.map((a) => a.id));
110
+ const platformIds = new Set(cloudAssistants.map((a) => a.id));
111
111
 
112
112
  // Add new platform assistants not yet in the lockfile
113
113
  const existingCloudEntries = loadAllAssistants().filter((a) =>
114
- isManagedCloud(a.cloud),
114
+ isCloudHome(a.cloud),
115
115
  );
116
116
  const existingCloudById = new Map(
117
117
  existingCloudEntries.map((a) => [a.assistantId, a]),
118
118
  );
119
119
  const existingCloudIds = new Set(existingCloudById.keys());
120
120
  log?.(
121
- `Lockfile has ${existingCloudIds.size} cloud assistant(s): ${[...existingCloudIds].join(", ") || "(none)"}`,
121
+ `Lockfile has ${existingCloudIds.size} ${existingCloudIds.size === 1 ? "cloud assistant" : "cloud assistants"}: ${[...existingCloudIds].join(", ") || "(none)"}`,
122
122
  );
123
123
 
124
124
  let added = 0;
125
125
  let updated = 0;
126
- for (const pa of platformAssistants) {
126
+ for (const pa of cloudAssistants) {
127
127
  const existing = existingCloudById.get(pa.id);
128
128
  const assistantName = pa.name.trim();
129
129
  const nameFields = assistantName ? { name: assistantName } : {};
@@ -132,7 +132,7 @@ export async function syncCloudAssistants(
132
132
  saveAssistantEntry({
133
133
  assistantId: pa.id,
134
134
  ...nameFields,
135
- runtimeUrl: getPlatformUrl(),
135
+ runtimeUrl: getCloudUrl(),
136
136
  cloud: "cloud",
137
137
  hatchedAt: new Date().toISOString(),
138
138
  });
@@ -147,7 +147,7 @@ export async function syncCloudAssistants(
147
147
  }
148
148
  }
149
149
 
150
- // Remove stale lockfile entries that the platform no longer knows about
150
+ // Remove stale lockfile entries that the cloud no longer knows about
151
151
  let removed = 0;
152
152
  for (const id of existingCloudIds) {
153
153
  if (!platformIds.has(id)) {
@@ -23,7 +23,7 @@ import {
23
23
 
24
24
  import type { AssistantEntry } from "./assistant-config.js";
25
25
  import { loopbackSafeFetch } from "./loopback-fetch.js";
26
- import { authHeaders, invalidateOrgIdCache } from "./platform-client.js";
26
+ import { authHeaders, invalidateOrgIdCache } from "./cloud-client.js";
27
27
  import { z } from "zod";
28
28
 
29
29
  const ManagedBackupEntrySchema = z.object({
@@ -53,7 +53,7 @@ async function errorSuffix(response: Response): Promise<string> {
53
53
  }
54
54
 
55
55
  /**
56
- * Send a platform backup request with user-session auth, refreshing the
56
+ * Send a cloud backup request with user-session auth, refreshing the
57
57
  * cached organization id once on 401 (the same recovery the runtime identity
58
58
  * probe applies).
59
59
  */
@@ -93,7 +93,7 @@ async function fetchPlatformBackups(
93
93
  }
94
94
 
95
95
  /**
96
- * `created_at` of every restorable PVC snapshot of a platform assistant:
96
+ * `created_at` of every restorable PVC snapshot of a cloud assistant:
97
97
  * `GET /v1/assistants/<id>/backups/`.
98
98
  */
99
99
  export async function listPlatformBackups(
@@ -109,7 +109,7 @@ export interface CreatePlatformBackupOptions {
109
109
  }
110
110
 
111
111
  /**
112
- * Take a PVC snapshot of a platform assistant and wait until it is
112
+ * Take a PVC snapshot of a cloud assistant and wait until it is
113
113
  * restorable: `POST /v1/assistants/<id>/backups/`, then re-list until the
114
114
  * returned `snapshot_name` reports `ready_to_use`. Throws if the POST fails
115
115
  * or the snapshot is not ready within `timeoutMs`.