@opentiny/webmcp-cli 0.0.1-alpha.2 → 0.0.1-alpha.3
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 +230 -145
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +230 -145
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +60 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +60 -31
- package/dist/index.js.map +1 -1
- package/dist/inject-bundle.js +3 -3
- package/dist/webmcp-tools/juejin.cn.js +72 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -89,31 +89,54 @@ async function killProcessOnPortIfZombie(port) {
|
|
|
89
89
|
} catch (e) {
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
function
|
|
92
|
+
function getDefaultBrowserPath() {
|
|
93
93
|
const platform = os.platform();
|
|
94
94
|
if (platform === "darwin") {
|
|
95
|
-
|
|
95
|
+
const chromePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
96
|
+
if (fs.existsSync(chromePath)) return { path: chromePath, name: "Chrome" };
|
|
97
|
+
const edgePath = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge";
|
|
98
|
+
if (fs.existsSync(edgePath)) return { path: edgePath, name: "Edge" };
|
|
99
|
+
return null;
|
|
96
100
|
} else if (platform === "win32") {
|
|
97
|
-
const
|
|
101
|
+
const chromePaths = [
|
|
98
102
|
process.env.LOCALAPPDATA + "\\Google\\Chrome\\Application\\chrome.exe",
|
|
99
103
|
process.env.PROGRAMFILES + "\\Google\\Chrome\\Application\\chrome.exe",
|
|
100
104
|
process.env["PROGRAMFILES(X86)"] + "\\Google\\Chrome\\Application\\chrome.exe"
|
|
101
105
|
];
|
|
102
|
-
|
|
106
|
+
const foundChrome = chromePaths.find((p) => fs.existsSync(p));
|
|
107
|
+
if (foundChrome) return { path: foundChrome, name: "Chrome" };
|
|
108
|
+
const edgePaths = [
|
|
109
|
+
process.env.LOCALAPPDATA + "\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
110
|
+
process.env.PROGRAMFILES + "\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
111
|
+
process.env["PROGRAMFILES(X86)"] + "\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
112
|
+
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
113
|
+
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe"
|
|
114
|
+
];
|
|
115
|
+
const foundEdge = edgePaths.find((p) => fs.existsSync(p));
|
|
116
|
+
if (foundEdge) return { path: foundEdge, name: "Edge" };
|
|
117
|
+
return null;
|
|
103
118
|
} else {
|
|
104
|
-
const paths = [
|
|
105
|
-
|
|
119
|
+
const paths = [
|
|
120
|
+
{ p: "/usr/bin/google-chrome", name: "Chrome" },
|
|
121
|
+
{ p: "/usr/bin/google-chrome-stable", name: "Chrome" },
|
|
122
|
+
{ p: "/usr/bin/chromium", name: "Chromium" },
|
|
123
|
+
{ p: "/usr/bin/chromium-browser", name: "Chromium" },
|
|
124
|
+
{ p: "/usr/bin/microsoft-edge", name: "Edge" },
|
|
125
|
+
{ p: "/usr/bin/microsoft-edge-stable", name: "Edge" }
|
|
126
|
+
];
|
|
127
|
+
const found = paths.find(({ p }) => fs.existsSync(p));
|
|
128
|
+
return found ? { path: found.p, name: found.name } : null;
|
|
106
129
|
}
|
|
107
130
|
}
|
|
108
|
-
async function
|
|
109
|
-
const
|
|
110
|
-
if (!
|
|
111
|
-
throw new Error("\u65E0\u6CD5\u5728\u7CFB\u7EDF\u4E2D\u627E\u5230 Chrome \u6D4F\u89C8\u5668\u7684\u9ED8\u8BA4\u5B89\u88C5\u8DEF\u5F84\u3002");
|
|
131
|
+
async function startBrowserInBackground() {
|
|
132
|
+
const browserInfo = getDefaultBrowserPath();
|
|
133
|
+
if (!browserInfo || !fs.existsSync(browserInfo.path)) {
|
|
134
|
+
throw new Error("\u65E0\u6CD5\u5728\u7CFB\u7EDF\u4E2D\u627E\u5230 Chrome \u6216 Edge \u6D4F\u89C8\u5668\u7684\u9ED8\u8BA4\u5B89\u88C5\u8DEF\u5F84\u3002");
|
|
112
135
|
}
|
|
113
|
-
console.log(pc.yellow(`\u6B63\u5728\u542F\u52A8\u540E\u53F0
|
|
136
|
+
console.log(pc.yellow(`\u6B63\u5728\u542F\u52A8\u540E\u53F0 ${browserInfo.name} \u5B9E\u4F8B (\u7AEF\u53E3: ${CDP_PORT})...`));
|
|
114
137
|
const userDataDir = process.env.WEBMCP_WORKSPACE || path.join(os.homedir(), ".webmcp_chrome_profile");
|
|
115
138
|
const child = spawn(
|
|
116
|
-
|
|
139
|
+
browserInfo.path,
|
|
117
140
|
[
|
|
118
141
|
`--remote-debugging-port=${CDP_PORT}`,
|
|
119
142
|
`--user-data-dir=${userDataDir}`,
|
|
@@ -133,7 +156,7 @@ async function startChromeInBackground() {
|
|
|
133
156
|
try {
|
|
134
157
|
const response = await fetchWithTimeout(url, 1e3);
|
|
135
158
|
if (response.ok) {
|
|
136
|
-
console.log(pc.green(
|
|
159
|
+
console.log(pc.green(`${browserInfo.name} \u542F\u52A8\u5E76\u5C31\u7EEA\u3002`));
|
|
137
160
|
return;
|
|
138
161
|
}
|
|
139
162
|
} catch (err) {
|
|
@@ -143,7 +166,7 @@ async function startChromeInBackground() {
|
|
|
143
166
|
}
|
|
144
167
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
145
168
|
}
|
|
146
|
-
throw new Error(
|
|
169
|
+
throw new Error(`${browserInfo.name} \u542F\u52A8\u8D85\u65F6\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u5230 CDP \u7AEF\u53E3\u3002`);
|
|
147
170
|
}
|
|
148
171
|
async function connectBrowser() {
|
|
149
172
|
const targetFilter = (target) => {
|
|
@@ -201,7 +224,7 @@ async function connectBrowser() {
|
|
|
201
224
|
console.log(pc.yellow(`connectBrowser: \u8FDE\u63A5\u5931\u8D25\uFF0C\u5C06\u5C1D\u8BD5\u5524\u8D77\u6D4F\u89C8\u5668\u3002\u9519\u8BEF\u539F\u56E0: ${error2 instanceof Error ? error2.message : String(error2)}`));
|
|
202
225
|
try {
|
|
203
226
|
await killProcessOnPortIfZombie(CDP_PORT);
|
|
204
|
-
await
|
|
227
|
+
await startBrowserInBackground();
|
|
205
228
|
try {
|
|
206
229
|
const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3);
|
|
207
230
|
if (!is127Ready) {
|
|
@@ -240,8 +263,8 @@ async function connectBrowser() {
|
|
|
240
263
|
} catch (launchError) {
|
|
241
264
|
const msg = launchError instanceof Error ? launchError.message : String(launchError);
|
|
242
265
|
console.error(pc.red(`\u65E0\u6CD5\u8FDE\u63A5\u6216\u542F\u52A8\u6D4F\u89C8\u5668: ${msg}`));
|
|
243
|
-
console.error(pc.yellow(`\u{1F4A1} \u63D0\u793A\uFF1A\u7531\u4E8E\u6211\u4EEC\u8981\u4F7F\u7528\u4F60\u65E5\u5E38\u7684\u9ED8\u8BA4\u6D4F\u89C8\u5668\uFF08\u5305\u542B\u4F60\u7684\u4E66\u7B7E
|
|
244
|
-
console.error(pc.yellow(`\u{1F449} \u89E3\u51B3\u529E\u6CD5\uFF1A\u8BF7\u5148\u5B8C\u5168\u9000\u51FA\u5F53\u524D\u7684 Chrome \u6D4F\u89C8\u5668\uFF08\u5728 Mac \u4E0A\u6309 Cmd+Q\uFF09\uFF0C\u7136\u540E\u518D\u91CD\u65B0\u8FD0\u884C\u547D\u4EE4\u3002`));
|
|
266
|
+
console.error(pc.yellow(`\u{1F4A1} \u63D0\u793A\uFF1A\u7531\u4E8E\u6211\u4EEC\u8981\u4F7F\u7528\u4F60\u65E5\u5E38\u7684\u9ED8\u8BA4\u6D4F\u89C8\u5668\uFF08\u5305\u542B\u4F60\u7684\u4E66\u7B7E\u548C\u767B\u5F55\u6001\uFF09\uFF0C\u5982\u679C\u4F60\u7684 Chrome/Edge \u76EE\u524D\u6B63\u5904\u4E8E\u6253\u5F00\u72B6\u6001\uFF0C\u5B83\u4F1A\u62D2\u7EDD\u4F7F\u7528\u5E26\u6709\u8C03\u8BD5\u7AEF\u53E3\u7684\u65B0\u53C2\u6570\u542F\u52A8\u3002`));
|
|
267
|
+
console.error(pc.yellow(`\u{1F449} \u89E3\u51B3\u529E\u6CD5\uFF1A\u8BF7\u5148\u5B8C\u5168\u9000\u51FA\u5F53\u524D\u7684 Chrome \u6216 Edge \u6D4F\u89C8\u5668\uFF08\u5728 Mac \u4E0A\u6309 Cmd+Q\uFF0CWindows \u4E0A\u53F3\u952E\u4EFB\u52A1\u680F\u56FE\u6807\u9000\u51FA\uFF09\uFF0C\u7136\u540E\u518D\u91CD\u65B0\u8FD0\u884C\u547D\u4EE4\u3002`));
|
|
245
268
|
throw new Error("Browser connection failed.");
|
|
246
269
|
}
|
|
247
270
|
}
|
|
@@ -334,6 +357,9 @@ async function getTargetPage(browser, tabid) {
|
|
|
334
357
|
await injectWebMCPPolyfillAndTools(targetPage);
|
|
335
358
|
return targetPage;
|
|
336
359
|
}
|
|
360
|
+
async function injectIntoPage(page) {
|
|
361
|
+
await injectWebMCPPolyfillAndTools(page, true);
|
|
362
|
+
}
|
|
337
363
|
async function injectWebMCPPolyfillAndTools(page, force = false) {
|
|
338
364
|
const polyfillReady = !force && await page.evaluate(() => {
|
|
339
365
|
return !!window.__webmcpcli_init;
|
|
@@ -383,6 +409,7 @@ async function stateCommand({ tabid }) {
|
|
|
383
409
|
const browser = await connectBrowser();
|
|
384
410
|
try {
|
|
385
411
|
const page = await getTargetPage(browser, tabid);
|
|
412
|
+
await injectIntoPage(page);
|
|
386
413
|
const state = await page.evaluate(async () => {
|
|
387
414
|
const url = document.URL;
|
|
388
415
|
const title = document.title;
|
|
@@ -432,20 +459,22 @@ async function stateCommand({ tabid }) {
|
|
|
432
459
|
};
|
|
433
460
|
});
|
|
434
461
|
const pages = await browser.pages();
|
|
435
|
-
const tabs = await Promise.all(
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
462
|
+
const tabs = await Promise.all(
|
|
463
|
+
pages.map(async (p) => {
|
|
464
|
+
const pUrl = p.url();
|
|
465
|
+
if (pUrl.startsWith("devtools://")) return null;
|
|
466
|
+
const pTitle = await Promise.race([
|
|
467
|
+
p.title().catch(() => "Unknown"),
|
|
468
|
+
new Promise((resolve) => setTimeout(() => resolve("Unknown"), 500))
|
|
469
|
+
]);
|
|
470
|
+
return {
|
|
471
|
+
// 使用真实的 Chrome target ID,而非数组下标
|
|
472
|
+
tabid: await getPageTargetId(p).catch(() => pUrl),
|
|
473
|
+
title: pTitle,
|
|
474
|
+
url: pUrl
|
|
475
|
+
};
|
|
476
|
+
})
|
|
477
|
+
);
|
|
449
478
|
return {
|
|
450
479
|
...state,
|
|
451
480
|
tabs: tabs.filter(Boolean)
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts","../src/commands/state.ts","../src/commands/run.ts"],"sourcesContent":["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"],"mappings":";;;;;;;;AAAA,OAAO,eAAkC;AACzC,OAAO,QAAQ;AACf,OAAO,QAAQ;AACf,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,aAAa;AACtB,SAAS,qBAAqB;AAE9B,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,KAAK,QAAQ,UAAU;AAEzC,IAAM,WAAW;AAEjB,IAAM,UAAU,oBAAoB,QAAQ;AAE5C,eAAe,iBAAiB,KAAa,YAAY,MAAyB;AAChF,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,KAAK,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AACzD,MAAI;AACF,WAAO,MAAM,MAAM,KAAK,EAAE,QAAQ,WAAW,OAAO,CAAC;AAAA,EACvD,UAAE;AACA,iBAAa,EAAE;AAAA,EACjB;AACF;AAEA,SAAS,mBAAsB,SAAqB,YAAY,KAAM,WAAW,uBAAmC;AAClH,MAAI;AACJ,QAAM,iBAAiB,IAAI,QAAe,CAAC,GAAG,WAAW;AACvD,gBAAY,WAAW,MAAM;AAC3B,aAAO,IAAI,MAAM,QAAQ,CAAC;AAAA,IAC5B,GAAG,SAAS;AAAA,EACd,CAAC;AAED,SAAO,QAAQ,KAAK,CAAC,SAAS,cAAc,CAAC,EAAE,QAAQ,MAAM;AAC3D,iBAAa,SAAS;AAAA,EACxB,CAAC;AACH;AAEA,eAAe,cAAc,KAAa,UAAU,GAAqB;AACvE,WAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,QAAI;AACF,YAAM,MAAM,MAAM,iBAAiB,KAAK,IAAI;AAC5C,UAAI,IAAI,GAAI,QAAO;AAAA,IACrB,QAAQ;AAAA,IAAC;AACT,QAAI,IAAI,UAAU,GAAG;AACnB,YAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,GAAG,CAAC;AAAA,IAC3C;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,0BAA0B,MAA6B;AAEpE,QAAM,eAAe,MAAM,cAAc,oBAAoB,IAAI,iBAAiB,CAAC;AACnF,MAAI,cAAc;AAChB,YAAQ,IAAI,GAAG,MAAM,gCAAsB,IAAI,+JAA6B,CAAC;AAC7E;AAAA,EACF;AAEA,MAAI;AACF,UAAM,WAAW,GAAG,SAAS;AAC7B,UAAM,EAAE,SAAS,IAAI,UAAQ,eAAe;AAC5C,QAAI,aAAa,YAAY,aAAa,SAAS;AACjD,cAAQ,IAAI,GAAG,OAAO,0DAAa,IAAI,4DAAe,CAAC;AACvD,YAAM,OAAO,SAAS,eAAe,IAAI,EAAE,EAAE,SAAS,EAAE,KAAK;AAC7D,UAAI,MAAM;AACR,gBAAQ,IAAI,GAAG,OAAO,iCAAa,KAAK,MAAM,IAAI,EAAE,KAAK,IAAI,CAAC,+CAAY,CAAC;AAC3E,iBAAS,WAAW,KAAK,MAAM,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE;AAChD,gBAAQ,IAAI,GAAG,MAAM,8DAAY,CAAC;AAAA,MACpC;AAAA,IACF,WAAW,aAAa,SAAS;AAC/B,cAAQ,IAAI,GAAG,OAAO,yEAAuB,IAAI,4DAAe,CAAC;AACjE,YAAM,SAAS,SAAS,2BAA2B,IAAI,EAAE,EAAE,SAAS,EAAE,KAAK;AAC3E,UAAI,QAAQ;AACV,cAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,cAAM,OAAO,oBAAI,IAAY;AAC7B,cAAM,QAAQ,CAAC,SAAiB;AAC9B,gBAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,KAAK;AACrC,gBAAM,MAAM,MAAM,MAAM,SAAS,CAAC;AAClC,cAAI,OAAO,QAAQ,KAAK,GAAG,KAAK,QAAQ,KAAK;AAC3C,iBAAK,IAAI,GAAG;AAAA,UACd;AAAA,QACF,CAAC;AACD,aAAK,QAAQ,SAAO;AAClB,kBAAQ,IAAI,GAAG,OAAO,sDAAwB,GAAG,+CAAY,CAAC;AAC9D,mBAAS,oBAAoB,GAAG,EAAE;AAAA,QACpC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,SAAS,GAAG;AAAA,EAEZ;AACF;AAEA,SAAS,uBAAsC;AAC7C,QAAM,WAAW,GAAG,SAAS;AAC7B,MAAI,aAAa,UAAU;AACzB,WAAO;AAAA,EACT,WAAW,aAAa,SAAS;AAC/B,UAAM,QAAQ;AAAA,MACZ,QAAQ,IAAI,eAAe;AAAA,MAC3B,QAAQ,IAAI,eAAe;AAAA,MAC3B,QAAQ,IAAI,mBAAmB,IAAI;AAAA,IACrC;AACA,WAAO,MAAM,KAAK,OAAK,GAAG,WAAW,CAAC,CAAC,KAAK;AAAA,EAC9C,OAAO;AAEL,UAAM,QAAQ,CAAC,0BAA0B,iCAAiC,qBAAqB,2BAA2B;AAC1H,WAAO,MAAM,KAAK,OAAK,GAAG,WAAW,CAAC,CAAC,KAAK;AAAA,EAC9C;AACF;AAEA,eAAe,0BAAyC;AACtD,QAAM,aAAa,qBAAqB;AACxC,MAAI,CAAC,cAAc,CAAC,GAAG,WAAW,UAAU,GAAG;AAC7C,UAAM,IAAI,MAAM,4HAA6B;AAAA,EAC/C;AAEA,UAAQ,IAAI,GAAG,OAAO,2EAAyB,QAAQ,MAAM,CAAC;AAG9D,QAAM,cAAc,QAAQ,IAAI,oBAAoB,KAAK,KAAK,GAAG,QAAQ,GAAG,wBAAwB;AAEpG,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,MACE,2BAA2B,QAAQ;AAAA,MACnC,mBAAmB,WAAW;AAAA,MAC9B;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,MACE,UAAU;AAAA,MACV,OAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,MAAM;AAGZ,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,QAAI;AAEF,YAAM,OAAO,CAAC,oBAAoB,QAAQ,iBAAiB,oBAAoB,QAAQ,eAAe;AACtG,iBAAW,OAAO,MAAM;AACtB,YAAI;AACF,gBAAM,WAAW,MAAM,iBAAiB,KAAK,GAAI;AACjD,cAAI,SAAS,IAAI;AACf,oBAAQ,IAAI,GAAG,MAAM,6CAAe,CAAC;AACrC;AAAA,UACF;AAAA,QACF,SAAS,KAAK;AAAA,QAAC;AAAA,MACjB;AAAA,IACF,SAAS,GAAG;AAAA,IAEZ;AACA,UAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,GAAG,CAAC;AAAA,EACvD;AAEA,QAAM,IAAI,MAAM,4FAA2B;AAC7C;AAEA,eAAsB,iBAAmC;AACvD,QAAM,eAAe,CAAC,WAAgB;AACpC,QAAI;AACF,YAAM,OAAO,OAAO,OAAO,mBAAmB,aAAa,OAAO,eAAe,IAAI;AACrF,YAAM,OAAO,KAAK,QAAQ;AAC1B,YAAM,MAAM,KAAK,OAAO;AAGxB,UACE,SAAS,oBACT,SAAS,mBACT,SAAS,YACT,SAAS,WACT,SAAS,aACT,SAAS,mBACT;AACA,eAAO;AAAA,MACT;AAGA,UAAI,IAAI,WAAW,aAAa,KAAK,IAAI,WAAW,qBAAqB,GAAG;AAC1E,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,IACT,SAAS,GAAG;AACV,aAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI;AACF,UAAM,aAAa,MAAM,cAAc,oBAAoB,QAAQ,iBAAiB,CAAC;AACrF,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AACA,YAAQ,IAAI,GAAG,OAAO,wEAA0C,CAAC;AAEjE,UAAM,UAAU,MAAM;AAAA,MACpB,UAAU,QAAQ;AAAA,QAChB,YAAY,oBAAoB,QAAQ;AAAA,QACxC,iBAAiB;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,MACD;AAAA,MACA;AAAA,IACF;AACA,YAAQ,IAAI,GAAG,MAAM,yDAAqC,CAAC;AAC3D,WAAO;AAAA,EACT,SAAS,OAAgB;AACvB,QAAI;AACF,YAAM,mBAAmB,MAAM,cAAc,oBAAoB,QAAQ,iBAAiB,CAAC;AAC3F,UAAI,CAAC,kBAAkB;AACrB,cAAM,IAAI,MAAM,mCAAmC;AAAA,MACrD;AACA,cAAQ,IAAI,GAAG,OAAO,wEAA0C,CAAC;AAEjE,YAAM,UAAU,MAAM;AAAA,QACpB,UAAU,QAAQ;AAAA,UAChB,YAAY,oBAAoB,QAAQ;AAAA,UACxC,iBAAiB;AAAA,UACjB;AAAA,QACF,CAAC;AAAA,QACD;AAAA,QACA;AAAA,MACF;AACA,cAAQ,IAAI,GAAG,MAAM,yDAAqC,CAAC;AAC3D,aAAO;AAAA,IACT,SAAS,QAAiB;AACxB,cAAQ,IAAI,GAAG,OAAO,iIAAuC,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC,EAAE,CAAC;AAEzH,UAAI;AACF,cAAM,0BAA0B,QAAQ;AACxC,cAAM,wBAAwB;AAE9B,YAAI;AACF,gBAAM,aAAa,MAAM,cAAc,oBAAoB,QAAQ,iBAAiB,CAAC;AACrF,cAAI,CAAC,YAAY;AACf,kBAAM,IAAI,MAAM,gDAAgD;AAAA,UAClE;AACA,kBAAQ,IAAI,GAAG,OAAO,kHAAiD,CAAC;AACxE,gBAAM,UAAU,MAAM;AAAA,YACpB,UAAU,QAAQ;AAAA,cAChB,YAAY,oBAAoB,QAAQ;AAAA,cACxC,iBAAiB;AAAA,cACjB;AAAA,YACF,CAAC;AAAA,YACD;AAAA,YACA;AAAA,UACF;AACA,kBAAQ,IAAI,GAAG,MAAM,yDAAqC,CAAC;AAC3D,iBAAO;AAAA,QACT,SAAS,GAAG;AACV,gBAAM,mBAAmB,MAAM,cAAc,oBAAoB,QAAQ,iBAAiB,CAAC;AAC3F,cAAI,CAAC,kBAAkB;AACrB,kBAAM,IAAI,MAAM,gDAAgD;AAAA,UAClE;AACA,kBAAQ,IAAI,GAAG,OAAO,wEAA0C,CAAC;AACjE,gBAAM,UAAU,MAAM;AAAA,YACpB,UAAU,QAAQ;AAAA,cAChB,YAAY,oBAAoB,QAAQ;AAAA,cACxC,iBAAiB;AAAA,cACjB;AAAA,YACF,CAAC;AAAA,YACD;AAAA,YACA;AAAA,UACF;AACA,kBAAQ,IAAI,GAAG,MAAM,yDAAqC,CAAC;AAC3D,iBAAO;AAAA,QACT;AAAA,MACF,SAAS,aAAsB;AAC7B,cAAM,MAAM,uBAAuB,QAAQ,YAAY,UAAU,OAAO,WAAW;AACnF,gBAAQ,MAAM,GAAG,IAAI,iEAAe,GAAG,EAAE,CAAC;AAC1C,gBAAQ,MAAM,GAAG,OAAO,yZAAkF,CAAC;AAC3G,gBAAQ,MAAM,GAAG,OAAO,qOAAyD,CAAC;AAClF,cAAM,IAAI,MAAM,4BAA4B;AAAA,MAC9C;AAAA,IACF;AAAA,EACF;AACF;AAOA,eAAsB,gBAAgB,MAA6B;AACjE,QAAM,UAAU,MAAM,KAAK,OAAO,EAAE,iBAAiB;AACrD,MAAI;AACF,UAAM,EAAE,WAAW,IAAI,MAAM,QAAQ,KAAK,sBAAsB;AAChE,WAAO,WAAW;AAAA,EACpB,UAAE;AACA,UAAM,QAAQ,OAAO,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACvC;AACF;AAEO,SAAS,eAAe,SAAkB;AAC/C,SAAO,QAAQ,QAAQ,EAAE,OAAO,OAAK;AACnC,QAAI;AACF,YAAM,QAAQ,OAAO,EAAE,SAAS,aAAa,EAAE,KAAK,IAAK,EAAU,SAAS;AAC5E,YAAM,OAAO,OAAO,EAAE,QAAQ,aAAa,EAAE,IAAI,IAAK,EAAU,QAAQ;AACxE,aAAO,SAAS,UAAU,CAAC,IAAI,WAAW,aAAa;AAAA,IACzD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACH;AAEO,SAAS,sBAAsB,QAAqB;AACzD,SAAO,OAAO,OAAO,mBAAmB,aACpC,OAAO,eAAe,EAAE,WACvB,OAAO,aAAa,OAAO,YAAY;AAC9C;AAEO,SAAS,sBAAsB,SAAkB,OAAe;AACrE,QAAM,cAAc,eAAe,OAAO;AAC1C,SAAO,YAAY,KAAK,OAAK;AAC3B,UAAM,MAAM,sBAAsB,CAAC;AACnC,WAAO,QAAQ,SAAS,IAAI,SAAS,KAAK;AAAA,EAC5C,CAAC,KAAK;AACR;AA0BA,eAAsB,cAAc,SAAkB,OAA+B;AACnF,QAAM,cAAc,eAAe,OAAO;AAE1C,MAAI,YAAY,WAAW,GAAG;AAC5B,UAAM,UAAU,MAAM,QAAQ,QAAQ;AACtC,UAAM,6BAA6B,OAAO;AAC1C,WAAO;AAAA,EACT;AAEA,MAAI,aAA0B;AAE9B,MAAI,UAAU,QAAW;AACvB,UAAM,SAAS,sBAAsB,SAAS,KAAK;AACnD,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,sBAAsB,KAAK,cAAc;AAAA,IAC3D;AACA,iBAAa,MAAM,OAAO,KAAK;AAAA,EACjC,OAAO;AAEL,QAAI;AACF,YAAM,OAAO;AAAA,QACX,oBAAoB,QAAQ;AAAA,QAC5B,oBAAoB,QAAQ;AAAA,MAC9B;AACA,UAAI,iBAAgC;AACpC,iBAAW,OAAO,MAAM;AACtB,YAAI;AACF,gBAAM,MAAM,MAAM,iBAAiB,KAAK,GAAI;AAC5C,cAAI,IAAI,IAAI;AACV,kBAAM,cAAgE,MAAM,IAAI,KAAK;AAErF,kBAAM,SAAS,YAAY,KAAK,OAAK,EAAE,SAAS,UAAU,CAAC,EAAE,IAAI,WAAW,aAAa,CAAC;AAC1F,gBAAI,QAAQ;AAAE,+BAAiB,OAAO;AAAI;AAAA,YAAM;AAAA,UAClD;AAAA,QACF,QAAQ;AAAA,QAAoB;AAAA,MAC9B;AAEA,UAAI,gBAAgB;AAClB,mBAAW,UAAU,aAAa;AAChC,cAAI,sBAAsB,MAAM,MAAM,gBAAgB;AACpD,yBAAa,MAAM,OAAO,KAAK;AAC/B;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAAuB;AAG/B,QAAI,CAAC,YAAY;AACf,YAAM,aAAa,YAAY,YAAY,SAAS,CAAC;AACrD,mBAAa,MAAM,WAAW,KAAK;AAAA,IACrC;AAAA,EACF;AAEA,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,kDAAU;AAAA,EAC5B;AAGA,QAAM,6BAA6B,UAAU;AAC7C,SAAO;AACT;AASA,eAAe,6BAA6B,MAAY,QAAQ,OAAO;AAErE,QAAM,gBAAgB,CAAC,SAAS,MAAM,KAAK,SAAS,MAAM;AACxD,WAAO,CAAC,CAAE,OAAe;AAAA,EAC3B,CAAC,EAAE,MAAM,MAAM,KAAK;AAEpB,MAAI,CAAC,eAAe;AAClB,YAAQ,IAAI,GAAG,KAAK,+HAAgC,CAAC;AAErD,UAAM,mBAAmB,KAAK,QAAQ,WAAW,kBAAkB;AACnE,QAAI,CAAC,GAAG,WAAW,gBAAgB,GAAG;AACpC,YAAM,IAAI,MAAM,mCAAmC,gBAAgB,oDAAoD;AAAA,IACzH;AAEA,UAAM,gBAAgB,GAAG,aAAa,kBAAkB,OAAO;AAG/D,QAAI;AACF,YAAM,KAAK,SAAS,aAAa;AAAA,IACnC,SAAS,KAAc;AACrB,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,YAAM,IAAI,MAAM,mEAAiB,GAAG;AAAA,IACtC;AAGA,UAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,GAAG,CAAC;AAAA,EACvD;AAGA,QAAM,kBAAkB,IAAI;AAC9B;AAMA,eAAe,kBAAkB,MAA2B;AAC1D,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,KAAK,IAAI,CAAC;AAC9B,eAAW,IAAI;AAAA,EACjB,QAAQ;AACN;AAAA,EACF;AAEA,QAAM,iBAAiB,KAAK,QAAQ,WAAW,gBAAgB,GAAG,QAAQ,KAAK;AAC/E,MAAI,CAAC,GAAG,WAAW,cAAc,GAAG;AAClC;AAAA,EACF;AAEA,UAAQ,IAAI,GAAG,KAAK,kCAAS,QAAQ,kEAAgB,CAAC;AAEtD,QAAM,aAAa,GAAG,aAAa,gBAAgB,OAAO;AAC1D,MAAI;AACF,UAAM,KAAK,SAAS,UAAU;AAC9B,YAAQ,IAAI,GAAG,MAAM,gBAAM,QAAQ,uCAAS,CAAC;AAAA,EAC/C,SAAS,KAAc;AACrB,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAE3D,YAAQ,KAAK,GAAG,OAAO,qDAAa,QAAQ,MAAM,GAAG,EAAE,CAAC;AAAA,EAC1D;AACF;;;AC5dA,eAAsB,aAAa,EAAE,MAAM,GAAuB;AAChE,QAAM,UAAU,MAAM,eAAe;AACrC,MAAI;AACF,UAAM,OAAO,MAAM,cAAc,SAAS,KAAK;AAG/C,UAAM,QAAQ,MAAM,KAAK,SAAS,YAAY;AAC5C,YAAM,MAAM,SAAS;AACrB,YAAM,QAAQ,SAAS;AAGvB,YAAM,MAAO,UAAkB,uBAAwB,UAAkB;AACzE,UAAI,cAAqB,CAAC;AAC1B,UAAI,cAAmB,yCAAW,KAAK;AAEvC,UAAI,KAAK;AACP,YAAI,OAAO,IAAI,cAAc,YAAY;AACvC,gBAAM,cAAc,MAAM,IAAI,UAAU;AACxC,wBAAc,aAAa,SAAS,eAAe,CAAC;AAAA,QACtD;AAEA,YAAI,OAAO,IAAI,gBAAgB,YAAY;AACzC,cAAI;AACF,kBAAM,aAAa,KAAK,UAAU,EAAE,QAAQ,eAAe,CAAC;AAC5D,gBAAI,WAAW,MAAM,IAAI,YAAY,mBAAmB,UAAU;AAElE,gBAAI,OAAO,aAAa,UAAU;AAChC,kBAAI;AACF,2BAAW,KAAK,MAAM,QAAQ;AAAA,cAChC,SAAS,GAAG;AAAA,cAEZ;AAAA,YACF;AACA,gBAAI,YAAY,SAAS,WAAW,SAAS,QAAQ,SAAS,GAAG;AAC/D,oBAAM,cAAc,SAAS,QAAQ,IAAI,CAAC,MAAW,EAAE,IAAI,EAAE,KAAK,KAAK;AAIvE,oBAAM,SAAS;AACf,kBAAI,YAAY,WAAW,MAAM,GAAG;AAClC,oBAAI;AACF,wBAAM,UAAU,YAAY,UAAU,OAAO,MAAM;AACnD,wBAAM,cAAc,KAAK,MAAM,OAAO;AAEtC,gCAAc,YAAY;AAAA,gBAC5B,SAAS,GAAG;AACV,gCAAc;AAAA,gBAChB;AAAA,cACF,OAAO;AACL,8BAAc;AAAA,cAChB;AAAA,YACF;AAAA,UACF,SAAS,GAAQ;AACf,oBAAQ,MAAM,mBAAmB,EAAE,OAAO;AAAA,UAC5C;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC;AAGD,UAAM,QAAQ,MAAM,QAAQ,MAAM;AAClC,UAAM,OAAO,MAAM,QAAQ,IAAI,MAAM,IAAI,OAAO,MAAM;AACpD,YAAM,OAAO,EAAE,IAAI;AACnB,UAAI,KAAK,WAAW,aAAa,EAAG,QAAO;AAE3C,YAAM,SAAS,MAAM,QAAQ,KAAK;AAAA,QAChC,EAAE,MAAM,EAAE,MAAM,MAAM,SAAS;AAAA,QAC/B,IAAI,QAAgB,aAAW,WAAW,MAAM,QAAQ,SAAS,GAAG,GAAG,CAAC;AAAA,MAC1E,CAAC;AAED,aAAO;AAAA;AAAA,QAEL,OAAO,MAAM,gBAAgB,CAAC,EAAE,MAAM,MAAM,IAAI;AAAA,QAChD,OAAO;AAAA,QACP,KAAK;AAAA,MACP;AAAA,IACF,CAAC,CAAC;AAEF,WAAO;AAAA,MACL,GAAG;AAAA,MACH,MAAM,KAAK,OAAO,OAAO;AAAA,IAC3B;AAAA,EACF,UAAE;AACA,UAAM,QAAQ,WAAW;AAAA,EAC3B;AACF;;;AC5FA,eAAsB,WAAW;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,UAAU,MAAM,eAAe;AACrC,MAAI;AACF,UAAM,OAAO,MAAM,cAAc,SAAS,KAAK;AAG/C,QAAI;AACF,WAAK,MAAM,QAAQ;AAAA,IACrB,SAAS,GAAQ;AACf,YAAM,IAAI,MAAM,oDAAiB,EAAE,OAAO,EAAE;AAAA,IAC9C;AAEA,UAAM,SAAS,MAAM,KAAK,SAAS,OAAO,MAAM,gBAAgB;AAC9D,YAAM,MAAO,UAAkB,uBAAwB,UAAkB;AAEzE,UAAI,CAAC,OAAO,OAAO,IAAI,gBAAgB,YAAY;AACjD,cAAM,IAAI,MAAM,qIAAoE;AAAA,MACtF;AAGA,UAAI,MAAM,MAAM,IAAI,YAAY,MAAM,WAAW;AAGjD,UAAI,OAAO,QAAQ,UAAU;AAC3B,YAAI;AACF,gBAAM,KAAK,MAAM,GAAG;AAAA,QACtB,SAAS,GAAG;AAAA,QAEZ;AAAA,MACF;AACA,aAAO;AAAA,IACT,GAAG,UAAU,QAAQ;AAErB,WAAO;AAAA,EACT,UAAE;AACA,UAAM,QAAQ,WAAW;AAAA,EAC3B;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts","../src/commands/state.ts","../src/commands/run.ts"],"sourcesContent":["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\ninterface BrowserInfo {\n path: string\n name: string\n}\n\nfunction getDefaultBrowserPath(): BrowserInfo | null {\n const platform = os.platform()\n if (platform === 'darwin') {\n // macOS:优先 Chrome,其次 Edge\n const chromePath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'\n if (fs.existsSync(chromePath)) return { path: chromePath, name: 'Chrome' }\n const edgePath = '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'\n if (fs.existsSync(edgePath)) return { path: edgePath, name: 'Edge' }\n return null\n } else if (platform === 'win32') {\n // Windows:优先 Chrome,其次 Edge(系统内置路径)\n const chromePaths = [\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 const foundChrome = chromePaths.find(p => fs.existsSync(p))\n if (foundChrome) return { path: foundChrome, name: 'Chrome' }\n\n // Edge 在 Windows 上的常见路径(包括系统内置的 Edge)\n const edgePaths = [\n process.env.LOCALAPPDATA + '\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe',\n process.env.PROGRAMFILES + '\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe',\n process.env['PROGRAMFILES(X86)'] + '\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe',\n 'C:\\\\Program Files (x86)\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe',\n 'C:\\\\Program Files\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe'\n ]\n const foundEdge = edgePaths.find(p => fs.existsSync(p))\n if (foundEdge) return { path: foundEdge, name: 'Edge' }\n return null\n } else {\n // Linux:优先 Chrome,其次 Chromium,最后 Edge\n const paths: Array<{ p: string; name: string }> = [\n { p: '/usr/bin/google-chrome', name: 'Chrome' },\n { p: '/usr/bin/google-chrome-stable', name: 'Chrome' },\n { p: '/usr/bin/chromium', name: 'Chromium' },\n { p: '/usr/bin/chromium-browser', name: 'Chromium' },\n { p: '/usr/bin/microsoft-edge', name: 'Edge' },\n { p: '/usr/bin/microsoft-edge-stable', name: 'Edge' }\n ]\n const found = paths.find(({ p }) => fs.existsSync(p))\n return found ? { path: found.p, name: found.name } : null\n }\n}\n\nasync function startBrowserInBackground(): Promise<void> {\n const browserInfo = getDefaultBrowserPath()\n if (!browserInfo || !fs.existsSync(browserInfo.path)) {\n throw new Error('无法在系统中找到 Chrome 或 Edge 浏览器的默认安装路径。')\n }\n\n console.log(pc.yellow(`正在启动后台 ${browserInfo.name} 实例 (端口: ${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 browserInfo.path,\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(`${browserInfo.name} 启动并就绪。`))\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(`${browserInfo.name} 启动超时,无法连接到 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 startBrowserInBackground()\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(`💡 提示:由于我们要使用你日常的默认浏览器(包含你的书签和登录态),如果你的 Chrome/Edge 目前正处于打开状态,它会拒绝使用带有调试端口的新参数启动。`))\n console.error(pc.yellow(`👉 解决办法:请先完全退出当前的 Chrome 或 Edge 浏览器(在 Mac 上按 Cmd+Q,Windows 上右键任务栏图标退出),然后再重新运行命令。`))\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, injectIntoPage } 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 // 1. 每一次state都重新注入一次page-agent-tool。 这样用户即使手动打开的网页也会有相应的工具。\n await injectIntoPage(page)\n\n // 2. 在页面上下文中执行,获取当前状态和可用工具\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 // 3. 获取所有的 tab 信息(排除 devtools:// 内部页面)\n const pages = await browser.pages()\n const tabs = await Promise.all(\n 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\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"],"mappings":"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","names":[]}
|
package/dist/inject-bundle.js
CHANGED
|
@@ -25246,15 +25246,15 @@ ${pi.pixels_above > 4 && viewportExpansion !== -1 ? `... ${pi.pixels_above} pixe
|
|
|
25246
25246
|
const result2 = await pageController.getBrowserState();
|
|
25247
25247
|
return buildContent("\u6D4F\u89C8\u5668\u72B6\u6001:", result2);
|
|
25248
25248
|
} else if (args.action === "click") {
|
|
25249
|
-
if (
|
|
25249
|
+
if (args.index === void 0) return buildContent("\u70B9\u51FB\u7ED3\u679C:", "\u7F3A\u5C11\u5143\u7D20\u7D22\u5F15");
|
|
25250
25250
|
const result2 = await pageController.clickElement(args.index);
|
|
25251
25251
|
return buildContent("\u70B9\u51FB\u7ED3\u679C:", result2);
|
|
25252
25252
|
} else if (args.action === "fill") {
|
|
25253
|
-
if (
|
|
25253
|
+
if (args.index === void 0 || !args.text) return buildContent("\u586B\u5199\u7ED3\u679C:", "\u7F3A\u5C11\u5143\u7D20\u7D22\u5F15\u6216\u6587\u672C\u5185\u5BB9");
|
|
25254
25254
|
const result2 = await pageController.inputText(args.index, args.text);
|
|
25255
25255
|
return buildContent("\u586B\u5199\u7ED3\u679C:", result2);
|
|
25256
25256
|
} else if (args.action === "select") {
|
|
25257
|
-
if (
|
|
25257
|
+
if (args.index === void 0 || !args.text) return buildContent("\u9009\u62E9\u7ED3\u679C:", "\u7F3A\u5C11\u5143\u7D20\u7D22\u5F15\u6216\u6587\u672C\u5185\u5BB9");
|
|
25258
25258
|
const result2 = await pageController.selectOption(args.index, args.text);
|
|
25259
25259
|
return buildContent("\u9009\u62E9\u7ED3\u679C:", result2);
|
|
25260
25260
|
} else if (args.action === "scroll") {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
;(function(){
|
|
2
|
+
"use strict";
|
|
3
|
+
(() => {
|
|
4
|
+
// webmcp-tools/juejin.cn/index.ts
|
|
5
|
+
var mcp = navigator.modelContext;
|
|
6
|
+
if (!mcp || typeof mcp.registerTool !== "function") {
|
|
7
|
+
console.warn("[webmcp-tools] juejin.cn: navigator.modelContext.registerTool \u672A\u5C31\u7EEA\uFF0C\u8DF3\u8FC7\u6CE8\u5165");
|
|
8
|
+
} else if (!window.__webmcptools_juejincn) {
|
|
9
|
+
try {
|
|
10
|
+
mcp.registerTool({
|
|
11
|
+
name: "create_article",
|
|
12
|
+
title: "\u53D1\u5E03\u65B0\u6587\u7AE0",
|
|
13
|
+
description: "\u63A5\u6536\u6587\u7AE0\u7684\u6807\u9898\u548C\u6B63\u6587\uFF0C\u5C06\u5B83\u4EEC\u586B\u5199\u5230\u7F51\u9875\u4E2D\u3002",
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
title: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "\u6587\u7AE0\u6807\u9898\uFF0C\u5FC5\u586B\uFF0C \u5EFA\u8BAE\u4E0D\u8D85\u8FC715\u4E2A\u5B57\uFF0C\u4E0D\u5141\u8BB8\u6709\u7279\u6B8A\u7B26\u53F7\u5F71\u54CD\u547D\u4EE4\u884C\u53C2\u6570\u89E3\u6790"
|
|
20
|
+
},
|
|
21
|
+
content: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "\u6587\u7AE0\u5185\u5BB9\u7684base64\u7F16\u7801\u540E\u7684\u5B57\u7B26\u4E32\uFF0C\u5FC5\u586B\u3002"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
required: ["title", "content"]
|
|
27
|
+
},
|
|
28
|
+
execute: async ({ title, content }) => {
|
|
29
|
+
if (!location.href.startsWith("https://juejin.cn/editor/drafts/new")) {
|
|
30
|
+
return {
|
|
31
|
+
content: [
|
|
32
|
+
{ type: "text", text: "\u5F53\u524D\u9875\u9762\u4E0D\u662F\u53D1\u5E03\u65B0\u6587\u7AE0\u9875\u9762\uFF0C\u8BF7\u5148\u8BBF\u95EE https://juejin.cn/editor/drafts/new?v=2" }
|
|
33
|
+
]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const titleInput = document.querySelector(".edit-draft .header .title-input");
|
|
37
|
+
if (titleInput) {
|
|
38
|
+
titleInput.focus();
|
|
39
|
+
titleInput.value = title;
|
|
40
|
+
titleInput.dispatchEvent(
|
|
41
|
+
new InputEvent("input", {
|
|
42
|
+
bubbles: true,
|
|
43
|
+
cancelable: true,
|
|
44
|
+
data: title
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
titleInput.dispatchEvent(new Event("change", { bubbles: true }));
|
|
48
|
+
titleInput.blur();
|
|
49
|
+
}
|
|
50
|
+
const decodeContent = decodeURIComponent(escape(atob(content)));
|
|
51
|
+
const cm5El = document.querySelector(".edit-draft .CodeMirror");
|
|
52
|
+
if (cm5El && cm5El.CodeMirror) {
|
|
53
|
+
cm5El.CodeMirror.setValue(decodeContent);
|
|
54
|
+
} else {
|
|
55
|
+
const cm6View = document.querySelector(".cm-editor")?.cmView?.view;
|
|
56
|
+
if (cm6View) {
|
|
57
|
+
cm6View.dispatch({
|
|
58
|
+
changes: { from: 0, to: cm6View.state.doc.length, insert: decodeContent }
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { content: [{ type: "text", text: "\u6587\u7AE0\u6807\u9898\u548C\u5185\u5BB9\u5DF2\u7ECF\u586B\u5199\u5230\u7F51\u9875" }] };
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
window.__webmcptools_juejincn = true;
|
|
66
|
+
console.log("[webmcp-tools] juejin.cn \u5DE5\u5177\u6CE8\u518C\u6210\u529F");
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.error("[webmcp-tools] juejin.cn \u5DE5\u5177\u6CE8\u518C\u5931\u8D25:", e.message);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentiny/webmcp-cli",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@mcp-b/webmcp-polyfill": "^2.2.0",
|
|
30
|
-
"@page-agent/page-controller": "^1.
|
|
30
|
+
"@page-agent/page-controller": "^1.8.2",
|
|
31
|
+
"clipboardy": "^5.3.1",
|
|
31
32
|
"commander": "^11.1.0",
|
|
32
33
|
"picocolors": "^1.0.0",
|
|
33
34
|
"puppeteer-core": "^22.0.0",
|