@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
@@ -21,6 +21,7 @@ import {
21
21
  setActiveAssistant,
22
22
  } from "./assistant-config.js";
23
23
  import type { AssistantEntry } from "./assistant-config.js";
24
+ import { green, yellow } from "./color.js";
24
25
  import { buildHatchConfigValues, writeInitialConfig } from "./config-utils.js";
25
26
  import {
26
27
  generateLocalSigningKey,
@@ -32,7 +33,10 @@ import {
32
33
  } from "./local.js";
33
34
 
34
35
  import { generateInstanceName } from "./random-name.js";
35
- import { leaseGuardianToken } from "./guardian-token.js";
36
+ import {
37
+ GuardianLeasePermanentError,
38
+ leaseGuardianToken,
39
+ } from "./guardian-token.js";
36
40
  import { archiveLogFile, resetLogFile } from "./xdg-log.js";
37
41
  import {
38
42
  consoleLifecycleReporter,
@@ -54,7 +58,7 @@ import {
54
58
  ensureCloudLoginInteractive,
55
59
  registerLocalAssistantWithPlatform,
56
60
  } from "./cloud-login.js";
57
- import { fetchCurrentUser, readPlatformToken } from "./platform-client.js";
61
+ import { fetchCurrentUser, readCloudToken } from "./cloud-client.js";
58
62
  import { withLocalHatchLock } from "./local-hatch-lock.js";
59
63
 
60
64
  /**
@@ -185,8 +189,6 @@ export interface HatchLocalResult {
185
189
  guardianAccessToken?: string;
186
190
  }
187
191
 
188
-
189
-
190
192
  export async function hatchLocal(
191
193
  params: HatchLocalParams = {},
192
194
  ): Promise<HatchLocalResult> {
@@ -199,8 +201,7 @@ export async function hatchLocal(
199
201
  } = params;
200
202
  const reporter = params.reporter ?? consoleLifecycleReporter;
201
203
  const verbose = params.verbose ?? false;
202
- const setupProviderCredentials =
203
- params.setupProviderCredentials !== false;
204
+ const setupProviderCredentials = params.setupProviderCredentials !== false;
204
205
  setStartupLogging(verbose);
205
206
  let provider = setupProviderCredentials
206
207
  ? resolveHatchProvider(configValues)
@@ -226,12 +227,7 @@ export async function hatchLocal(
226
227
 
227
228
  const resources = await allocateLocalResources(instanceName);
228
229
 
229
- const logsDir = join(
230
- resources.instanceDir,
231
- "workspace",
232
- "data",
233
- "logs",
234
- );
230
+ const logsDir = join(resources.instanceDir, "workspace", "data", "logs");
235
231
  archiveLogFile("hatch.log", logsDir);
236
232
  resetLogFile("hatch.log");
237
233
 
@@ -263,7 +259,7 @@ export async function hatchLocal(
263
259
  picked === "cadet" &&
264
260
  !process.env.CADET_DESKTOP_APP &&
265
261
  process.stdin.isTTY &&
266
- !readPlatformToken()
262
+ !readCloudToken()
267
263
  ) {
268
264
  reporter.log("");
269
265
  reporter.log(
@@ -276,9 +272,7 @@ export async function hatchLocal(
276
272
  reporter.warn(
277
273
  `⚠️ Cadet Cloud login failed: ${describeLoginError(error)}`,
278
274
  );
279
- reporter.warn(
280
- " You can retry later with `cadet login`.",
281
- );
275
+ reporter.warn(" You can retry later with `cadet login`.");
282
276
  }
283
277
  }
284
278
  }
@@ -380,17 +374,24 @@ export async function hatchLocal(
380
374
  guardianAccessToken = tokenData.accessToken;
381
375
  break;
382
376
  } catch (err) {
383
- if (attempt < maxLeaseAttempts) {
377
+ if (
378
+ attempt < maxLeaseAttempts &&
379
+ !(err instanceof GuardianLeasePermanentError)
380
+ ) {
384
381
  const delayMs = 2000 * 2 ** (attempt - 1);
385
382
  reporter.error(
386
- `⚠️ Guardian token lease attempt ${attempt}/${maxLeaseAttempts} failed — retrying in ${delayMs / 1000}s: ${err}`,
383
+ yellow(
384
+ `⚠️ Guardian token lease attempt ${attempt}/${maxLeaseAttempts} failed — retrying in ${delayMs / 1000}s: ${err}`,
385
+ ),
387
386
  );
388
387
  await new Promise((r) => setTimeout(r, delayMs));
389
388
  } else {
390
389
  reporter.error(
391
- `⚠️ Guardian token lease failed after ${maxLeaseAttempts} attempts: ${err}\n` +
392
- ` The assistant is running but guardian-token.json was not written.\n` +
393
- ` If the desktop app loses its stored credentials, re-hatch to recover.`,
390
+ yellow(
391
+ `⚠️ Guardian token lease failed${err instanceof GuardianLeasePermanentError ? ", not retrying — a malformed 200 will not fix itself" : ` after ${maxLeaseAttempts} attempts`}: ${err}\n` +
392
+ ` The assistant is running but guardian-token.json was not written.\n` +
393
+ ` Run \`cadet restart ${JSON.stringify(instanceName)} --repair-guardian\` to re-lease a token.`,
394
+ ),
394
395
  );
395
396
  }
396
397
  }
@@ -402,8 +403,10 @@ export async function hatchLocal(
402
403
 
403
404
  if (provider !== undefined && provider !== null && !guardianAccessToken) {
404
405
  reporter.error(
405
- `⚠️ Provider credential setup skipped because the guardian token was not leased.\n` +
406
- ` The assistant is still hatched. Run \`cadet setup --provider ${provider}\` after fixing the connection.`,
406
+ yellow(
407
+ `⚠️ Provider credential setup skipped because the guardian token was not leased.\n` +
408
+ ` The assistant is still hatched. Run \`cadet setup --provider ${provider}\` after fixing the connection.`,
409
+ ),
407
410
  );
408
411
  } else if (provider !== undefined) {
409
412
  await configureHatchProviderApiKey({
@@ -415,13 +418,13 @@ export async function hatchLocal(
415
418
  });
416
419
  }
417
420
 
418
- // Picking Cadet Cloud ends with the assistant registered on the platform
421
+ // Picking Cadet Cloud ends with the assistant registered on the cloud
419
422
  // and its cloud credentials injected — the same post-login flow
420
- // `cadet login` runs — so messages route through the managed connection
423
+ // `cadet login` runs — so messages route through the cloud connection
421
424
  // immediately after hatch. Non-fatal: the assistant is up either way.
422
- if (provider === "cadet" && readPlatformToken()) {
425
+ if (provider === "cadet" && readCloudToken()) {
423
426
  try {
424
- const token = readPlatformToken()!;
427
+ const token = readCloudToken()!;
425
428
  const user = await fetchCurrentUser(token);
426
429
  await registerLocalAssistantWithPlatform(token, user, {
427
430
  bearerToken: guardianAccessToken,
@@ -433,7 +436,7 @@ export async function hatchLocal(
433
436
  }
434
437
 
435
438
  reporter.log("");
436
- reporter.log(`✅ Local assistant hatched!`);
439
+ reporter.log(green(`✅ Local assistant hatched!`, process.stdout));
437
440
  printInstanceDetails(
438
441
  {
439
442
  name: instanceName,
@@ -442,7 +445,7 @@ export async function hatchLocal(
442
445
  },
443
446
  (message) => reporter.log(message),
444
447
  );
445
- logHatchNextSteps((message) => reporter.log(message), verbose);
448
+ logHatchNextSteps((message) => reporter.log(message));
446
449
 
447
450
  const result: HatchLocalResult = {
448
451
  assistantId: instanceName,
@@ -1,12 +1,8 @@
1
- export function logHatchNextSteps(
2
- log: (message: string) => void,
3
- verbose = false,
4
- ): void {
1
+ export function logHatchNextSteps(log: (message: string) => void): void {
5
2
  log("Next steps:");
6
- log(" cadet client");
7
- if (verbose) {
8
- log(' cadet message "hello"');
9
- log(" cadet events");
10
- }
3
+ log(" - Run `cadet client` to interact with your cadet via the terminal");
4
+ log(
5
+ " - Run `cadet tunnel` to make your cadet publicly accessible and interact with it via the browser",
6
+ );
11
7
  log("");
12
8
  }
@@ -19,8 +19,8 @@ export async function checkManagedHealth(
19
19
  runtimeUrl: string,
20
20
  assistantId: string,
21
21
  ): Promise<HealthCheckResult> {
22
- const { readPlatformToken } = await import("./platform-client.js");
23
- const token = readPlatformToken();
22
+ const { readCloudToken } = await import("./cloud-client.js");
23
+ const token = readCloudToken();
24
24
  if (!token) {
25
25
  return {
26
26
  status: "error (auth)",
@@ -30,7 +30,7 @@ export async function checkManagedHealth(
30
30
 
31
31
  let headers: Record<string, string>;
32
32
  try {
33
- const { authHeaders } = await import("./platform-client.js");
33
+ const { authHeaders } = await import("./cloud-client.js");
34
34
  headers = await authHeaders(token, runtimeUrl);
35
35
  } catch (err) {
36
36
  return {
@@ -130,9 +130,9 @@ export async function fetchManagedPs(
130
130
  runtimeUrl: string,
131
131
  assistantId: string,
132
132
  ): Promise<ManagedPsResponse | null> {
133
- const { readPlatformToken, authHeaders } =
134
- await import("./platform-client.js");
135
- const token = readPlatformToken();
133
+ const { readCloudToken, authHeaders } =
134
+ await import("./cloud-client.js");
135
+ const token = readCloudToken();
136
136
  if (!token) return null;
137
137
 
138
138
  let headers: Record<string, string>;
@@ -18,14 +18,13 @@ import { getStateDir } from "./environments/paths.js";
18
18
  import { getCurrentEnvironment } from "./environments/resolve.js";
19
19
 
20
20
  /**
21
- * Shared workspace-config helpers for tunnel providers (ngrok, cloudflare,
22
- * tailscale) and the nginx ingress proxy. Each provider fronts the local edge
23
- * and records the resulting public URL under `ingress.publicBaseUrl` so webhook
24
- * integrations can reach the assistant. The workspace config is the
25
- * gateway-facing contract; when an `assistantId` is supplied, the URL is also
26
- * mirrored onto the lockfile entry (`ingressUrl`) — the CLI-owned contract
27
- * that CLI features (e.g. remote-web pairing defaults) read, per the
28
- * no-`.cadet/`-reads boundary in cli/AGENTS.md.
21
+ * Shared workspace-config helpers for tunnel providers and the nginx ingress
22
+ * proxy. Each provider fronts the local edge and records the resulting public
23
+ * URL under `ingress.publicBaseUrl` so webhook integrations can reach the
24
+ * assistant. The workspace config is the gateway-facing contract; when an
25
+ * `assistantId` is supplied, the URL is also mirrored onto the lockfile entry
26
+ * (`ingressUrl`) — the CLI-owned contract that CLI features (e.g. remote-web
27
+ * pairing defaults) read, per the no-`.cadet/`-reads boundary in cli/AGENTS.md.
29
28
  */
30
29
 
31
30
  function parsePortFromUrl(url: unknown): number | undefined {
@@ -48,7 +47,7 @@ export function isLocalContainerEntry(entry: AssistantEntry): boolean {
48
47
  /**
49
48
  * Derive the gateway port from an entry's recorded URLs, preferring the
50
49
  * loopback `localUrl` over `runtimeUrl`. Undefined when neither carries an
51
- * explicit port (e.g. a platform-hosted https runtime URL).
50
+ * explicit port (e.g. a cloud-hosted https runtime URL).
52
51
  */
53
52
  export function parseGatewayPortFromEntryUrls(
54
53
  entry: AssistantEntry | undefined,
@@ -186,32 +185,6 @@ export function savePairingTunnel(
186
185
  saveRawConfig(workspaceDir, config);
187
186
  }
188
187
 
189
- /** Persist a reserved ngrok domain under `ingress.ngrok.domain`; null clears it. */
190
- export function saveNgrokDomain(
191
- workspaceDir: string,
192
- domain: string | null,
193
- ): void {
194
- const config = loadRawConfig(workspaceDir);
195
- const ingress = (config.ingress ?? {}) as Record<string, unknown>;
196
- if (domain) {
197
- ingress.ngrok = { domain };
198
- } else {
199
- delete ingress.ngrok;
200
- }
201
- config.ingress = ingress;
202
- saveRawConfig(workspaceDir, config);
203
- }
204
-
205
- /** Read the reserved ngrok domain from the workspace config, if saved. */
206
- export function loadNgrokDomain(workspaceDir: string): string | null {
207
- const ingress = loadRawConfig(workspaceDir).ingress as
208
- | Record<string, unknown>
209
- | undefined;
210
- const ngrok = ingress?.ngrok as Record<string, unknown> | undefined;
211
- const domain = ngrok?.domain;
212
- return typeof domain === "string" && domain.trim() ? domain : null;
213
- }
214
-
215
188
  /** Clear the ingress public base URL from the workspace config. */
216
189
  export function clearIngressUrl(
217
190
  workspaceDir: string,
@@ -1,4 +1,4 @@
1
- import type { UnifiedJobStatus } from "./platform-client.js";
1
+ import type { UnifiedJobStatus } from "./cloud-client.js";
2
2
 
3
3
  /**
4
4
  * Terminal status returned by {@link pollJobUntilDone}. Callers decide
@@ -166,7 +166,7 @@ export async function pollJobUntilDone(
166
166
  throw err;
167
167
  }
168
168
  const msg = err instanceof Error ? err.message : String(err);
169
- console.warn(`${options.label} polling failed, retrying... (${msg})`);
169
+ console.warn(`${options.label} polling failed: ${msg}. Retrying...`);
170
170
  if (Date.now() >= deadline) {
171
171
  throw new Error(
172
172
  `Timed out waiting for ${options.label} after ${Math.round(
@@ -25,7 +25,7 @@
25
25
  * passes by construction, and there is nothing new to provision here.
26
26
  */
27
27
 
28
- import { isManagedCloud } from "../managed-cloud.js";
28
+ import { isCloudHome } from "../cloud-values.js";
29
29
  import {
30
30
  formatAssistantLookupError,
31
31
  formatAssistantReference,
@@ -33,7 +33,7 @@ import {
33
33
  resolveTargetAssistant,
34
34
  type AssistantEntry,
35
35
  } from "../assistant-config.js";
36
- import { getPlatformUrl, readPlatformToken } from "../platform-client.js";
36
+ import { getCloudUrl, readCloudToken } from "../cloud-client.js";
37
37
  import {
38
38
  guardianTokenDueForRenewal,
39
39
  loadGuardianToken,
@@ -88,11 +88,11 @@ export async function resolveLiveVoiceConnection(
88
88
  ? resolveNamedAssistant(assistantIdArg)
89
89
  : resolveTargetAssistant();
90
90
 
91
- // Platform-cloud assistants are reached the other way round. Their gateway
91
+ // Cloud assistants are reached the other way round. Their gateway
92
92
  // is behind tunnel, which authenticates nobody and forwards an attestation
93
93
  // the gateway trusts, so the credential is not the guardian JWT below but a
94
- // WS token minted from the platform session the CLI already holds.
95
- if (isManagedCloud(entry.cloud)) {
94
+ // WS token minted from the cloud session the CLI already holds.
95
+ if (isCloudHome(entry.cloud)) {
96
96
  return await resolveCloudConnection(entry);
97
97
  }
98
98
 
@@ -126,7 +126,7 @@ export async function resolveLiveVoiceConnection(
126
126
  }
127
127
 
128
128
  /**
129
- * Resolve a live-voice connection to a platform-hosted assistant.
129
+ * Resolve a live-voice connection to a cloud-hosted assistant.
130
130
  *
131
131
  * The session token is the CLI's own cloud login, not anything specific to
132
132
  * this assistant, so a missing one is a `cadet login` problem rather than an
@@ -139,7 +139,7 @@ export async function resolveLiveVoiceConnection(
139
139
  async function resolveCloudConnection(
140
140
  entry: AssistantEntry,
141
141
  ): Promise<LiveVoiceConnection> {
142
- const sessionToken = readPlatformToken();
142
+ const sessionToken = readCloudToken();
143
143
  if (!sessionToken) {
144
144
  throw new LiveVoiceConnectionError(
145
145
  `${formatAssistantReference(entry)} is a Cadet cloud assistant, which ` +
@@ -147,10 +147,10 @@ async function resolveCloudConnection(
147
147
  );
148
148
  }
149
149
 
150
- // `runtimeUrl` is the platform this entry was hatched against, which is the
151
- // one that can mint for it. `getPlatformUrl()` is the fallback for an entry
150
+ // `runtimeUrl` is the cloud this entry was hatched against, which is the
151
+ // one that can mint for it. `getCloudUrl()` is the fallback for an entry
152
152
  // written before that field was recorded.
153
- const platformUrl = (entry.runtimeUrl || getPlatformUrl()).replace(
153
+ const cloudUrl = (entry.runtimeUrl || getCloudUrl()).replace(
154
154
  /\/+$/,
155
155
  "",
156
156
  );
@@ -158,7 +158,7 @@ async function resolveCloudConnection(
158
158
  let token: string;
159
159
  try {
160
160
  token = await mintTunnelWsToken({
161
- platformUrl,
161
+ cloudUrl,
162
162
  assistantId: entry.assistantId,
163
163
  sessionToken,
164
164
  });
@@ -171,7 +171,7 @@ async function resolveCloudConnection(
171
171
 
172
172
  return {
173
173
  url: buildTunnelLiveVoiceUrl({
174
- platformUrl,
174
+ cloudUrl,
175
175
  assistantId: entry.assistantId,
176
176
  token,
177
177
  }),
@@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test";
2
2
 
3
3
  import {
4
4
  buildTunnelLiveVoiceUrl,
5
- tunnelHostForPlatformUrl,
5
+ tunnelHostForCloudUrl,
6
6
  tunnelWsScheme,
7
7
  } from "./tunnel.js";
8
8
 
@@ -16,13 +16,13 @@ afterEach(() => {
16
16
  }
17
17
  });
18
18
 
19
- describe("tunnelHostForPlatformUrl", () => {
20
- test("maps a platform host onto its environment's tunnel", () => {
19
+ describe("tunnelHostForCloudUrl", () => {
20
+ test("maps a cloud host onto its environment's tunnel", () => {
21
21
  delete process.env.CADET_TUNNEL_HOST;
22
- expect(tunnelHostForPlatformUrl("https://cloud.mycadet.ai")).toBe(
22
+ expect(tunnelHostForCloudUrl("https://cloud.mycadet.ai")).toBe(
23
23
  "tunnel.mycadet.ai",
24
24
  );
25
- expect(tunnelHostForPlatformUrl("https://dev-cloud.mycadet.ai")).toBe(
25
+ expect(tunnelHostForCloudUrl("https://dev-cloud.mycadet.ai")).toBe(
26
26
  "tunnel-dev.mycadet.ai",
27
27
  );
28
28
  });
@@ -31,15 +31,15 @@ describe("tunnelHostForPlatformUrl", () => {
31
31
  delete process.env.CADET_TUNNEL_HOST;
32
32
  // A local platform predicts no tunnel of its own; guessing one would be
33
33
  // worse than dialing the host that certainly exists.
34
- expect(tunnelHostForPlatformUrl("http://localhost:8000")).toBe(
34
+ expect(tunnelHostForCloudUrl("http://localhost:8000")).toBe(
35
35
  "tunnel.mycadet.ai",
36
36
  );
37
- expect(tunnelHostForPlatformUrl("not a url")).toBe("tunnel.mycadet.ai");
37
+ expect(tunnelHostForCloudUrl("not a url")).toBe("tunnel.mycadet.ai");
38
38
  });
39
39
 
40
40
  test("the env override wins, which is how a local vel up tunnel is reached", () => {
41
41
  process.env.CADET_TUNNEL_HOST = "localhost:8501";
42
- expect(tunnelHostForPlatformUrl("https://cloud.mycadet.ai")).toBe(
42
+ expect(tunnelHostForCloudUrl("https://cloud.mycadet.ai")).toBe(
43
43
  "localhost:8501",
44
44
  );
45
45
  });
@@ -58,7 +58,7 @@ describe("buildTunnelLiveVoiceUrl", () => {
58
58
  delete process.env.CADET_TUNNEL_HOST;
59
59
  const url = new URL(
60
60
  buildTunnelLiveVoiceUrl({
61
- platformUrl: "https://cloud.mycadet.ai",
61
+ cloudUrl: "https://cloud.mycadet.ai",
62
62
  assistantId: "01a02514-e452-7019-88d5-30828f42cf17",
63
63
  token: "tok_abc",
64
64
  }),
@@ -76,7 +76,7 @@ describe("buildTunnelLiveVoiceUrl", () => {
76
76
  delete process.env.CADET_TUNNEL_HOST;
77
77
  const url = new URL(
78
78
  buildTunnelLiveVoiceUrl({
79
- platformUrl: "https://cloud.mycadet.ai",
79
+ cloudUrl: "https://cloud.mycadet.ai",
80
80
  assistantId: "a1",
81
81
  token: "a+b/c=&d",
82
82
  }),
@@ -88,7 +88,7 @@ describe("buildTunnelLiveVoiceUrl", () => {
88
88
  process.env.CADET_TUNNEL_HOST = "localhost:8501";
89
89
  const url = new URL(
90
90
  buildTunnelLiveVoiceUrl({
91
- platformUrl: "http://localhost:8000",
91
+ cloudUrl: "http://localhost:8000",
92
92
  assistantId: "a1",
93
93
  token: "t",
94
94
  }),
@@ -2,17 +2,17 @@
2
2
  * The cloud leg of a live-voice session: minting a tunnel WS token and building
3
3
  * the URL it opens.
4
4
  *
5
- * A platform-hosted assistant is not reachable the way a local one is. Its
5
+ * A cloud-hosted assistant is not reachable the way a local one is. Its
6
6
  * gateway sits behind tunnel, tunnel performs no user authentication of its own,
7
7
  * and the gateway upstream accepts only an actor edge JWT or a tunnel
8
8
  * attestation. The CLI holds neither for a cloud instance: what it has is a
9
9
  * platform session token, which is the wrong credential for the socket but the
10
- * right one for the platform.
10
+ * right one for the cloud.
11
11
  *
12
12
  * So the connection is made in two hops, the same two the web voice room makes
13
13
  * (`clients/web/src/domains/chat/voice/live-voice/connection.ts`):
14
14
  *
15
- * 1. POST the platform for a short-lived, org+assistant-scoped WS token.
15
+ * 1. POST the cloud for a short-lived, org+assistant-scoped WS token.
16
16
  * 2. Open `wss://<tunnel>/<assistantId>/v1/live-voice?token=<wsToken>`.
17
17
  *
18
18
  * tunnel consumes the token on the upgrade and injects the authenticated user
@@ -30,9 +30,9 @@
30
30
 
31
31
  import { tunnelHostForPlatformHost } from "@mycadet/service-contracts/ingress";
32
32
 
33
- import { authHeaders } from "../platform-client.js";
33
+ import { authHeaders } from "../cloud-client.js";
34
34
 
35
- /** Production tunnel, when the platform host implies nothing more specific. */
35
+ /** Production tunnel, when the cloud host implies nothing more specific. */
36
36
  const DEFAULT_TUNNEL_HOST = "tunnel.mycadet.ai";
37
37
 
38
38
  /** Give up on the mint rather than hang the session behind a stalled platform. */
@@ -53,20 +53,20 @@ export class TunnelWsTokenError extends Error {
53
53
  *
54
54
  * `CADET_TUNNEL_HOST` wins when set, which is how a local `vel up` stack is
55
55
  * reached: its tunnel is a loopback port that no naming convention predicts.
56
- * Otherwise the deployment convention maps the platform host onto its sibling
56
+ * Otherwise the deployment convention maps the cloud host onto its sibling
57
57
  * (`cloud.mycadet.ai` to `tunnel.mycadet.ai`, `dev-cloud.mycadet.ai` to
58
58
  * `tunnel-dev.mycadet.ai`). A host outside that convention, including localhost,
59
59
  * yields the production default, because guessing a tunnel for it would be
60
60
  * worse than dialing the one host that certainly exists.
61
61
  */
62
- export function tunnelHostForPlatformUrl(platformUrl: string): string {
62
+ export function tunnelHostForCloudUrl(cloudUrl: string): string {
63
63
  const override = process.env.CADET_TUNNEL_HOST?.trim();
64
64
  if (override) {
65
65
  return override;
66
66
  }
67
67
  try {
68
68
  return (
69
- tunnelHostForPlatformHost(new URL(platformUrl).host) ??
69
+ tunnelHostForPlatformHost(new URL(cloudUrl).host) ??
70
70
  DEFAULT_TUNNEL_HOST
71
71
  );
72
72
  } catch {
@@ -106,11 +106,11 @@ export function tunnelWsScheme(host: string): "ws" | "wss" {
106
106
  * credential anywhere tunnel reads except the query string.
107
107
  */
108
108
  export function buildTunnelLiveVoiceUrl(args: {
109
- platformUrl: string;
109
+ cloudUrl: string;
110
110
  assistantId: string;
111
111
  token: string;
112
112
  }): string {
113
- const host = tunnelHostForPlatformUrl(args.platformUrl);
113
+ const host = tunnelHostForCloudUrl(args.cloudUrl);
114
114
  const url = new URL(
115
115
  `${tunnelWsScheme(host)}://${host}/${args.assistantId}/v1/live-voice`,
116
116
  );
@@ -131,22 +131,22 @@ interface LiveVoiceTokenResponse {
131
131
  * (`SessionAuthentication` and `XSessionTokenAuthentication`) and deliberately
132
132
  * excludes API-key auth, so `X-Session-Token` is the CLI's way in and a
133
133
  * `vak_` key is not: a caller holding only an API key cannot mint here, by
134
- * design on the platform's side.
134
+ * design on the cloud's side.
135
135
  *
136
136
  * Throws {@link TunnelWsTokenError} carrying the HTTP status, so the caller can
137
- * tell "sign in again" (401) from "not your assistant" (403) from a platform
137
+ * tell "sign in again" (401) from "not your assistant" (403) from a cloud
138
138
  * that is simply down.
139
139
  */
140
140
  export async function mintTunnelWsToken(args: {
141
- platformUrl: string;
141
+ cloudUrl: string;
142
142
  assistantId: string;
143
143
  sessionToken: string;
144
144
  }): Promise<string> {
145
- const url = `${args.platformUrl.replace(/\/+$/, "")}/v1/auth/live-voice-token/`;
145
+ const url = `${args.cloudUrl.replace(/\/+$/, "")}/v1/auth/live-voice-token/`;
146
146
  // `authHeaders` resolves `Cadet-Organization-Id`, which the endpoint needs
147
147
  // to scope the token, and which is cached per token so a session does not
148
148
  // pay for an extra round trip.
149
- const headers = await authHeaders(args.sessionToken, args.platformUrl);
149
+ const headers = await authHeaders(args.sessionToken, args.cloudUrl);
150
150
 
151
151
  let response: Response;
152
152
  try {
@@ -1,11 +1,11 @@
1
- import { isManagedCloud } from "./managed-cloud.js";
1
+ import { isCloudHome } from "./cloud-values.js";
2
2
  import type { AssistantEntry } from "./assistant-config.js";
3
3
  import {
4
4
  authHeaders,
5
5
  invalidateOrgIdCache,
6
6
  parseUnifiedJobStatus,
7
7
  type UnifiedJobStatus,
8
- } from "./platform-client.js";
8
+ } from "./cloud-client.js";
9
9
  import {
10
10
  resolveRuntimeMigrationUrl,
11
11
  resolveRuntimeUrl,
@@ -49,7 +49,7 @@ function bearerHeaders(token: string): Record<string, string> {
49
49
  /**
50
50
  * Build the auth + content headers for a runtime migration request.
51
51
  *
52
- * - For `isManagedCloud(cloud)` we go through the platform's wildcard runtime
52
+ * - For `isCloudHome(cloud)` we go through the cloud's wildcard runtime
53
53
  * proxy, which authenticates user-session / vak_ tokens via DRF's default
54
54
  * authentication classes — `authHeaders()` produces the right combination
55
55
  * (`X-Session-Token` + `Cadet-Organization-Id`, or `Authorization: Bearer
@@ -60,7 +60,7 @@ async function migrationRequestHeaders(
60
60
  entry: Pick<AssistantEntry, "cloud" | "runtimeUrl">,
61
61
  token: string,
62
62
  ): Promise<Record<string, string>> {
63
- if (isManagedCloud(entry.cloud)) {
63
+ if (isCloudHome(entry.cloud)) {
64
64
  return {
65
65
  ...(await authHeaders(token, entry.runtimeUrl)),
66
66
  Accept: "application/json",
@@ -116,10 +116,10 @@ async function throwIfInProgress(
116
116
  *
117
117
  * For local/docker assistants this POSTs to
118
118
  * `{runtimeUrl}/v1/migrations/export-to-gcs` with guardian-token bearer
119
- * auth. For platform-managed (cloud="cadet") assistants the URL is rewritten
119
+ * auth. For cloud-managed (cloud="cadet") assistants the URL is rewritten
120
120
  * to the wildcard-runtime-proxy shape
121
- * `{platformUrl}/v1/assistants/<assistantId>/migrations/export-to-gcs` and
122
- * authenticated via the platform-token header set the platform's DRF auth
121
+ * `{cloudUrl}/v1/assistants/<assistantId>/migrations/export-to-gcs` and
122
+ * authenticated via the cloud-token header set the cloud's DRF auth
123
123
  * accepts (session / vak_).
124
124
  *
125
125
  * Returns the 202-accepted `job_id`. On 409 (another export in flight)
@@ -179,10 +179,10 @@ export async function localRuntimeExportToGcs(
179
179
  *
180
180
  * For local/docker assistants this POSTs to
181
181
  * `{runtimeUrl}/v1/migrations/import-from-gcs` with guardian-token bearer
182
- * auth. For platform-managed (cloud="cadet") assistants the URL is rewritten
182
+ * auth. For cloud-managed (cloud="cadet") assistants the URL is rewritten
183
183
  * to the wildcard-runtime-proxy shape
184
- * `{platformUrl}/v1/assistants/<assistantId>/migrations/import-from-gcs` and
185
- * authenticated via the platform token. On 409 throws
184
+ * `{cloudUrl}/v1/assistants/<assistantId>/migrations/import-from-gcs` and
185
+ * authenticated via the cloud token. On 409 throws
186
186
  * {@link MigrationInProgressError}.
187
187
  */
188
188
  export async function localRuntimeImportFromGcs(
@@ -261,10 +261,10 @@ export async function localRuntimePreflightFromGcs(
261
261
  *
262
262
  * For local/docker assistants this GETs
263
263
  * `{runtimeUrl}/v1/migrations/jobs/{jobId}` directly (guardian-token
264
- * bearer). For platform-managed assistants it routes through the wildcard
264
+ * bearer). For cloud assistants it routes through the wildcard
265
265
  * runtime proxy at
266
- * `{platformUrl}/v1/assistants/<assistantId>/migrations/jobs/{jobId}` with
267
- * platform-token auth — important: the platform's dedicated
266
+ * `{cloudUrl}/v1/assistants/<assistantId>/migrations/jobs/{jobId}` with
267
+ * platform-token auth — important: the cloud's dedicated
268
268
  * `/v1/migrations/jobs/{id}/` endpoint queries platform-side ImportJob
269
269
  * records and would 404 on runtime-created job IDs.
270
270
  */
@@ -320,15 +320,15 @@ export interface RuntimeIdentity {
320
320
  * (no filesystem reads), so it's safe to call against any running runtime.
321
321
  *
322
322
  * For local/docker assistants this GETs `{runtimeUrl}/v1/health` with
323
- * guardian-token bearer auth. For platform-managed (cloud="cadet")
323
+ * guardian-token bearer auth. For cloud-managed (cloud="cadet")
324
324
  * assistants the URL is rewritten to the wildcard runtime proxy shape
325
- * `{platformUrl}/v1/assistants/<assistantId>/health` and authenticated via
326
- * the platform token.
325
+ * `{cloudUrl}/v1/assistants/<assistantId>/health` and authenticated via
326
+ * the cloud token.
327
327
  *
328
328
  * For the cadet target this is the FIRST network call in the
329
329
  * teleport/backup export flow, so a stale `Cadet-Organization-Id` cache
330
330
  * entry would surface as a hard abort before any retry-friendly call (like
331
- * `platformRequestSignedUrl`) gets a chance to recover. Mirror that helper's
331
+ * `cloudRequestSignedUrl`) gets a chance to recover. Mirror that helper's
332
332
  * one-shot 401-retry: invalidate the org-ID cache and retry once. Local /
333
333
  * docker entries do not use the org-ID cache and are wrapped in
334
334
  * `callRuntimeWithAuthRetry` by callers for guardian-token refresh, so the
@@ -353,10 +353,10 @@ export async function localRuntimeIdentity(
353
353
  });
354
354
 
355
355
  let response = await doRequest();
356
- if (response.status === 401 && isManagedCloud(entry.cloud)) {
357
- // `entry.runtimeUrl` is the platform host for cadet-cloud entries
356
+ if (response.status === 401 && isCloudHome(entry.cloud)) {
357
+ // `entry.runtimeUrl` is the cloud host for cadet-cloud entries
358
358
  // (the wildcard runtime proxy lives there). Pass it as the cache key
359
- // platformUrl so we invalidate the same entry that authHeaders cached.
359
+ // cloudUrl so we invalidate the same entry that authHeaders cached.
360
360
  invalidateOrgIdCache(token, entry.runtimeUrl);
361
361
  response = await doRequest();
362
362
  }