@vellumai/cli 0.3.0 โ†’ 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.3.0",
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,13 +515,36 @@ 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
- const runtimeUrl = await startGateway();
538
+ let runtimeUrl: string;
539
+ try {
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;
547
+ }
524
548
 
525
549
  const baseDataDir = join(process.env.BASE_DATA_DIR?.trim() || (process.env.HOME ?? userInfo().homedir), ".vellum");
526
550
  const localEntry: AssistantEntry = {
@@ -545,14 +569,27 @@ async function hatchLocal(species: Species, name: string | null, daemonOnly: boo
545
569
  }
546
570
 
547
571
  function getCliVersion(): string {
572
+ // Strategy 1: createRequire โ€” works in Bun dev (source tree).
548
573
  try {
549
- // Use createRequire for JSON import โ€” works in both Bun dev and compiled binary.
550
574
  const require = createRequire(import.meta.url);
551
575
  const pkg = require("../../package.json") as { version?: string };
552
- return pkg.version ?? "unknown";
576
+ if (pkg.version) return pkg.version;
553
577
  } catch {
554
- return "unknown";
578
+ // Fall through to next strategy.
555
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";
556
593
  }
557
594
 
558
595
  export async function hatch(): Promise<void> {
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
+ }