@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 +1 -1
- package/dist/bin.cjs +364 -69
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +364 -69
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +257 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +257 -40
- 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 +4 -2
- package/dist/webmcp-tools/juejin.cn.js +132 -63
- package/dist/webmcp-tools/my.oschina.net.js +4 -2
- package/dist/webmcp-tools/segmentfault.com.js +856 -0
- package/dist/webmcp-tools/www.baidu.com.js +4 -2
- 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");
|
|
@@ -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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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("\
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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 =
|
|
652
|
+
const mcp = document.modelContext;
|
|
607
653
|
let webmcpTools = [];
|
|
608
654
|
if (mcp && typeof mcp.listTools === "function") {
|
|
609
|
-
const toolsResult = await mcp.
|
|
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, "&").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
|
+
|
|
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
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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
|
-
|
|
656
|
-
|
|
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
|
-
|
|
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
|
-
|
|
663
|
-
|
|
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
|
|
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:
|
|
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.
|
|
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(
|
|
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
|
-
|
|
928
|
-
|
|
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(
|
|
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(
|
|
1294
|
+
console.log(formatLogResult(result));
|
|
1000
1295
|
} catch (error) {
|
|
1001
1296
|
handleCommandError(error, "run", { ...rawArgs, finalArgsJson });
|
|
1002
1297
|
}
|