/** * bb-browser CLI 入口 */ import { fileURLToPath } from "node:url"; import { openCommand } from "./commands/open.js"; import { snapshotCommand } from "./commands/snapshot.js"; import { clickCommand } from "./commands/click.js"; import { hoverCommand } from "./commands/hover.js"; import { fillCommand } from "./commands/fill.js"; import { typeCommand } from "./commands/type.js"; import { closeCommand } from "./commands/close.js"; import { getCommand, type GetAttribute } from "./commands/get.js"; import { screenshotCommand } from "./commands/screenshot.js"; import { waitCommand } from "./commands/wait.js"; import { pressCommand } from "./commands/press.js"; import { scrollCommand } from "./commands/scroll.js"; import { backCommand, forwardCommand, refreshCommand } from "./commands/nav.js"; import { checkCommand, uncheckCommand } from "./commands/check.js"; import { selectCommand } from "./commands/select.js"; import { evalCommand } from "./commands/eval.js"; import { tabCommand } from "./commands/tab.js"; import { frameCommand, frameMainCommand } from "./commands/frame.js"; import { dialogCommand } from "./commands/dialog.js"; import { networkCommand } from "./commands/network.js"; import { consoleCommand } from "./commands/console.js"; import { errorsCommand } from "./commands/errors.js"; import { traceCommand } from "./commands/trace.js"; import { fetchCommand } from "./commands/fetch.js"; import { siteCommand } from "./commands/site.js"; import { historyCommand } from "./commands/history.js"; import { shutdownCommand, statusCommand } from "./commands/daemon.js"; import { getDaemonPath } from "./daemon-manager.js"; import { setJqExpression } from "./client.js"; declare const __BB_BROWSER_VERSION__: string; const VERSION = __BB_BROWSER_VERSION__; const HELP_TEXT = ` bb-browser - AI Agent 浏览器自动化工具 安装: npm install -g bb-browser 提示:大多数数据获取任务请直接使用 site 命令,无需手动操作浏览器: bb-browser site list 查看所有可用命令 bb-browser site twitter/search "AI" 示例:搜索推文 bb-browser site xueqiu/hot-stock 5 示例:获取人气股票 用法: bb-browser [options] 开始使用: site recommend 推荐你可能需要的 adapter(基于浏览历史) site list 列出所有 adapter site info 查看 adapter 用法(参数、返回值、示例) site [args] 运行 adapter site update 更新社区 adapter 库 guide 如何把任何网站变成 adapter star ⭐ Star bb-browser on GitHub 浏览器操作: open [--tab] 打开 URL snapshot [-i] [-c] [-d ] 获取页面快照 click 点击元素 hover 悬停元素 fill 填充输入框(清空后填入) type 逐字符输入(不清空) check/uncheck 勾选/取消复选框 select 下拉框选择 press 发送按键 scroll [px] 滚动页面 页面信息: get text|url|title 获取页面内容 screenshot [path] 截图 eval "" 执行 JavaScript fetch 带登录态的 HTTP 请求 标签页: tab [list|new|close|] 管理标签页 status 查看受管浏览器状态 导航: back / forward / refresh 后退 / 前进 / 刷新 调试: network requests [filter] 查看网络请求 console [--clear] 查看/清空控制台 errors [--clear] 查看/清空 JS 错误 trace start|stop|status 录制用户操作 history search|domains 查看浏览历史 daemon [status|stop] [opts] 前台运行或管理 daemon 选项: --json 以 JSON 格式输出 --port 指定 Chrome CDP 端口 --openclaw 优先复用 OpenClaw 浏览器实例 --jq 对 JSON 输出应用 jq 过滤(直接作用于数据,跳过 id/success 信封) -i, --interactive 只输出可交互元素(snapshot 命令) -c, --compact 移除空结构节点(snapshot 命令) -d, --depth 限制树深度(snapshot 命令) -s, --selector 限定 CSS 选择器范围(snapshot 命令) --tab 指定操作的标签页 ID --mcp 启动 MCP server(用于 Claude Code / Cursor 等 AI 工具) --help, -h 显示帮助信息 --version, -v 显示版本号 `.trim(); interface ParsedArgs { command: string | null; args: string[]; flags: { json: boolean; help: boolean; version: boolean; interactive: boolean; compact: boolean; depth?: number; selector?: string; tab?: string; days?: number; jq?: string; openclaw?: boolean; port?: number; since?: string; }; } /** * 解析命令行参数 */ function parseArgs(argv: string[]): ParsedArgs { const args = argv.slice(2); // 跳过 node 和脚本路径 const result: ParsedArgs = { command: null, args: [], flags: { json: false, help: false, version: false, interactive: false, compact: false, }, }; let skipNext = false; for (const arg of args) { if (skipNext) { skipNext = false; continue; } if (arg === "--json") { result.flags.json = true; } else if (arg === "--jq") { skipNext = true; const nextIdx = args.indexOf(arg) + 1; if (nextIdx < args.length) { result.flags.jq = args[nextIdx]; result.flags.json = true; } } else if (arg === "--openclaw") { result.flags.openclaw = true; } else if (arg === "--port") { skipNext = true; const nextIdx = args.indexOf(arg) + 1; if (nextIdx < args.length) { result.flags.port = parseInt(args[nextIdx], 10); } } else if (arg === "--help" || arg === "-h") { result.flags.help = true; } else if (arg === "--version" || arg === "-v") { result.flags.version = true; } else if (arg === "--interactive" || arg === "-i") { result.flags.interactive = true; } else if (arg === "--compact" || arg === "-c") { result.flags.compact = true; } else if (arg === "--depth" || arg === "-d") { skipNext = true; const nextIdx = args.indexOf(arg) + 1; if (nextIdx < args.length) { result.flags.depth = parseInt(args[nextIdx], 10); } } else if (arg === "--selector" || arg === "-s") { skipNext = true; const nextIdx = args.indexOf(arg) + 1; if (nextIdx < args.length) { result.flags.selector = args[nextIdx]; } } else if (arg === "--days") { skipNext = true; const nextIdx = args.indexOf(arg) + 1; if (nextIdx < args.length) { result.flags.days = parseInt(args[nextIdx], 10); } } else if (arg === "--id") { // --id 及其值由子命令通过 process.argv 自行解析,这里跳过 skipNext = true; } else if (arg === "--tab") { // --tab 参数及其值,无论出现在命令前后都跳过 skipNext = true; } else if (arg === "--since") { // --since 参数及其值,无论出现在命令前后都跳过 skipNext = true; } else if (arg === "--method") { // --method 参数及其值,由子命令通过 process.argv 解析 skipNext = true; } else if (arg === "--status") { // --status 参数及其值,由子命令通过 process.argv 解析 skipNext = true; } else if (arg.startsWith("-")) { // 未知选项,忽略 } else if (result.command === null) { result.command = arg; } else { result.args.push(arg); } } return result; } /** * 主函数 */ async function main(): Promise { const parsed = parseArgs(process.argv); setJqExpression(parsed.flags.jq); // 解析全局 --tab 参数 const tabArgIdx = process.argv.indexOf('--tab'); const globalTabId = tabArgIdx >= 0 && process.argv[tabArgIdx + 1] ? process.argv[tabArgIdx + 1] : undefined; // 解析全局 --since 参数 const sinceArgIdx = process.argv.indexOf('--since'); const globalSince = sinceArgIdx >= 0 && process.argv[sinceArgIdx + 1] ? process.argv[sinceArgIdx + 1] : undefined; // 处理全局选项 if (parsed.flags.version) { console.log(VERSION); return; } if (process.argv.includes("--mcp")) { const mcpPath = fileURLToPath(new URL("./mcp.js", import.meta.url)); const { spawn } = await import("node:child_process"); const child = spawn(process.execPath, [mcpPath], { stdio: "inherit" }); child.on("exit", (code) => process.exit(code ?? 0)); return; } if (!parsed.command) { console.log(HELP_TEXT); return; } if (parsed.flags.help && parsed.command !== "daemon") { console.log(HELP_TEXT); return; } // 路由到对应命令 try { switch (parsed.command) { case "open": { const url = parsed.args[0]; if (!url) { console.error("错误:缺少 URL 参数"); console.error("用法:bb-browser open [--tab current|]"); process.exit(1); } // 解析 --tab 参数 const tabIndex = process.argv.findIndex(a => a === "--tab"); const tab = tabIndex >= 0 ? process.argv[tabIndex + 1] : undefined; await openCommand(url, { json: parsed.flags.json, tab }); break; } case "snapshot": { await snapshotCommand({ json: parsed.flags.json, interactive: parsed.flags.interactive, compact: parsed.flags.compact, maxDepth: parsed.flags.depth, selector: parsed.flags.selector, tabId: globalTabId, }); break; } case "click": { const ref = parsed.args[0]; if (!ref) { console.error("错误:缺少 ref 参数"); console.error("用法:bb-browser click "); console.error("示例:bb-browser click @5"); process.exit(1); } await clickCommand(ref, { json: parsed.flags.json, tabId: globalTabId }); break; } case "hover": { const ref = parsed.args[0]; if (!ref) { console.error("错误:缺少 ref 参数"); console.error("用法:bb-browser hover "); console.error("示例:bb-browser hover @5"); process.exit(1); } await hoverCommand(ref, { json: parsed.flags.json, tabId: globalTabId }); break; } case "check": { const ref = parsed.args[0]; if (!ref) { console.error("错误:缺少 ref 参数"); console.error("用法:bb-browser check "); console.error("示例:bb-browser check @5"); process.exit(1); } await checkCommand(ref, { json: parsed.flags.json, tabId: globalTabId }); break; } case "uncheck": { const ref = parsed.args[0]; if (!ref) { console.error("错误:缺少 ref 参数"); console.error("用法:bb-browser uncheck "); console.error("示例:bb-browser uncheck @5"); process.exit(1); } await uncheckCommand(ref, { json: parsed.flags.json, tabId: globalTabId }); break; } case "fill": { const ref = parsed.args[0]; const text = parsed.args[1]; if (!ref) { console.error("错误:缺少 ref 参数"); console.error("用法:bb-browser fill "); console.error('示例:bb-browser fill @3 "hello world"'); process.exit(1); } if (text === undefined) { console.error("错误:缺少 text 参数"); console.error("用法:bb-browser fill "); console.error('示例:bb-browser fill @3 "hello world"'); process.exit(1); } await fillCommand(ref, text, { json: parsed.flags.json, tabId: globalTabId }); break; } case "type": { const ref = parsed.args[0]; const text = parsed.args[1]; if (!ref) { console.error("错误:缺少 ref 参数"); console.error("用法:bb-browser type "); console.error('示例:bb-browser type @3 "append text"'); process.exit(1); } if (text === undefined) { console.error("错误:缺少 text 参数"); console.error("用法:bb-browser type "); console.error('示例:bb-browser type @3 "append text"'); process.exit(1); } await typeCommand(ref, text, { json: parsed.flags.json, tabId: globalTabId }); break; } case "select": { const ref = parsed.args[0]; const value = parsed.args[1]; if (!ref) { console.error("错误:缺少 ref 参数"); console.error("用法:bb-browser select "); console.error('示例:bb-browser select @4 "option1"'); process.exit(1); } if (value === undefined) { console.error("错误:缺少 value 参数"); console.error("用法:bb-browser select "); console.error('示例:bb-browser select @4 "option1"'); process.exit(1); } await selectCommand(ref, value, { json: parsed.flags.json, tabId: globalTabId }); break; } case "eval": { const script = parsed.args[0]; if (!script) { console.error("错误:缺少 script 参数"); console.error("用法:bb-browser eval