@opentiny/webmcp-cli 0.0.3 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/bin.ts
4
4
  import fs2 from "fs";
5
5
  import path2 from "path";
6
- import os2 from "os";
6
+ import os3 from "os";
7
7
  import { Command } from "commander";
8
8
  import pc2 from "picocolors";
9
9
  import { readFileSync as readFileSync2 } from "fs";
@@ -161,21 +161,28 @@ ${hint}`);
161
161
  }
162
162
  }
163
163
  function prepareRunArgsJson(args, fileContent, baseDir) {
164
- let raw = "";
165
164
  if (fileContent !== void 0) {
166
- raw = fileContent.trim();
167
- } else if (args.length > 0) {
168
- const joined = joinShellArgs(args);
169
- if (joined.startsWith("{") || looksLikeObjectBody(joined)) {
170
- raw = joined;
171
- } else if (args.every((arg) => arg.includes("="))) {
172
- raw = buildArgsFromKeyValuePairs(args);
173
- } else {
174
- raw = joined;
165
+ const trimmed = fileContent.trim();
166
+ if (!trimmed) {
167
+ throw new Error("\u901A\u8FC7 -f/--file \u6307\u5B9A\u7684\u53C2\u6570\u6587\u4EF6\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A");
175
168
  }
169
+ const expanded2 = expandFileRefs(trimmed, baseDir);
170
+ return parseArgsJson(expanded2);
171
+ }
172
+ if (args.length === 0) {
173
+ return "{}";
174
+ }
175
+ const joined = joinShellArgs(args);
176
+ let raw = "";
177
+ if (joined.startsWith("{") || looksLikeObjectBody(joined)) {
178
+ raw = joined;
179
+ } else if (args.every((arg) => arg.includes("="))) {
180
+ raw = buildArgsFromKeyValuePairs(args);
181
+ } else {
182
+ raw = joined;
176
183
  }
177
- if (!raw) {
178
- throw new Error("\u5FC5\u987B\u63D0\u4F9B\u53C2\u6570\u6216\u901A\u8FC7 -f/--file \u6307\u5B9A\u53C2\u6570\u6587\u4EF6");
184
+ if (!raw.trim()) {
185
+ throw new Error("\u672A\u63D0\u4F9B\u6709\u6548\u53C2\u6570");
179
186
  }
180
187
  const expanded = expandFileRefs(raw, baseDir);
181
188
  return parseArgsJson(expanded);
@@ -194,6 +201,30 @@ var __filename = fileURLToPath2(import.meta.url);
194
201
  var __dirname = path.dirname(__filename);
195
202
  var CDP_PORT = 9222;
196
203
  var CDP_URL = `http://localhost:${CDP_PORT}`;
204
+ function getWorkspaceDir() {
205
+ return process.env.WEBMCP_WORKSPACE || path.join(os.homedir(), ".webmcp_chrome_profile");
206
+ }
207
+ function getLastTabIdFilePath() {
208
+ return path.join(getWorkspaceDir(), ".last-tab-id");
209
+ }
210
+ function setLastActiveTabId(tabid) {
211
+ try {
212
+ const dir = getWorkspaceDir();
213
+ if (!fs.existsSync(dir)) {
214
+ fs.mkdirSync(dir, { recursive: true });
215
+ }
216
+ fs.writeFileSync(getLastTabIdFilePath(), tabid, "utf-8");
217
+ } catch {
218
+ }
219
+ }
220
+ function getLastActiveTabId() {
221
+ try {
222
+ const tabid = fs.readFileSync(getLastTabIdFilePath(), "utf-8").trim();
223
+ return tabid || null;
224
+ } catch {
225
+ return null;
226
+ }
227
+ }
197
228
  function httpGet(url, timeoutMs = 1500) {
198
229
  return new Promise((resolve3, reject) => {
199
230
  const req = http.get(url, { timeout: timeoutMs }, (res) => {
@@ -452,33 +483,42 @@ async function getTargetPage(browser, tabid) {
452
483
  }
453
484
  targetPage = await target.page();
454
485
  } else {
455
- try {
456
- const urls = [
457
- `http://localhost:${CDP_PORT}/json/list`,
458
- `http://127.0.0.1:${CDP_PORT}/json/list`
459
- ];
460
- let activeTargetId = null;
461
- for (const url of urls) {
462
- try {
463
- const body = await httpGet(url, 1e3);
464
- const targetsData = JSON.parse(body);
465
- const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
466
- if (active) {
467
- activeTargetId = active.id;
468
- break;
486
+ const lastTabId = getLastActiveTabId();
487
+ if (lastTabId) {
488
+ const lastTarget = findPageTargetByTabId(browser, lastTabId);
489
+ if (lastTarget) {
490
+ targetPage = await lastTarget.page();
491
+ }
492
+ }
493
+ if (!targetPage) {
494
+ try {
495
+ const urls = [
496
+ `http://localhost:${CDP_PORT}/json/list`,
497
+ `http://127.0.0.1:${CDP_PORT}/json/list`
498
+ ];
499
+ let activeTargetId = null;
500
+ for (const url of urls) {
501
+ try {
502
+ const body = await httpGet(url, 1e3);
503
+ const targetsData = JSON.parse(body);
504
+ const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
505
+ if (active) {
506
+ activeTargetId = active.id;
507
+ break;
508
+ }
509
+ } catch {
469
510
  }
470
- } catch {
471
511
  }
472
- }
473
- if (activeTargetId) {
474
- for (const target of pageTargets) {
475
- if (getTargetIdFromTarget(target) === activeTargetId) {
476
- targetPage = await target.page();
477
- break;
512
+ if (activeTargetId) {
513
+ for (const target of pageTargets) {
514
+ if (getTargetIdFromTarget(target) === activeTargetId) {
515
+ targetPage = await target.page();
516
+ break;
517
+ }
478
518
  }
479
519
  }
520
+ } catch {
480
521
  }
481
- } catch {
482
522
  }
483
523
  if (!targetPage) {
484
524
  const lastTarget = pageTargets[pageTargets.length - 1];
@@ -559,6 +599,12 @@ async function injectDomainTools(page) {
559
599
  if (!fs.existsSync(toolBundlePath)) {
560
600
  return;
561
601
  }
602
+ const bundleKey = bundleName.replace(/^webmcp-tools\//, "").replace(/\.js$/, "");
603
+ const flagKey = `__webmcptools_${bundleKey.replace(/[^a-zA-Z0-9]/g, "")}`;
604
+ const alreadyInjected = await page.evaluate((key) => !!window[key], flagKey).catch(() => false);
605
+ if (alreadyInjected) {
606
+ return;
607
+ }
562
608
  console.log(pc.cyan(`\u68C0\u6D4B\u5230\u57DF\u540D ${hostname} \u6709\u9884\u7F6E\u5DE5\u5177\uFF0C\u6B63\u5728\u6CE8\u5165...`));
563
609
  const toolScript = fs.readFileSync(toolBundlePath, "utf-8");
564
610
  try {
@@ -579,14 +625,15 @@ async function stateCommand({ tabid }) {
579
625
  const state = await page.evaluate(async () => {
580
626
  const url = document.URL;
581
627
  const title = document.title;
582
- const mcp = navigator.modelContextTesting || navigator.modelContext;
628
+ const mcp = document.modelContext;
583
629
  let webmcpTools = [];
584
630
  if (mcp && typeof mcp.listTools === "function") {
585
- const toolsResult = await mcp.listTools();
631
+ const toolsResult = await mcp.getTools();
586
632
  webmcpTools = toolsResult?.tools || toolsResult || [];
587
633
  }
588
634
  return { url, title, webmcpTools };
589
635
  });
636
+ const activeTabid = await getPageTargetId(page).catch(() => null);
590
637
  const pages = await browser.pages();
591
638
  const tabs2 = await Promise.all(
592
639
  pages.map(async (p) => {
@@ -606,6 +653,7 @@ async function stateCommand({ tabid }) {
606
653
  );
607
654
  return {
608
655
  ...state,
656
+ activeTabid,
609
657
  tabs: tabs2.filter(Boolean)
610
658
  };
611
659
  } finally {
@@ -613,6 +661,155 @@ async function stateCommand({ tabid }) {
613
661
  }
614
662
  }
615
663
 
664
+ // src/zhihu/create-article.ts
665
+ import os2 from "os";
666
+
667
+ // src/zhihu/markdown.ts
668
+ import { Marked } from "marked";
669
+ function stripLeadingH1(markdown) {
670
+ return markdown.replace(/^#\s+[^\n]*\n+/, "");
671
+ }
672
+ function markdownToHtml(markdown) {
673
+ const marked = new Marked({
674
+ gfm: true,
675
+ breaks: false,
676
+ renderer: {
677
+ code({ text, lang }) {
678
+ const langClass = lang ? ` class="language-${lang}"` : "";
679
+ const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
680
+ return `<pre><code${langClass}>${escaped}</code></pre>`;
681
+ },
682
+ codespan({ text }) {
683
+ const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
684
+ return `<code>${escaped}</code>`;
685
+ }
686
+ }
687
+ });
688
+ return marked.parse(markdown);
689
+ }
690
+ function decodeBase64Content(content) {
691
+ return Buffer.from(content, "base64").toString("utf-8");
692
+ }
693
+ var ZHIHU_WRITE_PAGE_RE = /^https:\/\/zhuanlan\.zhihu\.com\/(write|p\/\d+\/edit)/;
694
+ function isZhihuWriteUrl(url) {
695
+ return ZHIHU_WRITE_PAGE_RE.test(url);
696
+ }
697
+
698
+ // src/zhihu/create-article.ts
699
+ function delay(ms) {
700
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
701
+ }
702
+ function getPasteModifier() {
703
+ return os2.platform() === "darwin" ? "Meta" : "Control";
704
+ }
705
+ async function fillTitle(page, title) {
706
+ await page.evaluate((titleText) => {
707
+ const input = document.querySelector('textarea[placeholder*="\u6807\u9898"]') || document.querySelector(".WriteIndex-titleInput textarea") || document.querySelector(".WriteIndex-titleInput input");
708
+ 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");
709
+ const setter = Object.getOwnPropertyDescriptor(
710
+ input instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,
711
+ "value"
712
+ )?.set;
713
+ if (setter) {
714
+ setter.call(input, titleText);
715
+ } else {
716
+ input.value = titleText;
717
+ }
718
+ input.dispatchEvent(new InputEvent("input", { bubbles: true, cancelable: true, data: titleText }));
719
+ input.dispatchEvent(new Event("change", { bubbles: true }));
720
+ if (input.value.trim() !== titleText.trim()) {
721
+ throw new Error("\u6807\u9898\u586B\u5199\u5931\u8D25\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u540E\u91CD\u8BD5");
722
+ }
723
+ }, title);
724
+ }
725
+ async function pasteHtmlToEditor(page, html) {
726
+ const browser = page.browser();
727
+ const tempPage = await browser.newPage();
728
+ const modifier = getPasteModifier();
729
+ try {
730
+ const fullHtml = `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>${html}</body></html>`;
731
+ await tempPage.setContent(fullHtml, { waitUntil: "domcontentloaded" });
732
+ await delay(300);
733
+ await tempPage.click("body");
734
+ await tempPage.keyboard.down(modifier);
735
+ await tempPage.keyboard.press("KeyA");
736
+ await tempPage.keyboard.up(modifier);
737
+ await delay(100);
738
+ await tempPage.keyboard.down(modifier);
739
+ await tempPage.keyboard.press("KeyC");
740
+ await tempPage.keyboard.up(modifier);
741
+ await delay(300);
742
+ } finally {
743
+ await tempPage.close();
744
+ }
745
+ await page.bringToFront();
746
+ await page.waitForSelector(".public-DraftEditor-content", { timeout: 1e4 });
747
+ await page.click(".public-DraftEditor-content");
748
+ await delay(200);
749
+ await page.keyboard.down(modifier);
750
+ await page.keyboard.press("KeyA");
751
+ await page.keyboard.up(modifier);
752
+ await delay(100);
753
+ await page.keyboard.press("Backspace");
754
+ await delay(200);
755
+ await page.keyboard.down(modifier);
756
+ await page.keyboard.press("KeyV");
757
+ await page.keyboard.up(modifier);
758
+ await delay(1500);
759
+ const probe = html.replace(/<[^>]+>/g, "").trim().slice(0, 15);
760
+ const verifyResult = await page.evaluate((probeText) => {
761
+ const editor = document.querySelector(".public-DraftEditor-content");
762
+ if (!editor) return { ok: false, reason: "no editor" };
763
+ const text = (editor.innerText || "").trim();
764
+ if (!text) return { ok: false, reason: "empty" };
765
+ const inner = editor.innerHTML;
766
+ const hasRichFormat = inner.includes("data-text") || /font-weight:\s*bold|font-style:\s*italic|code-block|blockquote/i.test(inner);
767
+ const normalizedText = text.replace(/\s+/g, "");
768
+ const normalizedProbe = probeText.replace(/\s+/g, "");
769
+ const hasProbe = normalizedText.includes(normalizedProbe);
770
+ return {
771
+ ok: text.length > 20 && hasProbe && (hasRichFormat || text.length > 50),
772
+ reason: !hasProbe ? "probe mismatch" : !hasRichFormat && text.length <= 50 ? "no rich format" : void 0
773
+ };
774
+ }, probe);
775
+ if (!verifyResult.ok) {
776
+ throw new Error(
777
+ `\u6B63\u6587\u586B\u5199\u5931\u8D25\uFF08Markdown \u8F6C HTML \u7C98\u8D34\u672A\u751F\u6548\uFF09${verifyResult.reason ? `: ${verifyResult.reason}` : ""}`
778
+ );
779
+ }
780
+ }
781
+ async function zhihuCreateArticle(page, args) {
782
+ const { title, content } = args;
783
+ if (!title?.trim()) throw new Error("\u53C2\u6570 title \u4E0D\u80FD\u4E3A\u7A7A");
784
+ if (!content?.trim()) throw new Error("\u53C2\u6570 content \u4E0D\u80FD\u4E3A\u7A7A");
785
+ const url = page.url();
786
+ if (!isZhihuWriteUrl(url)) {
787
+ 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");
788
+ }
789
+ let markdown;
790
+ try {
791
+ markdown = stripLeadingH1(decodeBase64Content(content));
792
+ } catch {
793
+ 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");
794
+ }
795
+ if (!markdown.trim()) {
796
+ throw new Error("\u6B63\u6587\u5185\u5BB9\u4E3A\u7A7A\uFF0C\u8BF7\u68C0\u67E5 Markdown \u6587\u4EF6");
797
+ }
798
+ const html = markdownToHtml(markdown);
799
+ await fillTitle(page, title.trim());
800
+ await delay(500);
801
+ await pasteHtmlToEditor(page, html);
802
+ const result = {
803
+ success: true,
804
+ 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",
805
+ title: title.trim(),
806
+ contentLength: markdown.length
807
+ };
808
+ return {
809
+ content: [{ type: "text", text: JSON.stringify(result) }]
810
+ };
811
+ }
812
+
616
813
  // src/commands/run.ts
617
814
  async function runCommand({
618
815
  toolName,
@@ -622,28 +819,65 @@ async function runCommand({
622
819
  const browser = await connectBrowser();
623
820
  try {
624
821
  const page = await getTargetPage(browser, tabid);
822
+ const urlBefore = page.url();
625
823
  const cleanedArgs = argsJson.trim();
626
- const result = await page.evaluate(async (name, inputString) => {
627
- const mcp = navigator.modelContextTesting || navigator.modelContext;
628
- if (!mcp || typeof mcp.executeTool !== "function") {
629
- throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (navigator.modelContextTesting.executeTool \u672A\u627E\u5230)");
824
+ if (toolName === "create_article") {
825
+ let parsedArgs;
826
+ try {
827
+ parsedArgs = JSON.parse(cleanedArgs);
828
+ } catch {
829
+ throw new Error("\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON");
830
+ }
831
+ if (isZhihuWriteUrl(page.url())) {
832
+ return await zhihuCreateArticle(page, parsedArgs);
630
833
  }
631
- let res = await mcp.executeTool(name, inputString);
632
- if (typeof res === "string") {
834
+ }
835
+ let result;
836
+ try {
837
+ result = await page.evaluate(async (name, inputString) => {
838
+ const mcp = document.modelContext || document.modelContext || document.modelContext;
839
+ if (!mcp || typeof mcp.executeTool !== "function") {
840
+ throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (document.modelContext \u672A\u627E\u5230)");
841
+ }
842
+ const tools = await mcp.getTools();
843
+ const toolObj = tools.find((t) => t.name === name);
844
+ if (!toolObj) throw new Error(`Tool ${name} not found`);
845
+ let res = await mcp.executeTool(toolObj, inputString);
846
+ if (typeof res === "string") {
847
+ try {
848
+ res = JSON.parse(res);
849
+ } catch {
850
+ }
851
+ }
852
+ if (res === void 0 || res === null) {
853
+ throw new Error("\u5DE5\u5177 execute \u672A\u8FD4\u56DE\u7ED3\u679C\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u5B9E\u73B0\u662F\u5426\u7F3A\u5C11 return");
854
+ }
855
+ if (typeof res === "object" && res.success === false) {
856
+ const failed = res;
857
+ throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
858
+ }
859
+ return res;
860
+ }, toolName, cleanedArgs);
861
+ } catch (evalError) {
862
+ const errMsg = evalError instanceof Error ? evalError.message : String(evalError);
863
+ const isContextDestroyed = errMsg.includes("context was destroyed") || errMsg.includes("Execution context was destroyed") || errMsg.includes("Cannot read properties of null") && errMsg.includes("context");
864
+ if (isContextDestroyed) {
865
+ await new Promise((resolve3) => setTimeout(resolve3, 800));
866
+ let newUrl = "";
633
867
  try {
634
- res = JSON.parse(res);
868
+ newUrl = page.url();
635
869
  } catch {
636
870
  }
871
+ if (newUrl && newUrl !== urlBefore) {
872
+ return {
873
+ success: true,
874
+ message: `\u5DE5\u5177 ${toolName} \u6267\u884C\u5B8C\u6210\uFF0C\u9875\u9762\u5DF2\u5BFC\u822A\u81F3 ${newUrl}`,
875
+ navigatedTo: newUrl
876
+ };
877
+ }
637
878
  }
638
- if (res === void 0 || res === null) {
639
- throw new Error("\u5DE5\u5177 execute \u672A\u8FD4\u56DE\u7ED3\u679C\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u5B9E\u73B0\u662F\u5426\u7F3A\u5C11 return");
640
- }
641
- if (typeof res === "object" && res.success === false) {
642
- const failed = res;
643
- throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
644
- }
645
- return res;
646
- }, toolName, cleanedArgs);
879
+ throw evalError;
880
+ }
647
881
  return result;
648
882
  } finally {
649
883
  await browser.disconnect();
@@ -675,11 +909,19 @@ async function tabsOpenCommand(url) {
675
909
  await page.goto(url, { waitUntil: "domcontentloaded" });
676
910
  await injectIntoPage(page);
677
911
  await new Promise((resolve3) => setTimeout(resolve3, 500));
912
+ let tabid;
913
+ try {
914
+ tabid = await getPageTargetId(page);
915
+ await activateTabById(browser, tabid);
916
+ setLastActiveTabId(tabid);
917
+ } catch (e) {
918
+ console.warn("Best-effort tab activation failed:", e);
919
+ }
678
920
  return {
679
921
  success: true,
680
- tabid: await getPageTargetId(page),
922
+ tabid,
681
923
  url: page.url(),
682
- title: await page.title()
924
+ title: await page.title().catch(() => "")
683
925
  };
684
926
  } finally {
685
927
  await browser.disconnect();
@@ -711,9 +953,11 @@ async function tabsSwitchCommand(tabid) {
711
953
  try {
712
954
  await activateTabById(browser, tabid);
713
955
  const page = await getTargetPage(browser, tabid);
956
+ const realTabid = await getPageTargetId(page);
957
+ setLastActiveTabId(realTabid);
714
958
  return {
715
959
  success: true,
716
- tabid: await getPageTargetId(page),
960
+ tabid: realTabid,
717
961
  url: page.url(),
718
962
  title: await page.title()
719
963
  };
@@ -775,7 +1019,7 @@ async function tabsForwardCommand(tabid) {
775
1019
  // package.json
776
1020
  var package_default = {
777
1021
  name: "@opentiny/webmcp-cli",
778
- version: "0.0.3",
1022
+ version: "0.0.6",
779
1023
  type: "module",
780
1024
  description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
781
1025
  main: "dist/index.js",
@@ -815,6 +1059,7 @@ var package_default = {
815
1059
  clipboardy: "^5.3.1",
816
1060
  commander: "^11.1.0",
817
1061
  json5: "^2.2.3",
1062
+ marked: "^15.0.7",
818
1063
  picocolors: "^1.0.0",
819
1064
  "puppeteer-core": "^22.0.0"
820
1065
  },
@@ -875,9 +1120,66 @@ function cleanOldLogs(baseDir, logDir) {
875
1120
  } catch (e) {
876
1121
  }
877
1122
  }
1123
+ function formatLogResult(result) {
1124
+ if (result === void 0 || result === null) {
1125
+ return "null";
1126
+ }
1127
+ try {
1128
+ if (typeof result !== "object") {
1129
+ return String(result);
1130
+ }
1131
+ const clone = JSON.parse(JSON.stringify(result));
1132
+ let a11yTreeStr = "";
1133
+ let searchResultStr = "";
1134
+ if (clone && Array.isArray(clone.content) && clone.content.length > 0 && clone.content[0] && typeof clone.content[0].text === "string") {
1135
+ const text = clone.content[0].text;
1136
+ if (text.startsWith("\u6D4F\u89C8\u5668\u72B6\u6001: ")) {
1137
+ let jsonStr = text.substring("\u6D4F\u89C8\u5668\u72B6\u6001: ".length);
1138
+ let extraAlerts = "";
1139
+ const firstNewline = jsonStr.indexOf("\n");
1140
+ if (firstNewline !== -1) {
1141
+ extraAlerts = jsonStr.substring(firstNewline);
1142
+ jsonStr = jsonStr.substring(0, firstNewline);
1143
+ }
1144
+ try {
1145
+ const parsed = JSON.parse(jsonStr);
1146
+ if (parsed && typeof parsed.content === "string") {
1147
+ a11yTreeStr = parsed.content;
1148
+ parsed.content = "[Formatted A11y Tree - See details below]";
1149
+ clone.content[0].text = `\u6D4F\u89C8\u5668\u72B6\u6001: ${JSON.stringify(parsed, null, 2)}${extraAlerts}`;
1150
+ }
1151
+ } catch {
1152
+ }
1153
+ } else if (text.includes("A11y Tree \u641C\u7D22\u7ED3\u679C") || text.includes("\u65E0\u969C\u788D\u6811\u641C\u7D22\u7ED3\u679C") || text.includes("\u65E0\u969C\u788D\u6811\u641C\u7D22") || text.includes("\u547D\u4E2D\u884C") || text.includes("context:")) {
1154
+ searchResultStr = text;
1155
+ clone.content[0].text = "[Search A11y Tree Result - See details below]";
1156
+ }
1157
+ }
1158
+ let out = JSON.stringify(clone, null, 2);
1159
+ if (a11yTreeStr) {
1160
+ out += `
1161
+
1162
+ [FORMATTED A11Y TREE]:
1163
+ ${a11yTreeStr}`;
1164
+ }
1165
+ if (searchResultStr) {
1166
+ out += `
1167
+
1168
+ [FORMATTED SEARCH RESULT]:
1169
+ ${searchResultStr}`;
1170
+ }
1171
+ return out;
1172
+ } catch (e) {
1173
+ try {
1174
+ return JSON.stringify(result, null, 2);
1175
+ } catch {
1176
+ return String(result);
1177
+ }
1178
+ }
1179
+ }
878
1180
  function writeLog(commandName, args, result, error) {
879
1181
  try {
880
- const baseDir = process.env.WEBMCP_WORKSPACE || path2.join(os2.homedir(), ".webmcp_chrome_profile");
1182
+ const baseDir = process.env.WEBMCP_WORKSPACE || path2.join(os3.homedir(), ".webmcp_chrome_profile");
881
1183
  const logDir = path2.join(baseDir, "logs");
882
1184
  if (!fs2.existsSync(logDir)) {
883
1185
  fs2.mkdirSync(logDir, { recursive: true });
@@ -900,15 +1202,8 @@ ${error instanceof Error ? error.stack : String(error)}
900
1202
  } else if (result) {
901
1203
  logText += `RESULT:
902
1204
  `;
903
- try {
904
- if (result && typeof result === "object") {
905
- logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
906
- } else {
907
- logText += String(result).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
908
- }
909
- } catch {
910
- logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
911
- }
1205
+ const formattedResult = formatLogResult(result);
1206
+ logText += formattedResult.split("\n").map((line) => ` ${line}`).join("\n") + "\n";
912
1207
  } else {
913
1208
  logText += `RESULT: null
914
1209
  `;
@@ -938,7 +1233,7 @@ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D
938
1233
  try {
939
1234
  const result = await stateCommand(args);
940
1235
  writeLog("state", args, result);
941
- console.log(JSON.stringify(result, null, 2));
1236
+ console.log(formatLogResult(result));
942
1237
  } catch (error) {
943
1238
  handleCommandError(error, "state", args);
944
1239
  }
@@ -972,7 +1267,7 @@ program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u987
972
1267
  tabid: parseTabId(options.tabid)
973
1268
  });
974
1269
  writeLog("run", { ...rawArgs, finalArgsJson }, result);
975
- console.log(JSON.stringify(result, null, 2));
1270
+ console.log(formatLogResult(result));
976
1271
  } catch (error) {
977
1272
  handleCommandError(error, "run", { ...rawArgs, finalArgsJson });
978
1273
  }