@vellumai/cli 0.1.13 → 0.3.0
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/commands/hatch.ts +1 -10
- package/src/commands/ps.ts +14 -16
- package/src/commands/retire.ts +2 -3
- package/src/components/DefaultMainScreen.tsx +0 -3
- package/src/lib/aws.ts +20 -33
- package/src/lib/gcp.ts +12 -50
- package/src/lib/local.ts +22 -1
package/package.json
CHANGED
package/src/commands/hatch.ts
CHANGED
|
@@ -520,16 +520,7 @@ async function hatchLocal(species: Species, name: string | null, daemonOnly: boo
|
|
|
520
520
|
|
|
521
521
|
await startLocalDaemon();
|
|
522
522
|
|
|
523
|
-
|
|
524
|
-
// so the HTTP gateway is only needed for non-desktop (CLI) usage.
|
|
525
|
-
let runtimeUrl: string;
|
|
526
|
-
|
|
527
|
-
if (process.env.VELLUM_DESKTOP_APP) {
|
|
528
|
-
// No gateway needed — the macOS app uses DaemonClient over the Unix socket.
|
|
529
|
-
runtimeUrl = "local";
|
|
530
|
-
} else {
|
|
531
|
-
runtimeUrl = await startGateway();
|
|
532
|
-
}
|
|
523
|
+
const runtimeUrl = await startGateway();
|
|
533
524
|
|
|
534
525
|
const baseDataDir = join(process.env.BASE_DATA_DIR?.trim() || (process.env.HOME ?? userInfo().homedir), ".vellum");
|
|
535
526
|
const localEntry: AssistantEntry = {
|
package/src/commands/ps.ts
CHANGED
|
@@ -145,7 +145,7 @@ async function getRemoteProcessesCustom(
|
|
|
145
145
|
]);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
function getLocalProcesses(): TableRow[] {
|
|
149
149
|
const rows: TableRow[] = [];
|
|
150
150
|
const vellumDir = join(homedir(), ".vellum");
|
|
151
151
|
|
|
@@ -179,21 +179,19 @@ async function getLocalProcesses(): Promise<TableRow[]> {
|
|
|
179
179
|
rows.push({ name: "qdrant", status: withStatusEmoji("not running"), info: "no PID file" });
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
// Check gateway
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
rows.push({ name: "gateway", status: withStatusEmoji("running"), info: `PID ${pid} | port 7830` });
|
|
192
|
-
} else {
|
|
193
|
-
rows.push({ name: "gateway", status: withStatusEmoji("not running"), info: "" });
|
|
182
|
+
// Check gateway PID
|
|
183
|
+
const gatewayPidFile = join(vellumDir, "gateway.pid");
|
|
184
|
+
if (existsSync(gatewayPidFile)) {
|
|
185
|
+
const pid = readFileSync(gatewayPidFile, "utf-8").trim();
|
|
186
|
+
let status = "running";
|
|
187
|
+
try {
|
|
188
|
+
process.kill(parseInt(pid, 10), 0);
|
|
189
|
+
} catch {
|
|
190
|
+
status = "not running";
|
|
194
191
|
}
|
|
195
|
-
|
|
196
|
-
|
|
192
|
+
rows.push({ name: "gateway", status: withStatusEmoji(status), info: `PID ${pid} | port 7830` });
|
|
193
|
+
} else {
|
|
194
|
+
rows.push({ name: "gateway", status: withStatusEmoji("not running"), info: "no PID file" });
|
|
197
195
|
}
|
|
198
196
|
|
|
199
197
|
return rows;
|
|
@@ -205,7 +203,7 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
|
|
|
205
203
|
console.log(`Processes for ${entry.assistantId} (${cloud}):\n`);
|
|
206
204
|
|
|
207
205
|
if (cloud === "local") {
|
|
208
|
-
const rows =
|
|
206
|
+
const rows = getLocalProcesses();
|
|
209
207
|
printTable(rows);
|
|
210
208
|
return;
|
|
211
209
|
}
|
package/src/commands/retire.ts
CHANGED
|
@@ -59,11 +59,10 @@ async function retireLocal(): Promise<void> {
|
|
|
59
59
|
child.on("error", () => resolve());
|
|
60
60
|
});
|
|
61
61
|
} catch {}
|
|
62
|
-
|
|
63
|
-
// Only delete ~/.vellum in non-desktop mode
|
|
64
|
-
rmSync(vellumDir, { recursive: true, force: true });
|
|
65
62
|
}
|
|
66
63
|
|
|
64
|
+
rmSync(vellumDir, { recursive: true, force: true });
|
|
65
|
+
|
|
67
66
|
console.log("\u2705 Local instance retired.");
|
|
68
67
|
}
|
|
69
68
|
|
|
@@ -58,9 +58,6 @@ interface PendingConfirmation {
|
|
|
58
58
|
executionTarget?: "sandbox" | "host";
|
|
59
59
|
allowlistOptions?: AllowlistOption[];
|
|
60
60
|
scopeOptions?: ScopeOption[];
|
|
61
|
-
principalKind?: string;
|
|
62
|
-
principalId?: string;
|
|
63
|
-
principalVersion?: string;
|
|
64
61
|
persistentDecisionsAllowed?: boolean;
|
|
65
62
|
}
|
|
66
63
|
|
package/src/lib/aws.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawn as spawnChild } from "child_process";
|
|
2
1
|
import { randomBytes } from "crypto";
|
|
3
2
|
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "fs";
|
|
4
3
|
import { homedir, tmpdir, userInfo } from "os";
|
|
@@ -224,6 +223,7 @@ export async function launchInstance(
|
|
|
224
223
|
userDataPath: string,
|
|
225
224
|
species: string,
|
|
226
225
|
region: string,
|
|
226
|
+
hatchedBy?: string,
|
|
227
227
|
): Promise<string> {
|
|
228
228
|
const blockDeviceMappings = JSON.stringify([
|
|
229
229
|
{
|
|
@@ -231,14 +231,18 @@ export async function launchInstance(
|
|
|
231
231
|
Ebs: { VolumeSize: 50, VolumeType: "gp3" },
|
|
232
232
|
},
|
|
233
233
|
]);
|
|
234
|
+
const tags = [
|
|
235
|
+
{ Key: "Name", Value: name },
|
|
236
|
+
{ Key: "vellum-assistant", Value: "true" },
|
|
237
|
+
{ Key: "species", Value: species },
|
|
238
|
+
];
|
|
239
|
+
if (hatchedBy) {
|
|
240
|
+
tags.push({ Key: "hatched-by", Value: hatchedBy });
|
|
241
|
+
}
|
|
234
242
|
const tagSpecifications = JSON.stringify([
|
|
235
243
|
{
|
|
236
244
|
ResourceType: "instance",
|
|
237
|
-
Tags:
|
|
238
|
-
{ Key: "Name", Value: name },
|
|
239
|
-
{ Key: "vellum-assistant", Value: "true" },
|
|
240
|
-
{ Key: "species", Value: species },
|
|
241
|
-
],
|
|
245
|
+
Tags: tags,
|
|
242
246
|
},
|
|
243
247
|
]);
|
|
244
248
|
|
|
@@ -398,6 +402,7 @@ export async function hatchAws(
|
|
|
398
402
|
|
|
399
403
|
const sshUser = userInfo().username;
|
|
400
404
|
const bearerToken = randomBytes(32).toString("hex");
|
|
405
|
+
const hatchedBy = process.env.VELLUM_HATCHED_BY;
|
|
401
406
|
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
402
407
|
if (!anthropicApiKey) {
|
|
403
408
|
console.error("Error: ANTHROPIC_API_KEY environment variable is not set.");
|
|
@@ -442,6 +447,7 @@ export async function hatchAws(
|
|
|
442
447
|
startupScriptPath,
|
|
443
448
|
species,
|
|
444
449
|
region,
|
|
450
|
+
hatchedBy,
|
|
445
451
|
);
|
|
446
452
|
} finally {
|
|
447
453
|
try {
|
|
@@ -606,33 +612,14 @@ export async function retireInstance(
|
|
|
606
612
|
|
|
607
613
|
console.log(`\u{1F5D1}\ufe0f Terminating AWS instance ${name} (${instanceId})\n`);
|
|
608
614
|
|
|
609
|
-
|
|
610
|
-
"
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
region,
|
|
618
|
-
],
|
|
619
|
-
{ stdio: "inherit" },
|
|
620
|
-
);
|
|
621
|
-
|
|
622
|
-
await new Promise<void>((resolve, reject) => {
|
|
623
|
-
child.on("close", (code) => {
|
|
624
|
-
if (code === 0) {
|
|
625
|
-
resolve();
|
|
626
|
-
} else {
|
|
627
|
-
reject(
|
|
628
|
-
new Error(
|
|
629
|
-
`aws ec2 terminate-instances exited with code ${code}`,
|
|
630
|
-
),
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
});
|
|
634
|
-
child.on("error", reject);
|
|
635
|
-
});
|
|
615
|
+
await exec("aws", [
|
|
616
|
+
"ec2",
|
|
617
|
+
"terminate-instances",
|
|
618
|
+
"--instance-ids",
|
|
619
|
+
instanceId,
|
|
620
|
+
"--region",
|
|
621
|
+
region,
|
|
622
|
+
]);
|
|
636
623
|
|
|
637
624
|
console.log(`\u2705 Instance ${name} (${instanceId}) terminated.`);
|
|
638
625
|
}
|
package/src/lib/gcp.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { spawn } from "child_process";
|
|
2
1
|
import { randomBytes } from "crypto";
|
|
3
|
-
import { existsSync,
|
|
2
|
+
import { existsSync, unlinkSync, writeFileSync } from "fs";
|
|
4
3
|
import { tmpdir, userInfo } from "os";
|
|
5
4
|
import { join } from "path";
|
|
6
5
|
|
|
@@ -11,26 +10,6 @@ import type { Species } from "./constants";
|
|
|
11
10
|
import { generateRandomSuffix } from "./random-name";
|
|
12
11
|
import { exec, execOutput } from "./step-runner";
|
|
13
12
|
|
|
14
|
-
export async function activateServiceAccount(): Promise<(() => void) | null> {
|
|
15
|
-
const account = process.env.GCP_ACCOUNT_EMAIL;
|
|
16
|
-
const keyFile = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
17
|
-
if (!account || !keyFile) return null;
|
|
18
|
-
|
|
19
|
-
const gcpConfigDir = mkdtempSync(join(tmpdir(), "vellum-gcloud-"));
|
|
20
|
-
process.env.CLOUDSDK_CONFIG = gcpConfigDir;
|
|
21
|
-
await exec("gcloud", [
|
|
22
|
-
"auth",
|
|
23
|
-
"activate-service-account",
|
|
24
|
-
account,
|
|
25
|
-
`--key-file=${keyFile}`,
|
|
26
|
-
]);
|
|
27
|
-
|
|
28
|
-
return () => {
|
|
29
|
-
delete process.env.CLOUDSDK_CONFIG;
|
|
30
|
-
try { rmSync(gcpConfigDir, { recursive: true, force: true }); } catch {}
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
13
|
export async function getActiveProject(): Promise<string> {
|
|
35
14
|
const output = await execOutput("gcloud", [
|
|
36
15
|
"config",
|
|
@@ -532,7 +511,6 @@ export async function hatchGcp(
|
|
|
532
511
|
): Promise<void> {
|
|
533
512
|
const startTime = Date.now();
|
|
534
513
|
const account = process.env.GCP_ACCOUNT_EMAIL;
|
|
535
|
-
const cleanupServiceAccount = await activateServiceAccount();
|
|
536
514
|
|
|
537
515
|
try {
|
|
538
516
|
const project = process.env.GCP_PROJECT ?? (await getActiveProject());
|
|
@@ -576,6 +554,7 @@ export async function hatchGcp(
|
|
|
576
554
|
|
|
577
555
|
const sshUser = userInfo().username;
|
|
578
556
|
const bearerToken = randomBytes(32).toString("hex");
|
|
557
|
+
const hatchedBy = process.env.VELLUM_HATCHED_BY;
|
|
579
558
|
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
580
559
|
if (!anthropicApiKey) {
|
|
581
560
|
console.error("Error: ANTHROPIC_API_KEY environment variable is not set.");
|
|
@@ -607,7 +586,7 @@ export async function hatchGcp(
|
|
|
607
586
|
"--boot-disk-size=50GB",
|
|
608
587
|
"--boot-disk-type=pd-standard",
|
|
609
588
|
`--metadata-from-file=startup-script=${startupScriptPath}`,
|
|
610
|
-
`--labels=species=${species},vellum-assistant=true`,
|
|
589
|
+
`--labels=species=${species},vellum-assistant=true${hatchedBy ? `,hatched-by=${hatchedBy.toLowerCase().replace(/[^a-z0-9_-]/g, "_")}` : ""}`,
|
|
611
590
|
"--tags=vellum-assistant",
|
|
612
591
|
"--no-service-account",
|
|
613
592
|
"--no-scopes",
|
|
@@ -716,8 +695,6 @@ export async function hatchGcp(
|
|
|
716
695
|
} catch (error) {
|
|
717
696
|
console.error("\u274c Error:", error instanceof Error ? error.message : error);
|
|
718
697
|
process.exit(1);
|
|
719
|
-
} finally {
|
|
720
|
-
cleanupServiceAccount?.();
|
|
721
698
|
}
|
|
722
699
|
}
|
|
723
700
|
|
|
@@ -772,30 +749,15 @@ export async function retireInstance(
|
|
|
772
749
|
|
|
773
750
|
console.log(`\u{1F5D1}\ufe0f Deleting GCP instance ${name}\n`);
|
|
774
751
|
|
|
775
|
-
|
|
776
|
-
"
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
"--quiet",
|
|
785
|
-
],
|
|
786
|
-
{ stdio: "inherit" },
|
|
787
|
-
);
|
|
788
|
-
|
|
789
|
-
await new Promise<void>((resolve, reject) => {
|
|
790
|
-
child.on("close", (code) => {
|
|
791
|
-
if (code === 0) {
|
|
792
|
-
resolve();
|
|
793
|
-
} else {
|
|
794
|
-
reject(new Error(`gcloud instance delete exited with code ${code}`));
|
|
795
|
-
}
|
|
796
|
-
});
|
|
797
|
-
child.on("error", reject);
|
|
798
|
-
});
|
|
752
|
+
await exec("gcloud", [
|
|
753
|
+
"compute",
|
|
754
|
+
"instances",
|
|
755
|
+
"delete",
|
|
756
|
+
name,
|
|
757
|
+
`--project=${project}`,
|
|
758
|
+
`--zone=${zone}`,
|
|
759
|
+
"--quiet",
|
|
760
|
+
]);
|
|
799
761
|
|
|
800
762
|
console.log(`\u2705 Instance ${name} deleted.`);
|
|
801
763
|
}
|
package/src/lib/local.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { createRequire } from "module";
|
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { dirname, join } from "path";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { loadAllAssistants, loadLatestAssistant } from "./assistant-config.js";
|
|
8
|
+
import { GATEWAY_PORT } from "./constants.js";
|
|
8
9
|
|
|
9
10
|
const _require = createRequire(import.meta.url);
|
|
10
11
|
|
|
@@ -286,12 +287,32 @@ export async function startGateway(): Promise<string> {
|
|
|
286
287
|
|
|
287
288
|
console.log("🌐 Starting gateway...");
|
|
288
289
|
const gatewayDir = resolveGatewayDir();
|
|
290
|
+
// Only auto-configure default routing when the workspace has exactly one
|
|
291
|
+
// assistant. In multi-assistant deployments, falling back to "default"
|
|
292
|
+
// would silently deliver unmapped Telegram chats to whichever assistant was
|
|
293
|
+
// most recently hatched — keep the "reject" policy instead.
|
|
294
|
+
const assistants = loadAllAssistants();
|
|
295
|
+
const isSingleAssistant = assistants.length === 1;
|
|
296
|
+
|
|
289
297
|
const gatewayEnv: Record<string, string> = {
|
|
290
298
|
...process.env as Record<string, string>,
|
|
291
299
|
GATEWAY_RUNTIME_PROXY_ENABLED: "true",
|
|
292
300
|
GATEWAY_RUNTIME_PROXY_REQUIRE_AUTH: "false",
|
|
293
301
|
RUNTIME_HTTP_PORT: process.env.RUNTIME_HTTP_PORT || "7821",
|
|
294
302
|
};
|
|
303
|
+
|
|
304
|
+
if (process.env.GATEWAY_UNMAPPED_POLICY) {
|
|
305
|
+
gatewayEnv.GATEWAY_UNMAPPED_POLICY = process.env.GATEWAY_UNMAPPED_POLICY;
|
|
306
|
+
} else if (isSingleAssistant) {
|
|
307
|
+
gatewayEnv.GATEWAY_UNMAPPED_POLICY = "default";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (process.env.GATEWAY_DEFAULT_ASSISTANT_ID) {
|
|
311
|
+
gatewayEnv.GATEWAY_DEFAULT_ASSISTANT_ID = process.env.GATEWAY_DEFAULT_ASSISTANT_ID;
|
|
312
|
+
} else if (isSingleAssistant) {
|
|
313
|
+
gatewayEnv.GATEWAY_DEFAULT_ASSISTANT_ID =
|
|
314
|
+
assistants[0].assistantId || loadLatestAssistant()?.assistantId || "default";
|
|
315
|
+
}
|
|
295
316
|
const workspaceIngressPublicBaseUrl = readWorkspaceIngressPublicBaseUrl();
|
|
296
317
|
const ingressPublicBaseUrl =
|
|
297
318
|
workspaceIngressPublicBaseUrl
|