UNPKG

@phren/cli

Version:

Knowledge layer for AI agents - CLI, MCP server, and data layer

66 lines (65 loc) • 2.77 kB
import { accessSync, closeSync, constants, openSync, readSync, statSync } from "node:fs"; import path from "node:path"; /** What `PHREN_CODEX_BINARY=off` resolves to: a path no spawn can run, so every * helper call fails as if codex were not installed. The vitest config sets it: * Codex 0.158 leaves a detached `app-server --managed-daemon` behind for the * HOME it ran under, and a test run's throwaway HOME is deleted under it. */ export const CODEX_OFF = "/nonexistent/phren-codex-off"; /** The `codex` the Hook runs for its own helper calls (`codex queue`, `codex * app-server`). `phren init` can put a session wrapper at ~/.local/bin/codex * that runs phren's session-start hook before the real binary for every * subcommand except a bare --help. That takes seconds (about 8 s on the Linux * box), longer than the Hook's 5 s `codex queue --help` probe, so Codex async * questions were reported as terminal-only, and each call opened a phren * session. The Hook skips its own wrapper and runs the binary it names. */ export function codexExecutable(env = process.env) { if (/^(?:0|off|false|no)$/i.test(env.PHREN_CODEX_BINARY ?? "")) return CODEX_OFF; if (process.platform === "win32") return "codex"; for (const directory of (env.PATH ?? "").split(path.delimiter)) { if (!path.isAbsolute(directory)) continue; const candidate = path.join(directory, "codex"); if (!executable(candidate)) continue; const wrapped = phrenWrapperTarget(candidate); if (wrapped === undefined) return candidate; if (wrapped && executable(wrapped)) return wrapped; } return "codex"; } function executable(file) { try { return statSync(file).isFile() && (accessSync(file, constants.X_OK), true); } catch { return false; } } /** The real binary a phren session wrapper names, "" for a phren wrapper * whose target cannot be read, undefined for anything else. */ export function phrenWrapperTarget(file) { let head = ""; try { const fd = openSync(file, "r"); try { const buffer = Buffer.alloc(4096); head = buffer.subarray(0, readSync(fd, buffer, 0, buffer.length, 0)).toString("utf8"); } finally { closeSync(fd); } } catch { return undefined; } if (!head.startsWith("#!/bin/sh") || !head.includes("phren wrapper error:")) return undefined; // shellEscape in hooks.ts: single quotes, an embedded quote written '\''. const match = /^REAL_BIN='((?:[^']|'\\'')*)'$/m.exec(head); const target = match?.[1].replace(/'\\''/g, "'"); return target && path.isAbsolute(target) ? target : ""; }