@vellumai/cli 0.11.2 → 0.11.3-staging.2

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 (97) hide show
  1. package/knip.json +1 -0
  2. package/node_modules/@vellumai/environments/package.json +2 -1
  3. package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  4. package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  5. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  6. package/node_modules/@vellumai/environments/src/index.ts +17 -6
  7. package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  8. package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  9. package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
  10. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
  11. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
  12. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +18 -0
  13. package/node_modules/@vellumai/ipc-server-utils/src/ipc-framing.ts +295 -0
  14. package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
  15. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +444 -0
  16. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +236 -0
  17. package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
  18. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/package.json +2 -1
  19. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  20. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  21. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  22. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/index.ts +17 -6
  23. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  24. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  25. package/node_modules/@vellumai/local-mode/node_modules/nanoid/LICENSE +20 -0
  26. package/node_modules/@vellumai/local-mode/node_modules/nanoid/README.md +38 -0
  27. package/node_modules/@vellumai/local-mode/node_modules/nanoid/bin/nanoid.js +55 -0
  28. package/node_modules/@vellumai/local-mode/node_modules/nanoid/index.browser.js +29 -0
  29. package/node_modules/@vellumai/local-mode/node_modules/nanoid/index.d.ts +106 -0
  30. package/node_modules/@vellumai/local-mode/node_modules/nanoid/index.js +47 -0
  31. package/node_modules/@vellumai/local-mode/node_modules/nanoid/nanoid.js +1 -0
  32. package/node_modules/@vellumai/local-mode/node_modules/nanoid/non-secure/index.d.ts +48 -0
  33. package/node_modules/@vellumai/local-mode/node_modules/nanoid/non-secure/index.js +21 -0
  34. package/node_modules/@vellumai/local-mode/node_modules/nanoid/package.json +46 -0
  35. package/node_modules/@vellumai/local-mode/node_modules/nanoid/url-alphabet/index.js +2 -0
  36. package/node_modules/@vellumai/local-mode/package.json +1 -0
  37. package/node_modules/@vellumai/local-mode/src/__tests__/environment.test.ts +63 -1
  38. package/node_modules/@vellumai/local-mode/src/__tests__/gateway-proxy.test.ts +503 -0
  39. package/node_modules/@vellumai/local-mode/src/__tests__/guardian-token.test.ts +203 -0
  40. package/node_modules/@vellumai/local-mode/src/__tests__/loopback-auth.test.ts +46 -1
  41. package/node_modules/@vellumai/local-mode/src/__tests__/package-boundary.test.ts +6 -4
  42. package/node_modules/@vellumai/local-mode/src/__tests__/pair.test.ts +621 -0
  43. package/node_modules/@vellumai/local-mode/src/__tests__/status.test.ts +5 -0
  44. package/node_modules/@vellumai/local-mode/src/__tests__/unpair.test.ts +232 -0
  45. package/node_modules/@vellumai/local-mode/src/config.ts +126 -20
  46. package/node_modules/@vellumai/local-mode/src/environment.ts +36 -15
  47. package/node_modules/@vellumai/local-mode/src/gateway-proxy.ts +313 -25
  48. package/node_modules/@vellumai/local-mode/src/guardian-token.ts +222 -13
  49. package/node_modules/@vellumai/local-mode/src/index.ts +35 -3
  50. package/node_modules/@vellumai/local-mode/src/lockfile-contract.test.ts +18 -0
  51. package/node_modules/@vellumai/local-mode/src/lockfile-contract.ts +72 -0
  52. package/node_modules/@vellumai/local-mode/src/lockfile.test.ts +130 -0
  53. package/node_modules/@vellumai/local-mode/src/lockfile.ts +134 -45
  54. package/node_modules/@vellumai/local-mode/src/pair.ts +354 -0
  55. package/node_modules/@vellumai/local-mode/src/paths.ts +49 -0
  56. package/node_modules/@vellumai/local-mode/src/status.ts +10 -8
  57. package/node_modules/@vellumai/local-mode/src/unpair.ts +101 -0
  58. package/node_modules/@vellumai/local-mode/src/util.ts +34 -0
  59. package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  60. package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  61. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  62. package/package.json +3 -1
  63. package/src/__tests__/client-token.test.ts +26 -1
  64. package/src/__tests__/guardian-token.test.ts +4 -0
  65. package/src/__tests__/nginx-ingress-command.test.ts +176 -16
  66. package/src/__tests__/nginx-ingress.test.ts +1821 -101
  67. package/src/__tests__/pair.test.ts +104 -2
  68. package/src/__tests__/sleep.test.ts +7 -3
  69. package/src/__tests__/tailscale-tunnel.test.ts +0 -1
  70. package/src/__tests__/tunnel.test.ts +1099 -55
  71. package/src/__tests__/wake.test.ts +166 -99
  72. package/src/commands/client.ts +182 -27
  73. package/src/commands/connect/import.ts +35 -156
  74. package/src/commands/nginx-ingress.ts +87 -110
  75. package/src/commands/pair.ts +84 -21
  76. package/src/commands/tunnel.ts +196 -85
  77. package/src/commands/upgrade.ts +3 -2
  78. package/src/commands/wake.ts +5 -163
  79. package/src/index.ts +1 -1
  80. package/src/lib/__tests__/local-ces.test.ts +10 -1
  81. package/src/lib/cloudflare-tunnel.ts +3 -16
  82. package/src/lib/environments/__tests__/paths.test.ts +20 -1
  83. package/src/lib/environments/paths.ts +29 -41
  84. package/src/lib/environments/resolve.ts +7 -5
  85. package/src/lib/guardian-token.ts +22 -62
  86. package/src/lib/ingress-config.ts +25 -0
  87. package/src/lib/local.ts +77 -20
  88. package/src/lib/nginx-ingress.ts +523 -76
  89. package/src/lib/ngrok.ts +154 -61
  90. package/src/lib/orphan-detection.test.ts +167 -0
  91. package/src/lib/orphan-detection.ts +40 -0
  92. package/src/lib/pair.test.ts +87 -0
  93. package/src/lib/pair.ts +55 -0
  94. package/src/lib/platform-client.ts +8 -5
  95. package/src/lib/tailscale-tunnel.ts +7 -18
  96. package/src/lib/tunnel-edge.ts +114 -0
  97. package/src/lib/xdg-log.ts +2 -2
@@ -11,9 +11,13 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
11
11
  import { tmpdir } from "node:os";
12
12
  import { join } from "node:path";
13
13
 
14
- // Real assistant-config reads the lockfile from VELLUM_LOCKFILE_DIR.
14
+ // Real assistant-config reads the lockfile from VELLUM_LOCKFILE_DIR. Pin the
15
+ // environment too: the lockfile filename is environment-dependent, and a dev
16
+ // machine's persisted default environment would otherwise redirect the lookup.
15
17
  const testDir = mkdtempSync(join(tmpdir(), "pair-command-test-"));
16
18
  process.env.VELLUM_LOCKFILE_DIR = testDir;
19
+ const originalEnvironment = process.env.VELLUM_ENVIRONMENT;
20
+ process.env.VELLUM_ENVIRONMENT = "production";
17
21
 
18
22
  import { buildAppConnectUrl, pair } from "../commands/pair.js";
19
23
 
@@ -55,6 +59,11 @@ describe("pair command", () => {
55
59
  afterAll(() => {
56
60
  rmSync(testDir, { recursive: true, force: true });
57
61
  delete process.env.VELLUM_LOCKFILE_DIR;
62
+ if (originalEnvironment === undefined) {
63
+ delete process.env.VELLUM_ENVIRONMENT;
64
+ } else {
65
+ process.env.VELLUM_ENVIRONMENT = originalEnvironment;
66
+ }
58
67
  });
59
68
 
60
69
  test("POSTs a cli device-bound pair request and prints a decodable bundle", async () => {
@@ -967,6 +976,29 @@ describe("pair command", () => {
967
976
  ).toBe(
968
977
  "vellum-assistant-dev://connect?url=https%3A%2F%2Fhost.example.ts.net%2Fassistant-123&code=a%2Bb%2Fc%3D",
969
978
  );
979
+ // A label rides along as an encoded `name` param (spaces percent-encoded,
980
+ // not form-encoded, for the app's URLComponents parser); an empty one is
981
+ // omitted.
982
+ expect(
983
+ buildAppConnectUrl(
984
+ "vellum-assistant",
985
+ "https://pair.example.ts.net",
986
+ "device-code",
987
+ "My Homelab",
988
+ ),
989
+ ).toBe(
990
+ "vellum-assistant://connect?url=https%3A%2F%2Fpair.example.ts.net&code=device-code&name=My%20Homelab",
991
+ );
992
+ expect(
993
+ buildAppConnectUrl(
994
+ "vellum-assistant",
995
+ "https://pair.example.ts.net",
996
+ "device-code",
997
+ "",
998
+ ),
999
+ ).toBe(
1000
+ "vellum-assistant://connect?url=https%3A%2F%2Fpair.example.ts.net&code=device-code",
1001
+ );
970
1002
  });
971
1003
 
972
1004
  test("--qr --app emits an app connect URL alongside the browser URL", async () => {
@@ -1032,8 +1064,10 @@ describe("pair command", () => {
1032
1064
  }
1033
1065
 
1034
1066
  const out = JSON.parse(logs.join("\n"));
1067
+ // Without --label the connect link names the assistant after its lockfile
1068
+ // display name (the id here, since the entry has no name).
1035
1069
  expect(out.appUrl).toBe(
1036
- "vellum-assistant-dev://connect?url=https%3A%2F%2Fpair.example.ts.net&code=device-code",
1070
+ "vellum-assistant-dev://connect?url=https%3A%2F%2Fpair.example.ts.net&code=device-code&name=pair-test",
1037
1071
  );
1038
1072
  // The browser URL stays available as the no-app fallback.
1039
1073
  expect(out.pairUrl).toBe(
@@ -1042,6 +1076,74 @@ describe("pair command", () => {
1042
1076
  expect(out.deviceCode).toBe("device-code");
1043
1077
  });
1044
1078
 
1079
+ test("--qr --app --label overrides the assistant name in the connect link", async () => {
1080
+ const origFetch = globalThis.fetch;
1081
+ 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
+ if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
1091
+ return new Response(
1092
+ JSON.stringify({
1093
+ deviceCode: "device-code",
1094
+ userCode: "ABCD-EFGH",
1095
+ verificationUri: "https://pair.example.ts.net/assistant/pair",
1096
+ expiresAt: "2026-06-04T00:10:00.000Z",
1097
+ expiresInSeconds: 600,
1098
+ }),
1099
+ { status: 200, headers: { "content-type": "application/json" } },
1100
+ );
1101
+ }
1102
+ if (url === `${LOCAL_URL}/v1/remote-web/pairing-verification`) {
1103
+ return new Response(
1104
+ JSON.stringify({
1105
+ status: "approved",
1106
+ verificationUri: "https://pair.example.ts.net/assistant/pair",
1107
+ expiresAt: "2026-06-04T00:10:00.000Z",
1108
+ }),
1109
+ { status: 200, headers: { "content-type": "application/json" } },
1110
+ );
1111
+ }
1112
+ return new Response("not found", { status: 404 });
1113
+ }) as unknown as typeof fetch;
1114
+
1115
+ const logs: string[] = [];
1116
+ const logSpy = spyOn(console, "log").mockImplementation(
1117
+ (...a: unknown[]) => {
1118
+ logs.push(a.join(" "));
1119
+ },
1120
+ );
1121
+
1122
+ process.argv = [
1123
+ "bun",
1124
+ "vellum",
1125
+ "pair",
1126
+ "--qr",
1127
+ "--app",
1128
+ "--label",
1129
+ "Homelab",
1130
+ "--url",
1131
+ "https://pair.example.ts.net",
1132
+ "--json",
1133
+ ];
1134
+ try {
1135
+ await pair();
1136
+ } finally {
1137
+ logSpy.mockRestore();
1138
+ globalThis.fetch = origFetch;
1139
+ }
1140
+
1141
+ const out = JSON.parse(logs.join("\n"));
1142
+ expect(out.appUrl).toBe(
1143
+ "vellum-assistant://connect?url=https%3A%2F%2Fpair.example.ts.net&code=device-code&name=Homelab",
1144
+ );
1145
+ });
1146
+
1045
1147
  test("--app without --qr is refused", async () => {
1046
1148
  let fetchCalled = false;
1047
1149
  const origFetch = globalThis.fetch;
@@ -56,9 +56,11 @@ mock.module("../lib/guardian-token.js", () => ({
56
56
  loadGuardianToken: loadGuardianTokenMock,
57
57
  }));
58
58
 
59
- // Stub the token-refresh helper without importing the real client module
60
- // (it drags in the interactive chat client's dependency graph). Pass-through
61
- // by default: the stored token is returned as-is.
59
+ // Stub the token-refresh helper. Pass-through by default: the stored token is
60
+ // returned as-is. Capture the real module first so afterAll can restore it;
61
+ // without the restore this mock leaks into client-tui-refresh.test.ts, whose
62
+ // refresh test then receives the pass-through instead of the real refresher.
63
+ const realClient = { ...(await import("../commands/client.js")) };
62
64
  const resolveFreshBearerTokenMock = mock(
63
65
  async (
64
66
  _runtimeUrl: string,
@@ -68,6 +70,7 @@ const resolveFreshBearerTokenMock = mock(
68
70
  ) => bearerToken,
69
71
  );
70
72
  mock.module("../commands/client.js", () => ({
73
+ ...realClient,
71
74
  resolveFreshBearerToken: resolveFreshBearerTokenMock,
72
75
  }));
73
76
 
@@ -77,6 +80,7 @@ afterAll(() => {
77
80
  mock.module("../lib/process.js", () => realProcess);
78
81
  mock.module("../lib/drain.js", () => realDrain);
79
82
  mock.module("../lib/guardian-token.js", () => realGuardianToken);
83
+ mock.module("../commands/client.js", () => realClient);
80
84
  });
81
85
 
82
86
  import { sleep } from "../commands/sleep.js";
@@ -160,7 +160,6 @@ describe("startTailscaleServe", () => {
160
160
 
161
161
  expect(info.publicUrl).toBe("https://my-host.tail-scale.ts.net");
162
162
  expect(info.port).toBe(7840);
163
- expect(info.viaIngress).toBe(false);
164
163
  expect(calls).toContainEqual(["serve", "--bg", "7840"]);
165
164
 
166
165
  const config = JSON.parse(