@vellumai/cli 0.11.3 → 0.11.4-dev.202608190019.b94dbf2

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 (41) hide show
  1. package/node_modules/@vellumai/local-mode/src/__tests__/unpair.test.ts +33 -0
  2. package/node_modules/@vellumai/local-mode/src/index.ts +3 -0
  3. package/node_modules/@vellumai/local-mode/src/lockfile-lock.test.ts +165 -0
  4. package/node_modules/@vellumai/local-mode/src/lockfile-lock.ts +156 -0
  5. package/node_modules/@vellumai/local-mode/src/lockfile.test.ts +249 -8
  6. package/node_modules/@vellumai/local-mode/src/lockfile.ts +186 -68
  7. package/node_modules/@vellumai/local-mode/src/unpair.ts +18 -0
  8. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  9. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  10. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  11. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  12. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  13. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  14. package/package.json +1 -1
  15. package/src/__tests__/assistant-config.test.ts +35 -0
  16. package/src/__tests__/nginx-ingress-command.test.ts +4 -23
  17. package/src/__tests__/nginx-ingress.test.ts +59 -215
  18. package/src/__tests__/pair.test.ts +11 -197
  19. package/src/__tests__/retire-archive.test.ts +13 -1
  20. package/src/__tests__/retire-local.test.ts +58 -4
  21. package/src/__tests__/tunnel.test.ts +0 -28
  22. package/src/__tests__/wake.test.ts +91 -69
  23. package/src/__tests__/windows-lifecycle.test.ts +157 -0
  24. package/src/commands/client.ts +9 -31
  25. package/src/commands/nginx-ingress.ts +0 -15
  26. package/src/commands/pair.ts +0 -39
  27. package/src/commands/wake.ts +39 -12
  28. package/src/lib/__tests__/web-dist.test.ts +86 -0
  29. package/src/lib/assistant-config.ts +64 -27
  30. package/src/lib/local.ts +60 -20
  31. package/src/lib/nginx-ingress.ts +35 -108
  32. package/src/lib/orphan-detection.test.ts +3 -0
  33. package/src/lib/orphan-detection.ts +33 -11
  34. package/src/lib/pgrep.ts +20 -2
  35. package/src/lib/process.ts +191 -18
  36. package/src/lib/retire-archive.ts +38 -8
  37. package/src/lib/retire-local.ts +75 -9
  38. package/src/lib/tunnel-edge.ts +14 -22
  39. package/src/lib/web-dist.ts +48 -0
  40. package/src/lib/feature-flags.test.ts +0 -157
  41. package/src/lib/feature-flags.ts +0 -38
@@ -382,14 +382,6 @@ describe("pair command", () => {
382
382
  const origFetch = globalThis.fetch;
383
383
  globalThis.fetch = (async (url: string, init?: RequestInit) => {
384
384
  calls.push([url, init]);
385
- if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
386
- return new Response(
387
- JSON.stringify({
388
- flags: [{ key: "web-remote-ingress", enabled: true }],
389
- }),
390
- { status: 200, headers: { "content-type": "application/json" } },
391
- );
392
- }
393
385
  if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
394
386
  return new Response(
395
387
  JSON.stringify({
@@ -430,9 +422,9 @@ describe("pair command", () => {
430
422
  globalThis.fetch = origFetch;
431
423
  }
432
424
 
433
- expect(calls).toHaveLength(2);
434
- expect(calls[1][0]).toBe(`${LOCAL_URL}/v1/remote-web/pairing-challenge`);
435
- expect(JSON.parse(calls[1][1]?.body as string)).toEqual({
425
+ expect(calls).toHaveLength(1);
426
+ expect(calls[0][0]).toBe(`${LOCAL_URL}/v1/remote-web/pairing-challenge`);
427
+ expect(JSON.parse(calls[0][1]?.body as string)).toEqual({
436
428
  publicBaseUrl: "https://abc123.ngrok.app/assistant-123",
437
429
  });
438
430
 
@@ -449,58 +441,6 @@ describe("pair command", () => {
449
441
  expect(logs.join("\n")).not.toContain("refresh");
450
442
  });
451
443
 
452
- test("--web refuses when the web remote ingress feature flag is off", async () => {
453
- const calls: Array<[string, RequestInit | undefined]> = [];
454
- const origFetch = globalThis.fetch;
455
- globalThis.fetch = (async (url: string, init?: RequestInit) => {
456
- calls.push([url, init]);
457
- return new Response(
458
- JSON.stringify({
459
- flags: [{ key: "web-remote-ingress", enabled: false }],
460
- }),
461
- { status: 200, headers: { "content-type": "application/json" } },
462
- );
463
- }) as unknown as typeof fetch;
464
-
465
- const errors: string[] = [];
466
- const errSpy = spyOn(console, "error").mockImplementation(
467
- (...a: unknown[]) => {
468
- errors.push(a.join(" "));
469
- },
470
- );
471
- const exitSpy = spyOn(process, "exit").mockImplementation(((
472
- code?: number,
473
- ) => {
474
- throw new Error(`exit:${code}`);
475
- }) as never);
476
-
477
- process.argv = [
478
- "bun",
479
- "vellum",
480
- "pair",
481
- "--web",
482
- "--url",
483
- "https://abc123.ngrok.app",
484
- ];
485
- let exited = false;
486
- try {
487
- await pair();
488
- } catch (e) {
489
- exited = (e as Error).message === "exit:1";
490
- } finally {
491
- errSpy.mockRestore();
492
- exitSpy.mockRestore();
493
- globalThis.fetch = origFetch;
494
- }
495
-
496
- expect(exited).toBe(true);
497
- expect(errors.join("\n")).toContain("web-remote-ingress");
498
- expect(calls).toHaveLength(1);
499
- expect(calls[0][0]).toBe(
500
- `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`,
501
- );
502
- });
503
-
504
444
  test("--web-approve approves a browser pairing code over loopback", async () => {
505
445
  writeFileSync(
506
446
  join(testDir, ".vellum.lock.json"),
@@ -521,14 +461,6 @@ describe("pair command", () => {
521
461
  const origFetch = globalThis.fetch;
522
462
  globalThis.fetch = (async (url: string, init?: RequestInit) => {
523
463
  calls.push([url, init]);
524
- if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
525
- return new Response(
526
- JSON.stringify({
527
- flags: [{ key: "web-remote-ingress", enabled: true }],
528
- }),
529
- { status: 200, headers: { "content-type": "application/json" } },
530
- );
531
- }
532
464
  if (url === `${LOCAL_URL}/v1/remote-web/pairing-verification`) {
533
465
  return new Response(
534
466
  JSON.stringify({
@@ -564,9 +496,9 @@ describe("pair command", () => {
564
496
  globalThis.fetch = origFetch;
565
497
  }
566
498
 
567
- expect(calls).toHaveLength(2);
568
- expect(calls[1][0]).toBe(`${LOCAL_URL}/v1/remote-web/pairing-verification`);
569
- expect(JSON.parse(calls[1][1]?.body as string)).toEqual({
499
+ expect(calls).toHaveLength(1);
500
+ expect(calls[0][0]).toBe(`${LOCAL_URL}/v1/remote-web/pairing-verification`);
501
+ expect(JSON.parse(calls[0][1]?.body as string)).toEqual({
570
502
  userCode: "ABCD-EFGH",
571
503
  });
572
504
  expect(JSON.parse(logs.join("\n"))).toEqual({
@@ -581,14 +513,6 @@ describe("pair command", () => {
581
513
  const origFetch = globalThis.fetch;
582
514
  globalThis.fetch = (async (url: string, init?: RequestInit) => {
583
515
  calls.push([url, init]);
584
- if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
585
- return new Response(
586
- JSON.stringify({
587
- flags: [{ key: "web-remote-ingress", enabled: true }],
588
- }),
589
- { status: 200, headers: { "content-type": "application/json" } },
590
- );
591
- }
592
516
  if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
593
517
  return new Response(
594
518
  JSON.stringify({
@@ -637,18 +561,17 @@ describe("pair command", () => {
637
561
  globalThis.fetch = origFetch;
638
562
  }
639
563
 
640
- // Flag check → create challenge → approve it, all over loopback. The
564
+ // Create challenge → approve it, all over loopback. The
641
565
  // approval is the whole point: running the CLI on the host IS the approval,
642
566
  // so the scan alone completes pairing.
643
567
  expect(calls.map((c) => c[0])).toEqual([
644
- `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`,
645
568
  `${LOCAL_URL}/v1/remote-web/pairing-challenge`,
646
569
  `${LOCAL_URL}/v1/remote-web/pairing-verification`,
647
570
  ]);
648
- expect(JSON.parse(calls[1][1]?.body as string)).toEqual({
571
+ expect(JSON.parse(calls[0][1]?.body as string)).toEqual({
649
572
  publicBaseUrl: "https://pair.example.ts.net",
650
573
  });
651
- expect(JSON.parse(calls[2][1]?.body as string)).toEqual({
574
+ expect(JSON.parse(calls[1][1]?.body as string)).toEqual({
652
575
  userCode: "ABCD-EFGH",
653
576
  });
654
577
 
@@ -669,14 +592,6 @@ describe("pair command", () => {
669
592
  test("--qr renders a QR and prints the fallback URL and expiry", async () => {
670
593
  const origFetch = globalThis.fetch;
671
594
  globalThis.fetch = (async (url: string) => {
672
- if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
673
- return new Response(
674
- JSON.stringify({
675
- flags: [{ key: "web-remote-ingress", enabled: true }],
676
- }),
677
- { status: 200, headers: { "content-type": "application/json" } },
678
- );
679
- }
680
595
  if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
681
596
  return new Response(
682
597
  JSON.stringify({
@@ -903,59 +818,6 @@ describe("pair command", () => {
903
818
  expect(fetchCalled).toBe(false);
904
819
  });
905
820
 
906
- test("--qr refuses when the web remote ingress feature flag is off", async () => {
907
- const calls: Array<[string, RequestInit | undefined]> = [];
908
- const origFetch = globalThis.fetch;
909
- globalThis.fetch = (async (url: string, init?: RequestInit) => {
910
- calls.push([url, init]);
911
- return new Response(
912
- JSON.stringify({
913
- flags: [{ key: "web-remote-ingress", enabled: false }],
914
- }),
915
- { status: 200, headers: { "content-type": "application/json" } },
916
- );
917
- }) as unknown as typeof fetch;
918
-
919
- const errors: string[] = [];
920
- const errSpy = spyOn(console, "error").mockImplementation(
921
- (...a: unknown[]) => {
922
- errors.push(a.join(" "));
923
- },
924
- );
925
- const exitSpy = spyOn(process, "exit").mockImplementation(((
926
- code?: number,
927
- ) => {
928
- throw new Error(`exit:${code}`);
929
- }) as never);
930
-
931
- process.argv = [
932
- "bun",
933
- "vellum",
934
- "pair",
935
- "--qr",
936
- "--url",
937
- "https://pair.example.ts.net",
938
- ];
939
- let exited = false;
940
- try {
941
- await pair();
942
- } catch (e) {
943
- exited = (e as Error).message === "exit:1";
944
- } finally {
945
- errSpy.mockRestore();
946
- exitSpy.mockRestore();
947
- globalThis.fetch = origFetch;
948
- }
949
-
950
- // Only the flag check runs; no challenge is minted when the flag is off.
951
- expect(exited).toBe(true);
952
- expect(errors.join("\n")).toContain("web-remote-ingress");
953
- expect(calls).toHaveLength(1);
954
- expect(calls[0][0]).toBe(
955
- `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`,
956
- );
957
- });
958
-
959
821
  test("buildAppConnectUrl composes and encodes the connect link", () => {
960
822
  expect(
961
823
  buildAppConnectUrl(
@@ -1004,14 +866,6 @@ describe("pair command", () => {
1004
866
  test("--qr --app emits an app connect URL alongside the browser URL", async () => {
1005
867
  const origFetch = globalThis.fetch;
1006
868
  globalThis.fetch = (async (url: string) => {
1007
- if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
1008
- return new Response(
1009
- JSON.stringify({
1010
- flags: [{ key: "web-remote-ingress", enabled: true }],
1011
- }),
1012
- { status: 200, headers: { "content-type": "application/json" } },
1013
- );
1014
- }
1015
869
  if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
1016
870
  return new Response(
1017
871
  JSON.stringify({
@@ -1079,14 +933,6 @@ describe("pair command", () => {
1079
933
  test("--qr --app --label overrides the assistant name in the connect link", async () => {
1080
934
  const origFetch = globalThis.fetch;
1081
935
  globalThis.fetch = (async (url: string) => {
1082
- if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
1083
- return new Response(
1084
- JSON.stringify({
1085
- flags: [{ key: "web-remote-ingress", enabled: true }],
1086
- }),
1087
- { status: 200, headers: { "content-type": "application/json" } },
1088
- );
1089
- }
1090
936
  if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
1091
937
  return new Response(
1092
938
  JSON.stringify({
@@ -1205,14 +1051,6 @@ describe("pair command", () => {
1205
1051
  const origFetch = globalThis.fetch;
1206
1052
  globalThis.fetch = (async (url: string, init?: RequestInit) => {
1207
1053
  calls.push(url);
1208
- if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
1209
- return new Response(
1210
- JSON.stringify({
1211
- flags: [{ key: "web-remote-ingress", enabled: true }],
1212
- }),
1213
- { status: 200, headers: { "content-type": "application/json" } },
1214
- );
1215
- }
1216
1054
  if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
1217
1055
  // The minted challenge must advertise the entry's recorded URL.
1218
1056
  expect(JSON.parse(init?.body as string)).toEqual({
@@ -1272,14 +1110,6 @@ describe("pair command", () => {
1272
1110
 
1273
1111
  const origFetch = globalThis.fetch;
1274
1112
  globalThis.fetch = (async (url: string, init?: RequestInit) => {
1275
- if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
1276
- return new Response(
1277
- JSON.stringify({
1278
- flags: [{ key: "web-remote-ingress", enabled: true }],
1279
- }),
1280
- { status: 200, headers: { "content-type": "application/json" } },
1281
- );
1282
- }
1283
1113
  if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
1284
1114
  expect(JSON.parse(init?.body as string)).toEqual({
1285
1115
  publicBaseUrl: "https://explicit.example.ts.net",
@@ -1338,15 +1168,7 @@ describe("pair command", () => {
1338
1168
 
1339
1169
  const origFetch = globalThis.fetch;
1340
1170
  let minted = false;
1341
- globalThis.fetch = (async (url: string) => {
1342
- if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
1343
- return new Response(
1344
- JSON.stringify({
1345
- flags: [{ key: "web-remote-ingress", enabled: true }],
1346
- }),
1347
- { status: 200, headers: { "content-type": "application/json" } },
1348
- );
1349
- }
1171
+ globalThis.fetch = (async () => {
1350
1172
  minted = true;
1351
1173
  return new Response("{}", { status: 200 });
1352
1174
  }) as unknown as typeof fetch;
@@ -1387,15 +1209,7 @@ describe("pair command", () => {
1387
1209
 
1388
1210
  const origFetch = globalThis.fetch;
1389
1211
  let minted = false;
1390
- globalThis.fetch = (async (url: string) => {
1391
- if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
1392
- return new Response(
1393
- JSON.stringify({
1394
- flags: [{ key: "web-remote-ingress", enabled: true }],
1395
- }),
1396
- { status: 200, headers: { "content-type": "application/json" } },
1397
- );
1398
- }
1212
+ globalThis.fetch = (async () => {
1399
1213
  minted = true;
1400
1214
  return new Response("{}", { status: 200 });
1401
1215
  }) as unknown as typeof fetch;
@@ -1,5 +1,10 @@
1
1
  import { describe, test, expect } from "bun:test";
2
- import { validateAssistantName } from "../lib/retire-archive.js";
2
+ import { win32 } from "node:path";
3
+
4
+ import {
5
+ resolveRetiredFilePath,
6
+ validateAssistantName,
7
+ } from "../lib/retire-archive.js";
3
8
 
4
9
  describe("validateAssistantName", () => {
5
10
  test("accepts valid names", () => {
@@ -32,3 +37,10 @@ describe("validateAssistantName", () => {
32
37
  expect(() => validateAssistantName(".")).toThrow("Invalid assistant name");
33
38
  });
34
39
  });
40
+
41
+ test("accepts a Windows archive path inside the retired directory", () => {
42
+ const retiredDir = "C:\\Users\\Example User\\AppData\\Local\\Vellum\\retired";
43
+ expect(
44
+ resolveRetiredFilePath("assistant", "tar.gz", retiredDir, "win32"),
45
+ ).toBe(win32.join(retiredDir, "assistant.tar.gz"));
46
+ });
@@ -12,7 +12,12 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
12
12
  import { tmpdir } from "node:os";
13
13
  import { join } from "node:path";
14
14
 
15
- import type { AssistantEntry } from "../lib/assistant-config.js";
15
+ import {
16
+ getDaemonPidPath,
17
+ type AssistantEntry,
18
+ } from "../lib/assistant-config.js";
19
+ import { getLockfilePath } from "../lib/environments/paths.js";
20
+ import { getCurrentEnvironment } from "../lib/environments/resolve.js";
16
21
 
17
22
  const testDir = mkdtempSync(join(tmpdir(), "retire-local-test-"));
18
23
  const originalLockfileDir = process.env.VELLUM_LOCKFILE_DIR;
@@ -31,7 +36,7 @@ const stopProcessByPidFileMock = mock(
31
36
  async (_pidFile: string, _label: string): Promise<boolean> => true,
32
37
  );
33
38
  const stopOrphanedDaemonProcessesMock = mock(
34
- async (): Promise<boolean> => false,
39
+ async (_excludePids?: ReadonlySet<string>): Promise<boolean> => false,
35
40
  );
36
41
  const stopIngressNginxMock = mock(async (): Promise<boolean> => false);
37
42
 
@@ -60,7 +65,7 @@ afterAll(() => {
60
65
  mock.module("../lib/retire-archive.js", () => realRetireArchive);
61
66
  });
62
67
 
63
- import { retireLocal } from "../lib/retire-local.js";
68
+ import { getRetireArchiveCommand, retireLocal } from "../lib/retire-local.js";
64
69
 
65
70
  const instanceDir = join(testDir, "test-instance");
66
71
  const vellumDir = join(instanceDir, ".vellum");
@@ -82,7 +87,7 @@ function makeEntry(assistantId: string): AssistantEntry {
82
87
 
83
88
  function writeLockfile(entries: AssistantEntry[]): void {
84
89
  writeFileSync(
85
- join(testDir, ".vellum.lock.json"),
90
+ getLockfilePath(getCurrentEnvironment()),
86
91
  JSON.stringify({ assistants: entries }, null, 2) + "\n",
87
92
  );
88
93
  }
@@ -174,4 +179,53 @@ describe("retireLocal — CES sibling stop", () => {
174
179
  );
175
180
  expect(cesStopCall).toBeDefined();
176
181
  });
182
+
183
+ test("protects other assistants during orphan cleanup", async () => {
184
+ const otherInstanceDir = join(testDir, "other-instance");
185
+ const otherEntry = makeEntry("other-assistant");
186
+ otherEntry.resources!.instanceDir = otherInstanceDir;
187
+ const otherPid = "4242";
188
+ const otherPidFile = getDaemonPidPath(otherEntry.resources);
189
+ mkdirSync(join(otherInstanceDir, ".vellum", "workspace"), {
190
+ recursive: true,
191
+ });
192
+ writeFileSync(otherPidFile, otherPid);
193
+ writeLockfile([makeEntry("test-assistant"), otherEntry]);
194
+ stopProcessByPidFileMock.mockImplementation(
195
+ async (_pidFile: string, label: string): Promise<boolean> =>
196
+ label !== "daemon",
197
+ );
198
+
199
+ await retireLocal("test-assistant", makeEntry("test-assistant"), {
200
+ progress: () => {},
201
+ log: () => {},
202
+ warn: () => {},
203
+ error: () => {},
204
+ });
205
+
206
+ expect(stopOrphanedDaemonProcessesMock).toHaveBeenCalledTimes(1);
207
+ const excludedPids = stopOrphanedDaemonProcessesMock.mock.calls[0]?.[0];
208
+ expect(excludedPids?.has(otherPid)).toBeTrue();
209
+ });
210
+ });
211
+
212
+ test("uses a Windows-native archive process", () => {
213
+ const archivePath =
214
+ "C:\\Users\\Example User\\Vellum\\retired\\assistant & data.tar.gz";
215
+ const stagingDir = `${archivePath}.staging`;
216
+ const archiveCommand = getRetireArchiveCommand(
217
+ archivePath,
218
+ stagingDir,
219
+ "win32",
220
+ );
221
+
222
+ expect(archiveCommand.command).toBe("powershell.exe");
223
+ expect(archiveCommand.args.join(" ")).toContain("tar.exe");
224
+ expect(archiveCommand.args.join(" ")).not.toContain(archivePath);
225
+ expect(archiveCommand.env).toMatchObject({
226
+ VELLUM_RETIRE_ARCHIVE_PATH: archivePath,
227
+ VELLUM_RETIRE_ARCHIVE_PARENT: "C:\\Users\\Example User\\Vellum\\retired",
228
+ VELLUM_RETIRE_STAGING_NAME: "assistant & data.tar.gz.staging",
229
+ VELLUM_RETIRE_STAGING_DIR: stagingDir,
230
+ });
177
231
  });
@@ -238,20 +238,6 @@ describe("tunnel edge targeting", () => {
238
238
  mock.module("../lib/nginx-ingress.js", () => realNginxIngress);
239
239
  });
240
240
 
241
- test("does not start ngrok when the edge flag lookup fails", async () => {
242
- process.argv = ["bun", "vellum", "tunnel", "--provider", "ngrok"];
243
- ensureTunnelEdgeMock.mockImplementation(realNginxIngress.ensureTunnelEdge);
244
-
245
- const { exited, errors } = await runTunnelExpectingExit1();
246
-
247
- expect(exited).toBe(true);
248
- expect(errors).toContain(
249
- "Could not verify the `web-remote-ingress` feature flag",
250
- );
251
- expect(runNgrokTunnelMock).not.toHaveBeenCalled();
252
- expect(runCloudflareTunnelMock).not.toHaveBeenCalled();
253
- });
254
-
255
241
  test("targets the edge port returned by ensureTunnelEdge for ngrok", async () => {
256
242
  const entry = makeLocalEntry();
257
243
  entry.runtimeUrl = "https://stale-tunnel.ngrok-free.dev";
@@ -660,20 +646,6 @@ describe("tunnel edge targeting", () => {
660
646
  expect(runTailscaleTunnelMock).not.toHaveBeenCalled();
661
647
  });
662
648
 
663
- test("does not start cloudflared when the edge flag lookup fails", async () => {
664
- process.argv = ["bun", "vellum", "tunnel", "--provider", "cloudflare"];
665
- ensureTunnelEdgeMock.mockImplementation(realNginxIngress.ensureTunnelEdge);
666
-
667
- const { exited, errors } = await runTunnelExpectingExit1();
668
-
669
- expect(exited).toBe(true);
670
- expect(errors).toContain(
671
- "Could not verify the `web-remote-ingress` feature flag",
672
- );
673
- expect(runNgrokTunnelMock).not.toHaveBeenCalled();
674
- expect(runCloudflareTunnelMock).not.toHaveBeenCalled();
675
- });
676
-
677
649
  test("rejects an unknown --provider with a stale-CLI hint", async () => {
678
650
  process.argv = ["bun", "vellum", "tunnel", "--provider", "bogus"];
679
651