super-commander
v0.4.1
Published
A fluent, deferred-registration wrapper around Commander.
Maintainers
Readme
super-commander
A fluent, deferred-registration wrapper around Commander. Register subcommands and global options in any order.
Installation
npm install super-commanderRequires Node.js >= 22.12.0.
Quick start
import { SuperCommander } from 'super-commander';
const cli = new SuperCommander('myapp', (cmd) => cmd.description('My awesome CLI').version('1.0.0'));
// Register a global option
cli.addOption('--verbose', 'Enable verbose output');
// Add a subcommand with Commander Command
cli.addSubCommand('upload', (cmd) =>
cmd
.description('Upload a file')
.argument('<file>')
.action((file) => console.log('Uploading:', file))
);
// Add a subcommand with its own options using SuperCommander
cli.addSuperSubCommand('download', (superCmd) => {
superCmd.withCommand((cmd) =>
cmd
.description('Download a file')
.argument('<url>')
.action((url) => console.log('Downloading:', url))
);
superCmd.addOption('--connection <number>', 'Number of download connections');
});
cli.parse();Why super-commander?
Plain Commander requires you to register options and subcommands in a specific
order on a single Command instance. This gets awkward when different modules
own different parts of the CLI; you end up passing the command around or
carefully sequencing initialization.
super-commander solves this with a builder that defers all registration
until you call .parse(). Each module gets the same SuperCommander instance
and adds its own options and subcommands whenever it's ready. Order doesn't
matter, and duplicates are caught early with clear error messages.
// auth.ts -- registers itself, no coordination needed
export function registerAuth(cli: SuperCommander) {
cli.addSubCommand('login', (cmd) =>
cmd.description('Sign in').action(() => {
/* ... */
})
);
cli.addSubCommand('logout', (cmd) =>
cmd.description('Sign out').action(() => {
/* ... */
})
);
}
// main.ts
import { SuperCommander } from 'super-commander';
import { registerAuth } from './auth.js';
const cli = new SuperCommander('myapp');
registerAuth(cli);
cli.parse();Nested command trees (addSuperSubCommand) let
you build deep CLI structures with the same pattern: each subtree manages its
own options, subcommands, and help configuration.
API
SuperCommander
new SuperCommander(programName, fn?)
Create a new CLI. programName is the root command name shown in help output.
The optional fn receives the underlying Commander Command for setting
.description(), .version(), etc.
const cli = new SuperCommander('myapp', (cmd) => cmd.description('My CLI').version('2.0.0'));.addOption(flags, description?, fn?)
Register a global option on the root command. Throws if an option with the same name is already registered.
// Flags + description
cli.addOption('--verbose', 'Enable verbose output');
// Flags + description + config callback
cli.addOption('--port <number>', 'Port to listen on', (opt) => opt.default(3000).env('PORT'));
// Flags + config callback (skip description)
cli.addOption('--debug', (opt) => opt.default(false));.addSubCommand(name, fn?)
Register a subcommand. Pass a callback to configure the command (.description(),
.argument(), .action(), etc.), or omit it for a bare subcommand. Throws if
name is already registered.
// With configuration
cli.addSubCommand('upload', (cmd) => cmd.description('Upload a file').argument('<file>').action(handler));
// Bare subcommand
cli.addSubCommand('status');.registerSubCommands(...commands)
Register one or more pre-built SuperCommander instances as subcommands of
the root command. Throws if any command name is already registered.
Please note that once a subcommand is registered, it can't be modified/mutated.
If you need a more dynamic approach, use addSuperSubCommand() instead.
// Build standalone command trees
const auth = new SuperCommander('auth');
auth.addSubCommand('login', (cmd) => cmd.description('Sign in').action(handler));
auth.addOption('--token <t>', 'Auth token');
const admin = new SuperCommander('admin');
admin.addSubCommand('dashboard', (cmd) => cmd.description('Show dashboard').action(handler));
// Register them all at once
cli.registerSubCommands(auth, admin);When subcommands are registered this way, the parent's configureGlobalHelp
config is propagated to each subtree automatically.
.addSuperSubCommand(subCmd)
Register a SuperSubCommand instance. Throws if a
subcommand with the same name is already registered.
.addSuperSubCommand(name, fn)
Register a nested command tree. The callback receives a fresh
SuperSubCommand instance, which has all the same
registration methods. Use this when a subcommand needs its own subcommands,
global options, or help configuration.
cli.addSuperSubCommand('admin', (admin) => {
admin.withCommand((cmd) => cmd.description('Admin commands'));
admin.addSubCommand('users', (cmd) =>
cmd.description('Manage users').action(() => {
/* ... */
})
);
admin.addSubCommand('roles', (cmd) =>
cmd.description('Manage roles').action(() => {
/* ... */
})
);
});.withCommand(fn)
Replace the root Command via a callback. Use for Commander features the
wrapper doesn't expose directly (.hook(), .showSuggestionAfterError(), etc.).
cli.withCommand((cmd) => cmd.showSuggestionAfterError(true));.configureGlobalHelp(config)
Apply HelpConfiguration
to the root command and all subcommands at parse time.
cli.configureGlobalHelp({ showGlobalOptions: true });.getHelpConfiguration()
Returns the current help configuration object set via configureGlobalHelp,
or an empty object if none was set.
cli.configureGlobalHelp({ showGlobalOptions: true });
console.log(cli.getHelpConfiguration()); // { showGlobalOptions: true }.parse(argv?, parseOptions?)
Assemble the full command tree and parse argv synchronously (defaults to
process.argv). Returns the parsed root Command.
.parseAsync(argv?, parseOptions?)
Like .parse() but returns Promise<Command>. Use when a command action is
async and you need to await it.
.command
Access the underlying root Command. Useful for inspecting the assembled tree
after parsing.
.getName()
Returns the program name.
SuperSubCommand
SuperSubCommand extends SuperCommander, so it has all the same methods.
Use it when registering a nested command tree via
addSuperSubCommand(name, fn), or build one
standalone and pass it to
addSuperSubCommand(subCmd).
import { SuperSubCommand } from 'super-commander';
const admin = new SuperSubCommand('admin');
admin.addSubCommand('users', (cmd) => cmd.description('Manage users').action(handler));
admin.addOption('--realm <name>', 'Admin realm');
cli.addSuperSubCommand(admin);Entry points
| Entry | What's included |
| --------------------------- | ------------------------------------------------------------------------------------ |
| super-commander | SuperCommander, SuperSubCommand, plus Command and Option from Commander |
| super-commander/commander | Full Commander re-export (Command, Option, Argument, program, and all types) |
| super-commander/ui | ask and clearLine -- terminal I/O utilities |
super-commander/ui
import { ask, clearLine } from 'super-commander/ui';
// Prompt the user
const name = await ask('What is your name? ');
// Password entry (keystrokes are hidden)
const password = await ask('Password: ', { silent: true });
// Prompt that cleans up after itself
const choice = await ask('Pick an option: ', { clear: true });
// Erase the last N lines from the terminal
clearLine(3);License
ISC
