@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,7 +1,18 @@
1
- import { isManagedCloud } from "../lib/managed-cloud.js";
2
- import { existsSync, readFileSync } from "fs";
3
-
4
- import { findAssistantByName } from "../lib/assistant-config.js";
1
+ import { isCloudHome } from "../lib/cloud-values.js";
2
+ import {
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ renameSync,
7
+ unlinkSync,
8
+ } from "fs";
9
+ import { basename, dirname, join } from "path";
10
+
11
+ import { resolveExistingLocalSigningKey } from "@mycadet/local-mode";
12
+ import {
13
+ findAssistantByName,
14
+ saveAssistantEntry,
15
+ } from "../lib/assistant-config.js";
5
16
  import type { AssistantEntry } from "../lib/assistant-config.js";
6
17
  import {
7
18
  bundleFileSizeBytes,
@@ -16,23 +27,37 @@ import {
16
27
  leaseGuardianToken,
17
28
  } from "../lib/guardian-token.js";
18
29
  import {
19
- readPlatformToken,
20
- rollbackPlatformAssistant,
21
- platformRequestSignedUrl,
30
+ generateLocalSigningKey,
31
+ startCes,
32
+ startLocalDaemon,
33
+ startGateway,
34
+ } from "../lib/local.js";
35
+ import { getArchivePath, getMetadataPath } from "../lib/retire-archive.js";
36
+ import { exec } from "../lib/step-runner.js";
37
+ import {
38
+ readCloudToken,
39
+ rollbackCloudAssistant,
40
+ cloudRequestSignedUrl,
22
41
  platformUploadToSignedUrl,
23
42
  platformImportPreflightFromGcs,
24
43
  platformImportBundleFromGcs,
25
44
  platformPollJobStatus,
26
- } from "../lib/platform-client.js";
45
+ } from "../lib/cloud-client.js";
27
46
  import { performDockerRollback } from "../lib/upgrade-lifecycle.js";
28
47
  import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
29
48
 
30
49
  function printUsage(): void {
31
50
  console.log(
32
- "Usage: cadet restore <name> --from <path> [--version <version>] [--dry-run]",
51
+ "Usage: cadet restore <name> [--from <path>] [--version <version>] [--dry-run]",
33
52
  );
34
53
  console.log("");
35
54
  console.log("Restore data from a .vbundle backup into an assistant.");
55
+ console.log(
56
+ "Without --from, recovers a retired assistant from its archive in",
57
+ );
58
+ console.log(
59
+ "$XDG_DATA_HOME/cadet/retired/ and starts it again.",
60
+ );
36
61
  console.log(
37
62
  "With --version, also rolls back to the specified version first.",
38
63
  );
@@ -50,6 +75,7 @@ function printUsage(): void {
50
75
  );
51
76
  console.log("");
52
77
  console.log("Examples:");
78
+ console.log(" cadet restore my-retired-assistant");
53
79
  console.log(" cadet restore my-assistant --from backup.vbundle");
54
80
  console.log(
55
81
  " cadet restore my-assistant --from backup.vbundle --version v1.2.3",
@@ -188,7 +214,7 @@ async function restorePlatform(
188
214
  opts: { version?: string; dryRun: boolean },
189
215
  ): Promise<void> {
190
216
  // Step 1 — Authenticate
191
- const token = readPlatformToken();
217
+ const token = readCloudToken();
192
218
  if (!token) {
193
219
  console.error("Not logged in. Run 'cadet login' first.");
194
220
  process.exit(1);
@@ -198,10 +224,10 @@ async function restorePlatform(
198
224
  // We deliberately omit min/max runtime version here: restore uploads an
199
225
  // arbitrary .vbundle from disk (often produced by a different runtime
200
226
  // than the one we'd query right now), and the bundle's own manifest is
201
- // the authority on its compatibility band. The platform skips the
227
+ // the authority on its compatibility band. The cloud skips the
202
228
  // version gate when these fields are absent and re-derives compat from
203
229
  // the manifest when it processes the import.
204
- const { url: uploadUrl, bundleKey } = await platformRequestSignedUrl(
230
+ const { url: uploadUrl, bundleKey } = await cloudRequestSignedUrl(
205
231
  { operation: "upload" },
206
232
  token,
207
233
  entry.runtimeUrl,
@@ -245,7 +271,7 @@ async function restorePlatform(
245
271
 
246
272
  if (preflightResult.statusCode === 404) {
247
273
  console.error(
248
- "No managed assistant found. Ensure your assistant is running.",
274
+ "No cloud assistant found. Ensure your assistant is running.",
249
275
  );
250
276
  process.exit(1);
251
277
  }
@@ -335,7 +361,7 @@ async function restorePlatform(
335
361
  );
336
362
 
337
363
  try {
338
- await rollbackPlatformAssistant(
364
+ await rollbackCloudAssistant(
339
365
  token,
340
366
  entry.assistantId,
341
367
  opts.version,
@@ -381,7 +407,7 @@ async function restorePlatform(
381
407
 
382
408
  if (importResult.statusCode === 404) {
383
409
  console.error(
384
- "No managed assistant found. Ensure your assistant is running.",
410
+ "No cloud assistant found. Ensure your assistant is running.",
385
411
  );
386
412
  process.exit(1);
387
413
  }
@@ -448,7 +474,7 @@ async function restorePlatform(
448
474
  }
449
475
  }
450
476
  // Transient error (5xx, network) — retry
451
- console.warn(`Polling failed, retrying... (${msg})`);
477
+ console.warn(`Polling failed: ${msg}. Retrying...`);
452
478
  continue;
453
479
  }
454
480
 
@@ -515,6 +541,86 @@ async function restorePlatform(
515
541
  }
516
542
  }
517
543
 
544
+ /**
545
+ * Recover a previously retired local assistant from its archive: extract the
546
+ * archived workspace data back to its original location, restore the lockfile
547
+ * entry, and start the assistant and gateway. Archives live in
548
+ * $XDG_DATA_HOME/cadet/retired/ (default: ~/.local/share/cadet/retired/).
549
+ */
550
+ async function recoverFromArchive(name: string): Promise<void> {
551
+ const archivePath = getArchivePath(name);
552
+ const metadataPath = getMetadataPath(name);
553
+
554
+ // Verify the archive exists
555
+ if (!existsSync(archivePath) || !existsSync(metadataPath)) {
556
+ console.error(`No retired archive found for '${name}'.`);
557
+ process.exit(1);
558
+ }
559
+
560
+ // Read and validate metadata before any side effects
561
+ const entry: AssistantEntry = JSON.parse(readFileSync(metadataPath, "utf-8"));
562
+ if (!entry.resources) {
563
+ throw new Error(
564
+ `Retired assistant '${name}' is missing resource configuration. ` +
565
+ `Fix the archive metadata at ${metadataPath} and retry, ` +
566
+ `or run 'cadet hatch' to re-provision with proper resource allocation.`,
567
+ );
568
+ }
569
+
570
+ // Check that the recovering entry's own target directory is free.
571
+ const target = entry.resources.instanceDir;
572
+ if (existsSync(target)) {
573
+ console.error(
574
+ `Error: ${target} already exists (owned by ${entry.assistantId}). ` +
575
+ `Retire the current assistant first.`,
576
+ );
577
+ process.exit(1);
578
+ }
579
+
580
+ // Extract and rename.
581
+ //
582
+ // retireLocal archives the full instanceDir. The directory is staged under
583
+ // `<archive>.staging` inside the retired dir before being packed with
584
+ // `tar -C <retiredDir> <stagingBasename>`, so the top-level entry inside
585
+ // the tarball is always `<name>.tar.gz.staging`.
586
+ //
587
+ // Correct restoration: extract to retiredDir, then rename the staging entry
588
+ // back to the original target path. Using homedir() as the -C target was
589
+ // wrong for any instance stored outside the home directory.
590
+ const targetDir = entry.resources.instanceDir;
591
+ const retiredDir = dirname(archivePath);
592
+ const extractedPath = join(retiredDir, basename(archivePath) + ".staging");
593
+
594
+ await exec("tar", ["xzf", archivePath, "-C", retiredDir]);
595
+ // Validate the staged identity before occupying the destination or updating
596
+ // the registry. A failed validation leaves the archive available for retry.
597
+ const signingKey = resolveExistingLocalSigningKey(
598
+ entry.resources,
599
+ extractedPath,
600
+ );
601
+ const bootstrapSecret =
602
+ entry.guardianBootstrapSecret ?? generateLocalSigningKey();
603
+ entry.resources = { ...entry.resources, signingKey };
604
+ entry.guardianBootstrapSecret = bootstrapSecret;
605
+ mkdirSync(dirname(targetDir), { recursive: true });
606
+ renameSync(extractedPath, targetDir);
607
+ saveAssistantEntry(entry);
608
+
609
+ // Start CES sibling + daemon + gateway in parallel, the way the
610
+ // Docker topology brings its sibling processes up together. startCes
611
+ // always launches the CES sibling.
612
+ await Promise.all([
613
+ startCes(false, entry.resources),
614
+ startLocalDaemon(false, entry.resources, { signingKey }),
615
+ startGateway(false, entry.resources, { signingKey, bootstrapSecret }),
616
+ ]);
617
+
618
+ unlinkSync(archivePath);
619
+ unlinkSync(metadataPath);
620
+
621
+ console.log(`✅ Recovered assistant '${name}'.`);
622
+ }
623
+
518
624
  export const restore = Object.assign(
519
625
  async function restore(): Promise<void> {
520
626
  const { name, fromPath, version, dryRun, help } = parseArgs(process.argv);
@@ -540,13 +646,25 @@ export const restore = Object.assign(
540
646
  process.exit(1);
541
647
  }
542
648
 
543
- if (!name || !fromPath) {
544
- console.error("Error: Both <name> and --from <path> are required.");
649
+ if (!name) {
650
+ console.error("Error: <name> is required.");
545
651
  console.error("");
546
652
  printUsage();
547
653
  process.exit(1);
548
654
  }
549
655
 
656
+ // Without --from: recover a retired assistant from its retire archive.
657
+ if (!fromPath) {
658
+ if (dryRun) {
659
+ console.error(
660
+ "Dry-run is only supported with --from. Recovery replays the retired assistant's archive directly.",
661
+ );
662
+ process.exit(1);
663
+ }
664
+ await recoverFromArchive(name);
665
+ return;
666
+ }
667
+
550
668
  // Look up the instance
551
669
  const entry = findAssistantByName(name);
552
670
  if (!entry) {
@@ -562,7 +680,7 @@ export const restore = Object.assign(
562
680
  }
563
681
 
564
682
  const sizeMB = formatBundleSizeMb(bundleFileSizeBytes(fromPath));
565
- console.log(`Reading ${fromPath} (${sizeMB} MB)...`);
683
+ console.log(`Reading ${fromPath} — ${sizeMB} MB...`);
566
684
 
567
685
  // Detect topology and route platform assistants through Django import
568
686
  const cloud =
@@ -575,7 +693,7 @@ export const restore = Object.assign(
575
693
  process.exit(1);
576
694
  }
577
695
 
578
- if (isManagedCloud(cloud)) {
696
+ if (isCloudHome(cloud)) {
579
697
  const bundleData = readFileSync(fromPath);
580
698
  await restorePlatform(entry, name, bundleData, { version, dryRun });
581
699
  return;
@@ -583,7 +701,7 @@ export const restore = Object.assign(
583
701
 
584
702
  if (version && cloud !== "docker") {
585
703
  console.error(
586
- "Restore with --version is only supported for Docker and managed assistants.",
704
+ "Restore with --version is only supported for Docker and cloud assistants.",
587
705
  );
588
706
  process.exit(1);
589
707
  }
@@ -625,7 +743,7 @@ export const restore = Object.assign(
625
743
  }
626
744
  }
627
745
  },
628
- { description: "Restore data (and optionally version) from a .vbundle backup" },
746
+ { description: "Restore a .vbundle backup, or recover a retired assistant" },
629
747
  );
630
748
 
631
749
 
@@ -1,4 +1,4 @@
1
- import { isManagedCloud } from "../lib/managed-cloud.js";
1
+ import { isCloudHome } from "../lib/cloud-values.js";
2
2
  import { existsSync, unlinkSync } from "fs";
3
3
  import { join } from "path";
4
4
 
@@ -24,10 +24,9 @@ import { printInstanceDetails } from "../lib/instance-details.js";
24
24
  import {
25
25
  authHeaders,
26
26
  authHeadersForKnownOrganization,
27
- getPlatformUrl,
28
- readGatewayCredential,
29
- readPlatformToken,
30
- } from "../lib/platform-client.js";
27
+ getCloudUrl,
28
+ readCloudToken,
29
+ } from "../lib/cloud-client.js";
31
30
  import { retireDocker, dockerResourceNames } from "../lib/docker.js";
32
31
  import { retireInstance as retireAwsInstance } from "../lib/aws.js";
33
32
  import { retireInstance as retireGcpInstance } from "../lib/gcp.js";
@@ -51,44 +50,15 @@ interface RetireArgs {
51
50
  yes: boolean;
52
51
  }
53
52
 
54
- interface SelfHostedPlatformRegistration {
55
- platformAssistantId: string;
56
- platformBaseUrl: string | null;
57
- organizationId: string | null;
58
- }
59
-
60
- const PLATFORM_TOKEN_ENV = "CADET_PLATFORM_TOKEN";
53
+ const CLOUD_TOKEN_ENV = "CADET_CLOUD_TOKEN";
54
+ const LEGACY_CLOUD_TOKEN_ENV = "CADET_PLATFORM_TOKEN";
61
55
 
62
56
  function readPlatformRetireToken(): string | null {
63
- const envToken = process.env[PLATFORM_TOKEN_ENV]?.trim();
57
+ const envToken = (
58
+ process.env[CLOUD_TOKEN_ENV] ?? process.env[LEGACY_CLOUD_TOKEN_ENV]
59
+ )?.trim();
64
60
  if (envToken) return envToken;
65
- return readPlatformToken();
66
- }
67
-
68
- function platformOrigin(platformUrl: string): string | null {
69
- try {
70
- return new URL(platformUrl).origin;
71
- } catch {
72
- return null;
73
- }
74
- }
75
-
76
- function resolveTrustedSelfHostedPlatformUrl(
77
- storedPlatformUrl: string | null,
78
- ): string | null {
79
- const configuredPlatformUrl = getPlatformUrl();
80
- if (!storedPlatformUrl) return configuredPlatformUrl;
81
-
82
- const storedOrigin = platformOrigin(storedPlatformUrl);
83
- const configuredOrigin = platformOrigin(configuredPlatformUrl);
84
- if (storedOrigin && configuredOrigin && storedOrigin === configuredOrigin) {
85
- return configuredPlatformUrl;
86
- }
87
-
88
- console.warn(
89
- "⚠️ Stored platform URL does not match the configured platform URL; skipping platform unregister.",
90
- );
91
- return null;
61
+ return readCloudToken();
92
62
  }
93
63
 
94
64
  function nonEmptyString(value: string | null | undefined): string | null {
@@ -96,35 +66,22 @@ function nonEmptyString(value: string | null | undefined): string | null {
96
66
  return trimmed ? trimmed : null;
97
67
  }
98
68
 
99
- function lockfileSelfHostedPlatformRegistration(
100
- entry: AssistantEntry,
101
- ): SelfHostedPlatformRegistration | null {
102
- const platformAssistantId = nonEmptyString(entry.platformAssistantId);
103
- if (!platformAssistantId) return null;
104
-
105
- return {
106
- platformAssistantId,
107
- platformBaseUrl: nonEmptyString(entry.platformBaseUrl),
108
- organizationId: nonEmptyString(entry.platformOrganizationId),
109
- };
110
- }
111
-
112
- async function deletePlatformAssistant(
69
+ async function deleteCloudAssistant(
113
70
  assistantId: string,
114
71
  options: {
115
72
  token: string;
116
- platformUrl?: string;
73
+ cloudUrl?: string;
117
74
  organizationId?: string;
118
75
  label: string;
119
76
  successMessage: string;
120
77
  alreadyGoneMessage: string;
121
78
  },
122
79
  ): Promise<void> {
123
- const platformUrl = options.platformUrl || getPlatformUrl();
124
- const url = `${platformUrl}/v1/assistants/${encodeURIComponent(assistantId)}/retire/`;
80
+ const cloudUrl = options.cloudUrl || getCloudUrl();
81
+ const url = `${cloudUrl}/v1/assistants/${encodeURIComponent(assistantId)}/retire/`;
125
82
  const headers = options.organizationId
126
83
  ? authHeadersForKnownOrganization(options.token, options.organizationId)
127
- : await authHeaders(options.token, platformUrl);
84
+ : await authHeaders(options.token, cloudUrl);
128
85
 
129
86
  const response = await loopbackSafeFetch(url, {
130
87
  method: "DELETE",
@@ -143,85 +100,23 @@ async function deletePlatformAssistant(
143
100
  }
144
101
  }
145
102
 
146
- async function readSelfHostedPlatformRegistration(
147
- entry: AssistantEntry,
148
- ): Promise<SelfHostedPlatformRegistration | null> {
149
- const fallback = lockfileSelfHostedPlatformRegistration(entry);
150
- const gatewayUrl = entry.localUrl ?? entry.runtimeUrl;
151
- const platformAssistant = await readGatewayCredential(
152
- gatewayUrl,
153
- "cadet:platform_assistant_id",
154
- entry.bearerToken,
155
- );
156
- if (platformAssistant.unreachable) {
157
- if (fallback) {
158
- console.warn(
159
- "\u26a0\ufe0f Could not read platform registration from the local assistant; using saved platform registration.",
160
- );
161
- return fallback;
162
- }
163
- console.warn(
164
- "\u26a0\ufe0f Could not read platform registration from the local assistant and no saved platform registration is available; skipping platform unregister.",
165
- );
166
- return null;
167
- }
168
-
169
- const platformAssistantId =
170
- nonEmptyString(platformAssistant.value) ?? fallback?.platformAssistantId;
171
- if (!platformAssistantId) return null;
172
-
173
- const [platformBaseUrl, organizationId] = await Promise.all([
174
- readGatewayCredential(
175
- gatewayUrl,
176
- "cadet:platform_base_url",
177
- entry.bearerToken,
178
- ),
179
- readGatewayCredential(
180
- gatewayUrl,
181
- "cadet:platform_organization_id",
182
- entry.bearerToken,
183
- ),
184
- ]);
185
-
186
- return {
187
- platformAssistantId,
188
- platformBaseUrl:
189
- nonEmptyString(platformBaseUrl.value) ??
190
- fallback?.platformBaseUrl ??
191
- null,
192
- organizationId:
193
- nonEmptyString(organizationId.value) ?? fallback?.organizationId ?? null,
194
- };
195
- }
196
-
197
103
  async function unregisterSelfHostedLocalPlatformRecord(
198
104
  entry: AssistantEntry,
199
105
  ): Promise<void> {
200
106
  const token = readPlatformRetireToken();
201
107
  if (!token) return;
202
108
 
203
- const registration = await readSelfHostedPlatformRegistration(entry);
204
- if (!registration) return;
205
-
206
- const platformUrl = resolveTrustedSelfHostedPlatformUrl(
207
- registration.platformBaseUrl,
208
- );
209
- if (!platformUrl) return;
210
- if (!registration.organizationId) {
211
- console.warn(
212
- "⚠️ Could not read platform organization ID from the local assistant; skipping platform unregister.",
213
- );
214
- return;
215
- }
109
+ const cloudAssistantId = nonEmptyString(entry.cloudAssistantId);
110
+ if (!cloudAssistantId) return;
216
111
 
217
- await deletePlatformAssistant(registration.platformAssistantId, {
112
+ await deleteCloudAssistant(cloudAssistantId, {
218
113
  token,
219
- platformUrl,
220
- organizationId: registration.organizationId,
114
+ cloudUrl: getCloudUrl(),
115
+ organizationId: nonEmptyString(entry.cloudOrganizationId) ?? undefined,
221
116
  label: "Platform self-hosted unregister",
222
- successMessage: "\u2705 Platform self-hosted registration unregistered.",
117
+ successMessage: "✅ Platform self-hosted registration unregistered.",
223
118
  alreadyGoneMessage:
224
- "\u2705 Platform self-hosted registration already unregistered (404).",
119
+ "✅ Platform self-hosted registration already unregistered (404).",
225
120
  });
226
121
  }
227
122
 
@@ -275,9 +170,9 @@ async function retireCadet(
275
170
  }
276
171
 
277
172
  try {
278
- await deletePlatformAssistant(assistantId, {
173
+ await deleteCloudAssistant(assistantId, {
279
174
  token,
280
- platformUrl: runtimeUrl,
175
+ cloudUrl: runtimeUrl,
281
176
  label: "Platform retire",
282
177
  successMessage: "\u2705 Platform-hosted instance retired.",
283
178
  alreadyGoneMessage:
@@ -324,8 +219,8 @@ function printRetireTarget(entry: AssistantEntry, cloud: string): void {
324
219
  : {}),
325
220
  ...(entry.cloud === "docker"
326
221
  ? {
327
- container:
328
- dockerResourceNames(entry.assistantId).assistantContainer,
222
+ container: dockerResourceNames(entry.assistantId)
223
+ .assistantContainer,
329
224
  }
330
225
  : {}),
331
226
  },
@@ -366,37 +261,38 @@ function teeConsoleToLogFile(fd: number | "ignore"): void {
366
261
 
367
262
  export const retire = Object.assign(
368
263
  async function retire(): Promise<void> {
369
- if (process.env.CADET_DESKTOP_APP) {
370
- resetLogFile("retire.log");
371
- }
372
- const logFd = process.env.CADET_DESKTOP_APP
373
- ? openLogFile("retire.log")
374
- : "ignore";
375
- teeConsoleToLogFile(logFd);
264
+ if (process.env.CADET_DESKTOP_APP) {
265
+ resetLogFile("retire.log");
266
+ }
267
+ const logFd = process.env.CADET_DESKTOP_APP
268
+ ? openLogFile("retire.log")
269
+ : "ignore";
270
+ teeConsoleToLogFile(logFd);
376
271
 
377
- try {
378
- await retireInner();
379
- } finally {
380
- closeLogFile(logFd);
381
- }
382
- },
272
+ try {
273
+ await retireInner();
274
+ } finally {
275
+ closeLogFile(logFd);
276
+ }
277
+ },
383
278
  { description: "Delete an assistant instance" },
384
279
  );
385
280
 
386
-
387
281
  async function retireInner(): Promise<void> {
388
282
  const args = process.argv.slice(3);
389
283
  if (args.includes("--help") || args.includes("-h")) {
390
- console.log("Usage: cadet retire [<name-or-id>] [--source <source>] [--yes]");
284
+ console.log(
285
+ "Usage: cadet retire [<name-or-id>] [--source <source>] [--yes]",
286
+ );
391
287
  console.log("");
392
288
  console.log("Delete an assistant instance and archive its data.");
393
289
  console.log(
394
- "With no name or ID, retires the active assistant (see `cadet use`). By default, retire prints the assistant name, ID, cloud, and runtime before asking for confirmation.",
290
+ "With no name or ID, retires the active assistant — see `cadet use`. By default, retire prints the assistant name, ID, cloud, and runtime before asking for confirmation.",
395
291
  );
396
292
  console.log("");
397
293
  console.log("Arguments:");
398
294
  console.log(
399
- " <name-or-id> Assistant display name or ID to retire (defaults to the active assistant)",
295
+ " <name-or-id> Assistant display name or ID to retire, defaults to the active assistant",
400
296
  );
401
297
  console.log("");
402
298
  console.log("Options:");
@@ -415,7 +311,9 @@ async function retireInner(): Promise<void> {
415
311
  console.error(
416
312
  "Error: No assistant specified and no active assistant. Pass a name or ID, or pick one with `cadet use <name>`.",
417
313
  );
418
- console.error("Usage: cadet retire <name-or-id> [--source <source>] [--yes]");
314
+ console.error(
315
+ "Usage: cadet retire <name-or-id> [--source <source>] [--yes]",
316
+ );
419
317
  process.exit(1);
420
318
  }
421
319
 
@@ -498,7 +396,7 @@ async function retireInner(): Promise<void> {
498
396
  await retireLocal(assistantId, entry);
499
397
  } else if (cloud === "custom") {
500
398
  await retireCustom(entry);
501
- } else if (isManagedCloud(cloud)) {
399
+ } else if (isCloudHome(cloud)) {
502
400
  await retireCadet(assistantId, entry.runtimeUrl);
503
401
  } else {
504
402
  console.error(`Error: Unknown cloud type '${cloud}'.`);