@jackwener/opencli 1.7.15 → 1.7.16
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 +9 -6
- package/README.zh-CN.md +9 -6
- package/cli-manifest.json +161 -31
- package/clis/chatgpt/ask.js +2 -1
- package/clis/chatgpt/detail.js +6 -1
- package/clis/chatgpt/read.js +2 -1
- package/clis/chatgpt/send.js +2 -1
- package/clis/chatgpt/utils.js +54 -12
- package/clis/chatgpt/utils.test.js +36 -1
- package/clis/claude/ask.js +22 -7
- package/clis/claude/detail.js +9 -2
- package/clis/claude/new.js +8 -2
- package/clis/claude/read.js +2 -1
- package/clis/claude/send.js +8 -3
- package/clis/claude/utils.js +27 -4
- package/clis/deepseek/ask.js +21 -8
- package/clis/deepseek/detail.js +9 -1
- package/clis/deepseek/new.js +13 -2
- package/clis/deepseek/read.js +2 -1
- package/clis/deepseek/utils.js +8 -1
- package/clis/linkedin/search.js +8 -11
- package/clis/maimai/search-talents.js +10 -6
- package/clis/openreview/author.js +58 -0
- package/clis/openreview/openreview.test.js +83 -1
- package/clis/openreview/utils.js +14 -0
- package/clis/reddit/comment.js +1 -0
- package/clis/reddit/frontpage.js +1 -0
- package/clis/reddit/popular.js +1 -0
- package/clis/reddit/read.js +2 -0
- package/clis/reddit/read.test.js +4 -0
- package/clis/reddit/save.js +1 -0
- package/clis/reddit/saved.js +1 -0
- package/clis/reddit/search.js +1 -0
- package/clis/reddit/subreddit.js +1 -0
- package/clis/reddit/subscribe.js +1 -0
- package/clis/reddit/upvote.js +1 -0
- package/clis/reddit/upvoted.js +1 -0
- package/clis/reddit/user-comments.js +1 -0
- package/clis/reddit/user-posts.js +1 -0
- package/clis/reddit/user.js +1 -0
- package/clis/twitter/article.js +7 -4
- package/clis/twitter/bookmark-folder.js +3 -5
- package/clis/twitter/bookmark-folder.test.js +5 -2
- package/clis/twitter/bookmark-folders.js +3 -5
- package/clis/twitter/bookmark-folders.test.js +3 -1
- package/clis/twitter/bookmarks.js +3 -5
- package/clis/twitter/download.js +1 -0
- package/clis/twitter/followers.js +1 -0
- package/clis/twitter/following.js +3 -6
- package/clis/twitter/following.test.js +2 -1
- package/clis/twitter/likes.js +3 -5
- package/clis/twitter/list-add.js +4 -3
- package/clis/twitter/list-add.test.js +23 -1
- package/clis/twitter/list-remove.js +4 -3
- package/clis/twitter/list-remove.test.js +23 -1
- package/clis/twitter/list-tweets.js +3 -5
- package/clis/twitter/lists.js +3 -5
- package/clis/twitter/notifications.js +1 -0
- package/clis/twitter/profile.js +7 -4
- package/clis/twitter/search.js +1 -0
- package/clis/twitter/thread.js +5 -7
- package/clis/twitter/timeline.js +5 -7
- package/clis/twitter/trending.js +4 -4
- package/clis/twitter/tweets.js +3 -6
- package/clis/youtube/like.js +6 -2
- package/clis/youtube/subscribe.js +6 -2
- package/clis/youtube/unlike.js +6 -2
- package/clis/youtube/unsubscribe.js +6 -2
- package/clis/youtube/utils.js +19 -13
- package/clis/youtube/utils.test.js +17 -1
- package/dist/src/browser/bridge.d.ts +1 -0
- package/dist/src/browser/bridge.js +1 -1
- package/dist/src/browser/cdp.d.ts +1 -0
- package/dist/src/browser/daemon-client.d.ts +2 -2
- package/dist/src/browser/daemon-client.js +6 -3
- package/dist/src/browser/daemon-client.test.js +10 -0
- package/dist/src/browser/page.d.ts +2 -1
- package/dist/src/browser/page.js +5 -1
- package/dist/src/cli.js +70 -2
- package/dist/src/cli.test.js +139 -7
- package/dist/src/commanderAdapter.js +7 -0
- package/dist/src/doctor.js +2 -2
- package/dist/src/doctor.test.js +4 -4
- package/dist/src/execution.d.ts +2 -0
- package/dist/src/execution.js +31 -6
- package/dist/src/execution.test.js +43 -16
- package/dist/src/external-clis.yaml +24 -0
- package/dist/src/help.d.ts +1 -0
- package/dist/src/help.js +29 -0
- package/dist/src/main.js +4 -14
- package/dist/src/runtime.d.ts +3 -0
- package/dist/src/runtime.js +1 -0
- package/dist/src/types.d.ts +1 -1
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -313,7 +313,7 @@ async function resolveStoredBrowserTarget(page, scope = DEFAULT_BROWSER_WORKSPAC
|
|
|
313
313
|
return resolveBrowserTargetInSession(page, defaultPage, { scope, source: 'saved' });
|
|
314
314
|
}
|
|
315
315
|
/** Create a browser page for browser commands. Uses a dedicated browser workspace for session persistence. */
|
|
316
|
-
async function getBrowserPage(targetPage, workspace = DEFAULT_BROWSER_WORKSPACE, contextId) {
|
|
316
|
+
async function getBrowserPage(targetPage, workspace = DEFAULT_BROWSER_WORKSPACE, contextId, opts = {}) {
|
|
317
317
|
const { BrowserBridge } = await import('./browser/index.js');
|
|
318
318
|
const bridge = new BrowserBridge();
|
|
319
319
|
// Idle timeout: how long the browser workspace lease stays alive between commands
|
|
@@ -325,6 +325,7 @@ async function getBrowserPage(targetPage, workspace = DEFAULT_BROWSER_WORKSPACE,
|
|
|
325
325
|
workspace,
|
|
326
326
|
...(contextId && { contextId }),
|
|
327
327
|
...(idleTimeout && idleTimeout > 0 && { idleTimeout }),
|
|
328
|
+
windowMode: opts.windowMode ?? getBrowserWindowMode(undefined, 'foreground'),
|
|
328
329
|
});
|
|
329
330
|
const targetScope = getBrowserScope(workspace, contextId);
|
|
330
331
|
const resolvedTargetPage = targetPage
|
|
@@ -338,6 +339,43 @@ async function getBrowserPage(targetPage, workspace = DEFAULT_BROWSER_WORKSPACE,
|
|
|
338
339
|
}
|
|
339
340
|
return page;
|
|
340
341
|
}
|
|
342
|
+
function getBrowserWindowMode(command, defaultMode) {
|
|
343
|
+
const optionRaw = getCommandOption(command, 'window');
|
|
344
|
+
if (optionRaw !== undefined && optionRaw !== '') {
|
|
345
|
+
if (optionRaw === 'foreground' || optionRaw === 'background')
|
|
346
|
+
return optionRaw;
|
|
347
|
+
throw new Error(`--window must be one of: foreground, background. Received: "${String(optionRaw)}"`);
|
|
348
|
+
}
|
|
349
|
+
const envRaw = process.env.OPENCLI_WINDOW;
|
|
350
|
+
if (envRaw !== undefined && envRaw !== '') {
|
|
351
|
+
if (envRaw === 'foreground' || envRaw === 'background')
|
|
352
|
+
return envRaw;
|
|
353
|
+
throw new Error(`OPENCLI_WINDOW must be one of: foreground, background. Received: "${envRaw}"`);
|
|
354
|
+
}
|
|
355
|
+
return defaultMode;
|
|
356
|
+
}
|
|
357
|
+
function parseBrowserBoolean(name, raw) {
|
|
358
|
+
if (raw === undefined || raw === '')
|
|
359
|
+
return undefined;
|
|
360
|
+
if (raw === 'true')
|
|
361
|
+
return true;
|
|
362
|
+
if (raw === 'false')
|
|
363
|
+
return false;
|
|
364
|
+
throw new Error(`${name} must be one of: true, false. Received: "${String(raw)}"`);
|
|
365
|
+
}
|
|
366
|
+
function getBrowserKeepTab(command, defaultValue) {
|
|
367
|
+
return parseBrowserBoolean('--keep-tab', getCommandOption(command, 'keepTab'))
|
|
368
|
+
?? parseBrowserBoolean('OPENCLI_KEEP_TAB', process.env.OPENCLI_KEEP_TAB)
|
|
369
|
+
?? defaultValue;
|
|
370
|
+
}
|
|
371
|
+
function hasExplicitBrowserWindowOption(command) {
|
|
372
|
+
const raw = getCommandOption(command, 'window');
|
|
373
|
+
return raw !== undefined && raw !== '';
|
|
374
|
+
}
|
|
375
|
+
function hasExplicitBrowserKeepTabOption(command) {
|
|
376
|
+
const raw = getCommandOption(command, 'keepTab');
|
|
377
|
+
return raw !== undefined && raw !== '';
|
|
378
|
+
}
|
|
341
379
|
function addBrowserTabOption(command) {
|
|
342
380
|
return command.option('--tab <targetId>', BROWSER_TAB_OPTION_DESCRIPTION);
|
|
343
381
|
}
|
|
@@ -583,6 +621,8 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
583
621
|
const browser = program
|
|
584
622
|
.command('browser')
|
|
585
623
|
.option('--workspace <name>', 'Browser workspace to use (default: browser:default; bound tabs use bound:<name>)')
|
|
624
|
+
.option('--window <mode>', 'Browser window mode: foreground or background')
|
|
625
|
+
.option('--keep-tab <bool>', 'Keep the browser tab lease after the command finishes')
|
|
586
626
|
.description('Browser control — navigate, click, type, extract, wait (no LLM needed)');
|
|
587
627
|
const originalBrowserDescription = browser.description();
|
|
588
628
|
/**
|
|
@@ -646,12 +686,20 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
646
686
|
/** Wrap browser actions with error handling and optional --json output */
|
|
647
687
|
function browserAction(fn) {
|
|
648
688
|
return async (...args) => {
|
|
689
|
+
let page = null;
|
|
690
|
+
let shouldReleasePage = false;
|
|
649
691
|
try {
|
|
650
692
|
const command = args.at(-1) instanceof Command ? args.at(-1) : undefined;
|
|
651
693
|
const targetPage = getBrowserTargetId(command);
|
|
652
694
|
const workspace = getBrowserWorkspace(command);
|
|
653
695
|
const contextId = getBrowserContextId(command);
|
|
654
|
-
const
|
|
696
|
+
const windowMode = getBrowserWindowMode(command, 'foreground');
|
|
697
|
+
const keepTab = getBrowserKeepTab(command, true);
|
|
698
|
+
shouldReleasePage = !keepTab && !workspace.startsWith('bound:');
|
|
699
|
+
if (workspace.startsWith('bound:') && (hasExplicitBrowserWindowOption(command) || hasExplicitBrowserKeepTabOption(command))) {
|
|
700
|
+
log.warn('--window/--keep-tab ignored for bound:* workspaces; bound tabs are user-owned.');
|
|
701
|
+
}
|
|
702
|
+
page = await getBrowserPage(targetPage, workspace, contextId, { windowMode });
|
|
655
703
|
await fn(page, ...args);
|
|
656
704
|
}
|
|
657
705
|
catch (err) {
|
|
@@ -699,6 +747,14 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
699
747
|
}
|
|
700
748
|
process.exitCode = EXIT_CODES.GENERIC_ERROR;
|
|
701
749
|
}
|
|
750
|
+
finally {
|
|
751
|
+
if (shouldReleasePage && page?.closeWindow) {
|
|
752
|
+
await page.closeWindow().catch((err) => {
|
|
753
|
+
if (process.env.OPENCLI_VERBOSE)
|
|
754
|
+
log.warn(`[browser] Failed to release tab lease: ${getErrorMessage(err)}`);
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
}
|
|
702
758
|
};
|
|
703
759
|
}
|
|
704
760
|
browser.command('bind')
|
|
@@ -2611,6 +2667,8 @@ cli({
|
|
|
2611
2667
|
});
|
|
2612
2668
|
// ── Plugin management ──────────────────────────────────────────────────────
|
|
2613
2669
|
const pluginCmd = program.command('plugin').description('Manage opencli plugins');
|
|
2670
|
+
// Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing.
|
|
2671
|
+
const originalPluginDescription = pluginCmd.description();
|
|
2614
2672
|
pluginCmd
|
|
2615
2673
|
.command('install')
|
|
2616
2674
|
.description('Install a plugin from a git repository')
|
|
@@ -2797,6 +2855,8 @@ cli({
|
|
|
2797
2855
|
});
|
|
2798
2856
|
// ── Built-in: adapter management ─────────────────────────────────────────
|
|
2799
2857
|
const adapterCmd = program.command('adapter').description('Manage CLI adapters');
|
|
2858
|
+
// Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing.
|
|
2859
|
+
const originalAdapterDescription = adapterCmd.description();
|
|
2800
2860
|
adapterCmd
|
|
2801
2861
|
.command('status')
|
|
2802
2862
|
.description('Show which sites have local overrides vs using official baseline')
|
|
@@ -2905,6 +2965,8 @@ cli({
|
|
|
2905
2965
|
});
|
|
2906
2966
|
// ── Built-in: browser profile selection ──────────────────────────────────
|
|
2907
2967
|
const profileCmd = program.command('profile').description('Manage Browser Bridge Chrome profiles');
|
|
2968
|
+
// Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing.
|
|
2969
|
+
const originalProfileDescription = profileCmd.description();
|
|
2908
2970
|
profileCmd
|
|
2909
2971
|
.command('list')
|
|
2910
2972
|
.description('List Chrome profiles connected through the Browser Bridge extension')
|
|
@@ -2982,6 +3044,8 @@ cli({
|
|
|
2982
3044
|
});
|
|
2983
3045
|
// ── Built-in: daemon ──────────────────────────────────────────────────────
|
|
2984
3046
|
const daemonCmd = program.command('daemon').description('Manage the opencli daemon');
|
|
3047
|
+
// Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing.
|
|
3048
|
+
const originalDaemonDescription = daemonCmd.description();
|
|
2985
3049
|
daemonCmd
|
|
2986
3050
|
.command('status')
|
|
2987
3051
|
.description('Show daemon status')
|
|
@@ -3107,6 +3171,10 @@ cli({
|
|
|
3107
3171
|
const adapterGroups = { external: externalNames, apps, sites };
|
|
3108
3172
|
const adapterNameSet = new Set([...externalNames, ...siteNames]);
|
|
3109
3173
|
installCommanderNamespaceStructuredHelp(browser, { globalCommand: program, description: originalBrowserDescription });
|
|
3174
|
+
installCommanderNamespaceStructuredHelp(daemonCmd, { globalCommand: program, description: originalDaemonDescription });
|
|
3175
|
+
installCommanderNamespaceStructuredHelp(pluginCmd, { globalCommand: program, description: originalPluginDescription });
|
|
3176
|
+
installCommanderNamespaceStructuredHelp(adapterCmd, { globalCommand: program, description: originalAdapterDescription });
|
|
3177
|
+
installCommanderNamespaceStructuredHelp(profileCmd, { globalCommand: program, description: originalProfileDescription });
|
|
3110
3178
|
program.configureHelp({
|
|
3111
3179
|
visibleCommands: (command) => command.commands.filter(child => command !== program || !adapterNameSet.has(child.name())),
|
|
3112
3180
|
});
|
package/dist/src/cli.test.js
CHANGED
|
@@ -349,13 +349,23 @@ describe('createProgram root help descriptions', () => {
|
|
|
349
349
|
expect(data.command).toBe('opencli browser');
|
|
350
350
|
expect(data.description).toBe('Browser control — navigate, click, type, extract, wait (no LLM needed)');
|
|
351
351
|
expect(data.command_count).toBeGreaterThan(20);
|
|
352
|
-
expect(data.namespace_options).
|
|
353
|
-
{
|
|
352
|
+
expect(data.namespace_options).toEqual(expect.arrayContaining([
|
|
353
|
+
expect.objectContaining({
|
|
354
354
|
name: 'workspace',
|
|
355
355
|
flags: '--workspace <name>',
|
|
356
356
|
takes_value: 'required',
|
|
357
|
-
},
|
|
358
|
-
|
|
357
|
+
}),
|
|
358
|
+
expect.objectContaining({
|
|
359
|
+
name: 'window',
|
|
360
|
+
flags: '--window <mode>',
|
|
361
|
+
takes_value: 'required',
|
|
362
|
+
}),
|
|
363
|
+
expect.objectContaining({
|
|
364
|
+
name: 'keepTab',
|
|
365
|
+
flags: '--keep-tab <bool>',
|
|
366
|
+
takes_value: 'required',
|
|
367
|
+
}),
|
|
368
|
+
]));
|
|
359
369
|
expect(data.global_options).toEqual(expect.arrayContaining([
|
|
360
370
|
expect.objectContaining({
|
|
361
371
|
name: 'version',
|
|
@@ -421,7 +431,7 @@ describe('createProgram root help descriptions', () => {
|
|
|
421
431
|
usage: 'opencli browser tab close [targetId] [options]',
|
|
422
432
|
positionals: [{ name: 'targetId', help: 'Target tab/page identity returned by "browser open", "browser tab new", or "browser tab list"' }],
|
|
423
433
|
});
|
|
424
|
-
expect(data.namespace_options.map((option) => option.name)).toEqual(['workspace']);
|
|
434
|
+
expect(data.namespace_options.map((option) => option.name)).toEqual(['workspace', 'window', 'keepTab']);
|
|
425
435
|
expect(data.structured_help).toMatchObject({
|
|
426
436
|
usage: 'opencli browser tab --help -f yaml',
|
|
427
437
|
});
|
|
@@ -450,13 +460,113 @@ describe('createProgram root help descriptions', () => {
|
|
|
450
460
|
},
|
|
451
461
|
});
|
|
452
462
|
expect(data.command_options.map((option) => option.name)).toEqual(['role', 'name', 'label', 'text', 'testid', 'nth', 'tab']);
|
|
453
|
-
expect(data.namespace_options.map((option) => option.name)).toEqual(['workspace']);
|
|
463
|
+
expect(data.namespace_options.map((option) => option.name)).toEqual(['workspace', 'window', 'keepTab']);
|
|
454
464
|
expect(data.global_options.map((option) => option.name)).toContain('profile');
|
|
455
465
|
}
|
|
456
466
|
finally {
|
|
457
467
|
process.argv = argv;
|
|
458
468
|
}
|
|
459
469
|
});
|
|
470
|
+
it('renders daemon namespace structured help with leaves and global options', () => {
|
|
471
|
+
const argv = process.argv;
|
|
472
|
+
try {
|
|
473
|
+
const program = createProgram('', '');
|
|
474
|
+
const daemon = program.commands.find(cmd => cmd.name() === 'daemon');
|
|
475
|
+
expect(daemon).toBeTruthy();
|
|
476
|
+
process.argv = ['node', 'opencli', 'daemon', '--help', '-f', 'yaml'];
|
|
477
|
+
const data = yaml.load(daemon.helpInformation());
|
|
478
|
+
expect(data).toMatchObject({
|
|
479
|
+
namespace: 'daemon',
|
|
480
|
+
command: 'opencli daemon',
|
|
481
|
+
usage: 'opencli daemon <command> [args] [options]',
|
|
482
|
+
description: 'Manage the opencli daemon',
|
|
483
|
+
command_count: 3,
|
|
484
|
+
namespace_options: [],
|
|
485
|
+
structured_help: { usage: 'opencli daemon --help -f yaml' },
|
|
486
|
+
});
|
|
487
|
+
expect(data.commands.map((cmd) => cmd.name)).toEqual(['restart', 'status', 'stop']);
|
|
488
|
+
expect(data.global_options.map((option) => option.name)).toEqual(expect.arrayContaining(['version', 'profile']));
|
|
489
|
+
}
|
|
490
|
+
finally {
|
|
491
|
+
process.argv = argv;
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
it('renders plugin namespace structured help with positional + option leaves', () => {
|
|
495
|
+
const argv = process.argv;
|
|
496
|
+
try {
|
|
497
|
+
const program = createProgram('', '');
|
|
498
|
+
const plugin = program.commands.find(cmd => cmd.name() === 'plugin');
|
|
499
|
+
expect(plugin).toBeTruthy();
|
|
500
|
+
process.argv = ['node', 'opencli', 'plugin', '--help', '-f', 'yaml'];
|
|
501
|
+
const data = yaml.load(plugin.helpInformation());
|
|
502
|
+
expect(data).toMatchObject({
|
|
503
|
+
namespace: 'plugin',
|
|
504
|
+
command: 'opencli plugin',
|
|
505
|
+
description: 'Manage opencli plugins',
|
|
506
|
+
namespace_options: [],
|
|
507
|
+
});
|
|
508
|
+
expect(data.commands.map((cmd) => cmd.name)).toEqual(['create', 'install', 'list', 'uninstall', 'update']);
|
|
509
|
+
const update = data.commands.find((cmd) => cmd.name === 'update');
|
|
510
|
+
expect(update).toMatchObject({
|
|
511
|
+
usage: 'opencli plugin update [name] [options]',
|
|
512
|
+
positionals: [{ name: 'name' }],
|
|
513
|
+
});
|
|
514
|
+
expect(update.command_options.map((option) => option.name)).toEqual(['all']);
|
|
515
|
+
}
|
|
516
|
+
finally {
|
|
517
|
+
process.argv = argv;
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
it('renders adapter namespace structured help preserving original description after applyRootSubcommandSummaries', () => {
|
|
521
|
+
const argv = process.argv;
|
|
522
|
+
try {
|
|
523
|
+
const program = createProgram('', '');
|
|
524
|
+
const adapter = program.commands.find(cmd => cmd.name() === 'adapter');
|
|
525
|
+
expect(adapter).toBeTruthy();
|
|
526
|
+
process.argv = ['node', 'opencli', 'adapter', '--help', '-f', 'yaml'];
|
|
527
|
+
const data = yaml.load(adapter.helpInformation());
|
|
528
|
+
// applyRootSubcommandSummaries() rewrites .description() to a child-name listing;
|
|
529
|
+
// structured help must surface the original product description via the snapshot.
|
|
530
|
+
expect(data.description).toBe('Manage CLI adapters');
|
|
531
|
+
expect(data.commands.map((cmd) => cmd.name)).toEqual(['eject', 'reset', 'status']);
|
|
532
|
+
const reset = data.commands.find((cmd) => cmd.name === 'reset');
|
|
533
|
+
expect(reset).toMatchObject({
|
|
534
|
+
usage: 'opencli adapter reset [site] [options]',
|
|
535
|
+
positionals: [{ name: 'site' }],
|
|
536
|
+
});
|
|
537
|
+
expect(reset.command_options.map((option) => option.name)).toEqual(['all']);
|
|
538
|
+
}
|
|
539
|
+
finally {
|
|
540
|
+
process.argv = argv;
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
it('renders profile namespace structured help including required positionals', () => {
|
|
544
|
+
const argv = process.argv;
|
|
545
|
+
try {
|
|
546
|
+
const program = createProgram('', '');
|
|
547
|
+
const profile = program.commands.find(cmd => cmd.name() === 'profile');
|
|
548
|
+
expect(profile).toBeTruthy();
|
|
549
|
+
process.argv = ['node', 'opencli', 'profile', '--help', '-f', 'yaml'];
|
|
550
|
+
const data = yaml.load(profile.helpInformation());
|
|
551
|
+
expect(data).toMatchObject({
|
|
552
|
+
namespace: 'profile',
|
|
553
|
+
description: 'Manage Browser Bridge Chrome profiles',
|
|
554
|
+
command_count: 3,
|
|
555
|
+
});
|
|
556
|
+
expect(data.commands.map((cmd) => cmd.name)).toEqual(['list', 'rename', 'use']);
|
|
557
|
+
const rename = data.commands.find((cmd) => cmd.name === 'rename');
|
|
558
|
+
expect(rename).toMatchObject({
|
|
559
|
+
usage: 'opencli profile rename <contextId> <alias> [options]',
|
|
560
|
+
positionals: [
|
|
561
|
+
{ name: 'contextId', required: true },
|
|
562
|
+
{ name: 'alias', required: true },
|
|
563
|
+
],
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
finally {
|
|
567
|
+
process.argv = argv;
|
|
568
|
+
}
|
|
569
|
+
});
|
|
460
570
|
});
|
|
461
571
|
describe('resolveBrowserVerifyInvocation', () => {
|
|
462
572
|
it('prefers the built entry declared in package metadata', () => {
|
|
@@ -727,6 +837,8 @@ describe('browser tab targeting commands', () => {
|
|
|
727
837
|
stderrSpy.mockClear();
|
|
728
838
|
mockBrowserConnect.mockClear();
|
|
729
839
|
mockBrowserClose.mockReset().mockResolvedValue(undefined);
|
|
840
|
+
delete process.env.OPENCLI_WINDOW;
|
|
841
|
+
delete process.env.OPENCLI_KEEP_TAB;
|
|
730
842
|
mockBindTab.mockReset().mockResolvedValue({
|
|
731
843
|
workspace: 'bound:default',
|
|
732
844
|
page: 'tab-2',
|
|
@@ -759,6 +871,7 @@ describe('browser tab targeting commands', () => {
|
|
|
759
871
|
screenshot: vi.fn().mockResolvedValue('base64-shot'),
|
|
760
872
|
annotatedScreenshot: vi.fn().mockResolvedValue('annotated-base64-shot'),
|
|
761
873
|
readNetworkCapture: vi.fn().mockResolvedValue([]),
|
|
874
|
+
closeWindow: vi.fn().mockResolvedValue(undefined),
|
|
762
875
|
waitForDownload: vi.fn().mockResolvedValue({
|
|
763
876
|
downloaded: true,
|
|
764
877
|
filename: '/tmp/receipt.pdf',
|
|
@@ -801,8 +914,27 @@ describe('browser tab targeting commands', () => {
|
|
|
801
914
|
it('runs browser commands against an explicit bound workspace', async () => {
|
|
802
915
|
const program = createProgram('', '');
|
|
803
916
|
await program.parseAsync(['node', 'opencli', 'browser', '--workspace', 'bound:default', 'state']);
|
|
804
|
-
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30, workspace: 'bound:default' });
|
|
917
|
+
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30, workspace: 'bound:default', windowMode: 'foreground' });
|
|
918
|
+
expect(browserState.page?.snapshot).toHaveBeenCalled();
|
|
919
|
+
});
|
|
920
|
+
it('passes browser --window through Commander options without relying on env pre-processing', async () => {
|
|
921
|
+
const program = createProgram('', '');
|
|
922
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--window', 'background', 'state']);
|
|
923
|
+
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30, workspace: 'browser:default', windowMode: 'background' });
|
|
924
|
+
expect(browserState.page?.snapshot).toHaveBeenCalled();
|
|
925
|
+
});
|
|
926
|
+
it('releases non-bound browser tab leases when --keep-tab=false', async () => {
|
|
927
|
+
const program = createProgram('', '');
|
|
928
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--keep-tab', 'false', 'state']);
|
|
929
|
+
expect(browserState.page?.snapshot).toHaveBeenCalled();
|
|
930
|
+
expect(browserState.page?.closeWindow).toHaveBeenCalled();
|
|
931
|
+
});
|
|
932
|
+
it('does not auto-release explicit bound workspaces when --keep-tab=false', async () => {
|
|
933
|
+
const program = createProgram('', '');
|
|
934
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--workspace', 'bound:default', '--keep-tab', 'false', 'state']);
|
|
805
935
|
expect(browserState.page?.snapshot).toHaveBeenCalled();
|
|
936
|
+
expect(browserState.page?.closeWindow).not.toHaveBeenCalled();
|
|
937
|
+
expect(stderrSpy.mock.calls.flat().join('')).toContain('--window/--keep-tab ignored for bound:* workspaces');
|
|
806
938
|
});
|
|
807
939
|
it('passes the opt-in AX source to browser state', async () => {
|
|
808
940
|
const program = createProgram('', '');
|
|
@@ -48,6 +48,11 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
48
48
|
.option('-f, --format <fmt>', 'Output format: table, plain, json, yaml, md, csv', 'table')
|
|
49
49
|
.option('--trace <mode>', 'Trace capture: off, on, retain-on-failure', 'off')
|
|
50
50
|
.option('-v, --verbose', 'Debug output', false);
|
|
51
|
+
if (cmd.browser) {
|
|
52
|
+
subCmd
|
|
53
|
+
.option('--window <mode>', 'Browser window mode: foreground or background')
|
|
54
|
+
.option('--keep-tab <bool>', 'Keep the browser tab lease after the command finishes');
|
|
55
|
+
}
|
|
51
56
|
const originalHelpInformation = subCmd.helpInformation.bind(subCmd);
|
|
52
57
|
subCmd.helpInformation = ((contextOptions) => {
|
|
53
58
|
const format = getRequestedHelpFormat();
|
|
@@ -102,6 +107,8 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
102
107
|
prepared: true,
|
|
103
108
|
...(typeof globals.profile === 'string' && globals.profile.trim() ? { profile: globals.profile.trim() } : {}),
|
|
104
109
|
...(typeof optionsRecord.trace === 'string' && optionsRecord.trace !== 'off' ? { trace: optionsRecord.trace } : {}),
|
|
110
|
+
...(cmd.browser && typeof optionsRecord.window === 'string' ? { windowMode: optionsRecord.window } : {}),
|
|
111
|
+
...(cmd.browser && typeof optionsRecord.keepTab === 'string' ? { keepTab: optionsRecord.keepTab } : {}),
|
|
105
112
|
});
|
|
106
113
|
if (result === null || result === undefined) {
|
|
107
114
|
return;
|
package/dist/src/doctor.js
CHANGED
|
@@ -271,8 +271,8 @@ export function renderBrowserDoctorReport(report) {
|
|
|
271
271
|
? `tab ${session.preferredTabId}`
|
|
272
272
|
: `window ${session.windowId ?? 'unknown'}`;
|
|
273
273
|
const mode = session.ownership ?? (session.owned === false ? 'borrowed' : 'owned');
|
|
274
|
-
const
|
|
275
|
-
lines.push(styleText('dim', ` • ${session.workspace ?? 'default'} → ${target}, mode=${mode}${
|
|
274
|
+
const windowRole = session.windowRole ? `, window=${session.windowRole}` : '';
|
|
275
|
+
lines.push(styleText('dim', ` • ${session.workspace ?? 'default'} → ${target}, mode=${mode}${windowRole}, tabs=${session.tabCount ?? 0}, idle=${idle}`));
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
}
|
package/dist/src/doctor.test.js
CHANGED
|
@@ -116,13 +116,13 @@ describe('doctor report rendering', () => {
|
|
|
116
116
|
windowId: 2,
|
|
117
117
|
preferredTabId: 42,
|
|
118
118
|
ownership: 'borrowed',
|
|
119
|
-
|
|
119
|
+
windowRole: 'borrowed-user',
|
|
120
120
|
tabCount: 1,
|
|
121
121
|
idleMsRemaining: null,
|
|
122
122
|
},
|
|
123
123
|
],
|
|
124
124
|
}));
|
|
125
|
-
expect(text).toContain('bound:default → tab 42, mode=borrowed,
|
|
125
|
+
expect(text).toContain('bound:default → tab 42, mode=borrowed, window=borrowed-user, tabs=1, idle=none');
|
|
126
126
|
});
|
|
127
127
|
it('renders connected profiles and groups sessions by profile', () => {
|
|
128
128
|
const text = strip(renderBrowserDoctorReport({
|
|
@@ -140,7 +140,7 @@ describe('doctor report rendering', () => {
|
|
|
140
140
|
windowId: 2,
|
|
141
141
|
preferredTabId: 42,
|
|
142
142
|
ownership: 'borrowed',
|
|
143
|
-
|
|
143
|
+
windowRole: 'borrowed-user',
|
|
144
144
|
tabCount: 1,
|
|
145
145
|
idleMsRemaining: null,
|
|
146
146
|
},
|
|
@@ -150,7 +150,7 @@ describe('doctor report rendering', () => {
|
|
|
150
150
|
windowId: 1,
|
|
151
151
|
preferredTabId: 10,
|
|
152
152
|
ownership: 'owned',
|
|
153
|
-
|
|
153
|
+
windowRole: 'automation',
|
|
154
154
|
tabCount: 1,
|
|
155
155
|
idleMsRemaining: 1000,
|
|
156
156
|
},
|
package/dist/src/execution.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export declare function executeCommand(cmd: CliCommand, rawKwargs: CommandArgs,
|
|
|
16
16
|
prepared?: boolean;
|
|
17
17
|
profile?: string;
|
|
18
18
|
trace?: string;
|
|
19
|
+
keepTab?: string;
|
|
20
|
+
windowMode?: string;
|
|
19
21
|
onTraceExport?: (trace: ObservationExportResult) => void;
|
|
20
22
|
}): Promise<unknown>;
|
|
21
23
|
export declare function prepareCommandArgs(cmd: CliCommand, rawKwargs: CommandArgs): CommandArgs;
|
package/dist/src/execution.js
CHANGED
|
@@ -215,6 +215,8 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
215
215
|
const browserReuse = resolveBrowserSessionReuse(cmd);
|
|
216
216
|
const workspace = resolveBrowserWorkspace(cmd, browserReuse);
|
|
217
217
|
const idleTimeout = browserReuse === 'site' ? INTERACTIVE_BROWSER_IDLE_TIMEOUT_SECONDS : undefined;
|
|
218
|
+
const keepTab = resolveKeepTab(browserReuse, opts.keepTab);
|
|
219
|
+
const windowMode = resolveBrowserWindowMode('background', opts.windowMode);
|
|
218
220
|
result = await browserSession(BrowserFactory, async (page) => {
|
|
219
221
|
const observation = traceMode === 'off'
|
|
220
222
|
? null
|
|
@@ -281,9 +283,6 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
281
283
|
throw wrapped;
|
|
282
284
|
}
|
|
283
285
|
}
|
|
284
|
-
// --live / OPENCLI_LIVE=1 keeps the current automation tab lease after
|
|
285
|
-
// the command finishes, so agents (or humans) can inspect the page state.
|
|
286
|
-
const keepOpen = browserReuse !== 'none' || process.env.OPENCLI_LIVE === '1' || process.env.OPENCLI_LIVE === 'true';
|
|
287
286
|
try {
|
|
288
287
|
const browserTimeout = userTimeoutSec !== null
|
|
289
288
|
? userTimeoutSec + RUNTIME_TIMEOUT_PADDING_SECONDS
|
|
@@ -304,7 +303,7 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
304
303
|
// Adapter commands are one-shot — release the current tab lease immediately
|
|
305
304
|
// instead of waiting for the 30s idle timeout. The automation container
|
|
306
305
|
// window stays open for reuse.
|
|
307
|
-
if (!
|
|
306
|
+
if (!keepTab)
|
|
308
307
|
await page.closeWindow?.().catch(() => { });
|
|
309
308
|
return result;
|
|
310
309
|
}
|
|
@@ -329,11 +328,11 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
329
328
|
// Release the tab lease on failure too — without this, the lease lingers
|
|
330
329
|
// until the extension's idle timer fires (unreliable on Windows where
|
|
331
330
|
// MV3 service workers may be suspended before setTimeout triggers).
|
|
332
|
-
if (!
|
|
331
|
+
if (!keepTab)
|
|
333
332
|
await page.closeWindow?.().catch(() => { });
|
|
334
333
|
throw err;
|
|
335
334
|
}
|
|
336
|
-
}, { workspace, cdpEndpoint, contextId, idleTimeout });
|
|
335
|
+
}, { workspace, cdpEndpoint, contextId, idleTimeout, windowMode });
|
|
337
336
|
}
|
|
338
337
|
else {
|
|
339
338
|
// Non-browser commands: enforce a timeout only when the command exposes
|
|
@@ -459,6 +458,32 @@ function resolveBrowserWorkspace(cmd, reuse) {
|
|
|
459
458
|
return `site:${cmd.site}`;
|
|
460
459
|
return `site:${cmd.site}:${crypto.randomUUID()}`;
|
|
461
460
|
}
|
|
461
|
+
function normalizeBooleanOption(name, raw) {
|
|
462
|
+
if (raw === undefined || raw === '')
|
|
463
|
+
return null;
|
|
464
|
+
if (raw === 'true')
|
|
465
|
+
return true;
|
|
466
|
+
if (raw === 'false')
|
|
467
|
+
return false;
|
|
468
|
+
throw new ArgumentError(`${name} must be one of: true, false. Received: "${String(raw)}"`);
|
|
469
|
+
}
|
|
470
|
+
function resolveKeepTab(reuse, rawOption) {
|
|
471
|
+
return normalizeBooleanOption('--keep-tab', rawOption)
|
|
472
|
+
?? normalizeBooleanOption('OPENCLI_KEEP_TAB', process.env.OPENCLI_KEEP_TAB)
|
|
473
|
+
?? reuse !== 'none';
|
|
474
|
+
}
|
|
475
|
+
function normalizeWindowMode(name, raw) {
|
|
476
|
+
if (raw === undefined || raw === '')
|
|
477
|
+
return null;
|
|
478
|
+
if (raw === 'foreground' || raw === 'background')
|
|
479
|
+
return raw;
|
|
480
|
+
throw new ArgumentError(`${name} must be one of: foreground, background. Received: "${String(raw)}"`);
|
|
481
|
+
}
|
|
482
|
+
function resolveBrowserWindowMode(defaultMode = 'background', rawOption) {
|
|
483
|
+
return normalizeWindowMode('--window', rawOption)
|
|
484
|
+
?? normalizeWindowMode('OPENCLI_WINDOW', process.env.OPENCLI_WINDOW)
|
|
485
|
+
?? defaultMode;
|
|
486
|
+
}
|
|
462
487
|
/**
|
|
463
488
|
* Resolve the user-controllable `--timeout` arg, in seconds.
|
|
464
489
|
*
|
|
@@ -157,8 +157,8 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
157
157
|
await executeCommand(cmd, {});
|
|
158
158
|
await executeCommand(cmd, {});
|
|
159
159
|
expect(sessionOpts).toHaveLength(2);
|
|
160
|
-
expect(sessionOpts[0]).toMatchObject({ workspace: 'site:test-execution', idleTimeout: 600 });
|
|
161
|
-
expect(sessionOpts[1]).toMatchObject({ workspace: 'site:test-execution', idleTimeout: 600 });
|
|
160
|
+
expect(sessionOpts[0]).toMatchObject({ workspace: 'site:test-execution', idleTimeout: 600, windowMode: 'background' });
|
|
161
|
+
expect(sessionOpts[1]).toMatchObject({ workspace: 'site:test-execution', idleTimeout: 600, windowMode: 'background' });
|
|
162
162
|
expect(closeWindow).not.toHaveBeenCalled();
|
|
163
163
|
vi.restoreAllMocks();
|
|
164
164
|
});
|
|
@@ -187,6 +187,8 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
187
187
|
expect(sessionOpts[0]?.workspace).not.toBe(sessionOpts[1]?.workspace);
|
|
188
188
|
expect(sessionOpts[0]?.idleTimeout).toBeUndefined();
|
|
189
189
|
expect(sessionOpts[1]?.idleTimeout).toBeUndefined();
|
|
190
|
+
expect(sessionOpts[0]?.windowMode).toBe('background');
|
|
191
|
+
expect(sessionOpts[1]?.windowMode).toBe('background');
|
|
190
192
|
expect(closeWindow).toHaveBeenCalledTimes(2);
|
|
191
193
|
vi.restoreAllMocks();
|
|
192
194
|
});
|
|
@@ -372,18 +374,18 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
372
374
|
expect(closeWindow).toHaveBeenCalledTimes(1);
|
|
373
375
|
vi.restoreAllMocks();
|
|
374
376
|
});
|
|
375
|
-
it('skips closeWindow when
|
|
377
|
+
it('skips closeWindow when OPENCLI_KEEP_TAB=true (success path)', async () => {
|
|
376
378
|
const closeWindow = vi.fn().mockResolvedValue(undefined);
|
|
377
379
|
const mockPage = { closeWindow };
|
|
378
380
|
vi.spyOn(capRouting, 'shouldUseBrowserSession').mockReturnValue(true);
|
|
379
381
|
vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
|
|
380
|
-
const prev = process.env.
|
|
381
|
-
process.env.
|
|
382
|
+
const prev = process.env.OPENCLI_KEEP_TAB;
|
|
383
|
+
process.env.OPENCLI_KEEP_TAB = 'true';
|
|
382
384
|
try {
|
|
383
385
|
const cmd = cli({
|
|
384
386
|
site: 'test-execution',
|
|
385
|
-
name: 'browser-
|
|
386
|
-
description: 'test closeWindow skipped with --
|
|
387
|
+
name: 'browser-keep-tab-success', access: 'read',
|
|
388
|
+
description: 'test closeWindow skipped with --keep-tab on success',
|
|
387
389
|
browser: true,
|
|
388
390
|
strategy: Strategy.PUBLIC,
|
|
389
391
|
func: async () => [{ ok: true }],
|
|
@@ -393,24 +395,24 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
393
395
|
}
|
|
394
396
|
finally {
|
|
395
397
|
if (prev === undefined)
|
|
396
|
-
delete process.env.
|
|
398
|
+
delete process.env.OPENCLI_KEEP_TAB;
|
|
397
399
|
else
|
|
398
|
-
process.env.
|
|
400
|
+
process.env.OPENCLI_KEEP_TAB = prev;
|
|
399
401
|
vi.restoreAllMocks();
|
|
400
402
|
}
|
|
401
403
|
});
|
|
402
|
-
it('skips closeWindow when
|
|
404
|
+
it('skips closeWindow when OPENCLI_KEEP_TAB=true (failure path)', async () => {
|
|
403
405
|
const closeWindow = vi.fn().mockResolvedValue(undefined);
|
|
404
406
|
const mockPage = { closeWindow };
|
|
405
407
|
vi.spyOn(capRouting, 'shouldUseBrowserSession').mockReturnValue(true);
|
|
406
408
|
vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
|
|
407
|
-
const prev = process.env.
|
|
408
|
-
process.env.
|
|
409
|
+
const prev = process.env.OPENCLI_KEEP_TAB;
|
|
410
|
+
process.env.OPENCLI_KEEP_TAB = 'true';
|
|
409
411
|
try {
|
|
410
412
|
const cmd = cli({
|
|
411
413
|
site: 'test-execution',
|
|
412
|
-
name: 'browser-
|
|
413
|
-
description: 'test closeWindow skipped with --
|
|
414
|
+
name: 'browser-keep-tab-failure', access: 'read',
|
|
415
|
+
description: 'test closeWindow skipped with --keep-tab on failure',
|
|
414
416
|
browser: true,
|
|
415
417
|
strategy: Strategy.PUBLIC,
|
|
416
418
|
func: async () => { throw new Error('adapter failure'); },
|
|
@@ -420,12 +422,37 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
420
422
|
}
|
|
421
423
|
finally {
|
|
422
424
|
if (prev === undefined)
|
|
423
|
-
delete process.env.
|
|
425
|
+
delete process.env.OPENCLI_KEEP_TAB;
|
|
424
426
|
else
|
|
425
|
-
process.env.
|
|
427
|
+
process.env.OPENCLI_KEEP_TAB = prev;
|
|
426
428
|
vi.restoreAllMocks();
|
|
427
429
|
}
|
|
428
430
|
});
|
|
431
|
+
it('lets browser common options override adapter window and keep-tab defaults', async () => {
|
|
432
|
+
const closeWindow = vi.fn().mockResolvedValue(undefined);
|
|
433
|
+
const mockPage = { closeWindow };
|
|
434
|
+
const sessionOpts = [];
|
|
435
|
+
vi.spyOn(capRouting, 'shouldUseBrowserSession').mockReturnValue(true);
|
|
436
|
+
vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn, opts) => {
|
|
437
|
+
sessionOpts.push(opts ?? {});
|
|
438
|
+
return fn(mockPage);
|
|
439
|
+
});
|
|
440
|
+
const cmd = cli({
|
|
441
|
+
site: 'test-execution',
|
|
442
|
+
name: 'browser-window-options', access: 'read',
|
|
443
|
+
description: 'test browser common options',
|
|
444
|
+
browser: true,
|
|
445
|
+
strategy: Strategy.PUBLIC,
|
|
446
|
+
func: async () => [{ ok: true }],
|
|
447
|
+
});
|
|
448
|
+
await executeCommand(cmd, {}, false, {
|
|
449
|
+
windowMode: 'foreground',
|
|
450
|
+
keepTab: 'true',
|
|
451
|
+
});
|
|
452
|
+
expect(sessionOpts[0]).toMatchObject({ windowMode: 'foreground' });
|
|
453
|
+
expect(closeWindow).not.toHaveBeenCalled();
|
|
454
|
+
vi.restoreAllMocks();
|
|
455
|
+
});
|
|
429
456
|
it('does not re-run custom validation when args are already prepared', async () => {
|
|
430
457
|
const validateArgs = vi.fn();
|
|
431
458
|
const cmd = {
|
|
@@ -54,3 +54,27 @@
|
|
|
54
54
|
tags: [vercel, deployment, serverless, frontend, devops]
|
|
55
55
|
install:
|
|
56
56
|
default: "npm install -g vercel"
|
|
57
|
+
|
|
58
|
+
- name: tg-cli
|
|
59
|
+
binary: tg
|
|
60
|
+
description: "Telegram CLI — local-first sync, search, export via MTProto for AI agents"
|
|
61
|
+
homepage: "https://github.com/jackwener/tg-cli"
|
|
62
|
+
tags: [telegram, messaging, search, export, ai-agent]
|
|
63
|
+
install:
|
|
64
|
+
default: "uv tool install kabi-tg-cli"
|
|
65
|
+
|
|
66
|
+
- name: discord-cli
|
|
67
|
+
binary: discord
|
|
68
|
+
description: "Discord CLI — local-first sync, search, export via SQLite for AI agents"
|
|
69
|
+
homepage: "https://github.com/jackwener/discord-cli"
|
|
70
|
+
tags: [discord, messaging, search, export, ai-agent]
|
|
71
|
+
install:
|
|
72
|
+
default: "uv tool install kabi-discord-cli"
|
|
73
|
+
|
|
74
|
+
- name: wx-cli
|
|
75
|
+
binary: wx
|
|
76
|
+
description: "WeChat local data CLI — sessions, messages, search, contacts, export for AI agents"
|
|
77
|
+
homepage: "https://github.com/jackwener/wx-cli"
|
|
78
|
+
tags: [wechat, messaging, search, export, ai-agent]
|
|
79
|
+
install:
|
|
80
|
+
default: "npm install -g @jackwener/wx-cli"
|
package/dist/src/help.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ export declare function rootHelpData(program: Command, groups: RootAdapterGroups
|
|
|
66
66
|
export declare function siteHelpData(site: string, commands: readonly CliCommand[]): Record<string, unknown>;
|
|
67
67
|
export declare function commandHelpData(cmd: CliCommand): Record<string, unknown>;
|
|
68
68
|
export declare function formatCommonOptionsHelpText(): string;
|
|
69
|
+
export declare function formatBrowserCommonOptionsHelpText(): string;
|
|
69
70
|
export declare function formatSiteHelpText(site: string, commands: readonly CliCommand[]): string;
|
|
70
71
|
export declare function formatCommandHelpText(cmd: CliCommand): string;
|
|
71
72
|
export declare function installStructuredHelp(command: Command, data: () => unknown, textSuffix?: string | (() => string)): void;
|