@vellumai/cli 0.4.53 → 0.4.54

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.53",
3
+ "version": "0.4.54",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -40,6 +40,7 @@ import {
40
40
  } from "../lib/constants";
41
41
  import type { RemoteHost, Species } from "../lib/constants";
42
42
  import { hatchDocker } from "../lib/docker";
43
+ import { mintLocalBearerToken } from "../lib/jwt";
43
44
  import { hatchGcp } from "../lib/gcp";
44
45
  import type { PollResult, WatchHatchingResult } from "../lib/gcp";
45
46
  import {
@@ -794,17 +795,9 @@ async function hatchLocal(
794
795
  delete process.env.BASE_DATA_DIR;
795
796
  }
796
797
 
797
- // Read the bearer token (JWT) written by the daemon so the CLI can
798
- // with the gateway (which requires auth by default). The daemon writes under
799
- // getRootDir() which resolves to <instanceDir>/.vellum/.
800
- let bearerToken: string | undefined;
801
- try {
802
- const tokenPath = join(resources.instanceDir, ".vellum", "http-token");
803
- const token = readFileSync(tokenPath, "utf-8").trim();
804
- if (token) bearerToken = token;
805
- } catch {
806
- // Token file may not exist if daemon started without HTTP server
807
- }
798
+ // Mint a JWT from the signing key so the CLI can authenticate with the
799
+ // daemon/gateway (which requires auth by default).
800
+ const bearerToken = mintLocalBearerToken(resources.instanceDir);
808
801
 
809
802
  const localEntry: AssistantEntry = {
810
803
  assistantId: instanceName,
package/src/lib/jwt.ts ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Minimal JWT minting for the external CLI.
3
+ *
4
+ * Loads the shared HMAC signing key from disk and mints short-lived JWTs
5
+ * so the CLI can authenticate with the daemon's HTTP server without reading
6
+ * the deprecated http-token file.
7
+ */
8
+
9
+ import { createHmac, randomBytes } from "crypto";
10
+ import { readFileSync } from "fs";
11
+ import { join } from "path";
12
+
13
+ import { CURRENT_POLICY_EPOCH } from "./policy.js";
14
+
15
+ function base64urlEncode(data: Buffer | string): string {
16
+ const buf = typeof data === "string" ? Buffer.from(data, "utf-8") : data;
17
+ return buf.toString("base64url");
18
+ }
19
+
20
+ const JWT_HEADER = base64urlEncode(
21
+ JSON.stringify({ alg: "HS256", typ: "JWT" }),
22
+ );
23
+
24
+ /**
25
+ * Mint a short-lived JWT bearer token for the given instance directory.
26
+ *
27
+ * Reads the signing key from `<instanceDir>/.vellum/protected/actor-token-signing-key`
28
+ * and mints a 30-day JWT with `aud=vellum-gateway`.
29
+ *
30
+ * Returns undefined if the signing key doesn't exist yet (daemon not started).
31
+ */
32
+ export function mintLocalBearerToken(instanceDir: string): string | undefined {
33
+ try {
34
+ const keyPath = join(
35
+ instanceDir,
36
+ ".vellum",
37
+ "protected",
38
+ "actor-token-signing-key",
39
+ );
40
+ const key = readFileSync(keyPath);
41
+ if (key.length !== 32) return undefined;
42
+
43
+ const now = Math.floor(Date.now() / 1000);
44
+ const claims = {
45
+ iss: "vellum-auth",
46
+ aud: "vellum-gateway",
47
+ sub: "local:cli:cli",
48
+ scope_profile: "actor_client_v1",
49
+ exp: now + 30 * 24 * 60 * 60,
50
+ policy_epoch: CURRENT_POLICY_EPOCH,
51
+ iat: now,
52
+ jti: randomBytes(16).toString("hex"),
53
+ };
54
+
55
+ const payload = base64urlEncode(JSON.stringify(claims));
56
+ const sigInput = JWT_HEADER + "." + payload;
57
+ const sig = createHmac("sha256", key).update(sigInput).digest();
58
+ return sigInput + "." + base64urlEncode(sig);
59
+ } catch {
60
+ return undefined;
61
+ }
62
+ }
package/src/lib/local.ts CHANGED
@@ -803,12 +803,17 @@ export async function startLocalDaemon(
803
803
  // macOS app the CLI inherits a huge environment (XPC_SERVICE_NAME,
804
804
  // __CFBundleIdentifier, CLAUDE_CODE_ENTRYPOINT, etc.) that can cause
805
805
  // the daemon to take 50+ seconds to start instead of ~1s.
806
- const bunBinDir = join(homedir(), ".bun", "bin");
806
+ const home = homedir();
807
+ const bunBinDir = join(home, ".bun", "bin");
808
+ const localBinDir = join(home, ".local", "bin");
807
809
  const basePath =
808
810
  process.env.PATH || "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
811
+ const extraDirs = [bunBinDir, localBinDir].filter(
812
+ (d) => !basePath.split(":").includes(d),
813
+ );
809
814
  const daemonEnv: Record<string, string> = {
810
- HOME: process.env.HOME || homedir(),
811
- PATH: `${bunBinDir}:${basePath}`,
815
+ HOME: process.env.HOME || home,
816
+ PATH: [...extraDirs, basePath].filter(Boolean).join(":"),
812
817
  };
813
818
  // Forward optional config env vars the daemon may need
814
819
  for (const key of [
@@ -11,9 +11,12 @@ import { join, dirname } from "path";
11
11
 
12
12
  const DEFAULT_PLATFORM_URL = "https://platform.vellum.ai";
13
13
 
14
+ function getXdgConfigHome(): string {
15
+ return process.env.XDG_CONFIG_HOME?.trim() || join(homedir(), ".config");
16
+ }
17
+
14
18
  function getPlatformTokenPath(): string {
15
- const base = process.env.BASE_DATA_DIR || homedir();
16
- return join(base, ".vellum", "platform-token");
19
+ return join(getXdgConfigHome(), "vellum", "platform-token");
17
20
  }
18
21
 
19
22
  export function getPlatformUrl(): string {
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Policy epoch constant for the external CLI.
3
+ *
4
+ * Must stay in sync with assistant/src/runtime/auth/policy.ts.
5
+ * When the daemon bumps CURRENT_POLICY_EPOCH, update this value too.
6
+ */
7
+ export const CURRENT_POLICY_EPOCH = 1;