@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 +1 -1
- package/dist/bin.cjs +233 -19
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +233 -19
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +168 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +168 -5
- package/dist/index.js.map +1 -1
- package/dist/inject-bundle.js +2704 -1420
- package/dist/webmcp-tools/creator.xiaohongshu.com.js +1 -1
- package/dist/webmcp-tools/editor.csdn.net.js +1 -1
- package/dist/webmcp-tools/excalidraw.com.js +1 -1
- package/dist/webmcp-tools/juejin.cn.js +1 -1
- package/dist/webmcp-tools/my.oschina.net.js +1 -1
- package/dist/webmcp-tools/segmentfault.com.js +856 -0
- package/dist/webmcp-tools/www.baidu.com.js +1 -1
- package/dist/webmcp-tools/xiaohongshu.com.js +1 -1
- package/dist/webmcp-tools/zhuanlan.zhihu.com.js +238 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ webmcp-cli run <toolName> <argsJson> [-t tabid]
|
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
**示例一:执行页面自带工具**
|
|
79
|
-
假设网页使用 `
|
|
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
|
|
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 =
|
|
652
|
+
const mcp = document.modelContext;
|
|
653
653
|
let webmcpTools = [];
|
|
654
654
|
if (mcp && typeof mcp.listTools === "function") {
|
|
655
|
-
const toolsResult = await mcp.
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
704
|
+
return `<pre><code${langClass}>${escaped}</code></pre>`;
|
|
705
|
+
},
|
|
706
|
+
codespan({ text }) {
|
|
707
|
+
const escaped = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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 =
|
|
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 (
|
|
864
|
+
throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (document.modelContext \u672A\u627E\u5230)");
|
|
705
865
|
}
|
|
706
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
1009
|
-
|
|
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(
|
|
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(
|
|
1294
|
+
console.log(formatLogResult(result));
|
|
1081
1295
|
} catch (error) {
|
|
1082
1296
|
handleCommandError(error, "run", { ...rawArgs, finalArgsJson });
|
|
1083
1297
|
}
|