/** * agent-browser setup extension. * * The bundled `agent-browser` skill needs the `agent-browser` CLI (npm package) * installed (`npm i -g agent-browser && agent-browser install`). Nothing is asked * at startup: web research uses the self-hosted search backend, so the browser is * opt-in. `/setup-agent-browser` probes for the CLI and, when it is missing, asks * to install it via npm and provision the browser. With no UI (headless/CI) it * prints a console warning with the install command instead. */ import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@selesai/code"; const PROBE_TIMEOUT_MS = 2_000; const INSTALL_TIMEOUT_MS = 180_000; // `npm i -g` can be slow on a cold cache const BROWSER_INSTALL_TIMEOUT_MS = 600_000; // `agent-browser install` provisions a browser export const INSTALL_COMMAND = "npm i -g agent-browser && agent-browser install"; // `pi.exec` spawns without a shell, so on Windows npm/agent-browser are .cmd // shims that must be launched through cmd.exe. function cliCommand(args: string[]): { command: string; args: string[] } { if (process.platform === "win32") { return { command: "cmd", args: ["/c", ...args] }; } return { command: args[0]!, args: args.slice(1) }; } function notify(ctx: ExtensionContext, message: string, type?: "info" | "warning" | "error"): void { if (ctx.hasUI) ctx.ui.notify(message, type); } /** True when the `agent-browser` CLI is on PATH and answers `--version`. */ export async function isAgentBrowserInstalled(pi: ExtensionAPI): Promise { const { command, args } = cliCommand(["agent-browser", "--version"]); try { const result = await pi.exec(command, args, { timeout: PROBE_TIMEOUT_MS }); return !result.killed && result.code === 0; } catch { return false; } } /** Run `npm i -g agent-browser`; returns an error message or null on success. */ async function runNpmInstall(pi: ExtensionAPI): Promise { const { command, args } = cliCommand(["npm", "i", "-g", "agent-browser"]); const result = await pi.exec(command, args, { timeout: INSTALL_TIMEOUT_MS }); if (result.killed) return "npm install timed out"; if (result.code !== 0) { // The exit-status fallback is always non-empty, so the final || is unreachable. /* v8 ignore next 1 */ return (result.stderr || result.stdout || `exit status ${result.code}`).trim() || "npm install failed"; } return null; } /** Best-effort `agent-browser install` (provisions the browser). Non-fatal. */ async function provisionBrowser(pi: ExtensionAPI): Promise { const { command, args } = cliCommand(["agent-browser", "install"]); try { const result = await pi.exec(command, args, { timeout: BROWSER_INSTALL_TIMEOUT_MS }); return !result.killed && result.code === 0; } catch { return false; } } /** * Probe for the CLI; when missing, ask the user to install it via npm and run * the documented install steps if they agree. Returns whether the CLI is * available afterwards. */ export async function ensureAgentBrowser(pi: ExtensionAPI, ctx: ExtensionContext): Promise { if (await isAgentBrowserInstalled(pi)) { notify(ctx, "agent-browser CLI is already installed.", "info"); return true; } // No dialog UI (headless/CI): warn with the install command, like rtk does. if (!ctx.hasUI) { console.warn(`[agent-browser] CLI not found; install with: ${INSTALL_COMMAND}`); return false; } const wantsInstall = await ctx.ui.confirm( "Install agent-browser?", `The agent-browser skill needs the agent-browser CLI. Install it globally via npm?\n\n${INSTALL_COMMAND}`, ); if (!wantsInstall) { ctx.ui.notify(`Skipped. Run /setup-agent-browser later or install manually: ${INSTALL_COMMAND}`, "info"); return false; } ctx.ui.notify("Installing agent-browser via npm…", "info"); const error = await runNpmInstall(pi); if (error) { ctx.ui.notify(`agent-browser install failed: ${error}`, "error"); ctx.ui.notify(`Install manually: ${INSTALL_COMMAND}`, "warning"); return false; } if (!(await isAgentBrowserInstalled(pi))) { ctx.ui.notify("npm reported success, but agent-browser is still not on PATH.", "warning"); ctx.ui.notify(`Try running: ${INSTALL_COMMAND}`, "info"); return false; } if (await provisionBrowser(pi)) { ctx.ui.notify("agent-browser installed and browser provisioned.", "info"); } else { ctx.ui.notify("agent-browser installed. Run `agent-browser install` to provision the browser.", "info"); } return true; } export default function agentBrowserSetupExtension(pi: ExtensionAPI): void { pi.registerCommand("setup-agent-browser", { description: "Install the agent-browser CLI (npm) if missing, or re-check", handler: async (_args: string, ctx: ExtensionCommandContext) => { await ensureAgentBrowser(pi, ctx); }, }); }