feat(ai-tools): catalog of AI coding CLIs, detection, enable/default, lanes for any tool (#40 A1)
server/lib/ai-tools.js loads server/data/ai-tools/*.json (38 tools, data from Orca, MIT) plus DASHBOARD_AI_TOOLS_DIR, detects each CLI by walking PATH, and keeps enable/default in <data dir>/ai-tools.json. shell-path.js merges the login shell's PATH at startup and on Refresh. /api/ai-tools lists, refreshes and toggles; /api/run/binary resolves through the catalog. Lanes accept any catalog id (EDISABLED for a disabled one on creation, ENOTOOL at start once a tool left the catalog). pty-run keeps the Claude/Codex builders and builds every other tool's argv from its catalog file, pasting an after-start prompt through a tmux buffer; every pane gets the hydrated PATH. A per-run bypass switch appends the catalog's bypass args (Codex included). POST /api/run now forwards sandbox/approval/resumeLast, which it dropped. agenthub lanes add gains --tool.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file Merge the user's login-shell PATH into `process.env.PATH`. A systemd
|
||||
* unit or LaunchAgent snapshots PATH when it is installed, so a CLI installed
|
||||
* later into a new directory (nvm, ~/.local/bin, a vendor installer) is
|
||||
* invisible to AI Tools detection and to every lane pane. `$SHELL -ilc` runs
|
||||
* the rc files where those installers add to PATH; delimiters discard
|
||||
* whatever the rc files print, stdin is closed so a prompt gets EOF, and a
|
||||
* timeout bounds a hang. Off with `AGENTHUB_SHELL_PATH=0`. Adapted from Orca's
|
||||
* `hydrate-shell-path.ts` (MIT).
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
const { execFile } = require("node:child_process");
|
||||
const path = require("node:path");
|
||||
|
||||
const MARK = "__AGENTHUB_PATH__";
|
||||
|
||||
/**
|
||||
* @returns {Promise<boolean>} whether a shell PATH was read and merged.
|
||||
*/
|
||||
function hydrateShellPath({
|
||||
spawn = execFile,
|
||||
shell = process.env.SHELL || "/bin/bash",
|
||||
timeoutMs = 5000,
|
||||
} = {}) {
|
||||
if (/^(0|false|off)$/i.test(process.env.AGENTHUB_SHELL_PATH || "")) return Promise.resolve(false);
|
||||
return new Promise((resolve) => {
|
||||
let child;
|
||||
try {
|
||||
child = spawn(
|
||||
shell,
|
||||
["-ilc", `printf '${MARK}%s${MARK}' "$PATH"`],
|
||||
{ timeout: timeoutMs, encoding: "utf8" },
|
||||
(err, stdout) => {
|
||||
const m = String(stdout || "").match(new RegExp(`${MARK}(.*?)${MARK}`, "s"));
|
||||
if (!m) return resolve(false); // timeout, spawn error, no output
|
||||
const have = (process.env.PATH || "").split(path.delimiter).filter(Boolean);
|
||||
const add = m[1].split(path.delimiter).filter((d) => d && !have.includes(d));
|
||||
process.env.PATH = [...have, ...new Set(add)].join(path.delimiter);
|
||||
resolve(true);
|
||||
}
|
||||
);
|
||||
} catch {
|
||||
return resolve(false);
|
||||
}
|
||||
// An rc file that reads stdin gets EOF instead of hanging until the timeout.
|
||||
child?.stdin?.end();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { hydrateShellPath };
|
||||
Reference in New Issue
Block a user