@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 +10 -6
- package/dist/bin.cjs +238 -74
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +238 -74
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +19 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +19 -13
- package/dist/index.js.map +1 -1
- 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);
|
|
@@ -280,9 +280,8 @@ async function getPageTargetId(page) {
|
|
|
280
280
|
});
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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/
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
555
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
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);
|