@vellumai/cli 0.10.11-dev.202607220701.38c4eff → 0.10.11-dev.202607220955.d4134f8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.10.11-dev.202607220701.38c4eff",
3
+ "version": "0.10.11-dev.202607220955.d4134f8",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -188,14 +188,7 @@ describe("vellum flags --assistant routing", () => {
188
188
  ],
189
189
  "alice-1",
190
190
  );
191
- process.argv = [
192
- "bun",
193
- "vellum",
194
- "flags",
195
- "set",
196
- "voice-mode",
197
- "true",
198
- ];
191
+ process.argv = ["bun", "vellum", "flags", "set", "voice-mode", "true"];
199
192
 
200
193
  await flags();
201
194
 
@@ -1,10 +1,5 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import {
3
- existsSync,
4
- mkdtempSync,
5
- readFileSync,
6
- rmSync,
7
- } from "node:fs";
2
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
8
3
  import { homedir, tmpdir } from "node:os";
9
4
  import { join } from "node:path";
10
5
 
@@ -3,7 +3,9 @@ import { describe, expect, test } from "bun:test";
3
3
  import { startLoopbackListener } from "../commands/login.js";
4
4
 
5
5
  /** Resolve "settled"/"pending" — proves whether `waitForCode` resolved. */
6
- async function settleState(p: Promise<unknown>): Promise<"settled" | "pending"> {
6
+ async function settleState(
7
+ p: Promise<unknown>,
8
+ ): Promise<"settled" | "pending"> {
7
9
  return Promise.race([
8
10
  p.then(
9
11
  () => "settled" as const,
@@ -49,7 +51,9 @@ describe("startLoopbackListener", () => {
49
51
  () => null,
50
52
  (e: Error) => e,
51
53
  );
52
- const res = await fetch(`${listener.redirectUri}?error=access_denied&state=st`);
54
+ const res = await fetch(
55
+ `${listener.redirectUri}?error=access_denied&state=st`,
56
+ );
53
57
  expect(res.status).toBe(400);
54
58
  const err = await settled;
55
59
  expect(err?.message).toMatch(/access_denied/);
@@ -15,7 +15,7 @@ import { join } from "node:path";
15
15
  const testDir = mkdtempSync(join(tmpdir(), "pair-command-test-"));
16
16
  process.env.VELLUM_LOCKFILE_DIR = testDir;
17
17
 
18
- import { pair } from "../commands/pair.js";
18
+ import { buildAppConnectUrl, pair } from "../commands/pair.js";
19
19
 
20
20
  // Distinct loopback (mint) vs reachable (advertised) URLs to verify the split.
21
21
  const LOCAL_URL = "http://127.0.0.1:7830";
@@ -800,4 +800,438 @@ describe("pair command", () => {
800
800
  `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`,
801
801
  );
802
802
  });
803
+
804
+ test("buildAppConnectUrl composes and encodes the connect link", () => {
805
+ expect(
806
+ buildAppConnectUrl(
807
+ "vellum-assistant",
808
+ "https://pair.example.ts.net",
809
+ "device-code",
810
+ ),
811
+ ).toBe(
812
+ "vellum-assistant://connect?url=https%3A%2F%2Fpair.example.ts.net&code=device-code",
813
+ );
814
+ // Path prefixes and fragment-hostile characters survive the encoding.
815
+ expect(
816
+ buildAppConnectUrl(
817
+ "vellum-assistant-dev",
818
+ "https://host.example.ts.net/assistant-123",
819
+ "a+b/c=",
820
+ ),
821
+ ).toBe(
822
+ "vellum-assistant-dev://connect?url=https%3A%2F%2Fhost.example.ts.net%2Fassistant-123&code=a%2Bb%2Fc%3D",
823
+ );
824
+ });
825
+
826
+ test("--qr --app emits an app connect URL alongside the browser URL", async () => {
827
+ const origFetch = globalThis.fetch;
828
+ globalThis.fetch = (async (url: string) => {
829
+ if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
830
+ return new Response(
831
+ JSON.stringify({
832
+ flags: [{ key: "web-remote-ingress", enabled: true }],
833
+ }),
834
+ { status: 200, headers: { "content-type": "application/json" } },
835
+ );
836
+ }
837
+ if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
838
+ return new Response(
839
+ JSON.stringify({
840
+ deviceCode: "device-code",
841
+ userCode: "ABCD-EFGH",
842
+ verificationUri: "https://pair.example.ts.net/assistant/pair",
843
+ expiresAt: "2026-06-04T00:10:00.000Z",
844
+ expiresInSeconds: 600,
845
+ }),
846
+ { status: 200, headers: { "content-type": "application/json" } },
847
+ );
848
+ }
849
+ if (url === `${LOCAL_URL}/v1/remote-web/pairing-verification`) {
850
+ return new Response(
851
+ JSON.stringify({
852
+ status: "approved",
853
+ verificationUri: "https://pair.example.ts.net/assistant/pair",
854
+ expiresAt: "2026-06-04T00:10:00.000Z",
855
+ }),
856
+ { status: 200, headers: { "content-type": "application/json" } },
857
+ );
858
+ }
859
+ return new Response("not found", { status: 404 });
860
+ }) as unknown as typeof fetch;
861
+
862
+ const logs: string[] = [];
863
+ const logSpy = spyOn(console, "log").mockImplementation(
864
+ (...a: unknown[]) => {
865
+ logs.push(a.join(" "));
866
+ },
867
+ );
868
+
869
+ process.argv = [
870
+ "bun",
871
+ "vellum",
872
+ "pair",
873
+ "--qr",
874
+ "--app",
875
+ "--app-scheme",
876
+ "vellum-assistant-dev",
877
+ "--url",
878
+ "https://pair.example.ts.net",
879
+ "--json",
880
+ ];
881
+ try {
882
+ await pair();
883
+ } finally {
884
+ logSpy.mockRestore();
885
+ globalThis.fetch = origFetch;
886
+ }
887
+
888
+ const out = JSON.parse(logs.join("\n"));
889
+ expect(out.appUrl).toBe(
890
+ "vellum-assistant-dev://connect?url=https%3A%2F%2Fpair.example.ts.net&code=device-code",
891
+ );
892
+ // The browser URL stays available as the no-app fallback.
893
+ expect(out.pairUrl).toBe(
894
+ "https://pair.example.ts.net/assistant/pair#device_code=device-code",
895
+ );
896
+ expect(out.deviceCode).toBe("device-code");
897
+ });
898
+
899
+ test("--app without --qr is refused", async () => {
900
+ let fetchCalled = false;
901
+ const origFetch = globalThis.fetch;
902
+ globalThis.fetch = (async () => {
903
+ fetchCalled = true;
904
+ return new Response("{}", { status: 200 });
905
+ }) as unknown as typeof fetch;
906
+ const errors: string[] = [];
907
+ const errSpy = spyOn(console, "error").mockImplementation(
908
+ (...a: unknown[]) => {
909
+ errors.push(a.join(" "));
910
+ },
911
+ );
912
+ const exitSpy = spyOn(process, "exit").mockImplementation(((
913
+ code?: number,
914
+ ) => {
915
+ throw new Error(`exit:${code}`);
916
+ }) as never);
917
+
918
+ process.argv = ["bun", "vellum", "pair", "--app"];
919
+ let exited = false;
920
+ try {
921
+ await pair();
922
+ } catch (e) {
923
+ exited = (e as Error).message === "exit:1";
924
+ } finally {
925
+ errSpy.mockRestore();
926
+ exitSpy.mockRestore();
927
+ globalThis.fetch = origFetch;
928
+ }
929
+
930
+ expect(exited).toBe(true);
931
+ expect(errors.join("\n")).toContain("--qr");
932
+ expect(fetchCalled).toBe(false);
933
+ });
934
+
935
+ test("--qr with no --url uses the tunnel-saved ingress URL", async () => {
936
+ const workspaceDir = mkdtempSync(join(tmpdir(), "vellum-pair-ingress-"));
937
+ // Point the synthesized instance dir at an empty temp XDG root so the
938
+ // resolution deterministically falls through to VELLUM_WORKSPACE_DIR.
939
+ const xdgDir = mkdtempSync(join(tmpdir(), "vellum-pair-xdg-"));
940
+ process.env.XDG_DATA_HOME = xdgDir;
941
+ writeFileSync(
942
+ join(workspaceDir, "config.json"),
943
+ JSON.stringify({
944
+ ingress: {
945
+ publicBaseUrl: "https://saved.example.ts.net",
946
+ enabled: true,
947
+ },
948
+ }),
949
+ );
950
+ process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
951
+
952
+ const calls: string[] = [];
953
+ const origFetch = globalThis.fetch;
954
+ globalThis.fetch = (async (url: string, init?: RequestInit) => {
955
+ calls.push(url);
956
+ if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
957
+ return new Response(
958
+ JSON.stringify({
959
+ flags: [{ key: "web-remote-ingress", enabled: true }],
960
+ }),
961
+ { status: 200, headers: { "content-type": "application/json" } },
962
+ );
963
+ }
964
+ if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
965
+ // The minted challenge must advertise the SAVED ingress URL.
966
+ expect(JSON.parse(init?.body as string)).toEqual({
967
+ publicBaseUrl: "https://saved.example.ts.net",
968
+ });
969
+ return new Response(
970
+ JSON.stringify({
971
+ deviceCode: "device-code",
972
+ userCode: "ABCD-EFGH",
973
+ verificationUri: "https://saved.example.ts.net/assistant/pair",
974
+ expiresAt: "2026-06-04T00:10:00.000Z",
975
+ expiresInSeconds: 600,
976
+ }),
977
+ { status: 200, headers: { "content-type": "application/json" } },
978
+ );
979
+ }
980
+ if (url === `${LOCAL_URL}/v1/remote-web/pairing-verification`) {
981
+ return new Response(
982
+ JSON.stringify({
983
+ status: "approved",
984
+ verificationUri: "https://saved.example.ts.net/assistant/pair",
985
+ expiresAt: "2026-06-04T00:10:00.000Z",
986
+ }),
987
+ { status: 200, headers: { "content-type": "application/json" } },
988
+ );
989
+ }
990
+ return new Response("not found", { status: 404 });
991
+ }) as unknown as typeof fetch;
992
+
993
+ const logs: string[] = [];
994
+ const logSpy = spyOn(console, "log").mockImplementation(
995
+ (...a: unknown[]) => {
996
+ logs.push(a.join(" "));
997
+ },
998
+ );
999
+
1000
+ process.argv = ["bun", "vellum", "pair", "--qr"];
1001
+ try {
1002
+ await pair();
1003
+ } finally {
1004
+ logSpy.mockRestore();
1005
+ globalThis.fetch = origFetch;
1006
+ delete process.env.VELLUM_WORKSPACE_DIR;
1007
+ delete process.env.XDG_DATA_HOME;
1008
+ rmSync(workspaceDir, { recursive: true, force: true });
1009
+ rmSync(xdgDir, { recursive: true, force: true });
1010
+ }
1011
+
1012
+ expect(calls).toContain(`${LOCAL_URL}/v1/remote-web/pairing-challenge`);
1013
+ const output = logs.join("\n");
1014
+ expect(output).toContain(
1015
+ "Using saved ingress URL https://saved.example.ts.net",
1016
+ );
1017
+ expect(output).toContain(
1018
+ "https://saved.example.ts.net/assistant/pair#device_code=device-code",
1019
+ );
1020
+ });
1021
+
1022
+ test("--url beats the saved ingress URL", async () => {
1023
+ const workspaceDir = mkdtempSync(join(tmpdir(), "vellum-pair-ingress-"));
1024
+ // Point the synthesized instance dir at an empty temp XDG root so the
1025
+ // resolution deterministically falls through to VELLUM_WORKSPACE_DIR.
1026
+ const xdgDir = mkdtempSync(join(tmpdir(), "vellum-pair-xdg-"));
1027
+ process.env.XDG_DATA_HOME = xdgDir;
1028
+ writeFileSync(
1029
+ join(workspaceDir, "config.json"),
1030
+ JSON.stringify({
1031
+ ingress: {
1032
+ publicBaseUrl: "https://saved.example.ts.net",
1033
+ enabled: true,
1034
+ },
1035
+ }),
1036
+ );
1037
+ process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
1038
+
1039
+ const origFetch = globalThis.fetch;
1040
+ globalThis.fetch = (async (url: string, init?: RequestInit) => {
1041
+ if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
1042
+ return new Response(
1043
+ JSON.stringify({
1044
+ flags: [{ key: "web-remote-ingress", enabled: true }],
1045
+ }),
1046
+ { status: 200, headers: { "content-type": "application/json" } },
1047
+ );
1048
+ }
1049
+ if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
1050
+ expect(JSON.parse(init?.body as string)).toEqual({
1051
+ publicBaseUrl: "https://explicit.example.ts.net",
1052
+ });
1053
+ return new Response(
1054
+ JSON.stringify({
1055
+ deviceCode: "device-code",
1056
+ userCode: "ABCD-EFGH",
1057
+ verificationUri: "https://explicit.example.ts.net/assistant/pair",
1058
+ expiresAt: "2026-06-04T00:10:00.000Z",
1059
+ expiresInSeconds: 600,
1060
+ }),
1061
+ { status: 200, headers: { "content-type": "application/json" } },
1062
+ );
1063
+ }
1064
+ if (url === `${LOCAL_URL}/v1/remote-web/pairing-verification`) {
1065
+ return new Response(
1066
+ JSON.stringify({
1067
+ status: "approved",
1068
+ verificationUri: "https://explicit.example.ts.net/assistant/pair",
1069
+ expiresAt: "2026-06-04T00:10:00.000Z",
1070
+ }),
1071
+ { status: 200, headers: { "content-type": "application/json" } },
1072
+ );
1073
+ }
1074
+ return new Response("not found", { status: 404 });
1075
+ }) as unknown as typeof fetch;
1076
+
1077
+ const logs: string[] = [];
1078
+ const logSpy = spyOn(console, "log").mockImplementation(
1079
+ (...a: unknown[]) => {
1080
+ logs.push(a.join(" "));
1081
+ },
1082
+ );
1083
+
1084
+ process.argv = [
1085
+ "bun",
1086
+ "vellum",
1087
+ "pair",
1088
+ "--qr",
1089
+ "--url",
1090
+ "https://explicit.example.ts.net",
1091
+ ];
1092
+ try {
1093
+ await pair();
1094
+ } finally {
1095
+ logSpy.mockRestore();
1096
+ globalThis.fetch = origFetch;
1097
+ delete process.env.VELLUM_WORKSPACE_DIR;
1098
+ delete process.env.XDG_DATA_HOME;
1099
+ rmSync(workspaceDir, { recursive: true, force: true });
1100
+ rmSync(xdgDir, { recursive: true, force: true });
1101
+ }
1102
+
1103
+ expect(logs.join("\n")).not.toContain("Using saved ingress URL");
1104
+ });
1105
+
1106
+ test("a non-https saved ingress URL is ignored", async () => {
1107
+ const workspaceDir = mkdtempSync(join(tmpdir(), "vellum-pair-ingress-"));
1108
+ // Point the synthesized instance dir at an empty temp XDG root so the
1109
+ // resolution deterministically falls through to VELLUM_WORKSPACE_DIR.
1110
+ const xdgDir = mkdtempSync(join(tmpdir(), "vellum-pair-xdg-"));
1111
+ process.env.XDG_DATA_HOME = xdgDir;
1112
+ writeFileSync(
1113
+ join(workspaceDir, "config.json"),
1114
+ JSON.stringify({
1115
+ ingress: {
1116
+ publicBaseUrl: "http://insecure.example.com",
1117
+ enabled: true,
1118
+ },
1119
+ }),
1120
+ );
1121
+ process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
1122
+
1123
+ const origFetch = globalThis.fetch;
1124
+ let minted = false;
1125
+ globalThis.fetch = (async (url: string) => {
1126
+ if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
1127
+ return new Response(
1128
+ JSON.stringify({
1129
+ flags: [{ key: "web-remote-ingress", enabled: true }],
1130
+ }),
1131
+ { status: 200, headers: { "content-type": "application/json" } },
1132
+ );
1133
+ }
1134
+ minted = true;
1135
+ return new Response("{}", { status: 200 });
1136
+ }) as unknown as typeof fetch;
1137
+
1138
+ const errors: string[] = [];
1139
+ const errSpy = spyOn(console, "error").mockImplementation(
1140
+ (...a: unknown[]) => {
1141
+ errors.push(a.join(" "));
1142
+ },
1143
+ );
1144
+ const exitSpy = spyOn(process, "exit").mockImplementation(((
1145
+ code?: number,
1146
+ ) => {
1147
+ throw new Error(`exit:${code}`);
1148
+ }) as never);
1149
+
1150
+ process.argv = ["bun", "vellum", "pair", "--qr"];
1151
+ let exited = false;
1152
+ try {
1153
+ await pair();
1154
+ } catch (e) {
1155
+ exited = (e as Error).message === "exit:1";
1156
+ } finally {
1157
+ errSpy.mockRestore();
1158
+ exitSpy.mockRestore();
1159
+ globalThis.fetch = origFetch;
1160
+ delete process.env.VELLUM_WORKSPACE_DIR;
1161
+ delete process.env.XDG_DATA_HOME;
1162
+ rmSync(workspaceDir, { recursive: true, force: true });
1163
+ rmSync(xdgDir, { recursive: true, force: true });
1164
+ }
1165
+
1166
+ // The saved http URL is skipped, so --qr falls through to the (non-https)
1167
+ // runtime URL and refuses — proving the saved value was not advertised.
1168
+ expect(exited).toBe(true);
1169
+ expect(errors.join("\n")).toContain(RUNTIME_URL);
1170
+ expect(minted).toBe(false);
1171
+ });
1172
+
1173
+ test("a saved ingress URL with ingress disabled is ignored", async () => {
1174
+ const workspaceDir = mkdtempSync(join(tmpdir(), "vellum-pair-ingress-"));
1175
+ // Point the synthesized instance dir at an empty temp XDG root so the
1176
+ // resolution deterministically falls through to VELLUM_WORKSPACE_DIR.
1177
+ const xdgDir = mkdtempSync(join(tmpdir(), "vellum-pair-xdg-"));
1178
+ process.env.XDG_DATA_HOME = xdgDir;
1179
+ writeFileSync(
1180
+ join(workspaceDir, "config.json"),
1181
+ JSON.stringify({
1182
+ ingress: {
1183
+ publicBaseUrl: "https://saved.example.ts.net",
1184
+ enabled: false,
1185
+ },
1186
+ }),
1187
+ );
1188
+ process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
1189
+
1190
+ const origFetch = globalThis.fetch;
1191
+ let minted = false;
1192
+ globalThis.fetch = (async (url: string) => {
1193
+ if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
1194
+ return new Response(
1195
+ JSON.stringify({
1196
+ flags: [{ key: "web-remote-ingress", enabled: true }],
1197
+ }),
1198
+ { status: 200, headers: { "content-type": "application/json" } },
1199
+ );
1200
+ }
1201
+ minted = true;
1202
+ return new Response("{}", { status: 200 });
1203
+ }) as unknown as typeof fetch;
1204
+
1205
+ const errors: string[] = [];
1206
+ const errSpy = spyOn(console, "error").mockImplementation(
1207
+ (...a: unknown[]) => {
1208
+ errors.push(a.join(" "));
1209
+ },
1210
+ );
1211
+ const exitSpy = spyOn(process, "exit").mockImplementation(((
1212
+ code?: number,
1213
+ ) => {
1214
+ throw new Error(`exit:${code}`);
1215
+ }) as never);
1216
+
1217
+ process.argv = ["bun", "vellum", "pair", "--qr"];
1218
+ let exited = false;
1219
+ try {
1220
+ await pair();
1221
+ } catch (e) {
1222
+ exited = (e as Error).message === "exit:1";
1223
+ } finally {
1224
+ errSpy.mockRestore();
1225
+ exitSpy.mockRestore();
1226
+ globalThis.fetch = origFetch;
1227
+ delete process.env.VELLUM_WORKSPACE_DIR;
1228
+ delete process.env.XDG_DATA_HOME;
1229
+ rmSync(workspaceDir, { recursive: true, force: true });
1230
+ rmSync(xdgDir, { recursive: true, force: true });
1231
+ }
1232
+
1233
+ expect(exited).toBe(true);
1234
+ expect(errors.join("\n")).toContain(RUNTIME_URL);
1235
+ expect(minted).toBe(false);
1236
+ });
803
1237
  });
@@ -240,7 +240,11 @@ describe("fetchAssistantDetail / fetchUpgradeInProgress", () => {
240
240
  current_release_version: "0.7.0",
241
241
  release_channel: "preview",
242
242
  });
243
- const detail = await fetchAssistantDetail(TOKEN, ASSISTANT_ID, PLATFORM_URL);
243
+ const detail = await fetchAssistantDetail(
244
+ TOKEN,
245
+ ASSISTANT_ID,
246
+ PLATFORM_URL,
247
+ );
244
248
  expect(detail).toEqual({
245
249
  currentReleaseVersion: "0.7.0",
246
250
  releaseChannel: "preview",
@@ -251,7 +255,11 @@ describe("fetchAssistantDetail / fetchUpgradeInProgress", () => {
251
255
 
252
256
  test("fetchAssistantDetail defaults missing fields", async () => {
253
257
  mockFetchJson({});
254
- const detail = await fetchAssistantDetail(TOKEN, ASSISTANT_ID, PLATFORM_URL);
258
+ const detail = await fetchAssistantDetail(
259
+ TOKEN,
260
+ ASSISTANT_ID,
261
+ PLATFORM_URL,
262
+ );
255
263
  expect(detail).toEqual({
256
264
  currentReleaseVersion: null,
257
265
  releaseChannel: "stable",
@@ -56,7 +56,9 @@ describe("getBuilderManagedEnvKeys", () => {
56
56
 
57
57
  test("gateway hostForwarded equals the three spec host entries", () => {
58
58
  const { hostForwarded } = getBuilderManagedEnvKeys("gateway");
59
- const sorted = [...hostForwarded].sort((a, b) => a.name.localeCompare(b.name));
59
+ const sorted = [...hostForwarded].sort((a, b) =>
60
+ a.name.localeCompare(b.name),
61
+ );
60
62
  expect(sorted).toEqual([
61
63
  { name: "VELAY_BASE_URL", hostVar: "VELAY_BASE_URL" },
62
64
  { name: "VELLUM_ENVIRONMENT", hostVar: "VELLUM_ENVIRONMENT" },