@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
66 lines (65 loc) • 2.77 kB
JavaScript
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 : "";
}