@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/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";
|
|
@@ -161,21 +161,28 @@ ${hint}`);
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
function prepareRunArgsJson(args, fileContent, baseDir) {
|
|
164
|
-
let raw = "";
|
|
165
164
|
if (fileContent !== void 0) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (joined.startsWith("{") || looksLikeObjectBody(joined)) {
|
|
170
|
-
raw = joined;
|
|
171
|
-
} else if (args.every((arg) => arg.includes("="))) {
|
|
172
|
-
raw = buildArgsFromKeyValuePairs(args);
|
|
173
|
-
} else {
|
|
174
|
-
raw = joined;
|
|
165
|
+
const trimmed = fileContent.trim();
|
|
166
|
+
if (!trimmed) {
|
|
167
|
+
throw new Error("\u901A\u8FC7 -f/--file \u6307\u5B9A\u7684\u53C2\u6570\u6587\u4EF6\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A");
|
|
175
168
|
}
|
|
169
|
+
const expanded2 = expandFileRefs(trimmed, baseDir);
|
|
170
|
+
return parseArgsJson(expanded2);
|
|
171
|
+
}
|
|
172
|
+
if (args.length === 0) {
|
|
173
|
+
return "{}";
|
|
174
|
+
}
|
|
175
|
+
const joined = joinShellArgs(args);
|
|
176
|
+
let raw = "";
|
|
177
|
+
if (joined.startsWith("{") || looksLikeObjectBody(joined)) {
|
|
178
|
+
raw = joined;
|
|
179
|
+
} else if (args.every((arg) => arg.includes("="))) {
|
|
180
|
+
raw = buildArgsFromKeyValuePairs(args);
|
|
181
|
+
} else {
|
|
182
|
+
raw = joined;
|
|
176
183
|
}
|
|
177
|
-
if (!raw) {
|
|
178
|
-
throw new Error("\
|
|
184
|
+
if (!raw.trim()) {
|
|
185
|
+
throw new Error("\u672A\u63D0\u4F9B\u6709\u6548\u53C2\u6570");
|
|
179
186
|
}
|
|
180
187
|
const expanded = expandFileRefs(raw, baseDir);
|
|
181
188
|
return parseArgsJson(expanded);
|
|
@@ -194,6 +201,30 @@ var __filename = fileURLToPath2(import.meta.url);
|
|
|
194
201
|
var __dirname = path.dirname(__filename);
|
|
195
202
|
var CDP_PORT = 9222;
|
|
196
203
|
var CDP_URL = `http://localhost:${CDP_PORT}`;
|
|
204
|
+
function getWorkspaceDir() {
|
|
205
|
+
return process.env.WEBMCP_WORKSPACE || path.join(os.homedir(), ".webmcp_chrome_profile");
|
|
206
|
+
}
|
|
207
|
+
function getLastTabIdFilePath() {
|
|
208
|
+
return path.join(getWorkspaceDir(), ".last-tab-id");
|
|
209
|
+
}
|
|
210
|
+
function setLastActiveTabId(tabid) {
|
|
211
|
+
try {
|
|
212
|
+
const dir = getWorkspaceDir();
|
|
213
|
+
if (!fs.existsSync(dir)) {
|
|
214
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
215
|
+
}
|
|
216
|
+
fs.writeFileSync(getLastTabIdFilePath(), tabid, "utf-8");
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function getLastActiveTabId() {
|
|
221
|
+
try {
|
|
222
|
+
const tabid = fs.readFileSync(getLastTabIdFilePath(), "utf-8").trim();
|
|
223
|
+
return tabid || null;
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
197
228
|
function httpGet(url, timeoutMs = 1500) {
|
|
198
229
|
return new Promise((resolve3, reject) => {
|
|
199
230
|
const req = http.get(url, { timeout: timeoutMs }, (res) => {
|
|
@@ -452,33 +483,42 @@ async function getTargetPage(browser, tabid) {
|
|
|
452
483
|
}
|
|
453
484
|
targetPage = await target.page();
|
|
454
485
|
} else {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
486
|
+
const lastTabId = getLastActiveTabId();
|
|
487
|
+
if (lastTabId) {
|
|
488
|
+
const lastTarget = findPageTargetByTabId(browser, lastTabId);
|
|
489
|
+
if (lastTarget) {
|
|
490
|
+
targetPage = await lastTarget.page();
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (!targetPage) {
|
|
494
|
+
try {
|
|
495
|
+
const urls = [
|
|
496
|
+
`http://localhost:${CDP_PORT}/json/list`,
|
|
497
|
+
`http://127.0.0.1:${CDP_PORT}/json/list`
|
|
498
|
+
];
|
|
499
|
+
let activeTargetId = null;
|
|
500
|
+
for (const url of urls) {
|
|
501
|
+
try {
|
|
502
|
+
const body = await httpGet(url, 1e3);
|
|
503
|
+
const targetsData = JSON.parse(body);
|
|
504
|
+
const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
|
|
505
|
+
if (active) {
|
|
506
|
+
activeTargetId = active.id;
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
} catch {
|
|
469
510
|
}
|
|
470
|
-
} catch {
|
|
471
511
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
512
|
+
if (activeTargetId) {
|
|
513
|
+
for (const target of pageTargets) {
|
|
514
|
+
if (getTargetIdFromTarget(target) === activeTargetId) {
|
|
515
|
+
targetPage = await target.page();
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
478
518
|
}
|
|
479
519
|
}
|
|
520
|
+
} catch {
|
|
480
521
|
}
|
|
481
|
-
} catch {
|
|
482
522
|
}
|
|
483
523
|
if (!targetPage) {
|
|
484
524
|
const lastTarget = pageTargets[pageTargets.length - 1];
|
|
@@ -559,6 +599,12 @@ async function injectDomainTools(page) {
|
|
|
559
599
|
if (!fs.existsSync(toolBundlePath)) {
|
|
560
600
|
return;
|
|
561
601
|
}
|
|
602
|
+
const bundleKey = bundleName.replace(/^webmcp-tools\//, "").replace(/\.js$/, "");
|
|
603
|
+
const flagKey = `__webmcptools_${bundleKey.replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
604
|
+
const alreadyInjected = await page.evaluate((key) => !!window[key], flagKey).catch(() => false);
|
|
605
|
+
if (alreadyInjected) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
562
608
|
console.log(pc.cyan(`\u68C0\u6D4B\u5230\u57DF\u540D ${hostname} \u6709\u9884\u7F6E\u5DE5\u5177\uFF0C\u6B63\u5728\u6CE8\u5165...`));
|
|
563
609
|
const toolScript = fs.readFileSync(toolBundlePath, "utf-8");
|
|
564
610
|
try {
|
|
@@ -579,14 +625,15 @@ async function stateCommand({ tabid }) {
|
|
|
579
625
|
const state = await page.evaluate(async () => {
|
|
580
626
|
const url = document.URL;
|
|
581
627
|
const title = document.title;
|
|
582
|
-
const mcp =
|
|
628
|
+
const mcp = document.modelContext;
|
|
583
629
|
let webmcpTools = [];
|
|
584
630
|
if (mcp && typeof mcp.listTools === "function") {
|
|
585
|
-
const toolsResult = await mcp.
|
|
631
|
+
const toolsResult = await mcp.getTools();
|
|
586
632
|
webmcpTools = toolsResult?.tools || toolsResult || [];
|
|
587
633
|
}
|
|
588
634
|
return { url, title, webmcpTools };
|
|
589
635
|
});
|
|
636
|
+
const activeTabid = await getPageTargetId(page).catch(() => null);
|
|
590
637
|
const pages = await browser.pages();
|
|
591
638
|
const tabs2 = await Promise.all(
|
|
592
639
|
pages.map(async (p) => {
|
|
@@ -606,6 +653,7 @@ async function stateCommand({ tabid }) {
|
|
|
606
653
|
);
|
|
607
654
|
return {
|
|
608
655
|
...state,
|
|
656
|
+
activeTabid,
|
|
609
657
|
tabs: tabs2.filter(Boolean)
|
|
610
658
|
};
|
|
611
659
|
} finally {
|
|
@@ -613,6 +661,155 @@ async function stateCommand({ tabid }) {
|
|
|
613
661
|
}
|
|
614
662
|
}
|
|
615
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
|
+
|
|
616
813
|
// src/commands/run.ts
|
|
617
814
|
async function runCommand({
|
|
618
815
|
toolName,
|
|
@@ -622,28 +819,65 @@ async function runCommand({
|
|
|
622
819
|
const browser = await connectBrowser();
|
|
623
820
|
try {
|
|
624
821
|
const page = await getTargetPage(browser, tabid);
|
|
822
|
+
const urlBefore = page.url();
|
|
625
823
|
const cleanedArgs = argsJson.trim();
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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);
|
|
630
833
|
}
|
|
631
|
-
|
|
632
|
-
|
|
834
|
+
}
|
|
835
|
+
let result;
|
|
836
|
+
try {
|
|
837
|
+
result = await page.evaluate(async (name, inputString) => {
|
|
838
|
+
const mcp = document.modelContext || document.modelContext || document.modelContext;
|
|
839
|
+
if (!mcp || typeof mcp.executeTool !== "function") {
|
|
840
|
+
throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (document.modelContext \u672A\u627E\u5230)");
|
|
841
|
+
}
|
|
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);
|
|
846
|
+
if (typeof res === "string") {
|
|
847
|
+
try {
|
|
848
|
+
res = JSON.parse(res);
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (res === void 0 || res === null) {
|
|
853
|
+
throw new Error("\u5DE5\u5177 execute \u672A\u8FD4\u56DE\u7ED3\u679C\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u5B9E\u73B0\u662F\u5426\u7F3A\u5C11 return");
|
|
854
|
+
}
|
|
855
|
+
if (typeof res === "object" && res.success === false) {
|
|
856
|
+
const failed = res;
|
|
857
|
+
throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
|
|
858
|
+
}
|
|
859
|
+
return res;
|
|
860
|
+
}, toolName, cleanedArgs);
|
|
861
|
+
} catch (evalError) {
|
|
862
|
+
const errMsg = evalError instanceof Error ? evalError.message : String(evalError);
|
|
863
|
+
const isContextDestroyed = errMsg.includes("context was destroyed") || errMsg.includes("Execution context was destroyed") || errMsg.includes("Cannot read properties of null") && errMsg.includes("context");
|
|
864
|
+
if (isContextDestroyed) {
|
|
865
|
+
await new Promise((resolve3) => setTimeout(resolve3, 800));
|
|
866
|
+
let newUrl = "";
|
|
633
867
|
try {
|
|
634
|
-
|
|
868
|
+
newUrl = page.url();
|
|
635
869
|
} catch {
|
|
636
870
|
}
|
|
871
|
+
if (newUrl && newUrl !== urlBefore) {
|
|
872
|
+
return {
|
|
873
|
+
success: true,
|
|
874
|
+
message: `\u5DE5\u5177 ${toolName} \u6267\u884C\u5B8C\u6210\uFF0C\u9875\u9762\u5DF2\u5BFC\u822A\u81F3 ${newUrl}`,
|
|
875
|
+
navigatedTo: newUrl
|
|
876
|
+
};
|
|
877
|
+
}
|
|
637
878
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
if (typeof res === "object" && res.success === false) {
|
|
642
|
-
const failed = res;
|
|
643
|
-
throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
|
|
644
|
-
}
|
|
645
|
-
return res;
|
|
646
|
-
}, toolName, cleanedArgs);
|
|
879
|
+
throw evalError;
|
|
880
|
+
}
|
|
647
881
|
return result;
|
|
648
882
|
} finally {
|
|
649
883
|
await browser.disconnect();
|
|
@@ -675,11 +909,19 @@ async function tabsOpenCommand(url) {
|
|
|
675
909
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
676
910
|
await injectIntoPage(page);
|
|
677
911
|
await new Promise((resolve3) => setTimeout(resolve3, 500));
|
|
912
|
+
let tabid;
|
|
913
|
+
try {
|
|
914
|
+
tabid = await getPageTargetId(page);
|
|
915
|
+
await activateTabById(browser, tabid);
|
|
916
|
+
setLastActiveTabId(tabid);
|
|
917
|
+
} catch (e) {
|
|
918
|
+
console.warn("Best-effort tab activation failed:", e);
|
|
919
|
+
}
|
|
678
920
|
return {
|
|
679
921
|
success: true,
|
|
680
|
-
tabid
|
|
922
|
+
tabid,
|
|
681
923
|
url: page.url(),
|
|
682
|
-
title: await page.title()
|
|
924
|
+
title: await page.title().catch(() => "")
|
|
683
925
|
};
|
|
684
926
|
} finally {
|
|
685
927
|
await browser.disconnect();
|
|
@@ -711,9 +953,11 @@ async function tabsSwitchCommand(tabid) {
|
|
|
711
953
|
try {
|
|
712
954
|
await activateTabById(browser, tabid);
|
|
713
955
|
const page = await getTargetPage(browser, tabid);
|
|
956
|
+
const realTabid = await getPageTargetId(page);
|
|
957
|
+
setLastActiveTabId(realTabid);
|
|
714
958
|
return {
|
|
715
959
|
success: true,
|
|
716
|
-
tabid:
|
|
960
|
+
tabid: realTabid,
|
|
717
961
|
url: page.url(),
|
|
718
962
|
title: await page.title()
|
|
719
963
|
};
|
|
@@ -775,7 +1019,7 @@ async function tabsForwardCommand(tabid) {
|
|
|
775
1019
|
// package.json
|
|
776
1020
|
var package_default = {
|
|
777
1021
|
name: "@opentiny/webmcp-cli",
|
|
778
|
-
version: "0.0.
|
|
1022
|
+
version: "0.0.6",
|
|
779
1023
|
type: "module",
|
|
780
1024
|
description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
|
|
781
1025
|
main: "dist/index.js",
|
|
@@ -815,6 +1059,7 @@ var package_default = {
|
|
|
815
1059
|
clipboardy: "^5.3.1",
|
|
816
1060
|
commander: "^11.1.0",
|
|
817
1061
|
json5: "^2.2.3",
|
|
1062
|
+
marked: "^15.0.7",
|
|
818
1063
|
picocolors: "^1.0.0",
|
|
819
1064
|
"puppeteer-core": "^22.0.0"
|
|
820
1065
|
},
|
|
@@ -875,9 +1120,66 @@ function cleanOldLogs(baseDir, logDir) {
|
|
|
875
1120
|
} catch (e) {
|
|
876
1121
|
}
|
|
877
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
|
+
}
|
|
878
1180
|
function writeLog(commandName, args, result, error) {
|
|
879
1181
|
try {
|
|
880
|
-
const baseDir = process.env.WEBMCP_WORKSPACE || path2.join(
|
|
1182
|
+
const baseDir = process.env.WEBMCP_WORKSPACE || path2.join(os3.homedir(), ".webmcp_chrome_profile");
|
|
881
1183
|
const logDir = path2.join(baseDir, "logs");
|
|
882
1184
|
if (!fs2.existsSync(logDir)) {
|
|
883
1185
|
fs2.mkdirSync(logDir, { recursive: true });
|
|
@@ -900,15 +1202,8 @@ ${error instanceof Error ? error.stack : String(error)}
|
|
|
900
1202
|
} else if (result) {
|
|
901
1203
|
logText += `RESULT:
|
|
902
1204
|
`;
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
|
|
906
|
-
} else {
|
|
907
|
-
logText += String(result).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
|
|
908
|
-
}
|
|
909
|
-
} catch {
|
|
910
|
-
logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
|
|
911
|
-
}
|
|
1205
|
+
const formattedResult = formatLogResult(result);
|
|
1206
|
+
logText += formattedResult.split("\n").map((line) => ` ${line}`).join("\n") + "\n";
|
|
912
1207
|
} else {
|
|
913
1208
|
logText += `RESULT: null
|
|
914
1209
|
`;
|
|
@@ -938,7 +1233,7 @@ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D
|
|
|
938
1233
|
try {
|
|
939
1234
|
const result = await stateCommand(args);
|
|
940
1235
|
writeLog("state", args, result);
|
|
941
|
-
console.log(
|
|
1236
|
+
console.log(formatLogResult(result));
|
|
942
1237
|
} catch (error) {
|
|
943
1238
|
handleCommandError(error, "state", args);
|
|
944
1239
|
}
|
|
@@ -972,7 +1267,7 @@ program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u987
|
|
|
972
1267
|
tabid: parseTabId(options.tabid)
|
|
973
1268
|
});
|
|
974
1269
|
writeLog("run", { ...rawArgs, finalArgsJson }, result);
|
|
975
|
-
console.log(
|
|
1270
|
+
console.log(formatLogResult(result));
|
|
976
1271
|
} catch (error) {
|
|
977
1272
|
handleCommandError(error, "run", { ...rawArgs, finalArgsJson });
|
|
978
1273
|
}
|