@vellumai/cli 0.1.14 โ†’ 0.3.2

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.14",
3
+ "version": "0.3.2",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,11 +1,11 @@
1
1
  import { randomBytes } from "crypto";
2
- import { existsSync, unlinkSync, writeFileSync } from "fs";
2
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
3
3
  import { createRequire } from "module";
4
- import { tmpdir, userInfo } from "os";
5
- import { join } from "path";
4
+ import { homedir, tmpdir, userInfo } from "os";
5
+ import { dirname, join } from "path";
6
6
 
7
7
  import { buildOpenclawStartupScript } from "../adapters/openclaw";
8
- import { saveAssistantEntry } from "../lib/assistant-config";
8
+ import { loadAllAssistants, saveAssistantEntry } from "../lib/assistant-config";
9
9
  import type { AssistantEntry } from "../lib/assistant-config";
10
10
  import { hatchAws } from "../lib/aws";
11
11
  import {
@@ -17,7 +17,8 @@ import {
17
17
  import type { RemoteHost, Species } from "../lib/constants";
18
18
  import { hatchGcp } from "../lib/gcp";
19
19
  import type { PollResult, WatchHatchingResult } from "../lib/gcp";
20
- import { startLocalDaemon, startGateway } from "../lib/local";
20
+ import { startLocalDaemon, startGateway, stopLocalProcesses } from "../lib/local";
21
+ import { isProcessAlive } from "../lib/process";
21
22
  import { generateRandomSuffix } from "../lib/random-name";
22
23
  import { exec } from "../lib/step-runner";
23
24
 
@@ -514,21 +515,35 @@ async function hatchLocal(species: Species, name: string | null, daemonOnly: boo
514
515
  const instanceName =
515
516
  name ?? process.env.VELLUM_ASSISTANT_NAME ?? `${species}-${generateRandomSuffix()}`;
516
517
 
518
+ // Clean up stale local state: if daemon/gateway processes are running but
519
+ // the lock file has no entries, stop them before starting fresh.
520
+ const vellumDir = join(homedir(), ".vellum");
521
+ const existingAssistants = loadAllAssistants();
522
+ const localAssistants = existingAssistants.filter((a) => a.cloud === "local");
523
+ if (localAssistants.length === 0) {
524
+ const daemonPid = isProcessAlive(join(vellumDir, "vellum.pid"));
525
+ const gatewayPid = isProcessAlive(join(vellumDir, "gateway.pid"));
526
+ if (daemonPid.alive || gatewayPid.alive) {
527
+ console.log("๐Ÿงน Cleaning up stale local processes (no lock file entry)...\n");
528
+ await stopLocalProcesses();
529
+ }
530
+ }
531
+
517
532
  console.log(`๐Ÿฅš Hatching local assistant: ${instanceName}`);
518
533
  console.log(` Species: ${species}`);
519
534
  console.log("");
520
535
 
521
536
  await startLocalDaemon();
522
537
 
523
- // The desktop app communicates with the daemon directly via Unix socket,
524
- // so the HTTP gateway is only needed for non-desktop (CLI) usage.
525
538
  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 {
539
+ try {
531
540
  runtimeUrl = await startGateway();
541
+ } catch (error) {
542
+ // Gateway failed โ€” stop the daemon we just started so we don't leave
543
+ // orphaned processes with no lock file entry.
544
+ console.error(`\nโŒ Gateway startup failed โ€” stopping daemon to avoid orphaned processes.`);
545
+ await stopLocalProcesses();
546
+ throw error;
532
547
  }
533
548
 
534
549
  const baseDataDir = join(process.env.BASE_DATA_DIR?.trim() || (process.env.HOME ?? userInfo().homedir), ".vellum");
@@ -554,14 +569,27 @@ async function hatchLocal(species: Species, name: string | null, daemonOnly: boo
554
569
  }
555
570
 
556
571
  function getCliVersion(): string {
572
+ // Strategy 1: createRequire โ€” works in Bun dev (source tree).
557
573
  try {
558
- // Use createRequire for JSON import โ€” works in both Bun dev and compiled binary.
559
574
  const require = createRequire(import.meta.url);
560
575
  const pkg = require("../../package.json") as { version?: string };
561
- return pkg.version ?? "unknown";
576
+ if (pkg.version) return pkg.version;
562
577
  } catch {
563
- return "unknown";
578
+ // Fall through to next strategy.
564
579
  }
580
+
581
+ // Strategy 2: Read package.json adjacent to the compiled binary.
582
+ try {
583
+ const binPkg = join(dirname(process.execPath), "package.json");
584
+ if (existsSync(binPkg)) {
585
+ const pkg = JSON.parse(readFileSync(binPkg, "utf-8")) as { version?: string };
586
+ if (pkg.version) return pkg.version;
587
+ }
588
+ } catch {
589
+ // Fall through.
590
+ }
591
+
592
+ return "unknown";
565
593
  }
566
594
 
567
595
  export async function hatch(): Promise<void> {
@@ -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
 
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;
@@ -339,3 +347,18 @@ export async function startGateway(): Promise<string> {
339
347
  console.log("โœ… Gateway started\n");
340
348
  return publicUrl || `http://localhost:${GATEWAY_PORT}`;
341
349
  }
350
+
351
+ /**
352
+ * Stop any locally-running daemon and gateway processes and clean up
353
+ * PID/socket files. Called when hatch fails partway through so we don't
354
+ * leave orphaned processes with no lock file entry.
355
+ */
356
+ export async function stopLocalProcesses(): Promise<void> {
357
+ const vellumDir = join(homedir(), ".vellum");
358
+ const daemonPidFile = join(vellumDir, "vellum.pid");
359
+ const socketFile = join(vellumDir, "vellum.sock");
360
+ await stopProcessByPidFile(daemonPidFile, "daemon", [socketFile]);
361
+
362
+ const gatewayPidFile = join(vellumDir, "gateway.pid");
363
+ await stopProcessByPidFile(gatewayPidFile, "gateway");
364
+ }