@vellumai/cli 0.10.2-dev.202606250318.5e7cfb0 → 0.10.2-staging.1

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.2-dev.202606250318.5e7cfb0",
3
+ "version": "0.10.2-staging.1",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -84,31 +84,4 @@ describe("client --token (ephemeral)", () => {
84
84
  expect(parsed.assistantId).toBe("remote-xyz");
85
85
  expect(parsed.bearerToken).toBe("tok");
86
86
  });
87
-
88
- test("auto-opens the browser by default", () => {
89
- process.argv = [
90
- "bun",
91
- "vellum",
92
- "client",
93
- "--url",
94
- REMOTE_URL,
95
- "--token",
96
- "tok",
97
- ];
98
- expect(parseArgs().openBrowser).toBe(true);
99
- });
100
-
101
- test("--no-open opts out of auto-opening the browser", () => {
102
- process.argv = [
103
- "bun",
104
- "vellum",
105
- "client",
106
- "--url",
107
- REMOTE_URL,
108
- "--token",
109
- "tok",
110
- "--no-open",
111
- ];
112
- expect(parseArgs().openBrowser).toBe(false);
113
- });
114
87
  });
@@ -60,7 +60,6 @@ import {
60
60
  import { tuiLog } from "../lib/tui-log";
61
61
  import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
62
62
  import { probePort } from "../lib/port-probe.js";
63
- import { openBrowser } from "../lib/open-browser";
64
63
 
65
64
  const SUPPORTED_INTERFACES = ["cli", "web"] as const;
66
65
  type SupportedInterface = (typeof SUPPORTED_INTERFACES)[number];
@@ -91,8 +90,6 @@ interface ParsedArgs {
91
90
  /** Parsed --flag overrides: kebab-case key -> typed value (for web injection). */
92
91
  parsedFlagOverrides: Record<string, boolean | string>;
93
92
  disablePlatform: boolean;
94
- /** Auto-open the web interface in the default browser (--interface web only). */
95
- openBrowser: boolean;
96
93
  }
97
94
 
98
95
  function readAssistantName(entry: AssistantEntry | null): string | undefined {
@@ -139,8 +136,6 @@ export function parseArgs(): ParsedArgs {
139
136
  "--token",
140
137
  "-t",
141
138
  ]);
142
- // Auto-open the web interface in the browser by default; --no-open opts out.
143
- let openBrowserPref = true;
144
139
  const flagArgs: string[] = [];
145
140
  for (let i = 0; i < args.length; i++) {
146
141
  const arg = args[i];
@@ -149,8 +144,6 @@ export function parseArgs(): ParsedArgs {
149
144
  process.exit(0);
150
145
  } else if (arg === "--disable-platform") {
151
146
  disablePlatform = true;
152
- } else if (arg === "--no-open") {
153
- openBrowserPref = false;
154
147
  } else if (
155
148
  (arg === "--url" ||
156
149
  arg === "-u" ||
@@ -271,7 +264,6 @@ export function parseArgs(): ParsedArgs {
271
264
  flagEnvVars,
272
265
  parsedFlagOverrides,
273
266
  disablePlatform,
274
- openBrowser: openBrowserPref,
275
267
  };
276
268
  }
277
269
 
@@ -291,7 +283,6 @@ ${ANSI.bold}OPTIONS:${ANSI.reset}
291
283
  not persisted.
292
284
  -a, --assistant-id <id> Assistant ID
293
285
  -i, --interface <id> Interface identifier: cli (default) or web
294
- --no-open Don't auto-open the browser (--interface web)
295
286
  --flag <key=value> Feature flag override (repeatable, kebab-case key)
296
287
  --disable-platform Suppress all outbound platform API calls
297
288
  -h, --help Show this help message
@@ -825,26 +816,10 @@ async function findFreeDualLoopbackPort(preferred: number): Promise<number> {
825
816
  return preferred;
826
817
  }
827
818
 
828
- /**
829
- * Open `url` in the browser once `port` is accepting connections, polling for
830
- * up to ~10s. Used for the Vite dev server, which binds the port asynchronously
831
- * after spawn — opening immediately would load the tab before Vite is ready.
832
- */
833
- async function openBrowserWhenReady(url: string, port: number): Promise<void> {
834
- for (let attempt = 0; attempt < 50; attempt++) {
835
- if (await probePort(port, "127.0.0.1")) {
836
- openBrowser(url);
837
- return;
838
- }
839
- await new Promise((resolve) => setTimeout(resolve, 200));
840
- }
841
- }
842
-
843
819
  async function runWebInterface(
844
820
  flagEnvVars: Record<string, string>,
845
821
  parsedFlagOverrides: Record<string, boolean | string>,
846
822
  disablePlatform: boolean,
847
- openInBrowser: boolean,
848
823
  ): Promise<void> {
849
824
  // Propagate flag env vars so child processes (e.g. hatch from the web UI) inherit them.
850
825
  Object.assign(process.env, flagEnvVars);
@@ -853,12 +828,7 @@ async function runWebInterface(
853
828
  // (HMR, __local endpoints, gateway proxy).
854
829
  const webSourceDir = findWebSourceDir();
855
830
  if (webSourceDir) {
856
- return runViteDevServer(
857
- webSourceDir,
858
- flagEnvVars,
859
- disablePlatform,
860
- openInBrowser,
861
- );
831
+ return runViteDevServer(webSourceDir, flagEnvVars, disablePlatform);
862
832
  }
863
833
 
864
834
  const distDir = findWebDistDir();
@@ -925,27 +895,14 @@ async function runWebInterface(
925
895
  headers.delete("Origin");
926
896
  headers.delete("Referer");
927
897
 
928
- // The DRF API authenticates by header (X-Session-Token); the allauth /
929
- // accounts session endpoints need the Django session cookie.
930
- const isApiRequest = pathname.startsWith("/v1/");
931
-
932
- // Authenticate with the loopback session token the SPA registered. Only
898
+ // Authenticate with the loopback session token the SPA registered. The
899
+ // platform expects it both as the Django session cookie and as
900
+ // X-Session-Token (for DRF views that accept header-based auth). Only
933
901
  // same-origin SPA traffic gets the credential — never a cross-site caller.
934
902
  const sessionToken = isSameOriginRequest(req)
935
903
  ? currentPlatformToken()
936
904
  : null;
937
- if (isApiRequest) {
938
- // Header-only auth for the DRF API. Sending a `sessionid` cookie would
939
- // engage Django's SessionAuthentication, which enforces CSRF — and the
940
- // proxy strips Origin/Referer above, so the CSRF Referer check would
941
- // reject every unsafe (POST/PUT/PATCH) request. Drop any browser cookie
942
- // (localhost jar) so it can't re-engage that path.
943
- headers.delete("Cookie");
944
- if (sessionToken) {
945
- headers.set("X-Session-Token", sessionToken);
946
- }
947
- } else if (sessionToken) {
948
- // allauth / accounts: the platform expects the Django session cookie.
905
+ if (sessionToken) {
949
906
  headers.set(
950
907
  "Cookie",
951
908
  `sessionid=${sessionToken}; __Secure-sessionid=${sessionToken}`,
@@ -1008,9 +965,7 @@ async function runWebInterface(
1008
965
  // Advertise `localhost` (not `127.0.0.1`) so the app origin matches the host
1009
966
  // the platform hardcodes in its loopback callback. We bind both loopback
1010
967
  // families above so `localhost` reaches us whichever one it resolves to.
1011
- const webInterfaceUrl = `http://localhost:${port}${SPA_BASE}`;
1012
- console.log(`Vellum web interface: ${webInterfaceUrl}`);
1013
- if (openInBrowser) openBrowser(webInterfaceUrl);
968
+ console.log(`Vellum web interface: http://localhost:${port}${SPA_BASE}`);
1014
969
 
1015
970
  const shutdown = (): void => {
1016
971
  for (const server of servers) server.stop();
@@ -1026,7 +981,6 @@ async function runViteDevServer(
1026
981
  webSourceDir: string,
1027
982
  flagEnvVars: Record<string, string>,
1028
983
  disablePlatform: boolean,
1029
- openInBrowser: boolean,
1030
984
  ): Promise<void> {
1031
985
  const platformUrl = getPlatformUrl();
1032
986
 
@@ -1060,12 +1014,6 @@ async function runViteDevServer(
1060
1014
  },
1061
1015
  });
1062
1016
 
1063
- // Vite binds the port itself, so wait until it's listening before opening the
1064
- // browser — otherwise the tab loads before the dev server is ready.
1065
- if (openInBrowser) {
1066
- void openBrowserWhenReady(`http://localhost:${port}${SPA_BASE}`, port);
1067
- }
1068
-
1069
1017
  const shutdown = (): void => {
1070
1018
  child.kill();
1071
1019
  process.exit(0);
@@ -1138,7 +1086,6 @@ export async function client(): Promise<void> {
1138
1086
  flagEnvVars,
1139
1087
  parsedFlagOverrides,
1140
1088
  disablePlatform,
1141
- openBrowser: openInBrowser,
1142
1089
  } = parseArgs();
1143
1090
 
1144
1091
  if (disablePlatform) {
@@ -1146,12 +1093,7 @@ export async function client(): Promise<void> {
1146
1093
  }
1147
1094
 
1148
1095
  if (interfaceId === WEB_INTERFACE_ID) {
1149
- await runWebInterface(
1150
- flagEnvVars,
1151
- parsedFlagOverrides,
1152
- disablePlatform,
1153
- openInBrowser,
1154
- );
1096
+ await runWebInterface(flagEnvVars, parsedFlagOverrides, disablePlatform);
1155
1097
  return;
1156
1098
  }
1157
1099
 
@@ -1,3 +1,4 @@
1
+ import { spawn } from "child_process";
1
2
  import { randomBytes } from "crypto";
2
3
  import { createServer } from "http";
3
4
  import type { AddressInfo } from "net";
@@ -10,7 +11,6 @@ import {
10
11
  setActiveAssistant,
11
12
  } from "../lib/assistant-config";
12
13
  import { computeDeviceId } from "../lib/guardian-token";
13
- import { openBrowser } from "../lib/open-browser";
14
14
  import {
15
15
  clearPlatformToken,
16
16
  ensureSelfHostedLocalRegistration,
@@ -169,6 +169,24 @@ function renderLoginPage(
169
169
  </html>`;
170
170
  }
171
171
 
172
+ /**
173
+ * Open a URL in the user's default browser.
174
+ */
175
+ function openBrowser(url: string): void {
176
+ const platform = process.platform;
177
+ const cmd =
178
+ platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
179
+ const args =
180
+ platform === "win32"
181
+ ? ["/c", "start", '""', url.replace(/&/g, "^&")]
182
+ : [url];
183
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
184
+ child.on("error", () => {
185
+ // Silently ignore — the user can still copy the URL from the console
186
+ });
187
+ child.unref();
188
+ }
189
+
172
190
  export interface LoopbackListener {
173
191
  /** The full `http://127.0.0.1:<port>/auth/callback` redirect URI. */
174
192
  redirectUri: string;
@@ -18,9 +18,6 @@ export function canPromptForConfirmation(): boolean {
18
18
  * Show `prompt` and resolve true on Enter, false on Esc/q/Ctrl-C. Restores the
19
19
  * prior stdin raw/paused state on exit. Caller must gate on
20
20
  * {@link canPromptForConfirmation} first.
21
- *
22
- * `unref()`s stdin on cleanup so the resumed handle doesn't keep the process
23
- * alive after the prompt resolves.
24
21
  */
25
22
  export async function confirmAction(prompt: string): Promise<boolean> {
26
23
  const stdin = process.stdin;
@@ -39,7 +36,6 @@ export async function confirmAction(prompt: string): Promise<boolean> {
39
36
  if (wasPaused) {
40
37
  stdin.pause();
41
38
  }
42
- stdin.unref?.();
43
39
  stdout.write("\n");
44
40
  };
45
41
 
@@ -1,20 +0,0 @@
1
- import { spawn } from "node:child_process";
2
-
3
- /**
4
- * Open a URL in the user's default browser. Best-effort: a failure to launch is
5
- * swallowed so the caller can still surface the URL for the user to copy.
6
- */
7
- export function openBrowser(url: string): void {
8
- const platform = process.platform;
9
- const cmd =
10
- platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
11
- const args =
12
- platform === "win32"
13
- ? ["/c", "start", '""', url.replace(/&/g, "^&")]
14
- : [url];
15
- const child = spawn(cmd, args, { stdio: "ignore", detached: true });
16
- child.on("error", () => {
17
- // Silently ignore — the user can still copy the URL from the console.
18
- });
19
- child.unref();
20
- }