@opentiny/webmcp-cli 0.0.1-alpha.0
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/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/bin.cjs +515 -0
- package/dist/bin.cjs.map +1 -0
- package/dist/bin.d.cts +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +491 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.cjs +420 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +379 -0
- package/dist/index.js.map +1 -0
- package/dist/inject-bundle.js +25318 -0
- package/dist/webmcp-tools/excalidraw.com.js +219 -0
- package/dist/webmcp-tools/www.baidu.com.js +76 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
// src/browser.ts
|
|
2
|
+
import puppeteer from "puppeteer-core";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
var __dirname = path.dirname(__filename);
|
|
11
|
+
var CDP_PORT = 9222;
|
|
12
|
+
var CDP_URL = `http://localhost:${CDP_PORT}`;
|
|
13
|
+
function getDefaultChromePath() {
|
|
14
|
+
const platform = os.platform();
|
|
15
|
+
if (platform === "darwin") {
|
|
16
|
+
return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
17
|
+
} else if (platform === "win32") {
|
|
18
|
+
const paths = [
|
|
19
|
+
process.env.LOCALAPPDATA + "\\Google\\Chrome\\Application\\chrome.exe",
|
|
20
|
+
process.env.PROGRAMFILES + "\\Google\\Chrome\\Application\\chrome.exe",
|
|
21
|
+
process.env["PROGRAMFILES(X86)"] + "\\Google\\Chrome\\Application\\chrome.exe"
|
|
22
|
+
];
|
|
23
|
+
return paths.find((p) => fs.existsSync(p)) || null;
|
|
24
|
+
} else {
|
|
25
|
+
const paths = ["/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium", "/usr/bin/chromium-browser"];
|
|
26
|
+
return paths.find((p) => fs.existsSync(p)) || null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function startChromeInBackground() {
|
|
30
|
+
const chromePath = getDefaultChromePath();
|
|
31
|
+
if (!chromePath || !fs.existsSync(chromePath)) {
|
|
32
|
+
throw new Error("\u65E0\u6CD5\u5728\u7CFB\u7EDF\u4E2D\u627E\u5230 Chrome \u6D4F\u89C8\u5668\u7684\u9ED8\u8BA4\u5B89\u88C5\u8DEF\u5F84\u3002");
|
|
33
|
+
}
|
|
34
|
+
console.log(pc.yellow(`\u6B63\u5728\u542F\u52A8\u540E\u53F0 Chrome \u5B9E\u4F8B (\u7AEF\u53E3: ${CDP_PORT})...`));
|
|
35
|
+
const userDataDir = process.env.WEBMCP_WORKSPACE || path.join(os.homedir(), ".webmcp_chrome_profile");
|
|
36
|
+
const child = spawn(
|
|
37
|
+
chromePath,
|
|
38
|
+
[
|
|
39
|
+
`--remote-debugging-port=${CDP_PORT}`,
|
|
40
|
+
`--user-data-dir=${userDataDir}`,
|
|
41
|
+
"--no-first-run",
|
|
42
|
+
"--no-default-browser-check"
|
|
43
|
+
],
|
|
44
|
+
{
|
|
45
|
+
detached: true,
|
|
46
|
+
stdio: "ignore"
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
child.unref();
|
|
50
|
+
for (let i = 0; i < 20; i++) {
|
|
51
|
+
try {
|
|
52
|
+
const urls = [`http://localhost:${CDP_PORT}/json/version`, `http://127.0.0.1:${CDP_PORT}/json/version`];
|
|
53
|
+
for (const url of urls) {
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(url);
|
|
56
|
+
if (response.ok) {
|
|
57
|
+
console.log(pc.green("Chrome \u542F\u52A8\u5E76\u5C31\u7EEA\u3002"));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {
|
|
64
|
+
}
|
|
65
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
66
|
+
}
|
|
67
|
+
throw new Error("Chrome \u542F\u52A8\u8D85\u65F6\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u5230 CDP \u7AEF\u53E3\u3002");
|
|
68
|
+
}
|
|
69
|
+
async function connectBrowser() {
|
|
70
|
+
const targetFilter = (target) => {
|
|
71
|
+
try {
|
|
72
|
+
const info = typeof target._getTargetInfo === "function" ? target._getTargetInfo() : target;
|
|
73
|
+
const type = info.type || "";
|
|
74
|
+
const url = info.url || "";
|
|
75
|
+
if (type === "service_worker" || type === "shared_worker" || type === "iframe" || type === "other" || type === "webview" || type === "background_page") {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (url.startsWith("devtools://") || url.startsWith("chrome-extension://")) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
} catch (e) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
try {
|
|
87
|
+
console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
|
|
88
|
+
const browser = await puppeteer.connect({
|
|
89
|
+
browserURL: `http://127.0.0.1:${CDP_PORT}`,
|
|
90
|
+
defaultViewport: null,
|
|
91
|
+
targetFilter
|
|
92
|
+
});
|
|
93
|
+
console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
|
|
94
|
+
return browser;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
try {
|
|
97
|
+
console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
|
|
98
|
+
const browser = await puppeteer.connect({
|
|
99
|
+
browserURL: `http://localhost:${CDP_PORT}`,
|
|
100
|
+
defaultViewport: null,
|
|
101
|
+
targetFilter
|
|
102
|
+
});
|
|
103
|
+
console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
|
|
104
|
+
return browser;
|
|
105
|
+
} catch (error2) {
|
|
106
|
+
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
|
+
try {
|
|
108
|
+
await startChromeInBackground();
|
|
109
|
+
try {
|
|
110
|
+
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 puppeteer.connect({
|
|
112
|
+
browserURL: `http://127.0.0.1:${CDP_PORT}`,
|
|
113
|
+
defaultViewport: null,
|
|
114
|
+
targetFilter
|
|
115
|
+
});
|
|
116
|
+
console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
|
|
117
|
+
return browser;
|
|
118
|
+
} catch (e) {
|
|
119
|
+
console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
|
|
120
|
+
const browser = await puppeteer.connect({
|
|
121
|
+
browserURL: `http://localhost:${CDP_PORT}`,
|
|
122
|
+
defaultViewport: null,
|
|
123
|
+
targetFilter
|
|
124
|
+
});
|
|
125
|
+
console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
|
|
126
|
+
return browser;
|
|
127
|
+
}
|
|
128
|
+
} catch (launchError) {
|
|
129
|
+
const msg = launchError instanceof Error ? launchError.message : String(launchError);
|
|
130
|
+
console.error(pc.red(`\u65E0\u6CD5\u8FDE\u63A5\u6216\u542F\u52A8\u6D4F\u89C8\u5668: ${msg}`));
|
|
131
|
+
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 and \u767B\u5F55\u6001\uFF09\uFF0C\u5982\u679C\u4F60\u7684 Chrome \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`));
|
|
132
|
+
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`));
|
|
133
|
+
throw new Error("Browser connection failed.");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function getPageTargetId(page) {
|
|
139
|
+
const session = await page.target().createCDPSession();
|
|
140
|
+
try {
|
|
141
|
+
const { targetInfo } = await session.send("Target.getTargetInfo");
|
|
142
|
+
return targetInfo.targetId;
|
|
143
|
+
} finally {
|
|
144
|
+
await session.detach().catch(() => {
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function getTargetPage(browser, tabid) {
|
|
149
|
+
const targets = browser.targets();
|
|
150
|
+
const pageTargets = targets.filter((t) => {
|
|
151
|
+
try {
|
|
152
|
+
const type = (typeof t.type === "function" ? t.type() : t.type) || "";
|
|
153
|
+
const url = (typeof t.url === "function" ? t.url() : t.url) || "";
|
|
154
|
+
return type === "page" && !url.startsWith("devtools://");
|
|
155
|
+
} catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (pageTargets.length === 0) {
|
|
160
|
+
const newPage = await browser.newPage();
|
|
161
|
+
await injectWebMCPPolyfillAndTools(newPage);
|
|
162
|
+
return newPage;
|
|
163
|
+
}
|
|
164
|
+
let targetPage = null;
|
|
165
|
+
if (tabid !== void 0) {
|
|
166
|
+
for (const target of pageTargets) {
|
|
167
|
+
const tid = typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
|
|
168
|
+
if (tid === tabid || tid.includes(tabid)) {
|
|
169
|
+
targetPage = await target.page();
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!targetPage) {
|
|
174
|
+
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
try {
|
|
178
|
+
const urls = [
|
|
179
|
+
`http://localhost:${CDP_PORT}/json/list`,
|
|
180
|
+
`http://127.0.0.1:${CDP_PORT}/json/list`
|
|
181
|
+
];
|
|
182
|
+
let activeTargetId = null;
|
|
183
|
+
for (const url of urls) {
|
|
184
|
+
try {
|
|
185
|
+
const res = await fetch(url);
|
|
186
|
+
if (res.ok) {
|
|
187
|
+
const targetsData = await res.json();
|
|
188
|
+
const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
|
|
189
|
+
if (active) {
|
|
190
|
+
activeTargetId = active.id;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (activeTargetId) {
|
|
198
|
+
for (const target of pageTargets) {
|
|
199
|
+
const tid = typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
|
|
200
|
+
if (tid === activeTargetId) {
|
|
201
|
+
targetPage = await target.page();
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
}
|
|
208
|
+
if (!targetPage) {
|
|
209
|
+
const lastTarget = pageTargets[pageTargets.length - 1];
|
|
210
|
+
targetPage = await lastTarget.page();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (!targetPage) {
|
|
214
|
+
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u76EE\u6807\u9875\u9762");
|
|
215
|
+
}
|
|
216
|
+
await injectWebMCPPolyfillAndTools(targetPage);
|
|
217
|
+
return targetPage;
|
|
218
|
+
}
|
|
219
|
+
async function injectWebMCPPolyfillAndTools(page, force = false) {
|
|
220
|
+
const polyfillReady = !force && await page.evaluate(() => {
|
|
221
|
+
return !!window.__webmcpcli_init;
|
|
222
|
+
}).catch(() => false);
|
|
223
|
+
if (!polyfillReady) {
|
|
224
|
+
console.log(pc.cyan("\u5F53\u524D\u9875\u9762\u5C1A\u672A\u6CE8\u5165 WebMCP \u73AF\u5883\uFF0C\u6B63\u5728\u6267\u884C\u81EA\u52A8\u6CE8\u5165..."));
|
|
225
|
+
const injectScriptPath = path.resolve(__dirname, "inject-bundle.js");
|
|
226
|
+
if (!fs.existsSync(injectScriptPath)) {
|
|
227
|
+
throw new Error(`Cannot find inject-bundle.js at ${injectScriptPath}. Please ensure you run 'pnpm build:inject' first.`);
|
|
228
|
+
}
|
|
229
|
+
const scriptContent = fs.readFileSync(injectScriptPath, "utf-8");
|
|
230
|
+
try {
|
|
231
|
+
await page.evaluate(scriptContent);
|
|
232
|
+
} catch (err) {
|
|
233
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
234
|
+
throw new Error("\u81EA\u52A8\u6CE8\u5165\u811A\u672C\u6267\u884C\u5931\u8D25: " + msg);
|
|
235
|
+
}
|
|
236
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
237
|
+
}
|
|
238
|
+
await injectDomainTools(page);
|
|
239
|
+
}
|
|
240
|
+
async function injectDomainTools(page) {
|
|
241
|
+
let hostname;
|
|
242
|
+
try {
|
|
243
|
+
const url = new URL(page.url());
|
|
244
|
+
hostname = url.hostname;
|
|
245
|
+
} catch {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const toolBundlePath = path.resolve(__dirname, "webmcp-tools", `${hostname}.js`);
|
|
249
|
+
if (!fs.existsSync(toolBundlePath)) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
console.log(pc.cyan(`\u68C0\u6D4B\u5230\u57DF\u540D ${hostname} \u6709\u9884\u7F6E\u5DE5\u5177\uFF0C\u6B63\u5728\u6CE8\u5165...`));
|
|
253
|
+
const toolScript = fs.readFileSync(toolBundlePath, "utf-8");
|
|
254
|
+
try {
|
|
255
|
+
await page.evaluate(toolScript);
|
|
256
|
+
console.log(pc.green(`\u5DF2\u4E3A ${hostname} \u6CE8\u5165\u9884\u7F6E\u5DE5\u5177`));
|
|
257
|
+
} catch (err) {
|
|
258
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
259
|
+
console.warn(pc.yellow(`\u57DF\u540D\u5DE5\u5177\u6CE8\u5165\u5931\u8D25 (${hostname}): ${msg}`));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/commands/state.ts
|
|
264
|
+
async function stateCommand({ tabid }) {
|
|
265
|
+
const browser = await connectBrowser();
|
|
266
|
+
try {
|
|
267
|
+
const page = await getTargetPage(browser, tabid);
|
|
268
|
+
const state = await page.evaluate(async () => {
|
|
269
|
+
const url = document.URL;
|
|
270
|
+
const title = document.title;
|
|
271
|
+
const mcp = navigator.modelContextTesting || navigator.modelContext;
|
|
272
|
+
let webmcpTools = [];
|
|
273
|
+
let contentData = `\u9875\u9762\u5DF2\u51C6\u5907\u597D: ${title}`;
|
|
274
|
+
if (mcp) {
|
|
275
|
+
if (typeof mcp.listTools === "function") {
|
|
276
|
+
const toolsResult = await mcp.listTools();
|
|
277
|
+
webmcpTools = toolsResult?.tools || toolsResult || [];
|
|
278
|
+
}
|
|
279
|
+
if (typeof mcp.executeTool === "function") {
|
|
280
|
+
try {
|
|
281
|
+
const argsString = JSON.stringify({ action: "browserState" });
|
|
282
|
+
let stateRes = await mcp.executeTool("page-agent-tool", argsString);
|
|
283
|
+
if (typeof stateRes === "string") {
|
|
284
|
+
try {
|
|
285
|
+
stateRes = JSON.parse(stateRes);
|
|
286
|
+
} catch (e) {
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (stateRes && stateRes.content && stateRes.content.length > 0) {
|
|
290
|
+
const textContent = stateRes.content.map((c) => c.text).join("\\n");
|
|
291
|
+
const prefix = "\u6D4F\u89C8\u5668\u72B6\u6001: ";
|
|
292
|
+
if (textContent.startsWith(prefix)) {
|
|
293
|
+
try {
|
|
294
|
+
const jsonStr = textContent.substring(prefix.length);
|
|
295
|
+
const parsedState = JSON.parse(jsonStr);
|
|
296
|
+
contentData = parsedState.content;
|
|
297
|
+
} catch (e) {
|
|
298
|
+
contentData = textContent;
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
contentData = textContent;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} catch (e) {
|
|
305
|
+
console.error("Snapshot error:", e.message);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
content: contentData,
|
|
311
|
+
url,
|
|
312
|
+
title,
|
|
313
|
+
webmcpTools
|
|
314
|
+
};
|
|
315
|
+
});
|
|
316
|
+
const pages = await browser.pages();
|
|
317
|
+
const tabs = await Promise.all(pages.map(async (p) => {
|
|
318
|
+
const pUrl = p.url();
|
|
319
|
+
if (pUrl.startsWith("devtools://")) return null;
|
|
320
|
+
const pTitle = await Promise.race([
|
|
321
|
+
p.title().catch(() => "Unknown"),
|
|
322
|
+
new Promise((resolve) => setTimeout(() => resolve("Unknown"), 500))
|
|
323
|
+
]);
|
|
324
|
+
return {
|
|
325
|
+
// 使用真实的 Chrome target ID,而非数组下标
|
|
326
|
+
tabid: await getPageTargetId(p).catch(() => pUrl),
|
|
327
|
+
title: pTitle,
|
|
328
|
+
url: pUrl
|
|
329
|
+
};
|
|
330
|
+
}));
|
|
331
|
+
return {
|
|
332
|
+
...state,
|
|
333
|
+
tabs: tabs.filter(Boolean)
|
|
334
|
+
};
|
|
335
|
+
} finally {
|
|
336
|
+
await browser.disconnect();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/commands/run.ts
|
|
341
|
+
async function runCommand({
|
|
342
|
+
toolName,
|
|
343
|
+
argsJson,
|
|
344
|
+
tabid
|
|
345
|
+
}) {
|
|
346
|
+
const browser = await connectBrowser();
|
|
347
|
+
try {
|
|
348
|
+
const page = await getTargetPage(browser, tabid);
|
|
349
|
+
try {
|
|
350
|
+
JSON.parse(argsJson);
|
|
351
|
+
} catch (e) {
|
|
352
|
+
throw new Error(`\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON: ${e.message}`);
|
|
353
|
+
}
|
|
354
|
+
const result = await page.evaluate(async (name, inputString) => {
|
|
355
|
+
const mcp = navigator.modelContextTesting || navigator.modelContext;
|
|
356
|
+
if (!mcp || typeof mcp.executeTool !== "function") {
|
|
357
|
+
throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (navigator.modelContextTesting.executeTool \u672A\u627E\u5230)");
|
|
358
|
+
}
|
|
359
|
+
let res = await mcp.executeTool(name, inputString);
|
|
360
|
+
if (typeof res === "string") {
|
|
361
|
+
try {
|
|
362
|
+
res = JSON.parse(res);
|
|
363
|
+
} catch (e) {
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return res;
|
|
367
|
+
}, toolName, argsJson);
|
|
368
|
+
return result;
|
|
369
|
+
} finally {
|
|
370
|
+
await browser.disconnect();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
export {
|
|
374
|
+
connectBrowser,
|
|
375
|
+
getTargetPage,
|
|
376
|
+
runCommand,
|
|
377
|
+
stateCommand
|
|
378
|
+
};
|
|
379
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":[]}
|