@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
@@ -1,4 +1,4 @@
1
- import { isManagedCloud } from "../lib/managed-cloud.js";
1
+ import { isCloudHome } from "../lib/cloud-values.js";
2
2
  import { spawn } from "node:child_process";
3
3
  import { existsSync } from "node:fs";
4
4
  import { homedir } from "node:os";
@@ -30,7 +30,7 @@ import {
30
30
  getLockfileData,
31
31
  renameLockfileAssistantIfPresent,
32
32
  upsertRendererLockfileAssistant,
33
- replacePlatformAssistants,
33
+ replaceCloudAssistants,
34
34
  isActiveAssistant,
35
35
  isPairedLockfileEntry,
36
36
  PAIRED_GUARDIAN_TOKEN_HOST_ONLY_ERROR,
@@ -64,13 +64,13 @@ import { parseAssistantTargetArg } from "../lib/assistant-target-args.js";
64
64
  import { parseFeatureFlagArgs, readAmbientFlagEnvVars } from "../lib/flag-args";
65
65
  import {
66
66
  fetchOrganizationId,
67
- fetchPlatformAssistants,
68
- getPlatformUrl,
67
+ fetchCloudAssistants,
68
+ getCloudUrl,
69
69
  getWebUrl,
70
- readPlatformToken,
71
- savePlatformToken,
72
- clearPlatformToken,
73
- } from "../lib/platform-client";
70
+ readCloudToken,
71
+ saveCloudToken,
72
+ clearCloudToken,
73
+ } from "../lib/cloud-client";
74
74
  import { tuiLog } from "../lib/tui-log";
75
75
  import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
76
76
  import { relaunchDetached } from "../lib/detached-process.js";
@@ -87,7 +87,7 @@ const TokenBodySchema = z.object({ token: z.unknown() }).nullable();
87
87
 
88
88
  const SyncPlatformBodySchema = z.object({
89
89
  syncPlatform: z.unknown().optional(),
90
- platformAssistants: z.array(z.record(z.string(), z.unknown())).optional(),
90
+ cloudAssistants: z.array(z.record(z.string(), z.unknown())).optional(),
91
91
  organizationId: z.string().optional(),
92
92
  rename: z
93
93
  .object({ assistantId: z.string(), name: z.string() })
@@ -133,10 +133,10 @@ interface ParsedArgs {
133
133
  assistantName?: string;
134
134
  /** Assistant data root (home-collapsed) for the TUI details panel. */
135
135
  workspaceRoot?: string;
136
- /** "cadet" for platform-hosted assistants, undefined for local. */
136
+ /** "cadet" for cloud-hosted assistants, undefined for local. */
137
137
  cloud?: string;
138
- /** Platform session token (X-Session-Token), set when isManagedCloud(cloud). */
139
- platformToken?: string;
138
+ /** Platform session token (X-Session-Token), set when isCloudHome(cloud). */
139
+ cloudToken?: string;
140
140
  /** Guardian JWT (Authorization: Bearer), set for local assistants. */
141
141
  bearerToken?: string;
142
142
  /** Interface identifier sent as X-Cadet-Interface-Id on all requests. */
@@ -145,7 +145,7 @@ interface ParsedArgs {
145
145
  flagEnvVars: Record<string, string>;
146
146
  /** Parsed --flag overrides: kebab-case key -> typed value (for web injection). */
147
147
  parsedFlagOverrides: Record<string, boolean | string>;
148
- disablePlatform: boolean;
148
+ disableCloud: boolean;
149
149
  /** Auto-open the web interface in the default browser (--interface web only). */
150
150
  openBrowser: boolean;
151
151
  /** Explicit web server port (--interface web only). Binds strictly — no scan. */
@@ -179,10 +179,10 @@ export function parseArgs(): ParsedArgs {
179
179
  const { envVars: cliFlagVars, remaining: argsWithoutFlags } =
180
180
  parseFeatureFlagArgs(process.argv.slice(3));
181
181
  const flagEnvVars = { ...readAmbientFlagEnvVars(), ...cliFlagVars };
182
- const disablePlatformAmbient =
183
- process.env.CADET_DISABLE_PLATFORM?.trim().toLowerCase();
184
- let disablePlatform =
185
- disablePlatformAmbient === "true" || disablePlatformAmbient === "1";
182
+ const disableCloudAmbient =
183
+ process.env.CADET_DISABLE_CLOUD?.trim().toLowerCase();
184
+ let disableCloud =
185
+ disableCloudAmbient === "true" || disableCloudAmbient === "1";
186
186
  const args = argsWithoutFlags;
187
187
 
188
188
  // Build parsedFlagOverrides from the extracted env vars:
@@ -222,8 +222,8 @@ export function parseArgs(): ParsedArgs {
222
222
  if (arg === "--help" || arg === "-h") {
223
223
  printUsage();
224
224
  process.exit(0);
225
- } else if (arg === "--disable-platform") {
226
- disablePlatform = true;
225
+ } else if (arg === "--disable-cloud") {
226
+ disableCloud = true;
227
227
  } else if (arg === "--no-open") {
228
228
  openBrowserPref = false;
229
229
  } else if (arg === "--background") {
@@ -312,14 +312,14 @@ export function parseArgs(): ParsedArgs {
312
312
  // other topology — local, docker, and "paired" (a remote assistant paired
313
313
  // from another machine) — uses a bearer guardian JWT. Both are skipped
314
314
  // entirely when --token supplies the credential, so no saved creds are read.
315
- const platformToken = bearerTokenOverride
315
+ const cloudToken = bearerTokenOverride
316
316
  ? undefined
317
- : isManagedCloud(cloud)
318
- ? (readPlatformToken() ?? undefined)
317
+ : isCloudHome(cloud)
318
+ ? (readCloudToken() ?? undefined)
319
319
  : undefined;
320
320
  const bearerToken = bearerTokenOverride
321
321
  ? bearerTokenOverride
322
- : isManagedCloud(cloud)
322
+ : isCloudHome(cloud)
323
323
  ? undefined
324
324
  : (loadGuardianToken(entry?.assistantId ?? "")?.accessToken ?? undefined);
325
325
 
@@ -365,12 +365,12 @@ export function parseArgs(): ParsedArgs {
365
365
  assistantName,
366
366
  workspaceRoot,
367
367
  cloud,
368
- platformToken,
368
+ cloudToken,
369
369
  bearerToken,
370
370
  interfaceId,
371
371
  flagEnvVars,
372
372
  parsedFlagOverrides,
373
- disablePlatform,
373
+ disableCloud,
374
374
  openBrowser: openBrowserPref,
375
375
  webPort,
376
376
  background,
@@ -401,7 +401,7 @@ ${ANSI.bold}OPTIONS:${ANSI.reset}
401
401
  (--interface web). Prints the URL, PID, and log
402
402
  path, then returns to the shell.
403
403
  --flag <key=value> Feature flag override (repeatable, kebab-case key)
404
- --disable-platform Suppress all outbound platform API calls
404
+ --disable-cloud Suppress all outbound platform API calls
405
405
  -h, --help Show this help message
406
406
 
407
407
  ${ANSI.bold}EXAMPLES:${ANSI.reset}
@@ -421,19 +421,19 @@ ${ANSI.bold}EXAMPLES:${ANSI.reset}
421
421
  `);
422
422
  }
423
423
 
424
- async function maybeHydratePlatformAssistantName(
424
+ async function maybeHydrateCloudAssistantName(
425
425
  assistantId: string,
426
426
  assistantName: string | undefined,
427
427
  cloud: string | undefined,
428
- platformToken: string | undefined,
428
+ cloudToken: string | undefined,
429
429
  ): Promise<string | undefined> {
430
- if (!isManagedCloud(cloud) || assistantName || !platformToken) {
430
+ if (!isCloudHome(cloud) || assistantName || !cloudToken) {
431
431
  return assistantName;
432
432
  }
433
433
 
434
434
  try {
435
435
  const matchedAssistant = (
436
- await fetchPlatformAssistants(platformToken)
436
+ await fetchCloudAssistants(cloudToken)
437
437
  ).find((assistant) => assistant.id === assistantId);
438
438
  const hydratedName = matchedAssistant?.name.trim();
439
439
  if (!hydratedName) {
@@ -500,18 +500,18 @@ const PAIRING_CANCEL_PATTERN = /^(?:\/assistant)?\/__local\/pairing-cancel$/;
500
500
  const GUARDIAN_TOKEN_PATTERN =
501
501
  /^(?:\/assistant)?\/__local\/guardian-token\/([^/]+)$/;
502
502
  const PLATFORM_SESSION_PATTERN =
503
- /^(?:\/assistant)?\/__local\/platform-session$/;
503
+ /^(?:\/assistant)?\/__local\/cloud-session$/;
504
504
 
505
505
  // The loopback platform session token. Persisted via the same store the CLI
506
506
  // uses (so `cadet client` restarts and CLI logins stay in sync), cached here
507
507
  // to keep it off the per-request proxy path. Set only after the SPA validates
508
508
  // the loopback `state`, so an unsolicited /callback can't fixate a session.
509
- let platformSessionToken: string | null | undefined;
509
+ let cloudSessionToken: string | null | undefined;
510
510
  function currentPlatformToken(): string | null {
511
- if (platformSessionToken === undefined) {
512
- platformSessionToken = readPlatformToken();
511
+ if (cloudSessionToken === undefined) {
512
+ cloudSessionToken = readCloudToken();
513
513
  }
514
- return platformSessionToken;
514
+ return cloudSessionToken;
515
515
  }
516
516
 
517
517
  // Whether to attach a host-owned credential to a proxied request. The browser
@@ -582,8 +582,8 @@ async function handleLocalEndpoints(
582
582
  // platform. The browser never holds a session cookie.
583
583
  if (PLATFORM_SESSION_PATTERN.test(pathname)) {
584
584
  if (req.method === "DELETE") {
585
- clearPlatformToken();
586
- platformSessionToken = null;
585
+ clearCloudToken();
586
+ cloudSessionToken = null;
587
587
  return Response.json({ ok: true });
588
588
  }
589
589
  if (req.method === "POST") {
@@ -598,8 +598,8 @@ async function handleLocalEndpoints(
598
598
  { status: 400 },
599
599
  );
600
600
  }
601
- savePlatformToken(token);
602
- platformSessionToken = token;
601
+ saveCloudToken(token);
602
+ cloudSessionToken = token;
603
603
  return Response.json({ ok: true });
604
604
  }
605
605
  return new Response(null, { status: 405 });
@@ -627,10 +627,10 @@ async function handleLocalEndpoints(
627
627
  );
628
628
  }
629
629
  let result;
630
- if (body.syncPlatform && body.platformAssistants) {
631
- result = replacePlatformAssistants(
630
+ if (body.syncPlatform && body.cloudAssistants) {
631
+ result = replaceCloudAssistants(
632
632
  lockfilePaths,
633
- body.platformAssistants,
633
+ body.cloudAssistants,
634
634
  body.organizationId,
635
635
  );
636
636
  } else if (body.rename) {
@@ -1223,7 +1223,7 @@ const WEB_BACKGROUND_START_TIMEOUT_MS = 30_000;
1223
1223
  * The port is resolved up front (explicit --port must be free; otherwise the
1224
1224
  * first free port at/above 3000) and pinned via `--port` on the child so the
1225
1225
  * URL printed here is the one the child binds. The child's stdout/stderr go to
1226
- * `<xdg-log-dir>/client-web.log` — same detach idiom as the nginx/ngrok
1226
+ * `<xdg-log-dir>/client-web.log` — same detach idiom as the nginx/tunnel
1227
1227
  * spawns. Success is only reported once the child is accepting connections on
1228
1228
  * the port; an early child exit (e.g. missing @mycadet/web assets) or a
1229
1229
  * startup timeout fails with a pointer at the log file.
@@ -1303,17 +1303,16 @@ async function spawnBackgroundWebInterface(
1303
1303
  */
1304
1304
  export function buildWebInterfaceConfig(opts: {
1305
1305
  webUrl: string;
1306
- platformUrl: string;
1307
- disablePlatform: boolean;
1306
+ cloudUrl: string;
1308
1307
  }): Record<string, unknown> {
1309
- const { webUrl, platformUrl, disablePlatform } = opts;
1310
- return { webUrl, platformUrl, disablePlatform };
1308
+ const { webUrl, cloudUrl } = opts;
1309
+ return { webUrl, cloudUrl };
1311
1310
  }
1312
1311
 
1313
1312
  async function runWebInterface(
1314
1313
  flagEnvVars: Record<string, string>,
1315
1314
  parsedFlagOverrides: Record<string, boolean | string>,
1316
- disablePlatform: boolean,
1315
+ disableCloud: boolean,
1317
1316
  openInBrowser: boolean,
1318
1317
  webPort: number | undefined,
1319
1318
  ): Promise<void> {
@@ -1327,7 +1326,7 @@ async function runWebInterface(
1327
1326
  return runViteDevServer(
1328
1327
  webSourceDir,
1329
1328
  flagEnvVars,
1330
- disablePlatform,
1329
+ disableCloud,
1331
1330
  openInBrowser,
1332
1331
  webPort,
1333
1332
  );
@@ -1339,19 +1338,17 @@ async function runWebInterface(
1339
1338
  `${ANSI.bold}--interface web${ANSI.reset}: unable to locate ` +
1340
1339
  `@mycadet/web assets.\n\n` +
1341
1340
  ` npm/bunx install: npm install @mycadet/web\n` +
1342
- ` source checkout: cd clients/web && VITE_PLATFORM_MODE=false bun run build`,
1341
+ ` source checkout: cd clients/web && VITE_CLOUD_MODE=false bun run build`,
1343
1342
  );
1344
1343
  process.exit(1);
1345
1344
  }
1346
1345
 
1347
1346
  const rawIndexHtml = await Bun.file(path.join(distDir, "index.html")).text();
1348
- const platformUrl = getPlatformUrl();
1347
+ const cloudUrl = getCloudUrl();
1349
1348
  const webUrl = getWebUrl();
1350
1349
  const safeJson = (v: unknown) =>
1351
1350
  JSON.stringify(v).replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
1352
- const configJson = safeJson(
1353
- buildWebInterfaceConfig({ webUrl, platformUrl, disablePlatform }),
1354
- );
1351
+ const configJson = safeJson(buildWebInterfaceConfig({ webUrl, cloudUrl }));
1355
1352
  const hasOverrides = Object.keys(parsedFlagOverrides).length > 0;
1356
1353
  const flagOverridesSnippet = hasOverrides
1357
1354
  ? `;window.__CADET_FLAG_OVERRIDES__=${safeJson(parsedFlagOverrides)}`
@@ -1380,9 +1377,9 @@ async function runWebInterface(
1380
1377
  return Response.redirect(SPA_BASE, 302);
1381
1378
  }
1382
1379
 
1383
- // Loopback auth: the platform redirects here after login with
1380
+ // Loopback auth: the cloud redirects here after login with
1384
1381
  // ?state=...&session_token=... — forward into the SPA, which validates the
1385
- // `state` nonce before registering the token via /__local/platform-session.
1382
+ // `state` nonce before registering the token via /__local/cloud-session.
1386
1383
  if (pathname === "/callback") {
1387
1384
  return Response.redirect(`/account/platform-callback${url.search}`, 302);
1388
1385
  }
@@ -1404,9 +1401,9 @@ async function runWebInterface(
1404
1401
  pathname.startsWith("/_allauth/") ||
1405
1402
  pathname.startsWith("/accounts/")
1406
1403
  ) {
1407
- const target = new URL(pathname + url.search, platformUrl);
1404
+ const target = new URL(pathname + url.search, cloudUrl);
1408
1405
  const headers = new Headers(req.headers);
1409
- headers.set("Host", new URL(platformUrl).host);
1406
+ headers.set("Host", new URL(cloudUrl).host);
1410
1407
  headers.delete("Origin");
1411
1408
  headers.delete("Referer");
1412
1409
 
@@ -1430,7 +1427,7 @@ async function runWebInterface(
1430
1427
  headers.set("X-Session-Token", sessionToken);
1431
1428
  }
1432
1429
  } else if (sessionToken) {
1433
- // allauth / accounts: the platform expects the Django session cookie.
1430
+ // allauth / accounts: the cloud expects the Django session cookie.
1434
1431
  headers.set(
1435
1432
  "Cookie",
1436
1433
  `sessionid=${sessionToken}; __Secure-sessionid=${sessionToken}`,
@@ -1507,7 +1504,7 @@ async function runWebInterface(
1507
1504
  console.log(`Port ${preferredPort} in use; using ${port}.`);
1508
1505
  }
1509
1506
  // Advertise `localhost` (not `127.0.0.1`) so the app origin matches the host
1510
- // the platform hardcodes in its loopback callback. We bind both loopback
1507
+ // the cloud hardcodes in its loopback callback. We bind both loopback
1511
1508
  // families above so `localhost` reaches us whichever one it resolves to.
1512
1509
  const webInterfaceUrl = `http://localhost:${port}${SPA_BASE}`;
1513
1510
  console.log(`Cadet web interface: ${webInterfaceUrl}`);
@@ -1526,11 +1523,11 @@ async function runWebInterface(
1526
1523
  async function runViteDevServer(
1527
1524
  webSourceDir: string,
1528
1525
  flagEnvVars: Record<string, string>,
1529
- disablePlatform: boolean,
1526
+ disableCloud: boolean,
1530
1527
  openInBrowser: boolean,
1531
1528
  webPort: number | undefined,
1532
1529
  ): Promise<void> {
1533
- const platformUrl = getPlatformUrl();
1530
+ const cloudUrl = getCloudUrl();
1534
1531
 
1535
1532
  // Build VITE_CADET_FLAG_* vars so Vite exposes them to the browser bundle.
1536
1533
  const viteFlagVars: Record<string, string> = {};
@@ -1555,11 +1552,12 @@ async function runViteDevServer(
1555
1552
  ...process.env,
1556
1553
  ...flagEnvVars,
1557
1554
  ...viteFlagVars,
1558
- ...(disablePlatform ? { VITE_CADET_DISABLE_PLATFORM: "true" } : {}),
1559
- VITE_PLATFORM_MODE: "false",
1560
- API_PROXY_TARGET: platformUrl,
1555
+ ...(disableCloud ? { VITE_CADET_DISABLE_CLOUD: "true" } : {}),
1556
+ VITE_CLOUD_MODE: "false",
1557
+ VITE_WEB_MODE: "full",
1558
+ API_PROXY_TARGET: cloudUrl,
1561
1559
  CADET_WEB_URL: getWebUrl(),
1562
- CADET_PLATFORM_URL: platformUrl,
1560
+ CADET_CLOUD_URL: cloudUrl,
1563
1561
  PORT: String(port),
1564
1562
  },
1565
1563
  });
@@ -1590,7 +1588,7 @@ async function runViteDevServer(
1590
1588
  * Return a possibly-refreshed bearer token for the TUI's startup auth.
1591
1589
  *
1592
1590
  * Only a STORED guardian token is refreshable: platform session auth
1593
- * (`isManagedCloud(cloud)`) and ephemeral `--token` overrides (whose token won't
1591
+ * (`isCloudHome(cloud)`) and ephemeral `--token` overrides (whose token won't
1594
1592
  * match the store) are left untouched, as is a token that's still fresh. When
1595
1593
  * the stored token has passed its `refreshAfter` (or expiry) and a refresh
1596
1594
  * token is available, refresh once via the concurrency-safe refreshGuardianToken
@@ -1603,7 +1601,7 @@ export async function resolveFreshBearerToken(
1603
1601
  bearerToken: string | undefined,
1604
1602
  cloud: string | undefined,
1605
1603
  ): Promise<string | undefined> {
1606
- if (isManagedCloud(cloud) || !bearerToken || !assistantId) return bearerToken;
1604
+ if (isCloudHome(cloud) || !bearerToken || !assistantId) return bearerToken;
1607
1605
 
1608
1606
  const stored = loadGuardianToken(assistantId);
1609
1607
  // Refresh only the stored token (an ephemeral --token won't match), and only
@@ -1637,19 +1635,19 @@ export const client = Object.assign(
1637
1635
  assistantName: parsedAssistantName,
1638
1636
  workspaceRoot,
1639
1637
  cloud,
1640
- platformToken,
1638
+ cloudToken,
1641
1639
  bearerToken,
1642
1640
  interfaceId,
1643
1641
  flagEnvVars,
1644
1642
  parsedFlagOverrides,
1645
- disablePlatform,
1643
+ disableCloud,
1646
1644
  openBrowser: openInBrowser,
1647
1645
  webPort,
1648
1646
  background,
1649
1647
  } = parseArgs();
1650
1648
 
1651
- if (disablePlatform) {
1652
- process.env.CADET_DISABLE_PLATFORM = "true";
1649
+ if (disableCloud) {
1650
+ process.env.CADET_DISABLE_CLOUD = "true";
1653
1651
  }
1654
1652
 
1655
1653
  if (interfaceId === WEB_INTERFACE_ID) {
@@ -1660,7 +1658,7 @@ export const client = Object.assign(
1660
1658
  await runWebInterface(
1661
1659
  flagEnvVars,
1662
1660
  parsedFlagOverrides,
1663
- disablePlatform,
1661
+ disableCloud,
1664
1662
  openInBrowser,
1665
1663
  webPort,
1666
1664
  );
@@ -1675,23 +1673,23 @@ export const client = Object.assign(
1675
1673
  interfaceId,
1676
1674
  });
1677
1675
 
1678
- const assistantName = await maybeHydratePlatformAssistantName(
1676
+ const assistantName = await maybeHydrateCloudAssistantName(
1679
1677
  assistantId,
1680
1678
  parsedAssistantName,
1681
1679
  cloud,
1682
- platformToken,
1680
+ cloudToken,
1683
1681
  );
1684
1682
 
1685
1683
  // Build pre-constructed request headers merged from auth + client registration.
1686
1684
  // Spreading into every fetch site ensures consistency across REST and SSE endpoints.
1687
1685
  let auth: Record<string, string> | undefined;
1688
- if (isManagedCloud(cloud) && platformToken) {
1689
- const orgId = await fetchOrganizationId(platformToken).catch((err) => {
1686
+ if (isCloudHome(cloud) && cloudToken) {
1687
+ const orgId = await fetchOrganizationId(cloudToken).catch((err) => {
1690
1688
  tuiLog.warn("failed to fetch organization id", { err: String(err) });
1691
1689
  return undefined;
1692
1690
  });
1693
1691
  auth = {
1694
- "X-Session-Token": platformToken,
1692
+ "X-Session-Token": cloudToken,
1695
1693
  ...(orgId ? { "Cadet-Organization-Id": orgId } : {}),
1696
1694
  ...getClientRegistrationHeaders(interfaceId),
1697
1695
  };
@@ -0,0 +1,104 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { mkdtempSync, rmSync } from "fs";
3
+ import { tmpdir } from "os";
4
+ import { join } from "path";
5
+
6
+ import { saveAssistantEntry } from "../lib/assistant-config.js";
7
+ import { completeCadet } from "./completions.js";
8
+
9
+ const COMMAND_NAMES = [
10
+ "backup",
11
+ "clean",
12
+ "client",
13
+ "completions",
14
+ "confirm",
15
+ "connect",
16
+ "hatch",
17
+ "ps",
18
+ "retire",
19
+ "sleep",
20
+ "use",
21
+ "wake",
22
+ ];
23
+
24
+ describe("completeCadet", () => {
25
+ let lockfileDir: string;
26
+ let prevLockfileDir: string | undefined;
27
+
28
+ beforeEach(() => {
29
+ lockfileDir = mkdtempSync(join(tmpdir(), "cadet-completions-"));
30
+ prevLockfileDir = process.env.CADET_LOCKFILE_DIR;
31
+ process.env.CADET_LOCKFILE_DIR = lockfileDir;
32
+ });
33
+
34
+ afterEach(() => {
35
+ if (prevLockfileDir === undefined) delete process.env.CADET_LOCKFILE_DIR;
36
+ else process.env.CADET_LOCKFILE_DIR = prevLockfileDir;
37
+ rmSync(lockfileDir, { recursive: true, force: true });
38
+ });
39
+
40
+ function seed(id: string): void {
41
+ saveAssistantEntry({
42
+ assistantId: id,
43
+ cloud: "docker",
44
+ runtimeUrl: "http://localhost:7830",
45
+ hatchedAt: new Date().toISOString(),
46
+ });
47
+ }
48
+
49
+ test("no words yet offers every command name, sorted", () => {
50
+ const input: string[] = [];
51
+ expect(completeCadet(input, COMMAND_NAMES)).toEqual(
52
+ [...COMMAND_NAMES].sort(),
53
+ );
54
+ // The word under the cursor is popped from a COPY — the caller's array
55
+ // (bash's COMP_WORDS) must come back untouched.
56
+ expect(input).toEqual([]);
57
+ });
58
+
59
+ test("first positional of an assistant-name command offers known assistant names", () => {
60
+ seed("quill-owl-d4e5f6");
61
+ seed("fable-fox-a1b2c3");
62
+ // `cadet retire <TAB>` — the empty trailing word is the word being completed.
63
+ expect(completeCadet(["retire", ""], COMMAND_NAMES)).toEqual([
64
+ "fable-fox-a1b2c3",
65
+ "quill-owl-d4e5f6",
66
+ ]);
67
+ });
68
+
69
+ test("partial words are returned unfiltered — the shell does prefix filtering", () => {
70
+ seed("quill-owl-d4e5f6");
71
+ seed("fable-fox-a1b2c3");
72
+ expect(completeCadet(["retire", "fable"], COMMAND_NAMES)).toEqual([
73
+ "fable-fox-a1b2c3",
74
+ "quill-owl-d4e5f6",
75
+ ]);
76
+ });
77
+
78
+ test("flag prefixes fall through to the shell default (no candidates)", () => {
79
+ seed("fable-fox-a1b2c3");
80
+ expect(completeCadet(["retire", "--"], COMMAND_NAMES)).toEqual([]);
81
+ expect(completeCadet(["retire", "--name"], COMMAND_NAMES)).toEqual([]);
82
+ });
83
+
84
+ test("deeper positionals of an assistant-name command offer nothing", () => {
85
+ seed("fable-fox-a1b2c3");
86
+ expect(
87
+ completeCadet(["exec", "fable-fox-a1b2c3", ""], COMMAND_NAMES),
88
+ ).toEqual([]);
89
+ expect(
90
+ completeCadet(["logs", "fable-fox-a1b2c3", "tail", ""], COMMAND_NAMES),
91
+ ).toEqual([]);
92
+ });
93
+
94
+ test("commands that take no assistant name offer nothing after the command word", () => {
95
+ seed("fable-fox-a1b2c3");
96
+ expect(completeCadet(["hatch", ""], COMMAND_NAMES)).toEqual([]);
97
+ expect(completeCadet(["ps", ""], COMMAND_NAMES)).toEqual([]);
98
+ });
99
+
100
+ test("an unreadable/empty lockfile completes quietly with no names", () => {
101
+ // No entries seeded — the lockfile dir exists but has no lockfile.json.
102
+ expect(completeCadet(["retire", ""], COMMAND_NAMES)).toEqual([]);
103
+ });
104
+ });
@@ -0,0 +1,87 @@
1
+ import { loadAllAssistants } from "../lib/assistant-config.js";
2
+
3
+ /**
4
+ * Commands whose first positional argument is an assistant name — completing
5
+ * their first word offers the names of known assistants (from the lockfile)
6
+ * instead of falling through to shell file completion.
7
+ */
8
+ const ASSISTANT_NAME_COMMANDS = new Set([
9
+ "exec",
10
+ "logs",
11
+ "message",
12
+ "restart",
13
+ "restore",
14
+ "retire",
15
+ "rollback",
16
+ "sleep",
17
+ "teleport",
18
+ "terminal",
19
+ "tunnel",
20
+ "unpair",
21
+ "upgrade",
22
+ "use",
23
+ "wake",
24
+ ]);
25
+
26
+ /** Assistant names known to this CLI's lockfile; [] when the lockfile is unreadable. */
27
+ function assistantNames(): string[] {
28
+ try {
29
+ return loadAllAssistants()
30
+ .map((entry) => entry.assistantId)
31
+ .sort();
32
+ } catch {
33
+ return [];
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Resolve completion candidates for the word the cursor is on.
39
+ *
40
+ * `words` carries every command-line word after `cadet` up to the cursor,
41
+ * INCLUDING the (possibly empty) word being completed — the same shape bash
42
+ * passes via `COMP_WORDS[@]:1`. The returned list is UNFILTERED: the shell
43
+ * (`compgen -W`) does the prefix filtering, so the CLI only decides WHAT
44
+ * KIND of thing completes here.
45
+ */
46
+ export function completeCadet(
47
+ input: string[],
48
+ commandNames: readonly string[],
49
+ ): string[] {
50
+ const words = [...input];
51
+ const toComplete = words.pop() ?? "";
52
+ const preceding = words;
53
+
54
+ // Completing the command word itself (or nothing typed yet).
55
+ if (preceding.length === 0) {
56
+ return [...commandNames].sort();
57
+ }
58
+
59
+ // First positional of an assistant-name command: offer instance names.
60
+ // Flag prefixes fall through to shell default (no flag completion yet).
61
+ if (
62
+ preceding.length === 1 &&
63
+ ASSISTANT_NAME_COMMANDS.has(preceding[0]) &&
64
+ !toComplete.startsWith("-")
65
+ ) {
66
+ return assistantNames();
67
+ }
68
+
69
+ return [];
70
+ }
71
+
72
+ const BASH_COMPLETION_SCRIPT = `# bash completion for the cadet CLI — load with: eval "$(cadet completions)"
73
+ _cadet_completions() {
74
+ local candidates
75
+ candidates=$(cadet __complete "\${COMP_WORDS[@]:1}" 2>/dev/null)
76
+ COMPREPLY=($(compgen -W "$candidates" -- "\${COMP_WORDS[COMP_CWORD]}"))
77
+ }
78
+ complete -F _cadet_completions cadet`;
79
+
80
+ // ── Entry point ─────────────────────────────────────────────────
81
+
82
+ export const completions = Object.assign(
83
+ async function completions(): Promise<void> {
84
+ console.log(BASH_COMPLETION_SCRIPT);
85
+ },
86
+ { description: `Print bash completion setup (eval "$(cadet completions)")` },
87
+ );