Lines 1-38javascript
2 * The `bash` tool (spec 04).
4 * Ports pi's `bash` execute logic + its local shell backend, stripped of all
5 * pi-package/TUI/operations coupling: `getShellConfig` (→ `$SHELL || "bash"`),
6 * `trackDetachedChildPid`/`waitForChildProcess` (→ a direct child-exit promise +
7 * detached-group `process.kill(-pid, "SIGKILL")`), `commandPrefix`/`spawnHook`/
8 * `BashOperations` (no execution seam — locked), and every render/TUI helper.
10 * Spawns `[shell, "-c", command]` at `rootDir` in its own process group, merges
11 * stdout+stderr into one `OutputAccumulator({ tempFilePrefix: "openhammer" })`,
12 * and on timeout kills the whole group so no child escapes. pi throws on non-zero
13 * exit / timeout; here those become `err(...)` — the MCP `CallTool` handler
14 * (spec 12) is the single narrowing point. (Abort is unreachable in v1: the
15 * `ToolModule.execute` signature carries no `AbortSignal`, so only the timeout
16 * path can kill.) Spawn-based tools manage their own streaming and do **not** use
17 * `io.ts`; the `try/catch` here is the at-the-end `Result` narrowing.
19import { spawn } from "node:child_process";
20import { existsSync } from "node:fs";
HighChild Process
Package source references child process execution.
dist/tools/bash.jsView on unpkg · L18 21import { OutputAccumulator } from "./output-accumulator.js";
22import { err, ok } from "./result.js";
23import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "./truncate.js";
24/** Send SIGKILL to the child's whole process group (best-effort; ignores ESRCH/EPERM). */
25function killProcessGroup(pid) {
26 if (pid === undefined)
29 // `-pid` targets the group; the child is its leader because `detached: true`.
30 process.kill(-pid, "SIGKILL");
33 // Already reaped (ESRCH) or permission denied — best-effort kill, nothing to do.
37 * Spawn the shell, stream merged stdout+stderr into `output`, and resolve with the
38 * exit code. Rejects with `timeout:<secs>` when the timeout fires (after killing the