@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.
- package/package.json +1 -1
- package/src/__tests__/assistant-config.test.ts +282 -3
- package/src/__tests__/multi-local.test.ts +13 -21
- package/src/__tests__/sleep.test.ts +172 -0
- package/src/commands/client.ts +72 -10
- package/src/commands/hatch.ts +65 -14
- package/src/commands/ps.ts +25 -8
- package/src/commands/recover.ts +17 -8
- package/src/commands/retire.ts +14 -23
- package/src/commands/sleep.ts +88 -16
- package/src/commands/wake.ts +9 -7
- package/src/components/DefaultMainScreen.tsx +19 -85
- package/src/index.ts +0 -3
- package/src/lib/assistant-config.ts +154 -61
- package/src/lib/aws.ts +30 -1
- package/src/lib/docker.ts +321 -0
- package/src/lib/gcp.ts +53 -1
- package/src/lib/http-client.ts +114 -0
- package/src/lib/local.ts +117 -167
- package/src/lib/step-runner.ts +9 -1
- package/src/lib/xdg-log.ts +47 -3
- package/src/__tests__/skills-uninstall.test.ts +0 -203
- package/src/commands/skills.ts +0 -514
package/src/commands/wake.ts
CHANGED
|
@@ -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
|
-
|
|
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"
|
|
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,
|
|
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" | "
|
|
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
|
-
...(
|
|
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(
|
|
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 =
|
|
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");
|