@opentiny/webmcp-cli 0.0.1-alpha.0 → 0.0.1-alpha.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 +10 -6
- package/dist/bin.cjs +365 -96
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +371 -96
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +146 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +153 -35
- package/dist/index.js.map +1 -1
- package/dist/webmcp-tools/excalidraw.com.js +8 -0
- package/package.json +1 -2
package/dist/bin.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
|
|
3
9
|
// src/bin.ts
|
|
4
10
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
11
|
+
import pc2 from "picocolors";
|
|
6
12
|
|
|
7
13
|
// src/browser.ts
|
|
8
14
|
import puppeteer from "puppeteer-core";
|
|
@@ -16,6 +22,78 @@ var __filename = fileURLToPath(import.meta.url);
|
|
|
16
22
|
var __dirname = path.dirname(__filename);
|
|
17
23
|
var CDP_PORT = 9222;
|
|
18
24
|
var CDP_URL = `http://localhost:${CDP_PORT}`;
|
|
25
|
+
async function fetchWithTimeout(url, timeoutMs = 1500) {
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const id = setTimeout(() => controller.abort(), timeoutMs);
|
|
28
|
+
try {
|
|
29
|
+
return await fetch(url, { signal: controller.signal });
|
|
30
|
+
} finally {
|
|
31
|
+
clearTimeout(id);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function promiseWithTimeout(promise, timeoutMs = 5e3, errorMsg = "Operation timed out") {
|
|
35
|
+
let timeoutId;
|
|
36
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
37
|
+
timeoutId = setTimeout(() => {
|
|
38
|
+
reject(new Error(errorMsg));
|
|
39
|
+
}, timeoutMs);
|
|
40
|
+
});
|
|
41
|
+
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
42
|
+
clearTimeout(timeoutId);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async function checkCdpReady(url, retries = 3) {
|
|
46
|
+
for (let i = 0; i < retries; i++) {
|
|
47
|
+
try {
|
|
48
|
+
const res = await fetchWithTimeout(url, 1500);
|
|
49
|
+
if (res.ok) return true;
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
if (i < retries - 1) {
|
|
53
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
async function killProcessOnPortIfZombie(port) {
|
|
59
|
+
const isResponding = await checkCdpReady(`http://127.0.0.1:${port}/json/version`, 1);
|
|
60
|
+
if (isResponding) {
|
|
61
|
+
console.log(pc.green(`connectBrowser: \u7AEF\u53E3 ${port} \u4E0A\u7684\u6D4F\u89C8\u5668\u5B9E\u4F8B\u4ECD\u5728\u6B63\u5E38\u54CD\u5E94\uFF0C\u8DF3\u8FC7\u5F3A\u6740\uFF0C\u5C1D\u8BD5\u76F4\u63A5\u63A5\u7BA1\u3002`));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const platform = os.platform();
|
|
66
|
+
const { execSync } = __require("child_process");
|
|
67
|
+
if (platform === "darwin" || platform === "linux") {
|
|
68
|
+
console.log(pc.yellow(`\u6B63\u5728\u68C0\u6D4B\u5E76\u6E05\u7406\u5360\u7528 ${port} \u7AEF\u53E3\u7684\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B...`));
|
|
69
|
+
const pids = execSync(`lsof -t -i :${port}`).toString().trim();
|
|
70
|
+
if (pids) {
|
|
71
|
+
console.log(pc.yellow(`\u53D1\u73B0\u50F5\u5C38 PID: ${pids.split("\n").join(", ")}\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`));
|
|
72
|
+
execSync(`kill -9 ${pids.split("\n").join(" ")}`);
|
|
73
|
+
console.log(pc.green(`\u6210\u529F\u6E05\u7406\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B`));
|
|
74
|
+
}
|
|
75
|
+
} else if (platform === "win32") {
|
|
76
|
+
console.log(pc.yellow(`\u6B63\u5728\u68C0\u6D4B\u5E76\u6E05\u7406 Windows \u4E0A\u5360\u7528 ${port} \u7AEF\u53E3\u7684\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B...`));
|
|
77
|
+
const output = execSync(`netstat -ano | findstr :${port}`).toString().trim();
|
|
78
|
+
if (output) {
|
|
79
|
+
const lines = output.split("\n");
|
|
80
|
+
const pids = /* @__PURE__ */ new Set();
|
|
81
|
+
lines.forEach((line) => {
|
|
82
|
+
const parts = line.trim().split(/\s+/);
|
|
83
|
+
const pid = parts[parts.length - 1];
|
|
84
|
+
if (pid && /^\d+$/.test(pid) && pid !== "0") {
|
|
85
|
+
pids.add(pid);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
pids.forEach((pid) => {
|
|
89
|
+
console.log(pc.yellow(`\u53D1\u73B0 Windows \u6B8B\u7559\u50F5\u5C38 PID: ${pid}\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`));
|
|
90
|
+
execSync(`taskkill /F /PID ${pid}`);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
19
97
|
function getDefaultChromePath() {
|
|
20
98
|
const platform = os.platform();
|
|
21
99
|
if (platform === "darwin") {
|
|
@@ -58,7 +136,7 @@ async function startChromeInBackground() {
|
|
|
58
136
|
const urls = [`http://localhost:${CDP_PORT}/json/version`, `http://127.0.0.1:${CDP_PORT}/json/version`];
|
|
59
137
|
for (const url of urls) {
|
|
60
138
|
try {
|
|
61
|
-
const response = await
|
|
139
|
+
const response = await fetchWithTimeout(url, 1e3);
|
|
62
140
|
if (response.ok) {
|
|
63
141
|
console.log(pc.green("Chrome \u542F\u52A8\u5E76\u5C31\u7EEA\u3002"));
|
|
64
142
|
return;
|
|
@@ -90,44 +168,77 @@ async function connectBrowser() {
|
|
|
90
168
|
}
|
|
91
169
|
};
|
|
92
170
|
try {
|
|
171
|
+
const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3);
|
|
172
|
+
if (!is127Ready) {
|
|
173
|
+
throw new Error("127.0.0.1 CDP port not responding");
|
|
174
|
+
}
|
|
93
175
|
console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
|
|
94
|
-
const browser = await
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
176
|
+
const browser = await promiseWithTimeout(
|
|
177
|
+
puppeteer.connect({
|
|
178
|
+
browserURL: `http://127.0.0.1:${CDP_PORT}`,
|
|
179
|
+
defaultViewport: null,
|
|
180
|
+
targetFilter
|
|
181
|
+
}),
|
|
182
|
+
1e4,
|
|
183
|
+
"puppeteer.connect to 127.0.0.1 timed out"
|
|
184
|
+
);
|
|
99
185
|
console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
|
|
100
186
|
return browser;
|
|
101
187
|
} catch (error) {
|
|
102
188
|
try {
|
|
189
|
+
const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3);
|
|
190
|
+
if (!isLocalhostReady) {
|
|
191
|
+
throw new Error("localhost CDP port not responding");
|
|
192
|
+
}
|
|
103
193
|
console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
|
|
104
|
-
const browser = await
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
194
|
+
const browser = await promiseWithTimeout(
|
|
195
|
+
puppeteer.connect({
|
|
196
|
+
browserURL: `http://localhost:${CDP_PORT}`,
|
|
197
|
+
defaultViewport: null,
|
|
198
|
+
targetFilter
|
|
199
|
+
}),
|
|
200
|
+
1e4,
|
|
201
|
+
"puppeteer.connect to localhost timed out"
|
|
202
|
+
);
|
|
109
203
|
console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
|
|
110
204
|
return browser;
|
|
111
205
|
} catch (error2) {
|
|
112
206
|
console.log(pc.yellow(`connectBrowser: \u8FDE\u63A5\u5931\u8D25\uFF0C\u5C06\u5C1D\u8BD5\u5524\u8D77\u6D4F\u89C8\u5668\u3002\u9519\u8BEF\u539F\u56E0: ${error2 instanceof Error ? error2.message : String(error2)}`));
|
|
113
207
|
try {
|
|
208
|
+
await killProcessOnPortIfZombie(CDP_PORT);
|
|
114
209
|
await startChromeInBackground();
|
|
115
210
|
try {
|
|
211
|
+
const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3);
|
|
212
|
+
if (!is127Ready) {
|
|
213
|
+
throw new Error("127.0.0.1 CDP port not responding after launch");
|
|
214
|
+
}
|
|
116
215
|
console.log(pc.yellow("connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
|
|
117
|
-
const browser = await
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
216
|
+
const browser = await promiseWithTimeout(
|
|
217
|
+
puppeteer.connect({
|
|
218
|
+
browserURL: `http://127.0.0.1:${CDP_PORT}`,
|
|
219
|
+
defaultViewport: null,
|
|
220
|
+
targetFilter
|
|
221
|
+
}),
|
|
222
|
+
1e4,
|
|
223
|
+
"puppeteer.connect to 127.0.0.1 after launch timed out"
|
|
224
|
+
);
|
|
122
225
|
console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
|
|
123
226
|
return browser;
|
|
124
227
|
} catch (e) {
|
|
228
|
+
const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3);
|
|
229
|
+
if (!isLocalhostReady) {
|
|
230
|
+
throw new Error("localhost CDP port not responding after launch");
|
|
231
|
+
}
|
|
125
232
|
console.log(pc.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
|
|
126
|
-
const browser = await
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
233
|
+
const browser = await promiseWithTimeout(
|
|
234
|
+
puppeteer.connect({
|
|
235
|
+
browserURL: `http://localhost:${CDP_PORT}`,
|
|
236
|
+
defaultViewport: null,
|
|
237
|
+
targetFilter
|
|
238
|
+
}),
|
|
239
|
+
1e4,
|
|
240
|
+
"puppeteer.connect to localhost after launch timed out"
|
|
241
|
+
);
|
|
131
242
|
console.log(pc.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
|
|
132
243
|
return browser;
|
|
133
244
|
}
|
|
@@ -151,9 +262,8 @@ async function getPageTargetId(page) {
|
|
|
151
262
|
});
|
|
152
263
|
}
|
|
153
264
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const pageTargets = targets.filter((t) => {
|
|
265
|
+
function getPageTargets(browser) {
|
|
266
|
+
return browser.targets().filter((t) => {
|
|
157
267
|
try {
|
|
158
268
|
const type = (typeof t.type === "function" ? t.type() : t.type) || "";
|
|
159
269
|
const url = (typeof t.url === "function" ? t.url() : t.url) || "";
|
|
@@ -162,6 +272,41 @@ async function getTargetPage(browser, tabid) {
|
|
|
162
272
|
return false;
|
|
163
273
|
}
|
|
164
274
|
});
|
|
275
|
+
}
|
|
276
|
+
function getTargetIdFromTarget(target) {
|
|
277
|
+
return typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
|
|
278
|
+
}
|
|
279
|
+
function findPageTargetByTabId(browser, tabid) {
|
|
280
|
+
const pageTargets = getPageTargets(browser);
|
|
281
|
+
return pageTargets.find((t) => {
|
|
282
|
+
const tid = getTargetIdFromTarget(t);
|
|
283
|
+
return tid === tabid || tid.includes(tabid);
|
|
284
|
+
}) ?? null;
|
|
285
|
+
}
|
|
286
|
+
async function activateTabById(browser, tabid) {
|
|
287
|
+
const target = findPageTargetByTabId(browser, tabid);
|
|
288
|
+
if (!target) {
|
|
289
|
+
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
290
|
+
}
|
|
291
|
+
const realTabId = getTargetIdFromTarget(target);
|
|
292
|
+
const pages = await browser.pages();
|
|
293
|
+
let sessionPage = pages.find((p) => !p.url().startsWith("devtools://"));
|
|
294
|
+
if (!sessionPage && pages.length > 0) {
|
|
295
|
+
sessionPage = pages[0];
|
|
296
|
+
}
|
|
297
|
+
if (!sessionPage) {
|
|
298
|
+
sessionPage = await browser.newPage();
|
|
299
|
+
}
|
|
300
|
+
const session = await sessionPage.createCDPSession();
|
|
301
|
+
try {
|
|
302
|
+
await session.send("Target.activateTarget", { targetId: realTabId });
|
|
303
|
+
} finally {
|
|
304
|
+
await session.detach().catch(() => {
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function getTargetPage(browser, tabid) {
|
|
309
|
+
const pageTargets = getPageTargets(browser);
|
|
165
310
|
if (pageTargets.length === 0) {
|
|
166
311
|
const newPage = await browser.newPage();
|
|
167
312
|
await injectWebMCPPolyfillAndTools(newPage);
|
|
@@ -169,16 +314,11 @@ async function getTargetPage(browser, tabid) {
|
|
|
169
314
|
}
|
|
170
315
|
let targetPage = null;
|
|
171
316
|
if (tabid !== void 0) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (tid === tabid || tid.includes(tabid)) {
|
|
175
|
-
targetPage = await target.page();
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
if (!targetPage) {
|
|
317
|
+
const target = findPageTargetByTabId(browser, tabid);
|
|
318
|
+
if (!target) {
|
|
180
319
|
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
181
320
|
}
|
|
321
|
+
targetPage = await target.page();
|
|
182
322
|
} else {
|
|
183
323
|
try {
|
|
184
324
|
const urls = [
|
|
@@ -188,7 +328,7 @@ async function getTargetPage(browser, tabid) {
|
|
|
188
328
|
let activeTargetId = null;
|
|
189
329
|
for (const url of urls) {
|
|
190
330
|
try {
|
|
191
|
-
const res = await
|
|
331
|
+
const res = await fetchWithTimeout(url, 1e3);
|
|
192
332
|
if (res.ok) {
|
|
193
333
|
const targetsData = await res.json();
|
|
194
334
|
const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
|
|
@@ -202,8 +342,7 @@ async function getTargetPage(browser, tabid) {
|
|
|
202
342
|
}
|
|
203
343
|
if (activeTargetId) {
|
|
204
344
|
for (const target of pageTargets) {
|
|
205
|
-
|
|
206
|
-
if (tid === activeTargetId) {
|
|
345
|
+
if (getTargetIdFromTarget(target) === activeTargetId) {
|
|
207
346
|
targetPage = await target.page();
|
|
208
347
|
break;
|
|
209
348
|
}
|
|
@@ -323,7 +462,7 @@ async function stateCommand({ tabid }) {
|
|
|
323
462
|
};
|
|
324
463
|
});
|
|
325
464
|
const pages = await browser.pages();
|
|
326
|
-
const
|
|
465
|
+
const tabs2 = await Promise.all(pages.map(async (p) => {
|
|
327
466
|
const pUrl = p.url();
|
|
328
467
|
if (pUrl.startsWith("devtools://")) return null;
|
|
329
468
|
const pTitle = await Promise.race([
|
|
@@ -339,7 +478,7 @@ async function stateCommand({ tabid }) {
|
|
|
339
478
|
}));
|
|
340
479
|
return {
|
|
341
480
|
...state,
|
|
342
|
-
tabs:
|
|
481
|
+
tabs: tabs2.filter(Boolean)
|
|
343
482
|
};
|
|
344
483
|
} finally {
|
|
345
484
|
await browser.disconnect();
|
|
@@ -380,56 +519,110 @@ async function runCommand({
|
|
|
380
519
|
}
|
|
381
520
|
}
|
|
382
521
|
|
|
383
|
-
// src/commands/
|
|
384
|
-
|
|
385
|
-
|
|
522
|
+
// src/commands/tabs.ts
|
|
523
|
+
function normalizeUrl(url) {
|
|
524
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
525
|
+
return "https://" + url;
|
|
526
|
+
}
|
|
527
|
+
return url;
|
|
528
|
+
}
|
|
529
|
+
async function tabsOpenCommand(url) {
|
|
386
530
|
const browser = await connectBrowser();
|
|
387
531
|
try {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const tid = typeof t._getTargetInfo === "function" ? t._getTargetInfo().targetId : t._targetId || t.targetId || "";
|
|
410
|
-
return tid === tabid || tid.includes(tabid);
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
if (selectedTarget) {
|
|
414
|
-
console.log(pc2.yellow("openCommand: \u6B63\u5728\u5C06\u5339\u914D\u7684 target \u8F6C\u6362\u4E3A page..."));
|
|
415
|
-
page = await selectedTarget.page();
|
|
416
|
-
} else if (pageTargets.length > 0) {
|
|
417
|
-
console.log(pc2.yellow("openCommand: \u6B63\u5728\u5C06\u6700\u540E\u4E00\u4E2A target \u8F6C\u6362\u4E3A page..."));
|
|
418
|
-
page = await pageTargets[pageTargets.length - 1].page();
|
|
419
|
-
}
|
|
420
|
-
if (!page) {
|
|
421
|
-
console.log(pc2.yellow("openCommand: \u6CA1\u6709\u627E\u5230\u53EF\u7528 page\uFF0C\u6B63\u5728\u521B\u5EFA\u65B0\u9875\u9762..."));
|
|
422
|
-
page = await browser.newPage();
|
|
423
|
-
}
|
|
532
|
+
const page = await browser.newPage();
|
|
533
|
+
url = normalizeUrl(url);
|
|
534
|
+
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
535
|
+
await injectIntoPage(page);
|
|
536
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
537
|
+
return {
|
|
538
|
+
success: true,
|
|
539
|
+
tabid: await getPageTargetId(page),
|
|
540
|
+
url: page.url(),
|
|
541
|
+
title: await page.title()
|
|
542
|
+
};
|
|
543
|
+
} finally {
|
|
544
|
+
await browser.disconnect();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
async function tabsCloseCommand(tabid) {
|
|
548
|
+
const browser = await connectBrowser();
|
|
549
|
+
try {
|
|
550
|
+
const target = findPageTargetByTabId(browser, tabid);
|
|
551
|
+
if (!target) {
|
|
552
|
+
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
424
553
|
}
|
|
425
|
-
|
|
426
|
-
|
|
554
|
+
const page = await target.page();
|
|
555
|
+
if (!page) {
|
|
556
|
+
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
557
|
+
}
|
|
558
|
+
const closedTabid = await getPageTargetId(page).catch(() => tabid);
|
|
559
|
+
await page.close();
|
|
560
|
+
return {
|
|
561
|
+
success: true,
|
|
562
|
+
tabid: closedTabid
|
|
563
|
+
};
|
|
564
|
+
} finally {
|
|
565
|
+
await browser.disconnect();
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
async function tabsSwitchCommand(tabid) {
|
|
569
|
+
const browser = await connectBrowser();
|
|
570
|
+
try {
|
|
571
|
+
await activateTabById(browser, tabid);
|
|
572
|
+
const page = await getTargetPage(browser, tabid);
|
|
573
|
+
return {
|
|
574
|
+
success: true,
|
|
575
|
+
tabid: await getPageTargetId(page),
|
|
576
|
+
url: page.url(),
|
|
577
|
+
title: await page.title()
|
|
578
|
+
};
|
|
579
|
+
} finally {
|
|
580
|
+
await browser.disconnect();
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async function tabsBackCommand(tabid) {
|
|
584
|
+
const browser = await connectBrowser();
|
|
585
|
+
try {
|
|
586
|
+
const page = await getTargetPage(browser, tabid);
|
|
587
|
+
const response = await page.goBack();
|
|
588
|
+
if (!response) {
|
|
589
|
+
return {
|
|
590
|
+
success: false,
|
|
591
|
+
error: "\u65E0\u6CD5\u540E\u9000\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u8D77\u70B9",
|
|
592
|
+
tabid: await getPageTargetId(page).catch(() => tabid),
|
|
593
|
+
url: page.url(),
|
|
594
|
+
title: await page.title()
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
await injectIntoPage(page);
|
|
598
|
+
return {
|
|
599
|
+
success: true,
|
|
600
|
+
tabid: await getPageTargetId(page),
|
|
601
|
+
url: page.url(),
|
|
602
|
+
title: await page.title()
|
|
603
|
+
};
|
|
604
|
+
} finally {
|
|
605
|
+
await browser.disconnect();
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
async function tabsForwardCommand(tabid) {
|
|
609
|
+
const browser = await connectBrowser();
|
|
610
|
+
try {
|
|
611
|
+
const page = await getTargetPage(browser, tabid);
|
|
612
|
+
const response = await page.goForward();
|
|
613
|
+
if (!response) {
|
|
614
|
+
return {
|
|
615
|
+
success: false,
|
|
616
|
+
error: "\u65E0\u6CD5\u524D\u8FDB\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u672B\u5C3E",
|
|
617
|
+
tabid: await getPageTargetId(page).catch(() => tabid),
|
|
618
|
+
url: page.url(),
|
|
619
|
+
title: await page.title()
|
|
620
|
+
};
|
|
427
621
|
}
|
|
428
|
-
console.log(pc2.cyan(`\u6B63\u5728\u6253\u5F00: ${url}`));
|
|
429
|
-
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
430
622
|
await injectIntoPage(page);
|
|
431
623
|
return {
|
|
432
624
|
success: true,
|
|
625
|
+
tabid: await getPageTargetId(page),
|
|
433
626
|
url: page.url(),
|
|
434
627
|
title: await page.title()
|
|
435
628
|
};
|
|
@@ -438,13 +631,71 @@ async function openCommand(url, { tabid, newTab }) {
|
|
|
438
631
|
}
|
|
439
632
|
}
|
|
440
633
|
|
|
634
|
+
// package.json
|
|
635
|
+
var package_default = {
|
|
636
|
+
name: "@opentiny/webmcp-cli",
|
|
637
|
+
version: "0.0.1-alpha.2",
|
|
638
|
+
type: "module",
|
|
639
|
+
description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
|
|
640
|
+
main: "dist/index.js",
|
|
641
|
+
types: "dist/index.d.ts",
|
|
642
|
+
bin: {
|
|
643
|
+
"webmcp-cli": "./dist/bin.js"
|
|
644
|
+
},
|
|
645
|
+
files: [
|
|
646
|
+
"dist",
|
|
647
|
+
"README.md"
|
|
648
|
+
],
|
|
649
|
+
scripts: {
|
|
650
|
+
dev: "tsup --watch",
|
|
651
|
+
"build:inject": "node scripts/build-inject.mjs",
|
|
652
|
+
build: "tsup && pnpm run build:inject",
|
|
653
|
+
prepublishOnly: "pnpm run build",
|
|
654
|
+
"link:global": "npm install -g .",
|
|
655
|
+
start: "node dist/bin.js"
|
|
656
|
+
},
|
|
657
|
+
keywords: [
|
|
658
|
+
"chrome",
|
|
659
|
+
"puppeteer",
|
|
660
|
+
"cli"
|
|
661
|
+
],
|
|
662
|
+
license: "MIT",
|
|
663
|
+
author: "OpenTiny Team",
|
|
664
|
+
engines: {
|
|
665
|
+
node: ">=16"
|
|
666
|
+
},
|
|
667
|
+
publishConfig: {
|
|
668
|
+
access: "public"
|
|
669
|
+
},
|
|
670
|
+
dependencies: {
|
|
671
|
+
"@mcp-b/webmcp-polyfill": "catalog:",
|
|
672
|
+
"@opentiny/next-sdk": "workspace:^",
|
|
673
|
+
"@page-agent/page-controller": "catalog:",
|
|
674
|
+
commander: "^11.1.0",
|
|
675
|
+
picocolors: "^1.0.0",
|
|
676
|
+
"puppeteer-core": "^22.0.0"
|
|
677
|
+
},
|
|
678
|
+
devDependencies: {
|
|
679
|
+
"@types/node": "^20.0.0",
|
|
680
|
+
esbuild: "^0.25.0",
|
|
681
|
+
tsup: "^8.0.2",
|
|
682
|
+
tsx: "^4.0.0",
|
|
683
|
+
typescript: "^5.3.3"
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
441
687
|
// src/bin.ts
|
|
442
688
|
var program = new Command();
|
|
443
689
|
function parseTabId(id) {
|
|
444
690
|
if (!id) return void 0;
|
|
445
691
|
return id;
|
|
446
692
|
}
|
|
447
|
-
|
|
693
|
+
function handleCommandError(error, commandName) {
|
|
694
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
695
|
+
console.error(pc2.red(`Error executing ${commandName} command: ${msg}`));
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
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) => {
|
|
448
699
|
const opts = thisCommand.opts();
|
|
449
700
|
if (opts.workspace) {
|
|
450
701
|
process.env.WEBMCP_WORKSPACE = opts.workspace;
|
|
@@ -455,9 +706,7 @@ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D
|
|
|
455
706
|
const result = await stateCommand({ tabid: parseTabId(options.tabid) });
|
|
456
707
|
console.log(JSON.stringify(result, null, 2));
|
|
457
708
|
} catch (error) {
|
|
458
|
-
|
|
459
|
-
console.error(pc3.red(`Error executing state command: ${msg}`));
|
|
460
|
-
process.exit(1);
|
|
709
|
+
handleCommandError(error, "state");
|
|
461
710
|
}
|
|
462
711
|
});
|
|
463
712
|
program.command("run <toolName> <argsJson>").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").action(async (toolName, argsJson, options) => {
|
|
@@ -469,22 +718,48 @@ program.command("run <toolName> <argsJson>").description("\u5411\u6307\u5B9A\u98
|
|
|
469
718
|
});
|
|
470
719
|
console.log(JSON.stringify(result, null, 2));
|
|
471
720
|
} catch (error) {
|
|
472
|
-
|
|
473
|
-
console.error(pc3.red(`Error executing run command: ${msg}`));
|
|
474
|
-
process.exit(1);
|
|
721
|
+
handleCommandError(error, "run");
|
|
475
722
|
}
|
|
476
723
|
});
|
|
477
|
-
program.command("
|
|
724
|
+
var tabs = program.command("tabs").description("\u7BA1\u7406\u6D4F\u89C8\u5668\u6807\u7B7E\u9875");
|
|
725
|
+
tabs.command("open <url>").description("\u6253\u5F00\u65B0\u7F51\u9875").action(async (url) => {
|
|
478
726
|
try {
|
|
479
|
-
const result = await
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
727
|
+
const result = await tabsOpenCommand(url);
|
|
728
|
+
console.log(JSON.stringify(result, null, 2));
|
|
729
|
+
} catch (error) {
|
|
730
|
+
handleCommandError(error, "tabs open");
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
tabs.command("close <tabid>").description("\u5173\u95ED\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid) => {
|
|
734
|
+
try {
|
|
735
|
+
const result = await tabsCloseCommand(tabid);
|
|
736
|
+
console.log(JSON.stringify(result, null, 2));
|
|
737
|
+
} catch (error) {
|
|
738
|
+
handleCommandError(error, "tabs close");
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
tabs.command("switch <tabid>").description("\u6FC0\u6D3B\u5E76\u5207\u6362\u5230\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid) => {
|
|
742
|
+
try {
|
|
743
|
+
const result = await tabsSwitchCommand(tabid);
|
|
744
|
+
console.log(JSON.stringify(result, null, 2));
|
|
745
|
+
} catch (error) {
|
|
746
|
+
handleCommandError(error, "tabs switch");
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
tabs.command("back [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u540E\u9000\u4E00\u6B65").action(async (tabid) => {
|
|
750
|
+
try {
|
|
751
|
+
const result = await tabsBackCommand(parseTabId(tabid));
|
|
752
|
+
console.log(JSON.stringify(result, null, 2));
|
|
753
|
+
} catch (error) {
|
|
754
|
+
handleCommandError(error, "tabs back");
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
tabs.command("forward [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u524D\u8FDB\u4E00\u6B65").action(async (tabid) => {
|
|
758
|
+
try {
|
|
759
|
+
const result = await tabsForwardCommand(parseTabId(tabid));
|
|
483
760
|
console.log(JSON.stringify(result, null, 2));
|
|
484
761
|
} catch (error) {
|
|
485
|
-
|
|
486
|
-
console.error(pc3.red(`Error executing open command: ${msg}`));
|
|
487
|
-
process.exit(1);
|
|
762
|
+
handleCommandError(error, "tabs forward");
|
|
488
763
|
}
|
|
489
764
|
});
|
|
490
765
|
program.parse(process.argv);
|