@vellumai/cli 0.4.32 → 0.4.33

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.4.32",
3
+ "version": "0.4.33",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,3 @@
1
- import { readFileSync } from "fs";
2
- import { join } from "path";
3
-
4
1
  import {
5
2
  findAssistantByName,
6
3
  loadLatestAssistant,
@@ -60,21 +57,8 @@ function parseArgs(): ParsedArgs {
60
57
  process.env.RUNTIME_URL || entry?.runtimeUrl || FALLBACK_RUNTIME_URL;
61
58
  let assistantId =
62
59
  process.env.ASSISTANT_ID || entry?.assistantId || FALLBACK_ASSISTANT_ID;
63
- let bearerToken =
60
+ const bearerToken =
64
61
  process.env.RUNTIME_PROXY_BEARER_TOKEN || entry?.bearerToken || undefined;
65
-
66
- // For local assistants, read the daemon's http-token file as a fallback
67
- // when the lockfile doesn't include a bearer token.
68
- if (!bearerToken && entry?.cloud === "local") {
69
- const tokenDir =
70
- entry.baseDataDir ?? join(process.env.HOME ?? "", ".vellum");
71
- try {
72
- const token = readFileSync(join(tokenDir, "http-token"), "utf-8").trim();
73
- if (token) bearerToken = token;
74
- } catch {
75
- // Token file may not exist
76
- }
77
- }
78
62
  const species: Species = (entry?.species as Species) ?? "vellum";
79
63
 
80
64
  for (let i = 0; i < flagArgs.length; i++) {
@@ -1,7 +1,3 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
-
5
1
  import { loadLatestAssistant } from "../lib/assistant-config";
6
2
  import { GATEWAY_PORT } from "../lib/constants.js";
7
3
 
@@ -17,21 +13,7 @@ function getGatewayUrl(): string {
17
13
 
18
14
  function getBearerToken(): string | undefined {
19
15
  const entry = loadLatestAssistant();
20
- if (entry?.bearerToken) return entry.bearerToken;
21
- try {
22
- const tokenPath = join(
23
- process.env.BASE_DATA_DIR?.trim() || homedir(),
24
- ".vellum",
25
- "http-token",
26
- );
27
- if (existsSync(tokenPath)) {
28
- const token = readFileSync(tokenPath, "utf-8").trim();
29
- if (token) return token;
30
- }
31
- } catch {
32
- // ignore
33
- }
34
- return undefined;
16
+ return entry?.bearerToken;
35
17
  }
36
18
 
37
19
  function buildHeaders(): Record<string, string> {
@@ -756,8 +756,8 @@ async function hatchLocal(
756
756
  throw error;
757
757
  }
758
758
 
759
- // Read the bearer token written by the daemon so the client can authenticate
760
- // with the gateway (which requires auth by default).
759
+ // Read the bearer token (JWT) written by the daemon so the CLI can
760
+ // authenticate with the gateway.
761
761
  let bearerToken: string | undefined;
762
762
  try {
763
763
  const token = readFileSync(join(baseDataDir, "http-token"), "utf-8").trim();
@@ -403,7 +403,7 @@ async function listAllAssistants(): Promise<void> {
403
403
 
404
404
  await Promise.all(
405
405
  assistants.map(async (a, rowIndex) => {
406
- const health = await checkHealth(a.runtimeUrl);
406
+ const health = await checkHealth(a.runtimeUrl, a.bearerToken);
407
407
 
408
408
  const infoParts = [a.runtimeUrl];
409
409
  if (a.cloud) infoParts.push(`cloud: ${a.cloud}`);
@@ -12,18 +12,26 @@ export interface HealthCheckResult {
12
12
 
13
13
  export async function checkHealth(
14
14
  runtimeUrl: string,
15
+ bearerToken?: string,
15
16
  ): Promise<HealthCheckResult> {
16
17
  try {
17
- const url = `${runtimeUrl}/healthz`;
18
+ const url = `${runtimeUrl}/v1/health`;
18
19
  const controller = new AbortController();
19
20
  const timeoutId = setTimeout(
20
21
  () => controller.abort(),
21
22
  HEALTH_CHECK_TIMEOUT_MS,
22
23
  );
23
24
 
25
+ const headers: Record<string, string> = {
26
+ "Content-Type": "application/json",
27
+ };
28
+ if (bearerToken) {
29
+ headers["Authorization"] = `Bearer ${bearerToken}`;
30
+ }
31
+
24
32
  const response = await fetch(url, {
25
33
  signal: controller.signal,
26
- headers: { "Content-Type": "application/json" },
34
+ headers,
27
35
  });
28
36
 
29
37
  clearTimeout(timeoutId);
package/src/lib/local.ts CHANGED
@@ -829,73 +829,10 @@ export async function startGateway(
829
829
  process.env.GATEWAY_DEFAULT_ASSISTANT_ID ||
830
830
  loadLatestAssistant()?.assistantId;
831
831
 
832
- // Read the bearer token so the gateway can authenticate proxied requests
833
- // (e.g. from paired iOS devices). Respect VELLUM_HTTP_TOKEN_PATH and
834
- // BASE_DATA_DIR for consistency with gateway/config.ts and the daemon.
835
- const httpTokenPath =
836
- process.env.VELLUM_HTTP_TOKEN_PATH ??
837
- join(
838
- process.env.BASE_DATA_DIR?.trim() || homedir(),
839
- ".vellum",
840
- "http-token",
841
- );
842
- let runtimeProxyBearerToken: string | undefined;
843
- try {
844
- const tok = readFileSync(httpTokenPath, "utf-8").trim();
845
- if (tok) runtimeProxyBearerToken = tok;
846
- } catch {
847
- // Token file doesn't exist yet — daemon hasn't written it.
848
- }
849
-
850
- // If no token is available (first startup — daemon hasn't written it yet),
851
- // poll for the file to appear. On fresh installs the daemon may take 60s+
852
- // for Qdrant download, migrations, and first-time init. Starting the
853
- // gateway without auth is a security risk since the config is loaded once
854
- // at startup and never reloads, so we fail rather than silently disabling auth.
855
- if (!runtimeProxyBearerToken) {
856
- console.log(" Waiting for bearer token file...");
857
- const maxWait = 60000;
858
- const pollInterval = 500;
859
- const start = Date.now();
860
- const pidFile = join(
861
- process.env.BASE_DATA_DIR?.trim() || homedir(),
862
- ".vellum",
863
- "vellum.pid",
864
- );
865
- while (Date.now() - start < maxWait) {
866
- await new Promise((r) => setTimeout(r, pollInterval));
867
- try {
868
- const tok = readFileSync(httpTokenPath, "utf-8").trim();
869
- if (tok) {
870
- runtimeProxyBearerToken = tok;
871
- break;
872
- }
873
- } catch {
874
- // File still doesn't exist, keep polling.
875
- }
876
- // Check if the daemon process is still alive — no point waiting if it crashed
877
- try {
878
- const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
879
- if (pid) process.kill(pid, 0); // throws if process doesn't exist
880
- } catch {
881
- break; // daemon process is gone
882
- }
883
- }
884
- }
885
-
886
- if (!runtimeProxyBearerToken) {
887
- throw new Error(
888
- `Bearer token file not found at ${httpTokenPath} after 60s.\n` +
889
- " The gateway cannot start without authentication — this would leave the proxy permanently unauthenticated.\n" +
890
- " Ensure the daemon is running and has written the token file, or set VELLUM_HTTP_TOKEN_PATH to the correct path.",
891
- );
892
- }
893
-
894
832
  const gatewayEnv: Record<string, string> = {
895
833
  ...(process.env as Record<string, string>),
896
834
  GATEWAY_RUNTIME_PROXY_ENABLED: "true",
897
835
  GATEWAY_RUNTIME_PROXY_REQUIRE_AUTH: "true",
898
- RUNTIME_PROXY_BEARER_TOKEN: runtimeProxyBearerToken,
899
836
  RUNTIME_HTTP_PORT: process.env.RUNTIME_HTTP_PORT || "7821",
900
837
  // Skip the drain window for locally-launched gateways — there is no load
901
838
  // balancer draining connections, so waiting serves no purpose and causes