@vellumai/cli 0.3.0 โ†’ 0.3.3

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.3.0",
3
+ "version": "0.3.3",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,11 +1,13 @@
1
1
  import { randomBytes } from "crypto";
2
2
  import { existsSync, unlinkSync, writeFileSync } from "fs";
3
- import { createRequire } from "module";
4
- import { tmpdir, userInfo } from "os";
3
+ import { homedir, tmpdir, userInfo } from "os";
5
4
  import { join } from "path";
6
5
 
6
+ // Direct import โ€” bun embeds this at compile time so it works in compiled binaries.
7
+ import cliPkg from "../../package.json";
8
+
7
9
  import { buildOpenclawStartupScript } from "../adapters/openclaw";
8
- import { saveAssistantEntry } from "../lib/assistant-config";
10
+ import { loadAllAssistants, saveAssistantEntry } from "../lib/assistant-config";
9
11
  import type { AssistantEntry } from "../lib/assistant-config";
10
12
  import { hatchAws } from "../lib/aws";
11
13
  import {
@@ -17,7 +19,8 @@ import {
17
19
  import type { RemoteHost, Species } from "../lib/constants";
18
20
  import { hatchGcp } from "../lib/gcp";
19
21
  import type { PollResult, WatchHatchingResult } from "../lib/gcp";
20
- import { startLocalDaemon, startGateway } from "../lib/local";
22
+ import { startLocalDaemon, startGateway, stopLocalProcesses } from "../lib/local";
23
+ import { isProcessAlive } from "../lib/process";
21
24
  import { generateRandomSuffix } from "../lib/random-name";
22
25
  import { exec } from "../lib/step-runner";
23
26
 
@@ -514,13 +517,36 @@ async function hatchLocal(species: Species, name: string | null, daemonOnly: boo
514
517
  const instanceName =
515
518
  name ?? process.env.VELLUM_ASSISTANT_NAME ?? `${species}-${generateRandomSuffix()}`;
516
519
 
520
+ // Clean up stale local state: if daemon/gateway processes are running but
521
+ // the lock file has no entries, stop them before starting fresh.
522
+ const vellumDir = join(homedir(), ".vellum");
523
+ const existingAssistants = loadAllAssistants();
524
+ const localAssistants = existingAssistants.filter((a) => a.cloud === "local");
525
+ if (localAssistants.length === 0) {
526
+ const daemonPid = isProcessAlive(join(vellumDir, "vellum.pid"));
527
+ const gatewayPid = isProcessAlive(join(vellumDir, "gateway.pid"));
528
+ if (daemonPid.alive || gatewayPid.alive) {
529
+ console.log("๐Ÿงน Cleaning up stale local processes (no lock file entry)...\n");
530
+ await stopLocalProcesses();
531
+ }
532
+ }
533
+
517
534
  console.log(`๐Ÿฅš Hatching local assistant: ${instanceName}`);
518
535
  console.log(` Species: ${species}`);
519
536
  console.log("");
520
537
 
521
538
  await startLocalDaemon();
522
539
 
523
- const runtimeUrl = await startGateway();
540
+ let runtimeUrl: string;
541
+ try {
542
+ runtimeUrl = await startGateway();
543
+ } catch (error) {
544
+ // Gateway failed โ€” stop the daemon we just started so we don't leave
545
+ // orphaned processes with no lock file entry.
546
+ console.error(`\nโŒ Gateway startup failed โ€” stopping daemon to avoid orphaned processes.`);
547
+ await stopLocalProcesses();
548
+ throw error;
549
+ }
524
550
 
525
551
  const baseDataDir = join(process.env.BASE_DATA_DIR?.trim() || (process.env.HOME ?? userInfo().homedir), ".vellum");
526
552
  const localEntry: AssistantEntry = {
@@ -545,14 +571,7 @@ async function hatchLocal(species: Species, name: string | null, daemonOnly: boo
545
571
  }
546
572
 
547
573
  function getCliVersion(): string {
548
- try {
549
- // Use createRequire for JSON import โ€” works in both Bun dev and compiled binary.
550
- const require = createRequire(import.meta.url);
551
- const pkg = require("../../package.json") as { version?: string };
552
- return pkg.version ?? "unknown";
553
- } catch {
554
- return "unknown";
555
- }
574
+ return cliPkg.version ?? "unknown";
556
575
  }
557
576
 
558
577
  export async function hatch(): Promise<void> {
package/src/index.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
+ import { createRequire } from "node:module";
4
+ import { dirname, join } from "node:path";
5
+ import { spawn } from "node:child_process";
3
6
  import { client } from "./commands/client";
4
7
  import { hatch } from "./commands/hatch";
5
8
  import { ps } from "./commands/ps";
@@ -23,7 +26,7 @@ async function main() {
23
26
  const commandName = args[0];
24
27
 
25
28
  if (!commandName || commandName === "--help" || commandName === "-h") {
26
- console.log("Usage: vellum-cli <command> [options]");
29
+ console.log("Usage: vellum <command> [options]");
27
30
  console.log("");
28
31
  console.log("Commands:");
29
32
  console.log(" client Connect to a hatched assistant");
@@ -38,9 +41,30 @@ async function main() {
38
41
  const command = commands[commandName as CommandName];
39
42
 
40
43
  if (!command) {
41
- console.error(`Error: Unknown command '${commandName}'`);
42
- console.error("Run 'vellum-cli --help' for usage information.");
43
- process.exit(1);
44
+ try {
45
+ const require = createRequire(import.meta.url);
46
+ const assistantPkgPath = require.resolve(
47
+ "@vellumai/assistant/package.json"
48
+ );
49
+ const assistantEntry = join(
50
+ dirname(assistantPkgPath),
51
+ "src",
52
+ "index.ts"
53
+ );
54
+ const child = spawn("bun", ["run", assistantEntry, ...args], {
55
+ stdio: "inherit",
56
+ });
57
+ child.on("exit", (code) => {
58
+ process.exit(code ?? 1);
59
+ });
60
+ } catch {
61
+ console.error(`Unknown command: ${commandName}`);
62
+ console.error(
63
+ "Install the full stack with: bun install -g vellum"
64
+ );
65
+ process.exit(1);
66
+ }
67
+ return;
44
68
  }
45
69
 
46
70
  try {
package/src/lib/local.ts CHANGED
@@ -6,6 +6,7 @@ import { dirname, join } from "path";
6
6
 
7
7
  import { loadAllAssistants, loadLatestAssistant } from "./assistant-config.js";
8
8
  import { GATEWAY_PORT } from "./constants.js";
9
+ import { stopProcessByPidFile } from "./process.js";
9
10
 
10
11
  const _require = createRequire(import.meta.url);
11
12
 
@@ -49,11 +50,18 @@ function resolveGatewayDir(): string {
49
50
  return override;
50
51
  }
51
52
 
53
+ // Source tree: cli/src/lib/ โ†’ ../../.. โ†’ repo root โ†’ gateway/
52
54
  const sourceDir = join(import.meta.dir, "..", "..", "..", "gateway");
53
55
  if (isGatewaySourceDir(sourceDir)) {
54
56
  return sourceDir;
55
57
  }
56
58
 
59
+ // Compiled binary: gateway/ bundled adjacent to the CLI executable.
60
+ const binGateway = join(dirname(process.execPath), "gateway");
61
+ if (isGatewaySourceDir(binGateway)) {
62
+ return binGateway;
63
+ }
64
+
57
65
  const cwdSourceDir = findGatewaySourceFromCwd();
58
66
  if (cwdSourceDir) {
59
67
  return cwdSourceDir;
@@ -286,7 +294,7 @@ export async function startGateway(): Promise<string> {
286
294
  }
287
295
 
288
296
  console.log("๐ŸŒ Starting gateway...");
289
- const gatewayDir = resolveGatewayDir();
297
+
290
298
  // Only auto-configure default routing when the workspace has exactly one
291
299
  // assistant. In multi-assistant deployments, falling back to "default"
292
300
  // would silently deliver unmapped Telegram chats to whichever assistant was
@@ -323,12 +331,34 @@ export async function startGateway(): Promise<string> {
323
331
  console.log(` Ingress URL: ${ingressPublicBaseUrl}`);
324
332
  }
325
333
 
326
- const gateway = spawn("bun", ["run", "src/index.ts"], {
327
- cwd: gatewayDir,
328
- detached: true,
329
- stdio: "ignore",
330
- env: gatewayEnv,
331
- });
334
+ let gateway;
335
+
336
+ if (process.env.VELLUM_DESKTOP_APP) {
337
+ // Desktop app: spawn the compiled gateway binary directly (mirrors daemon pattern).
338
+ const gatewayBinary = join(dirname(process.execPath), "vellum-gateway");
339
+ if (!existsSync(gatewayBinary)) {
340
+ throw new Error(
341
+ `vellum-gateway binary not found at ${gatewayBinary}.\n` +
342
+ " Ensure the gateway binary is bundled alongside the CLI in the app bundle.",
343
+ );
344
+ }
345
+
346
+ gateway = spawn(gatewayBinary, [], {
347
+ detached: true,
348
+ stdio: "ignore",
349
+ env: gatewayEnv,
350
+ });
351
+ } else {
352
+ // Source tree / bunx: resolve the gateway source directory and run via bun.
353
+ const gatewayDir = resolveGatewayDir();
354
+ gateway = spawn("bun", ["run", "src/index.ts"], {
355
+ cwd: gatewayDir,
356
+ detached: true,
357
+ stdio: "ignore",
358
+ env: gatewayEnv,
359
+ });
360
+ }
361
+
332
362
  gateway.unref();
333
363
 
334
364
  if (gateway.pid) {
@@ -339,3 +369,18 @@ export async function startGateway(): Promise<string> {
339
369
  console.log("โœ… Gateway started\n");
340
370
  return publicUrl || `http://localhost:${GATEWAY_PORT}`;
341
371
  }
372
+
373
+ /**
374
+ * Stop any locally-running daemon and gateway processes and clean up
375
+ * PID/socket files. Called when hatch fails partway through so we don't
376
+ * leave orphaned processes with no lock file entry.
377
+ */
378
+ export async function stopLocalProcesses(): Promise<void> {
379
+ const vellumDir = join(homedir(), ".vellum");
380
+ const daemonPidFile = join(vellumDir, "vellum.pid");
381
+ const socketFile = join(vellumDir, "vellum.sock");
382
+ await stopProcessByPidFile(daemonPidFile, "daemon", [socketFile]);
383
+
384
+ const gatewayPidFile = join(vellumDir, "gateway.pid");
385
+ await stopProcessByPidFile(gatewayPidFile, "gateway");
386
+ }