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