@mycadet/cli 0.12.7 → 0.12.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +2 -2
  2. package/knip.json +1 -1
  3. package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
  4. package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
  5. package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
  6. package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
  7. package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
  8. package/node_modules/@mycadet/assistant-api/events/{show-platform-login.ts → show-cloud-login.ts} +3 -5
  9. package/node_modules/@mycadet/assistant-api/index.ts +11 -11
  10. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +1 -1
  11. package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
  12. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  13. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  14. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  15. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  16. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  17. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  18. package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
  19. package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
  20. package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
  21. package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
  22. package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  23. package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  24. package/node_modules/@mycadet/environments/src/types.ts +6 -6
  25. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
  26. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
  27. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  28. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  29. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
  30. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +1 -1
  31. package/node_modules/@mycadet/{assistant-api/node_modules/@mycadet/service-contracts/src/platform-credential.ts → local-mode/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
  32. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  33. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  34. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  35. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  36. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  37. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  38. package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
  39. package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
  40. package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
  41. package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
  42. package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
  43. package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
  44. package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
  45. package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
  46. package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
  47. package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
  48. package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
  49. package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
  50. package/node_modules/@mycadet/service-contracts/package.json +1 -1
  51. package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
  52. package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  53. package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  54. package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  55. package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  56. package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  57. package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  58. package/package.json +3 -2
  59. package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
  60. package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
  61. package/src/__tests__/assistant-client-refresh.test.ts +2 -2
  62. package/src/__tests__/backup.test.ts +86 -91
  63. package/src/__tests__/client-token.test.ts +1 -1
  64. package/src/__tests__/client-tui-refresh.test.ts +1 -1
  65. package/src/__tests__/client-web-config.test.ts +2 -2
  66. package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
  67. package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +5 -5
  68. package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
  69. package/src/__tests__/connect-import.test.ts +55 -39
  70. package/src/__tests__/debug.test.ts +38 -38
  71. package/src/__tests__/guardian-token.test.ts +149 -56
  72. package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
  73. package/src/__tests__/helpers/guardian-token.ts +22 -0
  74. package/src/__tests__/helpers/http.ts +10 -0
  75. package/src/__tests__/nginx-ingress.test.ts +18 -18
  76. package/src/__tests__/orphan-detection.test.ts +1 -1
  77. package/src/__tests__/pair.test.ts +1 -8
  78. package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -26
  79. package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
  80. package/src/__tests__/retire.test.ts +58 -199
  81. package/src/__tests__/root-process-lockfile.test.ts +99 -0
  82. package/src/__tests__/sleep.test.ts +19 -19
  83. package/src/__tests__/statefulset.test.ts +8 -8
  84. package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +1 -1
  85. package/src/__tests__/teleport-backup.test.ts +3 -3
  86. package/src/__tests__/teleport.test.ts +180 -196
  87. package/src/__tests__/tunnel.test.ts +255 -227
  88. package/src/__tests__/uninstall.test.ts +13 -4
  89. package/src/__tests__/upgrade-local.test.ts +3 -3
  90. package/src/__tests__/upgrade-replay-env.test.ts +12 -12
  91. package/src/__tests__/wake.test.ts +258 -41
  92. package/src/__tests__/workos-pkce.test.ts +2 -2
  93. package/src/commands/backup.ts +22 -22
  94. package/src/commands/client.ts +76 -76
  95. package/src/commands/debug.ts +20 -20
  96. package/src/commands/exec.ts +5 -5
  97. package/src/commands/hatch.ts +22 -22
  98. package/src/commands/login.ts +22 -22
  99. package/src/commands/logs.ts +1 -1
  100. package/src/commands/ps.ts +188 -79
  101. package/src/commands/restart.ts +30 -0
  102. package/src/commands/restore.ts +138 -20
  103. package/src/commands/retire.ts +46 -148
  104. package/src/commands/rollback.ts +18 -24
  105. package/src/commands/setup.ts +2 -2
  106. package/src/commands/teleport.ts +93 -93
  107. package/src/commands/terminal.ts +157 -17
  108. package/src/commands/tunnel.ts +80 -122
  109. package/src/commands/uninstall.ts +55 -7
  110. package/src/commands/unpair.ts +3 -3
  111. package/src/commands/upgrade.ts +22 -46
  112. package/src/commands/wake.ts +162 -46
  113. package/src/index.ts +3 -9
  114. package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
  115. package/src/lib/__tests__/job-polling.test.ts +1 -1
  116. package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
  117. package/src/lib/__tests__/runtime-url.test.ts +1 -1
  118. package/src/lib/assistant-api-key-resolution.ts +5 -5
  119. package/src/lib/assistant-client.ts +8 -8
  120. package/src/lib/assistant-config.ts +58 -12
  121. package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
  122. package/src/lib/cloud-login.ts +24 -24
  123. package/src/lib/{platform-releases.ts → cloud-releases.ts} +5 -5
  124. package/src/lib/cloud-values.ts +3 -0
  125. package/src/lib/config-utils.ts +1 -1
  126. package/src/lib/detached-process.ts +1 -1
  127. package/src/lib/docker.ts +111 -10
  128. package/src/lib/environments/__tests__/paths.test.ts +2 -2
  129. package/src/lib/environments/__tests__/resolve.test.ts +26 -26
  130. package/src/lib/environments/resolve.ts +8 -8
  131. package/src/lib/guardian-token.ts +36 -4
  132. package/src/lib/hatch-local.ts +19 -23
  133. package/src/lib/health-check.ts +6 -6
  134. package/src/lib/ingress-config.ts +8 -35
  135. package/src/lib/job-polling.ts +1 -1
  136. package/src/lib/live-voice/connection.ts +12 -12
  137. package/src/lib/live-voice/tunnel.test.ts +11 -11
  138. package/src/lib/live-voice/tunnel.ts +15 -15
  139. package/src/lib/local-runtime-client.ts +20 -20
  140. package/src/lib/local.ts +9 -30
  141. package/src/lib/nginx-ingress.ts +41 -14
  142. package/src/lib/process.ts +1 -1
  143. package/src/lib/provider-secrets.ts +3 -3
  144. package/src/lib/retire-apple-container.ts +1 -1
  145. package/src/lib/runtime-url.ts +8 -8
  146. package/src/lib/statefulset.ts +2 -2
  147. package/src/lib/sync-cloud-assistants.ts +18 -18
  148. package/src/lib/teleport-backup.ts +4 -4
  149. package/src/lib/terminal-client.ts +17 -17
  150. package/src/lib/terminal-session.ts +24 -24
  151. package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
  152. package/src/lib/tunnels/cadet.ts +29 -0
  153. package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +15 -4
  154. package/src/lib/tunnels/gateway-notify.ts +61 -0
  155. package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +201 -30
  156. package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +19 -5
  157. package/src/lib/upgrade-lifecycle.ts +3 -17
  158. package/src/lib/upgrade-preflight.ts +2 -2
  159. package/src/lib/workos-pkce.ts +5 -5
  160. package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
  161. package/src/commands/recover.ts +0 -131
  162. package/src/commands/roadmap.ts +0 -459
  163. package/src/commands/ssh.ts +0 -144
  164. package/src/commands/workflows.ts +0 -305
  165. package/src/lib/managed-cloud.ts +0 -3
@@ -20,14 +20,7 @@ const originalEnvironment = process.env.CADET_ENVIRONMENT;
20
20
  process.env.CADET_ENVIRONMENT = "production";
21
21
 
22
22
  import { buildAppConnectUrl, pair } from "../commands/pair.js";
23
-
24
- // bun-types' global fetch carries a `preconnect` member; these mocks only need
25
- // the call shape, so hand the assignment a member-complete callable.
26
- function withPreconnect(
27
- impl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>,
28
- ): typeof fetch {
29
- return Object.assign(impl, { preconnect: () => {} });
30
- }
23
+ import { withPreconnect } from "./helpers/http.js";
31
24
 
32
25
  // Distinct loopback (mint) vs reachable (advertised) URLs to verify the split.
33
26
  const LOCAL_URL = "http://127.0.0.1:7830";
@@ -26,12 +26,16 @@ process.env.CADET_LOCKFILE_DIR = testDir;
26
26
  import * as assistantConfig from "../lib/assistant-config.js";
27
27
  import * as healthCheck from "../lib/health-check.js";
28
28
  import * as orphanDetection from "../lib/orphan-detection.js";
29
- import * as platformClient from "../lib/platform-client.js";
29
+ import * as cloudClient from "../lib/cloud-client.js";
30
30
 
31
31
  const loadAllAssistantsMock = spyOn(
32
32
  assistantConfig,
33
33
  "loadAllAssistants",
34
34
  ).mockReturnValue([]);
35
+ const readRootProcessesMock = spyOn(
36
+ assistantConfig,
37
+ "readRootProcesses",
38
+ ).mockReturnValue({});
35
39
  const getActiveAssistantMock = spyOn(
36
40
  assistantConfig,
37
41
  "getActiveAssistant",
@@ -40,31 +44,31 @@ const detectOrphanedProcessesMock = spyOn(
40
44
  orphanDetection,
41
45
  "detectOrphanedProcesses",
42
46
  ).mockResolvedValue([]);
43
- const getPlatformUrlMock = spyOn(
44
- platformClient,
45
- "getPlatformUrl",
47
+ const getCloudUrlMock = spyOn(
48
+ cloudClient,
49
+ "getCloudUrl",
46
50
  ).mockReturnValue("http://platform.test");
47
51
 
48
- // Per-test toggle for `readPlatformToken`.
49
- const readPlatformTokenMock = spyOn(
50
- platformClient,
51
- "readPlatformToken",
52
+ // Per-test toggle for `readCloudToken`.
53
+ const readCloudTokenMock = spyOn(
54
+ cloudClient,
55
+ "readCloudToken",
52
56
  ).mockReturnValue(null);
53
57
 
54
- // `fetchCurrentUser` + `fetchPlatformAssistants` are spied so we can assert
58
+ // `fetchCurrentUser` + `fetchCloudAssistants` are spied so we can assert
55
59
  // they're never invoked on the no-token path, and re-shaped per-test for the
56
60
  // token-but-unreachable path.
57
61
  const fetchCurrentUserMock = spyOn(
58
- platformClient,
62
+ cloudClient,
59
63
  "fetchCurrentUser",
60
64
  ).mockResolvedValue({
61
65
  id: "u1",
62
66
  email: "test@example.com",
63
67
  display: "Test",
64
68
  });
65
- const fetchPlatformAssistantsMock = spyOn(
66
- platformClient,
67
- "fetchPlatformAssistants",
69
+ const fetchCloudAssistantsMock = spyOn(
70
+ cloudClient,
71
+ "fetchCloudAssistants",
68
72
  ).mockResolvedValue([]);
69
73
  const checkManagedHealthMock = spyOn(
70
74
  healthCheck,
@@ -99,33 +103,36 @@ beforeEach(() => {
99
103
  }) as typeof process.stdout.write;
100
104
  });
101
105
 
106
+ const NL = String.fromCharCode(10);
107
+
102
108
  afterEach(() => {
103
109
  console.log = originalLog;
104
110
  console.error = originalError;
105
111
  process.stdout.write = originalStdoutWrite;
106
112
  loadAllAssistantsMock.mockReturnValue([]);
107
113
  getActiveAssistantMock.mockReturnValue(null);
108
- readPlatformTokenMock.mockReturnValue(null);
114
+ readCloudTokenMock.mockReturnValue(null);
109
115
  fetchCurrentUserMock.mockReset();
110
116
  fetchCurrentUserMock.mockResolvedValue({
111
117
  id: "u1",
112
118
  email: "test@example.com",
113
119
  display: "Test",
114
120
  });
115
- fetchPlatformAssistantsMock.mockReset();
116
- fetchPlatformAssistantsMock.mockResolvedValue([]);
121
+ fetchCloudAssistantsMock.mockReset();
122
+ fetchCloudAssistantsMock.mockResolvedValue([]);
117
123
  checkManagedHealthMock.mockReset();
118
124
  checkManagedHealthMock.mockResolvedValue({ status: "healthy", detail: null });
125
+ readRootProcessesMock.mockReturnValue({});
119
126
  });
120
127
 
121
128
  afterAll(() => {
122
129
  loadAllAssistantsMock.mockRestore();
123
130
  getActiveAssistantMock.mockRestore();
124
131
  detectOrphanedProcessesMock.mockRestore();
125
- getPlatformUrlMock.mockRestore();
126
- readPlatformTokenMock.mockRestore();
132
+ getCloudUrlMock.mockRestore();
133
+ readCloudTokenMock.mockRestore();
127
134
  fetchCurrentUserMock.mockRestore();
128
- fetchPlatformAssistantsMock.mockRestore();
135
+ fetchCloudAssistantsMock.mockRestore();
129
136
  checkManagedHealthMock.mockRestore();
130
137
  rmSync(testDir, { recursive: true, force: true });
131
138
  });
@@ -142,7 +149,7 @@ import { listAllAssistants } from "../commands/ps.js";
142
149
 
143
150
  describe("cadet ps — platform status line", () => {
144
151
  test("no local token: no login-status line, skips ALL network fetches", async () => {
145
- readPlatformTokenMock.mockReturnValue(null);
152
+ readCloudTokenMock.mockReturnValue(null);
146
153
 
147
154
  await listAllAssistants(false);
148
155
 
@@ -155,20 +162,20 @@ describe("cadet ps — platform status line", () => {
155
162
  false,
156
163
  );
157
164
 
158
- // Structural guarantee: we never even tried to talk to the platform.
165
+ // Structural guarantee: we never even tried to talk to the cloud.
159
166
  expect(fetchCurrentUserMock).not.toHaveBeenCalled();
160
- expect(fetchPlatformAssistantsMock).not.toHaveBeenCalled();
167
+ expect(fetchCloudAssistantsMock).not.toHaveBeenCalled();
161
168
  });
162
169
 
163
170
  test("local token present but platform is unreachable: no status line, no leaked org-fetch error", async () => {
164
- readPlatformTokenMock.mockReturnValue("session_abc123");
171
+ readCloudTokenMock.mockReturnValue("session_abc123");
165
172
  // Simulate the exact Bun connect failure the user reported:
166
173
  // "Unable to connect. Is the computer able to access the url?"
167
174
  const connectError = new Error(
168
175
  "Unable to connect. Is the computer able to access the url?",
169
176
  );
170
177
  fetchCurrentUserMock.mockRejectedValue(connectError);
171
- fetchPlatformAssistantsMock.mockRejectedValue(connectError);
178
+ fetchCloudAssistantsMock.mockRejectedValue(connectError);
172
179
 
173
180
  await listAllAssistants(false);
174
181
 
@@ -183,13 +190,13 @@ describe("cadet ps — platform status line", () => {
183
190
  });
184
191
 
185
192
  test("local token present and platform reachable: no login-status line", async () => {
186
- readPlatformTokenMock.mockReturnValue("session_abc123");
193
+ readCloudTokenMock.mockReturnValue("session_abc123");
187
194
  fetchCurrentUserMock.mockResolvedValue({
188
195
  id: "u1",
189
196
  email: "user@example.com",
190
197
  display: "Example User",
191
198
  });
192
- fetchPlatformAssistantsMock.mockResolvedValue([]);
199
+ fetchCloudAssistantsMock.mockResolvedValue([]);
193
200
 
194
201
  await listAllAssistants(false);
195
202
 
@@ -217,6 +224,52 @@ describe("cadet ps — platform status line", () => {
217
224
  }
218
225
  });
219
226
 
227
+ test("CLI-managed root processes nest under the assistant they front", async () => {
228
+ loadAllAssistantsMock.mockReturnValue([
229
+ {
230
+ assistantId: "assistant-123",
231
+ name: "Alice",
232
+ runtimeUrl: "https://platform.example",
233
+ cloud: "cadet",
234
+ },
235
+ {
236
+ assistantId: "assistant-456",
237
+ name: "Bob",
238
+ runtimeUrl: "https://platform.example/b",
239
+ cloud: "cadet",
240
+ },
241
+ ]);
242
+ getActiveAssistantMock.mockReturnValue(null);
243
+ checkManagedHealthMock.mockImplementation(async () => ({
244
+ status: "healthy",
245
+ detail: null,
246
+ }));
247
+ // The tunnel fronts Alice (recorded id); the edge records no assistant.
248
+ readRootProcessesMock.mockReturnValue({
249
+ tunnel: {
250
+ pid: 4242,
251
+ provider: "ngrok",
252
+ startedAt: "2026-10-11T00:00:00.000Z",
253
+ assistantId: "assistant-123",
254
+ },
255
+ app: { pid: 77, startedAt: "2026-10-11T00:00:00.000Z" },
256
+ });
257
+
258
+ await listAllAssistants(false);
259
+
260
+ const output = stdout.join(NL);
261
+ // The tunnel nests under Alice — its name is just the provider now…
262
+ expect(output).toContain("└ ngrok");
263
+ // …the unattributed edge renders machine-level…
264
+ expect(output).toContain("nginx edge");
265
+ // …the type column distinguishes the three process kinds…
266
+ expect(output).toContain("🧑‍🚀 Assistant");
267
+ expect(output).toContain("🚇 Tunnel");
268
+ expect(output).toContain("🌐 App");
269
+ // …and the second table is gone: one header row only.
270
+ expect(output.match(/NAME/g)?.length).toBe(1);
271
+ });
272
+
220
273
  test("assistant rows use display name as primary label and keep id visible", async () => {
221
274
  loadAllAssistantsMock.mockReturnValue([
222
275
  {
@@ -61,7 +61,7 @@ afterAll(() => {
61
61
  mock.module("../lib/step-runner.js", () => ({ exec: realExec }));
62
62
  });
63
63
 
64
- import { recover } from "../commands/recover.js";
64
+ import { restore } from "../commands/restore.js";
65
65
 
66
66
  // ── Test fixtures ─────────────────────────────────────────────────────────────
67
67
 
@@ -175,18 +175,19 @@ afterAll(() => {
175
175
 
176
176
  // ── Tests ─────────────────────────────────────────────────────────────────────
177
177
 
178
- describe("recover --help", () => {
178
+ describe("restore --help", () => {
179
179
  test("prints usage, description, and examples then exits 0", async () => {
180
- process.argv = ["bun", "cadet", "recover", "--help"];
181
- await expect(recover()).rejects.toThrow("process.exit(0)");
180
+ process.argv = ["bun", "cadet", "restore", "--help"];
181
+ await expect(restore()).rejects.toThrow("process.exit(0)");
182
182
  const output = consoleLogSpy.mock.calls.flat().join("\n");
183
- expect(output).toContain("Usage: cadet recover <name>");
183
+ expect(output).toContain("Usage: cadet restore <name>");
184
+ expect(output).toContain("recovers a retired assistant");
184
185
  expect(output).toContain("Examples:");
185
- expect(output).toContain("cadet recover");
186
+ expect(output).toContain("cadet restore");
186
187
  });
187
188
  });
188
189
 
189
- describe("recover error cases", () => {
190
+ describe("restore error cases", () => {
190
191
  for (const failure of ["missing", "conflict"] as const) {
191
192
  test(`recovery ${failure} leaves destination and registry untouched and permits retry`, async () => {
192
193
  const name = `retry-${failure}`;
@@ -211,8 +212,8 @@ describe("recover error cases", () => {
211
212
  }
212
213
  const registry = resolveLockfilePaths(process.env)[0]!;
213
214
  const before = readFileSync(registry, "utf8");
214
- process.argv = ["bun", "cadet", "recover", name];
215
- await expect(recover()).rejects.toThrow(`signing key ${failure}`);
215
+ process.argv = ["bun", "cadet", "restore", name];
216
+ await expect(restore()).rejects.toThrow(`signing key ${failure}`);
216
217
  expect(existsSync(instanceDir)).toBe(false);
217
218
  expect(readFileSync(registry, "utf8")).toBe(before);
218
219
  expect(existsSync(archivePath)).toBe(true);
@@ -222,7 +223,7 @@ describe("recover error cases", () => {
222
223
  // Restore the authoritative identity in the archive metadata and retry.
223
224
  entry.resources!.signingKey = "ab".repeat(32);
224
225
  writeFileSync(metadataPath, JSON.stringify(entry));
225
- await recover();
226
+ await restore();
226
227
  expect(existsSync(instanceDir)).toBe(true);
227
228
  expect(existsSync(archivePath)).toBe(false);
228
229
  expect(startGatewayMock).toHaveBeenCalledTimes(1);
@@ -241,8 +242,8 @@ describe("recover error cases", () => {
241
242
  join(dir, "actor-token-signing-key"),
242
243
  Buffer.from("cd".repeat(32), "hex"),
243
244
  );
244
- process.argv = ["bun", "cadet", "recover", name];
245
- await recover();
245
+ process.argv = ["bun", "cadet", "restore", name];
246
+ await restore();
246
247
  expect(startGatewayMock).toHaveBeenCalledWith(
247
248
  false,
248
249
  expect.objectContaining({ signingKey: "cd".repeat(32) }),
@@ -256,8 +257,8 @@ describe("recover error cases", () => {
256
257
  const entry = makeEntry(name, join(testDir, "cadet", "assistants", name));
257
258
  delete entry.resources!.signingKey;
258
259
  const { archivePath, metadataPath } = writeArchiveFixtures(name, entry);
259
- process.argv = ["bun", "cadet", "recover", name];
260
- await expect(recover()).rejects.toThrow("signing key missing");
260
+ process.argv = ["bun", "cadet", "restore", name];
261
+ await expect(restore()).rejects.toThrow("signing key missing");
261
262
  expect(existsSync(archivePath)).toBe(true);
262
263
  expect(existsSync(metadataPath)).toBe(true);
263
264
  expect(startLocalDaemonMock).not.toHaveBeenCalled();
@@ -265,14 +266,14 @@ describe("recover error cases", () => {
265
266
  });
266
267
 
267
268
  test("exits 1 when no name is given", async () => {
268
- process.argv = ["bun", "cadet", "recover"];
269
- await expect(recover()).rejects.toThrow("process.exit(1)");
270
- expect(consoleErrorSpy.mock.calls[0][0]).toContain("Usage:");
269
+ process.argv = ["bun", "cadet", "restore"];
270
+ await expect(restore()).rejects.toThrow("process.exit(1)");
271
+ expect(consoleErrorSpy.mock.calls[0][0]).toContain("<name> is required");
271
272
  });
272
273
 
273
274
  test("exits 1 when archive is missing", async () => {
274
- process.argv = ["bun", "cadet", "recover", "ghost-assistant"];
275
- await expect(recover()).rejects.toThrow("process.exit(1)");
275
+ process.argv = ["bun", "cadet", "restore", "ghost-assistant"];
276
+ await expect(restore()).rejects.toThrow("process.exit(1)");
276
277
  expect(consoleErrorSpy.mock.calls[0][0]).toContain(
277
278
  "No retired archive found for 'ghost-assistant'",
278
279
  );
@@ -292,8 +293,8 @@ describe("recover error cases", () => {
292
293
  join(retiredDir, `${name}.json`),
293
294
  JSON.stringify(entry, null, 2) + "\n",
294
295
  );
295
- process.argv = ["bun", "cadet", "recover", name];
296
- await expect(recover()).rejects.toThrow("missing resource configuration");
296
+ process.argv = ["bun", "cadet", "restore", name];
297
+ await expect(restore()).rejects.toThrow("missing resource configuration");
297
298
  });
298
299
 
299
300
  test("exits 1 when target .cadet/ already exists", async () => {
@@ -303,13 +304,13 @@ describe("recover error cases", () => {
303
304
  writeArchiveFixtures(name, entry);
304
305
  // Pre-create the collision path that recover checks
305
306
  mkdirSync(join(instanceDir, ".cadet"), { recursive: true });
306
- process.argv = ["bun", "cadet", "recover", name];
307
- await expect(recover()).rejects.toThrow("process.exit(1)");
307
+ process.argv = ["bun", "cadet", "restore", name];
308
+ await expect(restore()).rejects.toThrow("process.exit(1)");
308
309
  expect(consoleErrorSpy.mock.calls[0][0]).toContain("already exists");
309
310
  });
310
311
  });
311
312
 
312
- describe("recover extraction path — named instance (instanceDir !== homedir())", () => {
313
+ describe("archive-recovery extraction path — named instance (instanceDir !== homedir())", () => {
313
314
  test("extracts to retiredDir and renames staging dir to instanceDir directly", async () => {
314
315
  const name = "named-instance";
315
316
  const instanceDir = join(testDir, "custom-location", name);
@@ -321,8 +322,8 @@ describe("recover extraction path — named instance (instanceDir !== homedir())
321
322
  // Parent dir must exist for renameSync
322
323
  mkdirSync(join(testDir, "custom-location"), { recursive: true });
323
324
 
324
- process.argv = ["bun", "cadet", "recover", name];
325
- await recover();
325
+ process.argv = ["bun", "cadet", "restore", name];
326
+ await restore();
326
327
 
327
328
  // exec must have been called with -C retiredDir
328
329
  expect(execMock).toHaveBeenCalledTimes(1);
@@ -355,8 +356,8 @@ describe("recover extraction path — named instance (instanceDir !== homedir())
355
356
  const entry = makeEntry(name, instanceDir);
356
357
  const { archivePath, extractedPath } = writeArchiveFixtures(name, entry);
357
358
 
358
- process.argv = ["bun", "cadet", "recover", name];
359
- await recover();
359
+ process.argv = ["bun", "cadet", "restore", name];
360
+ await restore();
360
361
 
361
362
  expect(existsSync(extractedPath)).toBe(false);
362
363
  expect(existsSync(instanceDir)).toBe(true);