@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,6 @@
1
1
  import { resolveExistingLocalSigningKey } from "@mycadet/local-mode";
2
- import { isManagedCloud } from "../lib/managed-cloud.js";
2
+ import { isCloudHome } from "../lib/cloud-values.js";
3
3
  import { spawnSync } from "child_process";
4
- import { writeFileSync } from "fs";
5
4
  import { join } from "path";
6
5
 
7
6
  import cliPkg from "../../package.json";
@@ -20,6 +19,7 @@ import {
20
19
  GATEWAY_INTERNAL_PORT,
21
20
  ASSISTANT_INTERNAL_PORT,
22
21
  dockerResourceNames,
22
+ pullImage,
23
23
  startContainers,
24
24
  stopContainers,
25
25
  } from "../lib/docker";
@@ -27,14 +27,14 @@ import {
27
27
  fetchLatestStableVersion,
28
28
  fetchReleases,
29
29
  resolveImageRefs,
30
- } from "../lib/platform-releases";
30
+ } from "../lib/cloud-releases";
31
31
  import {
32
32
  authHeaders,
33
33
  fetchAssistantDetail,
34
34
  fetchUpgradeInProgress,
35
- getPlatformUrl,
36
- readPlatformToken,
37
- } from "../lib/platform-client";
35
+ getCloudUrl,
36
+ readCloudToken,
37
+ } from "../lib/cloud-client";
38
38
  import { checkManagedHealth } from "../lib/health-check.js";
39
39
  import {
40
40
  evaluateUpgradePoll,
@@ -47,7 +47,6 @@ import {
47
47
  } from "../lib/backup-ops.js";
48
48
  import { emitCliError, categorizeUpgradeError } from "../lib/cli-error.js";
49
49
  import { parseAssistantTargetArg } from "../lib/assistant-target-args.js";
50
- import { exec } from "../lib/step-runner.js";
51
50
  import {
52
51
  broadcastUpgradeEvent,
53
52
  attemptFailedStateRestore,
@@ -77,7 +76,7 @@ import {
77
76
  startLocalDaemon,
78
77
  stopLocalProcesses,
79
78
  } from "../lib/local.js";
80
- import { restoreTunnelEdgeAndAutoTunnel } from "../lib/tunnel-edge.js";
79
+ import { restoreTunnelEdgeAndAutoTunnel } from "../lib/tunnel.js";
81
80
  import {
82
81
  leaseGuardianToken,
83
82
  resetGuardianBootstrap,
@@ -119,12 +118,12 @@ function parseArgs(): UpgradeArgs {
119
118
  console.log("");
120
119
  console.log("Arguments:");
121
120
  console.log(
122
- " <name> Name of the assistant to upgrade (default: active or only assistant)",
121
+ " <name> Name of the assistant to upgrade, defaults to active or only assistant",
123
122
  );
124
123
  console.log("");
125
124
  console.log("Options:");
126
125
  console.log(
127
- " --version <version> Target version to upgrade to (default: CLI version)",
126
+ " --version <version> Target version to upgrade to, defaults to the CLI version",
128
127
  );
129
128
  console.log(
130
129
  " --latest Upgrade to the latest stable release, updating the CLI first if needed",
@@ -317,7 +316,7 @@ async function upgradeDocker(
317
316
  );
318
317
  return;
319
318
  }
320
- console.log(`🔁 Reinstalling ${versionTag} (--force)...\n`);
319
+ console.log(`🔁 Reinstalling ${versionTag} with --force...\n`);
321
320
  }
322
321
 
323
322
  console.log("🔍 Resolving image references...");
@@ -407,7 +406,7 @@ async function upgradeDocker(
407
406
  try {
408
407
  for (const [service, image] of pullImages) {
409
408
  console.log(` Pulling ${service}: ${image}`);
410
- await exec("docker", ["pull", image]);
409
+ await pullImage(image);
411
410
  }
412
411
  } catch (pullErr) {
413
412
  const detail = pullErr instanceof Error ? pullErr.message : String(pullErr);
@@ -454,7 +453,7 @@ async function upgradeDocker(
454
453
  // Clean up old pre-upgrade backups, keep last 3
455
454
  pruneOldBackups(entry.assistantId, 3);
456
455
  } else {
457
- console.warn("⚠️ Pre-upgrade backup failed (continuing with upgrade)\n");
456
+ console.warn("⚠️ Pre-upgrade backup failed, continuing with upgrade\n");
458
457
  }
459
458
 
460
459
  // Persist the backup path so `cadet rollback` can restore the exact backup
@@ -502,17 +501,12 @@ async function upgradeDocker(
502
501
  const ready = await waitForReady(entry.runtimeUrl);
503
502
  if (ready) {
504
503
  // Update lockfile with new service group topology
505
- const newDigests = await captureImageRefs(res);
506
504
  const updatedEntry: AssistantEntry = {
507
505
  ...entry,
508
506
  containerInfo: {
509
507
  assistantImage: imageTags.assistant,
510
508
  gatewayImage: imageTags.gateway,
511
509
  cesImage: imageTags["credential-executor"],
512
- assistantDigest: newDigests?.assistant,
513
- gatewayDigest: newDigests?.gateway,
514
- cesDigest: newDigests?.["credential-executor"],
515
- networkName: res.network,
516
510
  assistantPort,
517
511
  },
518
512
  previousContainerInfo: entry.containerInfo,
@@ -668,13 +662,9 @@ async function upgradeDocker(
668
662
  );
669
663
  }
670
664
 
671
- // Capture fresh digests from the now-running rolled-back containers.
672
- const rollbackDigests = await captureImageRefs(res);
673
-
674
665
  // Restore previous container info in lockfile after rollback.
675
- // The *Image fields hold human-readable image:tag names from the
676
- // pre-upgrade containerInfo; *Digest fields get fresh values from
677
- // the running containers (or fall back to previousImageRefs).
666
+ // The recorded image refs are the rollback record now (digests are
667
+ // gone from the contract).
678
668
  const rolledBackEntry: AssistantEntry = {
679
669
  ...entry,
680
670
  containerInfo: {
@@ -686,14 +676,6 @@ async function upgradeDocker(
686
676
  cesImage:
687
677
  entry.containerInfo?.cesImage ??
688
678
  previousImageRefs["credential-executor"],
689
- assistantDigest:
690
- rollbackDigests?.assistant ?? previousImageRefs.assistant,
691
- gatewayDigest:
692
- rollbackDigests?.gateway ?? previousImageRefs.gateway,
693
- cesDigest:
694
- rollbackDigests?.["credential-executor"] ??
695
- previousImageRefs["credential-executor"],
696
- networkName: res.network,
697
679
  assistantPort,
698
680
  },
699
681
  previousContainerInfo: undefined,
@@ -909,7 +891,7 @@ async function upgradeLocal(
909
891
  );
910
892
  return;
911
893
  }
912
- console.log(`🔁 Reinstalling ${targetVersion} (--force)...\n`);
894
+ console.log(`🔁 Reinstalling ${targetVersion} with --force...\n`);
913
895
  }
914
896
 
915
897
  console.log(
@@ -951,7 +933,7 @@ async function upgradeLocal(
951
933
  console.log(` Backup saved: ${backupPath}\n`);
952
934
  pruneOldBackups(entry.assistantId, 3);
953
935
  } else {
954
- console.warn("⚠️ Pre-upgrade backup failed (continuing with upgrade)\n");
936
+ console.warn("⚠️ Pre-upgrade backup failed, continuing with upgrade\n");
955
937
  }
956
938
 
957
939
  let bootstrapSecret = entry.guardianBootstrapSecret;
@@ -1035,17 +1017,11 @@ async function upgradeLocal(
1035
1017
  console.log("✅ Local assistant processes started\n");
1036
1018
 
1037
1019
  const workspaceDir = join(entry.resources.instanceDir, "workspace");
1038
- const ngrokChild = await restoreTunnelEdgeAndAutoTunnel(
1020
+ await restoreTunnelEdgeAndAutoTunnel(
1039
1021
  entry.assistantId,
1040
1022
  entry.resources.gatewayPort,
1041
1023
  workspaceDir,
1042
1024
  );
1043
- if (ngrokChild?.pid) {
1044
- writeFileSync(
1045
- join(entry.resources.instanceDir, "ngrok.pid"),
1046
- String(ngrokChild.pid),
1047
- );
1048
- }
1049
1025
 
1050
1026
  console.log("Waiting for assistant to become ready...");
1051
1027
  const ready = await waitForReady(adminUrl);
@@ -1113,7 +1089,7 @@ function sleep(ms: number): Promise<void> {
1113
1089
  }
1114
1090
 
1115
1091
  /**
1116
- * Poll the platform until the upgrade completes, then confirm health.
1092
+ * Poll the cloud until the upgrade completes, then confirm health.
1117
1093
  * Mirrors the web UI's Software Updates flow (poll the DB-backed
1118
1094
  * `current_release_version` rather than the gateway, which bounces while
1119
1095
  * the service group restarts).
@@ -1163,7 +1139,7 @@ async function waitForPlatformUpgrade(
1163
1139
  const healthDeadline = Date.now() + PLATFORM_HEALTH_CONFIRM_TIMEOUT_MS;
1164
1140
  while (Date.now() < healthDeadline) {
1165
1141
  const health = await checkManagedHealth(
1166
- entry.runtimeUrl || getPlatformUrl(),
1142
+ entry.runtimeUrl || getCloudUrl(),
1167
1143
  entry.assistantId,
1168
1144
  );
1169
1145
  if (health.status === "healthy") {
@@ -1187,7 +1163,7 @@ async function waitForPlatformUpgrade(
1187
1163
 
1188
1164
  const timeoutMin = Math.round(timeoutMs / 60_000);
1189
1165
  console.warn(
1190
- `⚠️ Upgrade request was accepted but completion was not confirmed within ${timeoutMin} minutes. The platform may still be working — check \`cadet ps\` or the web settings page.`,
1166
+ `⚠️ Upgrade request was accepted but completion was not confirmed within ${timeoutMin} minutes. The cloud may still be working — check \`cadet ps\` or the web settings page.`,
1191
1167
  );
1192
1168
  }
1193
1169
 
@@ -1197,10 +1173,10 @@ async function upgradePlatform(
1197
1173
  wait: boolean,
1198
1174
  ): Promise<void> {
1199
1175
  console.log(
1200
- `🔄 Upgrading platform-hosted assistant '${entry.assistantId}'...\n`,
1176
+ `🔄 Upgrading cloud-hosted assistant '${entry.assistantId}'...\n`,
1201
1177
  );
1202
1178
 
1203
- const token = readPlatformToken();
1179
+ const token = readCloudToken();
1204
1180
  if (!token) {
1205
1181
  const msg =
1206
1182
  "Error: Not logged in. Run `cadet login --token <token>` first.";
@@ -1214,7 +1190,7 @@ async function upgradePlatform(
1214
1190
  // downgrades before POSTing, and bail if an upgrade is already running.
1215
1191
  const [assistantDetail, health] = await Promise.all([
1216
1192
  fetchAssistantDetail(token, entry.assistantId, entry.runtimeUrl),
1217
- checkManagedHealth(entry.runtimeUrl || getPlatformUrl(), entry.assistantId),
1193
+ checkManagedHealth(entry.runtimeUrl || getCloudUrl(), entry.assistantId),
1218
1194
  ]);
1219
1195
  // Health probe first, DB-backed field as the sleeping-assistant fallback.
1220
1196
  const currentVersion =
@@ -1225,7 +1201,7 @@ async function upgradePlatform(
1225
1201
  // platform may differ from the active lockfile default.
1226
1202
  const releases = await fetchReleases({
1227
1203
  channel: releaseChannel,
1228
- platformUrl: entry.runtimeUrl,
1204
+ cloudUrl: entry.runtimeUrl,
1229
1205
  });
1230
1206
 
1231
1207
  const resolution = resolveUpgradeTarget({
@@ -1276,7 +1252,7 @@ async function upgradePlatform(
1276
1252
 
1277
1253
  const headers = await authHeaders(token, entry.runtimeUrl);
1278
1254
 
1279
- const url = `${entry.runtimeUrl || getPlatformUrl()}/v1/assistants/${encodeURIComponent(entry.assistantId)}/upgrade/`;
1255
+ const url = `${entry.runtimeUrl || getCloudUrl()}/v1/assistants/${encodeURIComponent(entry.assistantId)}/upgrade/`;
1280
1256
  const body: { version?: string } = {};
1281
1257
  if (version) {
1282
1258
  body.version = version;
@@ -1345,11 +1321,11 @@ async function upgradePlatform(
1345
1321
  }
1346
1322
 
1347
1323
  // NOTE: We intentionally do NOT broadcast a "complete" event here.
1348
- // The platform API returning 200 only means "upgrade request accepted" —
1324
+ // The cloud API returning 200 only means "upgrade request accepted" —
1349
1325
  // the service group has not yet restarted with the new version. The
1350
1326
  // completion signal will come from the client's health-check
1351
1327
  // version-change detection (DaemonConnection.swift) once the new
1352
- // version actually appears after the platform restarts the service group.
1328
+ // version actually appears after the cloud restarts the service group.
1353
1329
 
1354
1330
  console.log(`✅ ${result.detail}`);
1355
1331
  if (result.version) {
@@ -1483,7 +1459,7 @@ async function resolveLatestStableTag(): Promise<string> {
1483
1459
  );
1484
1460
  emitCliError(
1485
1461
  "UNKNOWN",
1486
- "Could not determine the latest stable release from the platform API",
1462
+ "Could not determine the latest stable release from the cloud API",
1487
1463
  );
1488
1464
  process.exit(1);
1489
1465
  }
@@ -1560,7 +1536,7 @@ export const upgrade = Object.assign(
1560
1536
  return;
1561
1537
  }
1562
1538
 
1563
- // When --latest is passed, resolve the target from the platform API and
1539
+ // When --latest is passed, resolve the target from the cloud API and
1564
1540
  // self-update the CLI if it's behind. The resolved version is then used
1565
1541
  // as the explicit target for the rest of the upgrade flow.
1566
1542
  let effectiveVersion = version;
@@ -1586,7 +1562,7 @@ export const upgrade = Object.assign(
1586
1562
  return;
1587
1563
  }
1588
1564
 
1589
- if (isManagedCloud(cloud)) {
1565
+ if (isCloudHome(cloud)) {
1590
1566
  await upgradePlatform(entry, effectiveVersion, !noWait);
1591
1567
  return;
1592
1568
  }
@@ -38,7 +38,7 @@ export const use = Object.assign(
38
38
  `Active assistant: ${formatAssistantReference(result.entry)}`,
39
39
  );
40
40
  } else {
41
- console.log(`Active assistant: ${active} (not found in lockfile)`);
41
+ console.log(`Active assistant: ${active} — not found in lockfile`);
42
42
  }
43
43
  } else {
44
44
  console.log("No active assistant set.");
@@ -2,16 +2,23 @@ import {
2
2
  SigningKeyContinuityError,
3
3
  resolveExistingLocalSigningKey,
4
4
  } from "@mycadet/local-mode";
5
- import { writeFileSync } from "fs";
6
5
  import { join } from "path";
7
6
 
8
7
  import {
9
8
  getDaemonPidPath,
9
+ loadAllAssistants,
10
10
  resolveTargetAssistant,
11
11
  saveAssistantEntry,
12
12
  } from "../lib/assistant-config.js";
13
- import { dockerResourceNames, wakeContainers } from "../lib/docker.js";
13
+ import type { AssistantEntry } from "../lib/assistant-config.js";
14
14
  import {
15
+ dockerResourceNames,
16
+ isNoSuchContainerError,
17
+ recreateDockerContainers,
18
+ wakeContainers,
19
+ } from "../lib/docker.js";
20
+ import {
21
+ GuardianLeasePermanentError,
15
22
  leaseGuardianToken,
16
23
  resetGuardianBootstrap,
17
24
  seedGuardianTokenFromSiblingEnv,
@@ -39,22 +46,25 @@ import {
39
46
  DOCKER_GATEWAY_READY_TIMEOUT_MS,
40
47
  restoreContainerTunnelEdge,
41
48
  restoreTunnelEdgeAndAutoTunnel,
42
- } from "../lib/tunnel-edge.js";
49
+ } from "../lib/tunnel.js";
43
50
 
44
51
  export const wake = Object.assign(
45
52
  async function wake(): Promise<void> {
46
53
  const args = process.argv.slice(3);
47
54
  if (args.includes("--help") || args.includes("-h")) {
48
- console.log("Usage: cadet wake [<name>] [options]");
55
+ console.log("Usage: cadet wake [<name> | --all] [options]");
49
56
  console.log("");
50
57
  console.log("Start the assistant and gateway processes.");
51
58
  console.log("");
52
59
  console.log("Arguments:");
53
60
  console.log(
54
- " <name> Name of the assistant to start (default: active or only local)",
61
+ " <name> Name of the assistant to start, defaults to active or only local",
55
62
  );
56
63
  console.log("");
57
64
  console.log("Options:");
65
+ console.log(
66
+ " --all Start every assistant across all remotes instead of one",
67
+ );
58
68
  console.log(
59
69
  " --watch Run assistant and gateway in watch mode (hot reload on source changes)",
60
70
  );
@@ -80,7 +90,122 @@ export const wake = Object.assign(
80
90
  // recovered deliberately via `cadet wake <id> --repair-guardian`.
81
91
  const repairGuardian = args.includes("--repair-guardian");
82
92
  const nameArg = args.find((a) => !a.startsWith("-"));
93
+ const all = args.includes("--all");
94
+
95
+ if (all && nameArg) {
96
+ console.error("Error: --all cannot be combined with an assistant name.");
97
+ process.exit(1);
98
+ }
99
+ if (all && (foreground || repairGuardian)) {
100
+ console.error(
101
+ "Error: --all cannot be combined with --foreground or --repair-guardian.",
102
+ );
103
+ process.exit(1);
104
+ }
105
+
106
+ if (all) {
107
+ await wakeAll({ watch });
108
+ return;
109
+ }
110
+
83
111
  const entry = resolveTargetAssistant(nameArg);
112
+ try {
113
+ await wakeAssistant(entry, { watch, foreground, repairGuardian });
114
+ } catch (err) {
115
+ if (err instanceof WakeExit) {
116
+ console.error(`Error: ${err.message}`);
117
+ process.exit(1);
118
+ }
119
+ throw err;
120
+ }
121
+
122
+ // Foreground blocking is a single-assistant concern (--all refuses the
123
+ // flag), and the docker path already warned that foreground is ignored.
124
+ if (foreground && entry.cloud !== "docker") {
125
+ console.log("Running in the foreground, Ctrl+C to stop...\n");
126
+ // Block forever — the daemon is running with inherited stdio so its
127
+ // output streams to this terminal. When the user hits Ctrl+C, SIGINT
128
+ // propagates to the daemon child and both exit.
129
+ await new Promise<void>((resolve) => {
130
+ process.on("SIGINT", () => {
131
+ console.log("\nShutting down...");
132
+ resolve();
133
+ });
134
+ process.on("SIGTERM", () => resolve());
135
+ });
136
+ }
137
+ },
138
+ { description: "Start the assistant and gateway" },
139
+ );
140
+
141
+ /**
142
+ * Start every assistant this CLI's environment knows about — `--all`. The
143
+ * CLI only ever reaches one environment, so this is the current env's
144
+ * lockfile. Same per-assistant wake as the single path, but one assistant's
145
+ * failure never blocks the others; failures only decide the exit code.
146
+ */
147
+ async function wakeAll(opts: { watch: boolean }): Promise<void> {
148
+ const entries = loadAllAssistants();
149
+ if (entries.length === 0) {
150
+ console.error("Error: no assistants found. Run 'cadet hatch' first.");
151
+ process.exit(1);
152
+ }
153
+
154
+ let failures = 0;
155
+ const seen = new Set<string>();
156
+ for (const entry of entries) {
157
+ if (seen.has(entry.assistantId)) continue;
158
+ seen.add(entry.assistantId);
159
+ if (entry.cloud === "apple-container" || entry.cloud === "paired") {
160
+ console.log(
161
+ `Skipping '${entry.assistantId}' (${entry.cloud}) — its lifecycle is managed elsewhere.`,
162
+ );
163
+ continue;
164
+ }
165
+ try {
166
+ await wakeAssistant(entry, {
167
+ watch: opts.watch,
168
+ foreground: false,
169
+ repairGuardian: false,
170
+ });
171
+ } catch (err) {
172
+ failures++;
173
+ console.error(
174
+ `Error: failed to wake '${entry.assistantId}': ${
175
+ err instanceof Error ? err.message : String(err)
176
+ }`,
177
+ );
178
+ }
179
+ }
180
+ if (failures > 0) {
181
+ process.exit(1);
182
+ }
183
+ }
184
+
185
+ type WakeAssistantOptions = {
186
+ watch: boolean;
187
+ foreground: boolean;
188
+ repairGuardian: boolean;
189
+ };
190
+
191
+ /**
192
+ * Marks a deliberate per-assistant refusal (an unmanageable runtime, missing
193
+ * resources, a daemon that died during startup). The single-assistant path
194
+ * renders it as the same "Error: …" + exit 1 the command always printed;
195
+ * `--all` treats it as one entry's failure and keeps waking the rest.
196
+ */
197
+ class WakeExit extends Error {}
198
+
199
+ /**
200
+ * Start one assistant and its gateway. Throws WakeExit on deliberate
201
+ * refusals so the caller decides between exiting (single path) and
202
+ * continuing to the next entry (`--all`).
203
+ */
204
+ async function wakeAssistant(
205
+ entry: AssistantEntry,
206
+ opts: WakeAssistantOptions,
207
+ ): Promise<void> {
208
+ const { watch, foreground, repairGuardian } = opts;
84
209
 
85
210
  if (entry.cloud === "docker") {
86
211
  if (watch || foreground) {
@@ -88,11 +213,23 @@ export const wake = Object.assign(
88
213
  .filter(Boolean)
89
214
  .join(" and ");
90
215
  console.warn(
91
- `Warning: ${ignored} ignored for Docker instances (not supported).`,
216
+ `Warning: ${ignored} ignored for Docker instances.`,
92
217
  );
93
218
  }
94
219
  const res = dockerResourceNames(entry.assistantId);
95
- await wakeContainers(res);
220
+ try {
221
+ await wakeContainers(res);
222
+ } catch (err) {
223
+ // A migrated install has data in the volumes but containers that were
224
+ // never created (v0.0.10 run feedback: "No such container"). Recreate
225
+ // the service group from the entry's recorded images; anything else
226
+ // is a real wake failure.
227
+ if (!isNoSuchContainerError(err)) throw err;
228
+ console.log(
229
+ `Containers for '${entry.assistantId}' don't exist yet — recreating from the recorded images...`,
230
+ );
231
+ await recreateDockerContainers(entry);
232
+ }
96
233
  console.log("Docker containers started.");
97
234
  await restoreContainerTunnelEdge(entry, DOCKER_GATEWAY_READY_TIMEOUT_MS);
98
235
  console.log("Wake complete.");
@@ -100,31 +237,27 @@ export const wake = Object.assign(
100
237
  }
101
238
 
102
239
  if (entry.cloud === "apple-container") {
103
- console.error(
104
- `Error: '${entry.assistantId}' uses the Apple Containers runtime. Its lifecycle is managed by the macOS app — use the app to start it.`,
240
+ throw new WakeExit(
241
+ `'${entry.assistantId}' uses the Apple Containers runtime. Its lifecycle is managed by the macOS app — use the app to start it.`,
105
242
  );
106
- process.exit(1);
107
243
  }
108
244
 
109
245
  if (entry.cloud === "paired") {
110
- console.error(
111
- `Error: '${entry.assistantId}' is a remote assistant paired from another machine — its lifecycle is managed on its host machine, not here. Use \`cadet client ${entry.assistantId}\` to chat with it.`,
246
+ throw new WakeExit(
247
+ `'${entry.assistantId}' is a remote assistant paired from another machine — its lifecycle is managed on its host machine, not here. Use \`cadet client ${entry.assistantId}\` to chat with it.`,
112
248
  );
113
- process.exit(1);
114
249
  }
115
250
 
116
251
  if (entry.cloud && entry.cloud !== "local") {
117
- console.error(
118
- `Error: 'cadet wake' only works with local and docker assistants. '${entry.assistantId}' is a ${entry.cloud} instance.`,
252
+ throw new WakeExit(
253
+ `'cadet wake' only works with local and docker assistants. '${entry.assistantId}' is a ${entry.cloud} instance.`,
119
254
  );
120
- process.exit(1);
121
255
  }
122
256
 
123
257
  if (!entry.resources) {
124
- console.error(
125
- `Error: Local assistant '${entry.assistantId}' is missing resource configuration. Re-hatch to fix.`,
258
+ throw new WakeExit(
259
+ `Local assistant '${entry.assistantId}' is missing resource configuration. Re-hatch to fix.`,
126
260
  );
127
- process.exit(1);
128
261
  }
129
262
  const resources = entry.resources;
130
263
  let signingKey: string;
@@ -231,7 +364,7 @@ export const wake = Object.assign(
231
364
  daemonMigrationsFailed = daemonState.status === "migration_failed";
232
365
  if (watch) {
233
366
  console.log(
234
- `Assistant running (pid ${daemonState.pid}) — watch mode not available (no source files). Keeping existing process.`,
367
+ `Assistant running (pid ${daemonState.pid}) — watch mode not available, no source files. Keeping existing process.`,
235
368
  );
236
369
  } else if (daemonMigrationsFailed) {
237
370
  console.log(
@@ -275,10 +408,9 @@ export const wake = Object.assign(
275
408
  // first and fail the command instead of reporting a successful wake for
276
409
  // an assistant that is not running.
277
410
  if (!isProcessAlive(pidFile).alive) {
278
- console.error(
279
- `Error: the assistant exited during startup and is not running. Its runtime HTTP port ${resources.daemonPort} may be held by another process (check \`lsof -i :${resources.daemonPort}\`); the daemon log records the startup error it reported.`,
411
+ throw new WakeExit(
412
+ `the assistant exited during startup and is not running. Its runtime HTTP port ${resources.daemonPort} may be held by another process (check \`lsof -i :${resources.daemonPort}\`); the daemon log records the startup error it reported.`,
280
413
  );
281
- process.exit(1);
282
414
  }
283
415
  // startLocalDaemon's post-spawn wait is bounded (60s), and a longer
284
416
  // migration outlives it. Classify the fresh spawn the same way the
@@ -356,7 +488,7 @@ export const wake = Object.assign(
356
488
  } else {
357
489
  if (watch) {
358
490
  console.log(
359
- `Gateway running (pid ${gatewayState.pid}) — watch mode not available (no source files). Keeping existing process.`,
491
+ `Gateway running (pid ${gatewayState.pid}) — watch mode not available, no source files. Keeping existing process.`,
360
492
  );
361
493
  } else {
362
494
  console.log(`Gateway already running (pid ${gatewayState.pid}).`);
@@ -430,11 +562,14 @@ export const wake = Object.assign(
430
562
  console.log(" Re-provisioned guardian token.");
431
563
  break;
432
564
  } catch (err) {
433
- if (attempt < maxAttempts) {
565
+ if (
566
+ attempt < maxAttempts &&
567
+ !(err instanceof GuardianLeasePermanentError)
568
+ ) {
434
569
  await new Promise((r) => setTimeout(r, 2000 * 2 ** (attempt - 1)));
435
570
  } else {
436
571
  console.warn(
437
- ` Guardian token re-provision failed after ${maxAttempts} attempts: ${err}`,
572
+ ` Guardian token re-provision failed${err instanceof GuardianLeasePermanentError ?", not retrying — a malformed 200 will not fix itself" : ` after ${maxAttempts} attempts`}: ${err}`,
438
573
  );
439
574
  // The user explicitly confirmed this destructive repair — a
440
575
  // success exit here would make callers (the web recovery flow)
@@ -446,18 +581,15 @@ export const wake = Object.assign(
446
581
  }
447
582
  }
448
583
 
449
- // Restore the nginx edge and point the webhook auto-tunnel at it. Non-fatal:
450
- // a down edge is a degraded remote-web/webhook path, not a broken assistant.
584
+ // Restore the nginx edge and point the auto-tunnel at it. The spawned agent
585
+ // records itself in the root lockfile under `tunnel`. Non-fatal: a down
586
+ // edge is a degraded remote-web/webhook path, not a broken assistant.
451
587
  const workspaceDir = join(resources.instanceDir, "workspace");
452
- const ngrokChild = await restoreTunnelEdgeAndAutoTunnel(
588
+ await restoreTunnelEdgeAndAutoTunnel(
453
589
  entry.assistantId,
454
590
  resources.gatewayPort,
455
591
  workspaceDir,
456
592
  );
457
- if (ngrokChild?.pid) {
458
- const ngrokPidFile = join(resources.instanceDir, "ngrok.pid");
459
- writeFileSync(ngrokPidFile, String(ngrokChild.pid));
460
- }
461
593
 
462
594
  if (daemonMigrationsFailed) {
463
595
  console.log(
@@ -469,21 +601,5 @@ export const wake = Object.assign(
469
601
  );
470
602
  }
471
603
  console.log("Wake complete.");
472
-
473
- if (foreground) {
474
- console.log("Running in foreground (Ctrl+C to stop)...\n");
475
- // Block forever — the daemon is running with inherited stdio so its
476
- // output streams to this terminal. When the user hits Ctrl+C, SIGINT
477
- // propagates to the daemon child and both exit.
478
- await new Promise<void>((resolve) => {
479
- process.on("SIGINT", () => {
480
- console.log("\nShutting down...");
481
- resolve();
482
- });
483
- process.on("SIGTERM", () => resolve());
484
- });
485
- }
486
- },
487
- { description: "Start the assistant and gateway" },
488
- );
604
+ }
489
605