@opentiny/webmcp-cli 0.0.1-alpha.5 → 0.0.2
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 +20 -7
- package/dist/bin.cjs +482 -291
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +466 -275
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +48 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +48 -55
- package/dist/index.js.map +1 -1
- package/dist/inject-bundle.js +2083 -226
- package/dist/webmcp-tools/creator.xiaohongshu.com.js +315 -0
- package/dist/webmcp-tools/editor.csdn.net.js +155 -0
- package/dist/webmcp-tools/juejin.cn.js +200 -22
- package/dist/webmcp-tools/my.oschina.net.js +94 -0
- package/dist/webmcp-tools/xiaohongshu.com.js +278 -0
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -1,10 +1,185 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin.ts
|
|
4
|
+
import fs2 from "fs";
|
|
5
|
+
import path2 from "path";
|
|
6
|
+
import os2 from "os";
|
|
4
7
|
import { Command } from "commander";
|
|
5
8
|
import pc2 from "picocolors";
|
|
9
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
10
|
+
import { resolve as resolve2 } from "path";
|
|
11
|
+
|
|
12
|
+
// src/expand-file-refs.ts
|
|
6
13
|
import { readFileSync } from "fs";
|
|
7
|
-
import { resolve } from "path";
|
|
14
|
+
import { dirname, isAbsolute, normalize, resolve } from "path";
|
|
15
|
+
import { fileURLToPath } from "url";
|
|
16
|
+
var JSON_STRING_LITERAL = /"(?:[^"\\]|\\.)*"/g;
|
|
17
|
+
var UNQUOTED_FILE_REF = /(?<![\\"])@(base64)?file:([^\s"',}\]]+)/g;
|
|
18
|
+
var FILE_REF_PREFIX = /^@(base64)?file:(.+)$/s;
|
|
19
|
+
function resolveFilePath(filePath, baseDir = process.cwd()) {
|
|
20
|
+
let normalizedPath = filePath.trim();
|
|
21
|
+
if (normalizedPath.startsWith('"') && normalizedPath.endsWith('"') || normalizedPath.startsWith("'") && normalizedPath.endsWith("'")) {
|
|
22
|
+
normalizedPath = normalizedPath.slice(1, -1).trim();
|
|
23
|
+
}
|
|
24
|
+
if (normalizedPath.startsWith("file://")) {
|
|
25
|
+
normalizedPath = fileURLToPath(normalizedPath);
|
|
26
|
+
}
|
|
27
|
+
if (isAbsolute(normalizedPath)) {
|
|
28
|
+
return normalize(normalizedPath);
|
|
29
|
+
}
|
|
30
|
+
return resolve(baseDir, normalizedPath);
|
|
31
|
+
}
|
|
32
|
+
function decodeFilePath(rawPath) {
|
|
33
|
+
return rawPath.replace(/\\\\/g, "\\");
|
|
34
|
+
}
|
|
35
|
+
function readFileRefContent(filePath, baseDir, base64) {
|
|
36
|
+
const absPath = resolveFilePath(filePath, baseDir);
|
|
37
|
+
let content;
|
|
38
|
+
try {
|
|
39
|
+
content = readFileSync(absPath, "utf-8");
|
|
40
|
+
} catch (e) {
|
|
41
|
+
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\u5F15\u7528 "${filePath}" (${absPath}): ${e.message}`);
|
|
42
|
+
}
|
|
43
|
+
return base64 ? Buffer.from(content, "utf-8").toString("base64") : content;
|
|
44
|
+
}
|
|
45
|
+
function expandFileRefValue(rawInner, baseDir) {
|
|
46
|
+
const match = rawInner.match(FILE_REF_PREFIX);
|
|
47
|
+
if (!match) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const [, base64Flag, rawPath] = match;
|
|
51
|
+
const filePath = decodeFilePath(rawPath);
|
|
52
|
+
const value = readFileRefContent(filePath, baseDir, Boolean(base64Flag));
|
|
53
|
+
return JSON.stringify(value);
|
|
54
|
+
}
|
|
55
|
+
function expandQuotedFileRefs(argsJson, baseDir) {
|
|
56
|
+
return argsJson.replace(JSON_STRING_LITERAL, (jsonStringLiteral) => {
|
|
57
|
+
const rawInner = jsonStringLiteral.slice(1, -1);
|
|
58
|
+
const replacement = expandFileRefValue(rawInner, baseDir);
|
|
59
|
+
return replacement ?? jsonStringLiteral;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function expandUnquotedFileRefs(argsJson, baseDir) {
|
|
63
|
+
return argsJson.replace(UNQUOTED_FILE_REF, (match, base64Flag, filePath) => {
|
|
64
|
+
const value = readFileRefContent(filePath, baseDir, Boolean(base64Flag));
|
|
65
|
+
return JSON.stringify(value);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function expandFileRefs(argsJson, baseDir = process.cwd()) {
|
|
69
|
+
const withQuotedExpanded = expandQuotedFileRefs(argsJson, baseDir);
|
|
70
|
+
return expandUnquotedFileRefs(withQuotedExpanded, baseDir);
|
|
71
|
+
}
|
|
72
|
+
function getFileBaseDir(fileOption) {
|
|
73
|
+
if (!fileOption) {
|
|
74
|
+
return process.cwd();
|
|
75
|
+
}
|
|
76
|
+
return dirname(resolve(process.cwd(), fileOption));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/parse-run-args.ts
|
|
80
|
+
import JSON5 from "json5";
|
|
81
|
+
function cleanJsonString(str) {
|
|
82
|
+
let cleaned = str.trim();
|
|
83
|
+
if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
|
|
84
|
+
cleaned = cleaned.slice(1, -1).trim();
|
|
85
|
+
}
|
|
86
|
+
return cleaned;
|
|
87
|
+
}
|
|
88
|
+
function looksLikeObjectBody(raw) {
|
|
89
|
+
return /^("?[\w-]+"?\s*:)/.test(raw);
|
|
90
|
+
}
|
|
91
|
+
function joinShellArgs(args) {
|
|
92
|
+
const cleaned = args.map(cleanJsonString);
|
|
93
|
+
const first = cleaned[0] ?? "";
|
|
94
|
+
if (first.startsWith("{")) {
|
|
95
|
+
return cleaned.join(" ");
|
|
96
|
+
}
|
|
97
|
+
if (looksLikeObjectBody(first)) {
|
|
98
|
+
let joined = cleaned.join(" ");
|
|
99
|
+
if (!joined.startsWith("{")) {
|
|
100
|
+
joined = `{${joined}}`;
|
|
101
|
+
}
|
|
102
|
+
if (!joined.endsWith("}")) {
|
|
103
|
+
joined = `${joined}}`;
|
|
104
|
+
}
|
|
105
|
+
return joined;
|
|
106
|
+
}
|
|
107
|
+
return cleaned.join(" ");
|
|
108
|
+
}
|
|
109
|
+
function insertMissingCommas(raw) {
|
|
110
|
+
return raw.replace(/"(\s+)("?[\w-]+"?\s*:)/g, '",$2');
|
|
111
|
+
}
|
|
112
|
+
function quoteUnquotedStringValues(raw) {
|
|
113
|
+
return raw.replace(
|
|
114
|
+
/([{,]\s*(?:"([^"]+)"|([\w-]+))\s*:\s*)(?!"@(?:base64)?file:|"|true|false|null|-?\d|\{|\[)([^",}\]]+?)(?=\s*[,}])/g,
|
|
115
|
+
(_match, prefix, _quotedKey, _unquotedKey, value) => {
|
|
116
|
+
const trimmed = value.trim();
|
|
117
|
+
if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(trimmed)) {
|
|
118
|
+
return prefix + trimmed;
|
|
119
|
+
}
|
|
120
|
+
return prefix + JSON.stringify(trimmed);
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
function normalizeShellDamagedJson(raw) {
|
|
125
|
+
let normalized = raw.trim();
|
|
126
|
+
normalized = insertMissingCommas(normalized);
|
|
127
|
+
normalized = quoteUnquotedStringValues(normalized);
|
|
128
|
+
return normalized;
|
|
129
|
+
}
|
|
130
|
+
function buildArgsFromKeyValuePairs(args) {
|
|
131
|
+
const obj = {};
|
|
132
|
+
for (const pair of args) {
|
|
133
|
+
const index = pair.indexOf("=");
|
|
134
|
+
if (index === -1) {
|
|
135
|
+
throw new Error(`\u65E0\u6548\u7684\u53C2\u6570\u683C\u5F0F "${pair}"\u3002\u8BF7\u4F7F\u7528 key=value\uFF0C\u6216 JSON / -f \u6587\u4EF6\u3002`);
|
|
136
|
+
}
|
|
137
|
+
const key = pair.substring(0, index).trim();
|
|
138
|
+
let val = cleanJsonString(pair.substring(index + 1).trim());
|
|
139
|
+
try {
|
|
140
|
+
obj[key] = JSON.parse(val);
|
|
141
|
+
} catch {
|
|
142
|
+
obj[key] = val;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return JSON.stringify(obj);
|
|
146
|
+
}
|
|
147
|
+
function parseArgsJson(raw) {
|
|
148
|
+
const trimmed = cleanJsonString(raw);
|
|
149
|
+
try {
|
|
150
|
+
return JSON.stringify(JSON5.parse(trimmed));
|
|
151
|
+
} catch (firstError) {
|
|
152
|
+
const repaired = normalizeShellDamagedJson(trimmed);
|
|
153
|
+
try {
|
|
154
|
+
return JSON.stringify(JSON5.parse(repaired));
|
|
155
|
+
} catch {
|
|
156
|
+
const hint = `\u63D0\u793A\uFF1Abash \u4E2D JSON \u5FC5\u987B\u7528\u5355\u5F15\u53F7\u5305\u88F9\uFF0C\u4F8B\u5982 '{"title":"\u6807\u9898","content":"@base64file:./article.md"}'\uFF1B\u6216\u4F7F\u7528 key=value\uFF1Atitle=\u6807\u9898 content=@base64file:./article.md\uFF1B\u6216\u4F7F\u7528 -f args.json\u3002`;
|
|
157
|
+
const message = firstError instanceof Error ? firstError.message : String(firstError);
|
|
158
|
+
throw new Error(`\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON \u6216 JS \u5BF9\u8C61: ${message}
|
|
159
|
+
${hint}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function prepareRunArgsJson(args, fileContent, baseDir) {
|
|
164
|
+
let raw = "";
|
|
165
|
+
if (fileContent !== void 0) {
|
|
166
|
+
raw = fileContent.trim();
|
|
167
|
+
} else if (args.length > 0) {
|
|
168
|
+
const joined = joinShellArgs(args);
|
|
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;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!raw) {
|
|
178
|
+
throw new Error("\u5FC5\u987B\u63D0\u4F9B\u53C2\u6570\u6216\u901A\u8FC7 -f/--file \u6307\u5B9A\u53C2\u6570\u6587\u4EF6");
|
|
179
|
+
}
|
|
180
|
+
const expanded = expandFileRefs(raw, baseDir);
|
|
181
|
+
return parseArgsJson(expanded);
|
|
182
|
+
}
|
|
8
183
|
|
|
9
184
|
// src/browser.ts
|
|
10
185
|
import puppeteer from "puppeteer-core";
|
|
@@ -14,13 +189,13 @@ import os from "os";
|
|
|
14
189
|
import path from "path";
|
|
15
190
|
import http from "http";
|
|
16
191
|
import { spawn } from "child_process";
|
|
17
|
-
import { fileURLToPath } from "url";
|
|
18
|
-
var __filename =
|
|
192
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
193
|
+
var __filename = fileURLToPath2(import.meta.url);
|
|
19
194
|
var __dirname = path.dirname(__filename);
|
|
20
195
|
var CDP_PORT = 9222;
|
|
21
196
|
var CDP_URL = `http://localhost:${CDP_PORT}`;
|
|
22
197
|
function httpGet(url, timeoutMs = 1500) {
|
|
23
|
-
return new Promise((
|
|
198
|
+
return new Promise((resolve3, reject) => {
|
|
24
199
|
const req = http.get(url, { timeout: timeoutMs }, (res) => {
|
|
25
200
|
let data = "";
|
|
26
201
|
res.on("data", (chunk) => {
|
|
@@ -28,7 +203,7 @@ function httpGet(url, timeoutMs = 1500) {
|
|
|
28
203
|
});
|
|
29
204
|
res.on("end", () => {
|
|
30
205
|
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
31
|
-
|
|
206
|
+
resolve3(data);
|
|
32
207
|
} else {
|
|
33
208
|
reject(new Error(`HTTP ${res.statusCode}`));
|
|
34
209
|
}
|
|
@@ -138,7 +313,7 @@ async function startBrowserInBackground() {
|
|
|
138
313
|
}
|
|
139
314
|
} catch (e) {
|
|
140
315
|
}
|
|
141
|
-
await new Promise((
|
|
316
|
+
await new Promise((resolve3) => setTimeout(resolve3, 500));
|
|
142
317
|
}
|
|
143
318
|
throw new Error(`${browserInfo.name} \u542F\u52A8\u8D85\u65F6\uFF0C\u65E0\u6CD5\u8FDE\u63A5\u5230 CDP \u7AEF\u53E3\u3002`);
|
|
144
319
|
}
|
|
@@ -169,13 +344,13 @@ async function connectBrowser() {
|
|
|
169
344
|
const browserURL = addr.replace("/json/version", "");
|
|
170
345
|
try {
|
|
171
346
|
console.log(pc.yellow(`connectBrowser: \u68C0\u6D4B\u5230\u7AEF\u53E3 ${CDP_PORT} \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u8FDE\u63A5 ${browserURL}...`));
|
|
172
|
-
const
|
|
347
|
+
const browser = await promiseWithTimeout(
|
|
173
348
|
puppeteer.connect({ browserURL, defaultViewport: null, targetFilter }),
|
|
174
349
|
1e4,
|
|
175
350
|
`puppeteer.connect to ${browserURL} timed out`
|
|
176
351
|
);
|
|
177
352
|
console.log(pc.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${browserURL}`));
|
|
178
|
-
return
|
|
353
|
+
return browser;
|
|
179
354
|
} catch {
|
|
180
355
|
}
|
|
181
356
|
}
|
|
@@ -193,20 +368,20 @@ async function connectBrowser() {
|
|
|
193
368
|
const browserURL = addr.replace("/json/version", "");
|
|
194
369
|
try {
|
|
195
370
|
console.log(pc.yellow(`connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u8FDE\u63A5 ${browserURL}...`));
|
|
196
|
-
const
|
|
371
|
+
const browser = await promiseWithTimeout(
|
|
197
372
|
puppeteer.connect({ browserURL, defaultViewport: null, targetFilter }),
|
|
198
373
|
1e4,
|
|
199
374
|
`puppeteer.connect to ${browserURL} after launch timed out`
|
|
200
375
|
);
|
|
201
376
|
console.log(pc.green(`connectBrowser: \u6210\u529F\u8FDE\u63A5 ${browserURL}`));
|
|
202
|
-
return
|
|
377
|
+
return browser;
|
|
203
378
|
} catch {
|
|
204
379
|
}
|
|
205
380
|
}
|
|
206
381
|
throw new Error(`\u65E0\u6CD5\u8FDE\u63A5\u5230\u6D4F\u89C8\u5668\uFF08\u7AEF\u53E3 ${CDP_PORT}\uFF09\uFF0C\u8BF7\u68C0\u67E5 Chrome/Edge \u662F\u5426\u5DF2\u5B89\u88C5\u3002`);
|
|
207
382
|
}
|
|
208
|
-
async function getPageTargetId(
|
|
209
|
-
const session = await
|
|
383
|
+
async function getPageTargetId(page) {
|
|
384
|
+
const session = await page.target().createCDPSession();
|
|
210
385
|
try {
|
|
211
386
|
const { targetInfo } = await session.send("Target.getTargetInfo");
|
|
212
387
|
return targetInfo.targetId;
|
|
@@ -215,8 +390,8 @@ async function getPageTargetId(page2) {
|
|
|
215
390
|
});
|
|
216
391
|
}
|
|
217
392
|
}
|
|
218
|
-
function getPageTargets(
|
|
219
|
-
return
|
|
393
|
+
function getPageTargets(browser) {
|
|
394
|
+
return browser.targets().filter((t) => {
|
|
220
395
|
try {
|
|
221
396
|
const type = (typeof t.type === "function" ? t.type() : t.type) || "";
|
|
222
397
|
const url = (typeof t.url === "function" ? t.url() : t.url) || "";
|
|
@@ -229,26 +404,26 @@ function getPageTargets(browser2) {
|
|
|
229
404
|
function getTargetIdFromTarget(target) {
|
|
230
405
|
return typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
|
|
231
406
|
}
|
|
232
|
-
function findPageTargetByTabId(
|
|
233
|
-
const pageTargets = getPageTargets(
|
|
407
|
+
function findPageTargetByTabId(browser, tabid) {
|
|
408
|
+
const pageTargets = getPageTargets(browser);
|
|
234
409
|
return pageTargets.find((t) => {
|
|
235
410
|
const tid = getTargetIdFromTarget(t);
|
|
236
|
-
return tid ===
|
|
411
|
+
return tid === tabid || tid.includes(tabid);
|
|
237
412
|
}) ?? null;
|
|
238
413
|
}
|
|
239
|
-
async function activateTabById(
|
|
240
|
-
const target = findPageTargetByTabId(
|
|
414
|
+
async function activateTabById(browser, tabid) {
|
|
415
|
+
const target = findPageTargetByTabId(browser, tabid);
|
|
241
416
|
if (!target) {
|
|
242
|
-
throw new Error(`Tab with targetId "${
|
|
417
|
+
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
243
418
|
}
|
|
244
419
|
const realTabId = getTargetIdFromTarget(target);
|
|
245
|
-
const pages = await
|
|
420
|
+
const pages = await browser.pages();
|
|
246
421
|
let sessionPage = pages.find((p) => !p.url().startsWith("devtools://"));
|
|
247
422
|
if (!sessionPage && pages.length > 0) {
|
|
248
423
|
sessionPage = pages[0];
|
|
249
424
|
}
|
|
250
425
|
if (!sessionPage) {
|
|
251
|
-
sessionPage = await
|
|
426
|
+
sessionPage = await browser.newPage();
|
|
252
427
|
}
|
|
253
428
|
const session = await sessionPage.createCDPSession();
|
|
254
429
|
try {
|
|
@@ -258,18 +433,18 @@ async function activateTabById(browser2, tabid2) {
|
|
|
258
433
|
});
|
|
259
434
|
}
|
|
260
435
|
}
|
|
261
|
-
async function getTargetPage(
|
|
262
|
-
const pageTargets = getPageTargets(
|
|
436
|
+
async function getTargetPage(browser, tabid) {
|
|
437
|
+
const pageTargets = getPageTargets(browser);
|
|
263
438
|
if (pageTargets.length === 0) {
|
|
264
|
-
const newPage = await
|
|
439
|
+
const newPage = await browser.newPage();
|
|
265
440
|
await injectWebMCPPolyfillAndTools(newPage);
|
|
266
441
|
return newPage;
|
|
267
442
|
}
|
|
268
443
|
let targetPage = null;
|
|
269
|
-
if (
|
|
270
|
-
const target = findPageTargetByTabId(
|
|
444
|
+
if (tabid !== void 0) {
|
|
445
|
+
const target = findPageTargetByTabId(browser, tabid);
|
|
271
446
|
if (!target) {
|
|
272
|
-
throw new Error(`Tab with targetId "${
|
|
447
|
+
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
273
448
|
}
|
|
274
449
|
targetPage = await target.page();
|
|
275
450
|
} else {
|
|
@@ -312,11 +487,11 @@ async function getTargetPage(browser2, tabid2) {
|
|
|
312
487
|
await injectWebMCPPolyfillAndTools(targetPage);
|
|
313
488
|
return targetPage;
|
|
314
489
|
}
|
|
315
|
-
async function injectIntoPage(
|
|
316
|
-
await injectWebMCPPolyfillAndTools(
|
|
490
|
+
async function injectIntoPage(page) {
|
|
491
|
+
await injectWebMCPPolyfillAndTools(page, true);
|
|
317
492
|
}
|
|
318
|
-
async function injectWebMCPPolyfillAndTools(
|
|
319
|
-
const polyfillReady = !force && await
|
|
493
|
+
async function injectWebMCPPolyfillAndTools(page, force = false) {
|
|
494
|
+
const polyfillReady = !force && await page.evaluate(() => {
|
|
320
495
|
return !!window.__webmcpcli_init;
|
|
321
496
|
}).catch(() => false);
|
|
322
497
|
if (!polyfillReady) {
|
|
@@ -327,31 +502,63 @@ async function injectWebMCPPolyfillAndTools(page2, force = false) {
|
|
|
327
502
|
}
|
|
328
503
|
const scriptContent = fs.readFileSync(injectScriptPath, "utf-8");
|
|
329
504
|
try {
|
|
330
|
-
await
|
|
505
|
+
await page.evaluate(scriptContent);
|
|
331
506
|
} catch (err) {
|
|
332
507
|
const msg = err instanceof Error ? err.message : String(err);
|
|
333
508
|
throw new Error("\u81EA\u52A8\u6CE8\u5165\u811A\u672C\u6267\u884C\u5931\u8D25: " + msg);
|
|
334
509
|
}
|
|
335
|
-
await
|
|
510
|
+
await page.waitForFunction(() => {
|
|
511
|
+
const mcp = navigator.modelContext;
|
|
512
|
+
return mcp && typeof mcp.registerTool === "function";
|
|
513
|
+
}, { timeout: 1e4 }).catch(() => {
|
|
514
|
+
console.warn("\u7B49\u5F85 WebMCP registerTool \u63A5\u53E3\u5C31\u7EEA\u8D85\u65F6\uFF0C\u6B63\u5728\u7EE7\u7EED\u6CE8\u5165...");
|
|
515
|
+
});
|
|
336
516
|
}
|
|
337
|
-
await injectDomainTools(
|
|
517
|
+
await injectDomainTools(page);
|
|
338
518
|
}
|
|
339
|
-
|
|
519
|
+
function getToolsBundleName(hostname) {
|
|
520
|
+
const normalized = hostname.split(":")[0].toLowerCase();
|
|
521
|
+
const toolsDir = path.resolve(__dirname, "webmcp-tools");
|
|
522
|
+
if (!fs.existsSync(toolsDir)) {
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
let files = [];
|
|
526
|
+
try {
|
|
527
|
+
files = fs.readdirSync(toolsDir);
|
|
528
|
+
} catch {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
const supportedDomains = files.filter((f) => f.endsWith(".js")).map((f) => f.slice(0, -3));
|
|
532
|
+
if (supportedDomains.includes(normalized)) {
|
|
533
|
+
return `webmcp-tools/${normalized}.js`;
|
|
534
|
+
}
|
|
535
|
+
for (const domain of supportedDomains) {
|
|
536
|
+
if (normalized.endsWith("." + domain)) {
|
|
537
|
+
return `webmcp-tools/${domain}.js`;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
async function injectDomainTools(page) {
|
|
340
543
|
let hostname;
|
|
341
544
|
try {
|
|
342
|
-
const url = new URL(
|
|
545
|
+
const url = new URL(page.url());
|
|
343
546
|
hostname = url.hostname;
|
|
344
547
|
} catch {
|
|
345
548
|
return;
|
|
346
549
|
}
|
|
347
|
-
const
|
|
550
|
+
const bundleName = getToolsBundleName(hostname);
|
|
551
|
+
if (!bundleName) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const toolBundlePath = path.resolve(__dirname, bundleName);
|
|
348
555
|
if (!fs.existsSync(toolBundlePath)) {
|
|
349
556
|
return;
|
|
350
557
|
}
|
|
351
558
|
console.log(pc.cyan(`\u68C0\u6D4B\u5230\u57DF\u540D ${hostname} \u6709\u9884\u7F6E\u5DE5\u5177\uFF0C\u6B63\u5728\u6CE8\u5165...`));
|
|
352
559
|
const toolScript = fs.readFileSync(toolBundlePath, "utf-8");
|
|
353
560
|
try {
|
|
354
|
-
await
|
|
561
|
+
await page.evaluate(toolScript);
|
|
355
562
|
console.log(pc.green(`\u5DF2\u4E3A ${hostname} \u6CE8\u5165\u9884\u7F6E\u5DE5\u5177`));
|
|
356
563
|
} catch (err) {
|
|
357
564
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -360,67 +567,30 @@ async function injectDomainTools(page2) {
|
|
|
360
567
|
}
|
|
361
568
|
|
|
362
569
|
// src/commands/state.ts
|
|
363
|
-
async function stateCommand({ tabid
|
|
364
|
-
const
|
|
570
|
+
async function stateCommand({ tabid }) {
|
|
571
|
+
const browser = await connectBrowser();
|
|
365
572
|
try {
|
|
366
|
-
const
|
|
367
|
-
await injectIntoPage(
|
|
368
|
-
const state = await
|
|
573
|
+
const page = await getTargetPage(browser, tabid);
|
|
574
|
+
await injectIntoPage(page);
|
|
575
|
+
const state = await page.evaluate(async () => {
|
|
369
576
|
const url = document.URL;
|
|
370
577
|
const title = document.title;
|
|
371
578
|
const mcp = navigator.modelContextTesting || navigator.modelContext;
|
|
372
579
|
let webmcpTools = [];
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const toolsResult = await mcp.listTools();
|
|
377
|
-
webmcpTools = toolsResult?.tools || toolsResult || [];
|
|
378
|
-
}
|
|
379
|
-
if (typeof mcp.executeTool === "function") {
|
|
380
|
-
try {
|
|
381
|
-
const argsString = JSON.stringify({ action: "browserState" });
|
|
382
|
-
let stateRes = await mcp.executeTool("page-agent-tool", argsString);
|
|
383
|
-
if (typeof stateRes === "string") {
|
|
384
|
-
try {
|
|
385
|
-
stateRes = JSON.parse(stateRes);
|
|
386
|
-
} catch (e) {
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
if (stateRes && stateRes.content && stateRes.content.length > 0) {
|
|
390
|
-
const textContent = stateRes.content.map((c) => c.text).join("\\n");
|
|
391
|
-
const prefix = "\u6D4F\u89C8\u5668\u72B6\u6001: ";
|
|
392
|
-
if (textContent.startsWith(prefix)) {
|
|
393
|
-
try {
|
|
394
|
-
const jsonStr = textContent.substring(prefix.length);
|
|
395
|
-
const parsedState = JSON.parse(jsonStr);
|
|
396
|
-
contentData = parsedState.content;
|
|
397
|
-
} catch (e) {
|
|
398
|
-
contentData = textContent;
|
|
399
|
-
}
|
|
400
|
-
} else {
|
|
401
|
-
contentData = textContent;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
} catch (e) {
|
|
405
|
-
console.error("Snapshot error:", e.message);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
580
|
+
if (mcp && typeof mcp.listTools === "function") {
|
|
581
|
+
const toolsResult = await mcp.listTools();
|
|
582
|
+
webmcpTools = toolsResult?.tools || toolsResult || [];
|
|
408
583
|
}
|
|
409
|
-
return {
|
|
410
|
-
content: contentData,
|
|
411
|
-
url,
|
|
412
|
-
title,
|
|
413
|
-
webmcpTools
|
|
414
|
-
};
|
|
584
|
+
return { url, title, webmcpTools };
|
|
415
585
|
});
|
|
416
|
-
const pages = await
|
|
586
|
+
const pages = await browser.pages();
|
|
417
587
|
const tabs2 = await Promise.all(
|
|
418
588
|
pages.map(async (p) => {
|
|
419
589
|
const pUrl = p.url();
|
|
420
590
|
if (pUrl.startsWith("devtools://")) return null;
|
|
421
591
|
const pTitle = await Promise.race([
|
|
422
592
|
p.title().catch(() => "Unknown"),
|
|
423
|
-
new Promise((
|
|
593
|
+
new Promise((resolve3) => setTimeout(() => resolve3("Unknown"), 500))
|
|
424
594
|
]);
|
|
425
595
|
return {
|
|
426
596
|
// 使用真实的 Chrome target ID,而非数组下标
|
|
@@ -435,31 +605,21 @@ async function stateCommand({ tabid: tabid2 }) {
|
|
|
435
605
|
tabs: tabs2.filter(Boolean)
|
|
436
606
|
};
|
|
437
607
|
} finally {
|
|
438
|
-
await
|
|
608
|
+
await browser.disconnect();
|
|
439
609
|
}
|
|
440
610
|
}
|
|
441
611
|
|
|
442
612
|
// src/commands/run.ts
|
|
443
|
-
import JSON5 from "json5";
|
|
444
613
|
async function runCommand({
|
|
445
614
|
toolName,
|
|
446
615
|
argsJson,
|
|
447
|
-
tabid
|
|
616
|
+
tabid
|
|
448
617
|
}) {
|
|
449
|
-
const
|
|
618
|
+
const browser = await connectBrowser();
|
|
450
619
|
try {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
cleanedArgs = cleanedArgs.slice(1, -1).trim();
|
|
455
|
-
}
|
|
456
|
-
try {
|
|
457
|
-
const obj = JSON5.parse(cleanedArgs);
|
|
458
|
-
cleanedArgs = JSON.stringify(obj);
|
|
459
|
-
} catch (e) {
|
|
460
|
-
throw new Error(`\u53C2\u6570\u4E0D\u662F\u6709\u6548\u7684 JSON \u6216 JS \u5BF9\u8C61: ${e.message}`);
|
|
461
|
-
}
|
|
462
|
-
const result2 = await page2.evaluate(async (name, inputString) => {
|
|
620
|
+
const page = await getTargetPage(browser, tabid);
|
|
621
|
+
const cleanedArgs = argsJson.trim();
|
|
622
|
+
const result = await page.evaluate(async (name, inputString) => {
|
|
463
623
|
const mcp = navigator.modelContextTesting || navigator.modelContext;
|
|
464
624
|
if (!mcp || typeof mcp.executeTool !== "function") {
|
|
465
625
|
throw new Error("\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6CE8\u5165 WebMCP \u73AF\u5883 (navigator.modelContextTesting.executeTool \u672A\u627E\u5230)");
|
|
@@ -468,39 +628,18 @@ async function runCommand({
|
|
|
468
628
|
if (typeof res === "string") {
|
|
469
629
|
try {
|
|
470
630
|
res = JSON.parse(res);
|
|
471
|
-
} catch
|
|
631
|
+
} catch {
|
|
472
632
|
}
|
|
473
633
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
} finally {
|
|
477
|
-
await browser2.disconnect();
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// src/commands/evaluate.ts
|
|
482
|
-
async function evaluateCommand({
|
|
483
|
-
jsScript,
|
|
484
|
-
tabid
|
|
485
|
-
}) {
|
|
486
|
-
const browser = await connectBrowser();
|
|
487
|
-
try {
|
|
488
|
-
const page = await getTargetPage(browser, tabid);
|
|
489
|
-
const result = await page.evaluate(async (script) => {
|
|
490
|
-
try {
|
|
491
|
-
const result = eval(script);
|
|
492
|
-
if (result instanceof Promise) {
|
|
493
|
-
return await result;
|
|
494
|
-
}
|
|
495
|
-
return result;
|
|
496
|
-
} catch (error) {
|
|
497
|
-
return {
|
|
498
|
-
error: true,
|
|
499
|
-
message: error.message,
|
|
500
|
-
stack: error.stack
|
|
501
|
-
};
|
|
634
|
+
if (res === void 0 || res === null) {
|
|
635
|
+
throw new Error("\u5DE5\u5177 execute \u672A\u8FD4\u56DE\u7ED3\u679C\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u5177\u5B9E\u73B0\u662F\u5426\u7F3A\u5C11 return");
|
|
502
636
|
}
|
|
503
|
-
|
|
637
|
+
if (typeof res === "object" && res.success === false) {
|
|
638
|
+
const failed = res;
|
|
639
|
+
throw new Error(failed.error || failed.message || "\u5DE5\u5177\u6267\u884C\u5931\u8D25");
|
|
640
|
+
}
|
|
641
|
+
return res;
|
|
642
|
+
}, toolName, cleanedArgs);
|
|
504
643
|
return result;
|
|
505
644
|
} finally {
|
|
506
645
|
await browser.disconnect();
|
|
@@ -525,114 +664,114 @@ function normalizeUrl(url) {
|
|
|
525
664
|
return url;
|
|
526
665
|
}
|
|
527
666
|
async function tabsOpenCommand(url) {
|
|
528
|
-
const
|
|
667
|
+
const browser = await connectBrowser();
|
|
529
668
|
try {
|
|
530
|
-
const
|
|
669
|
+
const page = await browser.newPage();
|
|
531
670
|
url = normalizeUrl(url);
|
|
532
|
-
await
|
|
533
|
-
await injectIntoPage(
|
|
534
|
-
await new Promise((
|
|
671
|
+
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
672
|
+
await injectIntoPage(page);
|
|
673
|
+
await new Promise((resolve3) => setTimeout(resolve3, 500));
|
|
535
674
|
return {
|
|
536
675
|
success: true,
|
|
537
|
-
tabid: await getPageTargetId(
|
|
538
|
-
url:
|
|
539
|
-
title: await
|
|
676
|
+
tabid: await getPageTargetId(page),
|
|
677
|
+
url: page.url(),
|
|
678
|
+
title: await page.title()
|
|
540
679
|
};
|
|
541
680
|
} finally {
|
|
542
|
-
await
|
|
681
|
+
await browser.disconnect();
|
|
543
682
|
}
|
|
544
683
|
}
|
|
545
|
-
async function tabsCloseCommand(
|
|
546
|
-
const
|
|
684
|
+
async function tabsCloseCommand(tabid) {
|
|
685
|
+
const browser = await connectBrowser();
|
|
547
686
|
try {
|
|
548
|
-
const target = findPageTargetByTabId(
|
|
687
|
+
const target = findPageTargetByTabId(browser, tabid);
|
|
549
688
|
if (!target) {
|
|
550
|
-
throw new Error(`Tab with targetId "${
|
|
689
|
+
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
551
690
|
}
|
|
552
|
-
const
|
|
553
|
-
if (!
|
|
554
|
-
throw new Error(`Tab with targetId "${
|
|
691
|
+
const page = await target.page();
|
|
692
|
+
if (!page) {
|
|
693
|
+
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
555
694
|
}
|
|
556
|
-
const closedTabid = await getPageTargetId(
|
|
557
|
-
await
|
|
695
|
+
const closedTabid = await getPageTargetId(page).catch(() => tabid);
|
|
696
|
+
await page.close();
|
|
558
697
|
return {
|
|
559
698
|
success: true,
|
|
560
699
|
tabid: closedTabid
|
|
561
700
|
};
|
|
562
701
|
} finally {
|
|
563
|
-
await
|
|
702
|
+
await browser.disconnect();
|
|
564
703
|
}
|
|
565
704
|
}
|
|
566
|
-
async function tabsSwitchCommand(
|
|
567
|
-
const
|
|
705
|
+
async function tabsSwitchCommand(tabid) {
|
|
706
|
+
const browser = await connectBrowser();
|
|
568
707
|
try {
|
|
569
|
-
await activateTabById(
|
|
570
|
-
const
|
|
708
|
+
await activateTabById(browser, tabid);
|
|
709
|
+
const page = await getTargetPage(browser, tabid);
|
|
571
710
|
return {
|
|
572
711
|
success: true,
|
|
573
|
-
tabid: await getPageTargetId(
|
|
574
|
-
url:
|
|
575
|
-
title: await
|
|
712
|
+
tabid: await getPageTargetId(page),
|
|
713
|
+
url: page.url(),
|
|
714
|
+
title: await page.title()
|
|
576
715
|
};
|
|
577
716
|
} finally {
|
|
578
|
-
await
|
|
717
|
+
await browser.disconnect();
|
|
579
718
|
}
|
|
580
719
|
}
|
|
581
|
-
async function tabsBackCommand(
|
|
582
|
-
const
|
|
720
|
+
async function tabsBackCommand(tabid) {
|
|
721
|
+
const browser = await connectBrowser();
|
|
583
722
|
try {
|
|
584
|
-
const
|
|
585
|
-
const response = await
|
|
723
|
+
const page = await getTargetPage(browser, tabid);
|
|
724
|
+
const response = await page.goBack();
|
|
586
725
|
if (!response) {
|
|
587
726
|
return {
|
|
588
727
|
success: false,
|
|
589
728
|
error: "\u65E0\u6CD5\u540E\u9000\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u8D77\u70B9",
|
|
590
|
-
tabid: await getPageTargetId(
|
|
591
|
-
url:
|
|
592
|
-
title: await
|
|
729
|
+
tabid: await getPageTargetId(page).catch(() => tabid),
|
|
730
|
+
url: page.url(),
|
|
731
|
+
title: await page.title()
|
|
593
732
|
};
|
|
594
733
|
}
|
|
595
|
-
await injectIntoPage(
|
|
734
|
+
await injectIntoPage(page);
|
|
596
735
|
return {
|
|
597
736
|
success: true,
|
|
598
|
-
tabid: await getPageTargetId(
|
|
599
|
-
url:
|
|
600
|
-
title: await
|
|
737
|
+
tabid: await getPageTargetId(page),
|
|
738
|
+
url: page.url(),
|
|
739
|
+
title: await page.title()
|
|
601
740
|
};
|
|
602
741
|
} finally {
|
|
603
|
-
await
|
|
742
|
+
await browser.disconnect();
|
|
604
743
|
}
|
|
605
744
|
}
|
|
606
|
-
async function tabsForwardCommand(
|
|
607
|
-
const
|
|
745
|
+
async function tabsForwardCommand(tabid) {
|
|
746
|
+
const browser = await connectBrowser();
|
|
608
747
|
try {
|
|
609
|
-
const
|
|
610
|
-
const response = await
|
|
748
|
+
const page = await getTargetPage(browser, tabid);
|
|
749
|
+
const response = await page.goForward();
|
|
611
750
|
if (!response) {
|
|
612
751
|
return {
|
|
613
752
|
success: false,
|
|
614
753
|
error: "\u65E0\u6CD5\u524D\u8FDB\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u672B\u5C3E",
|
|
615
|
-
tabid: await getPageTargetId(
|
|
616
|
-
url:
|
|
617
|
-
title: await
|
|
754
|
+
tabid: await getPageTargetId(page).catch(() => tabid),
|
|
755
|
+
url: page.url(),
|
|
756
|
+
title: await page.title()
|
|
618
757
|
};
|
|
619
758
|
}
|
|
620
|
-
await injectIntoPage(
|
|
759
|
+
await injectIntoPage(page);
|
|
621
760
|
return {
|
|
622
761
|
success: true,
|
|
623
|
-
tabid: await getPageTargetId(
|
|
624
|
-
url:
|
|
625
|
-
title: await
|
|
762
|
+
tabid: await getPageTargetId(page),
|
|
763
|
+
url: page.url(),
|
|
764
|
+
title: await page.title()
|
|
626
765
|
};
|
|
627
766
|
} finally {
|
|
628
|
-
await
|
|
767
|
+
await browser.disconnect();
|
|
629
768
|
}
|
|
630
769
|
}
|
|
631
770
|
|
|
632
771
|
// package.json
|
|
633
772
|
var package_default = {
|
|
634
773
|
name: "@opentiny/webmcp-cli",
|
|
635
|
-
version: "0.0.
|
|
774
|
+
version: "0.0.2",
|
|
636
775
|
type: "module",
|
|
637
776
|
description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
|
|
638
777
|
main: "dist/index.js",
|
|
@@ -686,47 +825,118 @@ var package_default = {
|
|
|
686
825
|
|
|
687
826
|
// src/bin.ts
|
|
688
827
|
var program = new Command();
|
|
689
|
-
function cleanJsonString(str) {
|
|
690
|
-
let cleaned = str.trim();
|
|
691
|
-
if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
|
|
692
|
-
cleaned = cleaned.slice(1, -1).trim();
|
|
693
|
-
}
|
|
694
|
-
return cleaned;
|
|
695
|
-
}
|
|
696
828
|
function parseTabId(id) {
|
|
697
829
|
if (!id) return void 0;
|
|
698
830
|
return id;
|
|
699
831
|
}
|
|
700
|
-
function
|
|
832
|
+
function cleanOldLogs(baseDir, logDir) {
|
|
833
|
+
try {
|
|
834
|
+
const oldLogFile = path2.join(baseDir, "webmcp-cli.log");
|
|
835
|
+
if (fs2.existsSync(oldLogFile)) {
|
|
836
|
+
try {
|
|
837
|
+
fs2.unlinkSync(oldLogFile);
|
|
838
|
+
} catch {
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (fs2.existsSync(baseDir)) {
|
|
842
|
+
const baseFiles = fs2.readdirSync(baseDir);
|
|
843
|
+
for (const file of baseFiles) {
|
|
844
|
+
if (/^webmcp-cli-\d{4}-\d{2}-\d{2}\.log$/.test(file)) {
|
|
845
|
+
try {
|
|
846
|
+
fs2.unlinkSync(path2.join(baseDir, file));
|
|
847
|
+
} catch {
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (!fs2.existsSync(logDir)) return;
|
|
853
|
+
const files = fs2.readdirSync(logDir);
|
|
854
|
+
const logFilePattern = /^webmcp-cli-(\d{4}-\d{2}-\d{2})\.log$/;
|
|
855
|
+
const now = Date.now();
|
|
856
|
+
const maxAgeMs = 7 * 24 * 60 * 60 * 1e3;
|
|
857
|
+
for (const file of files) {
|
|
858
|
+
const match = file.match(logFilePattern);
|
|
859
|
+
if (match) {
|
|
860
|
+
const fileDateStr = match[1];
|
|
861
|
+
const fileDate = new Date(fileDateStr).getTime();
|
|
862
|
+
if (isNaN(fileDate)) continue;
|
|
863
|
+
if (now - fileDate > maxAgeMs) {
|
|
864
|
+
try {
|
|
865
|
+
fs2.unlinkSync(path2.join(logDir, file));
|
|
866
|
+
} catch {
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
} catch (e) {
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
function writeLog(commandName, args, result, error) {
|
|
875
|
+
try {
|
|
876
|
+
const baseDir = process.env.WEBMCP_WORKSPACE || path2.join(os2.homedir(), ".webmcp_chrome_profile");
|
|
877
|
+
const logDir = path2.join(baseDir, "logs");
|
|
878
|
+
if (!fs2.existsSync(logDir)) {
|
|
879
|
+
fs2.mkdirSync(logDir, { recursive: true });
|
|
880
|
+
}
|
|
881
|
+
const todayStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
882
|
+
const logFile = path2.join(logDir, `webmcp-cli-${todayStr}.log`);
|
|
883
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
884
|
+
let logText = `========================================
|
|
885
|
+
`;
|
|
886
|
+
logText += `[${timestamp}] COMMAND: ${commandName}
|
|
887
|
+
`;
|
|
888
|
+
logText += `ARGS:
|
|
889
|
+
${JSON.stringify(args, null, 2)}
|
|
890
|
+
|
|
891
|
+
`;
|
|
892
|
+
if (error) {
|
|
893
|
+
logText += `ERROR:
|
|
894
|
+
${error instanceof Error ? error.stack : String(error)}
|
|
895
|
+
`;
|
|
896
|
+
} else if (result) {
|
|
897
|
+
logText += `RESULT:
|
|
898
|
+
`;
|
|
899
|
+
try {
|
|
900
|
+
if (result && typeof result === "object") {
|
|
901
|
+
logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
|
|
902
|
+
} else {
|
|
903
|
+
logText += String(result).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
|
|
904
|
+
}
|
|
905
|
+
} catch {
|
|
906
|
+
logText += JSON.stringify(result, null, 2).split("\n").map((line) => ` ${line}`).join("\n") + "\n";
|
|
907
|
+
}
|
|
908
|
+
} else {
|
|
909
|
+
logText += `RESULT: null
|
|
910
|
+
`;
|
|
911
|
+
}
|
|
912
|
+
logText += `========================================
|
|
913
|
+
|
|
914
|
+
`;
|
|
915
|
+
fs2.appendFileSync(logFile, logText, "utf-8");
|
|
916
|
+
cleanOldLogs(baseDir, logDir);
|
|
917
|
+
} catch (e) {
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
function handleCommandError(error, commandName, args) {
|
|
701
921
|
const msg = error instanceof Error ? error.message : String(error);
|
|
922
|
+
writeLog(commandName, args, null, error);
|
|
702
923
|
console.error(pc2.red(`Error executing ${commandName} command: ${msg}`));
|
|
703
924
|
process.exit(1);
|
|
704
925
|
}
|
|
705
|
-
function expandFileRefs(argsJson) {
|
|
706
|
-
return argsJson.replace(/"@(base64)?file:([^"]+)"/g, (_match, base64Flag, filePath) => {
|
|
707
|
-
const absPath = resolve(process.cwd(), filePath);
|
|
708
|
-
let content;
|
|
709
|
-
try {
|
|
710
|
-
content = readFileSync(absPath, "utf-8");
|
|
711
|
-
} catch (e) {
|
|
712
|
-
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\u5F15\u7528 "${filePath}": ${e.message}`);
|
|
713
|
-
}
|
|
714
|
-
const value = base64Flag ? Buffer.from(content, "utf-8").toString("base64") : content;
|
|
715
|
-
return JSON.stringify(value);
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
926
|
program.name("webmcp-cli").description("WebMCP CLI for interacting with browser via CDP").version(package_default.version).option("-w, --workspace <path>", "\u6307\u5B9A\u81EA\u5B9A\u4E49\u7684\u6D4F\u89C8\u5668\u5DE5\u4F5C\u7A7A\u95F4\uFF08\u7528\u6237\u914D\u7F6E\u76EE\u5F55\uFF09\u8DEF\u5F84").hook("preAction", (thisCommand) => {
|
|
719
927
|
const opts = thisCommand.opts();
|
|
720
928
|
if (opts.workspace) {
|
|
721
929
|
process.env.WEBMCP_WORKSPACE = opts.workspace;
|
|
722
930
|
}
|
|
723
931
|
});
|
|
724
|
-
program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D\u9875\u7B7E\u6216\u6307\u5B9A\u9875\u7B7E\u7684\
|
|
932
|
+
program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D\u9875\u7B7E\u6216\u6307\u5B9A\u9875\u7B7E\u7684\u5BFC\u822A\u5143\u6570\u636E\uFF08url\u3001title\u3001webmcpTools\u3001\u6240\u6709\u9875\u7B7E\u5217\u8868\uFF09").option("-t, --tabid <id>", "\u6307\u5B9A\u9875\u7B7E\u7684 ID").action(async (options) => {
|
|
933
|
+
const args = { tabid: parseTabId(options.tabid) };
|
|
725
934
|
try {
|
|
726
|
-
const
|
|
727
|
-
|
|
935
|
+
const result = await stateCommand(args);
|
|
936
|
+
writeLog("state", args, result);
|
|
937
|
+
console.log(JSON.stringify(result, null, 2));
|
|
728
938
|
} catch (error) {
|
|
729
|
-
handleCommandError(error, "state");
|
|
939
|
+
handleCommandError(error, "state", args);
|
|
730
940
|
}
|
|
731
941
|
});
|
|
732
942
|
program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u9875\u7B7E\u8C03\u7528\u6307\u5B9A\u7684 WebMCP \u5DE5\u5177\u6267\u884C\u64CD\u4F5C").option("-t, --tabid <id>", "\u6307\u5B9A\u9875\u7B7E\u7684 ID").option(
|
|
@@ -737,111 +947,92 @@ program.command("run <toolName> [args...]").description("\u5411\u6307\u5B9A\u987
|
|
|
737
947
|
@base64file:<path> \u8BFB\u53D6\u6587\u4EF6\u5E76 Base64 \u7F16\u7801
|
|
738
948
|
\u793A\u4F8B\uFF1Awebmcp-cli run mytool '{"content":"@base64file:./doc.md"}'`
|
|
739
949
|
).action(async (toolName, args, options) => {
|
|
950
|
+
let finalArgsJson = "";
|
|
951
|
+
const rawArgs = { toolName, args, file: options.file, tabid: options.tabid };
|
|
740
952
|
try {
|
|
741
|
-
let
|
|
953
|
+
let fileContent;
|
|
954
|
+
let fileBaseDir = process.cwd();
|
|
742
955
|
if (options.file) {
|
|
743
|
-
const filePath =
|
|
956
|
+
const filePath = resolve2(process.cwd(), options.file);
|
|
957
|
+
fileBaseDir = getFileBaseDir(options.file);
|
|
744
958
|
try {
|
|
745
|
-
|
|
959
|
+
fileContent = readFileSync2(filePath, "utf-8");
|
|
746
960
|
} catch (e) {
|
|
747
961
|
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6 "${filePath}": ${e.message}`);
|
|
748
962
|
}
|
|
749
|
-
} else if (args && args.length > 0) {
|
|
750
|
-
const firstArg = args[0].trim();
|
|
751
|
-
const cleanedFirstArg = cleanJsonString(firstArg);
|
|
752
|
-
if (cleanedFirstArg.startsWith("{") && cleanedFirstArg.endsWith("}")) {
|
|
753
|
-
finalArgsJson = args.map(cleanJsonString).join(" ");
|
|
754
|
-
} else {
|
|
755
|
-
const obj = {};
|
|
756
|
-
for (const pair of args) {
|
|
757
|
-
const index = pair.indexOf("=");
|
|
758
|
-
if (index === -1) {
|
|
759
|
-
throw new Error(`\u65E0\u6548\u7684\u53C2\u6570\u683C\u5F0F "${pair}"\u3002\u5FC5\u987B\u662F JSON \u5B57\u7B26\u4E32\u6216 key=value \u683C\u5F0F\u3002`);
|
|
760
|
-
}
|
|
761
|
-
const key = pair.substring(0, index).trim();
|
|
762
|
-
let val = pair.substring(index + 1).trim();
|
|
763
|
-
val = cleanJsonString(val);
|
|
764
|
-
try {
|
|
765
|
-
obj[key] = JSON.parse(val);
|
|
766
|
-
} catch {
|
|
767
|
-
obj[key] = val;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
finalArgsJson = JSON.stringify(obj);
|
|
771
|
-
}
|
|
772
963
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
finalArgsJson = expandFileRefs(finalArgsJson);
|
|
777
|
-
const result2 = await runCommand({
|
|
964
|
+
finalArgsJson = prepareRunArgsJson(args ?? [], fileContent, fileBaseDir);
|
|
965
|
+
const result = await runCommand({
|
|
778
966
|
toolName,
|
|
779
967
|
argsJson: finalArgsJson,
|
|
780
968
|
tabid: parseTabId(options.tabid)
|
|
781
969
|
});
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
handleCommandError(error, "run");
|
|
785
|
-
}
|
|
786
|
-
});
|
|
787
|
-
program.command("evaluate <jsScript>").description("\u5411\u5F53\u524D\u7F51\u9875\u6216\u6307\u5B9A\u9875\u7B7E\u6CE8\u5165\u5E76\u6267\u884C JavaScript \u4EE3\u7801").option("-t, --tabid <id>", "\u6307\u5B9A\u9875\u7B7E\u7684 ID").action(async (jsScript2, options) => {
|
|
788
|
-
try {
|
|
789
|
-
const result2 = await evaluateCommand({
|
|
790
|
-
jsScript: jsScript2,
|
|
791
|
-
tabid: parseTabId(options.tabid)
|
|
792
|
-
});
|
|
793
|
-
console.log(JSON.stringify(result2, null, 2));
|
|
970
|
+
writeLog("run", { ...rawArgs, finalArgsJson }, result);
|
|
971
|
+
console.log(JSON.stringify(result, null, 2));
|
|
794
972
|
} catch (error) {
|
|
795
|
-
handleCommandError(error, "
|
|
973
|
+
handleCommandError(error, "run", { ...rawArgs, finalArgsJson });
|
|
796
974
|
}
|
|
797
975
|
});
|
|
798
976
|
program.command("clipboard <content>").description("\u5C06\u5185\u5BB9\u8BBE\u7F6E\u5230\u7CFB\u7EDF\u526A\u8D34\u677F").action(async (content) => {
|
|
977
|
+
const args = { content };
|
|
799
978
|
try {
|
|
800
979
|
await setClipboard(content);
|
|
801
|
-
|
|
980
|
+
const result = { success: true, message: "\u5185\u5BB9\u5DF2\u8BBE\u7F6E\u5230\u7CFB\u7EDF\u526A\u8D34\u677F" };
|
|
981
|
+
writeLog("clipboard", args, result);
|
|
982
|
+
console.log(JSON.stringify(result, null, 2));
|
|
802
983
|
} catch (error) {
|
|
803
|
-
handleCommandError(error, "clipboard");
|
|
984
|
+
handleCommandError(error, "clipboard", args);
|
|
804
985
|
}
|
|
805
986
|
});
|
|
806
987
|
var tabs = program.command("tabs").description("\u7BA1\u7406\u6D4F\u89C8\u5668\u6807\u7B7E\u9875");
|
|
807
988
|
tabs.command("open <url>").description("\u6253\u5F00\u65B0\u7F51\u9875").action(async (url) => {
|
|
989
|
+
const args = { url };
|
|
808
990
|
try {
|
|
809
|
-
const
|
|
810
|
-
|
|
991
|
+
const result = await tabsOpenCommand(url);
|
|
992
|
+
writeLog("tabs open", args, result);
|
|
993
|
+
console.log(JSON.stringify(result, null, 2));
|
|
811
994
|
} catch (error) {
|
|
812
|
-
handleCommandError(error, "tabs open");
|
|
995
|
+
handleCommandError(error, "tabs open", args);
|
|
813
996
|
}
|
|
814
997
|
});
|
|
815
|
-
tabs.command("close <tabid>").description("\u5173\u95ED\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (
|
|
998
|
+
tabs.command("close <tabid>").description("\u5173\u95ED\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid) => {
|
|
999
|
+
const args = { tabid };
|
|
816
1000
|
try {
|
|
817
|
-
const
|
|
818
|
-
|
|
1001
|
+
const result = await tabsCloseCommand(tabid);
|
|
1002
|
+
writeLog("tabs close", args, result);
|
|
1003
|
+
console.log(JSON.stringify(result, null, 2));
|
|
819
1004
|
} catch (error) {
|
|
820
|
-
handleCommandError(error, "tabs close");
|
|
1005
|
+
handleCommandError(error, "tabs close", args);
|
|
821
1006
|
}
|
|
822
1007
|
});
|
|
823
|
-
tabs.command("switch <tabid>").description("\u6FC0\u6D3B\u5E76\u5207\u6362\u5230\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (
|
|
1008
|
+
tabs.command("switch <tabid>").description("\u6FC0\u6D3B\u5E76\u5207\u6362\u5230\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid) => {
|
|
1009
|
+
const args = { tabid };
|
|
824
1010
|
try {
|
|
825
|
-
const
|
|
826
|
-
|
|
1011
|
+
const result = await tabsSwitchCommand(tabid);
|
|
1012
|
+
writeLog("tabs switch", args, result);
|
|
1013
|
+
console.log(JSON.stringify(result, null, 2));
|
|
827
1014
|
} catch (error) {
|
|
828
|
-
handleCommandError(error, "tabs switch");
|
|
1015
|
+
handleCommandError(error, "tabs switch", args);
|
|
829
1016
|
}
|
|
830
1017
|
});
|
|
831
|
-
tabs.command("back [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u540E\u9000\u4E00\u6B65").action(async (
|
|
1018
|
+
tabs.command("back [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u540E\u9000\u4E00\u6B65").action(async (tabid) => {
|
|
1019
|
+
const args = { tabid: parseTabId(tabid) };
|
|
832
1020
|
try {
|
|
833
|
-
const
|
|
834
|
-
|
|
1021
|
+
const result = await tabsBackCommand(args.tabid);
|
|
1022
|
+
writeLog("tabs back", args, result);
|
|
1023
|
+
console.log(JSON.stringify(result, null, 2));
|
|
835
1024
|
} catch (error) {
|
|
836
|
-
handleCommandError(error, "tabs back");
|
|
1025
|
+
handleCommandError(error, "tabs back", args);
|
|
837
1026
|
}
|
|
838
1027
|
});
|
|
839
|
-
tabs.command("forward [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u524D\u8FDB\u4E00\u6B65").action(async (
|
|
1028
|
+
tabs.command("forward [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u524D\u8FDB\u4E00\u6B65").action(async (tabid) => {
|
|
1029
|
+
const args = { tabid: parseTabId(tabid) };
|
|
840
1030
|
try {
|
|
841
|
-
const
|
|
842
|
-
|
|
1031
|
+
const result = await tabsForwardCommand(args.tabid);
|
|
1032
|
+
writeLog("tabs forward", args, result);
|
|
1033
|
+
console.log(JSON.stringify(result, null, 2));
|
|
843
1034
|
} catch (error) {
|
|
844
|
-
handleCommandError(error, "tabs forward");
|
|
1035
|
+
handleCommandError(error, "tabs forward", args);
|
|
845
1036
|
}
|
|
846
1037
|
});
|
|
847
1038
|
program.parse(process.argv);
|