@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/README.md CHANGED
@@ -76,7 +76,7 @@ webmcp-cli run <toolName> <argsJson> [-t tabid]
76
76
  ```
77
77
 
78
78
  **示例一:执行页面自带工具**
79
- 假设网页使用 `navigator.modelContext.registerTool` 注册了一个名为 `change-color` 的工具:
79
+ 假设网页使用 `document.modelContext.registerTool` 注册了一个名为 `change-color` 的工具:
80
80
  ```bash
81
81
  webmcp-cli run change-color '{"color": "#ff0000"}'
82
82
  ```
package/dist/bin.cjs CHANGED
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/bin.ts
27
27
  var import_fs3 = __toESM(require("fs"), 1);
28
28
  var import_path3 = __toESM(require("path"), 1);
29
- var import_os2 = __toESM(require("os"), 1);
29
+ var import_os3 = __toESM(require("os"), 1);
30
30
  var import_commander = require("commander");
31
31
  var import_picocolors2 = __toESM(require("picocolors"), 1);
32
32
  var import_fs4 = require("fs");
@@ -184,21 +184,28 @@ ${hint}`);
184
184
  }
185
185
  }
186
186
  function prepareRunArgsJson(args, fileContent, baseDir) {
187
- let raw = "";
188
187
  if (fileContent !== void 0) {
189
- raw = fileContent.trim();
190
- } else if (args.length > 0) {
191
- const joined = joinShellArgs(args);
192
- if (joined.startsWith("{") || looksLikeObjectBody(joined)) {
193
- raw = joined;
194
- } else if (args.every((arg) => arg.includes("="))) {
195
- raw = buildArgsFromKeyValuePairs(args);
196
- } else {
197
- raw = joined;
188
+ const trimmed = fileContent.trim();
189
+ if (!trimmed) {
190
+ throw new Error("\u901A\u8FC7 -f/--file \u6307\u5B9A\u7684\u53C2\u6570\u6587\u4EF6\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A");
198
191
  }
192
+ const expanded2 = expandFileRefs(trimmed, baseDir);
193
+ return parseArgsJson(expanded2);
194
+ }
195
+ if (args.length === 0) {
196
+ return "{}";
197
+ }
198
+ const joined = joinShellArgs(args);
199
+ let raw = "";
200
+ if (joined.startsWith("{") || looksLikeObjectBody(joined)) {
201
+ raw = joined;
202
+ } else if (args.every((arg) => arg.includes("="))) {
203
+ raw = buildArgsFromKeyValuePairs(args);
204
+ } else {
205
+ raw = joined;
199
206
  }
200
- if (!raw) {
201
- throw new Error("\u5FC5\u987B\u63D0\u4F9B\u53C2\u6570\u6216\u901A\u8FC7 -f/--file \u6307\u5B9A\u53C2\u6570\u6587\u4EF6");
207
+ if (!raw.trim()) {
208
+ throw new Error("\u672A\u63D0\u4F9B\u6709\u6548\u53C2\u6570");
202
209
  }
203
210
  const expanded = expandFileRefs(raw, baseDir);
204
211
  return parseArgsJson(expanded);
@@ -218,6 +225,30 @@ var __filename = (0, import_url2.fileURLToPath)(import_meta.url);
218
225
  var __dirname = import_path2.default.dirname(__filename);
219
226
  var CDP_PORT = 9222;
220
227
  var CDP_URL = `http://localhost:${CDP_PORT}`;
228
+ function getWorkspaceDir() {
229
+ return process.env.WEBMCP_WORKSPACE || import_path2.default.join(import_os.default.homedir(), ".webmcp_chrome_profile");
230
+ }
231
+ function getLastTabIdFilePath() {
232
+ return import_path2.default.join(getWorkspaceDir(), ".last-tab-id");
233
+ }
234
+ function setLastActiveTabId(tabid) {
235
+ try {
236
+ const dir = getWorkspaceDir();
237
+ if (!import_fs2.default.existsSync(dir)) {
238
+ import_fs2.default.mkdirSync(dir, { recursive: true });
239
+ }
240
+ import_fs2.default.writeFileSync(getLastTabIdFilePath(), tabid, "utf-8");
241
+ } catch {
242
+ }
243
+ }
244
+ function getLastActiveTabId() {
245
+ try {
246
+ const tabid = import_fs2.default.readFileSync(getLastTabIdFilePath(), "utf-8").trim();
247
+ return tabid || null;
248
+ } catch {
249
+ return null;
250
+ }
251
+ }
221
252
  function httpGet(url, timeoutMs = 1500) {
222
253
  return new Promise((resolve3, reject) => {
223
254
  const req = import_http.default.get(url, { timeout: timeoutMs }, (res) => {
@@ -476,33 +507,42 @@ async function getTargetPage(browser, tabid) {
476
507
  }
477
508
  targetPage = await target.page();
478
509
  } else {
479
- try {
480
- const urls = [
481
- `http://localhost:${CDP_PORT}/json/list`,
482
- `http://127.0.0.1:${CDP_PORT}/json/list`
483
- ];
484
- let activeTargetId = null;
485
- for (const url of urls) {
486
- try {
487
- const body = await httpGet(url, 1e3);
488
- const targetsData = JSON.parse(body);
489
- const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
490
- if (active) {
491
- activeTargetId = active.id;
492
- break;
510
+ const lastTabId = getLastActiveTabId();
511
+ if (lastTabId) {
512
+ const lastTarget = findPageTargetByTabId(browser, lastTabId);
513
+ if (lastTarget) {
514
+ targetPage = await lastTarget.page();
515
+ }
516
+ }
517
+ if (!targetPage) {
518
+ try {
519
+ const urls = [
520
+ `http://localhost:${CDP_PORT}/json/list`,
521
+ `http://127.0.0.1:${CDP_PORT}/json/list`
522
+ ];
523
+ let activeTargetId = null;
524
+ for (const url of urls) {
525
+ try {
526
+ const body = await httpGet(url, 1e3);
527
+ const targetsData = JSON.parse(body);
528
+ const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
529
+ if (active) {
530
+ activeTargetId = active.id;
531
+ break;
532
+ }
533
+ } catch {
493
534
  }
494
- } catch {
495
535
  }
496
- }
497
- if (activeTargetId) {
498
- for (const target of pageTargets) {
499
- if (getTargetIdFromTarget(target) === activeTargetId) {
500
- targetPage = await target.page();
501
- break;
536
+ if (activeTargetId) {
537
+ for (const target of pageTargets) {
538
+ if (getTargetIdFromTarget(target) === activeTargetId) {
539
+ targetPage = await target.page();
540
+ break;
541
+ }
502
542
  }
503
543
  }
544
+ } catch {
504
545
  }
505
- } catch {
506
546
  }
507
547
  if (!targetPage) {
508
548
  const lastTarget = pageTargets[pageTargets.length - 1];
@@ -583,6 +623,12 @@ async function injectDomainTools(page) {
583
623
  if (!import_fs2.default.existsSync(toolBundlePath)) {
584
624
  return;
585
625
  }
626
+ const bundleKey = bundleName.replace(/^webmcp-tools\//, "").replace(/\.js$/, "");
627
+ const flagKey = `__webmcptools_${bundleKey.replace(/[^a-zA-Z0-9]/g, "")}`;
628
+ const alreadyInjected = await page.evaluate((key) => !!window[key], flagKey).catch(() => false);
629
+ if (alreadyInjected) {
630
+ return;
631
+ }
586
632
  console.log(import_picocolors.default.cyan(`\u68C0\u6D4B\u5230\u57DF\u540D ${hostname} \u6709\u9884\u7F6E\u5DE5\u5177\uFF0C\u6B63\u5728\u6CE8\u5165...`));
587
633
  const toolScript = import_fs2.default.readFileSync(toolBundlePath, "utf-8");
588
634
  try {
@@ -603,14 +649,15 @@ async function stateCommand({ tabid }) {
603
649
  const state = await page.evaluate(async () => {
604
650
  const url = document.URL;
605
651
  const title = document.title;
606
- const mcp = navigator.modelContextTesting || navigator.modelContext;
652
+ const mcp = document.modelContext;
607
653
  let webmcpTools = [];
608
654
  if (mcp && typeof mcp.listTools === "function") {
609
- const toolsResult = await mcp.listTools();
655
+ const toolsResult = await mcp.getTools();
610
656
  webmcpTools = toolsResult?.tools || toolsResult || [];
611
657
  }
612
658
  return { url, title, webmcpTools };
613
659
  });
660
+ const activeTabid = await getPageTargetId(page).catch(() => null);
614
661
  const pages = await browser.pages();
615
662
  const tabs2 = await Promise.all(
616
663
  pages.map(async (p) => {
@@ -630,6 +677,7 @@ async function stateCommand({ tabid }) {
630
677
  );
631
678
  return {
632
679
  ...state,
680
+ activeTabid,
633
681
  tabs: tabs2.filter(Boolean)
634
682
  };
635
683
  } finally {
@@ -637,6 +685,155 @@ async function stateCommand({ tabid }) {
637
685
  }
638
686
  }
639
687
 
688
+ // src/zhihu/create-article.ts
689
+ var import_os2 = __toESM(require("os"), 1);
690
+
691
+ // src/zhihu/markdown.ts
692
+ var import_marked = require("marked");
693
+ function stripLeadingH1(markdown) {
694
+ return markdown.replace(/^#\s+[^\n]*\n+/, "");
695
+ }
696
+ function markdownToHtml(markdown) {
697
+ const marked = new import_marked.Marked({
698
+ gfm: true,
699
+ breaks: false,
700
+ renderer: {
701
+ code({ text, lang }) {
702
+ const langClass = lang ? ` class="language-${lang}"` : "";
703
+ const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
704
+ return `<pre><code${langClass}>${escaped}</code></pre>`;
705
+ },
706
+ codespan({ text }) {
707
+ const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
708
+ return `<code>${escaped}</code>`;
709
+ }
710
+ }
711
+ });
712
+ return marked.parse(markdown);
713
+ }
714
+ function decodeBase64Content(content) {
715
+ return Buffer.from(content, "base64").toString("utf-8");
716
+ }
717
+ var ZHIHU_WRITE_PAGE_RE = /^https:\/\/zhuanlan\.zhihu\.com\/(write|p\/\d+\/edit)/;
718
+ function isZhihuWriteUrl(url) {
719
+ return ZHIHU_WRITE_PAGE_RE.test(url);
720
+ }
721
+
722
+ // src/zhihu/create-article.ts
723
+ function delay(ms) {
724
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
725
+ }
726
+ function getPasteModifier() {
727
+ return import_os2.default.platform() === "darwin" ? "Meta" : "Control";
728
+ }
729
+ async function fillTitle(page, title) {
730
+ await page.evaluate((titleText) => {
731
+ const input = document.querySelector('textarea[placeholder*="\u6807\u9898"]') || document.querySelector(".WriteIndex-titleInput textarea") || document.querySelector(".WriteIndex-titleInput input");
732
+ 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");
733
+ const setter = Object.getOwnPropertyDescriptor(
734
+ input instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype,
735
+ "value"
736
+ )?.set;
737
+ if (setter) {
738
+ setter.call(input, titleText);
739
+ } else {
740
+ input.value = titleText;
741
+ }
742
+ input.dispatchEvent(new InputEvent("input", { bubbles: true, cancelable: true, data: titleText }));
743
+ input.dispatchEvent(new Event("change", { bubbles: true }));
744
+ if (input.value.trim() !== titleText.trim()) {
745
+ throw new Error("\u6807\u9898\u586B\u5199\u5931\u8D25\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u540E\u91CD\u8BD5");
746
+ }
747
+ }, title);
748
+ }
749
+ async function pasteHtmlToEditor(page, html) {
750
+ const browser = page.browser();
751
+ const tempPage = await browser.newPage();
752
+ const modifier = getPasteModifier();
753
+ try {
754
+ const fullHtml = `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>${html}</body></html>`;
755
+ await tempPage.setContent(fullHtml, { waitUntil: "domcontentloaded" });
756
+ await delay(300);
757
+ await tempPage.click("body");
758
+ await tempPage.keyboard.down(modifier);
759
+ await tempPage.keyboard.press("KeyA");
760
+ await tempPage.keyboard.up(modifier);
761
+ await delay(100);
762
+ await tempPage.keyboard.down(modifier);
763
+ await tempPage.keyboard.press("KeyC");
764
+ await tempPage.keyboard.up(modifier);
765
+ await delay(300);
766
+ } finally {
767
+ await tempPage.close();
768
+ }
769
+ await page.bringToFront();
770
+ await page.waitForSelector(".public-DraftEditor-content", { timeout: 1e4 });
771
+ await page.click(".public-DraftEditor-content");
772
+ await delay(200);
773
+ await page.keyboard.down(modifier);
774
+ await page.keyboard.press("KeyA");
775
+ await page.keyboard.up(modifier);
776
+ await delay(100);
777
+ await page.keyboard.press("Backspace");
778
+ await delay(200);
779
+ await page.keyboard.down(modifier);
780
+ await page.keyboard.press("KeyV");
781
+ await page.keyboard.up(modifier);
782
+ await delay(1500);
783
+ const probe = html.replace(/<[^>]+>/g, "").trim().slice(0, 15);
784
+ const verifyResult = await page.evaluate((probeText) => {
785
+ const editor = document.querySelector(".public-DraftEditor-content");
786
+ if (!editor) return { ok: false, reason: "no editor" };
787
+ const text = (editor.innerText || "").trim();
788
+ if (!text) return { ok: false, reason: "empty" };
789
+ const inner = editor.innerHTML;
790
+ const hasRichFormat = inner.includes("data-text") || /font-weight:\s*bold|font-style:\s*italic|code-block|blockquote/i.test(inner);
791
+ const normalizedText = text.replace(/\s+/g, "");
792
+ const normalizedProbe = probeText.replace(/\s+/g, "");
793
+ const hasProbe = normalizedText.includes(normalizedProbe);
794
+ return {
795
+ ok: text.length > 20 && hasProbe && (hasRichFormat || text.length > 50),
796
+ reason: !hasProbe ? "probe mismatch" : !hasRichFormat && text.length <= 50 ? "no rich format" : void 0
797
+ };
798
+ }, probe);
799
+ if (!verifyResult.ok) {
800
+ throw new Error(
801
+ `\u6B63\u6587\u586B\u5199\u5931\u8D25\uFF08Markdown \u8F6C HTML \u7C98\u8D34\u672A\u751F\u6548\uFF09${verifyResult.reason ? `: ${verifyResult.reason}` : ""}`
802
+ );
803
+ }
804
+ }
805
+ async function zhihuCreateArticle(page, args) {
806
+ const { title, content } = args;
807
+ if (!title?.trim()) throw new Error("\u53C2\u6570 title \u4E0D\u80FD\u4E3A\u7A7A");
808
+ if (!content?.trim()) throw new Error("\u53C2\u6570 content \u4E0D\u80FD\u4E3A\u7A7A");
809
+ const url = page.url();
810
+ if (!isZhihuWriteUrl(url)) {
811
+ 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");
812
+ }
813
+ let markdown;
814
+ try {
815
+ markdown = stripLeadingH1(decodeBase64Content(content));
816
+ } catch {
817
+ 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");
818
+ }
819
+ if (!markdown.trim()) {
820
+ throw new Error("\u6B63\u6587\u5185\u5BB9\u4E3A\u7A7A\uFF0C\u8BF7\u68C0\u67E5 Markdown \u6587\u4EF6");
821
+ }
822
+ const html = markdownToHtml(markdown);
823
+ await fillTitle(page, title.trim());
824
+ await delay(500);
825
+ await pasteHtmlToEditor(page, html);
826
+ const result = {
827
+ success: true,
828
+ 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",
829
+ title: title.trim(),
830
+ contentLength: markdown.length
831
+ };
832
+ return {
833
+ content: [{ type: "text", text: JSON.stringify(result) }]
834
+ };
835
+ }
836
+
640
837
  // src/commands/run.ts
641
838
  async function runCommand({
642
839
  toolName,
@@ -646,28 +843,65 @@ async function runCommand({
646
843
  const browser = await connectBrowser();
647
844
  try {
648
845
  const page = await getTargetPage(browser, tabid);
846
+ const urlBefore = page.url();
649
847
  const cleanedArgs = argsJson.trim();
650
- const result = await page.evaluate(async (name, inputString) => {
651
- const mcp = navigator.modelContextTesting || navigator.modelContext;
652
- if (!mcp || typeof mcp.executeTool !== "function") {
653
- throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (navigator.modelContextTesting.executeTool \u672A\u627E\u5230)");
848
+ if (toolName === "create_article") {
849
+ let parsedArgs;
850
+ try {
851
+ parsedArgs = JSON.parse(cleanedArgs);
852
+ } catch {
853
+ throw new Error("\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON");
854
+ }
855
+ if (isZhihuWriteUrl(page.url())) {
856
+ return await zhihuCreateArticle(page, parsedArgs);
654
857
  }
655
- let res = await mcp.executeTool(name, inputString);
656
- if (typeof res === "string") {
858
+ }
859
+ let result;
860
+ try {
861
+ result = await page.evaluate(async (name, inputString) => {
862
+ const mcp = document.modelContext || document.modelContext || document.modelContext;
863
+ if (!mcp || typeof mcp.executeTool !== "function") {
864
+ throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (document.modelContext \u672A\u627E\u5230)");
865
+ }
866
+ const tools = await mcp.getTools();
867
+ const toolObj = tools.find((t) => t.name === name);
868
+ if (!toolObj) throw new Error(`Tool ${name} not found`);
869
+ let res = await mcp.executeTool(toolObj, inputString);
870
+ if (typeof res === "string") {
871
+ try {
872
+ res = JSON.parse(res);
873
+ } catch {
874
+ }
875
+ }
876
+ if (res === void 0 || res === null) {
877
+ throw new Error("\u5DE5\u5177 execute \u672A\u8FD4\u56DE\u7ED3\u679C\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u5B9E\u73B0\u662F\u5426\u7F3A\u5C11 return");
878
+ }
879
+ if (typeof res === "object" && res.success === false) {
880
+ const failed = res;
881
+ throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
882
+ }
883
+ return res;
884
+ }, toolName, cleanedArgs);
885
+ } catch (evalError) {
886
+ const errMsg = evalError instanceof Error ? evalError.message : String(evalError);
887
+ const isContextDestroyed = errMsg.includes("context was destroyed") || errMsg.includes("Execution context was destroyed") || errMsg.includes("Cannot read properties of null") && errMsg.includes("context");
888
+ if (isContextDestroyed) {
889
+ await new Promise((resolve3) => setTimeout(resolve3, 800));
890
+ let newUrl = "";
657
891
  try {
658
- res = JSON.parse(res);
892
+ newUrl = page.url();
659
893
  } catch {
660
894
  }
895
+ if (newUrl && newUrl !== urlBefore) {
896
+ return {
897
+ success: true,
898
+ message: `\u5DE5\u5177 ${toolName} \u6267\u884C\u5B8C\u6210\uFF0C\u9875\u9762\u5DF2\u5BFC\u822A\u81F3 ${newUrl}`,
899
+ navigatedTo: newUrl
900
+ };
901
+ }
661
902
  }
662
- if (res === void 0 || res === null) {
663
- throw new Error("\u5DE5\u5177 execute \u672A\u8FD4\u56DE\u7ED3\u679C\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u5B9E\u73B0\u662F\u5426\u7F3A\u5C11 return");
664
- }
665
- if (typeof res === "object" && res.success === false) {
666
- const failed = res;
667
- throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
668
- }
669
- return res;
670
- }, toolName, cleanedArgs);
903
+ throw evalError;
904
+ }
671
905
  return result;
672
906
  } finally {
673
907
  await browser.disconnect();
@@ -699,11 +933,19 @@ async function tabsOpenCommand(url) {
699
933
  await page.goto(url, { waitUntil: "domcontentloaded" });
700
934
  await injectIntoPage(page);
701
935
  await new Promise((resolve3) => setTimeout(resolve3, 500));
936
+ let tabid;
937
+ try {
938
+ tabid = await getPageTargetId(page);
939
+ await activateTabById(browser, tabid);
940
+ setLastActiveTabId(tabid);
941
+ } catch (e) {
942
+ console.warn("Best-effort tab activation failed:", e);
943
+ }
702
944
  return {
703
945
  success: true,
704
- tabid: await getPageTargetId(page),
946
+ tabid,
705
947
  url: page.url(),
706
- title: await page.title()
948
+ title: await page.title().catch(() => "")
707
949
  };
708
950
  } finally {
709
951
  await browser.disconnect();
@@ -735,9 +977,11 @@ async function tabsSwitchCommand(tabid) {
735
977
  try {
736
978
  await activateTabById(browser, tabid);
737
979
  const page = await getTargetPage(browser, tabid);
980
+ const realTabid = await getPageTargetId(page);
981
+ setLastActiveTabId(realTabid);
738
982
  return {
739
983
  success: true,
740
- tabid: await getPageTargetId(page),
984
+ tabid: realTabid,
741
985
  url: page.url(),
742
986
  title: await page.title()
743
987
  };
@@ -799,7 +1043,7 @@ async function tabsForwardCommand(tabid) {
799
1043
  // package.json
800
1044
  var package_default = {
801
1045
  name: "@opentiny/webmcp-cli",
802
- version: "0.0.3",
1046
+ version: "0.0.6",
803
1047
  type: "module",
804
1048
  description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
805
1049
  main: "dist/index.js",
@@ -839,6 +1083,7 @@ var package_default = {
839
1083
  clipboardy: "^5.3.1",
840
1084
  commander: "^11.1.0",
841
1085
  json5: "^2.2.3",
1086
+ marked: "^15.0.7",
842
1087
  picocolors: "^1.0.0",
843
1088
  "puppeteer-core": "^22.0.0"
844
1089
  },
@@ -899,9 +1144,66 @@ function cleanOldLogs(baseDir, logDir) {
899
1144
  } catch (e) {
900
1145
  }
901
1146
  }
1147
+ function formatLogResult(result) {
1148
+ if (result === void 0 || result === null) {
1149
+ return "null";
1150
+ }
1151
+ try {
1152
+ if (typeof result !== "object") {
1153
+ return String(result);
1154
+ }
1155
+ const clone = JSON.parse(JSON.stringify(result));
1156
+ let a11yTreeStr = "";
1157
+ let searchResultStr = "";
1158
+ if (clone && Array.isArray(clone.content) && clone.content.length > 0 && clone.content[0] && typeof clone.content[0].text === "string") {
1159
+ const text = clone.content[0].text;
1160
+ if (text.startsWith("\u6D4F\u89C8\u5668\u72B6\u6001: ")) {
1161
+ let jsonStr = text.substring("\u6D4F\u89C8\u5668\u72B6\u6001: ".length);
1162
+ let extraAlerts = "";
1163
+ const firstNewline = jsonStr.indexOf("\n");
1164
+ if (firstNewline !== -1) {
1165
+ extraAlerts = jsonStr.substring(firstNewline);
1166
+ jsonStr = jsonStr.substring(0, firstNewline);
1167
+ }
1168
+ try {
1169
+ const parsed = JSON.parse(jsonStr);
1170
+ if (parsed && typeof parsed.content === "string") {
1171
+ a11yTreeStr = parsed.content;
1172
+ parsed.content = "[Formatted A11y Tree - See details below]";
1173
+ clone.content[0].text = `\u6D4F\u89C8\u5668\u72B6\u6001: ${JSON.stringify(parsed, null, 2)}${extraAlerts}`;
1174
+ }
1175
+ } catch {
1176
+ }
1177
+ } 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:")) {
1178
+ searchResultStr = text;
1179
+ clone.content[0].text = "[Search A11y Tree Result - See details below]";
1180
+ }
1181
+ }
1182
+ let out = JSON.stringify(clone, null, 2);
1183
+ if (a11yTreeStr) {
1184
+ out += `
1185
+
1186
+ [FORMATTED A11Y TREE]:
1187
+ ${a11yTreeStr}`;
1188
+ }
1189
+ if (searchResultStr) {
1190
+ out += `
1191
+
1192
+ [FORMATTED SEARCH RESULT]:
1193
+ ${searchResultStr}`;
1194
+ }
1195
+ return out;
1196
+ } catch (e) {
1197
+ try {
1198
+ return JSON.stringify(result, null, 2);
1199
+ } catch {
1200
+ return String(result);
1201
+ }
1202
+ }
1203
+ }
902
1204
  function writeLog(commandName, args, result, error) {
903
1205
  try {
904
- const baseDir = process.env.WEBMCP_WORKSPACE || import_path3.default.join(import_os2.default.homedir(), ".webmcp_chrome_profile");
1206
+ const baseDir = process.env.WEBMCP_WORKSPACE || import_path3.default.join(import_os3.default.homedir(), ".webmcp_chrome_profile");
905
1207
  const logDir = import_path3.default.join(baseDir, "logs");
906
1208
  if (!import_fs3.default.existsSync(logDir)) {
907
1209
  import_fs3.default.mkdirSync(logDir, { recursive: true });
@@ -924,15 +1226,8 @@ ${error instanceof Error ? error.stack : String(error)}
924
1226
  } else if (result) {
925
1227
  logText += `RESULT:
926
1228
  `;
927
- try {
928
- if (result && typeof result === "object") {
929
- logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
930
- } else {
931
- logText += String(result).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
932
- }
933
- } catch {
934
- logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
935
- }
1229
+ const formattedResult = formatLogResult(result);
1230
+ logText += formattedResult.split("\n").map((line) => ` ${line}`).join("\n") + "\n";
936
1231
  } else {
937
1232
  logText += `RESULT: null
938
1233
  `;
@@ -962,7 +1257,7 @@ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D
962
1257
  try {
963
1258
  const result = await stateCommand(args);
964
1259
  writeLog("state", args, result);
965
- console.log(JSON.stringify(result, null, 2));
1260
+ console.log(formatLogResult(result));
966
1261
  } catch (error) {
967
1262
  handleCommandError(error, "state", args);
968
1263
  }
@@ -996,7 +1291,7 @@ program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u987
996
1291
  tabid: parseTabId(options.tabid)
997
1292
  });
998
1293
  writeLog("run", { ...rawArgs, finalArgsJson }, result);
999
- console.log(JSON.stringify(result, null, 2));
1294
+ console.log(formatLogResult(result));
1000
1295
  } catch (error) {
1001
1296
  handleCommandError(error, "run", { ...rawArgs, finalArgsJson });
1002
1297
  }