@opentiny/webmcp-cli 0.0.1-alpha.0 → 0.0.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +127 -22
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +133 -22
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +127 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +134 -22
- package/dist/index.js.map +1 -1
- package/dist/webmcp-tools/excalidraw.com.js +8 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// src/browser.ts
|
|
2
9
|
import puppeteer from "puppeteer-core";
|
|
3
10
|
import pc from "picocolors";
|
|
@@ -10,6 +17,78 @@ var __filename = fileURLToPath(import.meta.url);
|
|
|
10
17
|
var __dirname = path.dirname(__filename);
|
|
11
18
|
var CDP_PORT = 9222;
|
|
12
19
|
var CDP_URL = `http://localhost:${CDP_PORT}`;
|
|
20
|
+
async function fetchWithTimeout(url, timeoutMs = 1500) {
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
const id = setTimeout(() => controller.abort(), timeoutMs);
|
|
23
|
+
try {
|
|
24
|
+
return await fetch(url, { signal: controller.signal });
|
|
25
|
+
} finally {
|
|
26
|
+
clearTimeout(id);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function promiseWithTimeout(promise, timeoutMs = 5e3, errorMsg = "Operation timed out") {
|
|
30
|
+
let timeoutId;
|
|
31
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
32
|
+
timeoutId = setTimeout(() => {
|
|
33
|
+
reject(new Error(errorMsg));
|
|
34
|
+
}, timeoutMs);
|
|
35
|
+
});
|
|
36
|
+
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
37
|
+
clearTimeout(timeoutId);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async function checkCdpReady(url, retries = 3) {
|
|
41
|
+
for (let i = 0; i < retries; i++) {
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetchWithTimeout(url, 1500);
|
|
44
|
+
if (res.ok) return true;
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
if (i < retries - 1) {
|
|
48
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
async function killProcessOnPortIfZombie(port) {
|
|
54
|
+
const isResponding = await checkCdpReady(`http://127.0.0.1:${port}/json/version`, 1);
|
|
55
|
+
if (isResponding) {
|
|
56
|
+
console.log(pc.green(`connectBrowser: \u7AEF\u53E3 ${port} \u4E0A\u7684\u6D4F\u89C8\u5668\u5B9E\u4F8B\u4ECD\u5728\u6B63\u5E38\u54CD\u5E94\uFF0C\u8DF3\u8FC7\u5F3A\u6740\uFF0C\u5C1D\u8BD5\u76F4\u63A5\u63A5\u7BA1\u3002`));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const platform = os.platform();
|
|
61
|
+
const { execSync } = __require("child_process");
|
|
62
|
+
if (platform === "darwin" || platform === "linux") {
|
|
63
|
+
console.log(pc.yellow(`\u6B63\u5728\u68C0\u6D4B\u5E76\u6E05\u7406\u5360\u7528 ${port} \u7AEF\u53E3\u7684\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B...`));
|
|
64
|
+
const pids = execSync(`lsof -t -i :${port}`).toString().trim();
|
|
65
|
+
if (pids) {
|
|
66
|
+
console.log(pc.yellow(`\u53D1\u73B0\u50F5\u5C38 PID: ${pids.split("\n").join(", ")}\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`));
|
|
67
|
+
execSync(`kill -9 ${pids.split("\n").join(" ")}`);
|
|
68
|
+
console.log(pc.green(`\u6210\u529F\u6E05\u7406\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B`));
|
|
69
|
+
}
|
|
70
|
+
} else if (platform === "win32") {
|
|
71
|
+
console.log(pc.yellow(`\u6B63\u5728\u68C0\u6D4B\u5E76\u6E05\u7406 Windows \u4E0A\u5360\u7528 ${port} \u7AEF\u53E3\u7684\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B...`));
|
|
72
|
+
const output = execSync(`netstat -ano | findstr :${port}`).toString().trim();
|
|
73
|
+
if (output) {
|
|
74
|
+
const lines = output.split("\n");
|
|
75
|
+
const pids = /* @__PURE__ */ new Set();
|
|
76
|
+
lines.forEach((line) => {
|
|
77
|
+
const parts = line.trim().split(/\s+/);
|
|
78
|
+
const pid = parts[parts.length - 1];
|
|
79
|
+
if (pid && /^\d+$/.test(pid) && pid !== "0") {
|
|
80
|
+
pids.add(pid);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
pids.forEach((pid) => {
|
|
84
|
+
console.log(pc.yellow(`\u53D1\u73B0 Windows \u6B8B\u7559\u50F5\u5C38 PID: ${pid}\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`));
|
|
85
|
+
execSync(`taskkill /F /PID ${pid}`);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch (e) {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
13
92
|
function getDefaultChromePath() {
|
|
14
93
|
const platform = os.platform();
|
|
15
94
|
if (platform === "darwin") {
|
|
@@ -52,7 +131,7 @@ async function startChromeInBackground() {
|
|
|
52
131
|
const urls = [`http://localhost:${CDP_PORT}/json/version`, `http://127.0.0.1:${CDP_PORT}/json/version`];
|
|
53
132
|
for (const url of urls) {
|
|
54
133
|
try {
|
|
55
|
-
const response = await
|
|
134
|
+
const response = await fetchWithTimeout(url, 1e3);
|
|
56
135
|
if (response.ok) {
|
|
57
136
|
console.log(pc.green("Chrome \u542F\u52A8\u5E76\u5C31\u7EEA\u3002"));
|
|
58
137
|
return;
|
|
@@ -84,44 +163,77 @@ async function connectBrowser() {
|
|
|
84
163
|
}
|
|
85
164
|
};
|
|
86
165
|
try {
|
|
166
|
+
const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3);
|
|
167
|
+
if (!is127Ready) {
|
|
168
|
+
throw new Error("127.0.0.1 CDP port not responding");
|
|
169
|
+
}
|
|
87
170
|
console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
|
|
88
|
-
const browser = await
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
171
|
+
const browser = await promiseWithTimeout(
|
|
172
|
+
puppeteer.connect({
|
|
173
|
+
browserURL: `http://127.0.0.1:${CDP_PORT}`,
|
|
174
|
+
defaultViewport: null,
|
|
175
|
+
targetFilter
|
|
176
|
+
}),
|
|
177
|
+
1e4,
|
|
178
|
+
"puppeteer.connect to 127.0.0.1 timed out"
|
|
179
|
+
);
|
|
93
180
|
console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
|
|
94
181
|
return browser;
|
|
95
182
|
} catch (error) {
|
|
96
183
|
try {
|
|
184
|
+
const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3);
|
|
185
|
+
if (!isLocalhostReady) {
|
|
186
|
+
throw new Error("localhost CDP port not responding");
|
|
187
|
+
}
|
|
97
188
|
console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
|
|
98
|
-
const browser = await
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
189
|
+
const browser = await promiseWithTimeout(
|
|
190
|
+
puppeteer.connect({
|
|
191
|
+
browserURL: `http://localhost:${CDP_PORT}`,
|
|
192
|
+
defaultViewport: null,
|
|
193
|
+
targetFilter
|
|
194
|
+
}),
|
|
195
|
+
1e4,
|
|
196
|
+
"puppeteer.connect to localhost timed out"
|
|
197
|
+
);
|
|
103
198
|
console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
|
|
104
199
|
return browser;
|
|
105
200
|
} catch (error2) {
|
|
106
201
|
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)}`));
|
|
107
202
|
try {
|
|
203
|
+
await killProcessOnPortIfZombie(CDP_PORT);
|
|
108
204
|
await startChromeInBackground();
|
|
109
205
|
try {
|
|
206
|
+
const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3);
|
|
207
|
+
if (!is127Ready) {
|
|
208
|
+
throw new Error("127.0.0.1 CDP port not responding after launch");
|
|
209
|
+
}
|
|
110
210
|
console.log(pc.yellow("connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
|
|
111
|
-
const browser = await
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
211
|
+
const browser = await promiseWithTimeout(
|
|
212
|
+
puppeteer.connect({
|
|
213
|
+
browserURL: `http://127.0.0.1:${CDP_PORT}`,
|
|
214
|
+
defaultViewport: null,
|
|
215
|
+
targetFilter
|
|
216
|
+
}),
|
|
217
|
+
1e4,
|
|
218
|
+
"puppeteer.connect to 127.0.0.1 after launch timed out"
|
|
219
|
+
);
|
|
116
220
|
console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
|
|
117
221
|
return browser;
|
|
118
222
|
} catch (e) {
|
|
223
|
+
const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3);
|
|
224
|
+
if (!isLocalhostReady) {
|
|
225
|
+
throw new Error("localhost CDP port not responding after launch");
|
|
226
|
+
}
|
|
119
227
|
console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
|
|
120
|
-
const browser = await
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
228
|
+
const browser = await promiseWithTimeout(
|
|
229
|
+
puppeteer.connect({
|
|
230
|
+
browserURL: `http://localhost:${CDP_PORT}`,
|
|
231
|
+
defaultViewport: null,
|
|
232
|
+
targetFilter
|
|
233
|
+
}),
|
|
234
|
+
1e4,
|
|
235
|
+
"puppeteer.connect to localhost after launch timed out"
|
|
236
|
+
);
|
|
125
237
|
console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
|
|
126
238
|
return browser;
|
|
127
239
|
}
|
|
@@ -182,7 +294,7 @@ async function getTargetPage(browser, tabid) {
|
|
|
182
294
|
let activeTargetId = null;
|
|
183
295
|
for (const url of urls) {
|
|
184
296
|
try {
|
|
185
|
-
const res = await
|
|
297
|
+
const res = await fetchWithTimeout(url, 1e3);
|
|
186
298
|
if (res.ok) {
|
|
187
299
|
const targetsData = await res.json();
|
|
188
300
|
const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
|
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\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 fetch(url)\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 console.log(pc.yellow('connectBrowser: 正在尝试连接 127.0.0.1:9222...'))\n // 优先尝试通过 127.0.0.1 连接\n const browser = await puppeteer.connect({\n browserURL: `http://127.0.0.1:${CDP_PORT}`,\n defaultViewport: null,\n targetFilter,\n })\n console.log(pc.green('connectBrowser: 成功连接 127.0.0.1:9222'))\n return browser\n } catch (error: unknown) {\n try {\n console.log(pc.yellow('connectBrowser: 正在尝试连接 localhost:9222...'))\n // 尝试使用 localhost 连接\n const browser = await puppeteer.connect({\n browserURL: `http://localhost:${CDP_PORT}`,\n defaultViewport: null,\n targetFilter,\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 startChromeInBackground()\n // 再次尝试连接\n try {\n console.log(pc.yellow('connectBrowser: 浏览器已启动,正在尝试连接 127.0.0.1:9222...'))\n const browser = await puppeteer.connect({\n browserURL: `http://127.0.0.1:${CDP_PORT}`,\n defaultViewport: null,\n targetFilter,\n })\n console.log(pc.green('connectBrowser: 成功连接 127.0.0.1:9222'))\n return browser\n } catch (e) {\n console.log(pc.yellow('connectBrowser: 正在尝试连接 localhost:9222...'))\n const browser = await puppeteer.connect({\n browserURL: `http://localhost:${CDP_PORT}`,\n defaultViewport: null,\n targetFilter,\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 fetch(url)\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"],"mappings":"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","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\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"],"mappings":"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","names":[]}
|
|
@@ -96,6 +96,14 @@
|
|
|
96
96
|
el.points = [[0, 0], [100, 100]];
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
if (el.type === "text") {
|
|
100
|
+
if (el.originalText === void 0) {
|
|
101
|
+
el.originalText = el.text || "";
|
|
102
|
+
}
|
|
103
|
+
if (el.fontFamily === void 0) {
|
|
104
|
+
el.fontFamily = 2;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
99
107
|
return el;
|
|
100
108
|
};
|
|
101
109
|
getExcalidrawAPIFromDOM2 = getExcalidrawAPIFromDOM, createFullElement2 = createFullElement;
|