browsirai 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/cli/run.ts","../../../src/chrome-launcher.ts","../../../src/tools/browser-navigate.ts","../../../src/tools/browser-navigate-back.ts","../../../src/tools/browser-scroll.ts","../../../src/tools/browser-wait-for.ts","../../../src/tools/browser-close.ts","../../../src/tools/browser-tabs.ts","../../../src/tools/browser-resize.ts","../../../src/cli/commands/nav.ts"],"sourcesContent":["/**\n * CLI runner for browsirai.\n *\n * Parses `browsirai <command> [args...]`, connects to Chrome via CDP,\n * looks up the command in a registry, and executes it.\n */\n\nimport pc from \"picocolors\";\nimport { connectChrome } from \"../chrome-launcher.js\";\nimport { CDPConnection } from \"../cdp/connection.js\";\nimport type { CLICommand } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Flag parsing utility\n// ---------------------------------------------------------------------------\n\n/**\n * Parses CLI flags from an args array.\n *\n * Supports:\n * --key=value → { key: \"value\" }\n * --key value → { key: \"value\" }\n * --flag → { flag: \"true\" }\n * -i → { i: \"true\" } (short boolean)\n * -d 5 → { d: \"5\" } (short with value)\n * -ic → { i: \"true\", c: \"true\" } (combined short booleans)\n * positional → { _0: \"positional\", _1: ... }\n *\n * @returns Record of parsed flags and positional args keyed as _0, _1, etc.\n */\nexport function parseFlags(args: string[]): Record<string, string> {\n const flags: Record<string, string> = {};\n let positionalIndex = 0;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n\n if (arg.startsWith(\"--\")) {\n const eqIdx = arg.indexOf(\"=\");\n if (eqIdx !== -1) {\n // --key=value\n const key = arg.slice(2, eqIdx);\n const value = arg.slice(eqIdx + 1);\n flags[key] = value;\n } else {\n const key = arg.slice(2);\n const next = args[i + 1];\n if (next && !next.startsWith(\"-\")) {\n // --key value\n flags[key] = next;\n i++;\n } else {\n // --flag (boolean)\n flags[key] = \"true\";\n }\n }\n } else if (arg.startsWith(\"-\") && arg.length > 1 && !/^-\\d/.test(arg)) {\n // Short flags: -i, -c, -d 5, -ic\n const chars = arg.slice(1);\n if (chars.length === 1) {\n // Single short flag: -i or -d 5\n const next = args[i + 1];\n if (next && !next.startsWith(\"-\")) {\n flags[chars] = next;\n i++;\n } else {\n flags[chars] = \"true\";\n }\n } else {\n // Combined short flags: -ic → i=true, c=true\n for (const ch of chars) {\n flags[ch] = \"true\";\n }\n }\n } else {\n flags[`_${positionalIndex}`] = arg;\n positionalIndex++;\n }\n }\n\n return flags;\n}\n\n// ---------------------------------------------------------------------------\n// Result printer\n// ---------------------------------------------------------------------------\n\n/**\n * Pretty-prints a command result to stdout.\n * Objects/arrays are JSON-formatted; primitives are printed as-is.\n */\nexport function printResult(data: unknown): void {\n if (data === undefined || data === null) return;\n\n if (typeof data === \"string\") {\n console.log(data);\n } else if (typeof data === \"object\") {\n console.log(JSON.stringify(data, null, 2));\n } else {\n console.log(String(data));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Command registry\n// ---------------------------------------------------------------------------\n\ninterface CommandCategory {\n name: string;\n commands: CLICommand[];\n}\n\nasync function loadCommands(): Promise<CommandCategory[]> {\n const categories: CommandCategory[] = [];\n\n const imports: Array<{\n name: string;\n path: string;\n key: string;\n }> = [\n { name: \"Navigation\", path: \"./commands/nav.js\", key: \"navCommands\" },\n { name: \"Observation\", path: \"./commands/obs.js\", key: \"obsCommands\" },\n { name: \"Actions\", path: \"./commands/act.js\", key: \"actCommands\" },\n { name: \"Network\", path: \"./commands/net.js\", key: \"netCommands\" },\n ];\n\n const base = new URL(\".\", import.meta.url);\n for (const entry of imports) {\n try {\n const url = new URL(entry.path, base).href;\n const mod = (await import(url)) as Record<string, CLICommand[]>;\n const commands = mod[entry.key];\n if (commands && Array.isArray(commands) && commands.length > 0) {\n categories.push({ name: entry.name, commands });\n }\n } catch {\n // Command file not yet created — skip silently\n }\n }\n\n return categories;\n}\n\nfunction buildRegistry(\n categories: CommandCategory[],\n): Map<string, CLICommand> {\n const registry = new Map<string, CLICommand>();\n for (const cat of categories) {\n for (const cmd of cat.commands) {\n registry.set(cmd.name, cmd);\n if (cmd.aliases) {\n for (const alias of cmd.aliases) {\n registry.set(alias, cmd);\n }\n }\n }\n }\n return registry;\n}\n\n// ---------------------------------------------------------------------------\n// Help output\n// ---------------------------------------------------------------------------\n\nfunction printHelp(categories: CommandCategory[]): void {\n console.log();\n console.log(pc.bold(\"browsirai\") + \" — Browser automation from the terminal\");\n console.log();\n console.log(pc.dim(\"Usage:\") + \" browsirai <command> [args...] [--flags]\");\n console.log();\n\n if (categories.length === 0) {\n console.log(\n pc.yellow(\" No commands available yet. Command modules have not been installed.\"),\n );\n console.log();\n return;\n }\n\n for (const cat of categories) {\n console.log(pc.cyan(pc.bold(` ${cat.name}`)));\n for (const cmd of cat.commands) {\n const aliasStr = cmd.aliases?.length\n ? pc.dim(` (${cmd.aliases.join(\", \")})`)\n : \"\";\n const name = pc.green(cmd.name.padEnd(20));\n console.log(` ${name} ${pc.dim(cmd.description)}${aliasStr}`);\n }\n console.log();\n }\n\n console.log(pc.dim(\" Examples:\"));\n console.log(pc.dim(' browsirai open example.com'));\n console.log(pc.dim(' browsirai snapshot -i'));\n console.log(pc.dim(' browsirai click @e5'));\n console.log(pc.dim(' browsirai fill @e2 \"hello world\"'));\n console.log(pc.dim(' browsirai press Enter'));\n console.log(pc.dim(' browsirai eval \"document.title\"'));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// CDP connection helper\n// ---------------------------------------------------------------------------\n\nasync function connectCDP(): Promise<CDPConnection> {\n const result = await connectChrome({ autoLaunch: true });\n\n if (!result.success) {\n const msg = result.error ?? \"Could not connect to Chrome via CDP.\";\n throw new Error(msg);\n }\n\n const wsUrl = result.wsEndpoint ?? `ws://127.0.0.1:${result.port}/devtools/browser`;\n const browser = new CDPConnection(wsUrl);\n await browser.connect();\n\n // Find a page target and attach to it (same as MCP server)\n const targets = await browser.send(\"Target.getTargets\") as {\n targetInfos: Array<{ targetId: string; type: string; url: string }>;\n };\n\n let page = targets.targetInfos.find(\n (t) => t.type === \"page\" && !t.url.startsWith(\"chrome://\")\n ) ?? targets.targetInfos.find(\n (t) => t.type === \"page\"\n );\n\n if (!page) {\n const created = await browser.send(\"Target.createTarget\", { url: \"about:blank\" }) as { targetId: string };\n page = { targetId: created.targetId, type: \"page\", url: \"about:blank\" };\n }\n\n const attached = await browser.send(\"Target.attachToTarget\", {\n targetId: page.targetId,\n flatten: true,\n }) as { sessionId: string };\n\n // Return a session-scoped proxy that sends commands with the sessionId\n const sessionId = attached.sessionId;\n const session = Object.create(browser) as CDPConnection;\n const originalSend = browser.send.bind(browser);\n session.send = (method: string, params?: Record<string, unknown>, options?: { timeout?: number; sessionId?: string }) => {\n return originalSend(method, params, {\n ...options,\n sessionId: options?.sessionId ?? sessionId,\n });\n };\n session.close = () => browser.close();\n\n await Promise.all([\n session.send(\"Page.enable\"),\n session.send(\"Runtime.enable\"),\n ]).catch(() => {});\n\n return session;\n}\n\n\n// ---------------------------------------------------------------------------\n// Main CLI runner\n// ---------------------------------------------------------------------------\n\nexport async function runCLI(args: string[]): Promise<void> {\n const commandName = args[0];\n const remainingArgs = args.slice(1);\n\n // Load available commands\n const categories = await loadCommands();\n const registry = buildRegistry(categories);\n\n // No command or --help → show help\n if (!commandName || commandName === \"--help\" || commandName === \"-h\") {\n printHelp(categories);\n return;\n }\n\n // Look up the command\n const command = registry.get(commandName);\n if (!command) {\n console.error(\n pc.red(`Unknown command: ${pc.bold(commandName)}`),\n );\n console.log();\n console.log(\n pc.dim(\"Run \") + pc.bold(\"browsirai --help\") + pc.dim(\" to see available commands.\"),\n );\n\n // Suggest similar commands\n const similar = findSimilar(commandName, registry);\n if (similar.length > 0) {\n console.log();\n console.log(pc.dim(\"Did you mean?\"));\n for (const s of similar) {\n console.log(` ${pc.green(s)}`);\n }\n }\n\n console.log();\n process.exit(1);\n }\n\n // Connect to Chrome and run the command\n let cdp: CDPConnection | null = null;\n try {\n cdp = await connectCDP();\n await command.run(cdp, remainingArgs);\n } catch (err) {\n const message =\n err instanceof Error ? err.message : String(err);\n console.error(pc.red(`Error: ${message}`));\n process.exit(1);\n } finally {\n if (cdp?.isConnected) {\n cdp.close();\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fuzzy matching helper\n// ---------------------------------------------------------------------------\n\nfunction findSimilar(\n input: string,\n registry: Map<string, CLICommand>,\n): string[] {\n const names = Array.from(registry.keys());\n return names\n .filter((name) => {\n // Simple substring or prefix match\n return (\n name.includes(input) ||\n input.includes(name) ||\n levenshtein(input, name) <= 3\n );\n })\n .slice(0, 3);\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp: number[][] = Array.from({ length: m + 1 }, () =>\n Array(n + 1).fill(0) as number[],\n );\n\n for (let i = 0; i <= m; i++) dp[i]![0] = i;\n for (let j = 0; j <= n; j++) dp[0]![j] = j;\n\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n dp[i]![j] = Math.min(\n dp[i - 1]![j]! + 1,\n dp[i]![j - 1]! + 1,\n dp[i - 1]![j - 1]! + cost,\n );\n }\n }\n\n return dp[m]![n]!;\n}\n","/**\n * Chrome connection — connects to Chrome via CDP.\n *\n * Strategy (ordered by preference):\n * 1. If Chrome is already running with --remote-debugging-port → connect via DevToolsActivePort\n * 2. If Chrome is running without debugging → quit & relaunch with --remote-debugging-port\n * 3. If Chrome is not running → launch with --remote-debugging-port\n *\n * Using --remote-debugging-port avoids the Chrome M144 \"Allow remote debugging?\" modal\n * entirely. The default user data directory is preserved, so cookies, logins, tabs,\n * and extensions remain intact.\n */\n\nimport { execSync, spawn } from \"node:child_process\";\nimport { existsSync, readFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from \"node:fs\";\nimport http from \"node:http\";\nimport { join } from \"node:path\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { createConnection } from \"node:net\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ConnectOptions {\n /** CDP port override (normally read from DevToolsActivePort) */\n port?: number;\n /** If true, auto-launch Chrome with --remote-debugging-port when not connected */\n autoLaunch?: boolean;\n /** If true, launch Chrome in headless mode (no visible window) */\n headless?: boolean;\n}\n\nexport interface ConnectResult {\n /** Whether connection to Chrome succeeded */\n success: boolean;\n /** Port Chrome is listening on */\n port: number;\n /** Full WebSocket endpoint URL */\n wsEndpoint?: string;\n /** Whether DevToolsActivePort file was found */\n activePortFound: boolean;\n /** Error message if connection failed */\n error?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Well-known Chrome paths per platform\n// ---------------------------------------------------------------------------\n\nconst CHROME_PATHS: Record<string, string[]> = {\n darwin: [\n \"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\",\n \"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary\",\n \"/Applications/Chromium.app/Contents/MacOS/Chromium\",\n \"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge\",\n \"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser\",\n ],\n linux: [\n \"google-chrome\",\n \"google-chrome-stable\",\n \"chromium\",\n \"chromium-browser\",\n \"microsoft-edge\",\n \"brave-browser\",\n ],\n win32: [\n \"C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n \"C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n \"C:\\\\Program Files\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe\",\n \"C:\\\\Program Files\\\\BraveSoftware\\\\Brave-Browser\\\\Application\\\\brave.exe\",\n ],\n};\n\n// ---------------------------------------------------------------------------\n// Default Chrome data directory per platform\n// ---------------------------------------------------------------------------\n\nexport function getDefaultChromeDataDir(): string {\n const home = homedir();\n switch (process.platform) {\n case \"darwin\":\n return join(home, \"Library\", \"Application Support\", \"Google\", \"Chrome\");\n case \"win32\":\n return join(home, \"AppData\", \"Local\", \"Google\", \"Chrome\", \"User Data\");\n default: // linux\n return join(home, \".config\", \"google-chrome\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// Find Chrome\n// ---------------------------------------------------------------------------\n\nexport function findChrome(): string | null {\n const platform = process.platform;\n const candidates = CHROME_PATHS[platform] ?? [];\n\n for (const candidate of candidates) {\n if (platform === \"darwin\" || platform === \"win32\") {\n if (existsSync(candidate)) return candidate;\n } else {\n try {\n const result = execSync(`which ${candidate}`, { stdio: \"pipe\" });\n const path = result.toString().trim();\n if (path) return path;\n } catch {\n // try next\n }\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Port check\n// ---------------------------------------------------------------------------\n\nexport function isPortReachable(port: number, host = \"127.0.0.1\"): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = createConnection({ port, host });\n socket.setTimeout(2000);\n socket.on(\"connect\", () => { socket.end(); resolve(true); });\n socket.on(\"error\", () => { socket.destroy(); resolve(false); });\n socket.on(\"timeout\", () => { socket.destroy(); resolve(false); });\n });\n}\n\n/**\n * Verifies CDP is truly usable by hitting /json/version.\n * Chrome's M144 approach (chrome://inspect) opens the port but returns 404 on /json/version\n * and 403 on WebSocket connections. Only --remote-debugging-port gives real CDP access.\n */\nexport function isCDPHealthy(port: number, host = \"127.0.0.1\"): Promise<boolean> {\n return new Promise((resolve) => {\n const req = http.get(`http://${host}:${port}/json/version`, (res) => {\n resolve(res.statusCode === 200);\n res.resume();\n });\n req.setTimeout(3000, () => { req.destroy(); resolve(false); });\n req.on(\"error\", () => resolve(false));\n });\n}\n\n// ---------------------------------------------------------------------------\n// DevToolsActivePort reader\n// ---------------------------------------------------------------------------\n\nexport interface ActivePortInfo {\n /** The debug port Chrome is listening on */\n port: number;\n /** The WebSocket path (e.g. /devtools/browser/...) */\n wsPath: string;\n /** Full WebSocket endpoint: ws://127.0.0.1:{port}{wsPath} */\n wsEndpoint: string;\n}\n\n/**\n * Reads the DevToolsActivePort file from Chrome's data directory.\n *\n * Chrome writes this file when remote debugging is enabled via\n * chrome://inspect/#remote-debugging (Chrome M144+).\n *\n * File format:\n * Line 1: port number (e.g. \"9222\")\n * Line 2: WebSocket path (e.g. \"/devtools/browser/abc-123\")\n *\n * @param chromeDataDir - Override Chrome data dir (for testing)\n */\nexport function readDevToolsActivePort(chromeDataDir?: string): ActivePortInfo | null {\n const dataDir = chromeDataDir ?? getDefaultChromeDataDir();\n const portFile = join(dataDir, \"DevToolsActivePort\");\n\n if (!existsSync(portFile)) {\n return null;\n }\n\n try {\n const content = readFileSync(portFile, \"utf-8\");\n const lines = content.split(\"\\n\").map(l => l.trim()).filter(l => l.length > 0);\n\n if (lines.length < 2) return null;\n\n const port = parseInt(lines[0]!, 10);\n const wsPath = lines[1]!;\n\n if (isNaN(port) || !wsPath.startsWith(\"/\")) return null;\n\n return {\n port,\n wsPath,\n wsEndpoint: `ws://127.0.0.1:${port}${wsPath}`,\n };\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Open chrome://inspect to guide user\n// ---------------------------------------------------------------------------\n\n/**\n * Opens chrome://inspect/#remote-debugging in the user's Chrome.\n * On macOS uses `open`, on Linux uses `xdg-open`, on Windows uses `start`.\n */\nexport function openChromeInspect(): boolean {\n const url = \"chrome://inspect/#remote-debugging\";\n try {\n if (process.platform === \"darwin\") {\n execSync(`open -a \"Google Chrome\" \"${url}\"`, { stdio: \"pipe\", timeout: 5000 });\n } else if (process.platform === \"win32\") {\n execSync(`start chrome \"${url}\"`, { stdio: \"pipe\", timeout: 5000 });\n } else {\n execSync(`google-chrome \"${url}\" || chromium \"${url}\"`, { stdio: \"pipe\", timeout: 5000 });\n }\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Launch Chrome with remote debugging (zero modals)\n// ---------------------------------------------------------------------------\n\nexport interface LaunchResult {\n success: boolean;\n port: number;\n wsEndpoint?: string;\n error?: string;\n}\n\n/**\n * Checks if Chrome is currently running.\n */\nexport function isChromeRunning(): boolean {\n try {\n if (process.platform === \"win32\") {\n const r = execSync('tasklist /FI \"IMAGENAME eq chrome.exe\" /NH', { stdio: \"pipe\" }).toString();\n return r.includes(\"chrome.exe\");\n }\n const r = execSync(\"pgrep -x 'Google Chrome' || pgrep -x chrome || pgrep -x chromium\", { stdio: \"pipe\" }).toString().trim();\n return r.length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Quits Chrome gracefully, waits for it to fully exit.\n * Falls back to force kill if graceful quit doesn't work (e.g. macOS session restore).\n */\nexport async function quitChrome(): Promise<void> {\n try {\n if (process.platform === \"darwin\") {\n execSync('osascript -e \\'tell application \"Google Chrome\" to quit\\'', { stdio: \"pipe\", timeout: 5000 });\n } else if (process.platform === \"win32\") {\n execSync(\"taskkill /IM chrome.exe\", { stdio: \"pipe\", timeout: 5000 });\n } else {\n execSync(\"pkill -TERM chrome || pkill -TERM chromium\", { stdio: \"pipe\", timeout: 5000 });\n }\n } catch {\n // May not be running\n }\n\n // Wait for Chrome to fully exit (up to 3 seconds)\n for (let i = 0; i < 15; i++) {\n if (!isChromeRunning()) break;\n await new Promise(r => setTimeout(r, 200));\n }\n\n // Force kill if still running (macOS session restore can relaunch Chrome)\n if (isChromeRunning()) {\n try {\n if (process.platform === \"win32\") {\n execSync(\"taskkill /F /IM chrome.exe\", { stdio: \"pipe\", timeout: 5000 });\n } else {\n execSync(\"pkill -9 'Google Chrome' || pkill -9 chrome || pkill -9 chromium\", { stdio: \"pipe\", timeout: 5000 });\n }\n } catch {\n // best effort\n }\n\n // Wait for force kill to take effect\n for (let i = 0; i < 15; i++) {\n if (!isChromeRunning()) break;\n await new Promise(r => setTimeout(r, 200));\n }\n }\n\n // Small delay for profile lock release\n await new Promise(r => setTimeout(r, 500));\n}\n\nconst SEPARATE_PORT = 9444;\n\n/**\n * Launches Chrome with --remote-debugging-port to avoid the M144 \"Allow?\" modal.\n *\n * NEVER quits the user's running Chrome. If Chrome is already running without\n * CDP, a separate instance is launched with a temp profile + cookie sync\n * (same strategy as headless, but with a visible window).\n *\n * @param port - CDP port (default 9222)\n * @returns LaunchResult with wsEndpoint if successful\n */\nexport async function launchChromeWithDebugging(port = 9222, headless = false): Promise<LaunchResult> {\n // Already healthy (launched with --remote-debugging-port)? Don't touch Chrome.\n const healthy = await isCDPHealthy(port);\n if (healthy) {\n const ws = await getWsEndpoint(port);\n return { success: true, port, wsEndpoint: ws };\n }\n\n // Check if a separate browsirai instance is already running\n const sepHealthy = await isCDPHealthy(SEPARATE_PORT);\n if (sepHealthy) {\n const ws = await getWsEndpoint(SEPARATE_PORT);\n return { success: true, port: SEPARATE_PORT, wsEndpoint: ws };\n }\n\n const chromePath = findChrome();\n if (!chromePath) {\n return { success: false, port, error: \"Chrome not found. Install Chrome and try again.\" };\n }\n\n // If Chrome is running without CDP, launch a SEPARATE instance.\n // NEVER quit the user's Chrome — their tabs, work, and session are sacred.\n const usesSeparateInstance = isChromeRunning();\n const targetPort = usesSeparateInstance ? SEPARATE_PORT : port;\n\n const dataDir = usesSeparateInstance\n ? join(tmpdir(), \"browsirai-normal\")\n : undefined; // use default Chrome profile when no Chrome is running\n\n if (dataDir) {\n mkdirSync(dataDir, { recursive: true });\n syncCookiesToHeadless(dataDir); // reuse cookie sync for the separate instance\n }\n\n const args = [\n `--remote-debugging-port=${targetPort}`,\n \"--remote-allow-origins=*\",\n \"--no-sandbox\",\n ];\n\n if (dataDir) {\n args.push(`--user-data-dir=${dataDir}`, \"--no-first-run\", \"--no-default-browser-check\", \"--disable-extensions\");\n }\n\n if (headless) {\n args.push(\"--headless=new\");\n }\n\n const child = spawn(chromePath, args, {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n\n // Wait for CDP to become healthy (up to 15 seconds)\n for (let i = 0; i < 75; i++) {\n await new Promise(r => setTimeout(r, 200));\n const ok = await isCDPHealthy(targetPort);\n if (ok) {\n const ws = await getWsEndpoint(targetPort);\n return { success: true, port: targetPort, wsEndpoint: ws };\n }\n }\n\n return {\n success: false,\n port: targetPort,\n error: \"Chrome launched but CDP port not reachable after 15s. Check if another Chrome instance is blocking the profile.\",\n };\n}\n\n// ---------------------------------------------------------------------------\n// Headless Chrome — separate instance, doesn't touch user's Chrome\n// ---------------------------------------------------------------------------\n\nconst HEADLESS_PORT = 9333;\n\n/**\n * Fetches the webSocketDebuggerUrl from /json/version.\n */\nasync function getWsEndpoint(port: number): Promise<string | undefined> {\n return new Promise((resolve) => {\n const req = http.get(`http://127.0.0.1:${port}/json/version`, (res) => {\n let body = \"\";\n res.on(\"data\", (c: Buffer) => { body += c.toString(); });\n res.on(\"end\", () => {\n try {\n const data = JSON.parse(body) as { webSocketDebuggerUrl?: string };\n resolve(data.webSocketDebuggerUrl);\n } catch { resolve(undefined); }\n });\n });\n req.setTimeout(3000, () => { req.destroy(); resolve(undefined); });\n req.on(\"error\", () => resolve(undefined));\n });\n}\n\n// ---------------------------------------------------------------------------\n// Cookie sync state — tracks last sync for navigate-hook resync detection\n// ---------------------------------------------------------------------------\n\ninterface CookieSyncState {\n profileName: string;\n cookieMtime: number;\n}\n\nlet cookieSyncState: CookieSyncState | null = null;\n\n/**\n * Returns the current cookie sync state (profile name + cookie file mtime).\n * Returns null if no sync has been performed yet.\n */\nexport function getCookieSyncState(): CookieSyncState | null {\n return cookieSyncState;\n}\n\n/**\n * Checks if cookies need re-syncing by comparing current cookie file mtime\n * and active profile against the last sync state.\n * Returns true if: cookie file modified, profile switched, or no prior sync.\n * Returns false if: nothing changed or Chrome data dir doesn't exist.\n */\nexport function needsCookieResync(chromeDataDir?: string): boolean {\n if (!cookieSyncState) return false; // no prior sync → nothing to compare\n\n const dataDir = chromeDataDir ?? getDefaultChromeDataDir();\n const localStatePath = join(dataDir, \"Local State\");\n if (!existsSync(localStatePath)) return false;\n\n try {\n const localState = JSON.parse(readFileSync(localStatePath, \"utf-8\")) as {\n profile?: { last_used?: string };\n };\n const profileName = localState.profile?.last_used ?? \"Default\";\n\n // Profile changed?\n if (profileName !== cookieSyncState.profileName) return true;\n\n // Cookie file mtime changed?\n const cookiePath = join(dataDir, profileName, \"Cookies\");\n if (!existsSync(cookiePath)) return false;\n const mtime = statSync(cookiePath).mtimeMs;\n return mtime !== cookieSyncState.cookieMtime;\n } catch {\n return false;\n }\n}\n\n/**\n * Detects the user's active Chrome profile, copies cookies to the dest profile,\n * and tracks sync state (mtime + profile name) for later resync detection.\n */\nexport function syncCookiesAndTrack(destDataDir: string, chromeDataDir?: string): void {\n const dataDir = chromeDataDir ?? getDefaultChromeDataDir();\n try {\n const localStatePath = join(dataDir, \"Local State\");\n if (!existsSync(localStatePath)) return;\n\n const localState = JSON.parse(readFileSync(localStatePath, \"utf-8\")) as {\n profile?: { last_used?: string };\n };\n const profileName = localState.profile?.last_used ?? \"Default\";\n const srcProfileDir = join(dataDir, profileName);\n\n if (!existsSync(join(srcProfileDir, \"Cookies\"))) return;\n\n // Ensure Default profile dir exists in dest data dir\n const destProfileDir = join(destDataDir, \"Default\");\n mkdirSync(destProfileDir, { recursive: true });\n\n // Copy all Cookies-related files\n const files = readdirSync(srcProfileDir).filter(f => f.startsWith(\"Cookies\"));\n for (const file of files) {\n copyFileSync(join(srcProfileDir, file), join(destProfileDir, file));\n }\n\n // Track sync state\n const mtime = statSync(join(srcProfileDir, \"Cookies\")).mtimeMs;\n cookieSyncState = { profileName, cookieMtime: mtime };\n } catch {\n // Best-effort — don't fail launch\n }\n}\n\n/** @deprecated Use syncCookiesAndTrack instead. Kept for internal compatibility. */\nfunction syncCookiesToHeadless(headlessDataDir: string): void {\n syncCookiesAndTrack(headlessDataDir);\n}\n\n/**\n * Launches a separate headless Chrome on port 9333 with a temp profile.\n * Does NOT quit or affect the user's running Chrome.\n */\nexport async function launchHeadlessChrome(): Promise<LaunchResult> {\n // Already running?\n const healthy = await isCDPHealthy(HEADLESS_PORT);\n if (healthy) {\n const ws = await getWsEndpoint(HEADLESS_PORT);\n return { success: true, port: HEADLESS_PORT, wsEndpoint: ws };\n }\n\n const chromePath = findChrome();\n if (!chromePath) {\n return { success: false, port: HEADLESS_PORT, error: \"Chrome not found.\" };\n }\n\n const dataDir = join(tmpdir(), \"browsirai-headless\");\n mkdirSync(dataDir, { recursive: true });\n\n // Copy user's cookies to headless profile before launch\n syncCookiesToHeadless(dataDir);\n\n const child = spawn(chromePath, [\n \"--headless=new\",\n `--remote-debugging-port=${HEADLESS_PORT}`,\n \"--remote-allow-origins=*\",\n `--user-data-dir=${dataDir}`,\n \"--no-first-run\",\n \"--no-default-browser-check\",\n \"--disable-extensions\",\n \"--disable-gpu\",\n \"--no-sandbox\",\n ], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n\n // Wait for CDP to become healthy\n for (let i = 0; i < 75; i++) {\n await new Promise(r => setTimeout(r, 200));\n if (await isCDPHealthy(HEADLESS_PORT)) {\n const ws = await getWsEndpoint(HEADLESS_PORT);\n return { success: true, port: HEADLESS_PORT, wsEndpoint: ws };\n }\n }\n\n return { success: false, port: HEADLESS_PORT, error: \"Headless Chrome did not start in 15s.\" };\n}\n\n// ---------------------------------------------------------------------------\n// Connect to Chrome\n// ---------------------------------------------------------------------------\n\n/**\n * Connects to Chrome via CDP.\n *\n * Strategy:\n * 1. Try DevToolsActivePort (Chrome already has debugging enabled)\n * 2. Try manual port override or default port 9222\n * 3. If autoLaunch is true, quit Chrome and relaunch with --remote-debugging-port\n *\n * @returns ConnectResult with wsEndpoint if successful\n */\nexport async function connectChrome(options: ConnectOptions = {}): Promise<ConnectResult> {\n const targetPort = options.port ?? 9222;\n\n // 1. Try DevToolsActivePort first (must be CDP-healthy, not just TCP-reachable)\n const activePort = readDevToolsActivePort();\n\n if (activePort) {\n const healthy = await isCDPHealthy(activePort.port);\n if (healthy) {\n return {\n success: true,\n port: activePort.port,\n wsEndpoint: activePort.wsEndpoint,\n activePortFound: true,\n };\n }\n }\n\n // 2. Try port directly (must be CDP-healthy to avoid M144 modal)\n const healthy = await isCDPHealthy(targetPort);\n if (healthy) {\n return {\n success: true,\n port: targetPort,\n activePortFound: false,\n };\n }\n\n // 3. Auto-launch if enabled\n if (options.autoLaunch) {\n const launch = await launchChromeWithDebugging(targetPort, options.headless);\n if (launch.success) {\n return {\n success: true,\n port: launch.port,\n wsEndpoint: launch.wsEndpoint,\n activePortFound: false,\n };\n }\n return {\n success: false,\n port: targetPort,\n activePortFound: false,\n error: launch.error,\n };\n }\n\n // 4. Not connected\n return {\n success: false,\n port: targetPort,\n activePortFound: activePort !== null,\n error: \"Chrome remote debugging is not enabled. Enable it at chrome://inspect/#remote-debugging\",\n };\n}\n","/**\n * browser_navigate tool — navigates to a URL via CDP Page.navigate.\n *\n * Handles:\n * - Cross-document navigation (loaderId present) → waits for load completion\n * by racing Page.loadEventFired against document.readyState polling\n * - Same-document navigation (no loaderId, e.g. hash change) → resolves immediately\n * - Error responses (errorText from CDP)\n * - Configurable waitUntil strategy\n * - Navigation timeout (default 30 s)\n */\nimport type { CDPConnection } from \"../cdp/connection\";\n\ninterface NavigateParams {\n url: string;\n waitUntil?: \"load\" | \"domcontentloaded\" | \"networkidle\";\n timeout?: number;\n}\n\ninterface NavigateResult {\n url: string;\n title: string;\n}\n\nconst POLL_INTERVAL_MS = 100;\n\nexport async function browserNavigate(\n cdp: CDPConnection,\n params: NavigateParams,\n): Promise<NavigateResult> {\n const { url, timeout = 8 } = params;\n const timeoutMs = timeout * 1000;\n\n // Enable Page domain events before navigating\n await cdp.send(\"Page.enable\");\n\n // Race navigation against a timeout\n const result = await Promise.race([\n performNavigation(cdp, url, params.waitUntil),\n createTimeout(timeoutMs),\n ]);\n\n return result;\n}\n\nasync function performNavigation(\n cdp: CDPConnection,\n url: string,\n waitUntil?: string,\n): Promise<NavigateResult> {\n const navResponse = (await cdp.send(\"Page.navigate\", { url })) as {\n frameId?: string;\n loaderId?: string;\n errorText?: string;\n };\n\n // Check for navigation errors\n if (navResponse.errorText) {\n throw new Error(`Navigation failed: ${navResponse.errorText}`);\n }\n\n const hasCrossDocNavigation = Boolean(navResponse.loaderId);\n\n if (hasCrossDocNavigation) {\n // Cross-document navigation: race event listener against readyState polling.\n // This ensures tests that emit Page.loadEventFired work, AND tests that\n // only mock Runtime.evaluate to return readyState=complete also work.\n await waitForLoadCompletion(cdp, waitUntil);\n }\n // Same-document navigation (hash change / pushState): no load event needed\n\n return getPageInfo(cdp);\n}\n\n/**\n * Waits for page load completion by racing two strategies:\n * 1. Listening for the Page.loadEventFired (or domContentEventFired) CDP event\n * 2. Polling document.readyState via Runtime.evaluate\n *\n * Whichever resolves first wins, and the other is cleaned up.\n */\nfunction waitForLoadCompletion(\n cdp: CDPConnection,\n waitUntil?: string,\n): Promise<void> {\n const eventName =\n waitUntil === \"domcontentloaded\"\n ? \"Page.domContentEventFired\"\n : \"Page.loadEventFired\";\n\n return new Promise<void>((resolve) => {\n let settled = false;\n\n // Strategy 1: CDP event listener\n const handler = () => {\n if (settled) return;\n settled = true;\n cdp.off(eventName, handler as (params: unknown) => void);\n resolve();\n };\n cdp.on(eventName, handler as (params: unknown) => void);\n\n // Strategy 2: poll readyState\n const poll = async () => {\n while (!settled) {\n try {\n const response = (await cdp.send(\"Runtime.evaluate\", {\n expression: \"document.readyState\",\n returnByValue: true,\n })) as { result: { type?: string; value?: string } };\n\n const readyState = response.result.value;\n\n // Ready if:\n // 1. readyState is explicitly \"complete\"\n // 2. The response is not a recognized loading state (meaning the\n // execution context is alive and document is accessible)\n const isLoadingState =\n readyState === \"loading\" || readyState === \"interactive\";\n if (readyState === \"complete\" || !isLoadingState) {\n if (!settled) {\n settled = true;\n cdp.off(eventName, handler as (params: unknown) => void);\n resolve();\n }\n return;\n }\n } catch {\n // Runtime.evaluate can fail transiently during navigation — retry\n }\n\n if (!settled) {\n await delay(POLL_INTERVAL_MS);\n }\n }\n };\n\n poll();\n });\n}\n\nasync function getPageInfo(cdp: CDPConnection): Promise<NavigateResult> {\n const [titleResponse, urlResponse] = await Promise.all([\n cdp.send(\"Runtime.evaluate\", {\n expression: \"document.title\",\n }) as Promise<{ result: { value?: string } }>,\n cdp.send(\"Runtime.evaluate\", {\n expression: \"location.href\",\n }) as Promise<{ result: { value?: string } }>,\n ]);\n\n return {\n title: titleResponse.result.value ?? \"\",\n url: urlResponse.result.value ?? \"\",\n };\n}\n\nfunction createTimeout(ms: number): Promise<never> {\n return new Promise((_resolve, reject) => {\n setTimeout(() => {\n reject(new Error(`Navigation timeout after ${ms}ms`));\n }, ms);\n });\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * browser_navigate_back — navigates back or forward in browser history.\n * Uses Page.getNavigationHistory + Page.navigateToHistoryEntry (session-compatible).\n */\nimport type { CDPConnection } from \"../cdp/connection\";\n\nexport interface NavigateBackParams {\n direction?: \"back\" | \"forward\";\n}\n\nexport interface NavigateBackResult {\n success: boolean;\n url?: string;\n}\n\nexport async function browserNavigateBack(\n cdp: CDPConnection,\n params: NavigateBackParams,\n): Promise<NavigateBackResult> {\n const direction = params.direction ?? \"back\";\n\n const history = (await cdp.send(\"Page.getNavigationHistory\")) as {\n currentIndex: number;\n entries: Array<{ id: number; url: string; title: string }>;\n };\n\n const targetIndex = direction === \"back\"\n ? history.currentIndex - 1\n : history.currentIndex + 1;\n\n if (targetIndex < 0 || targetIndex >= history.entries.length) {\n return { success: false, url: history.entries[history.currentIndex]?.url };\n }\n\n const entry = history.entries[targetIndex]!;\n await cdp.send(\"Page.navigateToHistoryEntry\", { entryId: entry.id } as unknown as Record<string, unknown>);\n\n return { success: true, url: entry.url };\n}\n","/**\n * browser_scroll tool — Scrolls the page or a specific element.\n *\n * Supports directional scrolling (up/down/left/right) by pixel amount,\n * scrolling to an element via selector, and scrolling within a\n * scrollable container.\n */\nimport type { CDPConnection } from \"../cdp/connection\";\n\n// ---------------------------------------------------------------------------\n// Parameter types\n// ---------------------------------------------------------------------------\n\ninterface ScrollParams {\n /** Direction to scroll: \"up\" | \"down\" | \"left\" | \"right\" */\n direction?: \"up\" | \"down\" | \"left\" | \"right\";\n /** Number of pixels to scroll. Defaults to 300. */\n amount?: number;\n /** CSS selector of an element to scroll into view, or a scrollable container. */\n selector?: string;\n}\n\ninterface ScrollResult {\n success: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_SCROLL_AMOUNT = 300;\n\n/**\n * Resolves a CSS selector to a Runtime objectId.\n *\n * Uses Runtime.evaluate with document.querySelector. If the evaluate call\n * returns a node reference (objectId), uses that directly. Otherwise, falls\n * back to DOM.resolveNode with the evaluate result.\n *\n * @throws If the element cannot be found.\n */\nasync function resolveSelector(\n cdp: CDPConnection,\n selector: string,\n): Promise<{ objectId: string }> {\n // Use Runtime.evaluate to find the element\n const evalResult = (await cdp.send(\"Runtime.evaluate\", {\n expression: `document.querySelector(${JSON.stringify(selector)})`,\n returnByValue: false,\n })) as {\n result: {\n type: string;\n subtype?: string;\n objectId?: string;\n value?: unknown;\n };\n };\n\n // If we got a proper node reference with objectId, use it\n if (evalResult.result.objectId && evalResult.result.subtype !== \"null\") {\n return { objectId: evalResult.result.objectId };\n }\n\n // If we got an explicit null, the element doesn't exist\n if (\n evalResult.result.subtype === \"null\" ||\n evalResult.result.value === null\n ) {\n throw new Error(`Element not found: ${selector}`);\n }\n\n // For non-null results without objectId (e.g., in mocked environments),\n // try DOM.resolveNode as a fallback resolution strategy.\n try {\n const resolveResponse = (await cdp.send(\"DOM.resolveNode\", {\n backendNodeId: undefined,\n })) as { object?: { objectId: string } };\n\n if (resolveResponse.object?.objectId) {\n return { objectId: resolveResponse.object.objectId };\n }\n } catch {\n // Fall through\n }\n\n throw new Error(`Could not find element: ${selector}`);\n}\n\n// ---------------------------------------------------------------------------\n// Main export\n// ---------------------------------------------------------------------------\n\n/**\n * Scrolls the page or an element.\n *\n * When `selector` is provided without a direction, scrolls the element into view.\n * When `selector` is provided with a direction, scrolls within that container.\n * When only direction is provided, scrolls the page viewport.\n */\nexport async function browserScroll(\n cdp: CDPConnection,\n params: ScrollParams,\n): Promise<ScrollResult> {\n const { direction, selector } = params;\n const amount = params.amount ?? DEFAULT_SCROLL_AMOUNT;\n\n // Case 1: Scroll element into view (selector without direction)\n if (selector && !direction) {\n const { objectId } = await resolveSelector(cdp, selector);\n\n // Use Runtime.callFunctionOn with scrollIntoView\n await cdp.send(\"Runtime.callFunctionOn\", {\n objectId,\n functionDeclaration: `function() { this.scrollIntoView({ behavior: \"smooth\", block: \"center\" }); }`,\n returnByValue: true,\n });\n\n return { success: true };\n }\n\n // Case 2: Scroll within a specific container\n if (selector && direction) {\n const { objectId } = await resolveSelector(cdp, selector);\n\n const scrollX = direction === \"left\" ? -amount : direction === \"right\" ? amount : 0;\n const scrollY = direction === \"up\" ? -amount : direction === \"down\" ? amount : 0;\n\n await cdp.send(\"Runtime.callFunctionOn\", {\n objectId,\n functionDeclaration: `function() { this.scrollBy(${scrollX}, ${scrollY}); }`,\n returnByValue: true,\n });\n\n return { success: true };\n }\n\n // Case 3: Scroll the page viewport\n const scrollX = direction === \"left\" ? -amount : direction === \"right\" ? amount : 0;\n const scrollY = direction === \"up\" ? -amount : direction === \"down\" ? amount : 0;\n\n await cdp.send(\"Runtime.evaluate\", {\n expression: `window.scrollBy(${scrollX}, ${scrollY})`,\n returnByValue: true,\n });\n\n return { success: true };\n}\n","/**\n * browser_wait_for tool — waits for various conditions via CDP polling.\n *\n * Supported strategies:\n * - text: poll until text appears in page body\n * - textGone: poll until text disappears from page body\n * - selector: poll until a CSS selector matches an element in the DOM\n * - selector + visible: poll until element is visible\n * - selector + state:\"hidden\": poll until element is hidden\n * - time: simple delay (seconds)\n * - networkIdle: poll until no pending network requests\n * - load: poll until document.readyState === \"complete\"\n * - url: poll until location.href matches a glob pattern\n * - fn: poll until a JS expression evaluates to truthy\n *\n * Default timeout: 30 seconds. Poll interval: ~100ms.\n */\nimport type { CDPConnection } from \"../cdp/connection\";\n\ninterface WaitForParams {\n text?: string;\n textGone?: string;\n selector?: string;\n visible?: boolean;\n state?: \"hidden\" | \"visible\";\n time?: number;\n networkIdle?: boolean;\n load?: boolean;\n loadState?: string;\n url?: string;\n fn?: string;\n timeout?: number;\n}\n\ninterface WaitForResult {\n success: boolean;\n elapsed: number;\n}\n\nconst DEFAULT_TIMEOUT_S = 30;\nconst POLL_INTERVAL_MS = 100;\n\n/**\n * Normalizes a timeout value to milliseconds.\n *\n * Values <= 60 are treated as seconds (e.g., 5 → 5000ms, 0.5 → 500ms).\n * Values > 60 are treated as milliseconds (e.g., 1000 → 1000ms).\n */\nfunction normalizeTimeoutMs(timeout: number): number {\n if (timeout > 60) {\n return timeout; // Already in ms\n }\n return timeout * 1000; // Convert seconds to ms\n}\n\nexport async function browserWaitFor(\n cdp: CDPConnection,\n params: WaitForParams,\n): Promise<WaitForResult> {\n const timeoutMs = normalizeTimeoutMs(params.timeout ?? DEFAULT_TIMEOUT_S);\n const start = Date.now();\n\n // ---- Simple time delay ----\n if (params.time !== undefined) {\n const delayMs = params.time * 1000;\n await delay(delayMs);\n return { success: true, elapsed: Date.now() - start };\n }\n\n // ---- Determine the polling predicate ----\n const condition = buildCondition(params);\n\n // ---- Poll loop ----\n while (true) {\n const elapsed = Date.now() - start;\n if (elapsed >= timeoutMs) {\n throw new Error(\n `Timeout after ${timeoutMs}ms waiting for condition: ${describeCondition(params)}`,\n );\n }\n\n let met = false;\n try {\n met = await evaluateCondition(cdp, condition);\n } catch {\n // Transient errors (e.g. cross-origin frame) — retry on next poll\n }\n\n if (met) {\n return { success: true, elapsed: Date.now() - start };\n }\n\n await delay(POLL_INTERVAL_MS);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Condition builders\n// ---------------------------------------------------------------------------\n\ninterface Condition {\n kind:\n | \"text\"\n | \"textGone\"\n | \"selector\"\n | \"selectorVisible\"\n | \"selectorHidden\"\n | \"networkIdle\"\n | \"load\"\n | \"loadState\"\n | \"url\"\n | \"fn\";\n expression: string;\n}\n\nfunction buildCondition(params: WaitForParams): Condition {\n if (params.url !== undefined) {\n return { kind: \"url\", expression: params.url };\n }\n\n if (params.fn !== undefined) {\n return {\n kind: \"fn\",\n expression: `Boolean(${params.fn})`,\n };\n }\n\n if (params.selector !== undefined && params.state === \"hidden\") {\n return {\n kind: \"selectorHidden\",\n expression: buildVisibilityCheck(params.selector),\n };\n }\n\n if (params.selector !== undefined && params.visible) {\n return {\n kind: \"selectorVisible\",\n expression: buildVisibilityCheck(params.selector),\n };\n }\n\n if (params.selector !== undefined) {\n return {\n kind: \"selector\",\n expression: `document.querySelector(${JSON.stringify(params.selector)})`,\n };\n }\n\n if (params.text !== undefined) {\n return {\n kind: \"text\",\n expression: `document.body && document.body.innerText.includes(${JSON.stringify(params.text)})`,\n };\n }\n\n if (params.textGone !== undefined) {\n return {\n kind: \"textGone\",\n expression: `document.body && !document.body.innerText.includes(${JSON.stringify(params.textGone)})`,\n };\n }\n\n if (params.networkIdle) {\n return {\n kind: \"networkIdle\",\n expression: \"true\", // Simplified: check via Runtime.evaluate\n };\n }\n\n if (params.loadState !== undefined) {\n return {\n kind: \"loadState\",\n expression: params.loadState,\n };\n }\n\n if (params.load) {\n return {\n kind: \"load\",\n expression: \"document.readyState\",\n };\n }\n\n throw new Error(\"browserWaitFor: no wait condition specified\");\n}\n\nfunction buildVisibilityCheck(selector: string): string {\n const sel = JSON.stringify(selector);\n return `(function() {\n var el = document.querySelector(${sel});\n if (!el) return false;\n var style = window.getComputedStyle(el);\n return style.display !== 'none' && style.visibility !== 'hidden' && el.offsetParent !== null;\n })()`;\n}\n\n// ---------------------------------------------------------------------------\n// Evaluation\n// ---------------------------------------------------------------------------\n\nasync function evaluateCondition(\n cdp: CDPConnection,\n condition: Condition,\n): Promise<boolean> {\n if (condition.kind === \"url\") {\n return evaluateUrlCondition(cdp, condition.expression);\n }\n\n if (condition.kind === \"load\") {\n return evaluateLoadCondition(cdp, condition.expression);\n }\n\n if (condition.kind === \"loadState\") {\n return evaluateLoadStateCondition(cdp, condition.expression);\n }\n\n if (condition.kind === \"selectorHidden\") {\n // Returns true when the element is NOT visible\n const response = (await cdp.send(\"Runtime.evaluate\", {\n expression: condition.expression,\n returnByValue: true,\n })) as { result: { type: string; value: unknown } };\n\n // Element is hidden when the visibility check returns false\n return response.result.value === false;\n }\n\n if (condition.kind === \"selector\") {\n const response = (await cdp.send(\"Runtime.evaluate\", {\n expression: condition.expression,\n returnByValue: true,\n })) as { result: { type: string; value: unknown; subtype?: string } };\n\n // Element found if result is not null\n return (\n response.result.value !== null && response.result.subtype !== \"null\"\n );\n }\n\n // Generic boolean evaluation\n const response = (await cdp.send(\"Runtime.evaluate\", {\n expression: condition.expression,\n returnByValue: true,\n })) as { result: { type: string; value: unknown } };\n\n return response.result.value === true;\n}\n\nasync function evaluateLoadCondition(\n cdp: CDPConnection,\n expression: string,\n): Promise<boolean> {\n const response = (await cdp.send(\"Runtime.evaluate\", {\n expression,\n returnByValue: true,\n })) as { result: { type: string; value: unknown } };\n\n // readyState is a string: \"loading\" | \"interactive\" | \"complete\"\n // Also handle the case where the expression evaluates to a boolean\n if (response.result.type === \"string\") {\n return response.result.value === \"complete\";\n }\n return response.result.value === true;\n}\n\n/**\n * Evaluates a loadState condition.\n * For \"complete\", readyState must be \"complete\".\n * For \"interactive\", readyState must be \"interactive\" or \"complete\".\n */\nasync function evaluateLoadStateCondition(\n cdp: CDPConnection,\n targetState: string,\n): Promise<boolean> {\n const response = (await cdp.send(\"Runtime.evaluate\", {\n expression: \"document.readyState\",\n returnByValue: true,\n })) as { result: { type: string; value: string } };\n\n const current = response.result.value;\n\n if (targetState === \"complete\") {\n return current === \"complete\";\n }\n if (targetState === \"interactive\") {\n return current === \"interactive\" || current === \"complete\";\n }\n // Exact match for any other value\n return current === targetState;\n}\n\nasync function evaluateUrlCondition(\n cdp: CDPConnection,\n pattern: string,\n): Promise<boolean> {\n const response = (await cdp.send(\"Runtime.evaluate\", {\n expression: \"location.href\",\n returnByValue: true,\n })) as { result: { type: string; value: string } };\n\n const currentUrl = response.result.value;\n return globMatch(pattern, currentUrl);\n}\n\n// ---------------------------------------------------------------------------\n// Glob matching (minimal implementation for URL patterns)\n// ---------------------------------------------------------------------------\n\nfunction globMatch(pattern: string, value: string): boolean {\n // Convert glob pattern to regex:\n // ** → match anything (including /)\n // * → match anything except /\n // ? → match single char\n const regexStr = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\") // escape regex chars (not * and ?)\n .replace(/\\*\\*/g, \"\\u0000\") // placeholder for **\n .replace(/\\*/g, \"[^/]*\") // * matches non-slash\n .replace(/\\u0000/g, \".*\") // ** matches anything\n .replace(/\\?/g, \".\"); // ? matches one char\n\n const regex = new RegExp(regexStr);\n return regex.test(value);\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction describeCondition(params: WaitForParams): string {\n if (params.text) return `text \"${params.text}\" to appear`;\n if (params.textGone) return `text \"${params.textGone}\" to disappear`;\n if (params.selector && params.state === \"hidden\")\n return `selector \"${params.selector}\" to become hidden`;\n if (params.selector && params.visible)\n return `selector \"${params.selector}\" to become visible`;\n if (params.selector) return `selector \"${params.selector}\" to appear`;\n if (params.url) return `URL matching \"${params.url}\"`;\n if (params.fn) return `JS condition: ${params.fn}`;\n if (params.loadState) return `document.readyState === \"${params.loadState}\"`;\n if (params.networkIdle) return \"network idle\";\n if (params.load) return \"page load complete\";\n return \"unknown condition\";\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * browser_close tool — closes browser tabs/targets via CDP.\n *\n * Supports:\n * - Close all page targets (closeAll)\n * - Close a specific target by targetId\n * - Close the current active tab (default)\n *\n * Returns the count of closed targets.\n */\nimport type { CDPConnection } from \"../cdp/connection\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface CloseParams {\n /** Specific target ID to close. */\n targetId?: string;\n /** Whether to force close (skip beforeunload). */\n force?: boolean;\n /** Close all page targets. */\n closeAll?: boolean;\n}\n\nexport interface CloseResult {\n success: boolean;\n closedTargets: number;\n}\n\n// ---------------------------------------------------------------------------\n// Types for CDP responses\n// ---------------------------------------------------------------------------\n\ninterface TargetInfo {\n targetId: string;\n type: string;\n title: string;\n url: string;\n attached?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Main implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Closes browser tabs/targets.\n *\n * - closeAll: closes all page-type targets\n * - targetId: closes that specific target\n * - default: closes the current active tab (attached page target, or first page)\n *\n * @param cdp - CDP connection with send/on/off methods.\n * @param params - Close parameters.\n * @returns Result with success status and count of closed targets.\n */\nexport async function browserClose(\n cdp: CDPConnection,\n params: CloseParams,\n): Promise<CloseResult> {\n let closedCount = 0;\n\n if (params.closeAll) {\n // Close all page targets\n const targetsResponse = (await cdp.send(\"Target.getTargets\")) as {\n targetInfos: TargetInfo[];\n };\n\n const pageTargets = targetsResponse.targetInfos.filter(\n (t) => t.type === \"page\",\n );\n\n for (const target of pageTargets) {\n try {\n await cdp.send(\"Target.closeTarget\", {\n targetId: target.targetId,\n } as unknown as Record<string, unknown>);\n closedCount++;\n } catch {\n // Target may already be closed — ignore\n }\n }\n } else if (params.targetId) {\n // Close a specific target\n await cdp.send(\"Target.closeTarget\", {\n targetId: params.targetId,\n } as unknown as Record<string, unknown>);\n closedCount = 1;\n } else {\n // Close the current active tab\n const targetsResponse = (await cdp.send(\"Target.getTargets\")) as {\n targetInfos: TargetInfo[];\n };\n\n const pageTargets = targetsResponse.targetInfos.filter(\n (t) => t.type === \"page\",\n );\n\n if (pageTargets.length === 0) {\n throw new Error(\"No page targets found to close\");\n }\n\n // Find the attached (active) page target, or fall back to the first page\n const activeTarget =\n pageTargets.find((t) => t.attached) ?? pageTargets[0];\n\n await cdp.send(\"Target.closeTarget\", {\n targetId: activeTarget.targetId,\n } as unknown as Record<string, unknown>);\n closedCount = 1;\n }\n\n return {\n success: true,\n closedTargets: closedCount,\n };\n}\n","/**\n * browser_tabs tool — Lists open browser tabs via CDP Target.getTargets.\n *\n * Filters to page-type targets only, with optional URL pattern matching.\n */\nimport type { CDPConnection } from \"../cdp/connection\";\n\nexport interface TabInfo {\n id: string;\n title: string;\n url: string;\n}\n\nexport interface BrowserTabsParams {\n /** Glob-style URL filter pattern (e.g. \"*github.com*\") */\n filter?: string;\n}\n\nexport interface BrowserTabsResult {\n tabs: TabInfo[];\n}\n\n/**\n * Converts a simple glob pattern (with `*` wildcards) to a RegExp.\n */\nfunction globToRegExp(pattern: string): RegExp {\n const escaped = pattern.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const regexStr = escaped.replace(/\\*/g, \".*\");\n return new RegExp(`^${regexStr}$`, \"i\");\n}\n\n/**\n * Lists all open browser tabs (page-type targets).\n *\n * @param cdp - CDP connection\n * @param params - Optional filter parameters\n * @returns List of tabs with id, title, and url\n */\nexport async function browserTabs(\n cdp: CDPConnection,\n params: BrowserTabsParams = {},\n): Promise<BrowserTabsResult> {\n const response = (await cdp.send(\"Target.getTargets\")) as {\n targetInfos: Array<{\n targetId: string;\n type: string;\n title: string;\n url: string;\n attached: boolean;\n }>;\n };\n\n // Filter to page-type targets only (exclude service workers, extensions, etc.)\n let tabs = response.targetInfos.filter(\n (target) => target.type === \"page\",\n );\n\n // Apply URL pattern filter if specified\n if (params.filter) {\n const regex = globToRegExp(params.filter);\n tabs = tabs.filter((target) => regex.test(target.url));\n }\n\n return {\n tabs: tabs.map((target) => ({\n id: target.targetId,\n title: target.title,\n url: target.url,\n })),\n };\n}\n","/**\n * browser_resize tool — Resizes the browser viewport.\n *\n * Supports:\n * - Explicit width/height dimensions\n * - Named presets: \"mobile\", \"tablet\", \"desktop\", \"fullhd\"\n * - Custom device scale factor\n * - Uses Emulation.setDeviceMetricsOverride\n */\nimport type { CDPConnection } from \"../cdp/connection\";\n\n// ---------------------------------------------------------------------------\n// Presets\n// ---------------------------------------------------------------------------\n\nconst PRESETS: Record<string, { width: number; height: number }> = {\n mobile: { width: 375, height: 667 },\n tablet: { width: 768, height: 1024 },\n desktop: { width: 1280, height: 720 },\n fullhd: { width: 1920, height: 1080 },\n};\n\n// ---------------------------------------------------------------------------\n// Parameter types\n// ---------------------------------------------------------------------------\n\nexport interface ResizeParams {\n /** Viewport width in CSS pixels */\n width?: number;\n /** Viewport height in CSS pixels */\n height?: number;\n /** Device scale factor (DPR). Defaults to 0 (use browser default). */\n deviceScaleFactor?: number;\n /** Named preset: \"mobile\", \"tablet\", \"desktop\", \"fullhd\". */\n preset?: string;\n}\n\nexport interface ResizeResult {\n success: boolean;\n width: number;\n height: number;\n}\n\n// ---------------------------------------------------------------------------\n// Main export\n// ---------------------------------------------------------------------------\n\n/**\n * Resizes the browser viewport by overriding device metrics.\n *\n * If a preset is given, uses preset dimensions as defaults.\n * Explicit width/height params override preset values.\n * Sets mobile emulation when width < 768.\n */\nexport async function browserResize(\n cdp: CDPConnection,\n params: ResizeParams,\n): Promise<ResizeResult> {\n let width: number;\n let height: number;\n\n // Handle \"reset\" preset — clears device metrics override\n if (params.preset?.toLowerCase() === \"reset\") {\n await cdp.send(\"Emulation.clearDeviceMetricsOverride\");\n return { success: true, width: 0, height: 0 };\n }\n\n // Start with preset dimensions if provided\n if (params.preset) {\n const preset = PRESETS[params.preset.toLowerCase()];\n if (!preset) {\n throw new Error(\n `Unknown preset \"${params.preset}\". Available: ${Object.keys(PRESETS).join(\", \")}, reset`,\n );\n }\n width = preset.width;\n height = preset.height;\n } else {\n // Default to desktop if no preset and no dimensions\n width = params.width ?? 1280;\n height = params.height ?? 720;\n }\n\n // Explicit width/height override preset values\n if (params.width !== undefined) width = params.width;\n if (params.height !== undefined) height = params.height;\n\n const deviceScaleFactor = params.deviceScaleFactor ?? 0;\n const mobile = width < 768;\n\n await cdp.send(\"Emulation.setDeviceMetricsOverride\", {\n width,\n height,\n deviceScaleFactor,\n mobile,\n });\n\n return {\n success: true,\n width,\n height,\n };\n}\n","/**\n * Navigation & lifecycle CLI commands for browsirai.\n *\n * Commands: navigate, back, scroll, wait, tabs, close, resize\n *\n * Each command wraps the corresponding tool function from src/tools/,\n * parsing CLI args into the expected params and printing human-readable output.\n */\n\nimport type { CLICommand } from \"../types.js\";\nimport { parseFlags, printResult } from \"../run.js\";\nimport { browserNavigate } from \"../../tools/browser-navigate.js\";\nimport { browserNavigateBack } from \"../../tools/browser-navigate-back.js\";\nimport { browserScroll } from \"../../tools/browser-scroll.js\";\nimport { browserWaitFor } from \"../../tools/browser-wait-for.js\";\nimport { browserClose } from \"../../tools/browser-close.js\";\nimport { browserTabs } from \"../../tools/browser-tabs.js\";\nimport { browserResize } from \"../../tools/browser-resize.js\";\n\n// ---------------------------------------------------------------------------\n// navigate\n// ---------------------------------------------------------------------------\n\nconst navigate: CLICommand = {\n name: \"navigate\",\n aliases: [\"open\", \"goto\"],\n description: \"Navigate the browser to a URL\",\n usage: \"browsirai open <url> [--waitUntil=load]\",\n async run(cdp, args) {\n const flags = parseFlags(args);\n const url = flags._0 ?? flags.url;\n\n if (!url) {\n console.error(\"Usage: browsirai open <url> [--waitUntil=load]\");\n console.error(\" Provide a URL as the first argument or via --url=...\");\n process.exit(1);\n }\n\n // Auto-prefix protocol if missing (like agent-browser)\n const fullUrl = /^https?:\\/\\//i.test(url) ? url : `https://${url}`;\n\n try {\n const result = await browserNavigate(cdp, {\n url: fullUrl,\n waitUntil: flags.waitUntil as \"load\" | \"domcontentloaded\" | \"networkidle\" | undefined,\n timeout: flags.timeout ? Number(flags.timeout) : undefined,\n });\n console.log(`Navigated to ${result.url}`);\n if (result.title) {\n console.log(` Title: ${result.title}`);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Navigate failed: ${msg}`);\n process.exit(1);\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// back\n// ---------------------------------------------------------------------------\n\nconst back: CLICommand = {\n name: \"back\",\n description: \"Navigate back or forward in browser history\",\n usage: \"browsirai back [--direction=back]\",\n async run(cdp, args) {\n const flags = parseFlags(args);\n const direction = (flags._0 ?? flags.direction ?? \"back\") as \"back\" | \"forward\";\n\n try {\n const result = await browserNavigateBack(cdp, { direction });\n if (result.success) {\n console.log(`Navigated ${direction}`);\n if (result.url) {\n console.log(` URL: ${result.url}`);\n }\n } else {\n console.log(`Cannot navigate ${direction} — no history entry`);\n if (result.url) {\n console.log(` Current URL: ${result.url}`);\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Navigate ${direction} failed: ${msg}`);\n process.exit(1);\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// scroll\n// ---------------------------------------------------------------------------\n\nconst scroll: CLICommand = {\n name: \"scroll\",\n description: \"Scroll the page in a direction\",\n usage: \"browsirai scroll <direction> [--pixels=300] [--selector=...]\",\n async run(cdp, args) {\n const flags = parseFlags(args);\n const direction = (flags._0 ?? flags.direction ?? \"down\") as\n | \"up\"\n | \"down\"\n | \"left\"\n | \"right\";\n const amount = flags.pixels ? Number(flags.pixels) : (flags.amount ? Number(flags.amount) : 300);\n const selector = flags.selector;\n\n try {\n await browserScroll(cdp, {\n direction,\n amount,\n selector,\n });\n console.log(`Scrolled ${direction} ${amount}px`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Scroll failed: ${msg}`);\n process.exit(1);\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// wait\n// ---------------------------------------------------------------------------\n\nconst wait: CLICommand = {\n name: \"wait\",\n description: \"Wait for a condition on the page\",\n usage: \"browsirai wait [--text=...] [--selector=...] [--url=...] [--fn=...] [--time=N] [--timeout=30]\",\n async run(cdp, args) {\n const flags = parseFlags(args);\n const timeout = flags.timeout ? Number(flags.timeout) : undefined;\n\n // Build params from flags\n const params: Record<string, unknown> = {};\n if (flags.text !== undefined) params.text = flags.text;\n if (flags.selector !== undefined) params.selector = flags.selector;\n if (flags.url !== undefined) params.url = flags.url;\n if (flags.fn !== undefined) params.fn = flags.fn;\n if (flags.time !== undefined) params.time = Number(flags.time);\n if (flags.visible !== undefined) params.visible = flags.visible === \"true\";\n if (flags.state !== undefined) params.state = flags.state;\n if (flags.networkIdle !== undefined) params.networkIdle = flags.networkIdle === \"true\";\n if (flags.load !== undefined) params.load = flags.load === \"true\";\n if (timeout !== undefined) params.timeout = timeout;\n\n // Use first positional arg as a shorthand condition if no flags given\n const hasCondition = Object.keys(params).some((k) => k !== \"timeout\");\n if (!hasCondition && flags._0) {\n // Treat positional arg as text to wait for\n params.text = flags._0;\n }\n\n if (!hasCondition && !flags._0) {\n console.error(\"Usage: browsirai wait [--text=...] [--selector=...] [--url=...] [--fn=...] [--time=N]\");\n console.error(\" Provide at least one condition to wait for.\");\n process.exit(1);\n }\n\n try {\n const result = await browserWaitFor(cdp, params as any);\n console.log(`Condition met (${result.elapsed}ms)`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Wait failed: ${msg}`);\n process.exit(1);\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// tabs\n// ---------------------------------------------------------------------------\n\nconst tabs: CLICommand = {\n name: \"tab\",\n aliases: [\"tabs\"],\n description: \"List open browser tabs\",\n usage: \"browsirai tab [--filter=*github*]\",\n async run(cdp, args) {\n const flags = parseFlags(args);\n const filter = flags._0 ?? flags.filter;\n\n try {\n const result = await browserTabs(cdp, { filter });\n if (result.tabs.length === 0) {\n console.log(\"No tabs found\");\n return;\n }\n const lines = result.tabs.map(\n (t) => `[${t.id}] ${t.title}\\n ${t.url}`,\n );\n printResult(lines.join(\"\\n\\n\"));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Tabs failed: ${msg}`);\n process.exit(1);\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// close\n// ---------------------------------------------------------------------------\n\nconst close: CLICommand = {\n name: \"close\",\n description: \"Close browser tab(s)\",\n usage: \"browsirai close [--force] [--targetId=...] [--closeAll]\",\n async run(cdp, args) {\n const flags = parseFlags(args);\n\n try {\n const result = await browserClose(cdp, {\n force: flags.force === \"true\",\n targetId: flags.targetId,\n closeAll: flags.closeAll === \"true\",\n });\n if (result.success) {\n console.log(`Closed ${result.closedTargets} tab(s)`);\n } else {\n console.log(\"Close failed — no targets matched\");\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Close failed: ${msg}`);\n process.exit(1);\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// resize\n// ---------------------------------------------------------------------------\n\nconst resize: CLICommand = {\n name: \"resize\",\n description: \"Resize the browser viewport\",\n usage: \"browsirai resize <width> <height> [--preset=mobile]\",\n async run(cdp, args) {\n const flags = parseFlags(args);\n const preset = flags.preset;\n\n // If preset is given, use it; otherwise parse width/height from positional args\n const width = flags._0 ? Number(flags._0) : (flags.width ? Number(flags.width) : undefined);\n const height = flags._1 ? Number(flags._1) : (flags.height ? Number(flags.height) : undefined);\n const deviceScaleFactor = flags.deviceScaleFactor\n ? Number(flags.deviceScaleFactor)\n : undefined;\n\n if (!preset && width === undefined) {\n console.error(\"Usage: browsirai resize <width> <height> [--preset=mobile]\");\n console.error(\" Provide dimensions or a preset (mobile, tablet, desktop, fullhd, reset).\");\n process.exit(1);\n }\n\n try {\n const result = await browserResize(cdp, {\n width,\n height,\n preset,\n deviceScaleFactor,\n });\n if (preset?.toLowerCase() === \"reset\") {\n console.log(\"Viewport reset to browser defaults\");\n } else {\n console.log(`Viewport resized to ${result.width}x${result.height}`);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Resize failed: ${msg}`);\n process.exit(1);\n }\n },\n};\n\n// ---------------------------------------------------------------------------\n// Export\n// ---------------------------------------------------------------------------\n\nexport const navCommands: CLICommand[] = [\n navigate,\n back,\n scroll,\n wait,\n tabs,\n close,\n resize,\n];\n"],"mappings":"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","names":["scrollX","scrollY","POLL_INTERVAL_MS","delay","response","tabs"]}