@opentiny/webmcp-cli 0.0.1-alpha.1 → 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);
@@ -280,9 +280,8 @@ async function getPageTargetId(page) {
280
280
  });
281
281
  }
282
282
  }
283
- async function getTargetPage(browser, tabid) {
284
- const targets = browser.targets();
285
- const pageTargets = targets.filter((t) => {
283
+ function getPageTargets(browser) {
284
+ return browser.targets().filter((t) => {
286
285
  try {
287
286
  const type = (typeof t.type === "function" ? t.type() : t.type) || "";
288
287
  const url = (typeof t.url === "function" ? t.url() : t.url) || "";
@@ -291,6 +290,41 @@ async function getTargetPage(browser, tabid) {
291
290
  return false;
292
291
  }
293
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);
294
328
  if (pageTargets.length === 0) {
295
329
  const newPage = await browser.newPage();
296
330
  await injectWebMCPPolyfillAndTools(newPage);
@@ -298,16 +332,11 @@ async function getTargetPage(browser, tabid) {
298
332
  }
299
333
  let targetPage = null;
300
334
  if (tabid !== void 0) {
301
- for (const target of pageTargets) {
302
- const tid = typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
303
- if (tid === tabid || tid.includes(tabid)) {
304
- targetPage = await target.page();
305
- break;
306
- }
307
- }
308
- if (!targetPage) {
335
+ const target = findPageTargetByTabId(browser, tabid);
336
+ if (!target) {
309
337
  throw new Error(`Tab with targetId "${tabid}" not found.`);
310
338
  }
339
+ targetPage = await target.page();
311
340
  } else {
312
341
  try {
313
342
  const urls = [
@@ -331,8 +360,7 @@ async function getTargetPage(browser, tabid) {
331
360
  }
332
361
  if (activeTargetId) {
333
362
  for (const target of pageTargets) {
334
- const tid = typeof target._getTargetInfo === "function" ? target._getTargetInfo().targetId : target._targetId || target.targetId || "";
335
- if (tid === activeTargetId) {
363
+ if (getTargetIdFromTarget(target) === activeTargetId) {
336
364
  targetPage = await target.page();
337
365
  break;
338
366
  }
@@ -452,7 +480,7 @@ async function stateCommand({ tabid }) {
452
480
  };
453
481
  });
454
482
  const pages = await browser.pages();
455
- const tabs = await Promise.all(pages.map(async (p) => {
483
+ const tabs2 = await Promise.all(pages.map(async (p) => {
456
484
  const pUrl = p.url();
457
485
  if (pUrl.startsWith("devtools://")) return null;
458
486
  const pTitle = await Promise.race([
@@ -468,7 +496,7 @@ async function stateCommand({ tabid }) {
468
496
  }));
469
497
  return {
470
498
  ...state,
471
- tabs: tabs.filter(Boolean)
499
+ tabs: tabs2.filter(Boolean)
472
500
  };
473
501
  } finally {
474
502
  await browser.disconnect();
@@ -509,56 +537,110 @@ async function runCommand({
509
537
  }
510
538
  }
511
539
 
512
- // src/commands/open.ts
513
- var import_picocolors2 = __toESM(require("picocolors"), 1);
514
- 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) {
515
548
  const browser = await connectBrowser();
516
549
  try {
517
- let page;
518
- if (newTab) {
519
- console.log(import_picocolors2.default.yellow("openCommand: \u6B63\u5728\u521B\u5EFA\u65B0\u9875\u9762..."));
520
- page = await browser.newPage();
521
- } else {
522
- console.log(import_picocolors2.default.yellow("openCommand: \u83B7\u53D6\u6D4F\u89C8\u5668 targets..."));
523
- const targets = browser.targets();
524
- const pageTargets = targets.filter((t) => {
525
- try {
526
- const type = (typeof t.type === "function" ? t.type() : t.type) || "";
527
- const urlStr = (typeof t.url === "function" ? t.url() : t.url) || "";
528
- return type === "page" && !urlStr.startsWith("devtools://");
529
- } catch {
530
- return false;
531
- }
532
- });
533
- console.log(import_picocolors2.default.yellow(`openCommand: \u666E\u901A\u9875\u9762 targets \u6570\u91CF: ${pageTargets.length}`));
534
- let selectedTarget;
535
- if (tabid !== void 0) {
536
- console.log(import_picocolors2.default.yellow(`openCommand: \u6B63\u5728\u5339\u914D\u6307\u5B9A\u7684 tabid: ${tabid}...`));
537
- selectedTarget = pageTargets.find((t) => {
538
- const tid = typeof t._getTargetInfo === "function" ? t._getTargetInfo().targetId : t._targetId || t.targetId || "";
539
- return tid === tabid || tid.includes(tabid);
540
- });
541
- }
542
- if (selectedTarget) {
543
- console.log(import_picocolors2.default.yellow("openCommand: \u6B63\u5728\u5C06\u5339\u914D\u7684 target \u8F6C\u6362\u4E3A page..."));
544
- page = await selectedTarget.page();
545
- } else if (pageTargets.length > 0) {
546
- console.log(import_picocolors2.default.yellow("openCommand: \u6B63\u5728\u5C06\u6700\u540E\u4E00\u4E2A target \u8F6C\u6362\u4E3A page..."));
547
- page = await pageTargets[pageTargets.length - 1].page();
548
- }
549
- if (!page) {
550
- console.log(import_picocolors2.default.yellow("openCommand: \u6CA1\u6709\u627E\u5230\u53EF\u7528 page\uFF0C\u6B63\u5728\u521B\u5EFA\u65B0\u9875\u9762..."));
551
- page = await browser.newPage();
552
- }
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.`);
553
571
  }
554
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
555
- 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
+ };
556
614
  }
557
- console.log(import_picocolors2.default.cyan(`\u6B63\u5728\u6253\u5F00: ${url}`));
558
- await page.goto(url, { waitUntil: "domcontentloaded" });
559
615
  await injectIntoPage(page);
560
616
  return {
561
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
+ };
639
+ }
640
+ await injectIntoPage(page);
641
+ return {
642
+ success: true,
643
+ tabid: await getPageTargetId(page),
562
644
  url: page.url(),
563
645
  title: await page.title()
564
646
  };
@@ -567,13 +649,71 @@ async function openCommand(url, { tabid, newTab }) {
567
649
  }
568
650
  }
569
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
+
570
705
  // src/bin.ts
571
706
  var program = new import_commander.Command();
572
707
  function parseTabId(id) {
573
708
  if (!id) return void 0;
574
709
  return id;
575
710
  }
576
- 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) => {
577
717
  const opts = thisCommand.opts();
578
718
  if (opts.workspace) {
579
719
  process.env.WEBMCP_WORKSPACE = opts.workspace;
@@ -584,9 +724,7 @@ program.command("state").description("\u83B7\u53D6\u6D4F\u89C8\u5668\u5F53\u524D
584
724
  const result = await stateCommand({ tabid: parseTabId(options.tabid) });
585
725
  console.log(JSON.stringify(result, null, 2));
586
726
  } catch (error) {
587
- const msg = error instanceof Error ? error.message : String(error);
588
- console.error(import_picocolors3.default.red(`Error executing state command: ${msg}`));
589
- process.exit(1);
727
+ handleCommandError(error, "state");
590
728
  }
591
729
  });
592
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) => {
@@ -598,22 +736,48 @@ program.command("run <toolName> <argsJson>").description("\u5411\u6307\u5B9A\u98
598
736
  });
599
737
  console.log(JSON.stringify(result, null, 2));
600
738
  } catch (error) {
601
- const msg = error instanceof Error ? error.message : String(error);
602
- console.error(import_picocolors3.default.red(`Error executing run command: ${msg}`));
603
- process.exit(1);
739
+ handleCommandError(error, "run");
604
740
  }
605
741
  });
606
- 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) => {
607
744
  try {
608
- const result = await openCommand(url, {
609
- tabid: parseTabId(options.tabid),
610
- newTab: options.newTab
611
- });
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));
612
778
  console.log(JSON.stringify(result, null, 2));
613
779
  } catch (error) {
614
- const msg = error instanceof Error ? error.message : String(error);
615
- console.error(import_picocolors3.default.red(`Error executing open command: ${msg}`));
616
- process.exit(1);
780
+ handleCommandError(error, "tabs forward");
617
781
  }
618
782
  });
619
783
  program.parse(process.argv);