@vellumai/cli 0.5.16 → 0.6.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.
@@ -6,7 +6,6 @@ import { getBackupsDir, formatSize } from "../lib/backup-ops.js";
6
6
  import { loadGuardianToken, leaseGuardianToken } from "../lib/guardian-token";
7
7
  import {
8
8
  readPlatformToken,
9
- fetchOrganizationId,
10
9
  platformInitiateExport,
11
10
  platformPollExportStatus,
12
11
  platformDownloadExport,
@@ -196,24 +195,11 @@ async function backupPlatform(
196
195
  process.exit(1);
197
196
  }
198
197
 
199
- let orgId: string;
200
- try {
201
- orgId = await fetchOrganizationId(token, runtimeUrl);
202
- } catch (err) {
203
- const msg = err instanceof Error ? err.message : String(err);
204
- if (msg.includes("401") || msg.includes("403")) {
205
- console.error("Authentication failed. Run 'vellum login' to refresh.");
206
- process.exit(1);
207
- }
208
- throw err;
209
- }
210
-
211
198
  // Step 2 — Initiate export job
212
199
  let jobId: string;
213
200
  try {
214
201
  const result = await platformInitiateExport(
215
202
  token,
216
- orgId,
217
203
  "CLI backup",
218
204
  runtimeUrl,
219
205
  );
@@ -244,7 +230,7 @@ async function backupPlatform(
244
230
  while (Date.now() < deadline) {
245
231
  let status: { status: string; downloadUrl?: string; error?: string };
246
232
  try {
247
- status = await platformPollExportStatus(jobId, token, orgId, runtimeUrl);
233
+ status = await platformPollExportStatus(jobId, token, runtimeUrl);
248
234
  } catch (err) {
249
235
  const msg = err instanceof Error ? err.message : String(err);
250
236
  // Let non-transient errors (e.g. 404 "job not found") propagate immediately
@@ -0,0 +1,146 @@
1
+ /**
2
+ * `vellum events [assistant]`
3
+ *
4
+ * Subscribe to assistant events via the SSE endpoint and stream them
5
+ * to stdout. By default, events are rendered as human-readable
6
+ * markdown. Pass `--json` to emit one JSON object per event,
7
+ * separated by newlines.
8
+ */
9
+
10
+ import { extractFlag } from "../lib/arg-utils.js";
11
+ import { AssistantClient } from "../lib/assistant-client.js";
12
+
13
+ function printUsage(): void {
14
+ console.log(`vellum events - Stream events from a running assistant
15
+
16
+ USAGE:
17
+ vellum events [assistant] [options]
18
+
19
+ ARGUMENTS:
20
+ [assistant] Instance name (default: active assistant)
21
+
22
+ OPTIONS:
23
+ --conversation-key <key> Scope to a single conversation
24
+ --json Output raw JSON events (one per line)
25
+ -h, --help Show this help message
26
+
27
+ EXAMPLES:
28
+ vellum events
29
+ vellum events my-assistant
30
+ vellum events --json
31
+ vellum events --conversation-key my-thread
32
+ `);
33
+ }
34
+
35
+ interface AssistantEvent {
36
+ id: string;
37
+ assistantId: string;
38
+ conversationId?: string;
39
+ emittedAt: string;
40
+ message: {
41
+ type: string;
42
+ text?: string;
43
+ thinking?: string;
44
+ toolName?: string;
45
+ input?: Record<string, unknown>;
46
+ result?: string;
47
+ isError?: boolean;
48
+ content?: string;
49
+ message?: string;
50
+ chunk?: string;
51
+ conversationId?: string;
52
+ [key: string]: unknown;
53
+ };
54
+ }
55
+
56
+ /** Render an event as human-readable markdown to stdout. */
57
+ function renderMarkdown(event: AssistantEvent): void {
58
+ const msg = event.message;
59
+ switch (msg.type) {
60
+ case "assistant_text_delta":
61
+ process.stdout.write(msg.text ?? "");
62
+ break;
63
+ case "assistant_thinking_delta":
64
+ process.stdout.write(msg.thinking ?? "");
65
+ break;
66
+ case "tool_use_start":
67
+ console.log(`\n> **Tool call:** \`${msg.toolName}\``);
68
+ if (msg.input && Object.keys(msg.input).length > 0) {
69
+ console.log("```json");
70
+ console.log(JSON.stringify(msg.input, null, 2));
71
+ console.log("```");
72
+ }
73
+ break;
74
+ case "tool_input_delta":
75
+ process.stdout.write(msg.content ?? "");
76
+ break;
77
+ case "tool_result":
78
+ if (msg.isError) {
79
+ console.log(`\n> **Tool error** (\`${msg.toolName}\`): ${msg.result}`);
80
+ } else {
81
+ console.log(`\n> **Tool result** (\`${msg.toolName}\`): ${msg.result}`);
82
+ }
83
+ break;
84
+ case "tool_output_chunk":
85
+ process.stdout.write(msg.chunk ?? "");
86
+ break;
87
+ case "message_complete":
88
+ console.log("\n");
89
+ break;
90
+ case "error":
91
+ console.error(`\n**Error:** ${msg.message}`);
92
+ break;
93
+ case "user_message_echo":
94
+ console.log(`\n**You:** ${msg.text}`);
95
+ break;
96
+ default:
97
+ // Silently skip events that don't have a markdown representation
98
+ // (e.g. heartbeat comments, activity states, etc.)
99
+ break;
100
+ }
101
+ }
102
+
103
+ export async function events(): Promise<void> {
104
+ const rawArgs = process.argv.slice(3);
105
+
106
+ if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
107
+ printUsage();
108
+ return;
109
+ }
110
+
111
+ const jsonOutput = rawArgs.includes("--json");
112
+ let args = rawArgs.filter((a) => a !== "--json");
113
+
114
+ const [conversationKey, filteredArgs] = extractFlag(
115
+ args,
116
+ "--conversation-key",
117
+ );
118
+ args = filteredArgs;
119
+
120
+ const assistantId = args[0];
121
+
122
+ const client = new AssistantClient({ assistantId });
123
+
124
+ // Use an explicit AbortController so we can clean up on SIGINT
125
+ const controller = new AbortController();
126
+ process.on("SIGINT", () => {
127
+ controller.abort();
128
+ process.exit(0);
129
+ });
130
+
131
+ const query: Record<string, string> = {};
132
+ if (conversationKey) {
133
+ query.conversationKey = conversationKey;
134
+ }
135
+
136
+ for await (const event of client.stream<AssistantEvent>("/events", {
137
+ signal: controller.signal,
138
+ query,
139
+ })) {
140
+ if (jsonOutput) {
141
+ console.log(JSON.stringify(event));
142
+ } else {
143
+ renderMarkdown(event);
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * `vellum message <assistant> <message>`
3
+ *
4
+ * Send a message to a running assistant via its runtime HTTP API and
5
+ * print the result. This is a fire-and-send command — it does NOT
6
+ * subscribe to SSE events (use `vellum events` for that).
7
+ */
8
+
9
+ import { extractFlag } from "../lib/arg-utils.js";
10
+ import { AssistantClient } from "../lib/assistant-client.js";
11
+
12
+ function printUsage(): void {
13
+ console.log(`vellum message - Send a message to a running assistant
14
+
15
+ USAGE:
16
+ vellum message [assistant] <message>
17
+
18
+ ARGUMENTS:
19
+ [assistant] Instance name (default: active assistant)
20
+ <message> Message content to send
21
+
22
+ OPTIONS:
23
+ --conversation-key <key> Conversation key (default: stable key per channel/interface)
24
+ --json Output raw JSON response
25
+
26
+ EXAMPLES:
27
+ vellum message "hello"
28
+ vellum message my-assistant "ping"
29
+ vellum message --conversation-key my-thread "hello"
30
+ vellum message --json "hello"
31
+ `);
32
+ }
33
+
34
+ export async function message(): Promise<void> {
35
+ const rawArgs = process.argv.slice(3);
36
+
37
+ if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
38
+ printUsage();
39
+ return;
40
+ }
41
+
42
+ const jsonOutput = rawArgs.includes("--json");
43
+ let args = rawArgs.filter((a) => a !== "--json");
44
+
45
+ const [conversationKey, filteredArgs] = extractFlag(
46
+ args,
47
+ "--conversation-key",
48
+ );
49
+ args = filteredArgs;
50
+
51
+ let assistantId: string | undefined;
52
+ let messageContent: string | undefined;
53
+
54
+ if (args.length >= 2) {
55
+ // vellum message <assistant> <message>
56
+ assistantId = args[0];
57
+ messageContent = args[1];
58
+ } else if (args.length === 1) {
59
+ // vellum message <message> (uses active/latest assistant)
60
+ messageContent = args[0];
61
+ }
62
+
63
+ if (!messageContent) {
64
+ console.error("Error: message content is required.");
65
+ console.error("");
66
+ printUsage();
67
+ process.exit(1);
68
+ }
69
+
70
+ const client = new AssistantClient({ assistantId });
71
+
72
+ const payload: Record<string, string> = {
73
+ content: messageContent,
74
+ sourceChannel: "vellum",
75
+ interface: "cli",
76
+ };
77
+ if (conversationKey) {
78
+ payload.conversationKey = conversationKey;
79
+ }
80
+
81
+ const response = await client.post("/messages/", payload);
82
+
83
+ if (!response.ok) {
84
+ const body = await response.text().catch(() => "");
85
+ console.error(
86
+ `Error: HTTP ${response.status}: ${body || response.statusText}`,
87
+ );
88
+ process.exit(1);
89
+ }
90
+
91
+ const result = (await response.json()) as {
92
+ accepted: boolean;
93
+ messageId: string;
94
+ };
95
+
96
+ if (jsonOutput) {
97
+ console.log(JSON.stringify(result, null, 2));
98
+ } else {
99
+ if (result.accepted) {
100
+ console.log(`Message accepted (id: ${result.messageId})`);
101
+ } else {
102
+ console.log(`Message rejected (id: ${result.messageId})`);
103
+ }
104
+ }
105
+ }
@@ -8,7 +8,6 @@ import {
8
8
  } from "../lib/guardian-token.js";
9
9
  import {
10
10
  readPlatformToken,
11
- fetchOrganizationId,
12
11
  rollbackPlatformAssistant,
13
12
  platformImportPreflight,
14
13
  platformImportBundle,
@@ -177,18 +176,6 @@ async function restorePlatform(
177
176
  process.exit(1);
178
177
  }
179
178
 
180
- let orgId: string;
181
- try {
182
- orgId = await fetchOrganizationId(token, entry.runtimeUrl);
183
- } catch (err) {
184
- const msg = err instanceof Error ? err.message : String(err);
185
- if (msg.includes("401") || msg.includes("403")) {
186
- console.error("Authentication failed. Run 'vellum login' to refresh.");
187
- process.exit(1);
188
- }
189
- throw err;
190
- }
191
-
192
179
  // Step 2 — Dry-run path
193
180
  if (opts.dryRun) {
194
181
  if (opts.version) {
@@ -205,7 +192,6 @@ async function restorePlatform(
205
192
  preflightResult = await platformImportPreflight(
206
193
  new Uint8Array(bundleData),
207
194
  token,
208
- orgId,
209
195
  entry.runtimeUrl,
210
196
  );
211
197
  } catch (err) {
@@ -316,12 +302,7 @@ async function restorePlatform(
316
302
  );
317
303
 
318
304
  try {
319
- await rollbackPlatformAssistant(
320
- token,
321
- orgId,
322
- opts.version,
323
- entry.runtimeUrl,
324
- );
305
+ await rollbackPlatformAssistant(token, opts.version, entry.runtimeUrl);
325
306
  } catch (err) {
326
307
  const msg = err instanceof Error ? err.message : String(err);
327
308
  if (msg.includes("401") || msg.includes("403")) {
@@ -345,7 +326,6 @@ async function restorePlatform(
345
326
  importResult = await platformImportBundle(
346
327
  new Uint8Array(bundleData),
347
328
  token,
348
- orgId,
349
329
  entry.runtimeUrl,
350
330
  );
351
331
  } catch (err) {
@@ -4,7 +4,7 @@ import {
4
4
  } from "../lib/assistant-config";
5
5
  import type { AssistantEntry } from "../lib/assistant-config";
6
6
  import {
7
- fetchOrganizationId,
7
+ authHeaders,
8
8
  getPlatformUrl,
9
9
  readPlatformToken,
10
10
  } from "../lib/platform-client";
@@ -93,16 +93,11 @@ async function retireVellum(
93
93
  process.exit(1);
94
94
  }
95
95
 
96
- const orgId = await fetchOrganizationId(token, runtimeUrl);
97
-
98
96
  const platformUrl = runtimeUrl || getPlatformUrl();
99
97
  const url = `${platformUrl}/v1/assistants/${encodeURIComponent(assistantId)}/retire/`;
100
98
  const response = await fetch(url, {
101
99
  method: "DELETE",
102
- headers: {
103
- "X-Session-Token": token,
104
- "Vellum-Organization-Id": orgId,
105
- },
100
+ headers: await authHeaders(token, runtimeUrl),
106
101
  });
107
102
 
108
103
  if (!response.ok) {
@@ -17,7 +17,6 @@ import {
17
17
  import type { ServiceName } from "../lib/docker";
18
18
  import { emitCliError, categorizeUpgradeError } from "../lib/cli-error.js";
19
19
  import {
20
- fetchOrganizationId,
21
20
  readPlatformToken,
22
21
  rollbackPlatformAssistant,
23
22
  } from "../lib/platform-client.js";
@@ -35,7 +34,7 @@ import {
35
34
  UPGRADE_PROGRESS,
36
35
  waitForReady,
37
36
  } from "../lib/upgrade-lifecycle.js";
38
- import { parseVersion } from "../lib/version-compat.js";
37
+ import { compareVersions } from "../lib/version-compat.js";
39
38
 
40
39
  function parseArgs(): { name: string | null; version: string | null } {
41
40
  const args = process.argv.slice(3);
@@ -153,21 +152,12 @@ async function rollbackPlatformViaEndpoint(
153
152
 
154
153
  // Step 1 — Version validation (only if version provided)
155
154
  if (version && currentVersion) {
156
- const current = parseVersion(currentVersion);
157
- const target = parseVersion(version);
158
- if (current && target) {
159
- const isOlder =
160
- target.major < current.major ||
161
- (target.major === current.major && target.minor < current.minor) ||
162
- (target.major === current.major &&
163
- target.minor === current.minor &&
164
- target.patch < current.patch);
165
- if (!isOlder) {
166
- const msg = `Target version ${version} is not older than the current version ${currentVersion}. Use \`vellum upgrade --version ${version}\` to upgrade.`;
167
- console.error(msg);
168
- emitCliError("VERSION_DIRECTION", msg);
169
- process.exit(1);
170
- }
155
+ const cmp = compareVersions(version, currentVersion);
156
+ if (cmp !== null && cmp >= 0) {
157
+ const msg = `Target version ${version} is not older than the current version ${currentVersion}. Use \`vellum upgrade --version ${version}\` to upgrade.`;
158
+ console.error(msg);
159
+ emitCliError("VERSION_DIRECTION", msg);
160
+ process.exit(1);
171
161
  }
172
162
  }
173
163
 
@@ -181,20 +171,6 @@ async function rollbackPlatformViaEndpoint(
181
171
  process.exit(1);
182
172
  }
183
173
 
184
- let orgId: string;
185
- try {
186
- orgId = await fetchOrganizationId(token, entry.runtimeUrl);
187
- } catch (err) {
188
- const msg = err instanceof Error ? err.message : String(err);
189
- if (msg.includes("401") || msg.includes("403")) {
190
- console.error("Authentication failed. Run 'vellum login' to refresh.");
191
- } else {
192
- console.error(`Error: ${msg}`);
193
- }
194
- emitCliError("AUTH_FAILED", "Failed to authenticate with platform", msg);
195
- process.exit(1);
196
- }
197
-
198
174
  // Step 3 — Call rollback endpoint
199
175
  if (version) {
200
176
  console.log(`Rolling back to ${version}...`);
@@ -204,12 +180,7 @@ async function rollbackPlatformViaEndpoint(
204
180
 
205
181
  let result: { detail: string; version: string | null };
206
182
  try {
207
- result = await rollbackPlatformAssistant(
208
- token,
209
- orgId,
210
- version,
211
- entry.runtimeUrl,
212
- );
183
+ result = await rollbackPlatformAssistant(token, version, entry.runtimeUrl);
213
184
  } catch (err) {
214
185
  const detail = err instanceof Error ? err.message : String(err);
215
186
 
@@ -352,6 +323,11 @@ export async function rollback(): Promise<void> {
352
323
  ` Captured ${Object.keys(capturedEnv).length} env var(s) from ${res.assistantContainer}\n`,
353
324
  );
354
325
 
326
+ // Capture GUARDIAN_BOOTSTRAP_SECRET from the gateway container (it is only
327
+ // set on gateway, not assistant) so it persists across container restarts.
328
+ const gatewayEnv = await captureContainerEnv(res.gatewayContainer);
329
+ const bootstrapSecret = gatewayEnv["GUARDIAN_BOOTSTRAP_SECRET"];
330
+
355
331
  // Extract CES_SERVICE_TOKEN from captured env, or generate fresh one
356
332
  const cesServiceToken =
357
333
  capturedEnv["CES_SERVICE_TOKEN"] || randomBytes(32).toString("hex");
@@ -430,6 +406,7 @@ export async function rollback(): Promise<void> {
430
406
  await startContainers(
431
407
  {
432
408
  signingKey,
409
+ bootstrapSecret,
433
410
  cesServiceToken,
434
411
  extraAssistantEnv,
435
412
  gatewayPort,