@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
@@ -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
 
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ import cliPkg from "../package.json";
14
14
  import { backup } from "./commands/backup";
15
15
  import { clean } from "./commands/clean";
16
16
  import { client } from "./commands/client";
17
+ import { completeCadet, completions } from "./commands/completions";
17
18
  import { confirm } from "./commands/confirm";
18
19
  import { connect } from "./commands/connect";
19
20
  import { devices } from "./commands/devices";
@@ -27,14 +28,12 @@ import { logs } from "./commands/logs";
27
28
  import { message } from "./commands/message";
28
29
  import { pair } from "./commands/pair";
29
30
  import { ps } from "./commands/ps";
30
- import { recover } from "./commands/recover";
31
31
  import { restore } from "./commands/restore";
32
- import { roadmap } from "./commands/roadmap";
33
32
  import { retire } from "./commands/retire";
33
+ import { restart } from "./commands/restart";
34
34
  import { rollback } from "./commands/rollback";
35
35
  import { setup } from "./commands/setup";
36
36
  import { sleep } from "./commands/sleep";
37
- import { ssh } from "./commands/ssh";
38
37
  import { debug } from "./commands/debug";
39
38
  import { teleport } from "./commands/teleport";
40
39
  import { terminal } from "./commands/terminal";
@@ -45,7 +44,6 @@ import { upgrade } from "./commands/upgrade";
45
44
  import { use } from "./commands/use";
46
45
  import { voice } from "./commands/voice";
47
46
  import { wake } from "./commands/wake";
48
- import { workflows } from "./commands/workflows";
49
47
  import { red } from "./lib/color";
50
48
  import { resolveAssistant, setActiveAssistant } from "./lib/assistant-config";
51
49
  import { loadGuardianToken } from "./lib/guardian-token";
@@ -56,6 +54,7 @@ const commands = {
56
54
  backup,
57
55
  clean,
58
56
  client,
57
+ completions,
59
58
  confirm,
60
59
  connect,
61
60
  debug,
@@ -66,19 +65,17 @@ const commands = {
66
65
  gateway,
67
66
  hatch,
68
67
  login,
69
- logout,
70
68
  logs,
69
+ logout,
71
70
  message,
72
71
  pair,
73
72
  ps,
74
- recover,
75
73
  restore,
76
74
  retire,
77
- roadmap,
75
+ restart,
78
76
  rollback,
79
77
  setup,
80
78
  sleep,
81
- ssh,
82
79
  teleport,
83
80
  terminal,
84
81
  tunnel,
@@ -89,7 +86,6 @@ const commands = {
89
86
  voice,
90
87
  wake,
91
88
  whoami,
92
- workflows,
93
89
  };
94
90
 
95
91
  type CommandName = keyof typeof commands;
@@ -237,6 +233,19 @@ async function main() {
237
233
  process.exit(0);
238
234
  }
239
235
 
236
+ // Hidden completion hook: the bash completion script calls
237
+ // `cadet __complete <words-up-to-cursor>` and filters the printed
238
+ // candidates against the word under the cursor. Deliberately unlisted in
239
+ // the command map so it never shows up in help.
240
+ if (commandName === "__complete") {
241
+ const candidates = completeCadet(
242
+ process.argv.slice(3),
243
+ Object.keys(commands),
244
+ );
245
+ console.log(candidates.join("\n"));
246
+ process.exit(0);
247
+ }
248
+
240
249
  const command = commands[commandName as CommandName];
241
250
 
242
251
  if (!command) {
@@ -2,10 +2,10 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  import {
4
4
  platformPollJobStatus,
5
- platformRequestSignedUrl,
5
+ cloudRequestSignedUrl,
6
6
  VersionMismatchError,
7
7
  type UnifiedJobStatus,
8
- } from "../platform-client.js";
8
+ } from "../cloud-client.js";
9
9
 
10
10
  const PLATFORM_URL = "https://platform.example.test";
11
11
  const VAK_TOKEN = "vak_test_1234567890"; // API-key path skips org-ID fetch.
@@ -69,7 +69,7 @@ afterEach(() => {
69
69
  globalThis.fetch = originalFetch;
70
70
  });
71
71
 
72
- describe("platformRequestSignedUrl", () => {
72
+ describe("cloudRequestSignedUrl", () => {
73
73
  test("upload operation with just operation → posts correct body and parses response", async () => {
74
74
  const { calls, fetchMock } = captureFetch(() => {
75
75
  return new Response(
@@ -83,7 +83,7 @@ describe("platformRequestSignedUrl", () => {
83
83
  });
84
84
  globalThis.fetch = fetchMock;
85
85
 
86
- const result = await platformRequestSignedUrl(
86
+ const result = await cloudRequestSignedUrl(
87
87
  { operation: "upload" },
88
88
  VAK_TOKEN,
89
89
  PLATFORM_URL,
@@ -117,7 +117,7 @@ describe("platformRequestSignedUrl", () => {
117
117
  });
118
118
  globalThis.fetch = fetchMock;
119
119
 
120
- const result = await platformRequestSignedUrl(
120
+ const result = await cloudRequestSignedUrl(
121
121
  {
122
122
  operation: "upload",
123
123
  contentType: "application/octet-stream",
@@ -148,7 +148,7 @@ describe("platformRequestSignedUrl", () => {
148
148
  });
149
149
  globalThis.fetch = fetchMock;
150
150
 
151
- const result = await platformRequestSignedUrl(
151
+ const result = await cloudRequestSignedUrl(
152
152
  { operation: "download", bundleKey: "bundles/xyz.tar.gz" },
153
153
  VAK_TOKEN,
154
154
  PLATFORM_URL,
@@ -182,7 +182,7 @@ describe("platformRequestSignedUrl", () => {
182
182
  });
183
183
  globalThis.fetch = fetchMock;
184
184
 
185
- const result = await platformRequestSignedUrl(
185
+ const result = await cloudRequestSignedUrl(
186
186
  { operation: "upload" },
187
187
  VAK_TOKEN,
188
188
  PLATFORM_URL,
@@ -263,7 +263,7 @@ describe("platformRequestSignedUrl", () => {
263
263
  );
264
264
  globalThis.fetch = fetchMock as unknown as typeof globalThis.fetch;
265
265
 
266
- const result = await platformRequestSignedUrl(
266
+ const result = await cloudRequestSignedUrl(
267
267
  { operation: "upload" },
268
268
  SESSION_TOKEN,
269
269
  PLATFORM_URL,
@@ -305,7 +305,7 @@ describe("platformRequestSignedUrl", () => {
305
305
  });
306
306
  globalThis.fetch = fetchMock;
307
307
 
308
- await platformRequestSignedUrl(
308
+ await cloudRequestSignedUrl(
309
309
  {
310
310
  operation: "upload",
311
311
  minRuntimeVersion: "1.2.3",
@@ -335,7 +335,7 @@ describe("platformRequestSignedUrl", () => {
335
335
  });
336
336
  globalThis.fetch = fetchMock;
337
337
 
338
- await platformRequestSignedUrl(
338
+ await cloudRequestSignedUrl(
339
339
  {
340
340
  operation: "download",
341
341
  bundleKey: "bundles/dl-v.tar.gz",
@@ -370,7 +370,7 @@ describe("platformRequestSignedUrl", () => {
370
370
 
371
371
  let caught: unknown;
372
372
  try {
373
- await platformRequestSignedUrl(
373
+ await cloudRequestSignedUrl(
374
374
  {
375
375
  operation: "download",
376
376
  bundleKey: "bundles/foo.tar.gz",
@@ -414,7 +414,7 @@ describe("platformRequestSignedUrl", () => {
414
414
 
415
415
  let caught: unknown;
416
416
  try {
417
- await platformRequestSignedUrl(
417
+ await cloudRequestSignedUrl(
418
418
  {
419
419
  operation: "download",
420
420
  bundleKey: "bundles/foo.tar.gz",
@@ -444,7 +444,7 @@ describe("platformRequestSignedUrl", () => {
444
444
 
445
445
  let caught: unknown;
446
446
  try {
447
- await platformRequestSignedUrl(
447
+ await cloudRequestSignedUrl(
448
448
  { operation: "download", bundleKey: "bundles/foo.tar.gz" },
449
449
  VAK_TOKEN,
450
450
  PLATFORM_URL,
@@ -483,7 +483,7 @@ describe("platformRequestSignedUrl", () => {
483
483
 
484
484
  let caught: unknown;
485
485
  try {
486
- await platformRequestSignedUrl(
486
+ await cloudRequestSignedUrl(
487
487
  {
488
488
  operation: "download",
489
489
  bundleKey: "bundles/foo.tar.gz",
@@ -513,7 +513,7 @@ describe("platformRequestSignedUrl", () => {
513
513
  });
514
514
  globalThis.fetch = fetchMock;
515
515
 
516
- const result = await platformRequestSignedUrl(
516
+ const result = await cloudRequestSignedUrl(
517
517
  { operation: "download", bundleKey: "bundles/xyz.tar.gz" },
518
518
  VAK_TOKEN,
519
519
  PLATFORM_URL,
@@ -535,7 +535,7 @@ describe("platformRequestSignedUrl", () => {
535
535
  globalThis.fetch = fetchMock;
536
536
 
537
537
  await expect(
538
- platformRequestSignedUrl(
538
+ cloudRequestSignedUrl(
539
539
  { operation: "upload" },
540
540
  VAK_TOKEN,
541
541
  PLATFORM_URL,