@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.
- package/README.md +2 -2
- package/knip.json +1 -1
- package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
- package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/{show-platform-login.ts → show-cloud-login.ts} +3 -5
- package/node_modules/@mycadet/assistant-api/index.ts +11 -11
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
- package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
- package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +1 -1
- 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
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
- package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
- package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
- package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
- package/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/package.json +3 -2
- package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
- package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
- package/src/__tests__/assistant-client-refresh.test.ts +2 -2
- package/src/__tests__/backup.test.ts +86 -91
- package/src/__tests__/client-token.test.ts +1 -1
- package/src/__tests__/client-tui-refresh.test.ts +1 -1
- package/src/__tests__/client-web-config.test.ts +2 -2
- package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
- package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +5 -5
- package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
- package/src/__tests__/connect-import.test.ts +55 -39
- package/src/__tests__/debug.test.ts +38 -38
- package/src/__tests__/guardian-token.test.ts +149 -56
- package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
- package/src/__tests__/helpers/guardian-token.ts +22 -0
- package/src/__tests__/helpers/http.ts +10 -0
- package/src/__tests__/nginx-ingress.test.ts +18 -18
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/pair.test.ts +1 -8
- package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -26
- package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
- package/src/__tests__/retire.test.ts +58 -199
- package/src/__tests__/root-process-lockfile.test.ts +99 -0
- package/src/__tests__/sleep.test.ts +19 -19
- package/src/__tests__/statefulset.test.ts +8 -8
- package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +1 -1
- package/src/__tests__/teleport-backup.test.ts +3 -3
- package/src/__tests__/teleport.test.ts +180 -196
- package/src/__tests__/tunnel.test.ts +255 -227
- package/src/__tests__/uninstall.test.ts +13 -4
- package/src/__tests__/upgrade-local.test.ts +3 -3
- package/src/__tests__/upgrade-replay-env.test.ts +12 -12
- package/src/__tests__/wake.test.ts +258 -41
- package/src/__tests__/workos-pkce.test.ts +2 -2
- package/src/commands/backup.ts +22 -22
- package/src/commands/client.ts +76 -76
- package/src/commands/debug.ts +20 -20
- package/src/commands/exec.ts +5 -5
- package/src/commands/hatch.ts +22 -22
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +1 -1
- package/src/commands/ps.ts +188 -79
- package/src/commands/restart.ts +30 -0
- package/src/commands/restore.ts +138 -20
- package/src/commands/retire.ts +46 -148
- package/src/commands/rollback.ts +18 -24
- package/src/commands/setup.ts +2 -2
- package/src/commands/teleport.ts +93 -93
- package/src/commands/terminal.ts +157 -17
- package/src/commands/tunnel.ts +80 -122
- package/src/commands/uninstall.ts +55 -7
- package/src/commands/unpair.ts +3 -3
- package/src/commands/upgrade.ts +22 -46
- package/src/commands/wake.ts +162 -46
- package/src/index.ts +3 -9
- package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
- package/src/lib/__tests__/job-polling.test.ts +1 -1
- package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
- package/src/lib/__tests__/runtime-url.test.ts +1 -1
- package/src/lib/assistant-api-key-resolution.ts +5 -5
- package/src/lib/assistant-client.ts +8 -8
- package/src/lib/assistant-config.ts +58 -12
- package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
- package/src/lib/cloud-login.ts +24 -24
- package/src/lib/{platform-releases.ts → cloud-releases.ts} +5 -5
- package/src/lib/cloud-values.ts +3 -0
- package/src/lib/config-utils.ts +1 -1
- package/src/lib/detached-process.ts +1 -1
- package/src/lib/docker.ts +111 -10
- package/src/lib/environments/__tests__/paths.test.ts +2 -2
- package/src/lib/environments/__tests__/resolve.test.ts +26 -26
- package/src/lib/environments/resolve.ts +8 -8
- package/src/lib/guardian-token.ts +36 -4
- package/src/lib/hatch-local.ts +19 -23
- package/src/lib/health-check.ts +6 -6
- package/src/lib/ingress-config.ts +8 -35
- package/src/lib/job-polling.ts +1 -1
- package/src/lib/live-voice/connection.ts +12 -12
- package/src/lib/live-voice/tunnel.test.ts +11 -11
- package/src/lib/live-voice/tunnel.ts +15 -15
- package/src/lib/local-runtime-client.ts +20 -20
- package/src/lib/local.ts +9 -30
- package/src/lib/nginx-ingress.ts +41 -14
- package/src/lib/process.ts +1 -1
- package/src/lib/provider-secrets.ts +3 -3
- package/src/lib/retire-apple-container.ts +1 -1
- package/src/lib/runtime-url.ts +8 -8
- package/src/lib/statefulset.ts +2 -2
- package/src/lib/sync-cloud-assistants.ts +18 -18
- package/src/lib/teleport-backup.ts +4 -4
- package/src/lib/terminal-client.ts +17 -17
- package/src/lib/terminal-session.ts +24 -24
- package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
- package/src/lib/tunnels/cadet.ts +29 -0
- package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +15 -4
- package/src/lib/tunnels/gateway-notify.ts +61 -0
- package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +201 -30
- package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +19 -5
- package/src/lib/upgrade-lifecycle.ts +3 -17
- package/src/lib/upgrade-preflight.ts +2 -2
- package/src/lib/workos-pkce.ts +5 -5
- package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
- package/src/commands/recover.ts +0 -131
- package/src/commands/roadmap.ts +0 -459
- package/src/commands/ssh.ts +0 -144
- package/src/commands/workflows.ts +0 -305
- 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
|
|
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
|
|
44
|
-
|
|
45
|
-
"
|
|
47
|
+
const getCloudUrlMock = spyOn(
|
|
48
|
+
cloudClient,
|
|
49
|
+
"getCloudUrl",
|
|
46
50
|
).mockReturnValue("http://platform.test");
|
|
47
51
|
|
|
48
|
-
// Per-test toggle for `
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
+
// Per-test toggle for `readCloudToken`.
|
|
53
|
+
const readCloudTokenMock = spyOn(
|
|
54
|
+
cloudClient,
|
|
55
|
+
"readCloudToken",
|
|
52
56
|
).mockReturnValue(null);
|
|
53
57
|
|
|
54
|
-
// `fetchCurrentUser` + `
|
|
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
|
-
|
|
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
|
|
66
|
-
|
|
67
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
126
|
-
|
|
132
|
+
getCloudUrlMock.mockRestore();
|
|
133
|
+
readCloudTokenMock.mockRestore();
|
|
127
134
|
fetchCurrentUserMock.mockRestore();
|
|
128
|
-
|
|
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
|
-
|
|
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
|
|
165
|
+
// Structural guarantee: we never even tried to talk to the cloud.
|
|
159
166
|
expect(fetchCurrentUserMock).not.toHaveBeenCalled();
|
|
160
|
-
expect(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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("
|
|
178
|
+
describe("restore --help", () => {
|
|
179
179
|
test("prints usage, description, and examples then exits 0", async () => {
|
|
180
|
-
process.argv = ["bun", "cadet", "
|
|
181
|
-
await expect(
|
|
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
|
|
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
|
|
186
|
+
expect(output).toContain("cadet restore");
|
|
186
187
|
});
|
|
187
188
|
});
|
|
188
189
|
|
|
189
|
-
describe("
|
|
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", "
|
|
215
|
-
await expect(
|
|
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
|
|
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", "
|
|
245
|
-
await
|
|
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", "
|
|
260
|
-
await expect(
|
|
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", "
|
|
269
|
-
await expect(
|
|
270
|
-
expect(consoleErrorSpy.mock.calls[0][0]).toContain("
|
|
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", "
|
|
275
|
-
await expect(
|
|
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", "
|
|
296
|
-
await expect(
|
|
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", "
|
|
307
|
-
await expect(
|
|
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("
|
|
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", "
|
|
325
|
-
await
|
|
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", "
|
|
359
|
-
await
|
|
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);
|