@opentiny/webmcp-cli 0.0.6 → 0.0.8

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/index.cjs CHANGED
@@ -49,11 +49,37 @@ var import_url = require("url");
49
49
  var import_meta = {};
50
50
  var __filename = (0, import_url.fileURLToPath)(import_meta.url);
51
51
  var __dirname = import_path.default.dirname(__filename);
52
- var CDP_PORT = 9222;
53
- var CDP_URL = `http://localhost:${CDP_PORT}`;
52
+ function getCdpPort() {
53
+ const raw = process.env.WEBMCP_CDP_PORT;
54
+ if (raw) {
55
+ const port = Number.parseInt(raw, 10);
56
+ if (Number.isFinite(port) && port > 0) return port;
57
+ }
58
+ return 9222;
59
+ }
54
60
  function getWorkspaceDir() {
55
61
  return process.env.WEBMCP_WORKSPACE || import_path.default.join(import_os.default.homedir(), ".webmcp_chrome_profile");
56
62
  }
63
+ function shouldLaunchHeadless() {
64
+ if (process.env.WEBMCP_HEADLESS === "0") return false;
65
+ return process.env.WEBMCP_HEADLESS === "1" || process.env.CI === "true" || process.env.CI === "1";
66
+ }
67
+ function readActivePortFromProfile(userDataDir) {
68
+ try {
69
+ const content = import_fs.default.readFileSync(import_path.default.join(userDataDir, "DevToolsActivePort"), "utf-8");
70
+ const port = parseInt(content.split("\n")[0].trim(), 10);
71
+ return Number.isFinite(port) && port > 0 ? port : null;
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+ function candidateCdpBaseUrls(port) {
77
+ return [
78
+ `http://127.0.0.1:${port}`,
79
+ `http://[::1]:${port}`,
80
+ `http://localhost:${port}`
81
+ ];
82
+ }
57
83
  function getLastTabIdFilePath() {
58
84
  return import_path.default.join(getWorkspaceDir(), ".last-tab-id");
59
85
  }
@@ -137,6 +163,13 @@ async function connectPuppeteer(browserBaseUrl, attempt = 1, maxAttempts = 3) {
137
163
  }
138
164
  }
139
165
  function getDefaultBrowserPath() {
166
+ const envPath = process.env.CHROME_PATH || process.env.WEBMCP_CHROME_PATH || process.env.PUPPETEER_EXECUTABLE_PATH;
167
+ if (envPath && import_fs.default.existsSync(envPath)) {
168
+ return { path: envPath, name: "Chrome" };
169
+ }
170
+ if (envPath) {
171
+ console.log(import_picocolors.default.yellow(`\u6307\u5B9A\u7684\u6D4F\u89C8\u5668\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF0C\u5C06\u56DE\u9000\u5230\u81EA\u52A8\u68C0\u6D4B: ${envPath}`));
172
+ }
140
173
  const platform = import_os.default.platform();
141
174
  if (platform === "darwin") {
142
175
  const chromePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
@@ -180,16 +213,30 @@ async function startBrowserInBackground() {
180
213
  if (!browserInfo || !import_fs.default.existsSync(browserInfo.path)) {
181
214
  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");
182
215
  }
183
- console.log(import_picocolors.default.yellow(`\u6B63\u5728\u542F\u52A8\u540E\u53F0 ${browserInfo.name} \u5B9E\u4F8B (\u7AEF\u53E3: ${CDP_PORT})...`));
184
- const userDataDir = process.env.WEBMCP_WORKSPACE || import_path.default.join(import_os.default.homedir(), ".webmcp_chrome_profile");
216
+ const cdpPort = getCdpPort();
217
+ console.log(import_picocolors.default.yellow(`\u6B63\u5728\u542F\u52A8\u540E\u53F0 ${browserInfo.name} \u5B9E\u4F8B (\u7AEF\u53E3: ${cdpPort})...`));
218
+ const userDataDir = getWorkspaceDir();
219
+ if (!import_fs.default.existsSync(userDataDir)) {
220
+ import_fs.default.mkdirSync(userDataDir, { recursive: true });
221
+ }
222
+ try {
223
+ import_fs.default.unlinkSync(import_path.default.join(userDataDir, "DevToolsActivePort"));
224
+ } catch {
225
+ }
185
226
  const launchArgs = [
186
- `--remote-debugging-port=${CDP_PORT}`,
227
+ `--remote-debugging-port=${cdpPort}`,
187
228
  `--user-data-dir=${userDataDir}`,
188
229
  "--no-first-run",
189
230
  "--no-default-browser-check"
190
231
  ];
191
- if (import_os.default.platform() === "win32") {
192
- launchArgs.push("--remote-debugging-address=127.0.0.1");
232
+ if (shouldLaunchHeadless()) {
233
+ launchArgs.push(
234
+ "--headless=new",
235
+ "--no-sandbox",
236
+ "--disable-setuid-sandbox",
237
+ "--disable-gpu",
238
+ "--disable-dev-shm-usage"
239
+ );
193
240
  }
194
241
  const child = (0, import_child_process.spawn)(
195
242
  browserInfo.path,
@@ -200,15 +247,22 @@ async function startBrowserInBackground() {
200
247
  }
201
248
  );
202
249
  child.unref();
203
- const pollUrls = [`http://127.0.0.1:${CDP_PORT}/json/version`, `http://localhost:${CDP_PORT}/json/version`];
204
- for (let i = 0; i < 30; i++) {
205
- for (const url of pollUrls) {
206
- try {
207
- await httpGet(url, 1e3);
208
- console.log(import_picocolors.default.green(`${browserInfo.name} \u542F\u52A8\u5E76\u5C31\u7EEA\u3002`));
209
- await new Promise((resolve) => setTimeout(resolve, 500));
210
- return;
211
- } catch {
250
+ for (let i = 0; i < 40; i++) {
251
+ const ports = /* @__PURE__ */ new Set([cdpPort]);
252
+ const activePort = readActivePortFromProfile(userDataDir);
253
+ if (activePort) ports.add(activePort);
254
+ for (const port of ports) {
255
+ for (const baseUrl of candidateCdpBaseUrls(port)) {
256
+ try {
257
+ await httpGet(`${baseUrl}/json/version`, 1e3);
258
+ if (port !== cdpPort) {
259
+ console.log(import_picocolors.default.yellow(`\u68C0\u6D4B\u5230\u7AEF\u53E3 ${cdpPort} \u5DF2\u88AB\u5176\u4ED6\u7A0B\u5E8F\u5360\u7528\uFF0C${browserInfo.name} \u5DF2\u81EA\u52A8\u5207\u6362\u5230\u7AEF\u53E3 ${port}\u3002`));
260
+ }
261
+ console.log(import_picocolors.default.green(`${browserInfo.name} \u542F\u52A8\u5E76\u5C31\u7EEA\uFF08${baseUrl}\uFF09\u3002`));
262
+ await new Promise((resolve) => setTimeout(resolve, 500));
263
+ return baseUrl;
264
+ } catch {
265
+ }
212
266
  }
213
267
  }
214
268
  await new Promise((resolve) => setTimeout(resolve, 500));
@@ -216,13 +270,16 @@ async function startBrowserInBackground() {
216
270
  throw new Error(`${browserInfo.name} \u542F\u52A8\u8D85\u65F6\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u5230 CDP \u7AEF\u53E3\u3002`);
217
271
  }
218
272
  async function connectBrowser() {
273
+ const userDataDir = getWorkspaceDir();
274
+ const cdpPort = getCdpPort();
275
+ const recordedPort = readActivePortFromProfile(userDataDir);
219
276
  const versionAddresses = [
220
- `http://127.0.0.1:${CDP_PORT}/json/version`,
221
- `http://localhost:${CDP_PORT}/json/version`
277
+ ...recordedPort && recordedPort !== cdpPort ? candidateCdpBaseUrls(recordedPort).map((u) => `${u}/json/version`) : [],
278
+ ...candidateCdpBaseUrls(cdpPort).map((u) => `${u}/json/version`)
222
279
  ];
223
280
  const existingUrl = await findAvailableCdpUrl(versionAddresses, 2);
224
281
  if (existingUrl) {
225
- console.log(import_picocolors.default.yellow(`connectBrowser: \u68C0\u6D4B\u5230\u7AEF\u53E3 ${CDP_PORT} \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u8FDE\u63A5 ${existingUrl}...`));
282
+ console.log(import_picocolors.default.yellow(`connectBrowser: \u68C0\u6D4B\u5230\u7AEF\u53E3 ${cdpPort} \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u8FDE\u63A5 ${existingUrl}...`));
226
283
  try {
227
284
  const browser = await connectPuppeteer(existingUrl);
228
285
  console.log(import_picocolors.default.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${existingUrl}`));
@@ -234,25 +291,22 @@ async function connectBrowser() {
234
291
  );
235
292
  }
236
293
  }
237
- console.log(import_picocolors.default.yellow(`connectBrowser: \u7AEF\u53E3 ${CDP_PORT} \u65E0\u54CD\u5E94\uFF0C\u6B63\u5728\u542F\u52A8\u65B0\u7684\u6D4F\u89C8\u5668\u5B9E\u4F8B...`));
294
+ console.log(import_picocolors.default.yellow(`connectBrowser: \u7AEF\u53E3 ${cdpPort} \u65E0\u54CD\u5E94\uFF0C\u6B63\u5728\u542F\u52A8\u65B0\u7684\u6D4F\u89C8\u5668\u5B9E\u4F8B...`));
295
+ let launchedUrl;
238
296
  try {
239
- await startBrowserInBackground();
297
+ launchedUrl = await startBrowserInBackground();
240
298
  } catch (launchError) {
241
299
  const msg = launchError instanceof Error ? launchError.message : String(launchError);
242
300
  console.error(import_picocolors.default.red(`\u65E0\u6CD5\u542F\u52A8\u6D4F\u89C8\u5668: ${msg}`));
243
301
  throw new Error("Browser launch failed.");
244
302
  }
245
- const launchedUrl = await findAvailableCdpUrl(versionAddresses, 5);
246
- if (!launchedUrl) {
247
- throw new Error(`\u65E0\u6CD5\u8FDE\u63A5\u5230\u6D4F\u89C8\u5668\uFF08\u7AEF\u53E3 ${CDP_PORT}\uFF09\uFF0C\u8BF7\u68C0\u67E5 Chrome/Edge \u662F\u5426\u5DF2\u5B89\u88C5\u3002`);
248
- }
249
303
  try {
250
304
  console.log(import_picocolors.default.yellow(`connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u8FDE\u63A5 ${launchedUrl}...`));
251
305
  const browser = await connectPuppeteer(launchedUrl);
252
306
  console.log(import_picocolors.default.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${launchedUrl}`));
253
307
  return browser;
254
308
  } catch {
255
- throw new Error(`\u65E0\u6CD5\u8FDE\u63A5\u5230\u6D4F\u89C8\u5668\uFF08\u7AEF\u53E3 ${CDP_PORT}\uFF09\uFF0C\u8BF7\u68C0\u67E5 Chrome/Edge \u662F\u5426\u5DF2\u5B89\u88C5\u3002`);
309
+ throw new Error(`\u65E0\u6CD5\u8FDE\u63A5\u5230\u6D4F\u89C8\u5668\uFF08${launchedUrl}\uFF09\uFF0C\u8BF7\u68C0\u67E5 Chrome/Edge \u662F\u5426\u5DF2\u5B89\u88C5\u3002`);
256
310
  }
257
311
  }
258
312
  async function getPageTargetId(page) {
@@ -310,10 +364,11 @@ async function getTargetPage(browser, tabid) {
310
364
  }
311
365
  if (!targetPage) {
312
366
  try {
313
- const urls = [
314
- `http://localhost:${CDP_PORT}/json/list`,
315
- `http://127.0.0.1:${CDP_PORT}/json/list`
316
- ];
367
+ const workspaceDir = getWorkspaceDir();
368
+ const cdpPort = getCdpPort();
369
+ const recordedPort = readActivePortFromProfile(workspaceDir);
370
+ const ports = recordedPort && recordedPort !== cdpPort ? [recordedPort, cdpPort] : [cdpPort];
371
+ const urls = ports.flatMap((port) => candidateCdpBaseUrls(port).map((u) => `${u}/json/list`));
317
372
  let activeTargetId = null;
318
373
  for (const url of urls) {
319
374
  try {
@@ -350,10 +405,10 @@ async function getTargetPage(browser, tabid) {
350
405
  return targetPage;
351
406
  }
352
407
  async function injectIntoPage(page) {
353
- await injectWebMCPPolyfillAndTools(page, true);
408
+ await injectWebMCPPolyfillAndTools(page);
354
409
  }
355
- async function injectWebMCPPolyfillAndTools(page, force = false) {
356
- const polyfillReady = !force && await page.evaluate(() => {
410
+ async function injectWebMCPPolyfillAndTools(page) {
411
+ const polyfillReady = await page.evaluate(() => {
357
412
  return !!window.__webmcpcli_init;
358
413
  }).catch(() => false);
359
414
  if (!polyfillReady) {
@@ -443,11 +498,19 @@ async function stateCommand({ tabid }) {
443
498
  const state = await page.evaluate(async () => {
444
499
  const url = document.URL;
445
500
  const title = document.title;
446
- const mcp = document.modelContext;
501
+ const mcp = document.modelContext || navigator.modelContext;
447
502
  let webmcpTools = [];
448
- if (mcp && typeof mcp.listTools === "function") {
449
- const toolsResult = await mcp.getTools();
450
- webmcpTools = toolsResult?.tools || toolsResult || [];
503
+ if (mcp && typeof mcp.getTools === "function") {
504
+ try {
505
+ const toolsResult = await mcp.getTools();
506
+ const list = toolsResult?.tools || toolsResult || [];
507
+ webmcpTools = list.filter((t) => t && typeof t.name === "string").map((t) => ({
508
+ name: t.name,
509
+ description: typeof t.description === "string" ? t.description.slice(0, 200) : void 0
510
+ }));
511
+ } catch {
512
+ webmcpTools = [];
513
+ }
451
514
  }
452
515
  return { url, title, webmcpTools };
453
516
  });
@@ -479,155 +542,6 @@ async function stateCommand({ tabid }) {
479
542
  }
480
543
  }
481
544
 
482
- // src/zhihu/create-article.ts
483
- var import_os2 = __toESM(require("os"), 1);
484
-
485
- // src/zhihu/markdown.ts
486
- var import_marked = require("marked");
487
- function stripLeadingH1(markdown) {
488
- return markdown.replace(/^#\s+[^\n]*\n+/, "");
489
- }
490
- function markdownToHtml(markdown) {
491
- const marked = new import_marked.Marked({
492
- gfm: true,
493
- breaks: false,
494
- renderer: {
495
- code({ text, lang }) {
496
- const langClass = lang ? ` class="language-${lang}"` : "";
497
- const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
498
- return `<pre><code${langClass}>${escaped}</code></pre>`;
499
- },
500
- codespan({ text }) {
501
- const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
502
- return `<code>${escaped}</code>`;
503
- }
504
- }
505
- });
506
- return marked.parse(markdown);
507
- }
508
- function decodeBase64Content(content) {
509
- return Buffer.from(content, "base64").toString("utf-8");
510
- }
511
- var ZHIHU_WRITE_PAGE_RE = /^https:\/\/zhuanlan\.zhihu\.com\/(write|p\/\d+\/edit)/;
512
- function isZhihuWriteUrl(url) {
513
- return ZHIHU_WRITE_PAGE_RE.test(url);
514
- }
515
-
516
- // src/zhihu/create-article.ts
517
- function delay(ms) {
518
- return new Promise((resolve) => setTimeout(resolve, ms));
519
- }
520
- function getPasteModifier() {
521
- return import_os2.default.platform() === "darwin" ? "Meta" : "Control";
522
- }
523
- async function fillTitle(page, title) {
524
- await page.evaluate((titleText) => {
525
- const input = document.querySelector('textarea[placeholder*="\u6807\u9898"]') || document.querySelector(".WriteIndex-titleInput textarea") || document.querySelector(".WriteIndex-titleInput input");
526
- if (!input) throw new Error("\u672A\u627E\u5230\u6807\u9898\u8F93\u5165\u6846\uFF0C\u8BF7\u786E\u8BA4\u7F16\u8F91\u5668\u9875\u9762\u5DF2\u5B8C\u5168\u52A0\u8F7D");
527
- const setter = Object.getOwnPropertyDescriptor(
528
- input instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,
529
- "value"
530
- )?.set;
531
- if (setter) {
532
- setter.call(input, titleText);
533
- } else {
534
- input.value = titleText;
535
- }
536
- input.dispatchEvent(new InputEvent("input", { bubbles: true, cancelable: true, data: titleText }));
537
- input.dispatchEvent(new Event("change", { bubbles: true }));
538
- if (input.value.trim() !== titleText.trim()) {
539
- throw new Error("\u6807\u9898\u586B\u5199\u5931\u8D25\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u540E\u91CD\u8BD5");
540
- }
541
- }, title);
542
- }
543
- async function pasteHtmlToEditor(page, html) {
544
- const browser = page.browser();
545
- const tempPage = await browser.newPage();
546
- const modifier = getPasteModifier();
547
- try {
548
- const fullHtml = `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>${html}</body></html>`;
549
- await tempPage.setContent(fullHtml, { waitUntil: "domcontentloaded" });
550
- await delay(300);
551
- await tempPage.click("body");
552
- await tempPage.keyboard.down(modifier);
553
- await tempPage.keyboard.press("KeyA");
554
- await tempPage.keyboard.up(modifier);
555
- await delay(100);
556
- await tempPage.keyboard.down(modifier);
557
- await tempPage.keyboard.press("KeyC");
558
- await tempPage.keyboard.up(modifier);
559
- await delay(300);
560
- } finally {
561
- await tempPage.close();
562
- }
563
- await page.bringToFront();
564
- await page.waitForSelector(".public-DraftEditor-content", { timeout: 1e4 });
565
- await page.click(".public-DraftEditor-content");
566
- await delay(200);
567
- await page.keyboard.down(modifier);
568
- await page.keyboard.press("KeyA");
569
- await page.keyboard.up(modifier);
570
- await delay(100);
571
- await page.keyboard.press("Backspace");
572
- await delay(200);
573
- await page.keyboard.down(modifier);
574
- await page.keyboard.press("KeyV");
575
- await page.keyboard.up(modifier);
576
- await delay(1500);
577
- const probe = html.replace(/<[^>]+>/g, "").trim().slice(0, 15);
578
- const verifyResult = await page.evaluate((probeText) => {
579
- const editor = document.querySelector(".public-DraftEditor-content");
580
- if (!editor) return { ok: false, reason: "no editor" };
581
- const text = (editor.innerText || "").trim();
582
- if (!text) return { ok: false, reason: "empty" };
583
- const inner = editor.innerHTML;
584
- const hasRichFormat = inner.includes("data-text") || /font-weight:\s*bold|font-style:\s*italic|code-block|blockquote/i.test(inner);
585
- const normalizedText = text.replace(/\s+/g, "");
586
- const normalizedProbe = probeText.replace(/\s+/g, "");
587
- const hasProbe = normalizedText.includes(normalizedProbe);
588
- return {
589
- ok: text.length > 20 && hasProbe && (hasRichFormat || text.length > 50),
590
- reason: !hasProbe ? "probe mismatch" : !hasRichFormat && text.length <= 50 ? "no rich format" : void 0
591
- };
592
- }, probe);
593
- if (!verifyResult.ok) {
594
- throw new Error(
595
- `\u6B63\u6587\u586B\u5199\u5931\u8D25\uFF08Markdown \u8F6C HTML \u7C98\u8D34\u672A\u751F\u6548\uFF09${verifyResult.reason ? `: ${verifyResult.reason}` : ""}`
596
- );
597
- }
598
- }
599
- async function zhihuCreateArticle(page, args) {
600
- const { title, content } = args;
601
- if (!title?.trim()) throw new Error("\u53C2\u6570 title \u4E0D\u80FD\u4E3A\u7A7A");
602
- if (!content?.trim()) throw new Error("\u53C2\u6570 content \u4E0D\u80FD\u4E3A\u7A7A");
603
- const url = page.url();
604
- if (!isZhihuWriteUrl(url)) {
605
- throw new Error("\u5F53\u524D\u9875\u9762\u4E0D\u662F\u77E5\u4E4E\u4E13\u680F\u7F16\u8F91\u5668\uFF0C\u8BF7\u5148\u6253\u5F00 https://zhuanlan.zhihu.com/write");
606
- }
607
- let markdown;
608
- try {
609
- markdown = stripLeadingH1(decodeBase64Content(content));
610
- } catch {
611
- throw new Error("content \u4E0D\u662F\u6709\u6548\u7684 Base64 \u7F16\u7801\uFF0C\u8BF7\u68C0\u67E5\u53C2\u6570\u6216\u4F7F\u7528 @base64file: \u5F15\u7528\u6587\u4EF6");
612
- }
613
- if (!markdown.trim()) {
614
- throw new Error("\u6B63\u6587\u5185\u5BB9\u4E3A\u7A7A\uFF0C\u8BF7\u68C0\u67E5 Markdown \u6587\u4EF6");
615
- }
616
- const html = markdownToHtml(markdown);
617
- await fillTitle(page, title.trim());
618
- await delay(500);
619
- await pasteHtmlToEditor(page, html);
620
- const result = {
621
- success: true,
622
- message: "\u6587\u7AE0\u6807\u9898\u548C\u6B63\u6587\u5DF2\u6210\u529F\u586B\u5199\u5230\u77E5\u4E4E\u4E13\u680F\u7F16\u8F91\u5668\uFF08Markdown \u5DF2\u8F6C\u4E3A\u5BCC\u6587\u672C\uFF09\uFF0C\u8349\u7A3F\u5C06\u81EA\u52A8\u4FDD\u5B58",
623
- title: title.trim(),
624
- contentLength: markdown.length
625
- };
626
- return {
627
- content: [{ type: "text", text: JSON.stringify(result) }]
628
- };
629
- }
630
-
631
545
  // src/commands/run.ts
632
546
  async function runCommand({
633
547
  toolName,
@@ -638,18 +552,8 @@ async function runCommand({
638
552
  try {
639
553
  const page = await getTargetPage(browser, tabid);
640
554
  const urlBefore = page.url();
555
+ await injectIntoPage(page);
641
556
  const cleanedArgs = argsJson.trim();
642
- if (toolName === "create_article") {
643
- let parsedArgs;
644
- try {
645
- parsedArgs = JSON.parse(cleanedArgs);
646
- } catch {
647
- throw new Error("\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON");
648
- }
649
- if (isZhihuWriteUrl(page.url())) {
650
- return await zhihuCreateArticle(page, parsedArgs);
651
- }
652
- }
653
557
  let result;
654
558
  try {
655
559
  result = await page.evaluate(async (name, inputString) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/browser.ts","../src/commands/state.ts","../src/zhihu/create-article.ts","../src/zhihu/markdown.ts","../src/commands/run.ts"],"sourcesContent":["export { stateCommand } from './commands/state'\nexport { runCommand } from './commands/run'\nexport { connectBrowser, getTargetPage } from './browser'\n","import puppeteer, { Browser, Page } from 'puppeteer-core'\nimport pc from 'picocolors'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport http from 'http'\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 getWorkspaceDir(): string {\n return process.env.WEBMCP_WORKSPACE || path.join(os.homedir(), '.webmcp_chrome_profile')\n}\n\nfunction getLastTabIdFilePath(): string {\n return path.join(getWorkspaceDir(), '.last-tab-id')\n}\n\n/** 记录最近一次 tabs open / tabs switch 操作的标签页,供 run 无 -t 时回退定位 */\nexport function setLastActiveTabId(tabid: string): void {\n try {\n const dir = getWorkspaceDir()\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n fs.writeFileSync(getLastTabIdFilePath(), tabid, 'utf-8')\n } catch {\n // 写入失败不阻断主流程\n }\n}\n\nfunction getLastActiveTabId(): string | null {\n try {\n const tabid = fs.readFileSync(getLastTabIdFilePath(), 'utf-8').trim()\n return tabid || null\n } catch {\n return null\n }\n}\n\n/**\n * 使用 Node.js 原生 http 模块发起 GET 请求并返回响应体文本\n * 避免 Node.js fetch 将 localhost 解析为 IPv6 ::1 导致连接 Chrome CDP 失败的问题\n */\nfunction httpGet(url: string, timeoutMs = 1500): Promise<string> {\n return new Promise((resolve, reject) => {\n const req = http.get(url, { timeout: timeoutMs }, (res) => {\n let data = ''\n res.on('data', (chunk: Buffer) => { data += chunk.toString() })\n res.on('end', () => {\n if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {\n resolve(data)\n } else {\n reject(new Error(`HTTP ${res.statusCode}`))\n }\n })\n })\n req.on('error', reject)\n req.on('timeout', () => { req.destroy(); reject(new Error('request timeout')) })\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 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 await httpGet(url, 1500)\n return true\n } catch {}\n if (i < retries - 1) {\n await new Promise(r => setTimeout(r, 200))\n }\n }\n return false\n}\n\n/**\n * 找出第一个真正可达的 CDP 地址,避免 Windows 上 localhost 解析为 IPv6 导致 puppeteer 连接失败\n */\nasync function findAvailableCdpUrl(addresses: string[], retries = 2): Promise<string | null> {\n for (const addr of addresses) {\n if (await checkCdpReady(addr, retries)) {\n return addr.replace('/json/version', '')\n }\n }\n return null\n}\n\n/**\n * 通过 Puppeteer 连接已运行的浏览器。\n * 注意:不要在 connect 时使用 targetFilter——在 Windows Edge 上会导致 WebSocket 握手后永久挂起;\n * 页面 target 的筛选在 getPageTargets / getTargetPage 中按需完成。\n */\nasync function connectPuppeteer(browserBaseUrl: string, attempt = 1, maxAttempts = 3): Promise<Browser> {\n try {\n return await promiseWithTimeout(\n puppeteer.connect({ browserURL: browserBaseUrl, defaultViewport: null }),\n 10000,\n `puppeteer.connect to ${browserBaseUrl} timed out`\n )\n } catch (err) {\n if (attempt >= maxAttempts) {\n throw err\n }\n const delayMs = 500 * attempt\n console.log(pc.yellow(`connectBrowser: 第 ${attempt} 次连接失败,${delayMs}ms 后重试...`))\n await new Promise(resolve => setTimeout(resolve, delayMs))\n return connectPuppeteer(browserBaseUrl, attempt + 1, maxAttempts)\n }\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 launchArgs = [\n `--remote-debugging-port=${CDP_PORT}`,\n `--user-data-dir=${userDataDir}`,\n '--no-first-run',\n '--no-default-browser-check'\n ]\n // Windows 上显式绑定 IPv4,避免 localhost 解析到 IPv6 导致 CDP 连接不稳定\n if (os.platform() === 'win32') {\n launchArgs.push('--remote-debugging-address=127.0.0.1')\n }\n\n const child = spawn(\n browserInfo.path,\n launchArgs,\n {\n detached: true,\n stdio: 'ignore'\n }\n )\n\n child.unref() // 让子进程脱离父进程独立运行\n\n // 轮询等待 CDP 端口就绪(优先 127.0.0.1,避免 Windows IPv6 解析问题)\n const pollUrls = [`http://127.0.0.1:${CDP_PORT}/json/version`, `http://localhost:${CDP_PORT}/json/version`]\n for (let i = 0; i < 30; i++) {\n for (const url of pollUrls) {\n try {\n await httpGet(url, 1000)\n console.log(pc.green(`${browserInfo.name} 启动并就绪。`))\n // 额外等待 500ms,确保 CDP 完全稳定(Mac 首次启动时端口通但连接不稳定)\n await new Promise(resolve => setTimeout(resolve, 500))\n return\n } catch {}\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 // 第一步:找到第一个真正可达的 CDP 地址(优先 127.0.0.1,规避 Windows localhost → IPv6 问题)\n const versionAddresses = [\n `http://127.0.0.1:${CDP_PORT}/json/version`,\n `http://localhost:${CDP_PORT}/json/version`\n ]\n\n const existingUrl = await findAvailableCdpUrl(versionAddresses, 2)\n if (existingUrl) {\n console.log(pc.yellow(`connectBrowser: 检测到端口 ${CDP_PORT} 已就绪,正在连接 ${existingUrl}...`))\n try {\n const browser = await connectPuppeteer(existingUrl)\n console.log(pc.green(`connectBrowser: 成功连接 ${existingUrl}`))\n return browser\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(\n `无法连接到已在运行的浏览器(${existingUrl}):${msg}。` +\n 'CDP 端口可用,请勿重复启动浏览器;可关闭多余窗口后重试。'\n )\n }\n }\n\n // 第二步:端口无响应,启动新的浏览器实例(使用独立用户数据目录,不影响用户已有浏览器)\n console.log(pc.yellow(`connectBrowser: 端口 ${CDP_PORT} 无响应,正在启动新的浏览器实例...`))\n try {\n await startBrowserInBackground()\n } catch (launchError: unknown) {\n const msg = launchError instanceof Error ? launchError.message : String(launchError)\n console.error(pc.red(`无法启动浏览器: ${msg}`))\n throw new Error('Browser launch failed.')\n }\n\n // 第三步:浏览器启动后,再次找可用地址并连接\n const launchedUrl = await findAvailableCdpUrl(versionAddresses, 5)\n if (!launchedUrl) {\n throw new Error(`无法连接到浏览器(端口 ${CDP_PORT}),请检查 Chrome/Edge 是否已安装。`)\n }\n\n try {\n console.log(pc.yellow(`connectBrowser: 浏览器已启动,正在连接 ${launchedUrl}...`))\n const browser = await connectPuppeteer(launchedUrl)\n console.log(pc.green(`connectBrowser: 成功连接 ${launchedUrl}`))\n return browser\n } catch {\n throw new Error(`无法连接到浏览器(端口 ${CDP_PORT}),请检查 Chrome/Edge 是否已安装。`)\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 // 优先使用最近一次 tabs open/switch 记录的标签页(Agent 连续 CLI 调用更可靠)\n const lastTabId = getLastActiveTabId()\n if (lastTabId) {\n const lastTarget = findPageTargetByTabId(browser, lastTabId)\n if (lastTarget) {\n targetPage = await lastTarget.page()\n }\n }\n\n // Chrome 的 /json/list 接口把当前激活 the tab 排在第一位,用它来判断激活 tab\n if (!targetPage) {\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 body = await httpGet(url, 1000)\n const targetsData: Array<{ id: string; type: string; url: string }> = JSON.parse(body)\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 } 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\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 // 显式等待 WebMCP 注册接口完全就绪\n await page.waitForFunction(() => {\n const mcp = (navigator as any).modelContext\n return mcp && typeof mcp.registerTool === 'function'\n }, { timeout: 10000 }).catch(() => {\n console.warn('等待 WebMCP registerTool 接口就绪超时,正在继续注入...')\n })\n }\n\n // 无论 polyfill 是否刚注入,都检查域名工具(工具内部有防重复 flag)\n await injectDomainTools(page)\n}\n\nfunction getToolsBundleName(hostname: string): string | null {\n const normalized = hostname.split(':')[0].toLowerCase()\n const toolsDir = path.resolve(__dirname, 'webmcp-tools')\n if (!fs.existsSync(toolsDir)) {\n return null\n }\n let files: string[] = []\n try {\n files = fs.readdirSync(toolsDir)\n } catch {\n return null\n }\n const supportedDomains = files\n .filter(f => f.endsWith('.js'))\n .map(f => f.slice(0, -3))\n\n if (supportedDomains.includes(normalized)) {\n return `webmcp-tools/${normalized}.js`\n }\n for (const domain of supportedDomains) {\n if (normalized.endsWith('.' + domain)) {\n return `webmcp-tools/${domain}.js`\n }\n }\n return null\n}\n\n/**\n * 根据页面域名查找并注入对应的工具 bundle\n * bundle 文件位于 dist/webmcp-tools/{hostname}.js\n * 注意:每个工具 bundle 内部会在 window 上设置 __webmcptools_{domain} flag 防止重复注册,\n * 此处在 JS 层额外做一次快速检查,避免每次都 evaluate 完整脚本带来不必要的 IPC 开销。\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 bundleName = getToolsBundleName(hostname)\n if (!bundleName) {\n return // 没有对应的工具预置,跳过\n }\n\n const toolBundlePath = path.resolve(__dirname, bundleName)\n if (!fs.existsSync(toolBundlePath)) {\n return // 文件不存在,跳过\n }\n\n // 快速检查:若页面已注入,直接返回,省去读文件和 evaluate 的开销\n const bundleKey = bundleName.replace(/^webmcp-tools\\//, '').replace(/\\.js$/, '')\n const flagKey = `__webmcptools_${bundleKey.replace(/[^a-zA-Z0-9]/g, '')}`\n const alreadyInjected = await page.evaluate((key: string) => !!(window as any)[key], flagKey).catch(() => false)\n if (alreadyInjected) {\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\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 // 不获取 DOM content(由调用方按需通过 browserState 拉取)\n const state = await page.evaluate(async () => {\n const url = document.URL\n const title = document.title\n\n const mcp = (document as any).modelContext\n let webmcpTools: any[] = []\n\n if (mcp && typeof mcp.listTools === 'function') {\n const toolsResult = await mcp.getTools()\n webmcpTools = toolsResult?.tools || toolsResult || []\n }\n\n return { url, title, webmcpTools }\n })\n\n const activeTabid = await getPageTargetId(page).catch(() => null)\n\n // 3. 获取所有标签页信息(排除 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 activeTabid,\n tabs: tabs.filter(Boolean)\n }\n } finally {\n await browser.disconnect()\n }\n}\n","import type { Page } from 'puppeteer-core'\nimport os from 'os'\nimport {\n decodeBase64Content,\n isZhihuWriteUrl,\n markdownToHtml,\n stripLeadingH1\n} from './markdown'\n\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nfunction getPasteModifier(): 'Meta' | 'Control' {\n return os.platform() === 'darwin' ? 'Meta' : 'Control'\n}\n\n/** 通过 Puppeteer 设置标题 */\nasync function fillTitle(page: Page, title: string): Promise<void> {\n await page.evaluate((titleText) => {\n const input =\n (document.querySelector('textarea[placeholder*=\"标题\"]') as HTMLTextAreaElement | null) ||\n (document.querySelector('.WriteIndex-titleInput textarea') as HTMLTextAreaElement | null) ||\n (document.querySelector('.WriteIndex-titleInput input') as HTMLInputElement | null)\n\n if (!input) throw new Error('未找到标题输入框,请确认编辑器页面已完全加载')\n\n const setter = Object.getOwnPropertyDescriptor(\n input instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,\n 'value'\n )?.set\n\n if (setter) {\n setter.call(input, titleText)\n } else {\n input.value = titleText\n }\n input.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: titleText }))\n input.dispatchEvent(new Event('change', { bubbles: true }))\n\n if (input.value.trim() !== titleText.trim()) {\n throw new Error('标题填写失败,请刷新页面后重试')\n }\n }, title)\n}\n\n/** 通过临时页面复制 HTML 到系统剪贴板,再键盘粘贴到 Draft.js 编辑器 */\nasync function pasteHtmlToEditor(page: Page, html: string): Promise<void> {\n const browser = page.browser()\n const tempPage = await browser.newPage()\n const modifier = getPasteModifier()\n\n try {\n const fullHtml = `<!DOCTYPE html><html><head><meta charset=\"utf-8\"></head><body>${html}</body></html>`\n await tempPage.setContent(fullHtml, { waitUntil: 'domcontentloaded' })\n await delay(300)\n\n await tempPage.click('body')\n await tempPage.keyboard.down(modifier)\n await tempPage.keyboard.press('KeyA')\n await tempPage.keyboard.up(modifier)\n await delay(100)\n await tempPage.keyboard.down(modifier)\n await tempPage.keyboard.press('KeyC')\n await tempPage.keyboard.up(modifier)\n await delay(300)\n } finally {\n await tempPage.close()\n }\n\n await page.bringToFront()\n await page.waitForSelector('.public-DraftEditor-content', { timeout: 10000 })\n await page.click('.public-DraftEditor-content')\n await delay(200)\n\n await page.keyboard.down(modifier)\n await page.keyboard.press('KeyA')\n await page.keyboard.up(modifier)\n await delay(100)\n await page.keyboard.press('Backspace')\n await delay(200)\n\n await page.keyboard.down(modifier)\n await page.keyboard.press('KeyV')\n await page.keyboard.up(modifier)\n\n await delay(1500)\n\n // 用正文纯文本前 15 字作为探针,验证粘贴完整性\n const probe = html.replace(/<[^>]+>/g, '').trim().slice(0, 15)\n const verifyResult = await page.evaluate((probeText) => {\n const editor = document.querySelector('.public-DraftEditor-content') as HTMLElement | null\n if (!editor) return { ok: false, reason: 'no editor' }\n const text = (editor.innerText || '').trim()\n if (!text) return { ok: false, reason: 'empty' }\n const inner = editor.innerHTML\n const hasRichFormat =\n inner.includes('data-text') ||\n /font-weight:\\s*bold|font-style:\\s*italic|code-block|blockquote/i.test(inner)\n const normalizedText = text.replace(/\\s+/g, '')\n const normalizedProbe = probeText.replace(/\\s+/g, '')\n const hasProbe = normalizedText.includes(normalizedProbe)\n return {\n ok: text.length > 20 && hasProbe && (hasRichFormat || text.length > 50),\n reason: !hasProbe ? 'probe mismatch' : !hasRichFormat && text.length <= 50 ? 'no rich format' : undefined\n }\n }, probe)\n\n if (!verifyResult.ok) {\n throw new Error(\n `正文填写失败(Markdown 转 HTML 粘贴未生效)${verifyResult.reason ? `: ${verifyResult.reason}` : ''}`\n )\n }\n}\n\nexport async function zhihuCreateArticle(\n page: Page,\n args: { title?: string; content?: string }\n): Promise<{ content: Array<{ type: 'text'; text: string }> }> {\n const { title, content } = args\n\n if (!title?.trim()) throw new Error('参数 title 不能为空')\n if (!content?.trim()) throw new Error('参数 content 不能为空')\n\n const url = page.url()\n if (!isZhihuWriteUrl(url)) {\n throw new Error('当前页面不是知乎专栏编辑器,请先打开 https://zhuanlan.zhihu.com/write')\n }\n\n let markdown: string\n try {\n markdown = stripLeadingH1(decodeBase64Content(content))\n } catch {\n throw new Error('content 不是有效的 Base64 编码,请检查参数或使用 @base64file: 引用文件')\n }\n\n if (!markdown.trim()) {\n throw new Error('正文内容为空,请检查 Markdown 文件')\n }\n\n const html = markdownToHtml(markdown)\n\n await fillTitle(page, title.trim())\n await delay(500)\n await pasteHtmlToEditor(page, html)\n\n const result = {\n success: true,\n message: '文章标题和正文已成功填写到知乎专栏编辑器(Markdown 已转为富文本),草稿将自动保存',\n title: title.trim(),\n contentLength: markdown.length\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result) }]\n }\n}\n","import { Marked } from 'marked'\n\n/** 移除正文首个 H1(标题已通过 title 参数单独填写) */\nexport function stripLeadingH1(markdown: string): string {\n return markdown.replace(/^#\\s+[^\\n]*\\n+/, '')\n}\n\n/** Markdown → HTML(知乎 Draft.js 编辑器兼容) */\nexport function markdownToHtml(markdown: string): string {\n const marked = new Marked({\n gfm: true,\n breaks: false,\n renderer: {\n code({ text, lang }) {\n const langClass = lang ? ` class=\"language-${lang}\"` : ''\n const escaped = text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n return `<pre><code${langClass}>${escaped}</code></pre>`\n },\n codespan({ text }) {\n const escaped = text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n return `<code>${escaped}</code>`\n }\n }\n })\n\n return marked.parse(markdown) as string\n}\n\nexport function decodeBase64Content(content: string): string {\n return Buffer.from(content, 'base64').toString('utf-8')\n}\n\n/** 知乎专栏编辑器 URL 匹配 */\nexport const ZHIHU_WRITE_PAGE_RE = /^https:\\/\\/zhuanlan\\.zhihu\\.com\\/(write|p\\/\\d+\\/edit)/\n\nexport function isZhihuWriteUrl(url: string): boolean {\n return ZHIHU_WRITE_PAGE_RE.test(url)\n}\n","import { connectBrowser, getTargetPage } from '../browser'\nimport { zhihuCreateArticle } from '../zhihu/create-article'\nimport { isZhihuWriteUrl } from '../zhihu/markdown'\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 const urlBefore = page.url()\n\n // argsJson 已在 bin.ts 中完成 @base64file 展开与 JSON 校验\n const cleanedArgs = argsJson.trim()\n\n // 知乎专栏:在 Node 端完成 Markdown→HTML 转换 + 真实剪贴板粘贴(Draft.js 兼容)\n if (toolName === 'create_article') {\n let parsedArgs: { title?: string; content?: string }\n try {\n parsedArgs = JSON.parse(cleanedArgs)\n } catch {\n throw new Error('参数不是有效的 JSON')\n }\n if (isZhihuWriteUrl(page.url())) {\n return await zhihuCreateArticle(page, parsedArgs)\n }\n }\n\n let result: any\n try {\n result = await page.evaluate(async (name, inputString) => {\n // @ts-expect-error WebMCP APIs are experimental and not yet in DOM types\n const mcp = document.modelContext || document.modelContext || document.modelContext\n\n if (!mcp || typeof mcp.executeTool !== 'function') {\n throw new Error('当前页面没有注入 WebMCP 环境 (document.modelContext 未找到)')\n }\n\n // executeTool 的第二个参数必须是 JSON 字符串\n const tools = await mcp.getTools();\n const toolObj = tools.find((t: any) => t.name === name);\n if (!toolObj) throw new Error(`Tool ${name} not found`);\n let res = await mcp.executeTool(toolObj, inputString)\n\n // executeTool 的返回值可能是普通对象,也可能是 JSON 字符串\n if (typeof res === 'string') {\n try {\n res = JSON.parse(res)\n } catch {\n // 保留原始字符串\n }\n }\n\n if (res === undefined || res === null) {\n throw new Error('工具 execute 未返回结果,请检查工具实现是否缺少 return')\n }\n\n if (typeof res === 'object' && (res as { success?: boolean }).success === false) {\n const failed = res as { error?: string; message?: string }\n throw new Error(failed.error || failed.message || '工具执行失败')\n }\n\n return res\n }, toolName, cleanedArgs)\n } catch (evalError: unknown) {\n const errMsg = evalError instanceof Error ? evalError.message : String(evalError)\n\n // 识别\"执行上下文已销毁\"错误:这通常是工具执行期间页面发生了正常导航(如发布后跳转)\n // 此时工具实际上已经成功执行,跳转本身就是成功的副作用,不应报错\n const isContextDestroyed =\n errMsg.includes('context was destroyed') ||\n errMsg.includes('Execution context was destroyed') ||\n errMsg.includes('Cannot read properties of null') && errMsg.includes('context')\n\n if (isContextDestroyed) {\n // 等待导航稳定,然后读取新页面 URL\n await new Promise(resolve => setTimeout(resolve, 800))\n let newUrl = ''\n try {\n // 重新读取页面(导航后 JS 上下文虽被销毁,但 page 实例本身的 url() 依然是最新的)\n newUrl = page.url()\n } catch { /* 忽略 */ }\n\n // 如果 URL 已经发生变化,认为是正常导航触发的上下文销毁(工具执行成功)\n if (newUrl && newUrl !== urlBefore) {\n return {\n success: true,\n message: `工具 ${toolName} 执行完成,页面已导航至 ${newUrl}`,\n navigatedTo: newUrl\n }\n }\n // URL 未变化,可能是真正的错误,重新抛出\n }\n\n throw evalError\n }\n\n return result\n } finally {\n await browser.disconnect()\n }\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,4BAAyC;AACzC,wBAAe;AACf,gBAAe;AACf,gBAAe;AACf,kBAAiB;AACjB,kBAAiB;AACjB,2BAAsB;AACtB,iBAA8B;AAP9B;AASA,IAAM,iBAAa,0BAAc,YAAY,GAAG;AAChD,IAAM,YAAY,YAAAA,QAAK,QAAQ,UAAU;AAEzC,IAAM,WAAW;AAEjB,IAAM,UAAU,oBAAoB,QAAQ;AAE5C,SAAS,kBAA0B;AACjC,SAAO,QAAQ,IAAI,oBAAoB,YAAAA,QAAK,KAAK,UAAAC,QAAG,QAAQ,GAAG,wBAAwB;AACzF;AAEA,SAAS,uBAA+B;AACtC,SAAO,YAAAD,QAAK,KAAK,gBAAgB,GAAG,cAAc;AACpD;AAeA,SAAS,qBAAoC;AAC3C,MAAI;AACF,UAAM,QAAQ,UAAAE,QAAG,aAAa,qBAAqB,GAAG,OAAO,EAAE,KAAK;AACpE,WAAO,SAAS;AAAA,EAClB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,SAAS,QAAQ,KAAa,YAAY,MAAuB;AAC/D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,MAAM,YAAAC,QAAK,IAAI,KAAK,EAAE,SAAS,UAAU,GAAG,CAAC,QAAQ;AACzD,UAAI,OAAO;AACX,UAAI,GAAG,QAAQ,CAAC,UAAkB;AAAE,gBAAQ,MAAM,SAAS;AAAA,MAAE,CAAC;AAC9D,UAAI,GAAG,OAAO,MAAM;AAClB,YAAI,IAAI,cAAc,IAAI,cAAc,OAAO,IAAI,aAAa,KAAK;AACnE,kBAAQ,IAAI;AAAA,QACd,OAAO;AACL,iBAAO,IAAI,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;AAAA,QAC5C;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AACD,QAAI,GAAG,SAAS,MAAM;AACtB,QAAI,GAAG,WAAW,MAAM;AAAE,UAAI,QAAQ;AAAG,aAAO,IAAI,MAAM,iBAAiB,CAAC;AAAA,IAAE,CAAC;AAAA,EACjF,CAAC;AACH;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;AACD,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,QAAQ,KAAK,IAAI;AACvB,aAAO;AAAA,IACT,QAAQ;AAAA,IAAC;AACT,QAAI,IAAI,UAAU,GAAG;AACnB,YAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,GAAG,CAAC;AAAA,IAC3C;AAAA,EACF;AACA,SAAO;AACT;AAKA,eAAe,oBAAoB,WAAqB,UAAU,GAA2B;AAC3F,aAAW,QAAQ,WAAW;AAC5B,QAAI,MAAM,cAAc,MAAM,OAAO,GAAG;AACtC,aAAO,KAAK,QAAQ,iBAAiB,EAAE;AAAA,IACzC;AAAA,EACF;AACA,SAAO;AACT;AAOA,eAAe,iBAAiB,gBAAwB,UAAU,GAAG,cAAc,GAAqB;AACtG,MAAI;AACF,WAAO,MAAM;AAAA,MACX,sBAAAC,QAAU,QAAQ,EAAE,YAAY,gBAAgB,iBAAiB,KAAK,CAAC;AAAA,MACvE;AAAA,MACA,wBAAwB,cAAc;AAAA,IACxC;AAAA,EACF,SAAS,KAAK;AACZ,QAAI,WAAW,aAAa;AAC1B,YAAM;AAAA,IACR;AACA,UAAM,UAAU,MAAM;AACtB,YAAQ,IAAI,kBAAAC,QAAG,OAAO,0BAAqB,OAAO,wCAAU,OAAO,0BAAW,CAAC;AAC/E,UAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,OAAO,CAAC;AACzD,WAAO,iBAAiB,gBAAgB,UAAU,GAAG,WAAW;AAAA,EAClE;AACF;AASA,SAAS,wBAA4C;AACnD,QAAM,WAAW,UAAAC,QAAG,SAAS;AAC7B,MAAI,aAAa,UAAU;AAEzB,UAAM,aAAa;AACnB,QAAI,UAAAJ,QAAG,WAAW,UAAU,EAAG,QAAO,EAAE,MAAM,YAAY,MAAM,SAAS;AACzE,UAAM,WAAW;AACjB,QAAI,UAAAA,QAAG,WAAW,QAAQ,EAAG,QAAO,EAAE,MAAM,UAAU,MAAM,OAAO;AACnE,WAAO;AAAA,EACT,WAAW,aAAa,SAAS;AAE/B,UAAM,cAAc;AAAA,MAClB,QAAQ,IAAI,eAAe;AAAA,MAC3B,QAAQ,IAAI,eAAe;AAAA,MAC3B,QAAQ,IAAI,mBAAmB,IAAI;AAAA,IACrC;AACA,UAAM,cAAc,YAAY,KAAK,OAAK,UAAAA,QAAG,WAAW,CAAC,CAAC;AAC1D,QAAI,YAAa,QAAO,EAAE,MAAM,aAAa,MAAM,SAAS;AAG5D,UAAM,YAAY;AAAA,MAChB,QAAQ,IAAI,eAAe;AAAA,MAC3B,QAAQ,IAAI,eAAe;AAAA,MAC3B,QAAQ,IAAI,mBAAmB,IAAI;AAAA,MACnC;AAAA,MACA;AAAA,IACF;AACA,UAAM,YAAY,UAAU,KAAK,OAAK,UAAAA,QAAG,WAAW,CAAC,CAAC;AACtD,QAAI,UAAW,QAAO,EAAE,MAAM,WAAW,MAAM,OAAO;AACtD,WAAO;AAAA,EACT,OAAO;AAEL,UAAM,QAA4C;AAAA,MAChD,EAAE,GAAG,0BAA0B,MAAM,SAAS;AAAA,MAC9C,EAAE,GAAG,iCAAiC,MAAM,SAAS;AAAA,MACrD,EAAE,GAAG,qBAAqB,MAAM,WAAW;AAAA,MAC3C,EAAE,GAAG,6BAA6B,MAAM,WAAW;AAAA,MACnD,EAAE,GAAG,2BAA2B,MAAM,OAAO;AAAA,MAC7C,EAAE,GAAG,kCAAkC,MAAM,OAAO;AAAA,IACtD;AACA,UAAM,QAAQ,MAAM,KAAK,CAAC,EAAE,EAAE,MAAM,UAAAA,QAAG,WAAW,CAAC,CAAC;AACpD,WAAO,QAAQ,EAAE,MAAM,MAAM,GAAG,MAAM,MAAM,KAAK,IAAI;AAAA,EACvD;AACF;AAEA,eAAe,2BAA0C;AACvD,QAAM,cAAc,sBAAsB;AAC1C,MAAI,CAAC,eAAe,CAAC,UAAAA,QAAG,WAAW,YAAY,IAAI,GAAG;AACpD,UAAM,IAAI,MAAM,wIAAoC;AAAA,EACtD;AAEA,UAAQ,IAAI,kBAAAG,QAAG,OAAO,wCAAU,YAAY,IAAI,gCAAY,QAAQ,MAAM,CAAC;AAG3E,QAAM,cAAc,QAAQ,IAAI,oBAAoB,YAAAE,QAAK,KAAK,UAAAD,QAAG,QAAQ,GAAG,wBAAwB;AAEpG,QAAM,aAAa;AAAA,IACjB,2BAA2B,QAAQ;AAAA,IACnC,mBAAmB,WAAW;AAAA,IAC9B;AAAA,IACA;AAAA,EACF;AAEA,MAAI,UAAAA,QAAG,SAAS,MAAM,SAAS;AAC7B,eAAW,KAAK,sCAAsC;AAAA,EACxD;AAEA,QAAM,YAAQ;AAAA,IACZ,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,MACE,UAAU;AAAA,MACV,OAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,MAAM;AAGZ,QAAM,WAAW,CAAC,oBAAoB,QAAQ,iBAAiB,oBAAoB,QAAQ,eAAe;AAC1G,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,eAAW,OAAO,UAAU;AAC1B,UAAI;AACF,cAAM,QAAQ,KAAK,GAAI;AACvB,gBAAQ,IAAI,kBAAAD,QAAG,MAAM,GAAG,YAAY,IAAI,uCAAS,CAAC;AAElD,cAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,GAAG,CAAC;AACrD;AAAA,MACF,QAAQ;AAAA,MAAC;AAAA,IACX;AACA,UAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,GAAG,CAAC;AAAA,EACvD;AAEA,QAAM,IAAI,MAAM,GAAG,YAAY,IAAI,sFAAqB;AAC1D;AAEA,eAAsB,iBAAmC;AAEvD,QAAM,mBAAmB;AAAA,IACvB,oBAAoB,QAAQ;AAAA,IAC5B,oBAAoB,QAAQ;AAAA,EAC9B;AAEA,QAAM,cAAc,MAAM,oBAAoB,kBAAkB,CAAC;AACjE,MAAI,aAAa;AACf,YAAQ,IAAI,kBAAAA,QAAG,OAAO,kDAAyB,QAAQ,qDAAa,WAAW,KAAK,CAAC;AACrF,QAAI;AACF,YAAM,UAAU,MAAM,iBAAiB,WAAW;AAClD,cAAQ,IAAI,kBAAAA,QAAG,MAAM,4CAAwB,WAAW,EAAE,CAAC;AAC3D,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,YAAM,IAAI;AAAA,QACR,uFAAiB,WAAW,eAAK,GAAG;AAAA,MAEtC;AAAA,IACF;AAAA,EACF;AAGA,UAAQ,IAAI,kBAAAA,QAAG,OAAO,gCAAsB,QAAQ,gGAAqB,CAAC;AAC1E,MAAI;AACF,UAAM,yBAAyB;AAAA,EACjC,SAAS,aAAsB;AAC7B,UAAM,MAAM,uBAAuB,QAAQ,YAAY,UAAU,OAAO,WAAW;AACnF,YAAQ,MAAM,kBAAAA,QAAG,IAAI,+CAAY,GAAG,EAAE,CAAC;AACvC,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC1C;AAGA,QAAM,cAAc,MAAM,oBAAoB,kBAAkB,CAAC;AACjE,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,sEAAe,QAAQ,iFAA0B;AAAA,EACnE;AAEA,MAAI;AACF,YAAQ,IAAI,kBAAAA,QAAG,OAAO,sFAA+B,WAAW,KAAK,CAAC;AACtE,UAAM,UAAU,MAAM,iBAAiB,WAAW;AAClD,YAAQ,IAAI,kBAAAA,QAAG,MAAM,4CAAwB,WAAW,EAAE,CAAC;AAC3D,WAAO;AAAA,EACT,QAAQ;AACN,UAAM,IAAI,MAAM,sEAAe,QAAQ,iFAA0B;AAAA,EACnE;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,UAAM,YAAY,mBAAmB;AACrC,QAAI,WAAW;AACb,YAAM,aAAa,sBAAsB,SAAS,SAAS;AAC3D,UAAI,YAAY;AACd,qBAAa,MAAM,WAAW,KAAK;AAAA,MACrC;AAAA,IACF;AAGA,QAAI,CAAC,YAAY;AACf,UAAI;AACF,cAAM,OAAO;AAAA,UACX,oBAAoB,QAAQ;AAAA,UAC5B,oBAAoB,QAAQ;AAAA,QAC9B;AACA,YAAI,iBAAgC;AACpC,mBAAW,OAAO,MAAM;AACtB,cAAI;AACF,kBAAM,OAAO,MAAM,QAAQ,KAAK,GAAI;AACpC,kBAAM,cAAgE,KAAK,MAAM,IAAI;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,QAAQ;AAAA,UAAoB;AAAA,QAC9B;AAEA,YAAI,gBAAgB;AAClB,qBAAW,UAAU,aAAa;AAChC,gBAAI,sBAAsB,MAAM,MAAM,gBAAgB;AACpD,2BAAa,MAAM,OAAO,KAAK;AAC/B;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAAuB;AAAA,IACjC;AAGA,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;AAKA,eAAsB,eAAe,MAA2B;AAC9D,QAAM,6BAA6B,MAAM,IAAI;AAC/C;AAEA,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,kBAAAG,QAAG,KAAK,+HAAgC,CAAC;AAErD,UAAM,mBAAmB,YAAAC,QAAK,QAAQ,WAAW,kBAAkB;AACnE,QAAI,CAAC,UAAAC,QAAG,WAAW,gBAAgB,GAAG;AACpC,YAAM,IAAI,MAAM,mCAAmC,gBAAgB,oDAAoD;AAAA,IACzH;AAEA,UAAM,gBAAgB,UAAAA,QAAG,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,KAAK,gBAAgB,MAAM;AAC/B,YAAM,MAAO,UAAkB;AAC/B,aAAO,OAAO,OAAO,IAAI,iBAAiB;AAAA,IAC5C,GAAG,EAAE,SAAS,IAAM,CAAC,EAAE,MAAM,MAAM;AACjC,cAAQ,KAAK,oHAAyC;AAAA,IACxD,CAAC;AAAA,EACH;AAGA,QAAM,kBAAkB,IAAI;AAC9B;AAEA,SAAS,mBAAmB,UAAiC;AAC3D,QAAM,aAAa,SAAS,MAAM,GAAG,EAAE,CAAC,EAAE,YAAY;AACtD,QAAM,WAAW,YAAAD,QAAK,QAAQ,WAAW,cAAc;AACvD,MAAI,CAAC,UAAAC,QAAG,WAAW,QAAQ,GAAG;AAC5B,WAAO;AAAA,EACT;AACA,MAAI,QAAkB,CAAC;AACvB,MAAI;AACF,YAAQ,UAAAA,QAAG,YAAY,QAAQ;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,mBAAmB,MACtB,OAAO,OAAK,EAAE,SAAS,KAAK,CAAC,EAC7B,IAAI,OAAK,EAAE,MAAM,GAAG,EAAE,CAAC;AAE1B,MAAI,iBAAiB,SAAS,UAAU,GAAG;AACzC,WAAO,gBAAgB,UAAU;AAAA,EACnC;AACA,aAAW,UAAU,kBAAkB;AACrC,QAAI,WAAW,SAAS,MAAM,MAAM,GAAG;AACrC,aAAO,gBAAgB,MAAM;AAAA,IAC/B;AAAA,EACF;AACA,SAAO;AACT;AAQA,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,aAAa,mBAAmB,QAAQ;AAC9C,MAAI,CAAC,YAAY;AACf;AAAA,EACF;AAEA,QAAM,iBAAiB,YAAAD,QAAK,QAAQ,WAAW,UAAU;AACzD,MAAI,CAAC,UAAAC,QAAG,WAAW,cAAc,GAAG;AAClC;AAAA,EACF;AAGA,QAAM,YAAY,WAAW,QAAQ,mBAAmB,EAAE,EAAE,QAAQ,SAAS,EAAE;AAC/E,QAAM,UAAU,iBAAiB,UAAU,QAAQ,iBAAiB,EAAE,CAAC;AACvE,QAAM,kBAAkB,MAAM,KAAK,SAAS,CAAC,QAAgB,CAAC,CAAE,OAAe,GAAG,GAAG,OAAO,EAAE,MAAM,MAAM,KAAK;AAC/G,MAAI,iBAAiB;AACnB;AAAA,EACF;AAEA,UAAQ,IAAI,kBAAAF,QAAG,KAAK,kCAAS,QAAQ,kEAAgB,CAAC;AAEtD,QAAM,aAAa,UAAAE,QAAG,aAAa,gBAAgB,OAAO;AAC1D,MAAI;AACF,UAAM,KAAK,SAAS,UAAU;AAC9B,YAAQ,IAAI,kBAAAF,QAAG,MAAM,gBAAM,QAAQ,uCAAS,CAAC;AAAA,EAC/C,SAAS,KAAc;AACrB,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAE3D,YAAQ,KAAK,kBAAAA,QAAG,OAAO,qDAAa,QAAQ,MAAM,GAAG,EAAE,CAAC;AAAA,EAC1D;AACF;;;AClhBA,eAAsB,aAAa,EAAE,MAAM,GAAuB;AAChE,QAAM,UAAU,MAAM,eAAe;AACrC,MAAI;AACF,UAAM,OAAO,MAAM,cAAc,SAAS,KAAK;AAG/C,UAAM,eAAe,IAAI;AAIzB,UAAM,QAAQ,MAAM,KAAK,SAAS,YAAY;AAC5C,YAAM,MAAM,SAAS;AACrB,YAAM,QAAQ,SAAS;AAEvB,YAAM,MAAO,SAAiB;AAC9B,UAAI,cAAqB,CAAC;AAE1B,UAAI,OAAO,OAAO,IAAI,cAAc,YAAY;AAC9C,cAAM,cAAc,MAAM,IAAI,SAAS;AACvC,sBAAc,aAAa,SAAS,eAAe,CAAC;AAAA,MACtD;AAEA,aAAO,EAAE,KAAK,OAAO,YAAY;AAAA,IACnC,CAAC;AAED,UAAM,cAAc,MAAM,gBAAgB,IAAI,EAAE,MAAM,MAAM,IAAI;AAGhE,UAAM,QAAQ,MAAM,QAAQ,MAAM;AAClC,UAAM,OAAO,MAAM,QAAQ;AAAA,MACzB,MAAM,IAAI,OAAO,MAAM;AACrB,cAAM,OAAO,EAAE,IAAI;AACnB,YAAI,KAAK,WAAW,aAAa,EAAG,QAAO;AAE3C,cAAM,SAAS,MAAM,QAAQ,KAAK;AAAA,UAChC,EAAE,MAAM,EAAE,MAAM,MAAM,SAAS;AAAA,UAC/B,IAAI,QAAgB,CAAC,YAAY,WAAW,MAAM,QAAQ,SAAS,GAAG,GAAG,CAAC;AAAA,QAC5E,CAAC;AAED,eAAO;AAAA;AAAA,UAEL,OAAO,MAAM,gBAAgB,CAAC,EAAE,MAAM,MAAM,IAAI;AAAA,UAChD,OAAO;AAAA,UACP,KAAK;AAAA,QACP;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,MACL,GAAG;AAAA,MACH;AAAA,MACA,MAAM,KAAK,OAAO,OAAO;AAAA,IAC3B;AAAA,EACF,UAAE;AACA,UAAM,QAAQ,WAAW;AAAA,EAC3B;AACF;;;ACzDA,IAAAG,aAAe;;;ACDf,oBAAuB;AAGhB,SAAS,eAAe,UAA0B;AACvD,SAAO,SAAS,QAAQ,kBAAkB,EAAE;AAC9C;AAGO,SAAS,eAAe,UAA0B;AACvD,QAAM,SAAS,IAAI,qBAAO;AAAA,IACxB,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,UAAU;AAAA,MACR,KAAK,EAAE,MAAM,KAAK,GAAG;AACnB,cAAM,YAAY,OAAO,oBAAoB,IAAI,MAAM;AACvD,cAAM,UAAU,KACb,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM;AACvB,eAAO,aAAa,SAAS,IAAI,OAAO;AAAA,MAC1C;AAAA,MACA,SAAS,EAAE,KAAK,GAAG;AACjB,cAAM,UAAU,KACb,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM;AACvB,eAAO,SAAS,OAAO;AAAA,MACzB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,OAAO,MAAM,QAAQ;AAC9B;AAEO,SAAS,oBAAoB,SAAyB;AAC3D,SAAO,OAAO,KAAK,SAAS,QAAQ,EAAE,SAAS,OAAO;AACxD;AAGO,IAAM,sBAAsB;AAE5B,SAAS,gBAAgB,KAAsB;AACpD,SAAO,oBAAoB,KAAK,GAAG;AACrC;;;ADlCA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AACvD;AAEA,SAAS,mBAAuC;AAC9C,SAAO,WAAAC,QAAG,SAAS,MAAM,WAAW,SAAS;AAC/C;AAGA,eAAe,UAAU,MAAY,OAA8B;AACjE,QAAM,KAAK,SAAS,CAAC,cAAc;AACjC,UAAM,QACH,SAAS,cAAc,uCAA6B,KACpD,SAAS,cAAc,iCAAiC,KACxD,SAAS,cAAc,8BAA8B;AAExD,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,sIAAwB;AAEpD,UAAM,SAAS,OAAO;AAAA,MACpB,iBAAiB,sBAAsB,oBAAoB,YAAY,iBAAiB;AAAA,MACxF;AAAA,IACF,GAAG;AAEH,QAAI,QAAQ;AACV,aAAO,KAAK,OAAO,SAAS;AAAA,IAC9B,OAAO;AACL,YAAM,QAAQ;AAAA,IAChB;AACA,UAAM,cAAc,IAAI,WAAW,SAAS,EAAE,SAAS,MAAM,YAAY,MAAM,MAAM,UAAU,CAAC,CAAC;AACjG,UAAM,cAAc,IAAI,MAAM,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC;AAE1D,QAAI,MAAM,MAAM,KAAK,MAAM,UAAU,KAAK,GAAG;AAC3C,YAAM,IAAI,MAAM,4FAAiB;AAAA,IACnC;AAAA,EACF,GAAG,KAAK;AACV;AAGA,eAAe,kBAAkB,MAAY,MAA6B;AACxE,QAAM,UAAU,KAAK,QAAQ;AAC7B,QAAM,WAAW,MAAM,QAAQ,QAAQ;AACvC,QAAM,WAAW,iBAAiB;AAElC,MAAI;AACF,UAAM,WAAW,iEAAiE,IAAI;AACtF,UAAM,SAAS,WAAW,UAAU,EAAE,WAAW,mBAAmB,CAAC;AACrE,UAAM,MAAM,GAAG;AAEf,UAAM,SAAS,MAAM,MAAM;AAC3B,UAAM,SAAS,SAAS,KAAK,QAAQ;AACrC,UAAM,SAAS,SAAS,MAAM,MAAM;AACpC,UAAM,SAAS,SAAS,GAAG,QAAQ;AACnC,UAAM,MAAM,GAAG;AACf,UAAM,SAAS,SAAS,KAAK,QAAQ;AACrC,UAAM,SAAS,SAAS,MAAM,MAAM;AACpC,UAAM,SAAS,SAAS,GAAG,QAAQ;AACnC,UAAM,MAAM,GAAG;AAAA,EACjB,UAAE;AACA,UAAM,SAAS,MAAM;AAAA,EACvB;AAEA,QAAM,KAAK,aAAa;AACxB,QAAM,KAAK,gBAAgB,+BAA+B,EAAE,SAAS,IAAM,CAAC;AAC5E,QAAM,KAAK,MAAM,6BAA6B;AAC9C,QAAM,MAAM,GAAG;AAEf,QAAM,KAAK,SAAS,KAAK,QAAQ;AACjC,QAAM,KAAK,SAAS,MAAM,MAAM;AAChC,QAAM,KAAK,SAAS,GAAG,QAAQ;AAC/B,QAAM,MAAM,GAAG;AACf,QAAM,KAAK,SAAS,MAAM,WAAW;AACrC,QAAM,MAAM,GAAG;AAEf,QAAM,KAAK,SAAS,KAAK,QAAQ;AACjC,QAAM,KAAK,SAAS,MAAM,MAAM;AAChC,QAAM,KAAK,SAAS,GAAG,QAAQ;AAE/B,QAAM,MAAM,IAAI;AAGhB,QAAM,QAAQ,KAAK,QAAQ,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,EAAE;AAC7D,QAAM,eAAe,MAAM,KAAK,SAAS,CAAC,cAAc;AACtD,UAAM,SAAS,SAAS,cAAc,6BAA6B;AACnE,QAAI,CAAC,OAAQ,QAAO,EAAE,IAAI,OAAO,QAAQ,YAAY;AACrD,UAAM,QAAQ,OAAO,aAAa,IAAI,KAAK;AAC3C,QAAI,CAAC,KAAM,QAAO,EAAE,IAAI,OAAO,QAAQ,QAAQ;AAC/C,UAAM,QAAQ,OAAO;AACrB,UAAM,gBACJ,MAAM,SAAS,WAAW,KAC1B,kEAAkE,KAAK,KAAK;AAC9E,UAAM,iBAAiB,KAAK,QAAQ,QAAQ,EAAE;AAC9C,UAAM,kBAAkB,UAAU,QAAQ,QAAQ,EAAE;AACpD,UAAM,WAAW,eAAe,SAAS,eAAe;AACxD,WAAO;AAAA,MACL,IAAI,KAAK,SAAS,MAAM,aAAa,iBAAiB,KAAK,SAAS;AAAA,MACpE,QAAQ,CAAC,WAAW,mBAAmB,CAAC,iBAAiB,KAAK,UAAU,KAAK,mBAAmB;AAAA,IAClG;AAAA,EACF,GAAG,KAAK;AAER,MAAI,CAAC,aAAa,IAAI;AACpB,UAAM,IAAI;AAAA,MACR,sGAAgC,aAAa,SAAS,KAAK,aAAa,MAAM,KAAK,EAAE;AAAA,IACvF;AAAA,EACF;AACF;AAEA,eAAsB,mBACpB,MACA,MAC6D;AAC7D,QAAM,EAAE,OAAO,QAAQ,IAAI;AAE3B,MAAI,CAAC,OAAO,KAAK,EAAG,OAAM,IAAI,MAAM,6CAAe;AACnD,MAAI,CAAC,SAAS,KAAK,EAAG,OAAM,IAAI,MAAM,+CAAiB;AAEvD,QAAM,MAAM,KAAK,IAAI;AACrB,MAAI,CAAC,gBAAgB,GAAG,GAAG;AACzB,UAAM,IAAI,MAAM,+IAAqD;AAAA,EACvE;AAEA,MAAI;AACJ,MAAI;AACF,eAAW,eAAe,oBAAoB,OAAO,CAAC;AAAA,EACxD,QAAQ;AACN,UAAM,IAAI,MAAM,wJAAoD;AAAA,EACtE;AAEA,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,UAAM,IAAI,MAAM,oFAAwB;AAAA,EAC1C;AAEA,QAAM,OAAO,eAAe,QAAQ;AAEpC,QAAM,UAAU,MAAM,MAAM,KAAK,CAAC;AAClC,QAAM,MAAM,GAAG;AACf,QAAM,kBAAkB,MAAM,IAAI;AAElC,QAAM,SAAS;AAAA,IACb,SAAS;AAAA,IACT,SAAS;AAAA,IACT,OAAO,MAAM,KAAK;AAAA,IAClB,eAAe,SAAS;AAAA,EAC1B;AAEA,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,KAAK,UAAU,MAAM,EAAE,CAAC;AAAA,EACnE;AACF;;;AExJA,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;AAC/C,UAAM,YAAY,KAAK,IAAI;AAG3B,UAAM,cAAc,SAAS,KAAK;AAGlC,QAAI,aAAa,kBAAkB;AACjC,UAAI;AACJ,UAAI;AACF,qBAAa,KAAK,MAAM,WAAW;AAAA,MACrC,QAAQ;AACN,cAAM,IAAI,MAAM,iDAAc;AAAA,MAChC;AACA,UAAI,gBAAgB,KAAK,IAAI,CAAC,GAAG;AAC/B,eAAO,MAAM,mBAAmB,MAAM,UAAU;AAAA,MAClD;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,KAAK,SAAS,OAAO,MAAM,gBAAgB;AAExD,cAAM,MAAM,SAAS,gBAAgB,SAAS,gBAAgB,SAAS;AAEvE,YAAI,CAAC,OAAO,OAAO,IAAI,gBAAgB,YAAY;AACjD,gBAAM,IAAI,MAAM,iHAAgD;AAAA,QAClE;AAGA,cAAM,QAAQ,MAAM,IAAI,SAAS;AACjC,cAAM,UAAU,MAAM,KAAK,CAAC,MAAW,EAAE,SAAS,IAAI;AACtD,YAAI,CAAC,QAAS,OAAM,IAAI,MAAM,QAAQ,IAAI,YAAY;AACtD,YAAI,MAAM,MAAM,IAAI,YAAY,SAAS,WAAW;AAGpD,YAAI,OAAO,QAAQ,UAAU;AAC3B,cAAI;AACF,kBAAM,KAAK,MAAM,GAAG;AAAA,UACtB,QAAQ;AAAA,UAER;AAAA,QACF;AAEA,YAAI,QAAQ,UAAa,QAAQ,MAAM;AACrC,gBAAM,IAAI,MAAM,oIAAqC;AAAA,QACvD;AAEA,YAAI,OAAO,QAAQ,YAAa,IAA8B,YAAY,OAAO;AAC/E,gBAAM,SAAS;AACf,gBAAM,IAAI,MAAM,OAAO,SAAS,OAAO,WAAW,sCAAQ;AAAA,QAC5D;AAEA,eAAO;AAAA,MACT,GAAG,UAAU,WAAW;AAAA,IAC1B,SAAS,WAAoB;AAC3B,YAAM,SAAS,qBAAqB,QAAQ,UAAU,UAAU,OAAO,SAAS;AAIhF,YAAM,qBACJ,OAAO,SAAS,uBAAuB,KACvC,OAAO,SAAS,iCAAiC,KACjD,OAAO,SAAS,gCAAgC,KAAK,OAAO,SAAS,SAAS;AAEhF,UAAI,oBAAoB;AAEtB,cAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,GAAG,CAAC;AACrD,YAAI,SAAS;AACb,YAAI;AAEF,mBAAS,KAAK,IAAI;AAAA,QACpB,QAAQ;AAAA,QAAW;AAGnB,YAAI,UAAU,WAAW,WAAW;AAClC,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,SAAS,gBAAM,QAAQ,uEAAgB,MAAM;AAAA,YAC7C,aAAa;AAAA,UACf;AAAA,QACF;AAAA,MAEF;AAEA,YAAM;AAAA,IACR;AAEA,WAAO;AAAA,EACT,UAAE;AACA,UAAM,QAAQ,WAAW;AAAA,EAC3B;AACF;","names":["path","os","fs","http","puppeteer","pc","os","path","pc","path","fs","import_os","os"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/browser.ts","../src/commands/state.ts","../src/commands/run.ts"],"sourcesContent":["export { stateCommand } from './commands/state'\nexport { runCommand } from './commands/run'\nexport { connectBrowser, getTargetPage } from './browser'\n","import puppeteer, { Browser, Page } from 'puppeteer-core'\nimport pc from 'picocolors'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport http from 'http'\nimport { spawn } from 'child_process'\nimport { fileURLToPath } from 'url'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\n\nfunction getCdpPort(): number {\n const raw = process.env.WEBMCP_CDP_PORT\n if (raw) {\n const port = Number.parseInt(raw, 10)\n if (Number.isFinite(port) && port > 0) return port\n }\n return 9222\n}\n\nfunction getWorkspaceDir(): string {\n return process.env.WEBMCP_WORKSPACE || path.join(os.homedir(), '.webmcp_chrome_profile')\n}\n\n/** CI / 自动化测试:无头 + sandbox 关闭,避免 Linux runner 缺 display / 权限问题。\n * WEBMCP_HEADLESS=0 可显式强制有界面(覆盖 CI 环境变量)。\n */\nfunction shouldLaunchHeadless(): boolean {\n if (process.env.WEBMCP_HEADLESS === '0') return false\n return process.env.WEBMCP_HEADLESS === '1' || process.env.CI === 'true' || process.env.CI === '1'\n}\n\n/**\n * 读取 Chrome 写入 user-data-dir 的 DevToolsActivePort 文件,获取该 profile 实际绑定的 CDP 端口。\n * 当默认 CDP 端口(9222,或 WEBMCP_CDP_PORT)被系统上其他工具占用时,Chrome 会静默切换到一个随机可用端口并记录在此文件中。\n * 注意:Chrome 136+ 起该文件在部分版本/场景下可能不再写入,因此这只是辅助检测手段,\n * 不能作为唯一依据,主逻辑仍以直接探测固定端口为准(见 candidateCdpBaseUrls)。\n */\nfunction readActivePortFromProfile(userDataDir: string): number | null {\n try {\n const content = fs.readFileSync(path.join(userDataDir, 'DevToolsActivePort'), 'utf-8')\n const port = parseInt(content.split('\\n')[0].trim(), 10)\n return Number.isFinite(port) && port > 0 ? port : null\n } catch {\n return null\n }\n}\n\n/**\n * 生成某端口下所有需要尝试的 CDP 基础地址。\n *\n * 背景:自 Chrome 136 起,`--remote-debugging-address` 已被忽略/移除(安全加固),\n * CDP 服务器改为固定绑定 \"localhost\" 解析出的回环地址;而不同系统上 \"localhost\" 的\n * DNS 解析顺序不一致(有的优先 IPv4 127.0.0.1,有的优先 IPv6 ::1),我们无法再通过\n * 启动参数强制指定地址族。因此这里显式列出 IPv4、IPv6 字面地址和 \"localhost\" 三种形式,\n * 逐一探测,不对地址族做任何假设,才能在所有平台上稳定探测到实际监听地址。\n */\nfunction candidateCdpBaseUrls(port: number): string[] {\n return [\n `http://127.0.0.1:${port}`,\n `http://[::1]:${port}`,\n `http://localhost:${port}`\n ]\n}\n\nfunction getLastTabIdFilePath(): string {\n return path.join(getWorkspaceDir(), '.last-tab-id')\n}\n\n/** 记录最近一次 tabs open / tabs switch 操作的标签页,供 run 无 -t 时回退定位 */\nexport function setLastActiveTabId(tabid: string): void {\n try {\n const dir = getWorkspaceDir()\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n fs.writeFileSync(getLastTabIdFilePath(), tabid, 'utf-8')\n } catch {\n // 写入失败不阻断主流程\n }\n}\n\nfunction getLastActiveTabId(): string | null {\n try {\n const tabid = fs.readFileSync(getLastTabIdFilePath(), 'utf-8').trim()\n return tabid || null\n } catch {\n return null\n }\n}\n\n/**\n * 使用 Node.js 原生 http 模块发起 GET 请求并返回响应体文本\n * 避免 Node.js fetch 将 localhost 解析为 IPv6 ::1 导致连接 Chrome CDP 失败的问题\n */\nfunction httpGet(url: string, timeoutMs = 1500): Promise<string> {\n return new Promise((resolve, reject) => {\n const req = http.get(url, { timeout: timeoutMs }, (res) => {\n let data = ''\n res.on('data', (chunk: Buffer) => { data += chunk.toString() })\n res.on('end', () => {\n if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {\n resolve(data)\n } else {\n reject(new Error(`HTTP ${res.statusCode}`))\n }\n })\n })\n req.on('error', reject)\n req.on('timeout', () => { req.destroy(); reject(new Error('request timeout')) })\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 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 await httpGet(url, 1500)\n return true\n } catch {}\n if (i < retries - 1) {\n await new Promise(r => setTimeout(r, 200))\n }\n }\n return false\n}\n\n/**\n * 找出第一个真正可达的 CDP 地址,避免 Windows 上 localhost 解析为 IPv6 导致 puppeteer 连接失败\n */\nasync function findAvailableCdpUrl(addresses: string[], retries = 2): Promise<string | null> {\n for (const addr of addresses) {\n if (await checkCdpReady(addr, retries)) {\n return addr.replace('/json/version', '')\n }\n }\n return null\n}\n\n/**\n * 通过 Puppeteer 连接已运行的浏览器。\n * 注意:不要在 connect 时使用 targetFilter——在 Windows Edge 上会导致 WebSocket 握手后永久挂起;\n * 页面 target 的筛选在 getPageTargets / getTargetPage 中按需完成。\n */\nasync function connectPuppeteer(browserBaseUrl: string, attempt = 1, maxAttempts = 3): Promise<Browser> {\n try {\n return await promiseWithTimeout(\n puppeteer.connect({ browserURL: browserBaseUrl, defaultViewport: null }),\n 10000,\n `puppeteer.connect to ${browserBaseUrl} timed out`\n )\n } catch (err) {\n if (attempt >= maxAttempts) {\n throw err\n }\n const delayMs = 500 * attempt\n console.log(pc.yellow(`connectBrowser: 第 ${attempt} 次连接失败,${delayMs}ms 后重试...`))\n await new Promise(resolve => setTimeout(resolve, delayMs))\n return connectPuppeteer(browserBaseUrl, attempt + 1, maxAttempts)\n }\n}\n\n\n\ninterface BrowserInfo {\n path: string\n name: string\n}\n\nfunction getDefaultBrowserPath(): BrowserInfo | null {\n // CI / 本地可显式指定可执行文件(browser-actions/setup-chrome 会输出 chrome-path)\n const envPath =\n process.env.CHROME_PATH ||\n process.env.WEBMCP_CHROME_PATH ||\n process.env.PUPPETEER_EXECUTABLE_PATH\n if (envPath && fs.existsSync(envPath)) {\n return { path: envPath, name: 'Chrome' }\n }\n if (envPath) {\n console.log(pc.yellow(`指定的浏览器路径不存在,将回退到自动检测: ${envPath}`))\n }\n\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<string> {\n const browserInfo = getDefaultBrowserPath()\n if (!browserInfo || !fs.existsSync(browserInfo.path)) {\n throw new Error('无法在系统中找到 Chrome 或 Edge 浏览器的默认安装路径。')\n }\n\n const cdpPort = getCdpPort()\n console.log(pc.yellow(`正在启动后台 ${browserInfo.name} 实例 (端口: ${cdpPort})...`))\n \n // 用户可以通过 --workspace CLI 选项或 WEBMCP_WORKSPACE 环境变量自定义。\n const userDataDir = getWorkspaceDir()\n if (!fs.existsSync(userDataDir)) {\n fs.mkdirSync(userDataDir, { recursive: true })\n }\n\n // 启动前先清除旧的 DevToolsActivePort 记录,避免读取到上一次运行残留的过期端口\n try {\n fs.unlinkSync(path.join(userDataDir, 'DevToolsActivePort'))\n } catch {}\n \n // 注:Chrome 136+ 已不再支持 `--remote-debugging-address` 指定绑定地址(安全加固后被忽略),\n // CDP 服务器固定绑定 \"localhost\" 解析出的回环地址,具体是 IPv4 还是 IPv6 由系统决定,\n // 因此不再传递该参数,探测阶段改为同时尝试 IPv4/IPv6/localhost 三种地址形式。\n const launchArgs = [\n `--remote-debugging-port=${cdpPort}`,\n `--user-data-dir=${userDataDir}`,\n '--no-first-run',\n '--no-default-browser-check'\n ]\n\n if (shouldLaunchHeadless()) {\n launchArgs.push(\n '--headless=new',\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-gpu',\n '--disable-dev-shm-usage'\n )\n }\n\n const child = spawn(\n browserInfo.path,\n launchArgs,\n {\n detached: true,\n stdio: 'ignore'\n }\n )\n\n child.unref() // 让子进程脱离父进程独立运行\n\n // 轮询等待 CDP 端口就绪。优先探测固定的 cdpPort(IPv4/IPv6/localhost 三种地址形式都尝试,\n // 不对地址族做假设);同时也检查 DevToolsActivePort 文件——如果 cdpPort 被其他程序占用,\n // 部分 Chrome 版本会自动切换到随机端口并记录在该文件中,作为额外兜底。\n for (let i = 0; i < 40; i++) {\n const ports = new Set<number>([cdpPort])\n const activePort = readActivePortFromProfile(userDataDir)\n if (activePort) ports.add(activePort)\n\n for (const port of ports) {\n for (const baseUrl of candidateCdpBaseUrls(port)) {\n try {\n await httpGet(`${baseUrl}/json/version`, 1000)\n if (port !== cdpPort) {\n console.log(pc.yellow(`检测到端口 ${cdpPort} 已被其他程序占用,${browserInfo.name} 已自动切换到端口 ${port}。`))\n }\n console.log(pc.green(`${browserInfo.name} 启动并就绪(${baseUrl})。`))\n // 额外等待 500ms,确保 CDP 完全稳定(Mac 首次启动时端口通但连接不稳定)\n await new Promise(resolve => setTimeout(resolve, 500))\n return baseUrl\n } catch {}\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 userDataDir = getWorkspaceDir()\n const cdpPort = getCdpPort()\n\n // 第一步:找到第一个真正可达的 CDP 地址(IPv4/IPv6/localhost 三种形式都尝试,不假设地址族)。\n // 若本工具专属 profile 上一次运行时因端口冲突被 Chrome 切换到了非默认端口,\n // 优先尝试 DevToolsActivePort 记录的真实端口,避免误判为“未运行”而重复启动新实例。\n const recordedPort = readActivePortFromProfile(userDataDir)\n const versionAddresses = [\n ...(recordedPort && recordedPort !== cdpPort ? candidateCdpBaseUrls(recordedPort).map(u => `${u}/json/version`) : []),\n ...candidateCdpBaseUrls(cdpPort).map(u => `${u}/json/version`)\n ]\n\n const existingUrl = await findAvailableCdpUrl(versionAddresses, 2)\n if (existingUrl) {\n console.log(pc.yellow(`connectBrowser: 检测到端口 ${cdpPort} 已就绪,正在连接 ${existingUrl}...`))\n try {\n const browser = await connectPuppeteer(existingUrl)\n console.log(pc.green(`connectBrowser: 成功连接 ${existingUrl}`))\n return browser\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(\n `无法连接到已在运行的浏览器(${existingUrl}):${msg}。` +\n 'CDP 端口可用,请勿重复启动浏览器;可关闭多余窗口后重试。'\n )\n }\n }\n\n // 第二步:端口无响应,启动新的浏览器实例(使用独立用户数据目录,不影响用户已有浏览器)\n console.log(pc.yellow(`connectBrowser: 端口 ${cdpPort} 无响应,正在启动新的浏览器实例...`))\n let launchedUrl: string\n try {\n launchedUrl = await startBrowserInBackground()\n } catch (launchError: unknown) {\n const msg = launchError instanceof Error ? launchError.message : String(launchError)\n console.error(pc.red(`无法启动浏览器: ${msg}`))\n throw new Error('Browser launch failed.')\n }\n\n // 第三步:使用启动过程中确认过的真实 CDP 地址进行连接\n try {\n console.log(pc.yellow(`connectBrowser: 浏览器已启动,正在连接 ${launchedUrl}...`))\n const browser = await connectPuppeteer(launchedUrl)\n console.log(pc.green(`connectBrowser: 成功连接 ${launchedUrl}`))\n return browser\n } catch {\n throw new Error(`无法连接到浏览器(${launchedUrl}),请检查 Chrome/Edge 是否已安装。`)\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 // 优先使用最近一次 tabs open/switch 记录的标签页(Agent 连续 CLI 调用更可靠)\n const lastTabId = getLastActiveTabId()\n if (lastTabId) {\n const lastTarget = findPageTargetByTabId(browser, lastTabId)\n if (lastTarget) {\n targetPage = await lastTarget.page()\n }\n }\n\n // Chrome 的 /json/list 接口把当前激活 the tab 排在第一位,用它来判断激活 tab\n if (!targetPage) {\n try {\n // 与 connectBrowser 一致:若 Chrome 因端口冲突切到非默认端口,一并探测 DevToolsActivePort\n const workspaceDir = getWorkspaceDir()\n const cdpPort = getCdpPort()\n const recordedPort = readActivePortFromProfile(workspaceDir)\n const ports = recordedPort && recordedPort !== cdpPort ? [recordedPort, cdpPort] : [cdpPort]\n const urls = ports.flatMap((port) => candidateCdpBaseUrls(port).map((u) => `${u}/json/list`))\n let activeTargetId: string | null = null\n for (const url of urls) {\n try {\n const body = await httpGet(url, 1000)\n const targetsData: Array<{ id: string; type: string; url: string }> = JSON.parse(body)\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 } 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\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 / state / run 等命令调用:仅在尚未注入时注入一次(幂等)。\n * 导航后页面 JS 上下文会清空,`__webmcpcli_init` 自然失效,下次调用会重新注入。\n */\nexport async function injectIntoPage(page: Page): Promise<void> {\n await injectWebMCPPolyfillAndTools(page)\n}\n\nasync function injectWebMCPPolyfillAndTools(page: Page) {\n // 已注入则跳过,避免覆盖 page-agent-tool 闭包内的 refMap\n const polyfillReady = 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 // 显式等待 WebMCP 注册接口完全就绪\n await page.waitForFunction(() => {\n const mcp = (navigator as any).modelContext\n return mcp && typeof mcp.registerTool === 'function'\n }, { timeout: 10000 }).catch(() => {\n console.warn('等待 WebMCP registerTool 接口就绪超时,正在继续注入...')\n })\n }\n\n // 无论 polyfill 是否刚注入,都检查域名工具(工具内部有防重复 flag)\n await injectDomainTools(page)\n}\n\nfunction getToolsBundleName(hostname: string): string | null {\n const normalized = hostname.split(':')[0].toLowerCase()\n const toolsDir = path.resolve(__dirname, 'webmcp-tools')\n if (!fs.existsSync(toolsDir)) {\n return null\n }\n let files: string[] = []\n try {\n files = fs.readdirSync(toolsDir)\n } catch {\n return null\n }\n const supportedDomains = files\n .filter(f => f.endsWith('.js'))\n .map(f => f.slice(0, -3))\n\n if (supportedDomains.includes(normalized)) {\n return `webmcp-tools/${normalized}.js`\n }\n for (const domain of supportedDomains) {\n if (normalized.endsWith('.' + domain)) {\n return `webmcp-tools/${domain}.js`\n }\n }\n return null\n}\n\n/**\n * 根据页面域名查找并注入对应的工具 bundle\n * bundle 文件位于 dist/webmcp-tools/{hostname}.js\n * 注意:每个工具 bundle 内部会在 window 上设置 __webmcptools_{domain} flag 防止重复注册,\n * 此处在 JS 层额外做一次快速检查,避免每次都 evaluate 完整脚本带来不必要的 IPC 开销。\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 bundleName = getToolsBundleName(hostname)\n if (!bundleName) {\n return // 没有对应的工具预置,跳过\n }\n\n const toolBundlePath = path.resolve(__dirname, bundleName)\n if (!fs.existsSync(toolBundlePath)) {\n return // 文件不存在,跳过\n }\n\n // 快速检查:若页面已注入,直接返回,省去读文件和 evaluate 的开销\n const bundleKey = bundleName.replace(/^webmcp-tools\\//, '').replace(/\\.js$/, '')\n const flagKey = `__webmcptools_${bundleKey.replace(/[^a-zA-Z0-9]/g, '')}`\n const alreadyInjected = await page.evaluate((key: string) => !!(window as any)[key], flagKey).catch(() => false)\n if (alreadyInjected) {\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\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. 尚未注入时注入一次,确保手动打开的页面也能使用工具\n await injectIntoPage(page)\n\n // 2. 在页面上下文中获取导航元数据和已注册的工具列表\n // 不获取 DOM content(由调用方按需通过 browserState 拉取)\n const state = await page.evaluate(async () => {\n const url = document.URL\n const title = document.title\n\n const mcp = (document as any).modelContext || (navigator as any).modelContext\n let webmcpTools: Array<{ name: string; description?: string }> = []\n\n // polyfill 暴露的是 getTools(不是 MCP SDK 的 listTools)\n // 只回传可结构化克隆的字段,避免工具对象上的函数导致 page.evaluate 序列化失败\n if (mcp && typeof mcp.getTools === 'function') {\n try {\n const toolsResult = await mcp.getTools()\n const list = (toolsResult?.tools || toolsResult || []) as Array<{ name?: string; description?: string }>\n webmcpTools = list\n .filter((t) => t && typeof t.name === 'string')\n .map((t) => ({\n name: t.name as string,\n description: typeof t.description === 'string' ? t.description.slice(0, 200) : undefined,\n }))\n } catch {\n webmcpTools = []\n }\n }\n\n return { url, title, webmcpTools }\n })\n\n const activeTabid = await getPageTargetId(page).catch(() => null)\n\n // 3. 获取所有标签页信息(排除 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 activeTabid,\n tabs: tabs.filter(Boolean)\n }\n } finally {\n await browser.disconnect()\n }\n}\n","import { connectBrowser, getTargetPage, injectIntoPage } 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 const urlBefore = page.url()\n\n // 仅在尚未注入时注入一次,保留 page-agent-tool 的 refMap\n await injectIntoPage(page)\n\n // argsJson 已在 bin.ts 中完成 @base64file 展开与 JSON 校验\n const cleanedArgs = argsJson.trim()\n\n let result: any\n try {\n result = await page.evaluate(async (name, inputString) => {\n // @ts-expect-error WebMCP APIs are experimental and not yet in DOM types\n const mcp = document.modelContext || document.modelContext || document.modelContext\n\n if (!mcp || typeof mcp.executeTool !== 'function') {\n throw new Error('当前页面没有注入 WebMCP 环境 (document.modelContext 未找到)')\n }\n\n // executeTool 的第二个参数必须是 JSON 字符串\n const tools = await mcp.getTools();\n const toolObj = tools.find((t: any) => t.name === name);\n if (!toolObj) throw new Error(`Tool ${name} not found`);\n let res = await mcp.executeTool(toolObj, inputString)\n\n // executeTool 的返回值可能是普通对象,也可能是 JSON 字符串\n if (typeof res === 'string') {\n try {\n res = JSON.parse(res)\n } catch {\n // 保留原始字符串\n }\n }\n\n if (res === undefined || res === null) {\n throw new Error('工具 execute 未返回结果,请检查工具实现是否缺少 return')\n }\n\n if (typeof res === 'object' && (res as { success?: boolean }).success === false) {\n const failed = res as { error?: string; message?: string }\n throw new Error(failed.error || failed.message || '工具执行失败')\n }\n\n return res\n }, toolName, cleanedArgs)\n } catch (evalError: unknown) {\n const errMsg = evalError instanceof Error ? evalError.message : String(evalError)\n\n // 识别\"执行上下文已销毁\"错误:这通常是工具执行期间页面发生了正常导航(如发布后跳转)\n // 此时工具实际上已经成功执行,跳转本身就是成功的副作用,不应报错\n const isContextDestroyed =\n errMsg.includes('context was destroyed') ||\n errMsg.includes('Execution context was destroyed') ||\n errMsg.includes('Cannot read properties of null') && errMsg.includes('context')\n\n if (isContextDestroyed) {\n // 等待导航稳定,然后读取新页面 URL\n await new Promise(resolve => setTimeout(resolve, 800))\n let newUrl = ''\n try {\n // 重新读取页面(导航后 JS 上下文虽被销毁,但 page 实例本身的 url() 依然是最新的)\n newUrl = page.url()\n } catch { /* 忽略 */ }\n\n // 如果 URL 已经发生变化,认为是正常导航触发的上下文销毁(工具执行成功)\n if (newUrl && newUrl !== urlBefore) {\n return {\n success: true,\n message: `工具 ${toolName} 执行完成,页面已导航至 ${newUrl}`,\n navigatedTo: newUrl\n }\n }\n // URL 未变化,可能是真正的错误,重新抛出\n }\n\n throw evalError\n }\n\n return result\n } finally {\n await browser.disconnect()\n }\n}\n"],"mappings":"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","names":["path","os","fs","fs","http","puppeteer","pc","os","path","pc","path","fs"]}