caporal, commander, inquirer, and vorpal are npm packages that help developers create command-line interfaces (CLIs) in Node.js, but they serve different purposes and operate at different levels of abstraction. commander and caporal focus on defining command structures, parsing arguments, and handling options for traditional one-off CLI tools. inquirer specializes in interactive prompts—like questions, confirmations, and list selections—that run during CLI execution. vorpal provides a full REPL (Read-Eval-Print Loop) environment, enabling persistent, shell-like applications with custom commands and real-time interaction.
When you need to build a command-line tool in Node.js, the right package depends entirely on what kind of interaction you’re aiming for. Are you making a simple script runner? A wizard-style installer? Or a persistent shell? Let’s break down how caporal, commander, inquirer, and vorpal approach these problems — and why one might be a better fit than the others.
Before diving in: vorpal is deprecated. Its GitHub repository and npm page explicitly state it’s no longer maintained. While it pioneered REPL-based CLIs in the Node ecosystem, it hasn’t kept pace with modern JavaScript practices or Node.js changes. Avoid it in new projects. We’ll still compare it for historical context, but treat it as a cautionary example.
commander: The Minimalist Standardcommander uses a fluent, chainable API to define commands, options, and actions. It’s close to how you’d write a C-style CLI parser — straightforward and predictable.
// commander example
const { Command } = require('commander');
const program = new Command();
program
.name('deploy')
.description('Deploy the app to staging or production')
.option('-e, --env <env>', 'Environment', 'staging')
.option('-f, --force', 'Skip confirmation')
.action((options) => {
console.log(`Deploying to ${options.env} (force: ${options.force})`);
});
program.parse();
You get automatic --help, version flags, and argument parsing out of the box. It’s lightweight and doesn’t impose structure beyond what you define.
caporal: Type-Safe and Opinionatedcaporal takes a more structured, TypeScript-first approach. You register commands with explicit types, validators, and descriptions. It auto-generates help and enforces input contracts.
// caporal example (TypeScript)
import { program } from 'caporal';
program
.version('1.0.0')
.command('deploy', 'Deploy the app')
.argument('<env>', 'Environment', { validator: ['staging', 'production'] })
.option('--force', 'Skip confirmation', { default: false })
.action((args, options) => {
console.log(`Deploying to ${args.env} (force: ${options.force})`);
});
program.parse();
Notice how validator ensures only allowed values are accepted. This reduces boilerplate for input validation — a big plus for complex CLIs.
inquirer: Not a Command Parserinquirer doesn’t define commands at all. Instead, it runs after your CLI starts to ask questions:
// inquirer used alongside commander
const { prompt } = require('inquirer');
if (!options.force) {
const { confirmed } = await prompt([
{ type: 'confirm', name: 'confirmed', message: 'Proceed?' }
]);
if (!confirmed) return;
}
It’s complementary — often paired with commander or caporal to add interactivity where needed.
vorpal: REPL-Style Command Registrationvorpal treated your CLI like a shell. You’d register commands that could be called repeatedly in a session:
// vorpal (deprecated — do not use)
const vorpal = require('vorpal')();
vorpal
.command('deploy <env>')
.description('Deploy to environment')
.action(function(args, callback) {
this.log(`Deploying to ${args.env}`);
callback();
});
vorpal
.delimiter('myapp$')
.show();
This launched an interactive prompt (myapp$) where users could type deploy staging, help, etc. But again — this package is deprecated.
commander and caporal assume a one-shot execution model: parse args → run logic → exit. Any interactivity must be added manually (often via inquirer).inquirer enables guided, step-by-step input during that one-shot run — perfect for setup wizards or configuration flows.vorpal assumed a persistent session, like a custom shell. Users stayed in the CLI until they typed exit.For most modern CLIs (e.g., create-react-app, eslint --fix, git wrappers), the one-shot model dominates. Persistent shells are rare outside of database clients or debuggers.
caporal shines here with built-in validators:
program
.command('scale', 'Scale service')
.argument('<replicas>', 'Number of replicas', Number, 1)
.validator((value) => {
if (value < 1 || value > 100) throw new Error('Must be 1–100');
});
commander leaves validation to you:
.option('-r, --replicas <n>', 'Replicas', (v) => parseInt(v, 10), 1)
// You must check range manually
inquirer validates per-prompt:
{
type: 'input',
name: 'replicas',
message: 'Replicas?',
validate: (input) => {
const n = parseInt(input, 10);
return (n >= 1 && n <= 100) ? true : 'Enter 1–100';
}
}
vorpal had minimal validation — another reason it’s fallen out of favor.
commander is the de facto standard. It’s used by tools like npm, webpack-cli, and many others. If you want maximum compatibility and minimal surprises, it’s the safe choice.caporal appeals to teams using TypeScript who want stronger contracts and less manual validation code.inquirer is almost always used with another CLI framework. Rarely does a project use inquirer alone.vorpal tried to be a full platform but became a maintenance burden. Its architecture didn’t compose well with modern async/await patterns.You’re building a CLI that deploys code. If --env isn’t provided, ask interactively.
✅ Best combo: commander + inquirer
program.option('-e, --env <env>');
program.action(async (options) => {
let env = options.env;
if (!env) {
const answer = await prompt({
type: 'list',
name: 'env',
message: 'Select environment',
choices: ['staging', 'production']
});
env = answer.env;
}
deploy(env);
});
Your tool runs in automated pipelines. Inputs must be validated, and interactivity is forbidden.
✅ Best choice: caporal (or commander with manual checks)
// caporal enforces valid inputs at parse time
program
.argument('<branch>', 'Git branch', { validator: /^[a-z0-9-]+$/ })
.action(({ branch }) => {
// Safe to use — already validated
});
You’re scaffolding a new project. Users answer 5–10 questions before generation begins.
✅ Best choice: inquirer (possibly with a minimal commander wrapper for --help)
const answers = await prompt([
{ type: 'input', name: 'name', message: 'Project name?' },
{ type: 'list', name: 'template', choices: ['react', 'vue'] }
]);
scaffold(answers);
| Package | Command Parsing | Input Validation | Interactive Prompts | REPL Support | Maintenance Status |
|---|---|---|---|---|---|
commander | ✅ Strong | ❌ Manual | ❌ (use inquirer) | ❌ | ✅ Active |
caporal | ✅ Strong | ✅ Built-in | ❌ (use inquirer) | ❌ | ✅ Active |
inquirer | ❌ None | ✅ Per-prompt | ✅ Excellent | ❌ | ✅ Active |
vorpal | ✅ Basic | ❌ Weak | ✅ (REPL-based) | ✅ | ❌ Deprecated |
commander. It’s simple, reliable, and widely understood.caporal to reduce validation boilerplate.inquirer — it pairs beautifully with either commander or caporal.vorpal in new code. If you truly need a custom REPL, build one using Node’s built-in repl module or explore modern alternatives like node:readline with async handlers.The best CLI tools often combine these packages thoughtfully: commander for structure, inquirer for guidance, and clear validation to keep things robust. Keep it simple — no jargon, no fluff — and your users will thank you.
Choose caporal if you want a TypeScript-friendly CLI framework with built-in validation, automatic help generation, and support for subcommands using a declarative API. It’s well-suited for medium-complexity CLIs where type safety and developer ergonomics matter, though it’s less widely adopted than alternatives like commander.
Choose commander if you need a battle-tested, minimal, and flexible library for building standard Unix-style command-line tools with options, arguments, and subcommands. Its simple API, extensive documentation, and long-standing stability make it ideal for scripts, dev tools, and CLIs that follow conventional patterns without requiring interactivity beyond basic input.
Choose inquirer when your CLI needs to gather user input through interactive prompts—such as text inputs, multiple-choice lists, or confirmations—during execution. It integrates cleanly with other CLI frameworks like commander or caporal and is the go-to solution for scaffolding tools, installers, or any workflow requiring guided user decisions.
Do not choose vorpal for new projects—it is officially deprecated and unmaintained. While it once enabled rich, REPL-based CLI applications with persistent sessions and custom command registration, its lack of updates, unresolved bugs, and compatibility issues with modern Node.js versions make it unsuitable for production use. Consider alternatives like node:repl or building custom REPLs with lower-level modules.
A full-featured framework for building command line applications (cli) with node.js, including help generation, colored output, verbosity control, custom logger, coercion and casting, typos suggestions, and auto-complete for bash/zsh/fish.
Simply add Caporal as a dependency:
$ npm install caporal
# Or if you are using yarn (https://yarnpkg.com/lang/en/)
$ yarn add caporal
Angled brackets (e.g. <item>) indicate required input. Square brackets (e.g. [env]) indicate optional input.
#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
// you specify arguments using .argument()
// 'app' is required, 'env' is optional
.command('deploy', 'Deploy an application')
.argument('<app>', 'App to deploy', /^myapp|their-app$/)
.argument('[env]', 'Environment to deploy on', /^dev|staging|production$/, 'local')
// you specify options using .option()
// if --tail is passed, its value is required
.option('--tail <lines>', 'Tail <lines> lines of logs after deploy', prog.INT)
.action(function(args, options, logger) {
// args and options are objects
// args = {"app": "myapp", "env": "production"}
// options = {"tail" : 100}
});
prog.parse(process.argv);
// ./myprog deploy myapp production --tail 100
Or else if you prefer typescript
#!/usr/bin/env node
import * as prog from 'caporal';
prog
.version('1.0.0')
// you specify arguments using .argument()
// 'app' is required, 'env' is optional
.command('deploy', 'Deploy an application')
.argument('<app>', 'App to deploy', /^myapp|their-app$/)
.argument('[env]', 'Environment to deploy on', /^dev|staging|production$/, 'local')
// you specify options using .option()
// if --tail is passed, its value is required
.option('--tail <lines>', 'Tail <lines> lines of logs after deploy', prog.INT)
.action(function(args, options, logger) {
// args and options are objects
// args = {"app": "myapp", "env": "production"}
// options = {"tail" : 100}
});
prog.parse(process.argv);
// ./myprog deploy myapp production --tail 100
You can use ... to indicate variadic arguments. In that case, the resulted value will be an array.
Note: Only the last argument of a command can be variadic !
#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
.command('deploy', 'Our deploy command')
// 'app' and 'env' are required
// and you can pass additional environments
.argument('<app>', 'App to deploy')
.argument('<env>', 'Environment')
.argument('[other-env...]', 'Other environments')
.action(function(args, options, logger) {
console.log(args);
// {
// "app": "myapp",
// "env": "production",
// "otherEnv": ["google", "azure"]
// }
});
prog.parse(process.argv);
// ./myprog deploy myapp production aws google azure
For a very simple program with just one command, you can omit the .command() call:
#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
.description('A simple program that says "biiiip"')
.action(function(args, options, logger) {
logger.info("biiiip")
});
prog.parse(process.argv);
You can pass arguments and options directly to Caporal API.
#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
.command('deploy', 'Our deploy command')
.argument('<app>', 'App to deploy')
.argument('<env>', 'Environment')
.option('--how-much <amount>', 'How much app to deploy', prog.INT, 1)
.action(function(args, options, logger) {
logger.info(args);
logger.info(options);
// {
// "app": "myapp",
// "env": "production"
// }
// {
// "howMuch": 2
// }
});
prog.exec(['deploy', 'myapp', 'env'], {
howMuch: 2
});
Inside your action(), use the logger argument (third one) to log informations.
#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
.command('deploy', 'The deploy command')
.action((args, options, logger) => {
// Available methods:
// - logger.debug('message')
// - logger.info('message') or logger.log('level', 'message')
// - logger.warn('message')
// - logger.error('message')
logger.info("Application deployed !");
});
prog.parse(process.argv);
The default logging level is 'info'. The predefined options can be used to change the logging level:
-v, --verbose: Set the logging level to 'debug' so debug() logs will be output.--quiet, --silent: Set the logging level to 'warn' so only warn() and error() logs will be output.Caporal uses winston for logging. You can provide your own winston-compatible logger using .logger() the following way:
#!/usr/bin/env node
const prog = require('caporal');
const myLogger = require('/path/to/my/logger.js');
prog
.version('1.0.0')
.logger(myLogger)
.command('foo', 'Foo command description')
.action((args, options, logger) => {
logger.info("Foo !!");
});
prog.parse(process.argv);
-v, --verbose: Set the logging level to 'debug' so debug() logs will be output.--quiet, --silent: Set the logging level to 'warn' so only warn() and error() logs will be output.You can apply coercion and casting using various validators:
INT (or INTEGER): Check option looks like an int and cast it with parseInt()FLOAT: Will Check option looks like a float and cast it with parseFloat()BOOL (or BOOLEAN): Check for string like 0, 1, true, false, on, off and cast itLIST (or ARRAY): Transform input to array by splitting it on commaREPEATABLE: Make the option repeatable, eg ./mycli -f foo -f bar -f joe#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
.command('order pizza')
.option('--number <num>', 'Number of pizza', prog.INT, 1)
.option('--kind <kind>', 'Kind of pizza', /^margherita|hawaiian$/)
.option('--discount <amount>', 'Discount offer', prog.FLOAT)
.option('--add-ingredients <ingredients>', 'Ingredients', prog.LIST)
.action(function(args, options) {
// options.kind = 'margherita'
// options.number = 1
// options.addIngredients = ['pepperoni', 'onion']
// options.discount = 1.25
});
prog.parse(process.argv);
// ./myprog order pizza --kind margherita --discount=1.25 --add-ingredients=pepperoni,onion
#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
.command('concat') // concat files
.option('-f <file>', 'File to concat', prog.REPEATABLE)
.action(function(args, options) {
});
prog.parse(process.argv);
// Usage:
// ./myprog concat -f file1.txt -f file2.txt -f file3.txt
Using this method, you can check and cast user input. Make the check fail by throwing an Error,
and cast input by returning a new value from your function.
#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
.command('order pizza')
.option('--kind <kind>', 'Kind of pizza', function(opt) {
if (['margherita', 'hawaiian'].includes(opt) === false) {
throw new Error("You can only order margherita or hawaiian pizza!");
}
return opt.toUpperCase();
})
.action(function(args, options) {
// options = { "kind" : "MARGHERITA" }
});
prog.parse(process.argv);
// ./myprog order pizza --kind margherita
Using an Array, Caporal will check that it contains the argument/option passed.
Note: It is not possible to cast user input with this method, only checking it, so it's basically only interesting for strings, but a major advantage is that this method will allow autocompletion of arguments and option values.
#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
.command('order pizza')
.option('--kind <kind>', 'Kind of pizza', ["margherita", "hawaiian"])
.action(function(args, options) {
});
prog.parse(process.argv);
// ./myprog order pizza --kind margherita
Simply pass a RegExp object to test against it. Note: It is not possible to cast user input with this method, only checking it, so it's basically only interesting for strings.
#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
.command('order pizza')
.option('--kind <kind>', 'Kind of pizza', /^margherita|hawaiian$/)
.action(function(args, options) {
});
prog.parse(process.argv);
// ./myprog order pizza --kind margherita
By default, Caporal will output colors for help and errors.
This behaviour can be disabled by passing --no-color.
Caporal automatically generates help/usage instructions for you.
Help can be displayed using -h or --help options, or with the default help command.
You can add some custom help to the whole program or to specific commands using .help(text, options?). The text, even if multi-line, will be, optionally, automatically indented.
Multiple help sections, with custom names, are supported.
#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
.help('my global help') // here our custom help for the whole program
.command('order pizza')
.action(function(args, options) {
});
prog.parse(process.argv);
#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
// first command
.command('order')
.help('my help for the order command') // here our custom help for the `order` command
.action(function(args, options) {
})
// second command
.command('cancel')
.help('my help for the cancel command') // here our custom help for the `cancel` command
.action(function(args, options) {
})
prog.parse(process.argv);
Caporal will automatically make suggestions for option typos.
Caporal comes with an auto-completion feature out of the box for bash, zsh, and fish,
thanks to tabtab.
For this feature to work, you will have to:
$PATH (this is the case if your app is installed globally using npm install -g <myapp>)# For bash
source <(myapp completion bash)
# or add it to your .bashrc to make it persist
echo "source <(myapp completion bash)" >> ~/.bashrc \
&& source ~/.bashrc
# For zsh
source <(myapp completion zsh)
# or add it to your .zshrc to make it persist
echo "source <(myapp completion zsh)" >> ~/.zshrc \
&& source ~/.zshrc
# For fish
source <(myapp completion fish)
# or add it to your config.fish to make it persist
echo "source <(myapp completion fish)" >> ~/.config/fish/config.fish \
&& source ~/.config/fish/config.fish
By default, it will autocomplete commands and option names. Also, options having an Array validator will be autocompleted.
#!/usr/bin/env node
const prog = require('caporal');
prog
.version('1.0.0')
// the "order" command
.command('order', 'Order a pizza')
.alias('give-it-to-me')
// <kind> will be auto-magicaly autocompleted by providing the user with 3 choices
.argument('<kind>', 'Kind of pizza', ["margherita", "hawaiian", "fredo"])
.argument('<from-store>', 'Which store to order from')
// enable auto-completion for <from-store> argument using a sync function returning an array
.complete(function() {
return ['store-1', 'store-2', 'store-3', 'store-4', 'store-5'];
})
.argument('<account>', 'Which account id to use')
// enable auto-completion for <account> argument using a Promise
.complete(function() {
return Promise.resolve(['account-1', 'account-2']);
})
.option('-n, --number <num>', 'Number of pizza', prog.INT, 1)
.option('-d, --discount <amount>', 'Discount offer', prog.FLOAT)
.option('-p, --pay-by <mean>', 'Pay by option')
// enable auto-completion for -p | --pay-by option using a Promise
.complete(function() {
return Promise.resolve(['cash', 'credit-card']);
})
// -e | --extra will be auto-magicaly autocompleted by providing the user with 3 choices
.option('-e, --extra <ingredients>', 'Add extra ingredients', ['pepperoni', 'onion', 'cheese'])
.action(function(args, options, logger) {
logger.info("Command 'order' called with:");
logger.info("arguments: %j", args);
logger.info("options: %j", options);
})
// the "return" command
.command('return', 'Return an order')
.argument('<order-id>', 'Order id')
// enable auto-completion for <order-id> argument using a Promise
.complete(function() {
return Promise.resolve(['#82792', '#71727', '#526Z52']);
})
.argument('<to-store>', 'Store id')
.option('--ask-change <other-kind-pizza>', 'Ask for other kind of pizza')
// enable auto-completion for --ask-change option using a Promise
.complete(function() {
return Promise.resolve(["margherita", "hawaiian", "fredo"]);
})
.option('--say-something <something>', 'Say something to the manager')
.action(function(args, options, logger) {
logger.info("Command 'return' called with:");
logger.info("arguments: %j", args);
logger.info("options: %j", options);
});
prog.parse(process.argv);
require('caporal')Returns a Program instance.
.version(version) : ProgramSet the version of your program. You may want to use your package.json version to fill it:
const myProgVersion = require('./package.json').version;
const prog = require('caporal');
prog
.version(myProgVersion)
// [...]
prog.parse(process.argv);
Your program will then automaticaly handle -V and --version options:
matt@mb:~$ ./my-program --version
1.0.0
.help(text, options?) : ProgramAdd a program-level help section.
By default the optional options parameter is:
{
indent: true, // If `true` the text will be automatically indented
name: "MORE INFO" // The name of the section
}
.command(name, description) -> CommandSet up a new command with name and description. Multiple commands can be added to one program. Returns a {Command}.
const prog = require('caporal');
prog
.version('1.0.0')
// one command
.command('walk', 'Make the player walk')
.action((args, options, logger) => { logger.log("I'm walking !")}) // you must attach an action for your command
// a second command
.command('run', 'Make the player run')
.action((args, options, logger) => { logger.log("I'm running !")})
// a command may have multiple words
.command('cook pizza', 'Make the player cook a pizza')
.argument('<kind>', 'Kind of pizza')
.action((args, options, logger) => { logger.log("I'm cooking a pizza !")})
// [...]
prog.parse(process.argv);
.logger([logger]) -> Program | winstonGet or set the logger instance. Without argument, it returns the logger instance (winston by default). With the logger argument, it sets a new logger.
.argument(synopsis, description, [validator, [defaultValue]]) -> CommandAdd an argument to the command. Can be called multiple times to add several arguments.
<my-required-arg> or [my-optional-arg].option(synopsis, description, [validator, [defaultValue, [required]]) -> CommandAdd an option to the command. Can be called multiple times to add several options.
false.help(text, options?) -> CommandAdd a command-level help section.
By default the optional options parameter is:
{
indent: true, // If `true` the text will be automatically indented
name: "" // The name of the section, by default this line won't be displayed
}
.action(action) -> CommandDefine the action, e.g a Function, for the current command.
The action callback will be called with 3 arguments:
// sync action
const prog = require('caporal');
prog
.version('1.0.0')
.command('walk', 'Make the player walk')
.action((args, options, logger) => {
logger.log("I'm walking !")
});
You can make your actions async by using Promises:
// async action
const prog = require('caporal');
prog
.version('1.0.0')
.command('walk', 'Make the player walk')
.action((args, options, logger) => {
return new Promise(/* ... */);
});
.alias(alias) -> CommandDefine an alias for the current command. A command can only have one alias.
const prog = require('caporal');
prog
.version('1.0.0')
// one command
.command('walk', 'Make the player walk')
.alias('w')
.action((args, options, logger) => { logger.log("I'm walking !")});
prog.parse(process.argv);
// ./myapp w
// same as
// ./myapp walk
.complete(completer) -> CommandDefine an auto-completion handler for the latest argument or option added to the command.
Array or a Promise which resolves to an Array..visible(visibility?) -> Boolean | CommandGet or set the visibility value of this command. By default it's true, if you set it to false it will be omitted from the help message.
const prog = require('caporal');
prog
.version('1.0.0')
// one command
.command('walk', 'Make the player walk')
.visible ( false )
prog.parse(process.argv);
Caporal is strongly inspired by commander.js and Symfony Console. Caporal makes use of the following npm packages:
Copyright © Matthias ETIENNE
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.