@vellumai/cli 0.4.20 → 0.4.22

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.4.20",
3
+ "version": "0.4.22",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
package/src/lib/local.ts CHANGED
@@ -14,7 +14,7 @@ import { dirname, join } from "path";
14
14
  import { loadLatestAssistant } from "./assistant-config.js";
15
15
  import { GATEWAY_PORT } from "./constants.js";
16
16
  import { stopProcessByPidFile } from "./process.js";
17
- import { openLogFile, closeLogFile } from "./xdg-log.js";
17
+ import { openLogFile, pipeToLogFile } from "./xdg-log.js";
18
18
 
19
19
  const _require = createRequire(import.meta.url);
20
20
 
@@ -513,20 +513,16 @@ export async function startLocalDaemon(): Promise<void> {
513
513
  }
514
514
  }
515
515
 
516
- const daemonLogFd = openLogFile("daemon.log");
517
- let daemonPid: number | undefined;
518
- try {
519
- const child = spawn(daemonBinary, [], {
520
- cwd: dirname(daemonBinary),
521
- detached: true,
522
- stdio: ["ignore", daemonLogFd, daemonLogFd],
523
- env: daemonEnv,
524
- });
525
- child.unref();
526
- daemonPid = child.pid;
527
- } finally {
528
- closeLogFile(daemonLogFd);
529
- }
516
+ const daemonLogFd = openLogFile("hatch.log");
517
+ const child = spawn(daemonBinary, [], {
518
+ cwd: dirname(daemonBinary),
519
+ detached: true,
520
+ stdio: ["ignore", "pipe", "pipe"],
521
+ env: daemonEnv,
522
+ });
523
+ pipeToLogFile(child, daemonLogFd, "daemon");
524
+ child.unref();
525
+ const daemonPid = child.pid;
530
526
 
531
527
  // Write PID file immediately so the health monitor can find the process
532
528
  // and concurrent hatch() calls see it as alive.
@@ -691,30 +687,24 @@ export async function startGateway(assistantId?: string): Promise<string> {
691
687
  );
692
688
  }
693
689
 
694
- const gatewayLogFd = openLogFile("gateway.log");
695
- try {
696
- gateway = spawn(gatewayBinary, [], {
697
- detached: true,
698
- stdio: ["ignore", gatewayLogFd, gatewayLogFd],
699
- env: gatewayEnv,
700
- });
701
- } finally {
702
- closeLogFile(gatewayLogFd);
703
- }
690
+ const gatewayLogFd = openLogFile("hatch.log");
691
+ gateway = spawn(gatewayBinary, [], {
692
+ detached: true,
693
+ stdio: ["ignore", "pipe", "pipe"],
694
+ env: gatewayEnv,
695
+ });
696
+ pipeToLogFile(gateway, gatewayLogFd, "gateway");
704
697
  } else {
705
698
  // Source tree / bunx: resolve the gateway source directory and run via bun.
706
699
  const gatewayDir = resolveGatewayDir();
707
- const gwLogFd = openLogFile("gateway.log");
708
- try {
709
- gateway = spawn("bun", ["run", "src/index.ts", "--vellum-gateway"], {
710
- cwd: gatewayDir,
711
- detached: true,
712
- stdio: ["ignore", gwLogFd, gwLogFd],
713
- env: gatewayEnv,
714
- });
715
- } finally {
716
- closeLogFile(gwLogFd);
717
- }
700
+ const gwLogFd = openLogFile("hatch.log");
701
+ gateway = spawn("bun", ["run", "src/index.ts", "--vellum-gateway"], {
702
+ cwd: gatewayDir,
703
+ detached: true,
704
+ stdio: ["ignore", "pipe", "pipe"],
705
+ env: gatewayEnv,
706
+ });
707
+ pipeToLogFile(gateway, gwLogFd, "gateway");
718
708
  }
719
709
 
720
710
  gateway.unref();
@@ -1,4 +1,5 @@
1
1
  import { closeSync, mkdirSync, openSync, writeSync } from "fs";
2
+ import type { ChildProcess } from "child_process";
2
3
  import { homedir } from "os";
3
4
  import { join } from "path";
4
5
 
@@ -35,3 +36,39 @@ export function writeToLogFile(fd: number | "ignore", msg: string): void {
35
36
  try { writeSync(fd, msg); } catch { /* best-effort */ }
36
37
  }
37
38
  }
39
+
40
+ /** Pipe a child process's stdout/stderr to a shared log file descriptor,
41
+ * prefixing each line with a tag (e.g. "[daemon]" or "[gateway]").
42
+ * Streams are unref'd so they don't prevent the parent from exiting.
43
+ * The fd is closed automatically when both streams end. */
44
+ export function pipeToLogFile(child: ChildProcess, fd: number | "ignore", tag: string): void {
45
+ if (fd === "ignore") return;
46
+ const numFd: number = fd;
47
+ const tagLabel = `[${tag}]`;
48
+ const streams = [child.stdout, child.stderr].filter(Boolean);
49
+ let ended = 0;
50
+
51
+ function onDone() {
52
+ ended++;
53
+ if (ended >= streams.length) {
54
+ try { closeSync(numFd); } catch { /* best-effort */ }
55
+ }
56
+ }
57
+
58
+ for (const stream of streams) {
59
+ if (!stream) continue;
60
+ (stream as NodeJS.ReadableStream & { unref?: () => void }).unref?.();
61
+ stream.on("data", (chunk: Buffer) => {
62
+ const text = chunk.toString();
63
+ const lines = text.split("\n");
64
+ for (let i = 0; i < lines.length; i++) {
65
+ if (i === lines.length - 1 && lines[i] === "") break;
66
+ const nl = i < lines.length - 1 ? "\n" : "";
67
+ const prefix = `${new Date().toISOString()} ${tagLabel} `;
68
+ try { writeSync(numFd, prefix + lines[i] + nl); } catch { /* best-effort */ }
69
+ }
70
+ });
71
+ stream.on("end", onDone);
72
+ stream.on("error", onDone);
73
+ }
74
+ }