@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/README.md
CHANGED
|
@@ -100,18 +100,22 @@ webmcp-cli run page-agent-tool '{"action": "fill", "index": 40, "text": "OpenTin
|
|
|
100
100
|
|
|
101
101
|
---
|
|
102
102
|
|
|
103
|
-
### 3. `
|
|
103
|
+
### 3. `tabs` 命令
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
管理浏览器标签页。
|
|
106
106
|
|
|
107
107
|
**用法:**
|
|
108
108
|
```bash
|
|
109
|
-
webmcp-cli open <url>
|
|
110
|
-
webmcp-cli
|
|
111
|
-
webmcp-cli
|
|
109
|
+
webmcp-cli tabs open <url> # 在新标签页打开网页
|
|
110
|
+
webmcp-cli tabs close <tabid> # 关闭指定标签页
|
|
111
|
+
webmcp-cli tabs switch <tabid> # 切换到指定标签页
|
|
112
|
+
webmcp-cli tabs back [tabid] # 后退(默认当前标签页)
|
|
113
|
+
webmcp-cli tabs forward [tabid] # 前进(默认当前标签页)
|
|
112
114
|
```
|
|
113
115
|
|
|
114
116
|
**示例:**
|
|
115
117
|
```bash
|
|
116
|
-
webmcp-cli open "https://github.com/opentiny/tiny-vue"
|
|
118
|
+
webmcp-cli tabs open "https://github.com/opentiny/tiny-vue"
|
|
119
|
+
webmcp-cli tabs switch 2EA73ED323E46E5E108D4E46DA4E4AA7
|
|
120
|
+
webmcp-cli tabs back
|
|
117
121
|
```
|
package/dist/bin.cjs
CHANGED
|
@@ -25,7 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/bin.ts
|
|
27
27
|
var import_commander = require("commander");
|
|
28
|
-
var
|
|
28
|
+
var import_picocolors2 = __toESM(require("picocolors"), 1);
|
|
29
29
|
|
|
30
30
|
// src/browser.ts
|
|
31
31
|
var import_puppeteer_core = __toESM(require("puppeteer-core"), 1);
|
|
@@ -40,6 +40,78 @@ var __filename = (0, import_url.fileURLToPath)(import_meta.url);
|
|
|
40
40
|
var __dirname = import_path.default.dirname(__filename);
|
|
41
41
|
var CDP_PORT = 9222;
|
|
42
42
|
var CDP_URL = `http://localhost:${CDP_PORT}`;
|
|
43
|
+
async function fetchWithTimeout(url, timeoutMs = 1500) {
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const id = setTimeout(() => controller.abort(), timeoutMs);
|
|
46
|
+
try {
|
|
47
|
+
return await fetch(url, { signal: controller.signal });
|
|
48
|
+
} finally {
|
|
49
|
+
clearTimeout(id);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function promiseWithTimeout(promise, timeoutMs = 5e3, errorMsg = "Operation timed out") {
|
|
53
|
+
let timeoutId;
|
|
54
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
55
|
+
timeoutId = setTimeout(() => {
|
|
56
|
+
reject(new Error(errorMsg));
|
|
57
|
+
}, timeoutMs);
|
|
58
|
+
});
|
|
59
|
+
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
60
|
+
clearTimeout(timeoutId);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async function checkCdpReady(url, retries = 3) {
|
|
64
|
+
for (let i = 0; i < retries; i++) {
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetchWithTimeout(url, 1500);
|
|
67
|
+
if (res.ok) return true;
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
if (i < retries - 1) {
|
|
71
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
async function killProcessOnPortIfZombie(port) {
|
|
77
|
+
const isResponding = await checkCdpReady(`http://127.0.0.1:${port}/json/version`, 1);
|
|
78
|
+
if (isResponding) {
|
|
79
|
+
console.log(import_picocolors.default.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`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const platform = import_os.default.platform();
|
|
84
|
+
const { execSync } = require("child_process");
|
|
85
|
+
if (platform === "darwin" || platform === "linux") {
|
|
86
|
+
console.log(import_picocolors.default.yellow(`\u6B63\u5728\u68C0\u6D4B\u5E76\u6E05\u7406\u5360\u7528 ${port} \u7AEF\u53E3\u7684\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B...`));
|
|
87
|
+
const pids = execSync(`lsof -t -i :${port}`).toString().trim();
|
|
88
|
+
if (pids) {
|
|
89
|
+
console.log(import_picocolors.default.yellow(`\u53D1\u73B0\u50F5\u5C38 PID: ${pids.split("\n").join(", ")}\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`));
|
|
90
|
+
execSync(`kill -9 ${pids.split("\n").join(" ")}`);
|
|
91
|
+
console.log(import_picocolors.default.green(`\u6210\u529F\u6E05\u7406\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B`));
|
|
92
|
+
}
|
|
93
|
+
} else if (platform === "win32") {
|
|
94
|
+
console.log(import_picocolors.default.yellow(`\u6B63\u5728\u68C0\u6D4B\u5E76\u6E05\u7406 Windows \u4E0A\u5360\u7528 ${port} \u7AEF\u53E3\u7684\u6B8B\u7559\u50F5\u5C38\u8FDB\u7A0B...`));
|
|
95
|
+
const output = execSync(`netstat -ano | findstr :${port}`).toString().trim();
|
|
96
|
+
if (output) {
|
|
97
|
+
const lines = output.split("\n");
|
|
98
|
+
const pids = /* @__PURE__ */ new Set();
|
|
99
|
+
lines.forEach((line) => {
|
|
100
|
+
const parts = line.trim().split(/\s+/);
|
|
101
|
+
const pid = parts[parts.length - 1];
|
|
102
|
+
if (pid && /^\d+$/.test(pid) && pid !== "0") {
|
|
103
|
+
pids.add(pid);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
pids.forEach((pid) => {
|
|
107
|
+
console.log(import_picocolors.default.yellow(`\u53D1\u73B0 Windows \u6B8B\u7559\u50F5\u5C38 PID: ${pid}\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`));
|
|
108
|
+
execSync(`taskkill /F /PID ${pid}`);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
43
115
|
function getDefaultChromePath() {
|
|
44
116
|
const platform = import_os.default.platform();
|
|
45
117
|
if (platform === "darwin") {
|
|
@@ -82,7 +154,7 @@ async function startChromeInBackground() {
|
|
|
82
154
|
const urls = [`http://localhost:${CDP_PORT}/json/version`, `http://127.0.0.1:${CDP_PORT}/json/version`];
|
|
83
155
|
for (const url of urls) {
|
|
84
156
|
try {
|
|
85
|
-
const response = await
|
|
157
|
+
const response = await fetchWithTimeout(url, 1e3);
|
|
86
158
|
if (response.ok) {
|
|
87
159
|
console.log(import_picocolors.default.green("Chrome \u542F\u52A8\u5E76\u5C31\u7EEA\u3002"));
|
|
88
160
|
return;
|
|
@@ -114,44 +186,77 @@ async function connectBrowser() {
|
|
|
114
186
|
}
|
|
115
187
|
};
|
|
116
188
|
try {
|
|
189
|
+
const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3);
|
|
190
|
+
if (!is127Ready) {
|
|
191
|
+
throw new Error("127.0.0.1 CDP port not responding");
|
|
192
|
+
}
|
|
117
193
|
console.log(import_picocolors.default.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
|
|
118
|
-
const browser = await
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
194
|
+
const browser = await promiseWithTimeout(
|
|
195
|
+
import_puppeteer_core.default.connect({
|
|
196
|
+
browserURL: `http://127.0.0.1:${CDP_PORT}`,
|
|
197
|
+
defaultViewport: null,
|
|
198
|
+
targetFilter
|
|
199
|
+
}),
|
|
200
|
+
1e4,
|
|
201
|
+
"puppeteer.connect to 127.0.0.1 timed out"
|
|
202
|
+
);
|
|
123
203
|
console.log(import_picocolors.default.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
|
|
124
204
|
return browser;
|
|
125
205
|
} catch (error) {
|
|
126
206
|
try {
|
|
207
|
+
const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3);
|
|
208
|
+
if (!isLocalhostReady) {
|
|
209
|
+
throw new Error("localhost CDP port not responding");
|
|
210
|
+
}
|
|
127
211
|
console.log(import_picocolors.default.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
|
|
128
|
-
const browser = await
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
212
|
+
const browser = await promiseWithTimeout(
|
|
213
|
+
import_puppeteer_core.default.connect({
|
|
214
|
+
browserURL: `http://localhost:${CDP_PORT}`,
|
|
215
|
+
defaultViewport: null,
|
|
216
|
+
targetFilter
|
|
217
|
+
}),
|
|
218
|
+
1e4,
|
|
219
|
+
"puppeteer.connect to localhost timed out"
|
|
220
|
+
);
|
|
133
221
|
console.log(import_picocolors.default.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
|
|
134
222
|
return browser;
|
|
135
223
|
} catch (error2) {
|
|
136
224
|
console.log(import_picocolors.default.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)}`));
|
|
137
225
|
try {
|
|
226
|
+
await killProcessOnPortIfZombie(CDP_PORT);
|
|
138
227
|
await startChromeInBackground();
|
|
139
228
|
try {
|
|
229
|
+
const is127Ready = await checkCdpReady(`http://127.0.0.1:${CDP_PORT}/json/version`, 3);
|
|
230
|
+
if (!is127Ready) {
|
|
231
|
+
throw new Error("127.0.0.1 CDP port not responding after launch");
|
|
232
|
+
}
|
|
140
233
|
console.log(import_picocolors.default.yellow("connectBrowser: \u6D4F\u89C8\u5668\u5DF2\u542F\u52A8\uFF0C\u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 127.0.0.1:9222..."));
|
|
141
|
-
const browser = await
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
234
|
+
const browser = await promiseWithTimeout(
|
|
235
|
+
import_puppeteer_core.default.connect({
|
|
236
|
+
browserURL: `http://127.0.0.1:${CDP_PORT}`,
|
|
237
|
+
defaultViewport: null,
|
|
238
|
+
targetFilter
|
|
239
|
+
}),
|
|
240
|
+
1e4,
|
|
241
|
+
"puppeteer.connect to 127.0.0.1 after launch timed out"
|
|
242
|
+
);
|
|
146
243
|
console.log(import_picocolors.default.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 127.0.0.1:9222"));
|
|
147
244
|
return browser;
|
|
148
245
|
} catch (e) {
|
|
246
|
+
const isLocalhostReady = await checkCdpReady(`http://localhost:${CDP_PORT}/json/version`, 3);
|
|
247
|
+
if (!isLocalhostReady) {
|
|
248
|
+
throw new Error("localhost CDP port not responding after launch");
|
|
249
|
+
}
|
|
149
250
|
console.log(import_picocolors.default.yellow("connectBrowser: \u6B63\u5728\u5C1D\u8BD5\u8FDE\u63A5 localhost:9222..."));
|
|
150
|
-
const browser = await
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
251
|
+
const browser = await promiseWithTimeout(
|
|
252
|
+
import_puppeteer_core.default.connect({
|
|
253
|
+
browserURL: `http://localhost:${CDP_PORT}`,
|
|
254
|
+
defaultViewport: null,
|
|
255
|
+
targetFilter
|
|
256
|
+
}),
|
|
257
|
+
1e4,
|
|
258
|
+
"puppeteer.connect to localhost after launch timed out"
|
|
259
|
+
);
|
|
155
260
|
console.log(import_picocolors.default.green("connectBrowser: \u6210\u529F\u8FDE\u63A5 localhost:9222"));
|
|
156
261
|
return browser;
|
|
157
262
|
}
|
|
@@ -175,9 +280,8 @@ async function getPageTargetId(page) {
|
|
|
175
280
|
});
|
|
176
281
|
}
|
|
177
282
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const pageTargets = targets.filter((t) => {
|
|
283
|
+
function getPageTargets(browser) {
|
|
284
|
+
return browser.targets().filter((t) => {
|
|
181
285
|
try {
|
|
182
286
|
const type = (typeof t.type === "function" ? t.type() : t.type) || "";
|
|
183
287
|
const url = (typeof t.url === "function" ? t.url() : t.url) || "";
|
|
@@ -186,6 +290,41 @@ async function getTargetPage(browser, tabid) {
|
|
|
186
290
|
return false;
|
|
187
291
|
}
|
|
188
292
|
});
|
|
293
|
+
}
|
|
294
|
+
function getTargetIdFromTarget(target) {
|
|
295
|
+
return typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
|
|
296
|
+
}
|
|
297
|
+
function findPageTargetByTabId(browser, tabid) {
|
|
298
|
+
const pageTargets = getPageTargets(browser);
|
|
299
|
+
return pageTargets.find((t) => {
|
|
300
|
+
const tid = getTargetIdFromTarget(t);
|
|
301
|
+
return tid === tabid || tid.includes(tabid);
|
|
302
|
+
}) ?? null;
|
|
303
|
+
}
|
|
304
|
+
async function activateTabById(browser, tabid) {
|
|
305
|
+
const target = findPageTargetByTabId(browser, tabid);
|
|
306
|
+
if (!target) {
|
|
307
|
+
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
308
|
+
}
|
|
309
|
+
const realTabId = getTargetIdFromTarget(target);
|
|
310
|
+
const pages = await browser.pages();
|
|
311
|
+
let sessionPage = pages.find((p) => !p.url().startsWith("devtools://"));
|
|
312
|
+
if (!sessionPage && pages.length > 0) {
|
|
313
|
+
sessionPage = pages[0];
|
|
314
|
+
}
|
|
315
|
+
if (!sessionPage) {
|
|
316
|
+
sessionPage = await browser.newPage();
|
|
317
|
+
}
|
|
318
|
+
const session = await sessionPage.createCDPSession();
|
|
319
|
+
try {
|
|
320
|
+
await session.send("Target.activateTarget", { targetId: realTabId });
|
|
321
|
+
} finally {
|
|
322
|
+
await session.detach().catch(() => {
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async function getTargetPage(browser, tabid) {
|
|
327
|
+
const pageTargets = getPageTargets(browser);
|
|
189
328
|
if (pageTargets.length === 0) {
|
|
190
329
|
const newPage = await browser.newPage();
|
|
191
330
|
await injectWebMCPPolyfillAndTools(newPage);
|
|
@@ -193,16 +332,11 @@ async function getTargetPage(browser, tabid) {
|
|
|
193
332
|
}
|
|
194
333
|
let targetPage = null;
|
|
195
334
|
if (tabid !== void 0) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (tid === tabid || tid.includes(tabid)) {
|
|
199
|
-
targetPage = await target.page();
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
if (!targetPage) {
|
|
335
|
+
const target = findPageTargetByTabId(browser, tabid);
|
|
336
|
+
if (!target) {
|
|
204
337
|
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
205
338
|
}
|
|
339
|
+
targetPage = await target.page();
|
|
206
340
|
} else {
|
|
207
341
|
try {
|
|
208
342
|
const urls = [
|
|
@@ -212,7 +346,7 @@ async function getTargetPage(browser, tabid) {
|
|
|
212
346
|
let activeTargetId = null;
|
|
213
347
|
for (const url of urls) {
|
|
214
348
|
try {
|
|
215
|
-
const res = await
|
|
349
|
+
const res = await fetchWithTimeout(url, 1e3);
|
|
216
350
|
if (res.ok) {
|
|
217
351
|
const targetsData = await res.json();
|
|
218
352
|
const active = targetsData.find((t) => t.type === "page" && !t.url.startsWith("devtools://"));
|
|
@@ -226,8 +360,7 @@ async function getTargetPage(browser, tabid) {
|
|
|
226
360
|
}
|
|
227
361
|
if (activeTargetId) {
|
|
228
362
|
for (const target of pageTargets) {
|
|
229
|
-
|
|
230
|
-
if (tid === activeTargetId) {
|
|
363
|
+
if (getTargetIdFromTarget(target) === activeTargetId) {
|
|
231
364
|
targetPage = await target.page();
|
|
232
365
|
break;
|
|
233
366
|
}
|
|
@@ -347,7 +480,7 @@ async function stateCommand({ tabid }) {
|
|
|
347
480
|
};
|
|
348
481
|
});
|
|
349
482
|
const pages = await browser.pages();
|
|
350
|
-
const
|
|
483
|
+
const tabs2 = await Promise.all(pages.map(async (p) => {
|
|
351
484
|
const pUrl = p.url();
|
|
352
485
|
if (pUrl.startsWith("devtools://")) return null;
|
|
353
486
|
const pTitle = await Promise.race([
|
|
@@ -363,7 +496,7 @@ async function stateCommand({ tabid }) {
|
|
|
363
496
|
}));
|
|
364
497
|
return {
|
|
365
498
|
...state,
|
|
366
|
-
tabs:
|
|
499
|
+
tabs: tabs2.filter(Boolean)
|
|
367
500
|
};
|
|
368
501
|
} finally {
|
|
369
502
|
await browser.disconnect();
|
|
@@ -404,56 +537,110 @@ async function runCommand({
|
|
|
404
537
|
}
|
|
405
538
|
}
|
|
406
539
|
|
|
407
|
-
// src/commands/
|
|
408
|
-
|
|
409
|
-
|
|
540
|
+
// src/commands/tabs.ts
|
|
541
|
+
function normalizeUrl(url) {
|
|
542
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
543
|
+
return "https://" + url;
|
|
544
|
+
}
|
|
545
|
+
return url;
|
|
546
|
+
}
|
|
547
|
+
async function tabsOpenCommand(url) {
|
|
410
548
|
const browser = await connectBrowser();
|
|
411
549
|
try {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const tid = typeof t._getTargetInfo === "function" ? t._getTargetInfo().targetId : t._targetId || t.targetId || "";
|
|
434
|
-
return tid === tabid || tid.includes(tabid);
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
if (selectedTarget) {
|
|
438
|
-
console.log(import_picocolors2.default.yellow("openCommand: \u6B63\u5728\u5C06\u5339\u914D\u7684 target \u8F6C\u6362\u4E3A page..."));
|
|
439
|
-
page = await selectedTarget.page();
|
|
440
|
-
} else if (pageTargets.length > 0) {
|
|
441
|
-
console.log(import_picocolors2.default.yellow("openCommand: \u6B63\u5728\u5C06\u6700\u540E\u4E00\u4E2A target \u8F6C\u6362\u4E3A page..."));
|
|
442
|
-
page = await pageTargets[pageTargets.length - 1].page();
|
|
443
|
-
}
|
|
444
|
-
if (!page) {
|
|
445
|
-
console.log(import_picocolors2.default.yellow("openCommand: \u6CA1\u6709\u627E\u5230\u53EF\u7528 page\uFF0C\u6B63\u5728\u521B\u5EFA\u65B0\u9875\u9762..."));
|
|
446
|
-
page = await browser.newPage();
|
|
447
|
-
}
|
|
550
|
+
const page = await browser.newPage();
|
|
551
|
+
url = normalizeUrl(url);
|
|
552
|
+
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
553
|
+
await injectIntoPage(page);
|
|
554
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
555
|
+
return {
|
|
556
|
+
success: true,
|
|
557
|
+
tabid: await getPageTargetId(page),
|
|
558
|
+
url: page.url(),
|
|
559
|
+
title: await page.title()
|
|
560
|
+
};
|
|
561
|
+
} finally {
|
|
562
|
+
await browser.disconnect();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
async function tabsCloseCommand(tabid) {
|
|
566
|
+
const browser = await connectBrowser();
|
|
567
|
+
try {
|
|
568
|
+
const target = findPageTargetByTabId(browser, tabid);
|
|
569
|
+
if (!target) {
|
|
570
|
+
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
448
571
|
}
|
|
449
|
-
|
|
450
|
-
|
|
572
|
+
const page = await target.page();
|
|
573
|
+
if (!page) {
|
|
574
|
+
throw new Error(`Tab with targetId "${tabid}" not found.`);
|
|
575
|
+
}
|
|
576
|
+
const closedTabid = await getPageTargetId(page).catch(() => tabid);
|
|
577
|
+
await page.close();
|
|
578
|
+
return {
|
|
579
|
+
success: true,
|
|
580
|
+
tabid: closedTabid
|
|
581
|
+
};
|
|
582
|
+
} finally {
|
|
583
|
+
await browser.disconnect();
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
async function tabsSwitchCommand(tabid) {
|
|
587
|
+
const browser = await connectBrowser();
|
|
588
|
+
try {
|
|
589
|
+
await activateTabById(browser, tabid);
|
|
590
|
+
const page = await getTargetPage(browser, tabid);
|
|
591
|
+
return {
|
|
592
|
+
success: true,
|
|
593
|
+
tabid: await getPageTargetId(page),
|
|
594
|
+
url: page.url(),
|
|
595
|
+
title: await page.title()
|
|
596
|
+
};
|
|
597
|
+
} finally {
|
|
598
|
+
await browser.disconnect();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async function tabsBackCommand(tabid) {
|
|
602
|
+
const browser = await connectBrowser();
|
|
603
|
+
try {
|
|
604
|
+
const page = await getTargetPage(browser, tabid);
|
|
605
|
+
const response = await page.goBack();
|
|
606
|
+
if (!response) {
|
|
607
|
+
return {
|
|
608
|
+
success: false,
|
|
609
|
+
error: "\u65E0\u6CD5\u540E\u9000\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u8D77\u70B9",
|
|
610
|
+
tabid: await getPageTargetId(page).catch(() => tabid),
|
|
611
|
+
url: page.url(),
|
|
612
|
+
title: await page.title()
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
await injectIntoPage(page);
|
|
616
|
+
return {
|
|
617
|
+
success: true,
|
|
618
|
+
tabid: await getPageTargetId(page),
|
|
619
|
+
url: page.url(),
|
|
620
|
+
title: await page.title()
|
|
621
|
+
};
|
|
622
|
+
} finally {
|
|
623
|
+
await browser.disconnect();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
async function tabsForwardCommand(tabid) {
|
|
627
|
+
const browser = await connectBrowser();
|
|
628
|
+
try {
|
|
629
|
+
const page = await getTargetPage(browser, tabid);
|
|
630
|
+
const response = await page.goForward();
|
|
631
|
+
if (!response) {
|
|
632
|
+
return {
|
|
633
|
+
success: false,
|
|
634
|
+
error: "\u65E0\u6CD5\u524D\u8FDB\uFF1A\u5DF2\u5728\u5386\u53F2\u8BB0\u5F55\u672B\u5C3E",
|
|
635
|
+
tabid: await getPageTargetId(page).catch(() => tabid),
|
|
636
|
+
url: page.url(),
|
|
637
|
+
title: await page.title()
|
|
638
|
+
};
|
|
451
639
|
}
|
|
452
|
-
console.log(import_picocolors2.default.cyan(`\u6B63\u5728\u6253\u5F00: ${url}`));
|
|
453
|
-
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
454
640
|
await injectIntoPage(page);
|
|
455
641
|
return {
|
|
456
642
|
success: true,
|
|
643
|
+
tabid: await getPageTargetId(page),
|
|
457
644
|
url: page.url(),
|
|
458
645
|
title: await page.title()
|
|
459
646
|
};
|
|
@@ -462,13 +649,71 @@ async function openCommand(url, { tabid, newTab }) {
|
|
|
462
649
|
}
|
|
463
650
|
}
|
|
464
651
|
|
|
652
|
+
// package.json
|
|
653
|
+
var package_default = {
|
|
654
|
+
name: "@opentiny/webmcp-cli",
|
|
655
|
+
version: "0.0.1-alpha.2",
|
|
656
|
+
type: "module",
|
|
657
|
+
description: "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
|
|
658
|
+
main: "dist/index.js",
|
|
659
|
+
types: "dist/index.d.ts",
|
|
660
|
+
bin: {
|
|
661
|
+
"webmcp-cli": "./dist/bin.js"
|
|
662
|
+
},
|
|
663
|
+
files: [
|
|
664
|
+
"dist",
|
|
665
|
+
"README.md"
|
|
666
|
+
],
|
|
667
|
+
scripts: {
|
|
668
|
+
dev: "tsup --watch",
|
|
669
|
+
"build:inject": "node scripts/build-inject.mjs",
|
|
670
|
+
build: "tsup && pnpm run build:inject",
|
|
671
|
+
prepublishOnly: "pnpm run build",
|
|
672
|
+
"link:global": "npm install -g .",
|
|
673
|
+
start: "node dist/bin.js"
|
|
674
|
+
},
|
|
675
|
+
keywords: [
|
|
676
|
+
"chrome",
|
|
677
|
+
"puppeteer",
|
|
678
|
+
"cli"
|
|
679
|
+
],
|
|
680
|
+
license: "MIT",
|
|
681
|
+
author: "OpenTiny Team",
|
|
682
|
+
engines: {
|
|
683
|
+
node: ">=16"
|
|
684
|
+
},
|
|
685
|
+
publishConfig: {
|
|
686
|
+
access: "public"
|
|
687
|
+
},
|
|
688
|
+
dependencies: {
|
|
689
|
+
"@mcp-b/webmcp-polyfill": "catalog:",
|
|
690
|
+
"@opentiny/next-sdk": "workspace:^",
|
|
691
|
+
"@page-agent/page-controller": "catalog:",
|
|
692
|
+
commander: "^11.1.0",
|
|
693
|
+
picocolors: "^1.0.0",
|
|
694
|
+
"puppeteer-core": "^22.0.0"
|
|
695
|
+
},
|
|
696
|
+
devDependencies: {
|
|
697
|
+
"@types/node": "^20.0.0",
|
|
698
|
+
esbuild: "^0.25.0",
|
|
699
|
+
tsup: "^8.0.2",
|
|
700
|
+
tsx: "^4.0.0",
|
|
701
|
+
typescript: "^5.3.3"
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
|
|
465
705
|
// src/bin.ts
|
|
466
706
|
var program = new import_commander.Command();
|
|
467
707
|
function parseTabId(id) {
|
|
468
708
|
if (!id) return void 0;
|
|
469
709
|
return id;
|
|
470
710
|
}
|
|
471
|
-
|
|
711
|
+
function handleCommandError(error, commandName) {
|
|
712
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
713
|
+
console.error(import_picocolors2.default.red(`Error executing ${commandName} command: ${msg}`));
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
716
|
+
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) => {
|
|
472
717
|
const opts = thisCommand.opts();
|
|
473
718
|
if (opts.workspace) {
|
|
474
719
|
process.env.WEBMCP_WORKSPACE = opts.workspace;
|
|
@@ -479,9 +724,7 @@ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D
|
|
|
479
724
|
const result = await stateCommand({ tabid: parseTabId(options.tabid) });
|
|
480
725
|
console.log(JSON.stringify(result, null, 2));
|
|
481
726
|
} catch (error) {
|
|
482
|
-
|
|
483
|
-
console.error(import_picocolors3.default.red(`Error executing state command: ${msg}`));
|
|
484
|
-
process.exit(1);
|
|
727
|
+
handleCommandError(error, "state");
|
|
485
728
|
}
|
|
486
729
|
});
|
|
487
730
|
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) => {
|
|
@@ -493,22 +736,48 @@ program.command("run <toolName> <argsJson>").description("\u5411\u6307\u5B9A\u98
|
|
|
493
736
|
});
|
|
494
737
|
console.log(JSON.stringify(result, null, 2));
|
|
495
738
|
} catch (error) {
|
|
496
|
-
|
|
497
|
-
console.error(import_picocolors3.default.red(`Error executing run command: ${msg}`));
|
|
498
|
-
process.exit(1);
|
|
739
|
+
handleCommandError(error, "run");
|
|
499
740
|
}
|
|
500
741
|
});
|
|
501
|
-
program.command("
|
|
742
|
+
var tabs = program.command("tabs").description("\u7BA1\u7406\u6D4F\u89C8\u5668\u6807\u7B7E\u9875");
|
|
743
|
+
tabs.command("open <url>").description("\u6253\u5F00\u65B0\u7F51\u9875").action(async (url) => {
|
|
502
744
|
try {
|
|
503
|
-
const result = await
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
745
|
+
const result = await tabsOpenCommand(url);
|
|
746
|
+
console.log(JSON.stringify(result, null, 2));
|
|
747
|
+
} catch (error) {
|
|
748
|
+
handleCommandError(error, "tabs open");
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
tabs.command("close <tabid>").description("\u5173\u95ED\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid) => {
|
|
752
|
+
try {
|
|
753
|
+
const result = await tabsCloseCommand(tabid);
|
|
754
|
+
console.log(JSON.stringify(result, null, 2));
|
|
755
|
+
} catch (error) {
|
|
756
|
+
handleCommandError(error, "tabs close");
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
tabs.command("switch <tabid>").description("\u6FC0\u6D3B\u5E76\u5207\u6362\u5230\u6307\u5B9A tabid \u7684\u6807\u7B7E\u9875").action(async (tabid) => {
|
|
760
|
+
try {
|
|
761
|
+
const result = await tabsSwitchCommand(tabid);
|
|
762
|
+
console.log(JSON.stringify(result, null, 2));
|
|
763
|
+
} catch (error) {
|
|
764
|
+
handleCommandError(error, "tabs switch");
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
tabs.command("back [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u540E\u9000\u4E00\u6B65").action(async (tabid) => {
|
|
768
|
+
try {
|
|
769
|
+
const result = await tabsBackCommand(parseTabId(tabid));
|
|
770
|
+
console.log(JSON.stringify(result, null, 2));
|
|
771
|
+
} catch (error) {
|
|
772
|
+
handleCommandError(error, "tabs back");
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
tabs.command("forward [tabid]").description("\u5C06\u5F53\u524D\u6216\u6307\u5B9A\u6807\u7B7E\u9875\u5BFC\u822A\u524D\u8FDB\u4E00\u6B65").action(async (tabid) => {
|
|
776
|
+
try {
|
|
777
|
+
const result = await tabsForwardCommand(parseTabId(tabid));
|
|
507
778
|
console.log(JSON.stringify(result, null, 2));
|
|
508
779
|
} catch (error) {
|
|
509
|
-
|
|
510
|
-
console.error(import_picocolors3.default.red(`Error executing open command: ${msg}`));
|
|
511
|
-
process.exit(1);
|
|
780
|
+
handleCommandError(error, "tabs forward");
|
|
512
781
|
}
|
|
513
782
|
});
|
|
514
783
|
program.parse(process.argv);
|