(BUILTIN_CLIS: string, USER_CLIS: string)
| 691 | } |
| 692 | |
| 693 | export function createProgram(BUILTIN_CLIS: string, USER_CLIS: string): Command { |
| 694 | const program = new Command(); |
| 695 | // enablePositionalOptions: prevents parent from consuming flags meant for subcommands; |
| 696 | // prerequisite for passThroughOptions to forward --help/--version to external binaries |
| 697 | program |
| 698 | .name('opencli') |
| 699 | .description('Make any website your CLI. Zero setup. AI-powered.') |
| 700 | .version(PKG_VERSION) |
| 701 | .option('--profile <name>', 'Chrome profile/context alias for Browser Bridge commands') |
| 702 | .enablePositionalOptions(); |
| 703 | |
| 704 | // ── Built-in: list ──────────────────────────────────────────────────────── |
| 705 | |
| 706 | program |
| 707 | .command('list') |
| 708 | .description('List all available CLI commands') |
| 709 | .option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table') |
| 710 | .action((opts) => { |
| 711 | const registry = getRegistry(); |
| 712 | const commands = [...new Set(registry.values())].sort((a, b) => fullName(a).localeCompare(fullName(b))); |
| 713 | const fmt = opts.format; |
| 714 | const isStructured = fmt === 'json' || fmt === 'yaml'; |
| 715 | |
| 716 | if (fmt !== 'table') { |
| 717 | const rows = isStructured |
| 718 | ? commands.map(serializeCommand) |
| 719 | : commands.map(c => ({ |
| 720 | command: fullName(c), |
| 721 | site: c.site, |
| 722 | name: c.name, |
| 723 | aliases: c.aliases?.join(', ') ?? '', |
| 724 | description: c.description, |
| 725 | access: c.access, |
| 726 | strategy: strategyLabel(c), |
| 727 | browser: !!c.browser, |
| 728 | args: formatArgSummary(c.args), |
| 729 | })); |
| 730 | renderOutput(rows, { |
| 731 | fmt, |
| 732 | columns: ['command', 'site', 'name', 'aliases', 'description', 'access', 'strategy', 'browser', 'args', |
| 733 | ...(isStructured ? ['columns', 'domain'] : [])], |
| 734 | title: 'opencli/list', |
| 735 | source: 'opencli list', |
| 736 | }); |
| 737 | return; |
| 738 | } |
| 739 | |
| 740 | // Table (default) — grouped by adapter kind (app vs site), then by site name. |
| 741 | // classifyAdapter() reads the `domain` field: DNS-style domains are sites; |
| 742 | // localhost/loopback endpoints and bare app names are apps. |
| 743 | const appsBySite = new Map<string, CliCommand[]>(); |
| 744 | const sitesBySite = new Map<string, CliCommand[]>(); |
| 745 | for (const cmd of commands) { |
| 746 | const target = classifyAdapter(cmd.domain) === 'app' ? appsBySite : sitesBySite; |
| 747 | const g = target.get(cmd.site) ?? []; |
| 748 | g.push(cmd); |
| 749 | target.set(cmd.site, g); |
| 750 | } |
no test coverage detected