@mycadet/cli 0.12.7 → 0.12.9-dev.202610131452.9acab64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +2 -2
  2. package/knip.json +1 -1
  3. package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
  4. package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
  5. package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
  6. package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
  7. package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
  8. package/node_modules/@mycadet/assistant-api/events/show-cloud-login.ts +18 -0
  9. package/node_modules/@mycadet/assistant-api/index.ts +11 -11
  10. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +2 -2
  11. package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
  12. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  13. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  14. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  15. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  16. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  17. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  18. package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
  19. package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
  20. package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
  21. package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
  22. package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  23. package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  24. package/node_modules/@mycadet/environments/src/types.ts +6 -6
  25. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
  26. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
  27. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  28. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  29. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
  30. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +2 -2
  31. package/node_modules/@mycadet/{assistant-api/node_modules/@mycadet/service-contracts/src/platform-credential.ts → local-mode/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
  32. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  33. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  34. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  35. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  36. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  37. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  38. package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
  39. package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
  40. package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
  41. package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
  42. package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
  43. package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
  44. package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
  45. package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
  46. package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
  47. package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
  48. package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
  49. package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
  50. package/node_modules/@mycadet/service-contracts/package.json +2 -2
  51. package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
  52. package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  53. package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  54. package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  55. package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  56. package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  57. package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  58. package/package.json +6 -4
  59. package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
  60. package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
  61. package/src/__tests__/assistant-client-refresh.test.ts +2 -2
  62. package/src/__tests__/backup.test.ts +86 -91
  63. package/src/__tests__/client-token.test.ts +1 -1
  64. package/src/__tests__/client-tui-refresh.test.ts +1 -1
  65. package/src/__tests__/client-web-config.test.ts +1 -2
  66. package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
  67. package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +28 -18
  68. package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
  69. package/src/__tests__/connect-import.test.ts +55 -39
  70. package/src/__tests__/debug.test.ts +38 -38
  71. package/src/__tests__/ensure-binary.test.ts +182 -0
  72. package/src/__tests__/guardian-token.test.ts +149 -56
  73. package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
  74. package/src/__tests__/helpers/guardian-token.ts +22 -0
  75. package/src/__tests__/helpers/http.ts +10 -0
  76. package/src/__tests__/nginx-ingress.test.ts +15 -20
  77. package/src/__tests__/ngrok-spawn.test.ts +49 -0
  78. package/src/__tests__/orphan-detection.test.ts +1 -1
  79. package/src/__tests__/pair.test.ts +149 -85
  80. package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +80 -26
  81. package/src/__tests__/ps-table.test.ts +79 -0
  82. package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
  83. package/src/__tests__/retire.test.ts +58 -199
  84. package/src/__tests__/root-process-lockfile.test.ts +99 -0
  85. package/src/__tests__/sleep.test.ts +19 -19
  86. package/src/__tests__/statefulset.test.ts +8 -8
  87. package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +2 -2
  88. package/src/__tests__/teleport-backup.test.ts +3 -3
  89. package/src/__tests__/teleport.test.ts +180 -211
  90. package/src/__tests__/tunnel.test.ts +490 -270
  91. package/src/__tests__/uninstall.test.ts +150 -9
  92. package/src/__tests__/upgrade-local.test.ts +3 -3
  93. package/src/__tests__/upgrade-replay-env.test.ts +12 -12
  94. package/src/__tests__/wake.test.ts +260 -43
  95. package/src/__tests__/workos-pkce.test.ts +2 -2
  96. package/src/commands/backup.ts +24 -24
  97. package/src/commands/client.ts +76 -78
  98. package/src/commands/completions.test.ts +104 -0
  99. package/src/commands/completions.ts +87 -0
  100. package/src/commands/debug.ts +20 -20
  101. package/src/commands/exec.ts +12 -12
  102. package/src/commands/flags.ts +2 -2
  103. package/src/commands/hatch.ts +27 -24
  104. package/src/commands/login.ts +22 -22
  105. package/src/commands/logs.ts +8 -8
  106. package/src/commands/pair.ts +140 -37
  107. package/src/commands/ps.ts +232 -86
  108. package/src/commands/restart.ts +30 -0
  109. package/src/commands/restore.ts +140 -22
  110. package/src/commands/retire.ts +48 -150
  111. package/src/commands/rollback.ts +20 -26
  112. package/src/commands/setup.ts +2 -2
  113. package/src/commands/sleep.ts +2 -2
  114. package/src/commands/teleport.ts +110 -129
  115. package/src/commands/terminal.ts +158 -18
  116. package/src/commands/tunnel.ts +156 -175
  117. package/src/commands/uninstall.ts +137 -54
  118. package/src/commands/unpair.ts +3 -3
  119. package/src/commands/upgrade.ts +30 -54
  120. package/src/commands/use.ts +1 -1
  121. package/src/commands/wake.ts +166 -50
  122. package/src/index.ts +18 -9
  123. package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
  124. package/src/lib/__tests__/docker.test.ts +168 -20
  125. package/src/lib/__tests__/job-polling.test.ts +2 -2
  126. package/src/lib/__tests__/local-gateway-cli.test.ts +42 -0
  127. package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
  128. package/src/lib/__tests__/runtime-url.test.ts +1 -1
  129. package/src/lib/assistant-api-key-resolution.ts +5 -5
  130. package/src/lib/assistant-client.ts +8 -8
  131. package/src/lib/assistant-config.ts +58 -12
  132. package/src/lib/{platform-client.ts → cloud-client.ts} +105 -99
  133. package/src/lib/cloud-login.ts +23 -24
  134. package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
  135. package/src/lib/cloud-values.ts +3 -0
  136. package/src/lib/color.ts +4 -0
  137. package/src/lib/config-utils.ts +1 -1
  138. package/src/lib/detached-process.ts +1 -1
  139. package/src/lib/docker.ts +300 -111
  140. package/src/lib/ensure-binary.ts +171 -0
  141. package/src/lib/environments/__tests__/paths.test.ts +29 -3
  142. package/src/lib/environments/__tests__/resolve.test.ts +26 -26
  143. package/src/lib/environments/paths.ts +16 -8
  144. package/src/lib/environments/resolve.ts +10 -12
  145. package/src/lib/flag-args.test.ts +1 -1
  146. package/src/lib/flag-args.ts +1 -1
  147. package/src/lib/guardian-token.ts +37 -5
  148. package/src/lib/hatch-local.ts +32 -29
  149. package/src/lib/hatch-next-steps.ts +5 -9
  150. package/src/lib/health-check.ts +6 -6
  151. package/src/lib/ingress-config.ts +8 -35
  152. package/src/lib/job-polling.ts +2 -2
  153. package/src/lib/live-voice/connection.ts +12 -12
  154. package/src/lib/live-voice/tunnel.test.ts +11 -11
  155. package/src/lib/live-voice/tunnel.ts +15 -15
  156. package/src/lib/local-runtime-client.ts +20 -20
  157. package/src/lib/local.ts +46 -34
  158. package/src/lib/nginx-ingress.ts +40 -15
  159. package/src/lib/pair.ts +1 -1
  160. package/src/lib/process.ts +1 -1
  161. package/src/lib/provider-secrets.ts +3 -3
  162. package/src/lib/retire-apple-container.ts +1 -1
  163. package/src/lib/runtime-url.ts +8 -8
  164. package/src/lib/statefulset.ts +2 -2
  165. package/src/lib/step-runner.ts +6 -1
  166. package/src/lib/sync-cloud-assistants.ts +19 -19
  167. package/src/lib/teleport-backup.ts +4 -4
  168. package/src/lib/terminal-client.ts +17 -17
  169. package/src/lib/terminal-session.ts +24 -24
  170. package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
  171. package/src/lib/tunnels/cadet.ts +29 -0
  172. package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +36 -15
  173. package/src/lib/tunnels/gateway-notify.ts +61 -0
  174. package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +350 -89
  175. package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +53 -9
  176. package/src/lib/upgrade-lifecycle.ts +8 -21
  177. package/src/lib/upgrade-preflight.ts +2 -2
  178. package/src/lib/workos-pkce.ts +5 -5
  179. package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
  180. package/node_modules/@mycadet/assistant-api/events/show-platform-login.ts +0 -20
  181. package/src/commands/recover.ts +0 -131
  182. package/src/commands/roadmap.ts +0 -459
  183. package/src/commands/ssh.ts +0 -144
  184. package/src/commands/workflows.ts +0 -305
  185. package/src/lib/managed-cloud.ts +0 -3
@@ -20,10 +20,11 @@ import * as httpClient from "../lib/http-client.js";
20
20
  import * as ingressConfig from "../lib/ingress-config.js";
21
21
  import * as local from "../lib/local.js";
22
22
  import * as nginxIngress from "../lib/nginx-ingress.js";
23
- import * as ngrok from "../lib/ngrok.js";
23
+ import * as ngrok from "../lib/tunnels/ngrok.js";
24
24
  import * as processLib from "../lib/process.js";
25
25
  import * as portAllocator from "../lib/port-allocator.js";
26
26
  import type { AssistantEntry } from "../lib/assistant-config.js";
27
+ import { makeGuardianTokenData } from "./helpers/guardian-token.js";
27
28
 
28
29
  const realAssistantConfig = { ...assistantConfig };
29
30
  const realDocker = { ...docker };
@@ -61,11 +62,15 @@ const dockerResourceNamesMock = mock<typeof docker.dockerResourceNames>(
61
62
  realDocker.dockerResourceNames,
62
63
  );
63
64
  const wakeContainersMock = mock<typeof docker.wakeContainers>(async () => {});
65
+ const recreateDockerContainersMock = mock<typeof docker.recreateDockerContainers>(
66
+ async () => {},
67
+ );
64
68
 
65
69
  mock.module("../lib/docker.js", () => ({
66
70
  ...realDocker,
67
71
  dockerResourceNames: dockerResourceNamesMock,
68
72
  wakeContainers: wakeContainersMock,
73
+ recreateDockerContainers: recreateDockerContainersMock,
69
74
  }));
70
75
 
71
76
  const seedGuardianTokenFromSiblingEnvMock = mock<
@@ -73,18 +78,14 @@ const seedGuardianTokenFromSiblingEnvMock = mock<
73
78
  >(() => false);
74
79
  // Default: a token exists, so the re-provision recovery path is skipped. Tests
75
80
  // that exercise recovery override loadGuardianToken to return null.
76
- const loadGuardianTokenMock = mock<typeof guardianToken.loadGuardianToken>(
77
- () =>
78
- ({ accessToken: "existing" }) as ReturnType<
79
- typeof guardianToken.loadGuardianToken
80
- >,
81
+ const loadGuardianTokenMock = mock<typeof guardianToken.loadGuardianToken>(() =>
82
+ makeGuardianTokenData({ accessToken: "existing" }),
81
83
  );
82
84
  const resetGuardianBootstrapMock = mock<
83
85
  typeof guardianToken.resetGuardianBootstrap
84
86
  >(async () => {});
85
87
  const leaseGuardianTokenMock = mock<typeof guardianToken.leaseGuardianToken>(
86
- async () =>
87
- ({}) as Awaited<ReturnType<typeof guardianToken.leaseGuardianToken>>,
88
+ async () => makeGuardianTokenData(),
88
89
  );
89
90
 
90
91
  mock.module("../lib/guardian-token.js", () => ({
@@ -156,7 +157,7 @@ const maybeStartNgrokTunnelMock = mock<typeof ngrok.maybeStartNgrokTunnel>(
156
157
  async () => null,
157
158
  );
158
159
 
159
- mock.module("../lib/ngrok", () => ({
160
+ mock.module("../lib/tunnels/ngrok", () => ({
160
161
  ...realNgrok,
161
162
  maybeStartNgrokTunnel: maybeStartNgrokTunnelMock,
162
163
  }));
@@ -288,15 +289,13 @@ beforeEach(() => {
288
289
  seedGuardianTokenFromSiblingEnvMock.mockReset();
289
290
  seedGuardianTokenFromSiblingEnvMock.mockReturnValue(false);
290
291
  loadGuardianTokenMock.mockReset();
291
- loadGuardianTokenMock.mockReturnValue({
292
- accessToken: "existing",
293
- } as ReturnType<typeof guardianToken.loadGuardianToken>);
292
+ loadGuardianTokenMock.mockReturnValue(
293
+ makeGuardianTokenData({ accessToken: "existing" }),
294
+ );
294
295
  resetGuardianBootstrapMock.mockReset();
295
296
  resetGuardianBootstrapMock.mockResolvedValue(undefined);
296
297
  leaseGuardianTokenMock.mockReset();
297
- leaseGuardianTokenMock.mockResolvedValue(
298
- {} as Awaited<ReturnType<typeof guardianToken.leaseGuardianToken>>,
299
- );
298
+ leaseGuardianTokenMock.mockResolvedValue(makeGuardianTokenData());
300
299
  maybeStartNgrokTunnelMock.mockReset();
301
300
  maybeStartNgrokTunnelMock.mockResolvedValue(null);
302
301
  loadRawConfigMock.mockReset();
@@ -330,7 +329,7 @@ afterAll(() => {
330
329
  mock.module("../lib/process", () => realProcessLib);
331
330
  mock.module("../lib/port-allocator.js", () => realPortAllocator);
332
331
  mock.module("../lib/local", () => realLocal);
333
- mock.module("../lib/ngrok", () => realNgrok);
332
+ mock.module("../lib/tunnels/ngrok", () => realNgrok);
334
333
  mock.module("../lib/ingress-config.js", () => realIngressConfig);
335
334
  mock.module("../lib/nginx-ingress.js", () => realNginxIngress);
336
335
  mock.module("../lib/http-client.js", () => realHttpClient);
@@ -819,11 +818,11 @@ describe("cadet wake: daemon startup failure", () => {
819
818
  // AND a foreign listener on the runtime HTTP port answers readiness in
820
819
  // the dead daemon's place
821
820
  probeDaemonReadinessWithRetryMock.mockResolvedValue("ready");
822
- const exitSpy = spyOn(process, "exit").mockImplementation(((
823
- code?: number,
824
- ) => {
825
- throw new Error(`process.exit:${code}`);
826
- }) as never);
821
+ const exitSpy = spyOn(process, "exit").mockImplementation(
822
+ (code?: number | string | undefined): never => {
823
+ throw new Error(`process.exit:${code}`);
824
+ },
825
+ );
827
826
 
828
827
  // WHEN wake runs
829
828
  const result = wake();
@@ -945,7 +944,7 @@ describe("cadet wake — tunnel edge restore", () => {
945
944
  runtimeUrl: "http://localhost:7930",
946
945
  cloud: "docker",
947
946
  ingressUrl: "https://assistant.example.com",
948
- } as AssistantEntry);
947
+ });
949
948
  loadRawConfigMock.mockReturnValue(enabledConfig);
950
949
  process.argv = ["bun", "cadet", "wake", "docker-1"];
951
950
 
@@ -957,9 +956,13 @@ describe("cadet wake — tunnel edge restore", () => {
957
956
  workspaceDir: defaultWorkspace,
958
957
  gatewayPort: 7930,
959
958
  });
960
- // Container wakes never tracked a spawned ngrok PID, so the auto-tunnel
961
- // stays out of this path.
962
- expect(maybeStartNgrokTunnelMock).not.toHaveBeenCalled();
959
+ // Container wakes run the same auto-tunnel as local wakes (docker
960
+ // parity); the spawned agent records itself in the root lockfile.
961
+ expect(maybeStartNgrokTunnelMock).toHaveBeenCalledWith(
962
+ 7840,
963
+ defaultWorkspace,
964
+ "docker-1",
965
+ );
963
966
  expect(logSpy).toHaveBeenCalledWith("Wake complete.");
964
967
  } finally {
965
968
  if (originalWorkspaceDir === undefined) {
@@ -971,6 +974,48 @@ describe("cadet wake — tunnel edge restore", () => {
971
974
  }
972
975
  });
973
976
 
977
+ test("a migrated docker assistant (containers never created) is recreated from the entry", async () => {
978
+ const defaultWorkspace = mkdtempSync(join(tmpdir(), "cadet-wake-default-"));
979
+ const originalWorkspaceDir = process.env.CADET_WORKSPACE_DIR;
980
+ process.env.CADET_WORKSPACE_DIR = defaultWorkspace;
981
+ const migratedEntry = {
982
+ assistantId: "docker-1",
983
+ runtimeUrl: "http://localhost:7930",
984
+ cloud: "docker" as const,
985
+ containerInfo: {
986
+ assistantImage: "mycadet/mycadet-assistant:v0.12.7",
987
+ gatewayImage: "mycadet/mycadet-gateway:v0.12.7",
988
+ cesImage: "mycadet/mycadet-credential-executor:v0.12.7",
989
+ },
990
+ };
991
+ resolveTargetAssistantMock.mockReturnValue(migratedEntry);
992
+ loadRawConfigMock.mockReturnValue(enabledConfig);
993
+ // The v0.0.10 run feedback: the containers were never created —
994
+ // `docker start` answers "No such container".
995
+ wakeContainersMock.mockImplementation(async () => {
996
+ throw new Error(
997
+ "Error response from daemon: No such container: docker-1-assistant",
998
+ );
999
+ });
1000
+ process.argv = ["bun", "cadet", "wake", "docker-1"];
1001
+
1002
+ try {
1003
+ await wake();
1004
+
1005
+ expect(recreateDockerContainersMock).toHaveBeenCalledWith(migratedEntry);
1006
+ expect(logSpy).toHaveBeenCalledWith("Docker containers started.");
1007
+ expect(logSpy).toHaveBeenCalledWith("Wake complete.");
1008
+ } finally {
1009
+ wakeContainersMock.mockImplementation(async () => {});
1010
+ if (originalWorkspaceDir === undefined) {
1011
+ delete process.env.CADET_WORKSPACE_DIR;
1012
+ } else {
1013
+ process.env.CADET_WORKSPACE_DIR = originalWorkspaceDir;
1014
+ }
1015
+ rmSync(defaultWorkspace, { recursive: true, force: true });
1016
+ }
1017
+ });
1018
+
974
1019
  test("a docker wake leaves an edge fronting another container alone", async () => {
975
1020
  const defaultWorkspace = mkdtempSync(join(tmpdir(), "cadet-wake-default-"));
976
1021
  const originalWorkspaceDir = process.env.CADET_WORKSPACE_DIR;
@@ -980,7 +1025,7 @@ describe("cadet wake — tunnel edge restore", () => {
980
1025
  runtimeUrl: "http://localhost:7930",
981
1026
  cloud: "docker",
982
1027
  ingressUrl: "https://assistant.example.com",
983
- } as AssistantEntry);
1028
+ });
984
1029
  loadRawConfigMock.mockReturnValue(enabledConfig);
985
1030
  isIngressRunningMock.mockReturnValue(true);
986
1031
  readIngressStateMock.mockReturnValue({
@@ -1014,7 +1059,7 @@ describe("cadet wake — tunnel edge restore", () => {
1014
1059
  runtimeUrl: "http://localhost:7930",
1015
1060
  cloud: "docker",
1016
1061
  ingressUrl: "https://assistant.example.com",
1017
- } as AssistantEntry);
1062
+ });
1018
1063
  loadRawConfigMock.mockReturnValue(enabledConfig);
1019
1064
  isIngressRunningMock.mockReturnValue(true);
1020
1065
  readIngressStateMock.mockReturnValue({
@@ -1052,7 +1097,7 @@ describe("cadet wake — tunnel edge restore", () => {
1052
1097
  }
1053
1098
  });
1054
1099
 
1055
- test("a failed docker edge restore does not claim webhooks still work", async () => {
1100
+ test("a failed docker edge restore falls back to the gateway tunnel", async () => {
1056
1101
  const defaultWorkspace = mkdtempSync(join(tmpdir(), "cadet-wake-default-"));
1057
1102
  const originalWorkspaceDir = process.env.CADET_WORKSPACE_DIR;
1058
1103
  process.env.CADET_WORKSPACE_DIR = defaultWorkspace;
@@ -1061,7 +1106,7 @@ describe("cadet wake — tunnel edge restore", () => {
1061
1106
  runtimeUrl: "http://localhost:7930",
1062
1107
  cloud: "docker",
1063
1108
  ingressUrl: "https://assistant.example.com",
1064
- } as AssistantEntry);
1109
+ });
1065
1110
  loadRawConfigMock.mockReturnValue(enabledConfig);
1066
1111
  ensureTunnelEdgeMock.mockRejectedValue(
1067
1112
  new Error("nginx is not installed."),
@@ -1071,11 +1116,14 @@ describe("cadet wake — tunnel edge restore", () => {
1071
1116
  try {
1072
1117
  await wake();
1073
1118
 
1119
+ // Docker wakes get the same tunnel logic as local ones: when the edge
1120
+ // cannot be rebuilt, the auto-tunnel still fronts the gateway port so
1121
+ // webhook delivery survives.
1074
1122
  expect(warnSpy).toHaveBeenCalledWith(
1075
- expect.stringContaining("webhook delivery are unavailable"),
1123
+ expect.stringContaining("Webhooks still work"),
1076
1124
  );
1077
1125
  expect(warnSpy).not.toHaveBeenCalledWith(
1078
- expect.stringContaining("Webhooks still work"),
1126
+ expect.stringContaining("webhook delivery are unavailable"),
1079
1127
  );
1080
1128
  } finally {
1081
1129
  if (originalWorkspaceDir === undefined) {
@@ -1098,14 +1146,14 @@ describe("cadet wake — tunnel edge restore", () => {
1098
1146
  runtimeUrl: "http://localhost:7930",
1099
1147
  cloud: "docker",
1100
1148
  ingressUrl: "https://other.example.com",
1101
- } as AssistantEntry);
1149
+ });
1102
1150
  loadAllAssistantsMock.mockReturnValue([
1103
1151
  {
1104
1152
  assistantId: "docker-2",
1105
1153
  runtimeUrl: "http://localhost:8030",
1106
1154
  cloud: "docker",
1107
1155
  ingressUrl: "https://assistant.example.com",
1108
- } as AssistantEntry,
1156
+ },
1109
1157
  ]);
1110
1158
  loadRawConfigMock.mockReturnValue(enabledConfig);
1111
1159
  isIngressRunningMock.mockReturnValue(false);
@@ -1135,7 +1183,7 @@ describe("cadet wake — tunnel edge restore", () => {
1135
1183
  runtimeUrl: "http://localhost:7930",
1136
1184
  cloud: "docker",
1137
1185
  ingressUrl: "https://assistant.example.com",
1138
- } as AssistantEntry);
1186
+ });
1139
1187
  loadRawConfigMock.mockReturnValue(enabledConfig);
1140
1188
  process.argv = ["bun", "cadet", "wake", "docker-1"];
1141
1189
 
@@ -1163,7 +1211,7 @@ describe("cadet wake — tunnel edge restore", () => {
1163
1211
  runtimeUrl: "http://localhost:7930",
1164
1212
  cloud: "docker",
1165
1213
  ingressUrl: "https://assistant.example.com",
1166
- } as AssistantEntry);
1214
+ });
1167
1215
  loadRawConfigMock.mockReturnValue(enabledConfig);
1168
1216
  waitForDaemonReadyMock.mockImplementation(async () => false);
1169
1217
  process.argv = ["bun", "cadet", "wake", "docker-1"];
@@ -1194,7 +1242,7 @@ describe("cadet wake — tunnel edge restore", () => {
1194
1242
  assistantId: "docker-1",
1195
1243
  runtimeUrl: "http://localhost:7930",
1196
1244
  cloud: "docker",
1197
- } as AssistantEntry);
1245
+ });
1198
1246
  process.argv = ["bun", "cadet", "wake", "docker-1"];
1199
1247
 
1200
1248
  try {
@@ -1222,13 +1270,13 @@ describe("cadet wake — tunnel edge restore", () => {
1222
1270
  assistantId: "docker-1",
1223
1271
  runtimeUrl: "http://localhost:7930",
1224
1272
  cloud: "docker",
1225
- } as AssistantEntry);
1273
+ });
1226
1274
  loadAllAssistantsMock.mockReturnValue([
1227
1275
  {
1228
1276
  assistantId: "docker-1",
1229
1277
  runtimeUrl: "http://localhost:7930",
1230
1278
  cloud: "docker",
1231
- } as AssistantEntry,
1279
+ },
1232
1280
  ]);
1233
1281
  loadRawConfigMock.mockReturnValue(enabledConfig);
1234
1282
  isIngressRunningMock.mockReturnValue(false);
@@ -1260,14 +1308,14 @@ describe("cadet wake — tunnel edge restore", () => {
1260
1308
  assistantId: "docker-1",
1261
1309
  runtimeUrl: "http://localhost:7930",
1262
1310
  cloud: "docker",
1263
- } as AssistantEntry);
1311
+ });
1264
1312
  loadAllAssistantsAcrossEnvsMock.mockReturnValue([
1265
1313
  {
1266
1314
  assistantId: "docker-2",
1267
1315
  runtimeUrl: "http://localhost:8030",
1268
1316
  cloud: "docker",
1269
1317
  ingressUrl: "https://assistant.example.com",
1270
- } as AssistantEntry,
1318
+ },
1271
1319
  ]);
1272
1320
  loadRawConfigMock.mockReturnValue(enabledConfig);
1273
1321
  isIngressRunningMock.mockReturnValue(false);
@@ -1296,7 +1344,7 @@ describe("cadet wake — tunnel edge restore", () => {
1296
1344
  runtimeUrl: "http://localhost:7930",
1297
1345
  cloud: "docker",
1298
1346
  ingressUrl: "https://assistant.example.com",
1299
- } as AssistantEntry);
1347
+ });
1300
1348
  process.argv = ["bun", "cadet", "wake", "docker-1"];
1301
1349
 
1302
1350
  try {
@@ -1455,14 +1503,14 @@ describe("cadet wake — tunnel edge restore", () => {
1455
1503
  loadRawConfigMock.mockReturnValue(webhookConfig);
1456
1504
  ensureTunnelEdgeMock.mockRejectedValue(
1457
1505
  new Error(
1458
- "nginx is not installed, so the tunnel edge cannot start. Install it (macOS: `brew install nginx`, Linux: `sudo apt install nginx`) or point NGINX_BIN at an existing binary.",
1506
+ "nginx is not installed, so the tunnel edge cannot start. See https://nginx.org/en/docs/install.html for how to install, or point NGINX_BIN at an existing binary.",
1459
1507
  ),
1460
1508
  );
1461
1509
 
1462
1510
  await wake();
1463
1511
 
1464
1512
  expect(warnSpy).toHaveBeenCalledWith(
1465
- expect.stringContaining("brew install nginx"),
1513
+ expect.stringContaining("https://nginx.org/en/docs/install.html"),
1466
1514
  );
1467
1515
  // No tunnel is recorded here, so no provider can be named and the advice
1468
1516
  // points at the help that lists them.
@@ -1541,4 +1589,173 @@ describe("cadet wake — tunnel edge restore", () => {
1541
1589
  "local-assistant",
1542
1590
  );
1543
1591
  });
1592
+
1593
+ describe("--all", () => {
1594
+ function makeAllEntry(
1595
+ assistantId: string,
1596
+ instanceDir: string,
1597
+ ): AssistantEntry {
1598
+ return {
1599
+ assistantId,
1600
+ runtimeUrl: "http://127.0.0.1:7830",
1601
+ cloud: "local",
1602
+ resources: {
1603
+ instanceDir,
1604
+ daemonPort: 7821,
1605
+ gatewayPort: 7830,
1606
+ qdrantPort: 6333,
1607
+ cesPort: 7822,
1608
+ signingKey: "ab".repeat(32),
1609
+ },
1610
+ };
1611
+ }
1612
+
1613
+ function makeAllEntries(): AssistantEntry[] {
1614
+ mkdirSync(join(tempDir, "instance-1"), { recursive: true });
1615
+ mkdirSync(join(tempDir, "instance-2"), { recursive: true });
1616
+ return [
1617
+ makeAllEntry("wake-all-1", join(tempDir, "instance-1")),
1618
+ makeAllEntry("wake-all-2", join(tempDir, "instance-2")),
1619
+ ];
1620
+ }
1621
+
1622
+ // Cold-start both assistants so each wake's starts are observable.
1623
+ function mockColdStart(): void {
1624
+ resolveProcessStateMock.mockImplementation(async () => ({
1625
+ status: "needs_start",
1626
+ pid: null,
1627
+ }));
1628
+ isProcessAliveMock.mockReturnValue({ alive: true, pid: 123 });
1629
+ }
1630
+
1631
+ test("starts every assistant in the lockfile", async () => {
1632
+ mockColdStart();
1633
+ loadAllAssistantsMock.mockReturnValue(makeAllEntries());
1634
+ process.argv = ["bun", "cadet", "wake", "--all"];
1635
+
1636
+ await wake();
1637
+
1638
+ expect(startCesMock).toHaveBeenCalledTimes(2);
1639
+ expect(startLocalDaemonMock).toHaveBeenCalledTimes(2);
1640
+ expect(startGatewayMock).toHaveBeenCalledTimes(2);
1641
+ });
1642
+
1643
+ test("skips assistants whose lifecycle is managed elsewhere", async () => {
1644
+ mockColdStart();
1645
+ const entries = makeAllEntries();
1646
+ entries[1] = {
1647
+ assistantId: "paired-one",
1648
+ runtimeUrl: "http://127.0.0.1:1",
1649
+ cloud: "paired",
1650
+ };
1651
+ loadAllAssistantsMock.mockReturnValue(entries);
1652
+ process.argv = ["bun", "cadet", "wake", "--all"];
1653
+
1654
+ await wake();
1655
+
1656
+ expect(startCesMock).toHaveBeenCalledTimes(1);
1657
+ expect(startLocalDaemonMock).toHaveBeenCalledTimes(1);
1658
+ expect(startGatewayMock).toHaveBeenCalledTimes(1);
1659
+ expect(logSpy.mock.calls.flat().join("\n")).toContain(
1660
+ "Skipping 'paired-one' (paired) — its lifecycle is managed elsewhere.",
1661
+ );
1662
+ });
1663
+
1664
+ test("continues past a failing assistant and exits 1", async () => {
1665
+ mockColdStart();
1666
+ loadAllAssistantsMock.mockReturnValue([
1667
+ // Missing resources is a deliberate refusal for this entry.
1668
+ {
1669
+ assistantId: "wake-all-1",
1670
+ runtimeUrl: "http://127.0.0.1:7830",
1671
+ cloud: "local",
1672
+ },
1673
+ makeAllEntry("wake-all-2", join(tempDir, "instance-2")),
1674
+ ]);
1675
+ mkdirSync(join(tempDir, "instance-2"), { recursive: true });
1676
+ process.argv = ["bun", "cadet", "wake", "--all"];
1677
+
1678
+ const exitSpy = spyOn(process, "exit").mockImplementation(
1679
+ (code?: number | string | undefined): never => {
1680
+ throw new Error(`process.exit:${code}`);
1681
+ },
1682
+ );
1683
+
1684
+ try {
1685
+ await expect(wake()).rejects.toThrow("process.exit:1");
1686
+ expect(errorSpy.mock.calls.flat().join("\n")).toContain(
1687
+ "failed to wake 'wake-all-1'",
1688
+ );
1689
+ } finally {
1690
+ exitSpy.mockRestore();
1691
+ }
1692
+
1693
+ // The healthy assistant was still started.
1694
+ expect(startCesMock).toHaveBeenCalledTimes(1);
1695
+ expect(startLocalDaemonMock).toHaveBeenCalledTimes(1);
1696
+ expect(startGatewayMock).toHaveBeenCalledTimes(1);
1697
+ });
1698
+
1699
+ test("exits 1 when there is nothing to start", async () => {
1700
+ loadAllAssistantsMock.mockReturnValue([]);
1701
+ process.argv = ["bun", "cadet", "wake", "--all"];
1702
+
1703
+ const exitSpy = spyOn(process, "exit").mockImplementation(
1704
+ (code?: number | string | undefined): never => {
1705
+ throw new Error(`process.exit:${code}`);
1706
+ },
1707
+ );
1708
+
1709
+ try {
1710
+ await expect(wake()).rejects.toThrow("process.exit:1");
1711
+ expect(errorSpy).toHaveBeenCalledWith(
1712
+ expect.stringContaining("no assistants found"),
1713
+ );
1714
+ } finally {
1715
+ exitSpy.mockRestore();
1716
+ }
1717
+ });
1718
+
1719
+ test("refuses --all combined with a name", async () => {
1720
+ process.argv = ["bun", "cadet", "wake", "local-assistant", "--all"];
1721
+
1722
+ const exitSpy = spyOn(process, "exit").mockImplementation(
1723
+ (code?: number | string | undefined): never => {
1724
+ throw new Error(`process.exit:${code}`);
1725
+ },
1726
+ );
1727
+
1728
+ try {
1729
+ await expect(wake()).rejects.toThrow("process.exit:1");
1730
+ expect(errorSpy).toHaveBeenCalledWith(
1731
+ expect.stringContaining("--all cannot be combined"),
1732
+ );
1733
+ } finally {
1734
+ exitSpy.mockRestore();
1735
+ }
1736
+ });
1737
+
1738
+ test("refuses --all with --foreground and --repair-guardian", async () => {
1739
+ const exitSpy = spyOn(process, "exit").mockImplementation(
1740
+ (code?: number | string | undefined): never => {
1741
+ throw new Error(`process.exit:${code}`);
1742
+ },
1743
+ );
1744
+
1745
+ try {
1746
+ for (const flag of ["--foreground", "--repair-guardian"]) {
1747
+ process.argv = ["bun", "cadet", "wake", "--all", flag];
1748
+ await expect(wake()).rejects.toThrow("process.exit:1");
1749
+ }
1750
+ } finally {
1751
+ exitSpy.mockRestore();
1752
+ }
1753
+
1754
+ expect(errorSpy).toHaveBeenCalledWith(
1755
+ expect.stringContaining(
1756
+ "--all cannot be combined with --foreground or --repair-guardian",
1757
+ ),
1758
+ );
1759
+ });
1760
+ });
1544
1761
  });
@@ -88,7 +88,7 @@ describe("buildAuthorizeUrl", () => {
88
88
  });
89
89
 
90
90
  describe("selectWorkosClientId", () => {
91
- // During coexistence the platform lists two providers that share the
91
+ // During coexistence the cloud lists two providers that share the
92
92
  // "workos-oidc" id; only the OAuth2 one (no discovery URL) is usable.
93
93
  const legacy = {
94
94
  id: "workos-oidc",
@@ -110,7 +110,7 @@ describe("selectWorkosClientId", () => {
110
110
  expect(selectWorkosClientId([modern, legacy])).toBe("client_um");
111
111
  });
112
112
 
113
- test("returns null when the platform lacks token auth", () => {
113
+ test("returns null when the cloud lacks token auth", () => {
114
114
  const preTokenAuth = { ...modern, flows: ["provider_redirect"] };
115
115
  expect(selectWorkosClientId([legacy, preTokenAuth])).toBeNull();
116
116
  expect(selectWorkosClientId([])).toBeNull();
@@ -1,4 +1,4 @@
1
- import { isManagedCloud } from "../lib/managed-cloud.js";
1
+ import { isCloudHome } from "../lib/cloud-values.js";
2
2
  import { mkdirSync, writeFileSync } from "fs";
3
3
  import { dirname, join } from "path";
4
4
 
@@ -14,9 +14,9 @@ import {
14
14
  localRuntimePollJobStatus,
15
15
  } from "../lib/local-runtime-client.js";
16
16
  import {
17
- platformRequestSignedUrl,
18
- readPlatformToken,
19
- } from "../lib/platform-client.js";
17
+ cloudRequestSignedUrl,
18
+ readCloudToken,
19
+ } from "../lib/cloud-client.js";
20
20
  import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
21
21
 
22
22
  // Default timeout for the runtime-direct export request. Overridable via
@@ -42,10 +42,10 @@ export const backup = Object.assign(
42
42
  console.log("Options:");
43
43
  console.log(" --output <path> Path to save the .vbundle file");
44
44
  console.log(
45
- " (default: ~/.local/share/cadet/backups/<name>-<timestamp>.vbundle)",
45
+ " defaults to ~/.local/share/cadet/backups/<name>-<timestamp>.vbundle",
46
46
  );
47
47
  console.log(
48
- ` --export-timeout <secs> Export request timeout in seconds (default: ${DEFAULT_EXPORT_TIMEOUT_MS / 1000})`,
48
+ ` --export-timeout <secs> Export request timeout in seconds, defaults to ${DEFAULT_EXPORT_TIMEOUT_MS / 1000}`,
49
49
  );
50
50
  console.log("");
51
51
  console.log("Examples:");
@@ -102,7 +102,7 @@ export const backup = Object.assign(
102
102
  process.exit(1);
103
103
  }
104
104
 
105
- if (isManagedCloud(cloud)) {
105
+ if (isCloudHome(cloud)) {
106
106
  await backupPlatform(entry, name, outputArg);
107
107
  return;
108
108
  }
@@ -233,9 +233,9 @@ export const backup = Object.assign(
233
233
  // ---------------------------------------------------------------------------
234
234
  // Platform-managed (cloud="cadet") backup over GCS.
235
235
  //
236
- // The runtime exports the bundle straight to a platform-issued signed GCS
236
+ // The runtime exports the bundle straight to a cloud-issued signed GCS
237
237
  // URL; the CLI then downloads from GCS to local disk. Bytes never flow
238
- // through Django. Same architectural shape as the platform-source half of
238
+ // through Django. Same architectural shape as the cloud-source half of
239
239
  // `cadet teleport`. Output format and success log lines match mode 1
240
240
  // (runtime-direct local backup) so users see one consistent UX.
241
241
  //
@@ -251,24 +251,24 @@ async function backupPlatform(
251
251
  name: string,
252
252
  outputArg?: string,
253
253
  ): Promise<void> {
254
- const platformToken = readPlatformToken();
255
- if (!platformToken) {
254
+ const cloudToken = readCloudToken();
255
+ if (!cloudToken) {
256
256
  console.error(
257
- "Not logged in. Run 'cadet login' first (required for platform-managed backup).",
257
+ "Not logged in. Run 'cadet login' first.",
258
258
  );
259
259
  process.exit(1);
260
260
  }
261
261
  // Pin upload, download, and runtime requests to the same platform instance
262
- // the assistant lives on. Using `getPlatformUrl()` instead would target
262
+ // the assistant lives on. Using `getCloudUrl()` instead would target
263
263
  // whatever the lockfile / env-var resolves to, which may differ from
264
264
  // `entry.runtimeUrl` for staging/dev assistants and end up signing URLs
265
- // for the wrong GCS bucket. Mirrors the teleport bundlePlatformUrl
265
+ // for the wrong GCS bucket. Mirrors the teleport bundleCloudUrl
266
266
  // threading at `cli/src/commands/teleport.ts:1311-1312`.
267
- const platformUrl = entry.runtimeUrl;
267
+ const cloudUrl = entry.runtimeUrl;
268
268
  // Track the working platform token across kickoff/poll/download so a
269
269
  // 401-driven refresh during polling stays consistent through the final
270
270
  // signed-download request.
271
- let exportPlatformToken = platformToken;
271
+ let exportPlatformToken = cloudToken;
272
272
 
273
273
  // Step 0 — Ask the source runtime which version it's running. The bundle
274
274
  // is produced by the daemon (not the CLI), and the CLI version can drift
@@ -288,17 +288,17 @@ async function backupPlatform(
288
288
  }
289
289
 
290
290
  // Step 1 — Request a signed upload URL.
291
- const { url: uploadUrl, bundleKey } = await platformRequestSignedUrl(
291
+ const { url: uploadUrl, bundleKey } = await cloudRequestSignedUrl(
292
292
  {
293
293
  operation: "upload",
294
294
  minRuntimeVersion: runtimeIdentity.version,
295
295
  maxRuntimeVersion: null,
296
296
  },
297
297
  exportPlatformToken,
298
- platformUrl,
298
+ cloudUrl,
299
299
  );
300
300
 
301
- // Step 2 — Kick off runtime export-to-GCS through the platform's
301
+ // Step 2 — Kick off runtime export-to-GCS through the cloud's
302
302
  // wildcard runtime proxy. `localRuntimeExportToGcs` builds the
303
303
  // `/v1/assistants/<id>/migrations/export-to-gcs` URL for cloud="cadet"
304
304
  // and uses platform-token auth (no guardian-token bootstrap).
@@ -327,7 +327,7 @@ async function backupPlatform(
327
327
  label: "platform export",
328
328
  poll: () => localRuntimePollJobStatus(entry, exportPlatformToken, jobId),
329
329
  refreshOn401: async () => {
330
- const refreshed = readPlatformToken();
330
+ const refreshed = readCloudToken();
331
331
  if (!refreshed) {
332
332
  throw new Error(
333
333
  "Platform auth expired during export and no credential was found on disk. Run 'cadet login' and retry.",
@@ -344,7 +344,7 @@ async function backupPlatform(
344
344
 
345
345
  // Step 4 — Request a signed download URL for the same bundle and fetch
346
346
  // it from GCS directly. No auth on signed URLs.
347
- // Use `exportPlatformToken` (not the original `platformToken`) so a
347
+ // Use `exportPlatformToken` (not the original `cloudToken`) so a
348
348
  // poll-loop 401 refresh doesn't get clobbered here — otherwise a long
349
349
  // export that recovered mid-poll via re-auth would still 401 on the
350
350
  // download-URL request and abort an otherwise successful run.
@@ -354,15 +354,15 @@ async function backupPlatform(
354
354
  // runtime to gate against, and the user can later restore the file
355
355
  // into any compatible runtime. Sending the CLI's version would
356
356
  // incorrectly block older CLIs from backing up newer assistants.
357
- // The platform treats `target_runtime_version` as optional and skips
357
+ // The cloud treats `target_runtime_version` as optional and skips
358
358
  // the version check when it's omitted.
359
- const { url: bundleUrl } = await platformRequestSignedUrl(
359
+ const { url: bundleUrl } = await cloudRequestSignedUrl(
360
360
  {
361
361
  operation: "download",
362
362
  bundleKey,
363
363
  },
364
364
  exportPlatformToken,
365
- platformUrl,
365
+ cloudUrl,
366
366
  );
367
367
 
368
368
  let downloadResponse: Response;