@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
|
@@ -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
|
});
|
package/src/commands/client.ts
CHANGED
|
@@ -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
|
-
//
|
|
929
|
-
//
|
|
930
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
package/src/commands/login.ts
CHANGED
|
@@ -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
|
|
package/src/lib/open-browser.ts
DELETED
|
@@ -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
|
-
}
|