@mycadet/cli 0.12.10 → 0.13.0-dev.202610171617.9ea363e
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 +13 -0
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
- package/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
- package/package.json +8 -3
- package/src/__tests__/assistant-client-cloud-auth.test.ts +2 -2
- package/src/__tests__/backup-ops-matching.test.ts +15 -13
- package/src/__tests__/backup-ops.test.ts +105 -0
- package/src/__tests__/backup.test.ts +114 -673
- package/src/__tests__/cloud-login-poll.test.ts +69 -0
- package/src/__tests__/cloud-releases.test.ts +82 -0
- package/src/__tests__/commands-mock.ts +101 -0
- package/src/__tests__/debug.test.ts +17 -91
- package/src/__tests__/devices.test.ts +64 -2
- package/src/__tests__/ensure-binary.test.ts +52 -0
- package/src/__tests__/gateway-token.test.ts +267 -0
- package/src/__tests__/hatch-next-steps.test.ts +38 -0
- package/src/__tests__/host-teleport-docker.test.ts +371 -0
- package/src/__tests__/host-teleport.test.ts +405 -0
- package/src/__tests__/multi-local.test.ts +3 -3
- package/src/__tests__/nginx-ingress.test.ts +587 -36
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/paired-lifecycle.test.ts +2 -2
- package/src/__tests__/ps-cloud-status.test.ts +8 -7
- package/src/__tests__/root-backup-docker.test.ts +294 -0
- package/src/__tests__/root-backup.test.ts +527 -0
- package/src/__tests__/root-restore-command.test.ts +147 -0
- package/src/__tests__/root-signing-key.test.ts +50 -0
- package/src/__tests__/setup.test.ts +17 -7
- package/src/__tests__/shell-mode.test.ts +83 -0
- package/src/__tests__/sleep.test.ts +11 -0
- package/src/__tests__/teleport.test.ts +68 -2996
- package/src/__tests__/tunnel-startup-summary.test.ts +110 -0
- package/src/__tests__/tunnel-stop.test.ts +226 -0
- package/src/__tests__/tunnel.test.ts +22 -4
- package/src/__tests__/upgrade-local.test.ts +21 -11
- package/src/__tests__/upgrade-replay-env.test.ts +2 -2
- package/src/__tests__/upgrade-root-recovery.test.ts +185 -0
- package/src/__tests__/wake.test.ts +4 -1
- package/src/__tests__/windows-lifecycle.test.ts +25 -0
- package/src/commands/backup.ts +44 -399
- package/src/commands/client.ts +3 -3
- package/src/commands/completions.ts +0 -2
- package/src/commands/debug.ts +75 -131
- package/src/commands/devices.ts +117 -47
- package/src/commands/gateway/token.ts +77 -23
- package/src/commands/hatch.ts +10 -52
- package/src/commands/pair.ts +9 -3
- package/src/commands/ps.ts +4 -2
- package/src/commands/restore.ts +77 -444
- package/src/commands/retire.ts +2 -3
- package/src/commands/sleep.ts +15 -2
- package/src/commands/teleport.ts +47 -1559
- package/src/commands/tunnel.ts +91 -11
- package/src/commands/upgrade.ts +84 -157
- package/src/commands/wake.ts +1 -1
- package/src/index.ts +0 -4
- package/src/lib/__tests__/http-client-version.test.ts +29 -0
- package/src/lib/__tests__/runtime-url.test.ts +3 -75
- package/src/lib/__tests__/web-dist.test.ts +27 -1
- package/src/lib/assistant-client.ts +1 -1
- package/src/lib/assistant-config.ts +43 -23
- package/src/lib/backup-ops.ts +37 -251
- package/src/lib/cloud-client.ts +10 -13
- package/src/lib/cloud-login-poll.ts +75 -0
- package/src/lib/cloud-login.ts +5 -2
- package/src/lib/cloud-releases.ts +54 -10
- package/src/lib/cloud-values.ts +1 -1
- package/src/lib/confirm-action.ts +2 -2
- package/src/lib/docker.ts +65 -34
- package/src/lib/ensure-binary.ts +65 -6
- package/src/lib/github-repository.ts +2 -0
- package/src/lib/guardian-token.ts +1 -23
- package/src/lib/hatch-local.ts +18 -52
- package/src/lib/hatch-next-steps.ts +15 -2
- package/src/lib/host-teleport.ts +506 -0
- package/src/lib/http-client.ts +25 -0
- package/src/lib/ingress-config.ts +31 -0
- package/src/lib/live-voice/tunnel.test.ts +1 -1
- package/src/lib/live-voice/tunnel.ts +3 -3
- package/src/lib/local.ts +10 -8
- package/src/lib/nginx-ingress.ts +572 -95
- package/src/lib/process.ts +13 -0
- package/src/lib/root-backup.ts +917 -0
- package/src/lib/root-signing-key.ts +31 -0
- package/src/lib/runtime-url.ts +0 -29
- package/src/lib/teleport-backup.ts +3 -16
- package/src/lib/tunnel.ts +87 -34
- package/src/lib/tunnels/cadet.ts +12 -1
- package/src/lib/tunnels/cloudflare.ts +36 -1
- package/src/lib/tunnels/ngrok.ts +16 -2
- package/src/lib/tunnels/process-lifecycle.ts +26 -0
- package/src/lib/tunnels/tailscale.ts +79 -1
- package/src/lib/upgrade-lifecycle.ts +63 -141
- package/src/lib/web-dist.ts +79 -1
- package/src/lib/xdg-log.ts +11 -0
- package/src/__tests__/bundle-staging.test.ts +0 -268
- package/src/__tests__/unpair.test.ts +0 -160
- package/src/commands/rollback.ts +0 -479
- package/src/commands/unpair.ts +0 -122
- package/src/lib/__tests__/job-polling.test.ts +0 -278
- package/src/lib/__tests__/local-runtime-client.test.ts +0 -666
- package/src/lib/bundle-staging.ts +0 -220
- package/src/lib/job-polling.ts +0 -195
- package/src/lib/local-runtime-client.ts +0 -374
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { afterEach, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
mkdtempSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
statSync,
|
|
7
|
+
symlinkSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { persistRootSigningKey } from "../lib/root-signing-key.js";
|
|
13
|
+
const directories: string[] = [];
|
|
14
|
+
function fixture() {
|
|
15
|
+
const root = mkdtempSync(join(tmpdir(), "root-signing-key-"));
|
|
16
|
+
directories.push(root);
|
|
17
|
+
return root;
|
|
18
|
+
}
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
for (const root of directories.splice(0)) {
|
|
21
|
+
rmSync(root, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
test("persists the raw key privately so offline capture retains installation identity", () => {
|
|
25
|
+
const root = fixture();
|
|
26
|
+
persistRootSigningKey(root, "07".repeat(32));
|
|
27
|
+
const file = join(root, "gateway-security", "actor-token-signing-key");
|
|
28
|
+
expect(readFileSync(file)).toEqual(Buffer.alloc(32, 7));
|
|
29
|
+
if (process.platform !== "win32") {
|
|
30
|
+
expect(statSync(file).mode & 0o777).toBe(0o600);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
test("replaces a key file link without changing its target", () => {
|
|
34
|
+
const root = fixture();
|
|
35
|
+
persistRootSigningKey(root, "07".repeat(32));
|
|
36
|
+
const target = join(root, "other");
|
|
37
|
+
writeFileSync(target, "unchanged");
|
|
38
|
+
const file = join(root, "gateway-security", "actor-token-signing-key");
|
|
39
|
+
rmSync(file);
|
|
40
|
+
symlinkSync(target, file);
|
|
41
|
+
persistRootSigningKey(root, "09".repeat(32));
|
|
42
|
+
expect(readFileSync(target, "utf8")).toBe("unchanged");
|
|
43
|
+
expect(readFileSync(file)).toEqual(Buffer.alloc(32, 9));
|
|
44
|
+
});
|
|
45
|
+
test("rejects invalid key data before writing service state", () => {
|
|
46
|
+
const root = fixture();
|
|
47
|
+
expect(() => persistRootSigningKey(root, "bad")).toThrow(
|
|
48
|
+
"Invalid gateway signing key",
|
|
49
|
+
);
|
|
50
|
+
});
|
|
@@ -62,7 +62,7 @@ function writeLockfile(entry: AssistantEntry): void {
|
|
|
62
62
|
const lockfileDir = process.env.CADET_LOCKFILE_DIR!;
|
|
63
63
|
mkdirSync(lockfileDir, { recursive: true });
|
|
64
64
|
writeFileSync(
|
|
65
|
-
join(lockfileDir, ".
|
|
65
|
+
join(lockfileDir, "lockfile.json"),
|
|
66
66
|
JSON.stringify(
|
|
67
67
|
{
|
|
68
68
|
assistants: [entry],
|
|
@@ -119,6 +119,10 @@ function installFetchStub(
|
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
if (url.endsWith("/v1/config/llm/default-provider")) {
|
|
123
|
+
return Response.json({ provider: "anthropic" });
|
|
124
|
+
}
|
|
125
|
+
|
|
122
126
|
if (url.endsWith("/v1/secrets/read")) {
|
|
123
127
|
return new Response(JSON.stringify({ found: false }), {
|
|
124
128
|
status: 200,
|
|
@@ -189,7 +193,9 @@ describe("setup command", () => {
|
|
|
189
193
|
|
|
190
194
|
await setup();
|
|
191
195
|
|
|
192
|
-
expect(fetchCalls[0].url).toBe(
|
|
196
|
+
expect(fetchCalls[0].url).toBe(
|
|
197
|
+
"http://127.0.0.1:3000/v1/config/llm/default-provider",
|
|
198
|
+
);
|
|
193
199
|
expect(fetchCalls[0].headers.get("Authorization")).toBe(
|
|
194
200
|
"Bearer guardian-token",
|
|
195
201
|
);
|
|
@@ -245,7 +251,9 @@ describe("setup command", () => {
|
|
|
245
251
|
// gate instead of burning retry attempts against it.
|
|
246
252
|
expect(fetchCalls[0].url).toBe("http://127.0.0.1:3000/readyz");
|
|
247
253
|
expect(fetchCalls[1].url).toBe("http://127.0.0.1:3000/v1/guardian/init");
|
|
248
|
-
expect(fetchCalls[2].url).toBe(
|
|
254
|
+
expect(fetchCalls[2].url).toBe(
|
|
255
|
+
"http://127.0.0.1:3000/v1/config/llm/default-provider",
|
|
256
|
+
);
|
|
249
257
|
expect(fetchCalls[2].headers.get("Authorization")).toBe(
|
|
250
258
|
"Bearer leased-guardian-token",
|
|
251
259
|
);
|
|
@@ -283,12 +291,14 @@ describe("setup command", () => {
|
|
|
283
291
|
assistantId: "assistant-123",
|
|
284
292
|
runtimeUrl: "https://assistant.example",
|
|
285
293
|
bearerToken: "entry-token",
|
|
286
|
-
cloud: "
|
|
294
|
+
cloud: "cloud",
|
|
287
295
|
});
|
|
288
296
|
|
|
289
297
|
await setup();
|
|
290
298
|
|
|
291
|
-
expect(fetchCalls[0].url).toBe(
|
|
299
|
+
expect(fetchCalls[0].url).toBe(
|
|
300
|
+
"https://assistant.example/v1/config/llm/default-provider",
|
|
301
|
+
);
|
|
292
302
|
expect(fetchCalls[0].headers.get("Authorization")).toBe(
|
|
293
303
|
"Bearer entry-token",
|
|
294
304
|
);
|
|
@@ -300,7 +310,7 @@ describe("setup command", () => {
|
|
|
300
310
|
assistantId: "assistant-123",
|
|
301
311
|
runtimeUrl: "https://assistant.example",
|
|
302
312
|
bearerToken: "entry-token",
|
|
303
|
-
cloud: "
|
|
313
|
+
cloud: "cloud",
|
|
304
314
|
});
|
|
305
315
|
saveGuardianToken(
|
|
306
316
|
"assistant-123",
|
|
@@ -329,7 +339,7 @@ describe("setup command", () => {
|
|
|
329
339
|
assistantId: "assistant-123",
|
|
330
340
|
runtimeUrl: "https://assistant.example",
|
|
331
341
|
bearerToken: "entry-token",
|
|
332
|
-
cloud: "
|
|
342
|
+
cloud: "cloud",
|
|
333
343
|
});
|
|
334
344
|
saveGuardianToken(
|
|
335
345
|
"assistant-123",
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
mkdirSync,
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
readRootProcesses,
|
|
14
|
+
stampRootProcess,
|
|
15
|
+
} from "../lib/assistant-config.js";
|
|
16
|
+
import {
|
|
17
|
+
DEFAULT_SHELL_MODE,
|
|
18
|
+
readShellMode,
|
|
19
|
+
saveShellMode,
|
|
20
|
+
} from "../lib/ingress-config.js";
|
|
21
|
+
|
|
22
|
+
// Create a temp directory and set CADET_LOCKFILE_DIR so the real lockfile
|
|
23
|
+
// helpers resolve inside it (the root-process-lockfile.test.ts pattern).
|
|
24
|
+
let testDir: string;
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
testDir = mkdtempSync(join(tmpdir(), "cadet-shell-mode-test-"));
|
|
28
|
+
mkdirSync(join(testDir, "config"), { recursive: true });
|
|
29
|
+
process.env.CADET_LOCKFILE_DIR = join(testDir, "config");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
delete process.env.CADET_LOCKFILE_DIR;
|
|
34
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function lockfile(): Record<string, unknown> {
|
|
38
|
+
return JSON.parse(
|
|
39
|
+
readFileSync(join(testDir, "config", "lockfile.json"), "utf-8"),
|
|
40
|
+
) as Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("ingress shell mode setting", () => {
|
|
44
|
+
test("an absent lockfile reads as the web default", () => {
|
|
45
|
+
expect(readShellMode()).toBe("web");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("a lockfile without the key reads as the web default", () => {
|
|
49
|
+
stampRootProcess("app", {
|
|
50
|
+
pid: 7840,
|
|
51
|
+
startedAt: "2026-10-16T00:00:00.000Z",
|
|
52
|
+
});
|
|
53
|
+
expect(readShellMode()).toBe(DEFAULT_SHELL_MODE);
|
|
54
|
+
expect(readShellMode()).toBe("web");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("saves and reads back the app mode", () => {
|
|
58
|
+
saveShellMode("app");
|
|
59
|
+
expect(readShellMode()).toBe("app");
|
|
60
|
+
saveShellMode("web");
|
|
61
|
+
expect(readShellMode()).toBe("web");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("saving lands at the lockfile root beside the app record", () => {
|
|
65
|
+
stampRootProcess("app", {
|
|
66
|
+
pid: 7840,
|
|
67
|
+
startedAt: "2026-10-16T00:00:00.000Z",
|
|
68
|
+
});
|
|
69
|
+
saveShellMode("app");
|
|
70
|
+
const saved = lockfile();
|
|
71
|
+
expect(saved.edgeShell).toBe("app");
|
|
72
|
+
// The lifecycle-scoped nginx record the setting configures survives.
|
|
73
|
+
expect(readRootProcesses().app?.pid).toBe(7840);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("an unrecognized value fails loudly instead of defaulting", () => {
|
|
77
|
+
writeFileSync(
|
|
78
|
+
join(testDir, "config", "lockfile.json"),
|
|
79
|
+
JSON.stringify({ edgeShell: "aps" }) + "\n",
|
|
80
|
+
);
|
|
81
|
+
expect(() => readShellMode()).toThrow('must be "web" or "app"');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -216,6 +216,12 @@ describe("sleep command", () => {
|
|
|
216
216
|
expect(stopProcessByPidFileMock).toHaveBeenCalledTimes(3);
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
+
test("default sleep passes the non-escalating service stopper", async () => {
|
|
220
|
+
process.argv = ["bun", "cadet", "sleep", "sleep-test"];
|
|
221
|
+
await sleep();
|
|
222
|
+
expect(stopProcessByPidFileMock).toHaveBeenNthCalledWith(1, join(assistantRootDir, "workspace", "cadet.pid"), "assistant", undefined, 45_000, realProcess.stopProcessGracefully);
|
|
223
|
+
});
|
|
224
|
+
|
|
219
225
|
test("force stops the assistant even when an active call lease exists", async () => {
|
|
220
226
|
writeLeaseFile(["call-active-1"]);
|
|
221
227
|
process.argv = ["bun", "cadet", "sleep", "sleep-test", "--force"];
|
|
@@ -238,6 +244,7 @@ describe("sleep command", () => {
|
|
|
238
244
|
"assistant",
|
|
239
245
|
undefined,
|
|
240
246
|
45_000,
|
|
247
|
+
expect.any(Function),
|
|
241
248
|
);
|
|
242
249
|
expect(stopProcessByPidFileMock).toHaveBeenNthCalledWith(
|
|
243
250
|
2,
|
|
@@ -245,12 +252,16 @@ describe("sleep command", () => {
|
|
|
245
252
|
"gateway",
|
|
246
253
|
undefined,
|
|
247
254
|
7000,
|
|
255
|
+
expect.any(Function),
|
|
248
256
|
);
|
|
249
257
|
// The CES sibling is stopped by its PID file; a no-op when absent.
|
|
250
258
|
expect(stopProcessByPidFileMock).toHaveBeenNthCalledWith(
|
|
251
259
|
3,
|
|
252
260
|
join(assistantRootDir, "ces.pid"),
|
|
253
261
|
"credential-executor",
|
|
262
|
+
undefined,
|
|
263
|
+
45_000,
|
|
264
|
+
expect.any(Function),
|
|
254
265
|
);
|
|
255
266
|
});
|
|
256
267
|
|