@vellumai/cli 0.4.42 → 0.4.44

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.
@@ -1,10 +1,7 @@
1
1
  import { existsSync, readFileSync } from "fs";
2
2
  import { join } from "path";
3
3
 
4
- import {
5
- defaultLocalResources,
6
- resolveTargetAssistant,
7
- } from "../lib/assistant-config.js";
4
+ import { resolveTargetAssistant } from "../lib/assistant-config.js";
8
5
  import { isProcessAlive, stopProcessByPidFile } from "../lib/process";
9
6
  import { startLocalDaemon, startGateway } from "../lib/local";
10
7
 
@@ -38,10 +35,15 @@ export async function wake(): Promise<void> {
38
35
  process.exit(1);
39
36
  }
40
37
 
41
- const resources = entry.resources ?? defaultLocalResources();
38
+ if (!entry.resources) {
39
+ console.error(
40
+ `Error: Local assistant '${entry.assistantId}' is missing resource configuration. Re-hatch to fix.`,
41
+ );
42
+ process.exit(1);
43
+ }
44
+ const resources = entry.resources;
42
45
 
43
46
  const pidFile = resources.pidFile;
44
- const socketFile = resources.socketPath;
45
47
 
46
48
  // Check if daemon is already running
47
49
  let daemonRunning = false;
@@ -57,7 +59,7 @@ export async function wake(): Promise<void> {
57
59
  console.log(
58
60
  `Assistant running (pid ${pid}) — restarting in watch mode...`,
59
61
  );
60
- await stopProcessByPidFile(pidFile, "assistant", [socketFile]);
62
+ await stopProcessByPidFile(pidFile, "assistant");
61
63
  daemonRunning = false;
62
64
  } else {
63
65
  console.log(`Assistant already running (pid ${pid}).`);
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "child_process";
2
2
  import { createHash, randomBytes, randomUUID } from "crypto";
3
- import { hostname, platform, userInfo } from "os";
3
+ import { hostname, userInfo } from "os";
4
4
  import { basename } from "path";
5
5
  import qrcode from "qrcode-terminal";
6
6
  import {
@@ -145,38 +145,46 @@ interface PendingInteractionsResponse {
145
145
  pendingSecret: (PendingSecret & { requestId?: string }) | null;
146
146
  }
147
147
 
148
- type TrustDecision = "always_allow" | "always_allow_high_risk" | "always_deny";
148
+ type TrustDecision = "always_allow" | "always_deny";
149
149
 
150
150
  interface HealthResponse {
151
151
  status: string;
152
152
  message?: string;
153
153
  }
154
154
 
155
+ /** Extract human-readable message from a daemon JSON error response. */
156
+ function friendlyErrorMessage(status: number, body: string): string {
157
+ try {
158
+ const parsed = JSON.parse(body) as { error?: { message?: string } };
159
+ if (parsed?.error?.message) {
160
+ return parsed.error.message;
161
+ }
162
+ } catch {
163
+ // Not JSON — fall through
164
+ }
165
+ return `HTTP ${status}: ${body || "Unknown error"}`;
166
+ }
167
+
155
168
  async function runtimeRequest<T>(
156
169
  baseUrl: string,
157
170
  assistantId: string,
158
171
  path: string,
159
172
  init?: RequestInit,
160
173
  bearerToken?: string,
161
- accessToken?: string,
162
174
  ): Promise<T> {
163
175
  const url = `${baseUrl}/v1/assistants/${assistantId}${path}`;
164
- // Prefer the JWT access token (from bootstrap) over the shared-secret
165
- // bearer token. The JWT carries identity claims and is the canonical
166
- // auth mechanism in the single-header auth model.
167
- const authToken = accessToken ?? bearerToken;
168
176
  const response = await fetch(url, {
169
177
  ...init,
170
178
  headers: {
171
179
  "Content-Type": "application/json",
172
- ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
180
+ ...(bearerToken ? { Authorization: `Bearer ${bearerToken}` } : {}),
173
181
  ...(init?.headers as Record<string, string> | undefined),
174
182
  },
175
183
  });
176
184
 
177
185
  if (!response.ok) {
178
186
  const body = await response.text().catch(() => "");
179
- throw new Error(`HTTP ${response.status}: ${body || response.statusText}`);
187
+ throw new Error(friendlyErrorMessage(response.status, body));
180
188
  }
181
189
 
182
190
  if (response.status === 204) {
@@ -205,50 +213,10 @@ async function checkHealthRuntime(baseUrl: string): Promise<HealthResponse> {
205
213
  return response.json() as Promise<HealthResponse>;
206
214
  }
207
215
 
208
- async function bootstrapAccessToken(
209
- baseUrl: string,
210
- bearerToken?: string,
211
- ): Promise<string> {
212
- if (!bearerToken) {
213
- throw new Error("Missing bearer token; cannot bootstrap actor identity");
214
- }
215
-
216
- const deviceId = `vellum-cli:${platform()}:${hostname()}:${userInfo().username}`;
217
- const url = `${baseUrl}/v1/integrations/guardian/vellum/bootstrap`;
218
-
219
- const response = await fetch(url, {
220
- method: "POST",
221
- headers: {
222
- "Content-Type": "application/json",
223
- Authorization: `Bearer ${bearerToken}`,
224
- },
225
- body: JSON.stringify({ platform: "cli", deviceId }),
226
- });
227
-
228
- if (!response.ok) {
229
- const body = await response.text().catch(() => "");
230
- throw new Error(`HTTP ${response.status}: ${body || response.statusText}`);
231
- }
232
-
233
- const json: unknown = await response.json();
234
-
235
- if (typeof json !== "object" || json === null) {
236
- throw new Error("Invalid bootstrap response from gateway/runtime");
237
- }
238
-
239
- const accessToken = (json as Record<string, unknown>).accessToken;
240
- if (typeof accessToken !== "string" || accessToken.length === 0) {
241
- throw new Error("Invalid bootstrap response from gateway/runtime");
242
- }
243
-
244
- return accessToken;
245
- }
246
-
247
216
  async function pollMessages(
248
217
  baseUrl: string,
249
218
  assistantId: string,
250
219
  bearerToken?: string,
251
- accessToken?: string,
252
220
  ): Promise<ListMessagesResponse> {
253
221
  const params = new URLSearchParams({ conversationKey: assistantId });
254
222
  return runtimeRequest<ListMessagesResponse>(
@@ -257,7 +225,6 @@ async function pollMessages(
257
225
  `/messages?${params.toString()}`,
258
226
  undefined,
259
227
  bearerToken,
260
- accessToken,
261
228
  );
262
229
  }
263
230
 
@@ -267,7 +234,6 @@ async function sendMessage(
267
234
  content: string,
268
235
  signal?: AbortSignal,
269
236
  bearerToken?: string,
270
- accessToken?: string,
271
237
  ): Promise<SendMessageResponse> {
272
238
  return runtimeRequest<SendMessageResponse>(
273
239
  baseUrl,
@@ -284,7 +250,6 @@ async function sendMessage(
284
250
  signal,
285
251
  },
286
252
  bearerToken,
287
- accessToken,
288
253
  );
289
254
  }
290
255
 
@@ -294,7 +259,6 @@ async function submitDecision(
294
259
  requestId: string,
295
260
  decision: "allow" | "deny",
296
261
  bearerToken?: string,
297
- accessToken?: string,
298
262
  ): Promise<SubmitDecisionResponse> {
299
263
  return runtimeRequest<SubmitDecisionResponse>(
300
264
  baseUrl,
@@ -305,7 +269,6 @@ async function submitDecision(
305
269
  body: JSON.stringify({ requestId, decision }),
306
270
  },
307
271
  bearerToken,
308
- accessToken,
309
272
  );
310
273
  }
311
274
 
@@ -317,7 +280,6 @@ async function addTrustRule(
317
280
  scope: string,
318
281
  decision: "allow" | "deny",
319
282
  bearerToken?: string,
320
- accessToken?: string,
321
283
  ): Promise<AddTrustRuleResponse> {
322
284
  return runtimeRequest<AddTrustRuleResponse>(
323
285
  baseUrl,
@@ -328,7 +290,6 @@ async function addTrustRule(
328
290
  body: JSON.stringify({ requestId, pattern, scope, decision }),
329
291
  },
330
292
  bearerToken,
331
- accessToken,
332
293
  );
333
294
  }
334
295
 
@@ -336,7 +297,6 @@ async function pollPendingInteractions(
336
297
  baseUrl: string,
337
298
  assistantId: string,
338
299
  bearerToken?: string,
339
- accessToken?: string,
340
300
  ): Promise<PendingInteractionsResponse> {
341
301
  const params = new URLSearchParams({ conversationKey: assistantId });
342
302
  return runtimeRequest<PendingInteractionsResponse>(
@@ -345,7 +305,6 @@ async function pollPendingInteractions(
345
305
  `/pending-interactions?${params.toString()}`,
346
306
  undefined,
347
307
  bearerToken,
348
- accessToken,
349
308
  );
350
309
  }
351
310
 
@@ -388,7 +347,6 @@ async function handleConfirmationPrompt(
388
347
  confirmation: PendingConfirmation,
389
348
  chatApp: ChatAppHandle,
390
349
  bearerToken?: string,
391
- accessToken?: string,
392
350
  ): Promise<void> {
393
351
  const preview = formatConfirmationPreview(
394
352
  confirmation.toolName,
@@ -420,7 +378,6 @@ async function handleConfirmationPrompt(
420
378
  requestId,
421
379
  "allow",
422
380
  bearerToken,
423
- accessToken,
424
381
  );
425
382
  chatApp.addStatus("\u2714 Allowed", "green");
426
383
  return;
@@ -434,7 +391,6 @@ async function handleConfirmationPrompt(
434
391
  chatApp,
435
392
  "always_allow",
436
393
  bearerToken,
437
- accessToken,
438
394
  );
439
395
  return;
440
396
  }
@@ -447,7 +403,6 @@ async function handleConfirmationPrompt(
447
403
  chatApp,
448
404
  "always_deny",
449
405
  bearerToken,
450
- accessToken,
451
406
  );
452
407
  return;
453
408
  }
@@ -458,7 +413,6 @@ async function handleConfirmationPrompt(
458
413
  requestId,
459
414
  "deny",
460
415
  bearerToken,
461
- accessToken,
462
416
  );
463
417
  chatApp.addStatus("\u2718 Denied", "yellow");
464
418
  }
@@ -471,7 +425,6 @@ async function handlePatternSelection(
471
425
  chatApp: ChatAppHandle,
472
426
  trustDecision: TrustDecision,
473
427
  bearerToken?: string,
474
- accessToken?: string,
475
428
  ): Promise<void> {
476
429
  const allowlistOptions = confirmation.allowlistOptions ?? [];
477
430
  const label = trustDecision === "always_deny" ? "Denylist" : "Allowlist";
@@ -493,7 +446,6 @@ async function handlePatternSelection(
493
446
  selectedPattern,
494
447
  trustDecision,
495
448
  bearerToken,
496
- accessToken,
497
449
  );
498
450
  return;
499
451
  }
@@ -504,7 +456,6 @@ async function handlePatternSelection(
504
456
  requestId,
505
457
  "deny",
506
458
  bearerToken,
507
- accessToken,
508
459
  );
509
460
  chatApp.addStatus("\u2718 Denied", "yellow");
510
461
  }
@@ -518,7 +469,6 @@ async function handleScopeSelection(
518
469
  selectedPattern: string,
519
470
  trustDecision: TrustDecision,
520
471
  bearerToken?: string,
521
- accessToken?: string,
522
472
  ): Promise<void> {
523
473
  const scopeOptions = confirmation.scopeOptions ?? [];
524
474
  const label = trustDecision === "always_deny" ? "Denylist" : "Allowlist";
@@ -536,7 +486,6 @@ async function handleScopeSelection(
536
486
  scopeOptions[index].scope,
537
487
  ruleDecision,
538
488
  bearerToken,
539
- accessToken,
540
489
  );
541
490
  await submitDecision(
542
491
  baseUrl,
@@ -544,7 +493,6 @@ async function handleScopeSelection(
544
493
  requestId,
545
494
  ruleDecision === "deny" ? "deny" : "allow",
546
495
  bearerToken,
547
- accessToken,
548
496
  );
549
497
  const ruleLabel =
550
498
  trustDecision === "always_deny" ? "Denylisted" : "Allowlisted";
@@ -562,7 +510,6 @@ async function handleScopeSelection(
562
510
  requestId,
563
511
  "deny",
564
512
  bearerToken,
565
- accessToken,
566
513
  );
567
514
  chatApp.addStatus("\u2718 Denied", "yellow");
568
515
  }
@@ -1220,7 +1167,6 @@ function ChatApp({
1220
1167
  const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
1221
1168
  const doctorSessionIdRef = useRef(randomUUID());
1222
1169
  const handleRef_ = useRef<ChatAppHandle | null>(null);
1223
- const accessTokenRef = useRef<string | undefined>(undefined);
1224
1170
 
1225
1171
  const { stdout } = useStdout();
1226
1172
  const terminalRows = stdout.rows || DEFAULT_TERMINAL_ROWS;
@@ -1458,12 +1404,6 @@ function ChatApp({
1458
1404
 
1459
1405
  try {
1460
1406
  const health = await checkHealthRuntime(runtimeUrl);
1461
- if (!accessTokenRef.current) {
1462
- accessTokenRef.current = await bootstrapAccessToken(
1463
- runtimeUrl,
1464
- bearerToken,
1465
- );
1466
- }
1467
1407
  h.hideSpinner();
1468
1408
  h.updateHealthStatus(health.status);
1469
1409
  if (health.status === "healthy" || health.status === "ok") {
@@ -1486,7 +1426,6 @@ function ChatApp({
1486
1426
  runtimeUrl,
1487
1427
  assistantId,
1488
1428
  bearerToken,
1489
- accessTokenRef.current,
1490
1429
  );
1491
1430
  h.hideSpinner();
1492
1431
  if (historyResponse.messages.length > 0) {
@@ -1505,7 +1444,6 @@ function ChatApp({
1505
1444
  runtimeUrl,
1506
1445
  assistantId,
1507
1446
  bearerToken,
1508
- accessTokenRef.current,
1509
1447
  );
1510
1448
  for (const msg of response.messages) {
1511
1449
  if (!seenMessageIdsRef.current.has(msg.id)) {
@@ -1821,7 +1759,6 @@ function ChatApp({
1821
1759
  trimmed,
1822
1760
  controller.signal,
1823
1761
  bearerToken,
1824
- accessTokenRef.current,
1825
1762
  );
1826
1763
  clearTimeout(timeoutId);
1827
1764
  if (!sendResult.accepted) {
@@ -1834,7 +1771,8 @@ function ChatApp({
1834
1771
  clearTimeout(timeoutId);
1835
1772
  h.setBusy(false);
1836
1773
  h.hideSpinner();
1837
- const errorMsg = `Failed to send: ${sendErr instanceof Error ? sendErr.message : sendErr}`;
1774
+ const errorMsg =
1775
+ sendErr instanceof Error ? sendErr.message : String(sendErr);
1838
1776
  h.showError(errorMsg);
1839
1777
  chatLogRef.current.push({ role: "error", content: errorMsg });
1840
1778
  return;
@@ -1853,7 +1791,6 @@ function ChatApp({
1853
1791
  runtimeUrl,
1854
1792
  assistantId,
1855
1793
  bearerToken,
1856
- accessTokenRef.current,
1857
1794
  );
1858
1795
 
1859
1796
  if (pending.pendingConfirmation) {
@@ -1865,7 +1802,6 @@ function ChatApp({
1865
1802
  pending.pendingConfirmation,
1866
1803
  h,
1867
1804
  bearerToken,
1868
- accessTokenRef.current,
1869
1805
  );
1870
1806
  h.showSpinner("Working...");
1871
1807
  continue;
@@ -1890,7 +1826,6 @@ function ChatApp({
1890
1826
  }),
1891
1827
  },
1892
1828
  bearerToken,
1893
- accessTokenRef.current,
1894
1829
  );
1895
1830
  },
1896
1831
  );
@@ -1907,7 +1842,6 @@ function ChatApp({
1907
1842
  runtimeUrl,
1908
1843
  assistantId,
1909
1844
  bearerToken,
1910
- accessTokenRef.current,
1911
1845
  );
1912
1846
  for (const msg of pollResult.messages) {
1913
1847
  if (!seenMessageIdsRef.current.has(msg.id)) {
package/src/index.ts CHANGED
@@ -8,7 +8,6 @@ import { pair } from "./commands/pair";
8
8
  import { ps } from "./commands/ps";
9
9
  import { recover } from "./commands/recover";
10
10
  import { retire } from "./commands/retire";
11
- import { skills } from "./commands/skills";
12
11
  import { sleep } from "./commands/sleep";
13
12
  import { ssh } from "./commands/ssh";
14
13
  import { tunnel } from "./commands/tunnel";
@@ -24,7 +23,6 @@ const commands = {
24
23
  ps,
25
24
  recover,
26
25
  retire,
27
- skills,
28
26
  sleep,
29
27
  ssh,
30
28
  tunnel,
@@ -58,7 +56,6 @@ async function main() {
58
56
  );
59
57
  console.log(" recover Restore a previously retired local assistant");
60
58
  console.log(" retire Delete an assistant instance");
61
- console.log(" skills Browse and install skills from the Vellum catalog");
62
59
  console.log(" sleep Stop the assistant process");
63
60
  console.log(" ssh SSH into a remote assistant instance");
64
61
  console.log(" tunnel Create a tunnel for a locally hosted assistant");