@mycadet/cli 0.12.7 → 0.12.9

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 (179) 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 +3 -2
  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 +2 -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 +21 -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 +1 -8
  80. package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -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 +181 -197
  90. package/src/__tests__/tunnel.test.ts +257 -222
  91. package/src/__tests__/uninstall.test.ts +13 -4
  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 -76
  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 +23 -23
  104. package/src/commands/login.ts +22 -22
  105. package/src/commands/logs.ts +8 -8
  106. package/src/commands/ps.ts +244 -86
  107. package/src/commands/restart.ts +30 -0
  108. package/src/commands/restore.ts +140 -22
  109. package/src/commands/retire.ts +48 -150
  110. package/src/commands/rollback.ts +20 -26
  111. package/src/commands/setup.ts +2 -2
  112. package/src/commands/sleep.ts +2 -2
  113. package/src/commands/teleport.ts +109 -109
  114. package/src/commands/terminal.ts +158 -18
  115. package/src/commands/tunnel.ts +138 -157
  116. package/src/commands/uninstall.ts +57 -9
  117. package/src/commands/unpair.ts +3 -3
  118. package/src/commands/upgrade.ts +30 -54
  119. package/src/commands/use.ts +1 -1
  120. package/src/commands/wake.ts +166 -50
  121. package/src/index.ts +18 -9
  122. package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
  123. package/src/lib/__tests__/docker.test.ts +168 -20
  124. package/src/lib/__tests__/job-polling.test.ts +2 -2
  125. package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
  126. package/src/lib/__tests__/runtime-url.test.ts +1 -1
  127. package/src/lib/assistant-api-key-resolution.ts +5 -5
  128. package/src/lib/assistant-client.ts +8 -8
  129. package/src/lib/assistant-config.ts +58 -12
  130. package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
  131. package/src/lib/cloud-login.ts +24 -24
  132. package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
  133. package/src/lib/cloud-values.ts +3 -0
  134. package/src/lib/config-utils.ts +1 -1
  135. package/src/lib/detached-process.ts +1 -1
  136. package/src/lib/docker.ts +260 -53
  137. package/src/lib/ensure-binary.ts +171 -0
  138. package/src/lib/environments/__tests__/paths.test.ts +2 -2
  139. package/src/lib/environments/__tests__/resolve.test.ts +26 -26
  140. package/src/lib/environments/resolve.ts +8 -8
  141. package/src/lib/flag-args.test.ts +1 -1
  142. package/src/lib/flag-args.ts +1 -1
  143. package/src/lib/guardian-token.ts +37 -5
  144. package/src/lib/hatch-local.ts +19 -23
  145. package/src/lib/health-check.ts +6 -6
  146. package/src/lib/ingress-config.ts +8 -35
  147. package/src/lib/job-polling.ts +2 -2
  148. package/src/lib/live-voice/connection.ts +12 -12
  149. package/src/lib/live-voice/tunnel.test.ts +11 -11
  150. package/src/lib/live-voice/tunnel.ts +15 -15
  151. package/src/lib/local-runtime-client.ts +20 -20
  152. package/src/lib/local.ts +10 -31
  153. package/src/lib/nginx-ingress.ts +42 -15
  154. package/src/lib/pair.ts +1 -1
  155. package/src/lib/process.ts +1 -1
  156. package/src/lib/provider-secrets.ts +3 -3
  157. package/src/lib/retire-apple-container.ts +1 -1
  158. package/src/lib/runtime-url.ts +8 -8
  159. package/src/lib/statefulset.ts +2 -2
  160. package/src/lib/sync-cloud-assistants.ts +19 -19
  161. package/src/lib/teleport-backup.ts +4 -4
  162. package/src/lib/terminal-client.ts +17 -17
  163. package/src/lib/terminal-session.ts +24 -24
  164. package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
  165. package/src/lib/tunnels/cadet.ts +29 -0
  166. package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +36 -15
  167. package/src/lib/tunnels/gateway-notify.ts +61 -0
  168. package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +350 -89
  169. package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +53 -9
  170. package/src/lib/upgrade-lifecycle.ts +8 -21
  171. package/src/lib/upgrade-preflight.ts +2 -2
  172. package/src/lib/workos-pkce.ts +5 -5
  173. package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
  174. package/node_modules/@mycadet/assistant-api/events/show-platform-login.ts +0 -20
  175. package/src/commands/recover.ts +0 -131
  176. package/src/commands/roadmap.ts +0 -459
  177. package/src/commands/ssh.ts +0 -144
  178. package/src/commands/workflows.ts +0 -305
  179. 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 { mkdirSync, writeFileSync } from "fs";
3
3
  import { dirname, join } from "path";
4
4
 
@@ -14,9 +14,9 @@ import {
14
14
  localRuntimePollJobStatus,
15
15
  } from "../lib/local-runtime-client.js";
16
16
  import {
17
- platformRequestSignedUrl,
18
- readPlatformToken,
19
- } from "../lib/platform-client.js";
17
+ cloudRequestSignedUrl,
18
+ readCloudToken,
19
+ } from "../lib/cloud-client.js";
20
20
  import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
21
21
 
22
22
  // Default timeout for the runtime-direct export request. Overridable via
@@ -42,10 +42,10 @@ export const backup = Object.assign(
42
42
  console.log("Options:");
43
43
  console.log(" --output <path> Path to save the .vbundle file");
44
44
  console.log(
45
- " (default: ~/.local/share/cadet/backups/<name>-<timestamp>.vbundle)",
45
+ " defaults to ~/.local/share/cadet/backups/<name>-<timestamp>.vbundle",
46
46
  );
47
47
  console.log(
48
- ` --export-timeout <secs> Export request timeout in seconds (default: ${DEFAULT_EXPORT_TIMEOUT_MS / 1000})`,
48
+ ` --export-timeout <secs> Export request timeout in seconds, defaults to ${DEFAULT_EXPORT_TIMEOUT_MS / 1000}`,
49
49
  );
50
50
  console.log("");
51
51
  console.log("Examples:");
@@ -102,7 +102,7 @@ export const backup = Object.assign(
102
102
  process.exit(1);
103
103
  }
104
104
 
105
- if (isManagedCloud(cloud)) {
105
+ if (isCloudHome(cloud)) {
106
106
  await backupPlatform(entry, name, outputArg);
107
107
  return;
108
108
  }
@@ -233,9 +233,9 @@ export const backup = Object.assign(
233
233
  // ---------------------------------------------------------------------------
234
234
  // Platform-managed (cloud="cadet") backup over GCS.
235
235
  //
236
- // The runtime exports the bundle straight to a platform-issued signed GCS
236
+ // The runtime exports the bundle straight to a cloud-issued signed GCS
237
237
  // URL; the CLI then downloads from GCS to local disk. Bytes never flow
238
- // through Django. Same architectural shape as the platform-source half of
238
+ // through Django. Same architectural shape as the cloud-source half of
239
239
  // `cadet teleport`. Output format and success log lines match mode 1
240
240
  // (runtime-direct local backup) so users see one consistent UX.
241
241
  //
@@ -251,24 +251,24 @@ async function backupPlatform(
251
251
  name: string,
252
252
  outputArg?: string,
253
253
  ): Promise<void> {
254
- const platformToken = readPlatformToken();
255
- if (!platformToken) {
254
+ const cloudToken = readCloudToken();
255
+ if (!cloudToken) {
256
256
  console.error(
257
- "Not logged in. Run 'cadet login' first (required for platform-managed backup).",
257
+ "Not logged in. Run 'cadet login' first.",
258
258
  );
259
259
  process.exit(1);
260
260
  }
261
261
  // Pin upload, download, and runtime requests to the same platform instance
262
- // the assistant lives on. Using `getPlatformUrl()` instead would target
262
+ // the assistant lives on. Using `getCloudUrl()` instead would target
263
263
  // whatever the lockfile / env-var resolves to, which may differ from
264
264
  // `entry.runtimeUrl` for staging/dev assistants and end up signing URLs
265
- // for the wrong GCS bucket. Mirrors the teleport bundlePlatformUrl
265
+ // for the wrong GCS bucket. Mirrors the teleport bundleCloudUrl
266
266
  // threading at `cli/src/commands/teleport.ts:1311-1312`.
267
- const platformUrl = entry.runtimeUrl;
267
+ const cloudUrl = entry.runtimeUrl;
268
268
  // Track the working platform token across kickoff/poll/download so a
269
269
  // 401-driven refresh during polling stays consistent through the final
270
270
  // signed-download request.
271
- let exportPlatformToken = platformToken;
271
+ let exportPlatformToken = cloudToken;
272
272
 
273
273
  // Step 0 — Ask the source runtime which version it's running. The bundle
274
274
  // is produced by the daemon (not the CLI), and the CLI version can drift
@@ -288,17 +288,17 @@ async function backupPlatform(
288
288
  }
289
289
 
290
290
  // Step 1 — Request a signed upload URL.
291
- const { url: uploadUrl, bundleKey } = await platformRequestSignedUrl(
291
+ const { url: uploadUrl, bundleKey } = await cloudRequestSignedUrl(
292
292
  {
293
293
  operation: "upload",
294
294
  minRuntimeVersion: runtimeIdentity.version,
295
295
  maxRuntimeVersion: null,
296
296
  },
297
297
  exportPlatformToken,
298
- platformUrl,
298
+ cloudUrl,
299
299
  );
300
300
 
301
- // Step 2 — Kick off runtime export-to-GCS through the platform's
301
+ // Step 2 — Kick off runtime export-to-GCS through the cloud's
302
302
  // wildcard runtime proxy. `localRuntimeExportToGcs` builds the
303
303
  // `/v1/assistants/<id>/migrations/export-to-gcs` URL for cloud="cadet"
304
304
  // and uses platform-token auth (no guardian-token bootstrap).
@@ -327,7 +327,7 @@ async function backupPlatform(
327
327
  label: "platform export",
328
328
  poll: () => localRuntimePollJobStatus(entry, exportPlatformToken, jobId),
329
329
  refreshOn401: async () => {
330
- const refreshed = readPlatformToken();
330
+ const refreshed = readCloudToken();
331
331
  if (!refreshed) {
332
332
  throw new Error(
333
333
  "Platform auth expired during export and no credential was found on disk. Run 'cadet login' and retry.",
@@ -344,7 +344,7 @@ async function backupPlatform(
344
344
 
345
345
  // Step 4 — Request a signed download URL for the same bundle and fetch
346
346
  // it from GCS directly. No auth on signed URLs.
347
- // Use `exportPlatformToken` (not the original `platformToken`) so a
347
+ // Use `exportPlatformToken` (not the original `cloudToken`) so a
348
348
  // poll-loop 401 refresh doesn't get clobbered here — otherwise a long
349
349
  // export that recovered mid-poll via re-auth would still 401 on the
350
350
  // download-URL request and abort an otherwise successful run.
@@ -354,15 +354,15 @@ async function backupPlatform(
354
354
  // runtime to gate against, and the user can later restore the file
355
355
  // into any compatible runtime. Sending the CLI's version would
356
356
  // incorrectly block older CLIs from backing up newer assistants.
357
- // The platform treats `target_runtime_version` as optional and skips
357
+ // The cloud treats `target_runtime_version` as optional and skips
358
358
  // the version check when it's omitted.
359
- const { url: bundleUrl } = await platformRequestSignedUrl(
359
+ const { url: bundleUrl } = await cloudRequestSignedUrl(
360
360
  {
361
361
  operation: "download",
362
362
  bundleKey,
363
363
  },
364
364
  exportPlatformToken,
365
- platformUrl,
365
+ cloudUrl,
366
366
  );
367
367
 
368
368
  let downloadResponse: Response;
@@ -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,17 @@ async function spawnBackgroundWebInterface(
1303
1303
  */
1304
1304
  export function buildWebInterfaceConfig(opts: {
1305
1305
  webUrl: string;
1306
- platformUrl: string;
1307
- disablePlatform: boolean;
1306
+ cloudUrl: string;
1307
+ disableCloud: boolean;
1308
1308
  }): Record<string, unknown> {
1309
- const { webUrl, platformUrl, disablePlatform } = opts;
1310
- return { webUrl, platformUrl, disablePlatform };
1309
+ const { webUrl, cloudUrl, disableCloud } = opts;
1310
+ return { webUrl, cloudUrl, disableCloud };
1311
1311
  }
1312
1312
 
1313
1313
  async function runWebInterface(
1314
1314
  flagEnvVars: Record<string, string>,
1315
1315
  parsedFlagOverrides: Record<string, boolean | string>,
1316
- disablePlatform: boolean,
1316
+ disableCloud: boolean,
1317
1317
  openInBrowser: boolean,
1318
1318
  webPort: number | undefined,
1319
1319
  ): Promise<void> {
@@ -1327,7 +1327,7 @@ async function runWebInterface(
1327
1327
  return runViteDevServer(
1328
1328
  webSourceDir,
1329
1329
  flagEnvVars,
1330
- disablePlatform,
1330
+ disableCloud,
1331
1331
  openInBrowser,
1332
1332
  webPort,
1333
1333
  );
@@ -1339,18 +1339,18 @@ async function runWebInterface(
1339
1339
  `${ANSI.bold}--interface web${ANSI.reset}: unable to locate ` +
1340
1340
  `@mycadet/web assets.\n\n` +
1341
1341
  ` npm/bunx install: npm install @mycadet/web\n` +
1342
- ` source checkout: cd clients/web && VITE_PLATFORM_MODE=false bun run build`,
1342
+ ` source checkout: cd clients/web && VITE_CLOUD_MODE=false bun run build`,
1343
1343
  );
1344
1344
  process.exit(1);
1345
1345
  }
1346
1346
 
1347
1347
  const rawIndexHtml = await Bun.file(path.join(distDir, "index.html")).text();
1348
- const platformUrl = getPlatformUrl();
1348
+ const cloudUrl = getCloudUrl();
1349
1349
  const webUrl = getWebUrl();
1350
1350
  const safeJson = (v: unknown) =>
1351
1351
  JSON.stringify(v).replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
1352
1352
  const configJson = safeJson(
1353
- buildWebInterfaceConfig({ webUrl, platformUrl, disablePlatform }),
1353
+ buildWebInterfaceConfig({ webUrl, cloudUrl, disableCloud }),
1354
1354
  );
1355
1355
  const hasOverrides = Object.keys(parsedFlagOverrides).length > 0;
1356
1356
  const flagOverridesSnippet = hasOverrides
@@ -1380,9 +1380,9 @@ async function runWebInterface(
1380
1380
  return Response.redirect(SPA_BASE, 302);
1381
1381
  }
1382
1382
 
1383
- // Loopback auth: the platform redirects here after login with
1383
+ // Loopback auth: the cloud redirects here after login with
1384
1384
  // ?state=...&session_token=... — forward into the SPA, which validates the
1385
- // `state` nonce before registering the token via /__local/platform-session.
1385
+ // `state` nonce before registering the token via /__local/cloud-session.
1386
1386
  if (pathname === "/callback") {
1387
1387
  return Response.redirect(`/account/platform-callback${url.search}`, 302);
1388
1388
  }
@@ -1404,9 +1404,9 @@ async function runWebInterface(
1404
1404
  pathname.startsWith("/_allauth/") ||
1405
1405
  pathname.startsWith("/accounts/")
1406
1406
  ) {
1407
- const target = new URL(pathname + url.search, platformUrl);
1407
+ const target = new URL(pathname + url.search, cloudUrl);
1408
1408
  const headers = new Headers(req.headers);
1409
- headers.set("Host", new URL(platformUrl).host);
1409
+ headers.set("Host", new URL(cloudUrl).host);
1410
1410
  headers.delete("Origin");
1411
1411
  headers.delete("Referer");
1412
1412
 
@@ -1430,7 +1430,7 @@ async function runWebInterface(
1430
1430
  headers.set("X-Session-Token", sessionToken);
1431
1431
  }
1432
1432
  } else if (sessionToken) {
1433
- // allauth / accounts: the platform expects the Django session cookie.
1433
+ // allauth / accounts: the cloud expects the Django session cookie.
1434
1434
  headers.set(
1435
1435
  "Cookie",
1436
1436
  `sessionid=${sessionToken}; __Secure-sessionid=${sessionToken}`,
@@ -1507,7 +1507,7 @@ async function runWebInterface(
1507
1507
  console.log(`Port ${preferredPort} in use; using ${port}.`);
1508
1508
  }
1509
1509
  // 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
1510
+ // the cloud hardcodes in its loopback callback. We bind both loopback
1511
1511
  // families above so `localhost` reaches us whichever one it resolves to.
1512
1512
  const webInterfaceUrl = `http://localhost:${port}${SPA_BASE}`;
1513
1513
  console.log(`Cadet web interface: ${webInterfaceUrl}`);
@@ -1526,11 +1526,11 @@ async function runWebInterface(
1526
1526
  async function runViteDevServer(
1527
1527
  webSourceDir: string,
1528
1528
  flagEnvVars: Record<string, string>,
1529
- disablePlatform: boolean,
1529
+ disableCloud: boolean,
1530
1530
  openInBrowser: boolean,
1531
1531
  webPort: number | undefined,
1532
1532
  ): Promise<void> {
1533
- const platformUrl = getPlatformUrl();
1533
+ const cloudUrl = getCloudUrl();
1534
1534
 
1535
1535
  // Build VITE_CADET_FLAG_* vars so Vite exposes them to the browser bundle.
1536
1536
  const viteFlagVars: Record<string, string> = {};
@@ -1555,11 +1555,11 @@ async function runViteDevServer(
1555
1555
  ...process.env,
1556
1556
  ...flagEnvVars,
1557
1557
  ...viteFlagVars,
1558
- ...(disablePlatform ? { VITE_CADET_DISABLE_PLATFORM: "true" } : {}),
1559
- VITE_PLATFORM_MODE: "false",
1560
- API_PROXY_TARGET: platformUrl,
1558
+ ...(disableCloud ? { VITE_CADET_DISABLE_CLOUD: "true" } : {}),
1559
+ VITE_CLOUD_MODE: "false",
1560
+ API_PROXY_TARGET: cloudUrl,
1561
1561
  CADET_WEB_URL: getWebUrl(),
1562
- CADET_PLATFORM_URL: platformUrl,
1562
+ CADET_CLOUD_URL: cloudUrl,
1563
1563
  PORT: String(port),
1564
1564
  },
1565
1565
  });
@@ -1590,7 +1590,7 @@ async function runViteDevServer(
1590
1590
  * Return a possibly-refreshed bearer token for the TUI's startup auth.
1591
1591
  *
1592
1592
  * Only a STORED guardian token is refreshable: platform session auth
1593
- * (`isManagedCloud(cloud)`) and ephemeral `--token` overrides (whose token won't
1593
+ * (`isCloudHome(cloud)`) and ephemeral `--token` overrides (whose token won't
1594
1594
  * match the store) are left untouched, as is a token that's still fresh. When
1595
1595
  * the stored token has passed its `refreshAfter` (or expiry) and a refresh
1596
1596
  * token is available, refresh once via the concurrency-safe refreshGuardianToken
@@ -1603,7 +1603,7 @@ export async function resolveFreshBearerToken(
1603
1603
  bearerToken: string | undefined,
1604
1604
  cloud: string | undefined,
1605
1605
  ): Promise<string | undefined> {
1606
- if (isManagedCloud(cloud) || !bearerToken || !assistantId) return bearerToken;
1606
+ if (isCloudHome(cloud) || !bearerToken || !assistantId) return bearerToken;
1607
1607
 
1608
1608
  const stored = loadGuardianToken(assistantId);
1609
1609
  // Refresh only the stored token (an ephemeral --token won't match), and only
@@ -1637,19 +1637,19 @@ export const client = Object.assign(
1637
1637
  assistantName: parsedAssistantName,
1638
1638
  workspaceRoot,
1639
1639
  cloud,
1640
- platformToken,
1640
+ cloudToken,
1641
1641
  bearerToken,
1642
1642
  interfaceId,
1643
1643
  flagEnvVars,
1644
1644
  parsedFlagOverrides,
1645
- disablePlatform,
1645
+ disableCloud,
1646
1646
  openBrowser: openInBrowser,
1647
1647
  webPort,
1648
1648
  background,
1649
1649
  } = parseArgs();
1650
1650
 
1651
- if (disablePlatform) {
1652
- process.env.CADET_DISABLE_PLATFORM = "true";
1651
+ if (disableCloud) {
1652
+ process.env.CADET_DISABLE_CLOUD = "true";
1653
1653
  }
1654
1654
 
1655
1655
  if (interfaceId === WEB_INTERFACE_ID) {
@@ -1660,7 +1660,7 @@ export const client = Object.assign(
1660
1660
  await runWebInterface(
1661
1661
  flagEnvVars,
1662
1662
  parsedFlagOverrides,
1663
- disablePlatform,
1663
+ disableCloud,
1664
1664
  openInBrowser,
1665
1665
  webPort,
1666
1666
  );
@@ -1675,23 +1675,23 @@ export const client = Object.assign(
1675
1675
  interfaceId,
1676
1676
  });
1677
1677
 
1678
- const assistantName = await maybeHydratePlatformAssistantName(
1678
+ const assistantName = await maybeHydrateCloudAssistantName(
1679
1679
  assistantId,
1680
1680
  parsedAssistantName,
1681
1681
  cloud,
1682
- platformToken,
1682
+ cloudToken,
1683
1683
  );
1684
1684
 
1685
1685
  // Build pre-constructed request headers merged from auth + client registration.
1686
1686
  // Spreading into every fetch site ensures consistency across REST and SSE endpoints.
1687
1687
  let auth: Record<string, string> | undefined;
1688
- if (isManagedCloud(cloud) && platformToken) {
1689
- const orgId = await fetchOrganizationId(platformToken).catch((err) => {
1688
+ if (isCloudHome(cloud) && cloudToken) {
1689
+ const orgId = await fetchOrganizationId(cloudToken).catch((err) => {
1690
1690
  tuiLog.warn("failed to fetch organization id", { err: String(err) });
1691
1691
  return undefined;
1692
1692
  });
1693
1693
  auth = {
1694
- "X-Session-Token": platformToken,
1694
+ "X-Session-Token": cloudToken,
1695
1695
  ...(orgId ? { "Cadet-Organization-Id": orgId } : {}),
1696
1696
  ...getClientRegistrationHeaders(interfaceId),
1697
1697
  };
@@ -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
+ });