@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/src/index.ts CHANGED
@@ -97,10 +97,10 @@ async function main() {
97
97
  );
98
98
  console.log(" recover Restore a previously retired local assistant");
99
99
  console.log(" retire Delete an assistant instance");
100
- console.log(" sleep Stop the daemon process");
100
+ console.log(" sleep Stop the assistant process");
101
101
  console.log(" ssh SSH into a remote assistant instance");
102
102
  console.log(" tunnel Create a tunnel for a locally hosted assistant");
103
- console.log(" wake Start the daemon and gateway");
103
+ console.log(" wake Start the assistant and gateway");
104
104
  console.log(" whoami Show current logged-in user");
105
105
  process.exit(0);
106
106
  }
@@ -61,7 +61,8 @@ function readAssistants(): AssistantEntry[] {
61
61
  return [];
62
62
  }
63
63
  return entries.filter(
64
- (e) => typeof e.assistantId === "string" && typeof e.runtimeUrl === "string",
64
+ (e) =>
65
+ typeof e.assistantId === "string" && typeof e.runtimeUrl === "string",
65
66
  );
66
67
  }
67
68
 
@@ -99,7 +100,9 @@ export function loadAllAssistants(): AssistantEntry[] {
99
100
  }
100
101
 
101
102
  export function saveAssistantEntry(entry: AssistantEntry): void {
102
- const entries = readAssistants().filter((e) => e.assistantId !== entry.assistantId);
103
+ const entries = readAssistants().filter(
104
+ (e) => e.assistantId !== entry.assistantId,
105
+ );
103
106
  entries.unshift(entry);
104
107
  writeAssistants(entries);
105
108
  }
@@ -114,7 +117,10 @@ export function syncConfigToLockfile(): void {
114
117
  if (!existsSync(configPath)) return;
115
118
 
116
119
  try {
117
- const raw = JSON.parse(readFileSync(configPath, "utf-8")) as Record<string, unknown>;
120
+ const raw = JSON.parse(readFileSync(configPath, "utf-8")) as Record<
121
+ string,
122
+ unknown
123
+ >;
118
124
  const platform = raw.platform as Record<string, unknown> | undefined;
119
125
  const data = readLockfile();
120
126
  data.platformBaseUrl = (platform?.baseUrl as string) || undefined;
package/src/lib/aws.ts CHANGED
@@ -43,7 +43,9 @@ export async function getDefaultVpcId(region: string): Promise<string> {
43
43
  ]);
44
44
  const vpcId = output.trim();
45
45
  if (!vpcId || vpcId === "None") {
46
- throw new Error("No default VPC found. Please create a default VPC or specify one.");
46
+ throw new Error(
47
+ "No default VPC found. Please create a default VPC or specify one.",
48
+ );
47
49
  }
48
50
  return vpcId;
49
51
  }
@@ -342,15 +344,25 @@ async function pollAwsInstance(
342
344
  const output = await awsSshExec(ip, keyPath, remoteCmd);
343
345
  const sepIdx = output.indexOf("===HATCH_SEP===");
344
346
  if (sepIdx === -1) {
345
- return { lastLine: output.trim() || null, done: false, failed: false, errorContent: "" };
347
+ return {
348
+ lastLine: output.trim() || null,
349
+ done: false,
350
+ failed: false,
351
+ errorContent: "",
352
+ };
346
353
  }
347
354
  const errIdx = output.indexOf("===HATCH_ERR===");
348
355
  const lastLine = output.substring(0, sepIdx).trim() || null;
349
356
  const statusEnd = errIdx === -1 ? undefined : errIdx;
350
- const status = output.substring(sepIdx + "===HATCH_SEP===".length, statusEnd).trim();
357
+ const status = output
358
+ .substring(sepIdx + "===HATCH_SEP===".length, statusEnd)
359
+ .trim();
351
360
  const errorContent =
352
- errIdx === -1 ? "" : output.substring(errIdx + "===HATCH_ERR===".length).trim();
353
- const done = lastLine !== null && status !== "running" && status !== "pending";
361
+ errIdx === -1
362
+ ? ""
363
+ : output.substring(errIdx + "===HATCH_ERR===".length).trim();
364
+ const done =
365
+ lastLine !== null && status !== "running" && status !== "pending";
354
366
  const failed = errorContent.length > 0 || status === "error";
355
367
  return { lastLine, done, failed, errorContent };
356
368
  } catch {
@@ -394,7 +406,9 @@ export async function hatchAws(
394
406
  }
395
407
  } else {
396
408
  while (await instanceExistsByName(instanceName, region)) {
397
- console.log(`\u26a0\ufe0f Instance name ${instanceName} already exists, generating a new name...`);
409
+ console.log(
410
+ `\u26a0\ufe0f Instance name ${instanceName} already exists, generating a new name...`,
411
+ );
398
412
  const suffix = generateRandomSuffix();
399
413
  instanceName = `${species}-${suffix}`;
400
414
  }
@@ -405,7 +419,9 @@ export async function hatchAws(
405
419
  const hatchedBy = process.env.VELLUM_HATCHED_BY;
406
420
  const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
407
421
  if (!anthropicApiKey) {
408
- console.error("Error: ANTHROPIC_API_KEY environment variable is not set.");
422
+ console.error(
423
+ "Error: ANTHROPIC_API_KEY environment variable is not set.",
424
+ );
409
425
  process.exit(1);
410
426
  }
411
427
 
@@ -464,7 +480,9 @@ export async function hatchAws(
464
480
  try {
465
481
  externalIp = await getInstancePublicIp(instanceId, region);
466
482
  } catch {
467
- console.log("\u26a0\ufe0f Could not retrieve external IP yet (instance may still be starting)");
483
+ console.log(
484
+ "\u26a0\ufe0f Could not retrieve external IP yet (instance may still be starting)",
485
+ );
468
486
  }
469
487
 
470
488
  const runtimeUrl = externalIp
@@ -518,7 +536,9 @@ export async function hatchAws(
518
536
  process.exit(1);
519
537
  }
520
538
  } else {
521
- console.log("\u26a0\ufe0f No external IP available for monitoring. Instance is still running.");
539
+ console.log(
540
+ "\u26a0\ufe0f No external IP available for monitoring. Instance is still running.",
541
+ );
522
542
  console.log(` Monitor with: vel logs ${instanceName}`);
523
543
  console.log("");
524
544
  }
@@ -532,7 +552,10 @@ export async function hatchAws(
532
552
  }
533
553
  }
534
554
  } catch (error) {
535
- console.error("\u274c Error:", error instanceof Error ? error.message : error);
555
+ console.error(
556
+ "\u274c Error:",
557
+ error instanceof Error ? error.message : error,
558
+ );
536
559
  process.exit(1);
537
560
  }
538
561
  }
@@ -610,7 +633,9 @@ export async function retireInstance(
610
633
  }
611
634
  }
612
635
 
613
- console.log(`\u{1F5D1}\ufe0f Terminating AWS instance ${name} (${instanceId})\n`);
636
+ console.log(
637
+ `\u{1F5D1}\ufe0f Terminating AWS instance ${name} (${instanceId})\n`,
638
+ );
614
639
 
615
640
  await exec("aws", [
616
641
  "ec2",
@@ -1,5 +1,7 @@
1
1
  export const FIREWALL_TAG = "vellum-assistant";
2
- export const GATEWAY_PORT = process.env.GATEWAY_PORT ? Number(process.env.GATEWAY_PORT) : 7830;
2
+ export const GATEWAY_PORT = process.env.GATEWAY_PORT
3
+ ? Number(process.env.GATEWAY_PORT)
4
+ : 7830;
3
5
  export const VALID_REMOTE_HOSTS = ["local", "gcp", "aws", "custom"] as const;
4
6
  export type RemoteHost = (typeof VALID_REMOTE_HOSTS)[number];
5
7
  export const VALID_SPECIES = ["openclaw", "vellum"] as const;
@@ -1,6 +1,9 @@
1
1
  const DOCTOR_URL = process.env.DOCTOR_URL || "https://doctor.vellum.ai";
2
2
 
3
- export type ProgressPhase= "invoking_prompt" | "calling_tool" | "processing_tool_result";
3
+ export type ProgressPhase =
4
+ | "invoking_prompt"
5
+ | "calling_tool"
6
+ | "processing_tool_result";
4
7
 
5
8
  export interface ProgressEvent {
6
9
  phase: ProgressPhase;
@@ -48,7 +51,10 @@ async function streamDoctorResponse(
48
51
 
49
52
  for (const line of lines) {
50
53
  if (!line.trim()) continue;
51
- const parsed = JSON.parse(line) as { type: string } & Record<string, unknown>;
54
+ const parsed = JSON.parse(line) as { type: string } & Record<
55
+ string,
56
+ unknown
57
+ >;
52
58
  receivedEventTypes.push(parsed.type);
53
59
  if (parsed.type === "progress") {
54
60
  onProgress?.(parsed as unknown as ProgressEvent);
@@ -60,7 +66,8 @@ async function streamDoctorResponse(
60
66
  }
61
67
  }
62
68
  } catch (streamErr) {
63
- const detail = streamErr instanceof Error ? streamErr.message : String(streamErr);
69
+ const detail =
70
+ streamErr instanceof Error ? streamErr.message : String(streamErr);
64
71
  throw new Error(
65
72
  `Doctor stream interrupted after ${chunkCount} chunks ` +
66
73
  `(received events: [${receivedEventTypes.join(", ")}]): ${detail}`,
@@ -68,7 +75,10 @@ async function streamDoctorResponse(
68
75
  }
69
76
 
70
77
  if (buffer.trim()) {
71
- const parsed = JSON.parse(buffer) as { type: string } & Record<string, unknown>;
78
+ const parsed = JSON.parse(buffer) as { type: string } & Record<
79
+ string,
80
+ unknown
81
+ >;
72
82
  receivedEventTypes.push(parsed.type);
73
83
  if (parsed.type === "result") {
74
84
  result = parsed as unknown as DoctorResult;
@@ -105,14 +115,20 @@ async function callDoctorDaemon(
105
115
  const response = await fetch(`${DOCTOR_URL}/doctor`, {
106
116
  method: "POST",
107
117
  headers: { "Content-Type": "application/json" },
108
- body: JSON.stringify({ assistantId, project, zone, userPrompt, sessionId, chatContext }),
118
+ body: JSON.stringify({
119
+ assistantId,
120
+ project,
121
+ zone,
122
+ userPrompt,
123
+ sessionId,
124
+ chatContext,
125
+ }),
109
126
  });
110
127
  return await streamDoctorResponse(response, onProgress, onLog);
111
128
  } catch (err) {
112
129
  lastError = err;
113
130
  const errMsg = err instanceof Error ? err.message : String(err);
114
- const logMsg =
115
- `[doctor-client] Attempt ${attempt + 1}/${MAX_RETRIES} failed: ${errMsg}`;
131
+ const logMsg = `[doctor-client] Attempt ${attempt + 1}/${MAX_RETRIES} failed: ${errMsg}`;
116
132
  onLog?.(logMsg);
117
133
  if (attempt < MAX_RETRIES - 1) {
118
134
  await new Promise((resolve) => setTimeout(resolve, 500));
package/src/lib/gcp.ts CHANGED
@@ -11,11 +11,7 @@ import { generateRandomSuffix } from "./random-name";
11
11
  import { exec, execOutput } from "./step-runner";
12
12
 
13
13
  export async function getActiveProject(): Promise<string> {
14
- const output = await execOutput("gcloud", [
15
- "config",
16
- "get-value",
17
- "project",
18
- ]);
14
+ const output = await execOutput("gcloud", ["config", "get-value", "project"]);
19
15
  const project = output.trim();
20
16
  if (!project || project === "(unset)") {
21
17
  throw new Error(
@@ -87,7 +83,10 @@ async function describeFirewallRule(
87
83
  }
88
84
  }
89
85
 
90
- function ruleNeedsUpdate(spec: FirewallRuleSpec, state: FirewallRuleState): boolean {
86
+ function ruleNeedsUpdate(
87
+ spec: FirewallRuleSpec,
88
+ state: FirewallRuleState,
89
+ ): boolean {
91
90
  return (
92
91
  spec.direction !== state.direction ||
93
92
  spec.rules !== state.allowed ||
@@ -98,7 +97,11 @@ function ruleNeedsUpdate(spec: FirewallRuleSpec, state: FirewallRuleState): bool
98
97
  );
99
98
  }
100
99
 
101
- async function createFirewallRule(spec: FirewallRuleSpec, project: string, account?: string): Promise<void> {
100
+ async function createFirewallRule(
101
+ spec: FirewallRuleSpec,
102
+ project: string,
103
+ account?: string,
104
+ ): Promise<void> {
102
105
  const args = [
103
106
  "compute",
104
107
  "firewall-rules",
@@ -121,7 +124,11 @@ async function createFirewallRule(spec: FirewallRuleSpec, project: string, accou
121
124
  await exec("gcloud", args);
122
125
  }
123
126
 
124
- async function deleteFirewallRule(ruleName: string, project: string, account?: string): Promise<void> {
127
+ async function deleteFirewallRule(
128
+ ruleName: string,
129
+ project: string,
130
+ account?: string,
131
+ ): Promise<void> {
125
132
  const args = [
126
133
  "compute",
127
134
  "firewall-rules",
@@ -151,7 +158,10 @@ export async function syncFirewallRules(
151
158
  ];
152
159
  if (account) listArgs.push(`--account=${account}`);
153
160
  const output = await execOutput("gcloud", listArgs);
154
- const allRules = JSON.parse(output) as Array<{ name: string; targetTags?: string[] }>;
161
+ const allRules = JSON.parse(output) as Array<{
162
+ name: string;
163
+ targetTags?: string[];
164
+ }>;
155
165
  existingNames = allRules
156
166
  .filter((r) => r.targetTags?.includes(tag))
157
167
  .map((r) => r.name);
@@ -211,7 +221,9 @@ export interface GcpInstance {
211
221
  species: string | null;
212
222
  }
213
223
 
214
- export async function listAssistantInstances(project: string): Promise<GcpInstance[]> {
224
+ export async function listAssistantInstances(
225
+ project: string,
226
+ ): Promise<GcpInstance[]> {
215
227
  const output = await execOutput("gcloud", [
216
228
  "compute",
217
229
  "instances",
@@ -231,7 +243,8 @@ export async function listAssistantInstances(project: string): Promise<GcpInstan
231
243
  return {
232
244
  name: inst.name,
233
245
  zone: zoneParts[zoneParts.length - 1] || "",
234
- externalIp: inst.networkInterfaces?.[0]?.accessConfigs?.[0]?.natIP ?? null,
246
+ externalIp:
247
+ inst.networkInterfaces?.[0]?.accessConfigs?.[0]?.natIP ?? null,
235
248
  species: inst.labels?.species ?? null,
236
249
  };
237
250
  });
@@ -258,7 +271,10 @@ export async function instanceExists(
258
271
  return true;
259
272
  } catch (error) {
260
273
  const msg = error instanceof Error ? error.message.toLowerCase() : "";
261
- if (msg.includes("was not found") || msg.includes("could not fetch resource")) {
274
+ if (
275
+ msg.includes("was not found") ||
276
+ msg.includes("could not fetch resource")
277
+ ) {
262
278
  return false;
263
279
  }
264
280
  throw error;
@@ -407,15 +423,25 @@ async function pollInstance(
407
423
  const output = await execOutput("gcloud", args);
408
424
  const sepIdx = output.indexOf("===HATCH_SEP===");
409
425
  if (sepIdx === -1) {
410
- return { lastLine: output.trim() || null, done: false, failed: false, errorContent: "" };
426
+ return {
427
+ lastLine: output.trim() || null,
428
+ done: false,
429
+ failed: false,
430
+ errorContent: "",
431
+ };
411
432
  }
412
433
  const errIdx = output.indexOf("===HATCH_ERR===");
413
434
  const lastLine = output.substring(0, sepIdx).trim() || null;
414
435
  const statusEnd = errIdx === -1 ? undefined : errIdx;
415
- const status = output.substring(sepIdx + "===HATCH_SEP===".length, statusEnd).trim();
436
+ const status = output
437
+ .substring(sepIdx + "===HATCH_SEP===".length, statusEnd)
438
+ .trim();
416
439
  const errorContent =
417
- errIdx === -1 ? "" : output.substring(errIdx + "===HATCH_ERR===".length).trim();
418
- const done = lastLine !== null && status !== "active" && status !== "activating";
440
+ errIdx === -1
441
+ ? ""
442
+ : output.substring(errIdx + "===HATCH_ERR===".length).trim();
443
+ const done =
444
+ lastLine !== null && status !== "active" && status !== "activating";
419
445
  const failed = errorContent.length > 0 || status === "failed";
420
446
  return { lastLine, done, failed, errorContent };
421
447
  } catch {
@@ -483,7 +509,9 @@ async function recoverFromCurlFailure(
483
509
  if (account) sshArgs.push(`--account=${account}`);
484
510
  console.log("\ud83d\udd27 Running install script on instance...");
485
511
  await exec("gcloud", sshArgs);
486
- try { unlinkSync(installScriptPath); } catch {}
512
+ try {
513
+ unlinkSync(installScriptPath);
514
+ } catch {}
487
515
  }
488
516
 
489
517
  export async function hatchGcp(
@@ -542,7 +570,9 @@ export async function hatchGcp(
542
570
  }
543
571
  } else {
544
572
  while (await instanceExists(instanceName, project, zone, account)) {
545
- console.log(`\u26a0\ufe0f Instance name ${instanceName} already exists, generating a new name...`);
573
+ console.log(
574
+ `\u26a0\ufe0f Instance name ${instanceName} already exists, generating a new name...`,
575
+ );
546
576
  const suffix = generateRandomSuffix();
547
577
  instanceName = `${species}-${suffix}`;
548
578
  }
@@ -553,7 +583,9 @@ export async function hatchGcp(
553
583
  const hatchedBy = process.env.VELLUM_HATCHED_BY;
554
584
  const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
555
585
  if (!anthropicApiKey) {
556
- console.error("Error: ANTHROPIC_API_KEY environment variable is not set.");
586
+ console.error(
587
+ "Error: ANTHROPIC_API_KEY environment variable is not set.",
588
+ );
557
589
  process.exit(1);
558
590
  }
559
591
  const startupScript = await buildStartupScript(
@@ -596,7 +628,12 @@ export async function hatchGcp(
596
628
  }
597
629
 
598
630
  console.log("\ud83d\udd12 Syncing firewall rules...");
599
- await syncFirewallRules(DESIRED_FIREWALL_RULES, project, FIREWALL_TAG, account);
631
+ await syncFirewallRules(
632
+ DESIRED_FIREWALL_RULES,
633
+ project,
634
+ FIREWALL_TAG,
635
+ account,
636
+ );
600
637
 
601
638
  console.log(`\u2705 Instance ${instanceName} created successfully\n`);
602
639
 
@@ -615,7 +652,9 @@ export async function hatchGcp(
615
652
  const ipOutput = await execOutput("gcloud", describeArgs);
616
653
  externalIp = ipOutput.trim() || null;
617
654
  } catch {
618
- console.log("\u26a0\ufe0f Could not retrieve external IP yet (instance may still be starting)");
655
+ console.log(
656
+ "\u26a0\ufe0f Could not retrieve external IP yet (instance may still be starting)",
657
+ );
619
658
  }
620
659
 
621
660
  const runtimeUrl = externalIp
@@ -672,8 +711,16 @@ export async function hatchGcp(
672
711
  (await checkCurlFailure(instanceName, project, zone, account))
673
712
  ) {
674
713
  const installScriptUrl = `${process.env.VELLUM_ASSISTANT_PLATFORM_URL ?? "https://assistant.vellum.ai"}/install.sh`;
675
- console.log(`\ud83d\udd04 Detected install script curl failure for ${installScriptUrl}, attempting recovery...`);
676
- await recoverFromCurlFailure(instanceName, project, zone, sshUser, account);
714
+ console.log(
715
+ `\ud83d\udd04 Detected install script curl failure for ${installScriptUrl}, attempting recovery...`,
716
+ );
717
+ await recoverFromCurlFailure(
718
+ instanceName,
719
+ project,
720
+ zone,
721
+ sshUser,
722
+ account,
723
+ );
677
724
  console.log("\u2705 Recovery successful!");
678
725
  } else {
679
726
  process.exit(1);
@@ -689,7 +736,10 @@ export async function hatchGcp(
689
736
  }
690
737
  }
691
738
  } catch (error) {
692
- console.error("\u274c Error:", error instanceof Error ? error.message : error);
739
+ console.error(
740
+ "\u274c Error:",
741
+ error instanceof Error ? error.message : error,
742
+ );
693
743
  process.exit(1);
694
744
  }
695
745
  }
@@ -10,11 +10,16 @@ export interface HealthCheckResult {
10
10
  detail: string | null;
11
11
  }
12
12
 
13
- export async function checkHealth(runtimeUrl: string): Promise<HealthCheckResult> {
13
+ export async function checkHealth(
14
+ runtimeUrl: string,
15
+ ): Promise<HealthCheckResult> {
14
16
  try {
15
17
  const url = `${runtimeUrl}/healthz`;
16
18
  const controller = new AbortController();
17
- const timeoutId = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS);
19
+ const timeoutId = setTimeout(
20
+ () => controller.abort(),
21
+ HEALTH_CHECK_TIMEOUT_MS,
22
+ );
18
23
 
19
24
  const response = await fetch(url, {
20
25
  signal: controller.signal,
@@ -29,10 +34,15 @@ export async function checkHealth(runtimeUrl: string): Promise<HealthCheckResult
29
34
 
30
35
  const data = (await response.json()) as HealthResponse;
31
36
  const status = data.status || "unknown";
32
- return { status, detail: status !== "healthy" ? (data.message ?? null) : null };
37
+ return {
38
+ status,
39
+ detail: status !== "healthy" ? (data.message ?? null) : null,
40
+ };
33
41
  } catch (error) {
34
42
  const status =
35
- error instanceof Error && error.name === "AbortError" ? "timeout" : "unreachable";
43
+ error instanceof Error && error.name === "AbortError"
44
+ ? "timeout"
45
+ : "unreachable";
36
46
  return { status, detail: null };
37
47
  }
38
48
  }