@vellumai/cli 0.1.2 → 0.1.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,7 @@ import { spawn } from "child_process";
2
2
  import { randomBytes } from "crypto";
3
3
  import { existsSync, unlinkSync, writeFileSync } from "fs";
4
4
  import { tmpdir, userInfo } from "os";
5
- import { join } from "path";
5
+ import { dirname, join } from "path";
6
6
 
7
7
  import { buildOpenclawStartupScript } from "../adapters/openclaw";
8
8
  import { saveAssistantEntry } from "../lib/assistant-config";
@@ -583,7 +583,8 @@ async function hatchGcp(
583
583
  (await checkCurlFailure(instanceName, project, zone, account))
584
584
  ) {
585
585
  console.log("");
586
- console.log("🔄 Detected install script curl failure, attempting recovery...");
586
+ const installScriptUrl = `${process.env.VELLUM_ASSISTANT_PLATFORM_URL ?? "https://assistant.vellum.ai"}/install.sh`;
587
+ console.log(`🔄 Detected install script curl failure for ${installScriptUrl}, attempting recovery...`);
587
588
  await recoverFromCurlFailure(instanceName, project, zone, sshUser, account);
588
589
  console.log("✅ Recovery successful!");
589
590
  } else {
@@ -722,21 +723,58 @@ async function hatchLocal(species: Species, name: string | null): Promise<void>
722
723
  console.log("");
723
724
 
724
725
  console.log("🔨 Starting local daemon...");
725
- const child = spawn("bunx", ["vellum", "daemon", "start"], {
726
- stdio: "inherit",
727
- env: { ...process.env },
728
- });
729
726
 
730
- await new Promise<void>((resolve, reject) => {
731
- child.on("close", (code) => {
732
- if (code === 0) {
733
- resolve();
734
- } else {
735
- reject(new Error(`Daemon start exited with code ${code}`));
727
+ if (process.env.VELLUM_DESKTOP_APP) {
728
+ const daemonBinary = join(dirname(process.execPath), "vellum-daemon");
729
+ const child = spawn(daemonBinary, [], {
730
+ detached: true,
731
+ stdio: "ignore",
732
+ env: { ...process.env },
733
+ });
734
+ child.unref();
735
+
736
+ const homeDir = process.env.HOME ?? userInfo().homedir;
737
+ const socketPath = join(homeDir, ".vellum", "vellum.sock");
738
+ const maxWait = 10000;
739
+ const pollInterval = 100;
740
+ let waited = 0;
741
+ while (waited < maxWait) {
742
+ if (existsSync(socketPath)) {
743
+ break;
736
744
  }
745
+ await new Promise((r) => setTimeout(r, pollInterval));
746
+ waited += pollInterval;
747
+ }
748
+ if (!existsSync(socketPath)) {
749
+ console.warn("⚠️ Daemon socket did not appear within 10s — continuing anyway");
750
+ }
751
+ } else {
752
+ const assistantDir = join(import.meta.dir, "..", "..", "..", "assistant");
753
+ const assistantIndex = join(assistantDir, "src", "index.ts");
754
+
755
+ if (!existsSync(assistantIndex)) {
756
+ throw new Error(
757
+ "vellum-daemon binary not found and assistant source not available.\n" +
758
+ " Ensure the daemon binary is bundled alongside the CLI, or run from the source tree.",
759
+ );
760
+ }
761
+
762
+ const child = spawn("bun", ["run", assistantIndex, "daemon", "start"], {
763
+ stdio: "inherit",
764
+ env: { ...process.env },
737
765
  });
738
- child.on("error", reject);
739
- });
766
+
767
+ await new Promise<void>((resolve, reject) => {
768
+ child.on("close", (code) => {
769
+ if (code === 0) {
770
+ resolve();
771
+ } else {
772
+ reject(new Error(`Daemon start exited with code ${code}`));
773
+ }
774
+ });
775
+ child.on("error", reject);
776
+ });
777
+ }
740
778
 
741
779
  console.log("🌐 Starting gateway...");
742
780
  const gatewayDir = join(import.meta.dir, "..", "..", "..", "gateway");
@@ -92,12 +92,22 @@ async function retireCustom(entry: AssistantEntry): Promise<void> {
92
92
  console.log(`\u2705 Custom instance retired.`);
93
93
  }
94
94
 
95
+ function parseSource(): string | undefined {
96
+ const args = process.argv.slice(4);
97
+ for (let i = 0; i < args.length; i++) {
98
+ if (args[i] === "--source" && args[i + 1]) {
99
+ return args[i + 1];
100
+ }
101
+ }
102
+ return undefined;
103
+ }
104
+
95
105
  export async function retire(): Promise<void> {
96
106
  const name = process.argv[3];
97
107
 
98
108
  if (!name) {
99
109
  console.error("Error: Instance name is required.");
100
- console.error("Usage: vellum-cli retire <name>");
110
+ console.error("Usage: vellum-cli retire <name> [--source <source>]");
101
111
  process.exit(1);
102
112
  }
103
113
 
@@ -108,6 +118,7 @@ export async function retire(): Promise<void> {
108
118
  process.exit(1);
109
119
  }
110
120
 
121
+ const source = parseSource();
111
122
  const cloud = resolveCloud(entry);
112
123
 
113
124
  if (cloud === "gcp") {
@@ -117,14 +128,14 @@ export async function retire(): Promise<void> {
117
128
  console.error("Error: GCP project and zone not found in assistant config.");
118
129
  process.exit(1);
119
130
  }
120
- await retireGcpInstance(name, project, zone);
131
+ await retireGcpInstance(name, project, zone, source);
121
132
  } else if (cloud === "aws") {
122
133
  const region = entry.region;
123
134
  if (!region) {
124
135
  console.error("Error: AWS region not found in assistant config.");
125
136
  process.exit(1);
126
137
  }
127
- await retireAwsInstance(name, region);
138
+ await retireAwsInstance(name, region, source);
128
139
  } else if (cloud === "local") {
129
140
  await retireLocal();
130
141
  } else if (cloud === "custom") {
package/src/lib/aws.ts CHANGED
@@ -547,6 +547,7 @@ async function getInstanceIdByName(
547
547
  export async function retireInstance(
548
548
  name: string,
549
549
  region: string,
550
+ source?: string,
550
551
  ): Promise<void> {
551
552
  const instanceId = await getInstanceIdByName(name, region);
552
553
  if (!instanceId) {
@@ -556,6 +557,23 @@ export async function retireInstance(
556
557
  return;
557
558
  }
558
559
 
560
+ if (source) {
561
+ try {
562
+ await exec("aws", [
563
+ "ec2",
564
+ "create-tags",
565
+ "--resources",
566
+ instanceId,
567
+ "--tags",
568
+ `Key=retired-by,Value=${source}`,
569
+ "--region",
570
+ region,
571
+ ]);
572
+ } catch {
573
+ console.warn(`\u26a0\ufe0f Could not tag instance before termination`);
574
+ }
575
+ }
576
+
559
577
  console.log(`\u{1F5D1}\ufe0f Terminating AWS instance ${name} (${instanceId})\n`);
560
578
 
561
579
  const child = spawnChild(
package/src/lib/gcp.ts CHANGED
@@ -278,6 +278,7 @@ export async function retireInstance(
278
278
  name: string,
279
279
  project: string,
280
280
  zone: string,
281
+ source?: string,
281
282
  ): Promise<void> {
282
283
  const exists = await instanceExists(name, project, zone);
283
284
  if (!exists) {
@@ -287,6 +288,22 @@ export async function retireInstance(
287
288
  return;
288
289
  }
289
290
 
291
+ if (source) {
292
+ try {
293
+ await exec("gcloud", [
294
+ "compute",
295
+ "instances",
296
+ "add-labels",
297
+ name,
298
+ `--project=${project}`,
299
+ `--zone=${zone}`,
300
+ `--labels=retired-by=${source}`,
301
+ ]);
302
+ } catch {
303
+ console.warn(`\u26a0\ufe0f Could not label instance before deletion`);
304
+ }
305
+ }
306
+
290
307
  console.log(`\u{1F5D1}\ufe0f Deleting GCP instance ${name}\n`);
291
308
 
292
309
  const child = spawn(
@@ -1,20 +1,16 @@
1
- import { readFileSync } from "fs";
2
1
  import { join } from "path";
3
2
 
4
- const COMPONENTS_DIR = join(import.meta.dir, "..", "components");
5
- const CONSTANTS_PATH = join(import.meta.dir, "constants.ts");
3
+ const constantsSource = await Bun.file(join(import.meta.dir, "constants.ts")).text();
4
+ const defaultMainScreenSource = await Bun.file(join(import.meta.dir, "..", "components", "DefaultMainScreen.tsx")).text();
6
5
 
7
6
  function inlineLocalImports(source: string): string {
8
- const constantsSource = readFileSync(CONSTANTS_PATH, "utf-8");
9
-
10
7
  return source
11
8
  .replace(/import\s*\{[^}]*\}\s*from\s*["'][^"']*\/constants["'];?\s*\n/, constantsSource + "\n")
12
9
  .replace(/import\s*\{[^}]*\}\s*from\s*["']path["'];?\s*\n/, "");
13
10
  }
14
11
 
15
12
  export function buildInterfacesSeed(): string {
16
- const rawSource = readFileSync(join(COMPONENTS_DIR, "DefaultMainScreen.tsx"), "utf-8");
17
- const mainWindowSource = inlineLocalImports(rawSource);
13
+ const mainWindowSource = inlineLocalImports(defaultMainScreenSource);
18
14
 
19
15
  return `
20
16
  INTERFACES_SEED_DIR="/tmp/interfaces-seed"
@@ -1,10 +1,8 @@
1
- import { readFileSync } from "fs";
2
1
  import { join } from "path";
3
2
 
4
- const ADAPTER_PATH = join(import.meta.dir, "..", "adapters", "openclaw-http-server.ts");
3
+ const serverSource = await Bun.file(join(import.meta.dir, "..", "adapters", "openclaw-http-server.ts")).text();
5
4
 
6
5
  export function buildOpenclawRuntimeServer(): string {
7
- const serverSource = readFileSync(ADAPTER_PATH, "utf-8");
8
6
 
9
7
  return `
10
8
  cat > /opt/openclaw-runtime-server.ts << 'RUNTIME_SERVER_EOF'