@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/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 pc3 from "picocolors";
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 fetch(url);
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 puppeteer.connect({
95
- browserURL: `http://127.0.0.1:${CDP_PORT}`,
96
- defaultViewport: null,
97
- targetFilter
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 puppeteer.connect({
105
- browserURL: `http://localhost:${CDP_PORT}`,
106
- defaultViewport: null,
107
- targetFilter
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 puppeteer.connect({
118
- browserURL: `http://127.0.0.1:${CDP_PORT}`,
119
- defaultViewport: null,
120
- targetFilter
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 puppeteer.connect({
127
- browserURL: `http://localhost:${CDP_PORT}`,
128
- defaultViewport: null,
129
- targetFilter
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
- async function getTargetPage(browser, tabid) {
155
- const targets = browser.targets();
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
- for (const target of pageTargets) {
173
- const tid = typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
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 fetch(url);
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
- const tid = typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
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 tabs = await Promise.all(pages.map(async (p) => {
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: tabs.filter(Boolean)
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/open.ts
384
- import pc2 from "picocolors";
385
- async function openCommand(url, { tabid, newTab }) {
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
- let page;
389
- if (newTab) {
390
- console.log(pc2.yellow("openCommand: \u6B63\u5728\u521B\u5EFA\u65B0\u9875\u9762..."));
391
- page = await browser.newPage();
392
- } else {
393
- console.log(pc2.yellow("openCommand: \u83B7\u53D6\u6D4F\u89C8\u5668 targets..."));
394
- const targets = browser.targets();
395
- const pageTargets = targets.filter((t) => {
396
- try {
397
- const type = (typeof t.type === "function" ? t.type() : t.type) || "";
398
- const urlStr = (typeof t.url === "function" ? t.url() : t.url) || "";
399
- return type === "page" && !urlStr.startsWith("devtools://");
400
- } catch {
401
- return false;
402
- }
403
- });
404
- console.log(pc2.yellow(`openCommand: \u666E\u901A\u9875\u9762 targets \u6570\u91CF: ${pageTargets.length}`));
405
- let selectedTarget;
406
- if (tabid !== void 0) {
407
- console.log(pc2.yellow(`openCommand: \u6B63\u5728\u5339\u914D\u6307\u5B9A\u7684 tabid: ${tabid}...`));
408
- selectedTarget = pageTargets.find((t) => {
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
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
426
- url = "https://" + url;
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
- program.name("webmcp-cli").description("WebMCP CLI for interacting with browser via CDP").version("1.0.0").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) => {
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
- const msg = error instanceof Error ? error.message : String(error);
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
- const msg = error instanceof Error ? error.message : String(error);
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("open <url>").description("\u5728\u5F53\u524D\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u6307\u5B9A\u7F51\u9875").option("-t, --tabid <id>", "\u5728\u6307\u5B9A\u9875\u7B7E\u4E2D\u6253\u5F00").option("-n, --new-tab", "\u5728\u4E00\u4E2A\u5168\u65B0\u7684\u9875\u7B7E\u4E2D\u6253\u5F00").action(async (url, options) => {
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 openCommand(url, {
480
- tabid: parseTabId(options.tabid),
481
- newTab: options.newTab
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
- const msg = error instanceof Error ? error.message : String(error);
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);