@opentiny/webmcp-cli 0.0.4 → 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");
@@ -649,10 +649,10 @@ async function stateCommand({ tabid }) {
649
649
  const state = await page.evaluate(async () => {
650
650
  const url = document.URL;
651
651
  const title = document.title;
652
- const mcp = navigator.modelContextTesting || navigator.modelContext;
652
+ const mcp = document.modelContext;
653
653
  let webmcpTools = [];
654
654
  if (mcp && typeof mcp.listTools === "function") {
655
- const toolsResult = await mcp.listTools();
655
+ const toolsResult = await mcp.getTools();
656
656
  webmcpTools = toolsResult?.tools || toolsResult || [];
657
657
  }
658
658
  return { url, title, webmcpTools };
@@ -685,6 +685,155 @@ async function stateCommand({ tabid }) {
685
685
  }
686
686
  }
687
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
+
688
837
  // src/commands/run.ts
689
838
  async function runCommand({
690
839
  toolName,
@@ -696,14 +845,28 @@ async function runCommand({
696
845
  const page = await getTargetPage(browser, tabid);
697
846
  const urlBefore = page.url();
698
847
  const cleanedArgs = argsJson.trim();
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);
857
+ }
858
+ }
699
859
  let result;
700
860
  try {
701
861
  result = await page.evaluate(async (name, inputString) => {
702
- const mcp = navigator.modelContextTesting || navigator.modelContext;
862
+ const mcp = document.modelContext || document.modelContext || document.modelContext;
703
863
  if (!mcp || typeof mcp.executeTool !== "function") {
704
- throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (navigator.modelContextTesting.executeTool \u672A\u627E\u5230)");
864
+ throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (document.modelContext \u672A\u627E\u5230)");
705
865
  }
706
- let res = await mcp.executeTool(name, inputString);
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);
707
870
  if (typeof res === "string") {
708
871
  try {
709
872
  res = JSON.parse(res);
@@ -880,7 +1043,7 @@ async function tabsForwardCommand(tabid) {
880
1043
  // package.json
881
1044
  var package_default = {
882
1045
  name: "@opentiny/webmcp-cli",
883
- version: "0.0.4",
1046
+ version: "0.0.6",
884
1047
  type: "module",
885
1048
  description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
886
1049
  main: "dist/index.js",
@@ -920,6 +1083,7 @@ var package_default = {
920
1083
  clipboardy: "^5.3.1",
921
1084
  commander: "^11.1.0",
922
1085
  json5: "^2.2.3",
1086
+ marked: "^15.0.7",
923
1087
  picocolors: "^1.0.0",
924
1088
  "puppeteer-core": "^22.0.0"
925
1089
  },
@@ -980,9 +1144,66 @@ function cleanOldLogs(baseDir, logDir) {
980
1144
  } catch (e) {
981
1145
  }
982
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
+ }
983
1204
  function writeLog(commandName, args, result, error) {
984
1205
  try {
985
- 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");
986
1207
  const logDir = import_path3.default.join(baseDir, "logs");
987
1208
  if (!import_fs3.default.existsSync(logDir)) {
988
1209
  import_fs3.default.mkdirSync(logDir, { recursive: true });
@@ -1005,15 +1226,8 @@ ${error instanceof Error ? error.stack : String(error)}
1005
1226
  } else if (result) {
1006
1227
  logText += `RESULT:
1007
1228
  `;
1008
- try {
1009
- if (result && typeof result === "object") {
1010
- logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
1011
- } else {
1012
- logText += String(result).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
1013
- }
1014
- } catch {
1015
- logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
1016
- }
1229
+ const formattedResult = formatLogResult(result);
1230
+ logText += formattedResult.split("\n").map((line) => ` ${line}`).join("\n") + "\n";
1017
1231
  } else {
1018
1232
  logText += `RESULT: null
1019
1233
  `;
@@ -1043,7 +1257,7 @@ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D
1043
1257
  try {
1044
1258
  const result = await stateCommand(args);
1045
1259
  writeLog("state", args, result);
1046
- console.log(JSON.stringify(result, null, 2));
1260
+ console.log(formatLogResult(result));
1047
1261
  } catch (error) {
1048
1262
  handleCommandError(error, "state", args);
1049
1263
  }
@@ -1077,7 +1291,7 @@ program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u987
1077
1291
  tabid: parseTabId(options.tabid)
1078
1292
  });
1079
1293
  writeLog("run", { ...rawArgs, finalArgsJson }, result);
1080
- console.log(JSON.stringify(result, null, 2));
1294
+ console.log(formatLogResult(result));
1081
1295
  } catch (error) {
1082
1296
  handleCommandError(error, "run", { ...rawArgs, finalArgsJson });
1083
1297
  }