@vellumai/cli 0.4.25 → 0.4.29

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/README.md CHANGED
@@ -16,13 +16,13 @@ bun run ./src/index.ts <command> [options]
16
16
 
17
17
  ### Lifecycle: `ps`, `sleep`, `wake`
18
18
 
19
- Day-to-day process management for the daemon and gateway.
19
+ Day-to-day process management for the assistant and gateway.
20
20
 
21
- | Command | Description |
22
- |---------|-------------|
23
- | `vellum ps` | List assistants and per-assistant process status (daemon, gateway PIDs and health). |
24
- | `vellum sleep` | Stop daemon and gateway processes. Directory-agnostic — works from anywhere. |
25
- | `vellum wake` | Start the daemon and gateway from the current checkout. |
21
+ | Command | Description |
22
+ | -------------- | -------------------------------------------------------------------------------------- |
23
+ | `vellum ps` | List assistants and per-assistant process status (assistant, gateway PIDs and health). |
24
+ | `vellum sleep` | Stop assistant and gateway processes. Directory-agnostic — works from anywhere. |
25
+ | `vellum wake` | Start the assistant and gateway from the current checkout. |
26
26
 
27
27
  ```bash
28
28
  # Start everything
@@ -47,36 +47,36 @@ vellum hatch [species] [options]
47
47
 
48
48
  #### Species
49
49
 
50
- | Species | Description |
51
- | ---------- | ------------------------------------------- |
50
+ | Species | Description |
51
+ | ---------- | ------------------------------------------------- |
52
52
  | `vellum` | Default. Provisions the Vellum assistant runtime. |
53
- | `openclaw` | Provisions the OpenClaw runtime with gateway. |
53
+ | `openclaw` | Provisions the OpenClaw runtime with gateway. |
54
54
 
55
55
  #### Options
56
56
 
57
- | Option | Description |
58
- | ------------------- | ----------- |
59
- | `-d` | Detached mode. Start the instance in the background without watching startup progress. |
60
- | `--name <name>` | Use a specific instance name instead of an auto-generated one. |
57
+ | Option | Description |
58
+ | ------------------- | ---------------------------------------------------------------------------------------------- |
59
+ | `-d` | Detached mode. Start the instance in the background without watching startup progress. |
60
+ | `--name <name>` | Use a specific instance name instead of an auto-generated one. |
61
61
  | `--remote <target>` | Where to provision the instance. One of: `local`, `gcp`, `aws`, `custom`. Defaults to `local`. |
62
62
 
63
63
  #### Remote Targets
64
64
 
65
- - **`local`** -- Starts the local daemon and local gateway. Gateway source resolution order is: `VELLUM_GATEWAY_DIR` override, repo source tree, then installed `@vellumai/vellum-gateway` package.
65
+ - **`local`** -- Starts the local assistant and local gateway. Gateway source resolution order is: `VELLUM_GATEWAY_DIR` override, repo source tree, then installed `@vellumai/vellum-gateway` package.
66
66
  - **`gcp`** -- Creates a GCP Compute Engine VM (`e2-standard-4`: 4 vCPUs, 16 GB) with a startup script that bootstraps the assistant. Requires `gcloud` authentication and `GCP_PROJECT` / `GCP_DEFAULT_ZONE` environment variables.
67
67
  - **`aws`** -- Provisions an AWS instance.
68
68
  - **`custom`** -- Provisions on an arbitrary SSH host. Set `VELLUM_CUSTOM_HOST` (e.g. `user@hostname`) to specify the target.
69
69
 
70
70
  #### Environment Variables
71
71
 
72
- | Variable | Required For | Description |
73
- | --------------------- | ------------ | ----------- |
74
- | `ANTHROPIC_API_KEY` | All | Anthropic API key passed to the assistant runtime. |
75
- | `GCP_PROJECT` | `gcp` | GCP project ID. Falls back to the active `gcloud` project. |
76
- | `GCP_DEFAULT_ZONE` | `gcp` | GCP zone for the compute instance. |
77
- | `VELLUM_CUSTOM_HOST` | `custom` | SSH host in `user@hostname` format. |
78
- | `VELLUM_GATEWAY_DIR` | `local` | Optional absolute path to a local gateway source directory to run instead of the packaged gateway. |
79
- | `INGRESS_PUBLIC_BASE_URL` | `local` | Optional fallback public ingress URL when `ingress.publicBaseUrl` is not set in workspace config. |
72
+ | Variable | Required For | Description |
73
+ | ------------------------- | ------------ | -------------------------------------------------------------------------------------------------- |
74
+ | `ANTHROPIC_API_KEY` | All | Anthropic API key passed to the assistant runtime. |
75
+ | `GCP_PROJECT` | `gcp` | GCP project ID. Falls back to the active `gcloud` project. |
76
+ | `GCP_DEFAULT_ZONE` | `gcp` | GCP zone for the compute instance. |
77
+ | `VELLUM_CUSTOM_HOST` | `custom` | SSH host in `user@hostname` format. |
78
+ | `VELLUM_GATEWAY_DIR` | `local` | Optional absolute path to a local gateway source directory to run instead of the packaged gateway. |
79
+ | `INGRESS_PUBLIC_BASE_URL` | `local` | Optional fallback public ingress URL when `ingress.publicBaseUrl` is not set in workspace config. |
80
80
 
81
81
  #### Examples
82
82
 
@@ -111,8 +111,8 @@ The CLI looks up the instance by name in `~/.vellum.lock.json` and determines ho
111
111
 
112
112
  - **`gcp`** -- Deletes the GCP Compute Engine instance via `gcloud compute instances delete`.
113
113
  - **`aws`** -- Terminates the AWS EC2 instance by looking up the instance ID from its Name tag.
114
- - **`local`** -- Stops the local daemon (`bunx vellum daemon stop`) and removes the `~/.vellum` directory.
115
- - **`custom`** -- SSHs to the remote host to stop the daemon/gateway and remove the `~/.vellum` directory.
114
+ - **`local`** -- Stops the local assistant (`vellum sleep`) and removes the `~/.vellum` directory.
115
+ - **`custom`** -- SSHs to the remote host to stop the assistant/gateway and remove the `~/.vellum` directory.
116
116
 
117
117
  #### Examples
118
118
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.4.25",
3
+ "version": "0.4.29",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -28,7 +28,11 @@ function writeLockfile(data: unknown): void {
28
28
  );
29
29
  }
30
30
 
31
- const makeEntry = (id: string, runtimeUrl = "http://localhost:7821", extra?: Partial<AssistantEntry>): AssistantEntry => ({
31
+ const makeEntry = (
32
+ id: string,
33
+ runtimeUrl = "http://localhost:7821",
34
+ extra?: Partial<AssistantEntry>,
35
+ ): AssistantEntry => ({
32
36
  assistantId: id,
33
37
  runtimeUrl,
34
38
  cloud: "local",
@@ -103,9 +107,15 @@ describe("assistant-config", () => {
103
107
  test("loadLatestAssistant returns most recently hatched entry", () => {
104
108
  writeLockfile({
105
109
  assistants: [
106
- makeEntry("old", "http://localhost:7821", { hatchedAt: "2024-01-01T00:00:00Z" }),
107
- makeEntry("new", "http://localhost:7822", { hatchedAt: "2025-06-15T00:00:00Z" }),
108
- makeEntry("mid", "http://localhost:7823", { hatchedAt: "2024-06-15T00:00:00Z" }),
110
+ makeEntry("old", "http://localhost:7821", {
111
+ hatchedAt: "2024-01-01T00:00:00Z",
112
+ }),
113
+ makeEntry("new", "http://localhost:7822", {
114
+ hatchedAt: "2025-06-15T00:00:00Z",
115
+ }),
116
+ makeEntry("mid", "http://localhost:7823", {
117
+ hatchedAt: "2024-06-15T00:00:00Z",
118
+ }),
109
119
  ],
110
120
  });
111
121
  const latest = loadLatestAssistant();
@@ -117,7 +127,9 @@ describe("assistant-config", () => {
117
127
  writeLockfile({
118
128
  assistants: [
119
129
  makeEntry("no-date"),
120
- makeEntry("with-date", "http://localhost:7822", { hatchedAt: "2025-01-01T00:00:00Z" }),
130
+ makeEntry("with-date", "http://localhost:7822", {
131
+ hatchedAt: "2025-01-01T00:00:00Z",
132
+ }),
121
133
  ],
122
134
  });
123
135
  const latest = loadLatestAssistant();
@@ -13,11 +13,15 @@ describe("validateAssistantName", () => {
13
13
  });
14
14
 
15
15
  test("rejects names with forward slashes", () => {
16
- expect(() => validateAssistantName("foo/bar")).toThrow("Invalid assistant name");
16
+ expect(() => validateAssistantName("foo/bar")).toThrow(
17
+ "Invalid assistant name",
18
+ );
17
19
  });
18
20
 
19
21
  test("rejects names with backslashes", () => {
20
- expect(() => validateAssistantName("foo\\bar")).toThrow("Invalid assistant name");
22
+ expect(() => validateAssistantName("foo\\bar")).toThrow(
23
+ "Invalid assistant name",
24
+ );
21
25
  });
22
26
 
23
27
  test("rejects dot-dot traversal", () => {
@@ -13,7 +13,9 @@ interface StoredMessage {
13
13
 
14
14
  const messages: Record<string, StoredMessage[]> = {};
15
15
 
16
- function parseBody(req: http.IncomingMessage): Promise<Record<string, unknown>> {
16
+ function parseBody(
17
+ req: http.IncomingMessage,
18
+ ): Promise<Record<string, unknown>> {
17
19
  return new Promise((resolve, reject) => {
18
20
  let body = "";
19
21
  req.on("data", (chunk: Buffer) => (body += chunk.toString()));
@@ -58,7 +60,8 @@ const server = http.createServer(async (req, res) => {
58
60
  child.unref();
59
61
  const responseBody = JSON.stringify({
60
62
  status: "success",
61
- message: "HTTPS adapter installed and started. HTTP adapter shutting down.",
63
+ message:
64
+ "HTTPS adapter installed and started. HTTP adapter shutting down.",
62
65
  });
63
66
  res.writeHead(200);
64
67
  res.end(responseBody, () => {
@@ -96,7 +99,10 @@ const server = http.createServer(async (req, res) => {
96
99
  };
97
100
 
98
101
  const healthStr = JSON.stringify(health);
99
- if (healthStr.includes("1006") || healthStr.includes("abnormal closure")) {
102
+ if (
103
+ healthStr.includes("1006") ||
104
+ healthStr.includes("abnormal closure")
105
+ ) {
100
106
  try {
101
107
  const gatewayOutput = execSync("openclaw gateway status", {
102
108
  encoding: "utf-8",
@@ -106,7 +112,9 @@ const server = http.createServer(async (req, res) => {
106
112
  result.message = `${result.message}\n\nGateway Status:\n${gatewayOutput.trim()}`;
107
113
  } catch (gatewayErr) {
108
114
  const gatewayErrMsg =
109
- gatewayErr instanceof Error ? gatewayErr.message : String(gatewayErr);
115
+ gatewayErr instanceof Error
116
+ ? gatewayErr.message
117
+ : String(gatewayErr);
110
118
  result.message = `${result.message}\n\nGateway Status Error:\n${gatewayErrMsg}`;
111
119
  }
112
120
  }
@@ -121,7 +129,10 @@ const server = http.createServer(async (req, res) => {
121
129
  message: errorMessage,
122
130
  };
123
131
 
124
- if (errorMessage.includes("1006") || errorMessage.includes("abnormal closure")) {
132
+ if (
133
+ errorMessage.includes("1006") ||
134
+ errorMessage.includes("abnormal closure")
135
+ ) {
125
136
  try {
126
137
  const gatewayOutput = execSync("openclaw gateway status", {
127
138
  encoding: "utf-8",
@@ -131,7 +142,9 @@ const server = http.createServer(async (req, res) => {
131
142
  result.message = `${result.message}\n\nGateway Status:\n${gatewayOutput.trim()}`;
132
143
  } catch (gatewayErr) {
133
144
  const gatewayErrMsg =
134
- gatewayErr instanceof Error ? gatewayErr.message : String(gatewayErr);
145
+ gatewayErr instanceof Error
146
+ ? gatewayErr.message
147
+ : String(gatewayErr);
135
148
  result.message = `${result.message}\n\nGateway Status Error:\n${gatewayErrMsg}`;
136
149
  }
137
150
  }
@@ -144,7 +157,9 @@ const server = http.createServer(async (req, res) => {
144
157
  return;
145
158
  }
146
159
 
147
- const messagesMatch = url.pathname.match(/^\/v1\/assistants\/([^/]+)\/messages$/);
160
+ const messagesMatch = url.pathname.match(
161
+ /^\/v1\/assistants\/([^/]+)\/messages$/,
162
+ );
148
163
  if (messagesMatch) {
149
164
  const assistantId = messagesMatch[1];
150
165
 
@@ -29,26 +29,20 @@ const DEFAULT_AUTONOMY_CONFIG: AutonomyConfig = {
29
29
  // ---------------------------------------------------------------------------
30
30
 
31
31
  function getConfigPath(): string {
32
- const root = join(
33
- process.env.BASE_DATA_DIR?.trim() || homedir(),
34
- ".vellum",
35
- );
32
+ const root = join(process.env.BASE_DATA_DIR?.trim() || homedir(), ".vellum");
36
33
  return join(root, "workspace", "autonomy.json");
37
34
  }
38
35
 
39
36
  function isValidTier(value: unknown): value is AutonomyTier {
40
37
  return (
41
- typeof value === "string" &&
42
- AUTONOMY_TIERS.includes(value as AutonomyTier)
38
+ typeof value === "string" && AUTONOMY_TIERS.includes(value as AutonomyTier)
43
39
  );
44
40
  }
45
41
 
46
42
  function validateTierRecord(raw: unknown): Record<string, AutonomyTier> {
47
43
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
48
44
  const result: Record<string, AutonomyTier> = {};
49
- for (const [key, value] of Object.entries(
50
- raw as Record<string, unknown>,
51
- )) {
45
+ for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
52
46
  if (isValidTier(value)) {
53
47
  result[key] = value;
54
48
  }
@@ -186,7 +180,9 @@ function printUsage(): void {
186
180
  console.log("Usage: vellum autonomy <subcommand> [options]");
187
181
  console.log("");
188
182
  console.log("Subcommands:");
189
- console.log(" get Show current autonomy configuration");
183
+ console.log(
184
+ " get Show current autonomy configuration",
185
+ );
190
186
  console.log(" set --default <tier> Set the global default tier");
191
187
  console.log(" set --channel <ch> --tier <t> Set tier for a channel");
192
188
  console.log(" set --category <cat> --tier <t> Set tier for a category");
@@ -254,7 +250,10 @@ export function autonomy(): void {
254
250
 
255
251
  if (!tier) {
256
252
  output(
257
- { ok: false, error: "Missing --tier. Use --tier <auto|draft|notify>." },
253
+ {
254
+ ok: false,
255
+ error: "Missing --tier. Use --tier <auto|draft|notify>.",
256
+ },
258
257
  true,
259
258
  );
260
259
  process.exitCode = 1;
@@ -1,7 +1,10 @@
1
1
  import { readFileSync } from "fs";
2
2
  import { join } from "path";
3
3
 
4
- import { findAssistantByName, loadLatestAssistant } from "../lib/assistant-config";
4
+ import {
5
+ findAssistantByName,
6
+ loadLatestAssistant,
7
+ } from "../lib/assistant-config";
5
8
  import { GATEWAY_PORT, type Species } from "../lib/constants";
6
9
 
7
10
  const ANSI = {
@@ -33,7 +36,10 @@ function parseArgs(): ParsedArgs {
33
36
  printUsage();
34
37
  process.exit(0);
35
38
  } else if (
36
- (arg === "--url" || arg === "-u" || arg === "--assistant-id" || arg === "-a") &&
39
+ (arg === "--url" ||
40
+ arg === "-u" ||
41
+ arg === "--assistant-id" ||
42
+ arg === "-a") &&
37
43
  args[i + 1]
38
44
  ) {
39
45
  flagArgs.push(arg, args[++i]);
@@ -42,20 +48,26 @@ function parseArgs(): ParsedArgs {
42
48
  }
43
49
  }
44
50
 
45
- const entry = positionalName ? findAssistantByName(positionalName) : loadLatestAssistant();
51
+ const entry = positionalName
52
+ ? findAssistantByName(positionalName)
53
+ : loadLatestAssistant();
46
54
  if (positionalName && !entry) {
47
55
  console.error(`No assistant instance found with name '${positionalName}'.`);
48
56
  process.exit(1);
49
57
  }
50
58
 
51
- let runtimeUrl = process.env.RUNTIME_URL || entry?.runtimeUrl || FALLBACK_RUNTIME_URL;
52
- let assistantId = process.env.ASSISTANT_ID || entry?.assistantId || FALLBACK_ASSISTANT_ID;
53
- let bearerToken = process.env.RUNTIME_PROXY_BEARER_TOKEN || entry?.bearerToken || undefined;
59
+ let runtimeUrl =
60
+ process.env.RUNTIME_URL || entry?.runtimeUrl || FALLBACK_RUNTIME_URL;
61
+ let assistantId =
62
+ process.env.ASSISTANT_ID || entry?.assistantId || FALLBACK_ASSISTANT_ID;
63
+ let bearerToken =
64
+ process.env.RUNTIME_PROXY_BEARER_TOKEN || entry?.bearerToken || undefined;
54
65
 
55
66
  // For local assistants, read the daemon's http-token file as a fallback
56
67
  // when the lockfile doesn't include a bearer token.
57
68
  if (!bearerToken && entry?.cloud === "local") {
58
- const tokenDir = entry.baseDataDir ?? join(process.env.HOME ?? "", ".vellum");
69
+ const tokenDir =
70
+ entry.baseDataDir ?? join(process.env.HOME ?? "", ".vellum");
59
71
  try {
60
72
  const token = readFileSync(join(tokenDir, "http-token"), "utf-8").trim();
61
73
  if (token) bearerToken = token;
@@ -69,7 +81,10 @@ function parseArgs(): ParsedArgs {
69
81
  const flag = flagArgs[i];
70
82
  if ((flag === "--url" || flag === "-u") && flagArgs[i + 1]) {
71
83
  runtimeUrl = flagArgs[++i];
72
- } else if ((flag === "--assistant-id" || flag === "-a") && flagArgs[i + 1]) {
84
+ } else if (
85
+ (flag === "--assistant-id" || flag === "-a") &&
86
+ flagArgs[i + 1]
87
+ ) {
73
88
  assistantId = flagArgs[++i];
74
89
  }
75
90
  }
@@ -111,7 +126,8 @@ ${ANSI.bold}EXAMPLES:${ANSI.reset}
111
126
  }
112
127
 
113
128
  export async function client(): Promise<void> {
114
- const { runtimeUrl, assistantId, species, bearerToken, project, zone } = parseArgs();
129
+ const { runtimeUrl, assistantId, species, bearerToken, project, zone } =
130
+ parseArgs();
115
131
 
116
132
  const { renderChatApp } = await import("../components/DefaultMainScreen");
117
133
 
@@ -63,9 +63,7 @@ function validateAllowlistFile(): AllowlistValidationError[] | null {
63
63
  if (!existsSync(filePath)) return null;
64
64
 
65
65
  const raw = readFileSync(filePath, "utf-8");
66
- const allowlistConfig: AllowlistConfig = JSON.parse(
67
- raw,
68
- ) as AllowlistConfig;
66
+ const allowlistConfig: AllowlistConfig = JSON.parse(raw) as AllowlistConfig;
69
67
  return validateAllowlist(allowlistConfig);
70
68
  }
71
69
 
@@ -79,9 +77,7 @@ function printUsage(): void {
79
77
  console.log(
80
78
  " set <key> <value> Set a config value (supports dotted paths like apiKeys.anthropic)",
81
79
  );
82
- console.log(
83
- " list List all config values",
84
- );
80
+ console.log(" list List all config values");
85
81
  console.log(
86
82
  " validate-allowlist Validate regex patterns in secret-allowlist.json",
87
83
  );
@@ -55,10 +55,7 @@ async function apiGet(path: string): Promise<unknown> {
55
55
  return response.json();
56
56
  }
57
57
 
58
- async function apiPost(
59
- path: string,
60
- body: unknown,
61
- ): Promise<unknown> {
58
+ async function apiPost(path: string, body: unknown): Promise<unknown> {
62
59
  const url = `${getGatewayUrl()}/v1/${path}`;
63
60
  const response = await fetch(url, {
64
61
  method: "POST",