@mycadet/cli 0.12.7 → 0.12.8

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 (165) 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-platform-login.ts → show-cloud-login.ts} +3 -5
  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 +1 -1
  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 +1 -1
  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 +1 -1
  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} +5 -5
  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__/guardian-token.test.ts +149 -56
  72. package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
  73. package/src/__tests__/helpers/guardian-token.ts +22 -0
  74. package/src/__tests__/helpers/http.ts +10 -0
  75. package/src/__tests__/nginx-ingress.test.ts +18 -18
  76. package/src/__tests__/orphan-detection.test.ts +1 -1
  77. package/src/__tests__/pair.test.ts +1 -8
  78. package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -26
  79. package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
  80. package/src/__tests__/retire.test.ts +58 -199
  81. package/src/__tests__/root-process-lockfile.test.ts +99 -0
  82. package/src/__tests__/sleep.test.ts +19 -19
  83. package/src/__tests__/statefulset.test.ts +8 -8
  84. package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +1 -1
  85. package/src/__tests__/teleport-backup.test.ts +3 -3
  86. package/src/__tests__/teleport.test.ts +180 -196
  87. package/src/__tests__/tunnel.test.ts +255 -227
  88. package/src/__tests__/uninstall.test.ts +13 -4
  89. package/src/__tests__/upgrade-local.test.ts +3 -3
  90. package/src/__tests__/upgrade-replay-env.test.ts +12 -12
  91. package/src/__tests__/wake.test.ts +258 -41
  92. package/src/__tests__/workos-pkce.test.ts +2 -2
  93. package/src/commands/backup.ts +22 -22
  94. package/src/commands/client.ts +76 -76
  95. package/src/commands/debug.ts +20 -20
  96. package/src/commands/exec.ts +5 -5
  97. package/src/commands/hatch.ts +22 -22
  98. package/src/commands/login.ts +22 -22
  99. package/src/commands/logs.ts +1 -1
  100. package/src/commands/ps.ts +188 -79
  101. package/src/commands/restart.ts +30 -0
  102. package/src/commands/restore.ts +138 -20
  103. package/src/commands/retire.ts +46 -148
  104. package/src/commands/rollback.ts +18 -24
  105. package/src/commands/setup.ts +2 -2
  106. package/src/commands/teleport.ts +93 -93
  107. package/src/commands/terminal.ts +157 -17
  108. package/src/commands/tunnel.ts +80 -122
  109. package/src/commands/uninstall.ts +55 -7
  110. package/src/commands/unpair.ts +3 -3
  111. package/src/commands/upgrade.ts +22 -46
  112. package/src/commands/wake.ts +162 -46
  113. package/src/index.ts +3 -9
  114. package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
  115. package/src/lib/__tests__/job-polling.test.ts +1 -1
  116. package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
  117. package/src/lib/__tests__/runtime-url.test.ts +1 -1
  118. package/src/lib/assistant-api-key-resolution.ts +5 -5
  119. package/src/lib/assistant-client.ts +8 -8
  120. package/src/lib/assistant-config.ts +58 -12
  121. package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
  122. package/src/lib/cloud-login.ts +24 -24
  123. package/src/lib/{platform-releases.ts → cloud-releases.ts} +5 -5
  124. package/src/lib/cloud-values.ts +3 -0
  125. package/src/lib/config-utils.ts +1 -1
  126. package/src/lib/detached-process.ts +1 -1
  127. package/src/lib/docker.ts +111 -10
  128. package/src/lib/environments/__tests__/paths.test.ts +2 -2
  129. package/src/lib/environments/__tests__/resolve.test.ts +26 -26
  130. package/src/lib/environments/resolve.ts +8 -8
  131. package/src/lib/guardian-token.ts +36 -4
  132. package/src/lib/hatch-local.ts +19 -23
  133. package/src/lib/health-check.ts +6 -6
  134. package/src/lib/ingress-config.ts +8 -35
  135. package/src/lib/job-polling.ts +1 -1
  136. package/src/lib/live-voice/connection.ts +12 -12
  137. package/src/lib/live-voice/tunnel.test.ts +11 -11
  138. package/src/lib/live-voice/tunnel.ts +15 -15
  139. package/src/lib/local-runtime-client.ts +20 -20
  140. package/src/lib/local.ts +9 -30
  141. package/src/lib/nginx-ingress.ts +41 -14
  142. package/src/lib/process.ts +1 -1
  143. package/src/lib/provider-secrets.ts +3 -3
  144. package/src/lib/retire-apple-container.ts +1 -1
  145. package/src/lib/runtime-url.ts +8 -8
  146. package/src/lib/statefulset.ts +2 -2
  147. package/src/lib/sync-cloud-assistants.ts +18 -18
  148. package/src/lib/teleport-backup.ts +4 -4
  149. package/src/lib/terminal-client.ts +17 -17
  150. package/src/lib/terminal-session.ts +24 -24
  151. package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
  152. package/src/lib/tunnels/cadet.ts +29 -0
  153. package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +15 -4
  154. package/src/lib/tunnels/gateway-notify.ts +61 -0
  155. package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +201 -30
  156. package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +19 -5
  157. package/src/lib/upgrade-lifecycle.ts +3 -17
  158. package/src/lib/upgrade-preflight.ts +2 -2
  159. package/src/lib/workos-pkce.ts +5 -5
  160. package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
  161. package/src/commands/recover.ts +0 -131
  162. package/src/commands/roadmap.ts +0 -459
  163. package/src/commands/ssh.ts +0 -144
  164. package/src/commands/workflows.ts +0 -305
  165. 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
  }
@@ -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) {
@@ -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,28 +261,29 @@ 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(
@@ -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}'.`);
@@ -1,4 +1,4 @@
1
- import { isManagedCloud } from "../lib/managed-cloud.js";
1
+ import { isCloudHome } from "../lib/cloud-values.js";
2
2
  import {
3
3
  findAssistantByName,
4
4
  getActiveAssistant,
@@ -7,7 +7,6 @@ import {
7
7
  type AssistantEntry,
8
8
  } from "../lib/assistant-config";
9
9
  import {
10
- captureImageRefs,
11
10
  GATEWAY_INTERNAL_PORT,
12
11
  ASSISTANT_INTERNAL_PORT,
13
12
  dockerResourceNames,
@@ -17,9 +16,9 @@ import {
17
16
  import type { ServiceName } from "../lib/docker";
18
17
  import { emitCliError, categorizeUpgradeError } from "../lib/cli-error.js";
19
18
  import {
20
- readPlatformToken,
21
- rollbackPlatformAssistant,
22
- } from "../lib/platform-client.js";
19
+ readCloudToken,
20
+ rollbackCloudAssistant,
21
+ } from "../lib/cloud-client.js";
23
22
  import {
24
23
  broadcastUpgradeEvent,
25
24
  buildCompleteEvent,
@@ -47,7 +46,7 @@ function parseArgs(): { name: string | null; version: string | null } {
47
46
  console.log("Usage: cadet rollback [<name>] [--version <version>]");
48
47
  console.log("");
49
48
  console.log(
50
- "Roll back a Docker or managed assistant to a previous version.",
49
+ "Roll back a Docker or cloud assistant to a previous version.",
51
50
  );
52
51
  console.log("");
53
52
  console.log("Arguments:");
@@ -136,7 +135,7 @@ async function rollbackPlatformViaEndpoint(
136
135
  version?: string,
137
136
  ): Promise<void> {
138
137
  // Step 1 — Authenticate
139
- const token = readPlatformToken();
138
+ const token = readCloudToken();
140
139
  if (!token) {
141
140
  const msg =
142
141
  "Error: Not logged in. Run `cadet login --token <token>` first.";
@@ -157,7 +156,7 @@ async function rollbackPlatformViaEndpoint(
157
156
 
158
157
  let result: { detail: string; version: string | null };
159
158
  try {
160
- result = await rollbackPlatformAssistant(
159
+ result = await rollbackCloudAssistant(
161
160
  token,
162
161
  entry.assistantId,
163
162
  version,
@@ -225,14 +224,14 @@ export const rollback = Object.assign(
225
224
  }
226
225
 
227
226
  // ---------- Managed (Cadet Cloud) rollback ----------
228
- if (isManagedCloud(cloud)) {
227
+ if (isCloudHome(cloud)) {
229
228
  await rollbackPlatformViaEndpoint(entry, version ?? undefined);
230
229
  return;
231
230
  }
232
231
 
233
232
  // ---------- Unsupported topologies ----------
234
233
  if (cloud !== "docker") {
235
- const msg = "Rollback is only supported for Docker and managed assistants.";
234
+ const msg = "Rollback is only supported for Docker and cloud assistants.";
236
235
  console.error(msg);
237
236
  emitCliError("UNSUPPORTED_TOPOLOGY", msg);
238
237
  process.exit(1);
@@ -273,21 +272,23 @@ export const rollback = Object.assign(
273
272
  process.exit(1);
274
273
  }
275
274
 
276
- // Verify all three digest fields are present
275
+ // Roll back to the previous images. Older entries recorded per-service
276
+ // digests (the image sha captured at upgrade time) — those are gone from
277
+ // the contract now; the recorded image refs are the rollback target.
277
278
  const prev = entry.previousContainerInfo;
278
- if (!prev.assistantDigest || !prev.gatewayDigest || !prev.cesDigest) {
279
+ if (!prev.assistantImage || !prev.gatewayImage || !prev.cesImage) {
279
280
  const msg =
280
- "Incomplete rollback state. Previous container digests are missing.";
281
+ "Incomplete rollback state. Previous container image refs are missing.";
281
282
  console.error(msg);
282
283
  emitCliError("ROLLBACK_NO_STATE", msg);
283
284
  process.exit(1);
284
285
  }
285
286
 
286
- // Build image refs from the previous digests
287
+ // Build image refs from the previous record
287
288
  const previousImageRefs: Record<ServiceName, string> = {
288
- assistant: prev.assistantDigest,
289
- "credential-executor": prev.cesDigest,
290
- gateway: prev.gatewayDigest,
289
+ assistant: prev.assistantImage,
290
+ "credential-executor": prev.cesImage,
291
+ gateway: prev.gatewayImage,
291
292
  };
292
293
 
293
294
  const instanceName = entry.assistantId;
@@ -398,9 +399,6 @@ export const rollback = Object.assign(
398
399
  const ready = await waitForReady(entry.runtimeUrl);
399
400
 
400
401
  if (ready) {
401
- // Capture new digests from the rolled-back containers
402
- const newDigests = await captureImageRefs(res);
403
-
404
402
  // Swap current/previous state to enable "rollback the rollback"
405
403
  const updatedEntry: AssistantEntry = {
406
404
  ...entry,
@@ -408,10 +406,6 @@ export const rollback = Object.assign(
408
406
  assistantImage: prev.assistantImage ?? previousImageRefs.assistant,
409
407
  gatewayImage: prev.gatewayImage ?? previousImageRefs.gateway,
410
408
  cesImage: prev.cesImage ?? previousImageRefs["credential-executor"],
411
- assistantDigest: newDigests?.assistant,
412
- gatewayDigest: newDigests?.gateway,
413
- cesDigest: newDigests?.["credential-executor"],
414
- networkName: res.network,
415
409
  assistantPort,
416
410
  },
417
411
  previousContainerInfo: entry.containerInfo,
@@ -48,7 +48,7 @@ function parseDefaultProviderResponse(value: unknown): DefaultProviderResponse {
48
48
  * Ask the active assistant which provider it routes by default
49
49
  * (`GET /v1/config/llm/default-provider`), so `cadet setup` without
50
50
  * `--provider` configures the connection the assistant actually uses —
51
- * e.g. the keyless Cadet Cloud managed connection picked at hatch —
51
+ * e.g. the keyless Cadet Cloud cloud connection picked at hatch —
52
52
  * instead of blindly prompting for an API key. Falls back to cadet
53
53
  * when the runtime is unreachable or predates the endpoint; the
54
54
  * returned warning says why, for the caller to render.
@@ -184,7 +184,7 @@ export const setup = Object.assign(
184
184
 
185
185
  // Respect the assistant's configured default provider (e.g. Cadet Cloud
186
186
  // chosen at hatch) unless the caller named one explicitly — the runtime's
187
- // keyless managed connection should not be greeted by an API-key prompt.
187
+ // keyless cloud connection should not be greeted by an API-key prompt.
188
188
  let provider = parsed.provider;
189
189
  if (!provider) {
190
190
  const resolved = await resolveDefaultProvider(gatewayUrl, bearerToken);