@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/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
|
|
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";
|
|
@@ -625,10 +625,10 @@ async function stateCommand({ tabid }) {
|
|
|
625
625
|
const state = await page.evaluate(async () => {
|
|
626
626
|
const url = document.URL;
|
|
627
627
|
const title = document.title;
|
|
628
|
-
const mcp =
|
|
628
|
+
const mcp = document.modelContext;
|
|
629
629
|
let webmcpTools = [];
|
|
630
630
|
if (mcp && typeof mcp.listTools === "function") {
|
|
631
|
-
const toolsResult = await mcp.
|
|
631
|
+
const toolsResult = await mcp.getTools();
|
|
632
632
|
webmcpTools = toolsResult?.tools || toolsResult || [];
|
|
633
633
|
}
|
|
634
634
|
return { url, title, webmcpTools };
|
|
@@ -661,6 +661,155 @@ async function stateCommand({ tabid }) {
|
|
|
661
661
|
}
|
|
662
662
|
}
|
|
663
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
680
|
+
return `<pre><code${langClass}>${escaped}</code></pre>`;
|
|
681
|
+
},
|
|
682
|
+
codespan({ text }) {
|
|
683
|
+
const escaped = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
+
|
|
664
813
|
// src/commands/run.ts
|
|
665
814
|
async function runCommand({
|
|
666
815
|
toolName,
|
|
@@ -672,14 +821,28 @@ async function runCommand({
|
|
|
672
821
|
const page = await getTargetPage(browser, tabid);
|
|
673
822
|
const urlBefore = page.url();
|
|
674
823
|
const cleanedArgs = argsJson.trim();
|
|
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);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
675
835
|
let result;
|
|
676
836
|
try {
|
|
677
837
|
result = await page.evaluate(async (name, inputString) => {
|
|
678
|
-
const mcp =
|
|
838
|
+
const mcp = document.modelContext || document.modelContext || document.modelContext;
|
|
679
839
|
if (!mcp || typeof mcp.executeTool !== "function") {
|
|
680
|
-
throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (
|
|
840
|
+
throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (document.modelContext \u672A\u627E\u5230)");
|
|
681
841
|
}
|
|
682
|
-
|
|
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);
|
|
683
846
|
if (typeof res === "string") {
|
|
684
847
|
try {
|
|
685
848
|
res = JSON.parse(res);
|
|
@@ -856,7 +1019,7 @@ async function tabsForwardCommand(tabid) {
|
|
|
856
1019
|
// package.json
|
|
857
1020
|
var package_default = {
|
|
858
1021
|
name: "@opentiny/webmcp-cli",
|
|
859
|
-
version: "0.0.
|
|
1022
|
+
version: "0.0.6",
|
|
860
1023
|
type: "module",
|
|
861
1024
|
description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
|
|
862
1025
|
main: "dist/index.js",
|
|
@@ -896,6 +1059,7 @@ var package_default = {
|
|
|
896
1059
|
clipboardy: "^5.3.1",
|
|
897
1060
|
commander: "^11.1.0",
|
|
898
1061
|
json5: "^2.2.3",
|
|
1062
|
+
marked: "^15.0.7",
|
|
899
1063
|
picocolors: "^1.0.0",
|
|
900
1064
|
"puppeteer-core": "^22.0.0"
|
|
901
1065
|
},
|
|
@@ -956,9 +1120,66 @@ function cleanOldLogs(baseDir, logDir) {
|
|
|
956
1120
|
} catch (e) {
|
|
957
1121
|
}
|
|
958
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
|
+
}
|
|
959
1180
|
function writeLog(commandName, args, result, error) {
|
|
960
1181
|
try {
|
|
961
|
-
const baseDir = process.env.WEBMCP_WORKSPACE || path2.join(
|
|
1182
|
+
const baseDir = process.env.WEBMCP_WORKSPACE || path2.join(os3.homedir(), ".webmcp_chrome_profile");
|
|
962
1183
|
const logDir = path2.join(baseDir, "logs");
|
|
963
1184
|
if (!fs2.existsSync(logDir)) {
|
|
964
1185
|
fs2.mkdirSync(logDir, { recursive: true });
|
|
@@ -981,15 +1202,8 @@ ${error instanceof Error ? error.stack : String(error)}
|
|
|
981
1202
|
} else if (result) {
|
|
982
1203
|
logText += `RESULT:
|
|
983
1204
|
`;
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
|
|
987
|
-
} else {
|
|
988
|
-
logText += String(result).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
|
|
989
|
-
}
|
|
990
|
-
} catch {
|
|
991
|
-
logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
|
|
992
|
-
}
|
|
1205
|
+
const formattedResult = formatLogResult(result);
|
|
1206
|
+
logText += formattedResult.split("\n").map((line) => ` ${line}`).join("\n") + "\n";
|
|
993
1207
|
} else {
|
|
994
1208
|
logText += `RESULT: null
|
|
995
1209
|
`;
|
|
@@ -1019,7 +1233,7 @@ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D
|
|
|
1019
1233
|
try {
|
|
1020
1234
|
const result = await stateCommand(args);
|
|
1021
1235
|
writeLog("state", args, result);
|
|
1022
|
-
console.log(
|
|
1236
|
+
console.log(formatLogResult(result));
|
|
1023
1237
|
} catch (error) {
|
|
1024
1238
|
handleCommandError(error, "state", args);
|
|
1025
1239
|
}
|
|
@@ -1053,7 +1267,7 @@ program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u987
|
|
|
1053
1267
|
tabid: parseTabId(options.tabid)
|
|
1054
1268
|
});
|
|
1055
1269
|
writeLog("run", { ...rawArgs, finalArgsJson }, result);
|
|
1056
|
-
console.log(
|
|
1270
|
+
console.log(formatLogResult(result));
|
|
1057
1271
|
} catch (error) {
|
|
1058
1272
|
handleCommandError(error, "run", { ...rawArgs, finalArgsJson });
|
|
1059
1273
|
}
|