@mycadet/cli 0.12.6-dev.202610091338.ee7f3e3 → 0.12.7-dev.202610102207.24bf931

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 (51) hide show
  1. package/package.json +5 -4
  2. package/src/__tests__/backup.test.ts +40 -45
  3. package/src/__tests__/connect-import.test.ts +55 -39
  4. package/src/__tests__/debug.test.ts +24 -24
  5. package/src/__tests__/guardian-token.test.ts +149 -56
  6. package/src/__tests__/hatch-provider-secrets.test.ts +3 -3
  7. package/src/__tests__/helpers/guardian-token.ts +22 -0
  8. package/src/__tests__/helpers/http.ts +10 -0
  9. package/src/__tests__/nginx-ingress.test.ts +78 -86
  10. package/src/__tests__/pair.test.ts +128 -121
  11. package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
  12. package/src/__tests__/sleep.test.ts +19 -19
  13. package/src/__tests__/teleport.test.ts +55 -71
  14. package/src/__tests__/tunnel.test.ts +135 -154
  15. package/src/__tests__/uninstall.test.ts +180 -0
  16. package/src/__tests__/wake.test.ts +242 -32
  17. package/src/commands/client.ts +76 -34
  18. package/src/commands/ps.ts +1 -0
  19. package/src/commands/restart.ts +30 -0
  20. package/src/commands/restore.ts +125 -7
  21. package/src/commands/rollback.ts +9 -15
  22. package/src/commands/terminal.ts +153 -13
  23. package/src/commands/uninstall.ts +276 -0
  24. package/src/commands/upgrade.ts +2 -19
  25. package/src/commands/wake.ts +157 -37
  26. package/src/index.ts +5 -9
  27. package/src/lib/assistant-config.ts +10 -22
  28. package/src/lib/backup-ops.ts +3 -4
  29. package/src/lib/client-identity.ts +8 -10
  30. package/src/lib/docker.ts +107 -6
  31. package/src/lib/guardian-token.ts +104 -18
  32. package/src/lib/hatch-local.ts +13 -17
  33. package/src/lib/health-check.ts +24 -15
  34. package/src/lib/local-hatch-lock.ts +19 -27
  35. package/src/lib/local-runtime-client.ts +25 -14
  36. package/src/lib/nginx-ingress.ts +9 -3
  37. package/src/lib/ngrok.ts +48 -17
  38. package/src/lib/platform-client.ts +141 -72
  39. package/src/lib/retire-archive.ts +4 -4
  40. package/src/lib/retire-local.ts +2 -25
  41. package/src/lib/teleport-backup.ts +22 -13
  42. package/src/lib/terminal-client.ts +19 -12
  43. package/src/lib/terminal-session.ts +4 -4
  44. package/src/lib/tunnel-edge.ts +11 -13
  45. package/src/lib/upgrade-lifecycle.ts +16 -18
  46. package/src/lib/workos-pkce.ts +51 -25
  47. package/src/commands/recover.ts +0 -131
  48. package/src/commands/roadmap.ts +0 -459
  49. package/src/commands/ssh.ts +0 -144
  50. package/src/commands/workflows.ts +0 -305
  51. package/src/lib/json.ts +0 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mycadet/cli",
3
- "version": "0.12.6-dev.202610091338.ee7f3e3",
3
+ "version": "0.12.7-dev.202610102207.24bf931",
4
4
  "description": "CLI tools for mycadet",
5
5
  "type": "module",
6
6
  "exports": {
@@ -15,12 +15,12 @@
15
15
  "./src/commands/*": "./src/commands/*.ts"
16
16
  },
17
17
  "bin": {
18
- "cadet": "./src/index.ts"
18
+ "cadet-dev": "./src/index.ts"
19
19
  },
20
20
  "scripts": {
21
21
  "format": "prettier --write .",
22
22
  "format:check": "prettier --check .",
23
- "lint": "eslint --max-warnings 696",
23
+ "lint": "eslint --max-warnings 326",
24
24
  "lint:unused": "knip --include files,dependencies,unlisted",
25
25
  "compile:check": "bun build --compile src/index.ts --outfile /dev/null",
26
26
  "test": "bun run scripts/run-tests.ts",
@@ -40,7 +40,8 @@
40
40
  "nanoid": "5.1.7",
41
41
  "qrcode-terminal": "0.12.0",
42
42
  "react": "19.2.6",
43
- "react-devtools-core": "7.0.1"
43
+ "react-devtools-core": "7.0.1",
44
+ "zod": "4.3.6"
44
45
  },
45
46
  "bundledDependencies": [
46
47
  "@mycadet/assistant-api",
@@ -5,6 +5,7 @@ import {
5
5
  describe,
6
6
  expect,
7
7
  mock,
8
+ type Mock,
8
9
  spyOn,
9
10
  test,
10
11
  } from "bun:test";
@@ -79,6 +80,7 @@ const localRuntimePollJobStatusMock = spyOn(
79
80
  result: { manifest_sha256: "abc123def456" },
80
81
  });
81
82
 
83
+ /** A complete-but-fake guardian token; the SUT only reads the fields it sets. */
82
84
  // Mode 1 (runtime-direct local backup) uses guardian tokens. Don't exercise
83
85
  // it here, but the spies need to exist so the module under test can import
84
86
  // them without surprises. Saved to variables so afterAll can restore them —
@@ -87,31 +89,25 @@ const localRuntimePollJobStatusMock = spyOn(
87
89
  const loadGuardianTokenSpy = spyOn(
88
90
  guardianToken,
89
91
  "loadGuardianToken",
90
- ).mockReturnValue({
91
- accessToken: "local-token",
92
- accessTokenExpiresAt: new Date(Date.now() + 60_000).toISOString(),
93
- } as unknown as ReturnType<typeof guardianToken.loadGuardianToken>);
92
+ ).mockReturnValue(makeGuardianTokenData({ accessToken: "local-token" }));
94
93
  const leaseGuardianTokenSpy = spyOn(
95
94
  guardianToken,
96
95
  "leaseGuardianToken",
97
- ).mockResolvedValue({
98
- accessToken: "leased-token",
99
- accessTokenExpiresAt: new Date(Date.now() + 60_000).toISOString(),
100
- } as unknown as Awaited<ReturnType<typeof guardianToken.leaseGuardianToken>>);
96
+ ).mockResolvedValue(makeGuardianTokenData({ accessToken: "leased-token" }));
101
97
 
102
98
  const getBackupsDirMock = spyOn(backupOps, "getBackupsDir").mockReturnValue(
103
99
  "/tmp/backups-default",
104
100
  );
105
101
 
106
102
  const mkdirSyncMock = spyOn(fs, "mkdirSync").mockImplementation(
107
- (() => undefined) as never,
103
+ () => undefined,
108
104
  );
109
105
  const writeFileSyncMock = spyOn(fs, "writeFileSync").mockImplementation(
110
106
  () => undefined,
111
107
  );
112
108
 
113
109
  let originalFetch: typeof globalThis.fetch;
114
- let exitMock: ReturnType<typeof mock>;
110
+ let exitMock: Mock<(code?: number | string | undefined) => never>;
115
111
 
116
112
  const CADET_ENTRY = {
117
113
  assistantId: "11111111-2222-3333-4444-555555555555",
@@ -126,10 +122,10 @@ function setArgv(...rest: string[]) {
126
122
 
127
123
  beforeEach(() => {
128
124
  originalFetch = globalThis.fetch;
129
- exitMock = mock((code?: number) => {
125
+ exitMock = mock((code?: number | string | undefined): never => {
130
126
  throw new Error(`process.exit:${code}`);
131
127
  });
132
- process.exit = exitMock as unknown as typeof process.exit;
128
+ process.exit = exitMock;
133
129
 
134
130
  findAssistantByNameMock.mockReset();
135
131
  findAssistantByNameMock.mockReturnValue(null);
@@ -162,17 +158,15 @@ beforeEach(() => {
162
158
  getBackupsDirMock.mockReset();
163
159
  getBackupsDirMock.mockReturnValue("/tmp/backups-default");
164
160
  loadGuardianTokenSpy.mockReset();
165
- loadGuardianTokenSpy.mockReturnValue({
166
- accessToken: "local-token",
167
- accessTokenExpiresAt: new Date(Date.now() + 60_000).toISOString(),
168
- } as unknown as ReturnType<typeof guardianToken.loadGuardianToken>);
161
+ loadGuardianTokenSpy.mockReturnValue(
162
+ makeGuardianTokenData({ accessToken: "local-token" }),
163
+ );
169
164
  leaseGuardianTokenSpy.mockReset();
170
- leaseGuardianTokenSpy.mockResolvedValue({
171
- accessToken: "leased-token",
172
- accessTokenExpiresAt: new Date(Date.now() + 60_000).toISOString(),
173
- } as unknown as Awaited<ReturnType<typeof guardianToken.leaseGuardianToken>>);
165
+ leaseGuardianTokenSpy.mockResolvedValue(
166
+ makeGuardianTokenData({ accessToken: "leased-token" }),
167
+ );
174
168
  mkdirSyncMock.mockReset();
175
- mkdirSyncMock.mockImplementation((() => undefined) as never);
169
+ mkdirSyncMock.mockImplementation(() => undefined);
176
170
  writeFileSyncMock.mockReset();
177
171
  writeFileSyncMock.mockImplementation(() => undefined);
178
172
  });
@@ -200,20 +194,26 @@ afterAll(() => {
200
194
  });
201
195
 
202
196
  import { backup } from "../commands/backup.js";
197
+ import { withPreconnect } from "./helpers/http.js";
198
+ import { makeGuardianTokenData } from "./helpers/guardian-token.js";
203
199
 
204
200
  // ---------------------------------------------------------------------------
205
201
  // Helper: simulated GCS download response
206
202
  // ---------------------------------------------------------------------------
207
- function mockGcsDownload(body: Uint8Array, ok = true, status = 200) {
208
- globalThis.fetch = mock(async () => {
209
- const responseBody: BodyInit = ok
210
- ? new Blob([body as unknown as ArrayBuffer])
211
- : "boom";
203
+ function mockGcsDownload(
204
+ body: Uint8Array<ArrayBuffer>,
205
+ ok = true,
206
+ status = 200,
207
+ ) {
208
+ const gcsFetch = mock(async () => {
209
+ const responseBody: BodyInit = ok ? new Blob([body]) : "boom";
212
210
  return new Response(responseBody, {
213
211
  status,
214
212
  statusText: ok ? "OK" : "Error",
215
213
  });
216
- }) as unknown as typeof globalThis.fetch;
214
+ });
215
+ globalThis.fetch = withPreconnect(gcsFetch);
216
+ return gcsFetch;
217
217
  }
218
218
 
219
219
  describe("cadet backup <local>: guardian bootstrap secret", () => {
@@ -228,11 +228,9 @@ describe("cadet backup <local>: guardian bootstrap secret", () => {
228
228
  loadGuardianTokenSpy.mockReturnValue(null);
229
229
  setArgv("my-local", "--output", "/tmp/local-backup.vbundle");
230
230
 
231
- globalThis.fetch = mock(async () => {
232
- return new Response(new Uint8Array([1, 2, 3]), {
233
- status: 200,
234
- });
235
- }) as unknown as typeof globalThis.fetch;
231
+ globalThis.fetch = withPreconnect(
232
+ async () => new Response(new Uint8Array([1, 2, 3]), { status: 200 }),
233
+ );
236
234
 
237
235
  await backup();
238
236
 
@@ -294,9 +292,9 @@ describe("cadet backup: --export-timeout flag", () => {
294
292
  "/tmp/local-backup.vbundle",
295
293
  );
296
294
 
297
- globalThis.fetch = mock(async () => {
298
- return new Response(new Uint8Array([1, 2, 3]), { status: 200 });
299
- }) as unknown as typeof globalThis.fetch;
295
+ globalThis.fetch = withPreconnect(
296
+ async () => new Response(new Uint8Array([1, 2, 3]), { status: 200 }),
297
+ );
300
298
 
301
299
  await backup();
302
300
 
@@ -313,7 +311,7 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
313
311
  setArgv("my-platform");
314
312
 
315
313
  const bytes = new Uint8Array([1, 2, 3, 4]);
316
- mockGcsDownload(bytes);
314
+ const gcsFetch = mockGcsDownload(bytes);
317
315
 
318
316
  await backup();
319
317
 
@@ -366,13 +364,12 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
366
364
  "https://cloud.mycadet.ai",
367
365
  );
368
366
  const downloadCall = platformRequestSignedUrlMock.mock.calls.find(
369
- (c) => (c[0] as { operation: string }).operation === "download",
367
+ (c) => c[0].operation === "download",
370
368
  );
371
369
  expect(downloadCall).toBeDefined();
372
370
  expect(downloadCall![0]).not.toHaveProperty("targetRuntimeVersion");
373
371
 
374
372
  // GCS fetch went directly to the signed download URL with no auth.
375
- const gcsFetch = globalThis.fetch as unknown as ReturnType<typeof mock>;
376
373
  expect(gcsFetch).toHaveBeenCalledWith(
377
374
  "https://storage.googleapis.com/bucket/signed-download",
378
375
  );
@@ -382,7 +379,7 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
382
379
  const [outputPath, written] = writeFileSyncMock.mock.calls[0]!;
383
380
  expect(written).toEqual(bytes);
384
381
  expect(typeof outputPath).toBe("string");
385
- expect(outputPath as string).toMatch(
382
+ expect(outputPath).toMatch(
386
383
  /\/tmp\/backups-default\/my-platform-.*\.vbundle$/,
387
384
  );
388
385
  expect(mkdirSyncMock).toHaveBeenCalled();
@@ -411,10 +408,8 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
411
408
  await backup();
412
409
 
413
410
  const [outputPath] = writeFileSyncMock.mock.calls[0]!;
414
- expect(outputPath as string).toMatch(
415
- /^\/tmp\/backups-default\/my-platform-/,
416
- );
417
- expect(outputPath as string).toMatch(/\.vbundle$/);
411
+ expect(outputPath).toMatch(/^\/tmp\/backups-default\/my-platform-/);
412
+ expect(outputPath).toMatch(/\.vbundle$/);
418
413
  });
419
414
 
420
415
  test("signed-URL requests target entry.runtimeUrl, not getPlatformUrl() — regression for staging/dev assistants", async () => {
@@ -502,10 +497,10 @@ describe("cadet backup <platform-managed>: GCS happy path", () => {
502
497
  // `platformToken` parameter instead of `exportPlatformToken`, a
503
498
  // future poll-loop refresh would silently break this invariant.
504
499
  const uploadCallToken = platformRequestSignedUrlMock.mock.calls.find(
505
- (c) => (c[0] as { operation: string }).operation === "upload",
500
+ (c) => c[0].operation === "upload",
506
501
  )![1];
507
502
  const downloadCallToken = platformRequestSignedUrlMock.mock.calls.find(
508
- (c) => (c[0] as { operation: string }).operation === "download",
503
+ (c) => c[0].operation === "download",
509
504
  )![1];
510
505
  expect(downloadCallToken).toBe(uploadCallToken);
511
506
  const kickoffToken = localRuntimeExportToGcsMock.mock.calls[0]![1];
@@ -27,7 +27,9 @@ import {
27
27
  findAssistantByName,
28
28
  saveAssistantEntry,
29
29
  } from "../lib/assistant-config.js";
30
+ import { z } from "zod";
30
31
  import { loadGuardianToken } from "../lib/guardian-token.js";
32
+ import { withPreconnect } from "./helpers/http.js";
31
33
 
32
34
  const HOST = "https://host.example.ts.net";
33
35
  const PAIRING_LINK = `${HOST}/assistant/pair#device_code=dev-code`;
@@ -69,6 +71,9 @@ function approvedBody(overrides: Record<string, unknown> = {}) {
69
71
  };
70
72
  }
71
73
 
74
+ /** The commands always send flat string-keyed JSON bodies. */
75
+ const JsonRecord = z.record(z.string(), z.unknown());
76
+
72
77
  interface FetchCall {
73
78
  url: string;
74
79
  body: Record<string, unknown>;
@@ -79,17 +84,20 @@ function stubFetch(
79
84
  replies: (call: FetchCall, index: number) => Response,
80
85
  ): FetchCall[] {
81
86
  const calls: FetchCall[] = [];
82
- globalThis.fetch = (async (url: string, init?: RequestInit) => {
83
- const call = {
84
- url,
85
- body: JSON.parse((init?.body as string) ?? "{}") as Record<
86
- string,
87
- unknown
88
- >,
89
- };
90
- calls.push(call);
91
- return replies(call, calls.length - 1);
92
- }) as unknown as typeof fetch;
87
+ globalThis.fetch = withPreconnect(
88
+ async (input: RequestInfo | URL, init?: RequestInit) => {
89
+ const call = {
90
+ url: String(input),
91
+ // The command always sends a JSON string body; anything else records
92
+ // as an empty record.
93
+ body: JsonRecord.parse(
94
+ JSON.parse(typeof init?.body === "string" ? init.body : "{}"),
95
+ ),
96
+ };
97
+ calls.push(call);
98
+ return replies(call, calls.length - 1);
99
+ },
100
+ );
93
101
  return calls;
94
102
  }
95
103
 
@@ -112,14 +120,16 @@ async function runExpectingExit(): Promise<{ exited: boolean; out: string }> {
112
120
  const logSpy = spyOn(console, "log").mockImplementation((...a: unknown[]) => {
113
121
  messages.push(a.join(" "));
114
122
  });
115
- const exitSpy = spyOn(process, "exit").mockImplementation(((c?: number) => {
116
- throw new Error(`exit:${c}`);
117
- }) as never);
123
+ const exitSpy = spyOn(process, "exit").mockImplementation(
124
+ (c?: number | string | undefined): never => {
125
+ throw new Error(`exit:${c}`);
126
+ },
127
+ );
118
128
  let exited = false;
119
129
  try {
120
130
  await connectImport();
121
131
  } catch (e) {
122
- exited = (e as Error).message === "exit:1";
132
+ exited = e instanceof Error && e.message === "exit:1";
123
133
  } finally {
124
134
  errSpy.mockRestore();
125
135
  logSpy.mockRestore();
@@ -144,10 +154,13 @@ describe("connect import", () => {
144
154
  // a multi-second cadence is exercised in full without being spent, and
145
155
  // both the cadence and the backoff are assertable rather than implicit.
146
156
  setSystemTime(new Date());
147
- sleepSpy = spyOn(Bun, "sleep").mockImplementation((async (ms: number) => {
148
- waits.push(ms);
149
- setSystemTime(new Date(Date.now() + ms));
150
- }) as never);
157
+ sleepSpy = spyOn(Bun, "sleep").mockImplementation(
158
+ async (ms: number | Date) => {
159
+ const waitMs = typeof ms === "number" ? ms : ms.getTime() - Date.now();
160
+ waits.push(waitMs);
161
+ setSystemTime(new Date(Date.now() + waitMs));
162
+ },
163
+ );
151
164
  });
152
165
 
153
166
  afterEach(() => {
@@ -312,10 +325,10 @@ describe("connect import", () => {
312
325
  "http://insecure.example.com",
313
326
  ];
314
327
  let fetched = false;
315
- globalThis.fetch = (async () => {
328
+ globalThis.fetch = withPreconnect(async () => {
316
329
  fetched = true;
317
330
  return jsonResponse({});
318
- }) as unknown as typeof fetch;
331
+ });
319
332
 
320
333
  const { exited, out } = await runExpectingExit();
321
334
 
@@ -333,10 +346,10 @@ describe("connect import", () => {
333
346
  "https://127.0.0.1:7830",
334
347
  ];
335
348
  let fetched = false;
336
- globalThis.fetch = (async () => {
349
+ globalThis.fetch = withPreconnect(async () => {
337
350
  fetched = true;
338
351
  return jsonResponse({});
339
- }) as unknown as typeof fetch;
352
+ });
340
353
 
341
354
  const { exited, out } = await runExpectingExit();
342
355
 
@@ -361,10 +374,10 @@ describe("connect import", () => {
361
374
  "legacy-box",
362
375
  ];
363
376
  let fetched = false;
364
- globalThis.fetch = (async () => {
377
+ globalThis.fetch = withPreconnect(async () => {
365
378
  fetched = true;
366
379
  return jsonResponse({});
367
- }) as unknown as typeof fetch;
380
+ });
368
381
  const { logs, restore } = captureLogs();
369
382
  try {
370
383
  await connectImport();
@@ -385,10 +398,10 @@ describe("connect import", () => {
385
398
  test("a string that is neither an address nor a bundle keeps the address error", async () => {
386
399
  process.argv = ["bun", "cadet", "connect", "import", "not-a-url"];
387
400
  let fetched = false;
388
- globalThis.fetch = (async () => {
401
+ globalThis.fetch = withPreconnect(async () => {
389
402
  fetched = true;
390
403
  return jsonResponse({});
391
- }) as unknown as typeof fetch;
404
+ });
392
405
 
393
406
  const { exited, out } = await runExpectingExit();
394
407
 
@@ -410,7 +423,8 @@ describe("connect import", () => {
410
423
  "flaky-box",
411
424
  ];
412
425
  let attempt = 0;
413
- globalThis.fetch = (async (url: string) => {
426
+ globalThis.fetch = withPreconnect(async (input: RequestInfo | URL) => {
427
+ const url = String(input);
414
428
  if (url === CHALLENGE_URL) {
415
429
  return jsonResponse(challengeBody());
416
430
  }
@@ -420,7 +434,7 @@ describe("connect import", () => {
420
434
  throw new Error("connect ECONNRESET");
421
435
  }
422
436
  return jsonResponse(approvedBody());
423
- }) as unknown as typeof fetch;
437
+ });
424
438
  const { logs, restore } = captureLogs();
425
439
  try {
426
440
  await connectImport();
@@ -448,7 +462,8 @@ describe("connect import", () => {
448
462
  "repairing-box",
449
463
  ];
450
464
  let attempt = 0;
451
- globalThis.fetch = (async (url: string) => {
465
+ globalThis.fetch = withPreconnect(async (input: RequestInfo | URL) => {
466
+ const url = String(input);
452
467
  if (url === CHALLENGE_URL) {
453
468
  return jsonResponse(challengeBody());
454
469
  }
@@ -459,7 +474,7 @@ describe("connect import", () => {
459
474
  return jsonResponse({ error: "guardian repair required" }, 503);
460
475
  }
461
476
  return jsonResponse(approvedBody());
462
- }) as unknown as typeof fetch;
477
+ });
463
478
  const { logs, restore } = captureLogs();
464
479
  try {
465
480
  await connectImport();
@@ -477,7 +492,8 @@ describe("connect import", () => {
477
492
  test("reports expiry once transient failures outlast the code", async () => {
478
493
  process.argv = ["bun", "cadet", "connect", "import", HOST];
479
494
  let exchanges = 0;
480
- globalThis.fetch = (async (url: string) => {
495
+ globalThis.fetch = withPreconnect(async (input: RequestInfo | URL) => {
496
+ const url = String(input);
481
497
  if (url === CHALLENGE_URL) {
482
498
  return jsonResponse(
483
499
  challengeBody({
@@ -488,7 +504,7 @@ describe("connect import", () => {
488
504
  }
489
505
  exchanges += 1;
490
506
  throw new Error("connect ECONNREFUSED");
491
- }) as unknown as typeof fetch;
507
+ });
492
508
 
493
509
  const { exited, out } = await runExpectingExit();
494
510
 
@@ -507,10 +523,10 @@ describe("connect import", () => {
507
523
  // ten-minute TTL.
508
524
  process.argv = ["bun", "cadet", "connect", "import", PAIRING_LINK];
509
525
  let attempts = 0;
510
- globalThis.fetch = (async () => {
526
+ globalThis.fetch = withPreconnect(async () => {
511
527
  attempts += 1;
512
528
  throw new Error("connect ECONNREFUSED");
513
- }) as unknown as typeof fetch;
529
+ });
514
530
 
515
531
  const { exited, out } = await runExpectingExit();
516
532
 
@@ -532,10 +548,10 @@ describe("connect import", () => {
532
548
  test("rejects an unknown --flag instead of pairing against it", async () => {
533
549
  process.argv = ["bun", "cadet", "connect", "import", "--qr", HOST];
534
550
  let fetched = false;
535
- globalThis.fetch = (async () => {
551
+ globalThis.fetch = withPreconnect(async () => {
536
552
  fetched = true;
537
553
  return jsonResponse({});
538
- }) as unknown as typeof fetch;
554
+ });
539
555
 
540
556
  const { exited, out } = await runExpectingExit();
541
557
 
@@ -560,10 +576,10 @@ describe("connect import", () => {
560
576
  "--frobnicate",
561
577
  ];
562
578
  let fetched = false;
563
- globalThis.fetch = (async () => {
579
+ globalThis.fetch = withPreconnect(async () => {
564
580
  fetched = true;
565
581
  return jsonResponse({});
566
- }) as unknown as typeof fetch;
582
+ });
567
583
 
568
584
  const { exited, out } = await runExpectingExit();
569
585
 
@@ -20,6 +20,8 @@ import * as assistantConfig from "../lib/assistant-config.js";
20
20
  import * as guardianToken from "../lib/guardian-token.js";
21
21
  import * as localRuntimeClient from "../lib/local-runtime-client.js";
22
22
  import * as platformClient from "../lib/platform-client.js";
23
+ import { withPreconnect } from "./helpers/http.js";
24
+ import { makeGuardianTokenData } from "./helpers/guardian-token.js";
23
25
 
24
26
  const resolveTargetMock = spyOn(
25
27
  assistantConfig,
@@ -46,23 +48,25 @@ const pollMock = spyOn(
46
48
  jobId: "job-1",
47
49
  type: "export",
48
50
  status: "complete",
49
- } as unknown as Awaited<
50
- ReturnType<typeof localRuntimeClient.localRuntimePollJobStatus>
51
- >);
51
+ });
52
52
  const loadGuardianTokenSpy = spyOn(
53
53
  guardianToken,
54
54
  "loadGuardianToken",
55
- ).mockReturnValue({
56
- accessToken: "local-token",
57
- accessTokenExpiresAt: new Date(Date.now() + 10 * 60_000).toISOString(),
58
- } as unknown as ReturnType<typeof guardianToken.loadGuardianToken>);
55
+ ).mockReturnValue(
56
+ makeGuardianTokenData({
57
+ accessToken: "local-token",
58
+ accessTokenExpiresAt: new Date(Date.now() + 10 * 60_000).toISOString(),
59
+ }),
60
+ );
59
61
  const leaseGuardianTokenSpy = spyOn(
60
62
  guardianToken,
61
63
  "leaseGuardianToken",
62
- ).mockResolvedValue({
63
- accessToken: "leased-token",
64
- accessTokenExpiresAt: new Date(Date.now() + 10 * 60_000).toISOString(),
65
- } as unknown as Awaited<ReturnType<typeof guardianToken.leaseGuardianToken>>);
64
+ ).mockResolvedValue(
65
+ makeGuardianTokenData({
66
+ accessToken: "leased-token",
67
+ accessTokenExpiresAt: new Date(Date.now() + 10 * 60_000).toISOString(),
68
+ }),
69
+ );
66
70
 
67
71
  const { debug } = await import("../commands/debug.js");
68
72
 
@@ -75,7 +79,7 @@ const LOCAL_ENTRY = {
75
79
  platformAssistantId: "11111111-2222-3333-4444-555555555555",
76
80
  platformBaseUrl: "https://cloud.mycadet.ai",
77
81
  platformOrganizationId: "org-1",
78
- } as unknown as assistantConfig.AssistantEntry;
82
+ } satisfies assistantConfig.AssistantEntry;
79
83
 
80
84
  let originalFetch: typeof globalThis.fetch;
81
85
  let originalExit: typeof process.exit;
@@ -86,12 +90,12 @@ beforeEach(() => {
86
90
  process.argv = ["bun", "cadet", "debug"];
87
91
  originalFetch = globalThis.fetch;
88
92
  originalExit = process.exit;
89
- process.exit = mock((code?: number) => {
93
+ process.exit = mock((code?: number | string | undefined): never => {
90
94
  throw new Error(`process.exit:${code}`);
91
- }) as unknown as typeof process.exit;
95
+ });
92
96
  fetchCalls = [];
93
97
  uploadUrlStatus = 201;
94
- globalThis.fetch = mock(
98
+ globalThis.fetch = withPreconnect(
95
99
  async (input: RequestInfo | URL, init?: RequestInit) => {
96
100
  fetchCalls.push({ url: String(input), init });
97
101
  return new Response(
@@ -102,7 +106,7 @@ beforeEach(() => {
102
106
  },
103
107
  );
104
108
  },
105
- ) as unknown as typeof globalThis.fetch;
109
+ );
106
110
  resolveTargetMock.mockReset();
107
111
  resolveTargetMock.mockReturnValue(LOCAL_ENTRY);
108
112
  identityMock.mockReset();
@@ -140,9 +144,7 @@ describe("cadet debug", () => {
140
144
  );
141
145
  expect(fetchCalls[0].init?.method).toBe("POST");
142
146
  expect(
143
- (fetchCalls[0].init?.headers as Record<string, string>)[
144
- "Cadet-Organization-Id"
145
- ],
147
+ new Headers(fetchCalls[0].init?.headers).get("Cadet-Organization-Id"),
146
148
  ).toBe("org-1");
147
149
 
148
150
  expect(exportMock).toHaveBeenCalledTimes(1);
@@ -196,9 +198,7 @@ describe("cadet debug", () => {
196
198
  jobId: "job-1",
197
199
  type: "export",
198
200
  status: "complete",
199
- } as unknown as Awaited<
200
- ReturnType<typeof localRuntimeClient.localRuntimePollJobStatus>
201
- >;
201
+ };
202
202
  });
203
203
 
204
204
  await debug();
@@ -226,7 +226,7 @@ describe("cadet debug", () => {
226
226
  resolveTargetMock.mockReturnValue({
227
227
  ...LOCAL_ENTRY,
228
228
  cloud: "cadet",
229
- } as unknown as assistantConfig.AssistantEntry);
229
+ });
230
230
  const errorSpy = spyOn(console, "error").mockImplementation(() => {});
231
231
  try {
232
232
  await expect(debug()).rejects.toThrow("process.exit:1");
@@ -240,7 +240,7 @@ describe("cadet debug", () => {
240
240
  resolveTargetMock.mockReturnValue({
241
241
  ...LOCAL_ENTRY,
242
242
  platformAssistantId: undefined,
243
- } as unknown as assistantConfig.AssistantEntry);
243
+ });
244
244
  const errors: string[] = [];
245
245
  const errorSpy = spyOn(console, "error").mockImplementation((msg) => {
246
246
  errors.push(String(msg));