@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 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. `open` 命令
103
+ ### 3. `tabs` 命令
104
104
 
105
- 在浏览器中打开指定的网页,并且可以选择是在当前页签导航,还是开启全新页签。
105
+ 管理浏览器标签页。
106
106
 
107
107
  **用法:**
108
108
  ```bash
109
- webmcp-cli open <url>
110
- webmcp-cli open <url> -t <tabid>
111
- webmcp-cli open <url> -n # 在新页签中打开
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" -n
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 import_picocolors3 = __toESM(require("picocolors"), 1);
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 fetch(url);
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 import_puppeteer_core.default.connect({
119
- browserURL: `http://127.0.0.1:${CDP_PORT}`,
120
- defaultViewport: null,
121
- targetFilter
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 import_puppeteer_core.default.connect({
129
- browserURL: `http://localhost:${CDP_PORT}`,
130
- defaultViewport: null,
131
- targetFilter
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 import_puppeteer_core.default.connect({
142
- browserURL: `http://127.0.0.1:${CDP_PORT}`,
143
- defaultViewport: null,
144
- targetFilter
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 import_puppeteer_core.default.connect({
151
- browserURL: `http://localhost:${CDP_PORT}`,
152
- defaultViewport: null,
153
- targetFilter
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
- async function getTargetPage(browser, tabid) {
179
- const targets = browser.targets();
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
- for (const target of pageTargets) {
197
- const tid = typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
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 fetch(url);
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
- const tid = typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
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 tabs = await Promise.all(pages.map(async (p) => {
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: tabs.filter(Boolean)
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/open.ts
408
- var import_picocolors2 = __toESM(require("picocolors"), 1);
409
- async function openCommand(url, { tabid, newTab }) {
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
- let page;
413
- if (newTab) {
414
- console.log(import_picocolors2.default.yellow("openCommand: \u6B63\u5728\u521B\u5EFA\u65B0\u9875\u9762..."));
415
- page = await browser.newPage();
416
- } else {
417
- console.log(import_picocolors2.default.yellow("openCommand: \u83B7\u53D6\u6D4F\u89C8\u5668 targets..."));
418
- const targets = browser.targets();
419
- const pageTargets = targets.filter((t) => {
420
- try {
421
- const type = (typeof t.type === "function" ? t.type() : t.type) || "";
422
- const urlStr = (typeof t.url === "function" ? t.url() : t.url) || "";
423
- return type === "page" && !urlStr.startsWith("devtools://");
424
- } catch {
425
- return false;
426
- }
427
- });
428
- console.log(import_picocolors2.default.yellow(`openCommand: \u666E\u901A\u9875\u9762 targets \u6570\u91CF: ${pageTargets.length}`));
429
- let selectedTarget;
430
- if (tabid !== void 0) {
431
- console.log(import_picocolors2.default.yellow(`openCommand: \u6B63\u5728\u5339\u914D\u6307\u5B9A\u7684 tabid: ${tabid}...`));
432
- selectedTarget = pageTargets.find((t) => {
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
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
450
- url = "https://" + url;
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
- 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) => {
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
- const msg = error instanceof Error ? error.message : String(error);
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
- const msg = error instanceof Error ? error.message : String(error);
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("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) => {
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 openCommand(url, {
504
- tabid: parseTabId(options.tabid),
505
- newTab: options.newTab
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
- const msg = error instanceof Error ? error.message : String(error);
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);