@opentiny/webmcp-cli 0.0.1-alpha.1 → 0.0.1-alpha.2

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.
package/dist/bin.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bin.ts","../src/browser.ts","../src/commands/state.ts","../src/commands/run.ts","../src/commands/open.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander'\nimport pc from 'picocolors'\nimport { stateCommand } from './commands/state'\nimport { runCommand } from './commands/run'\nimport { openCommand } from './commands/open'\n\nconst program = new Command()\n\nfunction parseTabId(id?: string): string | undefined {\n if (!id) return undefined\n return id\n}\n\nprogram\n .name('webmcp-cli')\n .description('WebMCP CLI for interacting with browser via CDP')\n .version('1.0.0')\n .option('-w, --workspace <path>', '指定自定义的浏览器工作空间(用户配置目录)路径')\n .hook('preAction', (thisCommand) => {\n const opts = thisCommand.opts()\n if (opts.workspace) {\n process.env.WEBMCP_WORKSPACE = opts.workspace\n }\n })\n\nprogram\n .command('state')\n .description('获取浏览器当前页签或指定页签的状态(内容、所有页签列表、可用 WebMCP 工具列表)')\n .option('-t, --tabid <id>', '指定页签的 ID')\n .action(async (options) => {\n try {\n const result = await stateCommand({ tabid: parseTabId(options.tabid) })\n console.log(JSON.stringify(result, null, 2))\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n console.error(pc.red(`Error executing state command: ${msg}`))\n process.exit(1)\n }\n })\n\nprogram\n .command('run <toolName> <argsJson>')\n .description('向指定页签调用指定的 WebMCP 工具执行操作')\n .option('-t, --tabid <id>', '指定页签的 ID')\n .action(async (toolName, argsJson, options) => {\n try {\n const result = await runCommand({\n toolName,\n argsJson,\n tabid: parseTabId(options.tabid)\n })\n console.log(JSON.stringify(result, null, 2))\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n console.error(pc.red(`Error executing run command: ${msg}`))\n process.exit(1)\n }\n })\n\nprogram\n .command('open <url>')\n .description('在当前浏览器中打开指定网页')\n .option('-t, --tabid <id>', '在指定页签中打开')\n .option('-n, --new-tab', '在一个全新的页签中打开')\n .action(async (url, options) => {\n try {\n const result = await openCommand(url, {\n tabid: parseTabId(options.tabid),\n newTab: options.newTab\n })\n console.log(JSON.stringify(result, null, 2))\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n console.error(pc.red(`Error executing open command: ${msg}`))\n process.exit(1)\n }\n })\n\nprogram.parse(process.argv)\n","import puppeteer, { Browser, Page } from 'puppeteer-core'\nimport pc from 'picocolors'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { spawn } from 'child_process'\nimport { fileURLToPath } from 'url'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\n\nconst CDP_PORT = 9222\n// 使用 localhost 以兼容 IPv4/IPv6 绑定\nconst CDP_URL = `http://localhost:${CDP_PORT}`\n\nasync function fetchWithTimeout(url: string, timeoutMs = 1500): Promise<Response> {\n const controller = new AbortController()\n const id = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await fetch(url, { signal: controller.signal })\n } finally {\n clearTimeout(id)\n }\n}\n\nfunction promiseWithTimeout<T>(promise: Promise<T>, timeoutMs = 5000, errorMsg = 'Operation timed out'): Promise<T> {\n let timeoutId: NodeJS.Timeout\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(errorMsg))\n }, timeoutMs)\n })\n \n return Promise.race([promise, timeoutPromise]).finally(() => {\n clearTimeout(timeoutId)\n })\n}\n\nasync function checkCdpReady(url: string, retries = 3): Promise<boolean> {\n for (let i = 0; i < retries; i++) {\n try {\n const res = await fetchWithTimeout(url, 1500)\n if (res.ok) return true\n } catch {}\n if (i < retries - 1) {\n await new Promise(r => setTimeout(r, 200))\n }\n }\n return false\n}\n\nasync function killProcessOnPortIfZombie(port: number): Promise<void> {\n // 先检测端口是否还在正常响应 HTTP 请求\n const isResponding = await checkCdpReady(`http://127.0.0.1:${port}/json/version`, 1)\n if (isResponding) {\n console.log(pc.green(`connectBrowser: 端口 ${port} 上的浏览器实例仍在正常响应,跳过强杀,尝试直接接管。`))\n return\n }\n\n try {\n const platform = os.platform()\n const { execSync } = require('child_process')\n if (platform === 'darwin' || platform === 'linux') {\n console.log(pc.yellow(`正在检测并清理占用 ${port} 端口的残留僵尸进程...`))\n const pids = execSync(`lsof -t -i :${port}`).toString().trim()\n if (pids) {\n console.log(pc.yellow(`发现僵尸 PID: ${pids.split('\\n').join(', ')},正在强制终止...`))\n execSync(`kill -9 ${pids.split('\\n').join(' ')}`)\n console.log(pc.green(`成功清理残留僵尸进程`))\n }\n } else if (platform === 'win32') {\n console.log(pc.yellow(`正在检测并清理 Windows 上占用 ${port} 端口的残留僵尸进程...`))\n const output = execSync(`netstat -ano | findstr :${port}`).toString().trim()\n if (output) {\n const lines = output.split('\\n')\n const pids = new Set<string>()\n lines.forEach((line: string) => {\n const parts = line.trim().split(/\\s+/)\n const pid = parts[parts.length - 1]\n if (pid && /^\\d+$/.test(pid) && pid !== '0') {\n pids.add(pid)\n }\n })\n pids.forEach(pid => {\n console.log(pc.yellow(`发现 Windows 残留僵尸 PID: ${pid},正在强制终止...`))\n execSync(`taskkill /F /PID ${pid}`)\n })\n }\n }\n } catch (e) {\n // 忽略找不到残留进程时的报错\n }\n}\n\nfunction getDefaultChromePath(): string | null {\n const platform = os.platform()\n if (platform === 'darwin') {\n return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'\n } else if (platform === 'win32') {\n const paths = [\n process.env.LOCALAPPDATA + '\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n process.env.PROGRAMFILES + '\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n process.env['PROGRAMFILES(X86)'] + '\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe'\n ]\n return paths.find(p => fs.existsSync(p)) || null\n } else {\n // Linux\n const paths = ['/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/usr/bin/chromium', '/usr/bin/chromium-browser']\n return paths.find(p => fs.existsSync(p)) || null\n }\n}\n\nasync function startChromeInBackground(): Promise<void> {\n const chromePath = getDefaultChromePath()\n if (!chromePath || !fs.existsSync(chromePath)) {\n throw new Error('无法在系统中找到 Chrome 浏览器的默认安装路径。')\n }\n\n console.log(pc.yellow(`正在启动后台 Chrome 实例 (端口: ${CDP_PORT})...`))\n \n // 用户可以通过 --workspace CLI 选项或 WEBMCP_WORKSPACE 环境变量自定义。\n const userDataDir = process.env.WEBMCP_WORKSPACE || path.join(os.homedir(), '.webmcp_chrome_profile')\n \n const child = spawn(\n chromePath,\n [\n `--remote-debugging-port=${CDP_PORT}`,\n `--user-data-dir=${userDataDir}`,\n '--no-first-run',\n '--no-default-browser-check'\n ],\n {\n detached: true,\n stdio: 'ignore'\n }\n )\n\n child.unref() // 让子进程脱离父进程独立运行\n\n // 轮询等待 CDP 端口就绪\n for (let i = 0; i < 20; i++) {\n try {\n // 尝试 127.0.0.1 和 localhost,兼容不同 Node 版本的 fetch 行为\n const urls = [`http://localhost:${CDP_PORT}/json/version`, `http://127.0.0.1:${CDP_PORT}/json/version`]\n for (const url of urls) {\n try {\n const response = await fetchWithTimeout(url, 1000)\n if (response.ok) {\n console.log(pc.green('Chrome 启动并就绪。'))\n return\n }\n } catch (err) {}\n }\n } catch (e) {\n // 忽略连接错误,继续重试\n }\n await new Promise(resolve => setTimeout(resolve, 500))\n }\n\n throw new Error('Chrome 启动超时,无法连接到 CDP 端口。')\n}\n\nexport async function connectBrowser(): Promise<Browser> {\n const targetFilter = (target: any) => {\n try {\n const info = typeof target._getTargetInfo === 'function' ? target._getTargetInfo() : target\n const type = info.type || ''\n const url = info.url || ''\n \n // 过滤掉绝对不需要 attach 且容易发生死锁的后台/子框架 target\n if (\n type === 'service_worker' || \n type === 'shared_worker' || \n type === 'iframe' || \n type === 'other' || \n type === 'webview' ||\n type === 'background_page'\n ) {\n return false\n }\n \n // 过滤掉 devtools 和插件页面\n if (url.startsWith('devtools://') || url.startsWith('chrome-extension://')) {\n return false\n }\n \n return true\n } catch (e) {\n return false\n }\n }\n\n try {\n const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3)\n if (!is127Ready) {\n throw new Error('127.0.0.1 CDP port not responding')\n }\n console.log(pc.yellow('connectBrowser: 正在尝试连接 127.0.0.1:9222...'))\n // 优先尝试通过 127.0.0.1 连接\n const browser = await promiseWithTimeout(\n puppeteer.connect({\n browserURL: `http://127.0.0.1:${CDP_PORT}`,\n defaultViewport: null,\n targetFilter,\n }),\n 10000,\n 'puppeteer.connect to 127.0.0.1 timed out'\n )\n console.log(pc.green('connectBrowser: 成功连接 127.0.0.1:9222'))\n return browser\n } catch (error: unknown) {\n try {\n const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3)\n if (!isLocalhostReady) {\n throw new Error('localhost CDP port not responding')\n }\n console.log(pc.yellow('connectBrowser: 正在尝试连接 localhost:9222...'))\n // 尝试使用 localhost 连接\n const browser = await promiseWithTimeout(\n puppeteer.connect({\n browserURL: `http://localhost:${CDP_PORT}`,\n defaultViewport: null,\n targetFilter,\n }),\n 10000,\n 'puppeteer.connect to localhost timed out'\n )\n console.log(pc.green('connectBrowser: 成功连接 localhost:9222'))\n return browser\n } catch (error2: unknown) {\n console.log(pc.yellow(`connectBrowser: 连接失败,将尝试唤起浏览器。错误原因: ${error2 instanceof Error ? error2.message : String(error2)}`))\n // 连接失败时,尝试唤起浏览器\n try {\n await killProcessOnPortIfZombie(CDP_PORT)\n await startChromeInBackground()\n // 再次尝试连接\n try {\n const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3)\n if (!is127Ready) {\n throw new Error('127.0.0.1 CDP port not responding after launch')\n }\n console.log(pc.yellow('connectBrowser: 浏览器已启动,正在尝试连接 127.0.0.1:9222...'))\n const browser = await promiseWithTimeout(\n puppeteer.connect({\n browserURL: `http://127.0.0.1:${CDP_PORT}`,\n defaultViewport: null,\n targetFilter,\n }),\n 10000,\n 'puppeteer.connect to 127.0.0.1 after launch timed out'\n )\n console.log(pc.green('connectBrowser: 成功连接 127.0.0.1:9222'))\n return browser\n } catch (e) {\n const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3)\n if (!isLocalhostReady) {\n throw new Error('localhost CDP port not responding after launch')\n }\n console.log(pc.yellow('connectBrowser: 正在尝试连接 localhost:9222...'))\n const browser = await promiseWithTimeout(\n puppeteer.connect({\n browserURL: `http://localhost:${CDP_PORT}`,\n defaultViewport: null,\n targetFilter,\n }),\n 10000,\n 'puppeteer.connect to localhost after launch timed out'\n )\n console.log(pc.green('connectBrowser: 成功连接 localhost:9222'))\n return browser\n }\n } catch (launchError: unknown) {\n const msg = launchError instanceof Error ? launchError.message : String(launchError)\n console.error(pc.red(`无法连接或启动浏览器: ${msg}`))\n console.error(pc.yellow(`💡 提示:由于我们要使用你日常的默认浏览器(包含你的书签 and 登录态),如果你的 Chrome 目前正处于打开状态,它会拒绝使用带有调试端口的新参数启动。`))\n console.error(pc.yellow(`👉 解决办法:请先完全退出当前的 Chrome 浏览器(在 Mac 上按 Cmd+Q),然后再重新运行命令。`))\n throw new Error('Browser connection failed.')\n }\n }\n }\n}\n\n\n/**\n * 通过 CDP 获取页面真实的 Chrome target ID(UUID 格式字符串)\n * 供 state.ts 等命令展示 tabs 列表时使用\n */\nexport async function getPageTargetId(page: Page): Promise<string> {\n const session = await page.target().createCDPSession()\n try {\n const { targetInfo } = await session.send('Target.getTargetInfo')\n return targetInfo.targetId\n } finally {\n await session.detach().catch(() => {})\n }\n}\n\nexport async function getTargetPage(browser: Browser, tabid?: string): Promise<Page> {\n const targets = browser.targets()\n const pageTargets = targets.filter(t => {\n try {\n const type = (typeof t.type === 'function' ? t.type() : (t as any).type) || ''\n const url = (typeof t.url === 'function' ? t.url() : (t as any).url) || ''\n return type === 'page' && !url.startsWith('devtools://')\n } catch {\n return false\n }\n })\n\n if (pageTargets.length === 0) {\n const newPage = await browser.newPage()\n await injectWebMCPPolyfillAndTools(newPage)\n return newPage\n }\n\n let targetPage: Page | null = null\n\n if (tabid !== undefined) {\n // 按真实 Chrome target ID 查找\n for (const target of pageTargets) {\n const tid = typeof (target as any)._getTargetInfo === 'function'\n ? (target as any)._getTargetInfo().targetId\n : ((target as any)._targetId || (target as any).targetId || '')\n if (tid === tabid || tid.includes(tabid)) {\n targetPage = await target.page()\n break\n }\n }\n if (!targetPage) {\n throw new Error(`Tab with targetId \"${tabid}\" not found.`)\n }\n } else {\n // Chrome 的 /json/list 接口把当前激活 the tab 排在第一位,用它来判断激活 tab\n try {\n const urls = [\n `http://localhost:${CDP_PORT}/json/list`,\n `http://127.0.0.1:${CDP_PORT}/json/list`\n ]\n let activeTargetId: string | null = null\n for (const url of urls) {\n try {\n const res = await fetchWithTimeout(url, 1000)\n if (res.ok) {\n const targetsData: Array<{ id: string; type: string; url: string }> = await res.json()\n // 找第一个 type=page 且不是 devtools:// 的 target(Chrome 把激活的排第一)\n const active = targetsData.find(t => t.type === 'page' && !t.url.startsWith('devtools://'))\n if (active) { activeTargetId = active.id; break }\n }\n } catch { /* 忽略,继续试下一个地址 */ }\n }\n\n if (activeTargetId) {\n for (const target of pageTargets) {\n const tid = typeof (target as any)._getTargetInfo === 'function'\n ? (target as any)._getTargetInfo().targetId\n : ((target as any)._targetId || (target as any).targetId || '')\n if (tid === activeTargetId) {\n targetPage = await target.page()\n break\n }\n }\n }\n } catch { /* 忽略,使用 fallback */ }\n\n // fallback:取最后一个非 devtools 页面\n if (!targetPage) {\n const lastTarget = pageTargets[pageTargets.length - 1]\n targetPage = await lastTarget.page()\n }\n }\n\n if (!targetPage) {\n throw new Error('无法获取目标页面')\n }\n\n // 注入 polyfill 和域名工具(幂等检查)\n await injectWebMCPPolyfillAndTools(targetPage)\n return targetPage\n}\n\n/**\n * 供 open 命令在 goto 完成后调用:强制注入(不做 flag 检查,因为 goto 后页面上下文已清空)\n */\nexport async function injectIntoPage(page: Page): Promise<void> {\n await injectWebMCPPolyfillAndTools(page, true)\n}\n\nasync function injectWebMCPPolyfillAndTools(page: Page, force = false) {\n // 检查 polyfill 是否已注入(force=true 时跳过,用于 goto 之后的强制重注入)\n const polyfillReady = !force && await page.evaluate(() => {\n return !!(window as any).__webmcpcli_init\n }).catch(() => false)\n\n if (!polyfillReady) {\n console.log(pc.cyan('当前页面尚未注入 WebMCP 环境,正在执行自动注入...'))\n\n const injectScriptPath = path.resolve(__dirname, 'inject-bundle.js')\n if (!fs.existsSync(injectScriptPath)) {\n throw new Error(`Cannot find inject-bundle.js at ${injectScriptPath}. Please ensure you run 'pnpm build:inject' first.`)\n }\n\n const scriptContent = fs.readFileSync(injectScriptPath, 'utf-8')\n\n // 注入 WebMCP polyfill\n try {\n await page.evaluate(scriptContent)\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error('自动注入脚本执行失败: ' + msg)\n }\n\n // 等待工具异步注册\n await new Promise(resolve => setTimeout(resolve, 300))\n }\n\n // 无论 polyfill 是否刚注入,都检查域名工具(工具内部有防重复 flag)\n await injectDomainTools(page)\n}\n\n/**\n * 根据页面域名查找并注入对应的工具 bundle\n * bundle 文件位于 dist/webmcp-tools/{hostname}.js\n */\nasync function injectDomainTools(page: Page): Promise<void> {\n let hostname: string\n try {\n const url = new URL(page.url())\n hostname = url.hostname\n } catch {\n return // 非 http(s) 页面,跳过\n }\n\n const toolBundlePath = path.resolve(__dirname, 'webmcp-tools', `${hostname}.js`)\n if (!fs.existsSync(toolBundlePath)) {\n return // 没有对应的工具预置,跳过\n }\n\n console.log(pc.cyan(`检测到域名 ${hostname} 有预置工具,正在注入...`))\n\n const toolScript = fs.readFileSync(toolBundlePath, 'utf-8')\n try {\n await page.evaluate(toolScript)\n console.log(pc.green(`已为 ${hostname} 注入预置工具`))\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err)\n // 工具注入失败不阻断主流程,仅打印警告\n console.warn(pc.yellow(`域名工具注入失败 (${hostname}): ${msg}`))\n }\n}\n","import { connectBrowser, getTargetPage, getPageTargetId } from '../browser'\n\nexport async function stateCommand({ tabid }: { tabid?: string }) {\n const browser = await connectBrowser()\n try {\n const page = await getTargetPage(browser, tabid)\n\n // 在页面上下文中执行,获取当前状态和可用工具\n const state = await page.evaluate(async () => {\n const url = document.URL\n const title = document.title\n\n // 尝试获取内置工具\n const mcp = (navigator as any).modelContextTesting || (navigator as any).modelContext\n let webmcpTools: any[] = []\n let contentData: any = `页面已准备好: ${title}`\n \n if (mcp) {\n if (typeof mcp.listTools === 'function') {\n const toolsResult = await mcp.listTools()\n webmcpTools = toolsResult?.tools || toolsResult || []\n }\n\n if (typeof mcp.executeTool === 'function') {\n try {\n const argsString = JSON.stringify({ action: 'browserState' })\n let stateRes = await mcp.executeTool('page-agent-tool', argsString)\n \n if (typeof stateRes === 'string') {\n try {\n stateRes = JSON.parse(stateRes)\n } catch (e) {\n // ignore\n }\n }\n if (stateRes && stateRes.content && stateRes.content.length > 0) {\n const textContent = stateRes.content.map((c: any) => c.text).join('\\\\n')\n \n // page-agent-tool 返回的格式通常是 \"浏览器状态: {\\\"url\\\":..., \\\"content\\\":\\\"[0]...\\\"}\"\n // 我们尝试把这个 JSON 提取出来,让外层更容易解析\n const prefix = '浏览器状态: '\n if (textContent.startsWith(prefix)) {\n try {\n const jsonStr = textContent.substring(prefix.length)\n const parsedState = JSON.parse(jsonStr)\n // 提取出带有索引的 DOM 树数据作为 content\n contentData = parsedState.content\n } catch (e) {\n contentData = textContent\n }\n } else {\n contentData = textContent\n }\n }\n } catch (e: any) {\n console.error('Snapshot error:', e.message)\n }\n }\n }\n\n return {\n content: contentData,\n url,\n title,\n webmcpTools\n }\n })\n\n // 获取所有的 tab 信息(排除 devtools:// 内部页面)\n const pages = await browser.pages()\n const tabs = await Promise.all(pages.map(async (p) => {\n const pUrl = p.url()\n if (pUrl.startsWith('devtools://')) return null\n\n const pTitle = await Promise.race([\n p.title().catch(() => 'Unknown'),\n new Promise<string>(resolve => setTimeout(() => resolve('Unknown'), 500))\n ])\n\n return {\n // 使用真实的 Chrome target ID,而非数组下标\n tabid: await getPageTargetId(p).catch(() => pUrl),\n title: pTitle,\n url: pUrl\n }\n }))\n\n return {\n ...state,\n tabs: tabs.filter(Boolean)\n }\n } finally {\n await browser.disconnect()\n }\n}\n","import { connectBrowser, getTargetPage } from '../browser'\n\nexport async function runCommand({\n toolName,\n argsJson,\n tabid\n}: {\n toolName: string\n argsJson: string\n tabid?: string\n}) {\n const browser = await connectBrowser()\n try {\n const page = await getTargetPage(browser, tabid)\n\n // 验证一下是否是合法的 JSON,以防用户传入了非法的字符串\n try {\n JSON.parse(argsJson)\n } catch (e: any) {\n throw new Error(`参数不是有效的 JSON: ${e.message}`)\n }\n\n const result = await page.evaluate(async (name, inputString) => {\n const mcp = (navigator as any).modelContextTesting || (navigator as any).modelContext\n \n if (!mcp || typeof mcp.executeTool !== 'function') {\n throw new Error('当前页面没有注入 WebMCP 环境 (navigator.modelContextTesting.executeTool 未找到)')\n }\n\n // executeTool 的第二个参数必须是 JSON 字符串\n let res = await mcp.executeTool(name, inputString)\n \n // executeTool 的返回值可能是普通对象,也可能是 JSON 字符串\n if (typeof res === 'string') {\n try {\n res = JSON.parse(res)\n } catch (e) {\n // ignore\n }\n }\n return res\n }, toolName, argsJson)\n\n return result\n } finally {\n await browser.disconnect()\n }\n}\n","import { connectBrowser, getTargetPage, injectIntoPage } from '../browser'\nimport pc from 'picocolors'\n\nexport async function openCommand(url: string, { tabid, newTab }: { tabid?: string, newTab?: boolean }) {\n const browser = await connectBrowser()\n try {\n let page;\n if (newTab) {\n console.log(pc.yellow('openCommand: 正在创建新页面...'))\n page = await browser.newPage()\n } else {\n console.log(pc.yellow('openCommand: 获取浏览器 targets...'))\n const targets = browser.targets()\n const pageTargets = targets.filter(t => {\n try {\n const type = (typeof t.type === 'function' ? t.type() : (t as any).type) || ''\n const urlStr = (typeof t.url === 'function' ? t.url() : (t as any).url) || ''\n return type === 'page' && !urlStr.startsWith('devtools://')\n } catch {\n return false\n }\n })\n console.log(pc.yellow(`openCommand: 普通页面 targets 数量: ${pageTargets.length}`))\n\n let selectedTarget;\n if (tabid !== undefined) {\n console.log(pc.yellow(`openCommand: 正在匹配指定的 tabid: ${tabid}...`))\n selectedTarget = pageTargets.find(t => {\n const tid = typeof (t as any)._getTargetInfo === 'function'\n ? (t as any)._getTargetInfo().targetId\n : ((t as any)._targetId || (t as any).targetId || '')\n return tid === tabid || tid.includes(tabid)\n })\n }\n\n if (selectedTarget) {\n console.log(pc.yellow('openCommand: 正在将匹配的 target 转换为 page...'))\n page = await selectedTarget.page()\n } else if (pageTargets.length > 0) {\n console.log(pc.yellow('openCommand: 正在将最后一个 target 转换为 page...'))\n page = await pageTargets[pageTargets.length - 1].page()\n }\n\n if (!page) {\n console.log(pc.yellow('openCommand: 没有找到可用 page,正在创建新页面...'))\n page = await browser.newPage()\n }\n }\n\n // 格式化 URL,补全协议\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\n url = 'https://' + url\n }\n\n console.log(pc.cyan(`正在打开: ${url}`))\n // 先导航到目标页面\n await page.goto(url, { waitUntil: 'domcontentloaded' })\n\n // 导航完成后再注入 polyfill + 域名工具(此时 hostname 已是目标域名)\n await injectIntoPage(page)\n\n return {\n success: true,\n url: page.url(),\n title: await page.title()\n }\n } finally {\n await browser.disconnect()\n }\n}\n"],"mappings":"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","names":["import_picocolors","path","pc","os","fs","puppeteer","import_picocolors","pc","pc"]}
1
+ {"version":3,"sources":["../src/bin.ts","../src/browser.ts","../src/commands/state.ts","../src/commands/run.ts","../src/commands/tabs.ts","../package.json"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander'\nimport pc from 'picocolors'\nimport { stateCommand } from './commands/state'\nimport { runCommand } from './commands/run'\nimport {\n tabsOpenCommand,\n tabsCloseCommand,\n tabsSwitchCommand,\n tabsBackCommand,\n tabsForwardCommand\n} from './commands/tabs'\nimport packageJson from '../package.json'\n\nconst program = new Command()\n\nfunction parseTabId(id?: string): string | undefined {\n if (!id) return undefined\n return id\n}\n\nfunction handleCommandError(error: unknown, commandName: string): never {\n const msg = error instanceof Error ? error.message : String(error)\n console.error(pc.red(`Error executing ${commandName} command: ${msg}`))\n process.exit(1)\n}\n\nprogram\n .name('webmcp-cli')\n .description('WebMCP CLI for interacting with browser via CDP')\n .version(packageJson.version)\n .option('-w, --workspace <path>', '指定自定义的浏览器工作空间(用户配置目录)路径')\n .hook('preAction', (thisCommand) => {\n const opts = thisCommand.opts()\n if (opts.workspace) {\n process.env.WEBMCP_WORKSPACE = opts.workspace\n }\n })\n\nprogram\n .command('state')\n .description('获取浏览器当前页签或指定页签的状态(内容、所有页签列表、可用 WebMCP 工具列表)')\n .option('-t, --tabid <id>', '指定页签的 ID')\n .action(async (options) => {\n try {\n const result = await stateCommand({ tabid: parseTabId(options.tabid) })\n console.log(JSON.stringify(result, null, 2))\n } catch (error: unknown) {\n handleCommandError(error, 'state')\n }\n })\n\nprogram\n .command('run <toolName> <argsJson>')\n .description('向指定页签调用指定的 WebMCP 工具执行操作')\n .option('-t, --tabid <id>', '指定页签的 ID')\n .action(async (toolName, argsJson, options) => {\n try {\n const result = await runCommand({\n toolName,\n argsJson,\n tabid: parseTabId(options.tabid)\n })\n console.log(JSON.stringify(result, null, 2))\n } catch (error: unknown) {\n handleCommandError(error, 'run')\n }\n })\n\nconst tabs = program\n .command('tabs')\n .description('管理浏览器标签页')\n\ntabs\n .command('open <url>')\n .description('打开新网页')\n .action(async (url) => {\n try {\n const result = await tabsOpenCommand(url)\n console.log(JSON.stringify(result, null, 2))\n } catch (error: unknown) {\n handleCommandError(error, 'tabs open')\n }\n })\n\ntabs\n .command('close <tabid>')\n .description('关闭指定 tabid 的标签页')\n .action(async (tabid) => {\n try {\n const result = await tabsCloseCommand(tabid)\n console.log(JSON.stringify(result, null, 2))\n } catch (error: unknown) {\n handleCommandError(error, 'tabs close')\n }\n })\n\ntabs\n .command('switch <tabid>')\n .description('激活并切换到指定 tabid 的标签页')\n .action(async (tabid) => {\n try {\n const result = await tabsSwitchCommand(tabid)\n console.log(JSON.stringify(result, null, 2))\n } catch (error: unknown) {\n handleCommandError(error, 'tabs switch')\n }\n })\n\ntabs\n .command('back [tabid]')\n .description('将当前或指定标签页导航后退一步')\n .action(async (tabid) => {\n try {\n const result = await tabsBackCommand(parseTabId(tabid))\n console.log(JSON.stringify(result, null, 2))\n } catch (error: unknown) {\n handleCommandError(error, 'tabs back')\n }\n })\n\ntabs\n .command('forward [tabid]')\n .description('将当前或指定标签页导航前进一步')\n .action(async (tabid) => {\n try {\n const result = await tabsForwardCommand(parseTabId(tabid))\n console.log(JSON.stringify(result, null, 2))\n } catch (error: unknown) {\n handleCommandError(error, 'tabs forward')\n }\n })\n\nprogram.parse(process.argv)\n","import puppeteer, { Browser, Page } from 'puppeteer-core'\nimport pc from 'picocolors'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { spawn } from 'child_process'\nimport { fileURLToPath } from 'url'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\n\nconst CDP_PORT = 9222\n// 使用 localhost 以兼容 IPv4/IPv6 绑定\nconst CDP_URL = `http://localhost:${CDP_PORT}`\n\nasync function fetchWithTimeout(url: string, timeoutMs = 1500): Promise<Response> {\n const controller = new AbortController()\n const id = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await fetch(url, { signal: controller.signal })\n } finally {\n clearTimeout(id)\n }\n}\n\nfunction promiseWithTimeout<T>(promise: Promise<T>, timeoutMs = 5000, errorMsg = 'Operation timed out'): Promise<T> {\n let timeoutId: NodeJS.Timeout\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(errorMsg))\n }, timeoutMs)\n })\n \n return Promise.race([promise, timeoutPromise]).finally(() => {\n clearTimeout(timeoutId)\n })\n}\n\nasync function checkCdpReady(url: string, retries = 3): Promise<boolean> {\n for (let i = 0; i < retries; i++) {\n try {\n const res = await fetchWithTimeout(url, 1500)\n if (res.ok) return true\n } catch {}\n if (i < retries - 1) {\n await new Promise(r => setTimeout(r, 200))\n }\n }\n return false\n}\n\nasync function killProcessOnPortIfZombie(port: number): Promise<void> {\n // 先检测端口是否还在正常响应 HTTP 请求\n const isResponding = await checkCdpReady(`http://127.0.0.1:${port}/json/version`, 1)\n if (isResponding) {\n console.log(pc.green(`connectBrowser: 端口 ${port} 上的浏览器实例仍在正常响应,跳过强杀,尝试直接接管。`))\n return\n }\n\n try {\n const platform = os.platform()\n const { execSync } = require('child_process')\n if (platform === 'darwin' || platform === 'linux') {\n console.log(pc.yellow(`正在检测并清理占用 ${port} 端口的残留僵尸进程...`))\n const pids = execSync(`lsof -t -i :${port}`).toString().trim()\n if (pids) {\n console.log(pc.yellow(`发现僵尸 PID: ${pids.split('\\n').join(', ')},正在强制终止...`))\n execSync(`kill -9 ${pids.split('\\n').join(' ')}`)\n console.log(pc.green(`成功清理残留僵尸进程`))\n }\n } else if (platform === 'win32') {\n console.log(pc.yellow(`正在检测并清理 Windows 上占用 ${port} 端口的残留僵尸进程...`))\n const output = execSync(`netstat -ano | findstr :${port}`).toString().trim()\n if (output) {\n const lines = output.split('\\n')\n const pids = new Set<string>()\n lines.forEach((line: string) => {\n const parts = line.trim().split(/\\s+/)\n const pid = parts[parts.length - 1]\n if (pid && /^\\d+$/.test(pid) && pid !== '0') {\n pids.add(pid)\n }\n })\n pids.forEach(pid => {\n console.log(pc.yellow(`发现 Windows 残留僵尸 PID: ${pid},正在强制终止...`))\n execSync(`taskkill /F /PID ${pid}`)\n })\n }\n }\n } catch (e) {\n // 忽略找不到残留进程时的报错\n }\n}\n\nfunction getDefaultChromePath(): string | null {\n const platform = os.platform()\n if (platform === 'darwin') {\n return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'\n } else if (platform === 'win32') {\n const paths = [\n process.env.LOCALAPPDATA + '\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n process.env.PROGRAMFILES + '\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n process.env['PROGRAMFILES(X86)'] + '\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe'\n ]\n return paths.find(p => fs.existsSync(p)) || null\n } else {\n // Linux\n const paths = ['/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/usr/bin/chromium', '/usr/bin/chromium-browser']\n return paths.find(p => fs.existsSync(p)) || null\n }\n}\n\nasync function startChromeInBackground(): Promise<void> {\n const chromePath = getDefaultChromePath()\n if (!chromePath || !fs.existsSync(chromePath)) {\n throw new Error('无法在系统中找到 Chrome 浏览器的默认安装路径。')\n }\n\n console.log(pc.yellow(`正在启动后台 Chrome 实例 (端口: ${CDP_PORT})...`))\n \n // 用户可以通过 --workspace CLI 选项或 WEBMCP_WORKSPACE 环境变量自定义。\n const userDataDir = process.env.WEBMCP_WORKSPACE || path.join(os.homedir(), '.webmcp_chrome_profile')\n \n const child = spawn(\n chromePath,\n [\n `--remote-debugging-port=${CDP_PORT}`,\n `--user-data-dir=${userDataDir}`,\n '--no-first-run',\n '--no-default-browser-check'\n ],\n {\n detached: true,\n stdio: 'ignore'\n }\n )\n\n child.unref() // 让子进程脱离父进程独立运行\n\n // 轮询等待 CDP 端口就绪\n for (let i = 0; i < 20; i++) {\n try {\n // 尝试 127.0.0.1 和 localhost,兼容不同 Node 版本的 fetch 行为\n const urls = [`http://localhost:${CDP_PORT}/json/version`, `http://127.0.0.1:${CDP_PORT}/json/version`]\n for (const url of urls) {\n try {\n const response = await fetchWithTimeout(url, 1000)\n if (response.ok) {\n console.log(pc.green('Chrome 启动并就绪。'))\n return\n }\n } catch (err) {}\n }\n } catch (e) {\n // 忽略连接错误,继续重试\n }\n await new Promise(resolve => setTimeout(resolve, 500))\n }\n\n throw new Error('Chrome 启动超时,无法连接到 CDP 端口。')\n}\n\nexport async function connectBrowser(): Promise<Browser> {\n const targetFilter = (target: any) => {\n try {\n const info = typeof target._getTargetInfo === 'function' ? target._getTargetInfo() : target\n const type = info.type || ''\n const url = info.url || ''\n \n // 过滤掉绝对不需要 attach 且容易发生死锁的后台/子框架 target\n if (\n type === 'service_worker' || \n type === 'shared_worker' || \n type === 'iframe' || \n type === 'other' || \n type === 'webview' ||\n type === 'background_page'\n ) {\n return false\n }\n \n // 过滤掉 devtools 和插件页面\n if (url.startsWith('devtools://') || url.startsWith('chrome-extension://')) {\n return false\n }\n \n return true\n } catch (e) {\n return false\n }\n }\n\n try {\n const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3)\n if (!is127Ready) {\n throw new Error('127.0.0.1 CDP port not responding')\n }\n console.log(pc.yellow('connectBrowser: 正在尝试连接 127.0.0.1:9222...'))\n // 优先尝试通过 127.0.0.1 连接\n const browser = await promiseWithTimeout(\n puppeteer.connect({\n browserURL: `http://127.0.0.1:${CDP_PORT}`,\n defaultViewport: null,\n targetFilter,\n }),\n 10000,\n 'puppeteer.connect to 127.0.0.1 timed out'\n )\n console.log(pc.green('connectBrowser: 成功连接 127.0.0.1:9222'))\n return browser\n } catch (error: unknown) {\n try {\n const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3)\n if (!isLocalhostReady) {\n throw new Error('localhost CDP port not responding')\n }\n console.log(pc.yellow('connectBrowser: 正在尝试连接 localhost:9222...'))\n // 尝试使用 localhost 连接\n const browser = await promiseWithTimeout(\n puppeteer.connect({\n browserURL: `http://localhost:${CDP_PORT}`,\n defaultViewport: null,\n targetFilter,\n }),\n 10000,\n 'puppeteer.connect to localhost timed out'\n )\n console.log(pc.green('connectBrowser: 成功连接 localhost:9222'))\n return browser\n } catch (error2: unknown) {\n console.log(pc.yellow(`connectBrowser: 连接失败,将尝试唤起浏览器。错误原因: ${error2 instanceof Error ? error2.message : String(error2)}`))\n // 连接失败时,尝试唤起浏览器\n try {\n await killProcessOnPortIfZombie(CDP_PORT)\n await startChromeInBackground()\n // 再次尝试连接\n try {\n const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3)\n if (!is127Ready) {\n throw new Error('127.0.0.1 CDP port not responding after launch')\n }\n console.log(pc.yellow('connectBrowser: 浏览器已启动,正在尝试连接 127.0.0.1:9222...'))\n const browser = await promiseWithTimeout(\n puppeteer.connect({\n browserURL: `http://127.0.0.1:${CDP_PORT}`,\n defaultViewport: null,\n targetFilter,\n }),\n 10000,\n 'puppeteer.connect to 127.0.0.1 after launch timed out'\n )\n console.log(pc.green('connectBrowser: 成功连接 127.0.0.1:9222'))\n return browser\n } catch (e) {\n const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3)\n if (!isLocalhostReady) {\n throw new Error('localhost CDP port not responding after launch')\n }\n console.log(pc.yellow('connectBrowser: 正在尝试连接 localhost:9222...'))\n const browser = await promiseWithTimeout(\n puppeteer.connect({\n browserURL: `http://localhost:${CDP_PORT}`,\n defaultViewport: null,\n targetFilter,\n }),\n 10000,\n 'puppeteer.connect to localhost after launch timed out'\n )\n console.log(pc.green('connectBrowser: 成功连接 localhost:9222'))\n return browser\n }\n } catch (launchError: unknown) {\n const msg = launchError instanceof Error ? launchError.message : String(launchError)\n console.error(pc.red(`无法连接或启动浏览器: ${msg}`))\n console.error(pc.yellow(`💡 提示:由于我们要使用你日常的默认浏览器(包含你的书签 and 登录态),如果你的 Chrome 目前正处于打开状态,它会拒绝使用带有调试端口的新参数启动。`))\n console.error(pc.yellow(`👉 解决办法:请先完全退出当前的 Chrome 浏览器(在 Mac 上按 Cmd+Q),然后再重新运行命令。`))\n throw new Error('Browser connection failed.')\n }\n }\n }\n}\n\n\n/**\n * 通过 CDP 获取页面真实的 Chrome target ID(UUID 格式字符串)\n * 供 state.ts 等命令展示 tabs 列表时使用\n */\nexport async function getPageTargetId(page: Page): Promise<string> {\n const session = await page.target().createCDPSession()\n try {\n const { targetInfo } = await session.send('Target.getTargetInfo')\n return targetInfo.targetId\n } finally {\n await session.detach().catch(() => {})\n }\n}\n\nexport function getPageTargets(browser: Browser) {\n return browser.targets().filter(t => {\n try {\n const type = (typeof t.type === 'function' ? t.type() : (t as any).type) || ''\n const url = (typeof t.url === 'function' ? t.url() : (t as any).url) || ''\n return type === 'page' && !url.startsWith('devtools://')\n } catch {\n return false\n }\n })\n}\n\nexport function getTargetIdFromTarget(target: any): string {\n return typeof target._getTargetInfo === 'function'\n ? target._getTargetInfo().targetId\n : (target._targetId || target.targetId || '')\n}\n\nexport function findPageTargetByTabId(browser: Browser, tabid: string) {\n const pageTargets = getPageTargets(browser)\n return pageTargets.find(t => {\n const tid = getTargetIdFromTarget(t)\n return tid === tabid || tid.includes(tabid)\n }) ?? null\n}\n\nexport async function activateTabById(browser: Browser, tabid: string): Promise<void> {\n const target = findPageTargetByTabId(browser, tabid)\n if (!target) {\n throw new Error(`Tab with targetId \"${tabid}\" not found.`)\n }\n\n const realTabId = getTargetIdFromTarget(target)\n const pages = await browser.pages()\n let sessionPage = pages.find(p => !p.url().startsWith('devtools://'))\n if (!sessionPage && pages.length > 0) {\n sessionPage = pages[0]\n }\n if (!sessionPage) {\n sessionPage = await browser.newPage()\n }\n\n const session = await sessionPage.createCDPSession()\n try {\n await session.send('Target.activateTarget', { targetId: realTabId })\n } finally {\n await session.detach().catch(() => {})\n }\n}\n\nexport async function getTargetPage(browser: Browser, tabid?: string): Promise<Page> {\n const pageTargets = getPageTargets(browser)\n\n if (pageTargets.length === 0) {\n const newPage = await browser.newPage()\n await injectWebMCPPolyfillAndTools(newPage)\n return newPage\n }\n\n let targetPage: Page | null = null\n\n if (tabid !== undefined) {\n const target = findPageTargetByTabId(browser, tabid)\n if (!target) {\n throw new Error(`Tab with targetId \"${tabid}\" not found.`)\n }\n targetPage = await target.page()\n } else {\n // Chrome 的 /json/list 接口把当前激活 the tab 排在第一位,用它来判断激活 tab\n try {\n const urls = [\n `http://localhost:${CDP_PORT}/json/list`,\n `http://127.0.0.1:${CDP_PORT}/json/list`\n ]\n let activeTargetId: string | null = null\n for (const url of urls) {\n try {\n const res = await fetchWithTimeout(url, 1000)\n if (res.ok) {\n const targetsData: Array<{ id: string; type: string; url: string }> = await res.json()\n // 找第一个 type=page 且不是 devtools:// 的 target(Chrome 把激活的排第一)\n const active = targetsData.find(t => t.type === 'page' && !t.url.startsWith('devtools://'))\n if (active) { activeTargetId = active.id; break }\n }\n } catch { /* 忽略,继续试下一个地址 */ }\n }\n\n if (activeTargetId) {\n for (const target of pageTargets) {\n if (getTargetIdFromTarget(target) === activeTargetId) {\n targetPage = await target.page()\n break\n }\n }\n }\n } catch { /* 忽略,使用 fallback */ }\n\n // fallback:取最后一个非 devtools 页面\n if (!targetPage) {\n const lastTarget = pageTargets[pageTargets.length - 1]\n targetPage = await lastTarget.page()\n }\n }\n\n if (!targetPage) {\n throw new Error('无法获取目标页面')\n }\n\n // 注入 polyfill 和域名工具(幂等检查)\n await injectWebMCPPolyfillAndTools(targetPage)\n return targetPage\n}\n\n/**\n * 供 tabs open / back / forward 命令在导航完成后调用:强制注入(不做 flag 检查,因为 goto 后页面上下文已清空)\n */\nexport async function injectIntoPage(page: Page): Promise<void> {\n await injectWebMCPPolyfillAndTools(page, true)\n}\n\nasync function injectWebMCPPolyfillAndTools(page: Page, force = false) {\n // 检查 polyfill 是否已注入(force=true 时跳过,用于 goto 之后的强制重注入)\n const polyfillReady = !force && await page.evaluate(() => {\n return !!(window as any).__webmcpcli_init\n }).catch(() => false)\n\n if (!polyfillReady) {\n console.log(pc.cyan('当前页面尚未注入 WebMCP 环境,正在执行自动注入...'))\n\n const injectScriptPath = path.resolve(__dirname, 'inject-bundle.js')\n if (!fs.existsSync(injectScriptPath)) {\n throw new Error(`Cannot find inject-bundle.js at ${injectScriptPath}. Please ensure you run 'pnpm build:inject' first.`)\n }\n\n const scriptContent = fs.readFileSync(injectScriptPath, 'utf-8')\n\n // 注入 WebMCP polyfill\n try {\n await page.evaluate(scriptContent)\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error('自动注入脚本执行失败: ' + msg)\n }\n\n // 等待工具异步注册\n await new Promise(resolve => setTimeout(resolve, 300))\n }\n\n // 无论 polyfill 是否刚注入,都检查域名工具(工具内部有防重复 flag)\n await injectDomainTools(page)\n}\n\n/**\n * 根据页面域名查找并注入对应的工具 bundle\n * bundle 文件位于 dist/webmcp-tools/{hostname}.js\n */\nasync function injectDomainTools(page: Page): Promise<void> {\n let hostname: string\n try {\n const url = new URL(page.url())\n hostname = url.hostname\n } catch {\n return // 非 http(s) 页面,跳过\n }\n\n const toolBundlePath = path.resolve(__dirname, 'webmcp-tools', `${hostname}.js`)\n if (!fs.existsSync(toolBundlePath)) {\n return // 没有对应的工具预置,跳过\n }\n\n console.log(pc.cyan(`检测到域名 ${hostname} 有预置工具,正在注入...`))\n\n const toolScript = fs.readFileSync(toolBundlePath, 'utf-8')\n try {\n await page.evaluate(toolScript)\n console.log(pc.green(`已为 ${hostname} 注入预置工具`))\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err)\n // 工具注入失败不阻断主流程,仅打印警告\n console.warn(pc.yellow(`域名工具注入失败 (${hostname}): ${msg}`))\n }\n}\n","import { connectBrowser, getTargetPage, getPageTargetId } from '../browser'\n\nexport async function stateCommand({ tabid }: { tabid?: string }) {\n const browser = await connectBrowser()\n try {\n const page = await getTargetPage(browser, tabid)\n\n // 在页面上下文中执行,获取当前状态和可用工具\n const state = await page.evaluate(async () => {\n const url = document.URL\n const title = document.title\n\n // 尝试获取内置工具\n const mcp = (navigator as any).modelContextTesting || (navigator as any).modelContext\n let webmcpTools: any[] = []\n let contentData: any = `页面已准备好: ${title}`\n \n if (mcp) {\n if (typeof mcp.listTools === 'function') {\n const toolsResult = await mcp.listTools()\n webmcpTools = toolsResult?.tools || toolsResult || []\n }\n\n if (typeof mcp.executeTool === 'function') {\n try {\n const argsString = JSON.stringify({ action: 'browserState' })\n let stateRes = await mcp.executeTool('page-agent-tool', argsString)\n \n if (typeof stateRes === 'string') {\n try {\n stateRes = JSON.parse(stateRes)\n } catch (e) {\n // ignore\n }\n }\n if (stateRes && stateRes.content && stateRes.content.length > 0) {\n const textContent = stateRes.content.map((c: any) => c.text).join('\\\\n')\n \n // page-agent-tool 返回的格式通常是 \"浏览器状态: {\\\"url\\\":..., \\\"content\\\":\\\"[0]...\\\"}\"\n // 我们尝试把这个 JSON 提取出来,让外层更容易解析\n const prefix = '浏览器状态: '\n if (textContent.startsWith(prefix)) {\n try {\n const jsonStr = textContent.substring(prefix.length)\n const parsedState = JSON.parse(jsonStr)\n // 提取出带有索引的 DOM 树数据作为 content\n contentData = parsedState.content\n } catch (e) {\n contentData = textContent\n }\n } else {\n contentData = textContent\n }\n }\n } catch (e: any) {\n console.error('Snapshot error:', e.message)\n }\n }\n }\n\n return {\n content: contentData,\n url,\n title,\n webmcpTools\n }\n })\n\n // 获取所有的 tab 信息(排除 devtools:// 内部页面)\n const pages = await browser.pages()\n const tabs = await Promise.all(pages.map(async (p) => {\n const pUrl = p.url()\n if (pUrl.startsWith('devtools://')) return null\n\n const pTitle = await Promise.race([\n p.title().catch(() => 'Unknown'),\n new Promise<string>(resolve => setTimeout(() => resolve('Unknown'), 500))\n ])\n\n return {\n // 使用真实的 Chrome target ID,而非数组下标\n tabid: await getPageTargetId(p).catch(() => pUrl),\n title: pTitle,\n url: pUrl\n }\n }))\n\n return {\n ...state,\n tabs: tabs.filter(Boolean)\n }\n } finally {\n await browser.disconnect()\n }\n}\n","import { connectBrowser, getTargetPage } from '../browser'\n\nexport async function runCommand({\n toolName,\n argsJson,\n tabid\n}: {\n toolName: string\n argsJson: string\n tabid?: string\n}) {\n const browser = await connectBrowser()\n try {\n const page = await getTargetPage(browser, tabid)\n\n // 验证一下是否是合法的 JSON,以防用户传入了非法的字符串\n try {\n JSON.parse(argsJson)\n } catch (e: any) {\n throw new Error(`参数不是有效的 JSON: ${e.message}`)\n }\n\n const result = await page.evaluate(async (name, inputString) => {\n const mcp = (navigator as any).modelContextTesting || (navigator as any).modelContext\n \n if (!mcp || typeof mcp.executeTool !== 'function') {\n throw new Error('当前页面没有注入 WebMCP 环境 (navigator.modelContextTesting.executeTool 未找到)')\n }\n\n // executeTool 的第二个参数必须是 JSON 字符串\n let res = await mcp.executeTool(name, inputString)\n \n // executeTool 的返回值可能是普通对象,也可能是 JSON 字符串\n if (typeof res === 'string') {\n try {\n res = JSON.parse(res)\n } catch (e) {\n // ignore\n }\n }\n return res\n }, toolName, argsJson)\n\n return result\n } finally {\n await browser.disconnect()\n }\n}\n","import { connectBrowser, getTargetPage, injectIntoPage, getPageTargetId, findPageTargetByTabId, activateTabById } from '../browser'\nimport pc from 'picocolors'\n\nfunction normalizeUrl(url: string): string {\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\n return 'https://' + url\n }\n return url\n}\n\nexport async function tabsOpenCommand(url: string) {\n const browser = await connectBrowser()\n try {\n const page = await browser.newPage()\n url = normalizeUrl(url)\n\n await page.goto(url, { waitUntil: 'domcontentloaded' })\n await injectIntoPage(page)\n await new Promise(resolve => setTimeout(resolve, 500))\n return {\n success: true,\n tabid: await getPageTargetId(page),\n url: page.url(),\n title: await page.title()\n }\n } finally {\n await browser.disconnect()\n }\n}\n\nexport async function tabsCloseCommand(tabid: string) {\n const browser = await connectBrowser()\n try {\n const target = findPageTargetByTabId(browser, tabid)\n if (!target) {\n throw new Error(`Tab with targetId \"${tabid}\" not found.`)\n }\n\n const page = await target.page()\n if (!page) {\n throw new Error(`Tab with targetId \"${tabid}\" not found.`)\n }\n\n const closedTabid = await getPageTargetId(page).catch(() => tabid)\n await page.close()\n\n return {\n success: true,\n tabid: closedTabid\n }\n } finally {\n await browser.disconnect()\n }\n}\n\nexport async function tabsSwitchCommand(tabid: string) {\n const browser = await connectBrowser()\n try {\n await activateTabById(browser, tabid)\n const page = await getTargetPage(browser, tabid)\n\n return {\n success: true,\n tabid: await getPageTargetId(page),\n url: page.url(),\n title: await page.title()\n }\n } finally {\n await browser.disconnect()\n }\n}\n\nexport async function tabsBackCommand(tabid?: string) {\n const browser = await connectBrowser()\n try {\n const page = await getTargetPage(browser, tabid)\n const response = await page.goBack()\n\n if (!response) {\n return {\n success: false,\n error: '无法后退:已在历史记录起点',\n tabid: await getPageTargetId(page).catch(() => tabid),\n url: page.url(),\n title: await page.title()\n }\n }\n\n await injectIntoPage(page)\n\n return {\n success: true,\n tabid: await getPageTargetId(page),\n url: page.url(),\n title: await page.title()\n }\n } finally {\n await browser.disconnect()\n }\n}\n\nexport async function tabsForwardCommand(tabid?: string) {\n const browser = await connectBrowser()\n try {\n const page = await getTargetPage(browser, tabid)\n const response = await page.goForward()\n\n if (!response) {\n return {\n success: false,\n error: '无法前进:已在历史记录末尾',\n tabid: await getPageTargetId(page).catch(() => tabid),\n url: page.url(),\n title: await page.title()\n }\n }\n\n await injectIntoPage(page)\n\n return {\n success: true,\n tabid: await getPageTargetId(page),\n url: page.url(),\n title: await page.title()\n }\n } finally {\n await browser.disconnect()\n }\n}\n","{\n \"name\": \"@opentiny/webmcp-cli\",\n \"version\": \"0.0.1-alpha.2\",\n \"type\": \"module\",\n \"description\": \"WebMCP CLI for AI Agents to interact with browser via Puppeteer\",\n \"main\": \"dist/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"bin\": {\n \"webmcp-cli\": \"./dist/bin.js\"\n },\n \"files\": [\n \"dist\",\n \"README.md\"\n ],\n \"scripts\": {\n \"dev\": \"tsup --watch\",\n \"build:inject\": \"node scripts/build-inject.mjs\",\n \"build\": \"tsup && pnpm run build:inject\",\n \"prepublishOnly\": \"pnpm run build\",\n \"link:global\": \"npm install -g .\",\n \"start\": \"node dist/bin.js\"\n },\n \"keywords\": [\n \"chrome\",\n \"puppeteer\",\n \"cli\"\n ],\n \"license\": \"MIT\",\n \"author\": \"OpenTiny Team\",\n \"engines\": {\n \"node\": \">=16\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"dependencies\": {\n \"@mcp-b/webmcp-polyfill\": \"catalog:\",\n \"@opentiny/next-sdk\": \"workspace:^\",\n \"@page-agent/page-controller\": \"catalog:\",\n \"commander\": \"^11.1.0\",\n \"picocolors\": \"^1.0.0\",\n \"puppeteer-core\": \"^22.0.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"esbuild\": \"^0.25.0\",\n \"tsup\": \"^8.0.2\",\n \"tsx\": \"^4.0.0\",\n \"typescript\": \"^5.3.3\"\n }\n}\n"],"mappings":"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","names":["import_picocolors","path","pc","os","fs","puppeteer","tabs","pc"]}
package/dist/bin.js CHANGED
@@ -8,7 +8,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
 
9
9
  // src/bin.ts
10
10
  import { Command } from "commander";
11
- import pc3 from "picocolors";
11
+ import pc2 from "picocolors";
12
12
 
13
13
  // src/browser.ts
14
14
  import puppeteer from "puppeteer-core";
@@ -262,9 +262,8 @@ async function getPageTargetId(page) {
262
262
  });
263
263
  }
264
264
  }
265
- async function getTargetPage(browser, tabid) {
266
- const targets = browser.targets();
267
- const pageTargets = targets.filter((t) => {
265
+ function getPageTargets(browser) {
266
+ return browser.targets().filter((t) => {
268
267
  try {
269
268
  const type = (typeof t.type === "function" ? t.type() : t.type) || "";
270
269
  const url = (typeof t.url === "function" ? t.url() : t.url) || "";
@@ -273,6 +272,41 @@ async function getTargetPage(browser, tabid) {
273
272
  return false;
274
273
  }
275
274
  });
275
+ }
276
+ function getTargetIdFromTarget(target) {
277
+ return typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
278
+ }
279
+ function findPageTargetByTabId(browser, tabid) {
280
+ const pageTargets = getPageTargets(browser);
281
+ return pageTargets.find((t) => {
282
+ const tid = getTargetIdFromTarget(t);
283
+ return tid === tabid || tid.includes(tabid);
284
+ }) ?? null;
285
+ }
286
+ async function activateTabById(browser, tabid) {
287
+ const target = findPageTargetByTabId(browser, tabid);
288
+ if (!target) {
289
+ throw new Error(`Tab with targetId "${tabid}" not found.`);
290
+ }
291
+ const realTabId = getTargetIdFromTarget(target);
292
+ const pages = await browser.pages();
293
+ let sessionPage = pages.find((p) => !p.url().startsWith("devtools://"));
294
+ if (!sessionPage && pages.length > 0) {
295
+ sessionPage = pages[0];
296
+ }
297
+ if (!sessionPage) {
298
+ sessionPage = await browser.newPage();
299
+ }
300
+ const session = await sessionPage.createCDPSession();
301
+ try {
302
+ await session.send("Target.activateTarget", { targetId: realTabId });
303
+ } finally {
304
+ await session.detach().catch(() => {
305
+ });
306
+ }
307
+ }
308
+ async function getTargetPage(browser, tabid) {
309
+ const pageTargets = getPageTargets(browser);
276
310
  if (pageTargets.length === 0) {
277
311
  const newPage = await browser.newPage();
278
312
  await injectWebMCPPolyfillAndTools(newPage);
@@ -280,16 +314,11 @@ async function getTargetPage(browser, tabid) {
280
314
  }
281
315
  let targetPage = null;
282
316
  if (tabid !== void 0) {
283
- for (const target of pageTargets) {
284
- const tid = typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
285
- if (tid === tabid || tid.includes(tabid)) {
286
- targetPage = await target.page();
287
- break;
288
- }
289
- }
290
- if (!targetPage) {
317
+ const target = findPageTargetByTabId(browser, tabid);
318
+ if (!target) {
291
319
  throw new Error(`Tab with targetId "${tabid}" not found.`);
292
320
  }
321
+ targetPage = await target.page();
293
322
  } else {
294
323
  try {
295
324
  const urls = [
@@ -313,8 +342,7 @@ async function getTargetPage(browser, tabid) {
313
342
  }
314
343
  if (activeTargetId) {
315
344
  for (const target of pageTargets) {
316
- const tid = typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
317
- if (tid === activeTargetId) {
345
+ if (getTargetIdFromTarget(target) === activeTargetId) {
318
346
  targetPage = await target.page();
319
347
  break;
320
348
  }
@@ -434,7 +462,7 @@ async function stateCommand({ tabid }) {
434
462
  };
435
463
  });
436
464
  const pages = await browser.pages();
437
- const tabs = await Promise.all(pages.map(async (p) => {
465
+ const tabs2 = await Promise.all(pages.map(async (p) => {
438
466
  const pUrl = p.url();
439
467
  if (pUrl.startsWith("devtools://")) return null;
440
468
  const pTitle = await Promise.race([
@@ -450,7 +478,7 @@ async function stateCommand({ tabid }) {
450
478
  }));
451
479
  return {
452
480
  ...state,
453
- tabs: tabs.filter(Boolean)
481
+ tabs: tabs2.filter(Boolean)
454
482
  };
455
483
  } finally {
456
484
  await browser.disconnect();
@@ -491,56 +519,110 @@ async function runCommand({
491
519
  }
492
520
  }
493
521
 
494
- // src/commands/open.ts
495
- import pc2 from "picocolors";
496
- async function openCommand(url, { tabid, newTab }) {
522
+ // src/commands/tabs.ts
523
+ function normalizeUrl(url) {
524
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
525
+ return "https://" + url;
526
+ }
527
+ return url;
528
+ }
529
+ async function tabsOpenCommand(url) {
497
530
  const browser = await connectBrowser();
498
531
  try {
499
- let page;
500
- if (newTab) {
501
- console.log(pc2.yellow("openCommand: \u6B63\u5728\u521B\u5EFA\u65B0\u9875\u9762..."));
502
- page = await browser.newPage();
503
- } else {
504
- console.log(pc2.yellow("openCommand: \u83B7\u53D6\u6D4F\u89C8\u5668 targets..."));
505
- const targets = browser.targets();
506
- const pageTargets = targets.filter((t) => {
507
- try {
508
- const type = (typeof t.type === "function" ? t.type() : t.type) || "";
509
- const urlStr = (typeof t.url === "function" ? t.url() : t.url) || "";
510
- return type === "page" && !urlStr.startsWith("devtools://");
511
- } catch {
512
- return false;
513
- }
514
- });
515
- console.log(pc2.yellow(`openCommand: \u666E\u901A\u9875\u9762 targets \u6570\u91CF: ${pageTargets.length}`));
516
- let selectedTarget;
517
- if (tabid !== void 0) {
518
- console.log(pc2.yellow(`openCommand: \u6B63\u5728\u5339\u914D\u6307\u5B9A\u7684 tabid: ${tabid}...`));
519
- selectedTarget = pageTargets.find((t) => {
520
- const tid = typeof t._getTargetInfo === "function" ? t._getTargetInfo().targetId : t._targetId || t.targetId || "";
521
- return tid === tabid || tid.includes(tabid);
522
- });
523
- }
524
- if (selectedTarget) {
525
- console.log(pc2.yellow("openCommand: \u6B63\u5728\u5C06\u5339\u914D\u7684 target \u8F6C\u6362\u4E3A page..."));
526
- page = await selectedTarget.page();
527
- } else if (pageTargets.length > 0) {
528
- console.log(pc2.yellow("openCommand: \u6B63\u5728\u5C06\u6700\u540E\u4E00\u4E2A target \u8F6C\u6362\u4E3A page..."));
529
- page = await pageTargets[pageTargets.length - 1].page();
530
- }
531
- if (!page) {
532
- console.log(pc2.yellow("openCommand: \u6CA1\u6709\u627E\u5230\u53EF\u7528 page\uFF0C\u6B63\u5728\u521B\u5EFA\u65B0\u9875\u9762..."));
533
- page = await browser.newPage();
534
- }
532
+ const page = await browser.newPage();
533
+ url = normalizeUrl(url);
534
+ await page.goto(url, { waitUntil: "domcontentloaded" });
535
+ await injectIntoPage(page);
536
+ await new Promise((resolve) => setTimeout(resolve, 500));
537
+ return {
538
+ success: true,
539
+ tabid: await getPageTargetId(page),
540
+ url: page.url(),
541
+ title: await page.title()
542
+ };
543
+ } finally {
544
+ await browser.disconnect();
545
+ }
546
+ }
547
+ async function tabsCloseCommand(tabid) {
548
+ const browser = await connectBrowser();
549
+ try {
550
+ const target = findPageTargetByTabId(browser, tabid);
551
+ if (!target) {
552
+ throw new Error(`Tab with targetId "${tabid}" not found.`);
535
553
  }
536
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
537
- url = "https://" + url;
554
+ const page = await target.page();
555
+ if (!page) {
556
+ throw new Error(`Tab with targetId "${tabid}" not found.`);
557
+ }
558
+ const closedTabid = await getPageTargetId(page).catch(() => tabid);
559
+ await page.close();
560
+ return {
561
+ success: true,
562
+ tabid: closedTabid
563
+ };
564
+ } finally {
565
+ await browser.disconnect();
566
+ }
567
+ }
568
+ async function tabsSwitchCommand(tabid) {
569
+ const browser = await connectBrowser();
570
+ try {
571
+ await activateTabById(browser, tabid);
572
+ const page = await getTargetPage(browser, tabid);
573
+ return {
574
+ success: true,
575
+ tabid: await getPageTargetId(page),
576
+ url: page.url(),
577
+ title: await page.title()
578
+ };
579
+ } finally {
580
+ await browser.disconnect();
581
+ }
582
+ }
583
+ async function tabsBackCommand(tabid) {
584
+ const browser = await connectBrowser();
585
+ try {
586
+ const page = await getTargetPage(browser, tabid);
587
+ const response = await page.goBack();
588
+ if (!response) {
589
+ return {
590
+ success: false,
591
+ error: "\u65E0\u6CD5\u540E\u9000\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u8D77\u70B9",
592
+ tabid: await getPageTargetId(page).catch(() => tabid),
593
+ url: page.url(),
594
+ title: await page.title()
595
+ };
538
596
  }
539
- console.log(pc2.cyan(`\u6B63\u5728\u6253\u5F00: ${url}`));
540
- await page.goto(url, { waitUntil: "domcontentloaded" });
541
597
  await injectIntoPage(page);
542
598
  return {
543
599
  success: true,
600
+ tabid: await getPageTargetId(page),
601
+ url: page.url(),
602
+ title: await page.title()
603
+ };
604
+ } finally {
605
+ await browser.disconnect();
606
+ }
607
+ }
608
+ async function tabsForwardCommand(tabid) {
609
+ const browser = await connectBrowser();
610
+ try {
611
+ const page = await getTargetPage(browser, tabid);
612
+ const response = await page.goForward();
613
+ if (!response) {
614
+ return {
615
+ success: false,
616
+ error: "\u65E0\u6CD5\u524D\u8FDB\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u672B\u5C3E",
617
+ tabid: await getPageTargetId(page).catch(() => tabid),
618
+ url: page.url(),
619
+ title: await page.title()
620
+ };
621
+ }
622
+ await injectIntoPage(page);
623
+ return {
624
+ success: true,
625
+ tabid: await getPageTargetId(page),
544
626
  url: page.url(),
545
627
  title: await page.title()
546
628
  };
@@ -549,13 +631,71 @@ async function openCommand(url, { tabid, newTab }) {
549
631
  }
550
632
  }
551
633
 
634
+ // package.json
635
+ var package_default = {
636
+ name: "@opentiny/webmcp-cli",
637
+ version: "0.0.1-alpha.2",
638
+ type: "module",
639
+ description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
640
+ main: "dist/index.js",
641
+ types: "dist/index.d.ts",
642
+ bin: {
643
+ "webmcp-cli": "./dist/bin.js"
644
+ },
645
+ files: [
646
+ "dist",
647
+ "README.md"
648
+ ],
649
+ scripts: {
650
+ dev: "tsup --watch",
651
+ "build:inject": "node scripts/build-inject.mjs",
652
+ build: "tsup && pnpm run build:inject",
653
+ prepublishOnly: "pnpm run build",
654
+ "link:global": "npm install -g .",
655
+ start: "node dist/bin.js"
656
+ },
657
+ keywords: [
658
+ "chrome",
659
+ "puppeteer",
660
+ "cli"
661
+ ],
662
+ license: "MIT",
663
+ author: "OpenTiny Team",
664
+ engines: {
665
+ node: ">=16"
666
+ },
667
+ publishConfig: {
668
+ access: "public"
669
+ },
670
+ dependencies: {
671
+ "@mcp-b/webmcp-polyfill": "catalog:",
672
+ "@opentiny/next-sdk": "workspace:^",
673
+ "@page-agent/page-controller": "catalog:",
674
+ commander: "^11.1.0",
675
+ picocolors: "^1.0.0",
676
+ "puppeteer-core": "^22.0.0"
677
+ },
678
+ devDependencies: {
679
+ "@types/node": "^20.0.0",
680
+ esbuild: "^0.25.0",
681
+ tsup: "^8.0.2",
682
+ tsx: "^4.0.0",
683
+ typescript: "^5.3.3"
684
+ }
685
+ };
686
+
552
687
  // src/bin.ts
553
688
  var program = new Command();
554
689
  function parseTabId(id) {
555
690
  if (!id) return void 0;
556
691
  return id;
557
692
  }
558
- program.name("webmcp-cli").description("WebMCP CLI for interacting with browser via CDP").version("1.0.0").option("-w, --workspace <path>", "\u6307\u5B9A\u81EA\u5B9A\u4E49\u7684\u6D4F\u89C8\u5668\u5DE5\u4F5C\u7A7A\u95F4\uFF08\u7528\u6237\u914D\u7F6E\u76EE\u5F55\uFF09\u8DEF\u5F84").hook("preAction", (thisCommand) => {
693
+ function handleCommandError(error, commandName) {
694
+ const msg = error instanceof Error ? error.message : String(error);
695
+ console.error(pc2.red(`Error executing ${commandName} command: ${msg}`));
696
+ process.exit(1);
697
+ }
698
+ program.name("webmcp-cli").description("WebMCP CLI for interacting with browser via CDP").version(package_default.version).option("-w, --workspace <path>", "\u6307\u5B9A\u81EA\u5B9A\u4E49\u7684\u6D4F\u89C8\u5668\u5DE5\u4F5C\u7A7A\u95F4\uFF08\u7528\u6237\u914D\u7F6E\u76EE\u5F55\uFF09\u8DEF\u5F84").hook("preAction", (thisCommand) => {
559
699
  const opts = thisCommand.opts();
560
700
  if (opts.workspace) {
561
701
  process.env.WEBMCP_WORKSPACE = opts.workspace;
@@ -566,9 +706,7 @@ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D
566
706
  const result = await stateCommand({ tabid: parseTabId(options.tabid) });
567
707
  console.log(JSON.stringify(result, null, 2));
568
708
  } catch (error) {
569
- const msg = error instanceof Error ? error.message : String(error);
570
- console.error(pc3.red(`Error executing state command: ${msg}`));
571
- process.exit(1);
709
+ handleCommandError(error, "state");
572
710
  }
573
711
  });
574
712
  program.command("run <toolName> <argsJson>").description("\u5411\u6307\u5B9A\u9875\u7B7E\u8C03\u7528\u6307\u5B9A\u7684 WebMCP \u5DE5\u5177\u6267\u884C\u64CD\u4F5C").option("-t, --tabid <id>", "\u6307\u5B9A\u9875\u7B7E\u7684 ID").action(async (toolName, argsJson, options) => {
@@ -580,22 +718,48 @@ program.command("run <toolName> <argsJson>").description("\u5411\u6307\u5B9A\u98
580
718
  });
581
719
  console.log(JSON.stringify(result, null, 2));
582
720
  } catch (error) {
583
- const msg = error instanceof Error ? error.message : String(error);
584
- console.error(pc3.red(`Error executing run command: ${msg}`));
585
- process.exit(1);
721
+ handleCommandError(error, "run");
586
722
  }
587
723
  });
588
- program.command("open <url>").description("\u5728\u5F53\u524D\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u6307\u5B9A\u7F51\u9875").option("-t, --tabid <id>", "\u5728\u6307\u5B9A\u9875\u7B7E\u4E2D\u6253\u5F00").option("-n, --new-tab", "\u5728\u4E00\u4E2A\u5168\u65B0\u7684\u9875\u7B7E\u4E2D\u6253\u5F00").action(async (url, options) => {
724
+ var tabs = program.command("tabs").description("\u7BA1\u7406\u6D4F\u89C8\u5668\u6807\u7B7E\u9875");
725
+ tabs.command("open <url>").description("\u6253\u5F00\u65B0\u7F51\u9875").action(async (url) => {
589
726
  try {
590
- const result = await openCommand(url, {
591
- tabid: parseTabId(options.tabid),
592
- newTab: options.newTab
593
- });
727
+ const result = await tabsOpenCommand(url);
728
+ console.log(JSON.stringify(result, null, 2));
729
+ } catch (error) {
730
+ handleCommandError(error, "tabs open");
731
+ }
732
+ });
733
+ tabs.command("close <tabid>").description("\u5173\u95ED\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid) => {
734
+ try {
735
+ const result = await tabsCloseCommand(tabid);
736
+ console.log(JSON.stringify(result, null, 2));
737
+ } catch (error) {
738
+ handleCommandError(error, "tabs close");
739
+ }
740
+ });
741
+ tabs.command("switch <tabid>").description("\u6FC0\u6D3B\u5E76\u5207\u6362\u5230\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid) => {
742
+ try {
743
+ const result = await tabsSwitchCommand(tabid);
744
+ console.log(JSON.stringify(result, null, 2));
745
+ } catch (error) {
746
+ handleCommandError(error, "tabs switch");
747
+ }
748
+ });
749
+ tabs.command("back [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u540E\u9000\u4E00\u6B65").action(async (tabid) => {
750
+ try {
751
+ const result = await tabsBackCommand(parseTabId(tabid));
752
+ console.log(JSON.stringify(result, null, 2));
753
+ } catch (error) {
754
+ handleCommandError(error, "tabs back");
755
+ }
756
+ });
757
+ tabs.command("forward [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u524D\u8FDB\u4E00\u6B65").action(async (tabid) => {
758
+ try {
759
+ const result = await tabsForwardCommand(parseTabId(tabid));
594
760
  console.log(JSON.stringify(result, null, 2));
595
761
  } catch (error) {
596
- const msg = error instanceof Error ? error.message : String(error);
597
- console.error(pc3.red(`Error executing open command: ${msg}`));
598
- process.exit(1);
762
+ handleCommandError(error, "tabs forward");
599
763
  }
600
764
  });
601
765
  program.parse(process.argv);