Shell autocompletions are largely missing in the JavaScript CLI ecosystem. tab provides a simple API for adding autocompletions to any JavaScript CLI tool.
Additionally, tab supports autocompletions for pnpm, npm, yarn, and bun.
Tab has already been adopted by major tools and CLI frameworks, including:
![]() Cloudflare | ![]() Nuxt | ![]() Astro | ![]() Vitest | ![]() Gunshi | ![]() Clerc | ![]() Prisma |
As CLI tooling authors, if we can spare our users a second or two by not checking documentation or writing the -h flag, we're doing them a huge favor. The unconscious mind loves hitting the [TAB] key and always expects feedback. When nothing happens, it breaks the user's flow - a frustration apparent across the whole JavaScript CLI tooling ecosystem.
tab solves this complexity by providing autocompletions that work consistently across zsh, bash, fish, and powershell.
Note: Global install is recommended
npm install -g @bomb.sh/tabThen enable completions permanently:
# For zshecho'source <(tab pnpm zsh)'>>~/.zshrc
source~/.zshrc
# For bashecho'source <(tab pnpm bash)'>>~/.bashrc
source~/.bashrc
# The same can be done for other shells!npm install @bomb.sh/tab
# or
pnpm add @bomb.sh/tab
# or
yarn add @bomb.sh/tab
# or
bun add @bomb.sh/tabAdd autocompletions to your CLI tool:
importtfrom'@bomb.sh/tab';// Define your CLI structureconstdevCmd=t.command('dev','Start development server');devCmd.option('port','Specify port',(complete)=>{complete('3000','Development port');complete('8080','Production port');});// Handle completion requestsif(process.argv[2]==='complete'){constshell=process.argv[3];if(shell==='--'){constargs=process.argv.slice(4);t.parse(args);}else{t.setup('my-cli','node my-cli.js',shell);}}Test your completions:
node my-cli.js complete -- dev --port=<TAB># Output: --port=3000 Development port# --port=8080 Production portInstall for users:
# One-time setupsource<(my-cli complete zsh)# Permanent setup
my-cli complete zsh >~/.my-cli-completion.zsh
echo'source ~/.my-cli-completion.zsh'>>~/.zshrcGenerated completion scripts invoke your CLI by its program name (e.g. my-cli),
which the shell resolves via PATH, an alias, or a shell function. During local
development your CLI usually isn't installed on PATH, so define a session-scoped
shell function that runs it from source, then source the completions. No build,
no install, and no PATH changes are needed — the function disappears when you
close the terminal.
Replace my-cli with your program name and adjust the source path if needed.
# zshmy-cli() { pnpm tsx src/index.ts "$@"; }
source<(my-cli complete zsh)# bashmy-cli() { pnpm tsx src/index.ts "$@"; }
source<(my-cli complete bash)# fishfunction my-cli; pnpm tsx src/index.ts $argv; end
source (my-cli complete fish |psub)# powershellfunctionmy-cli { pnpm tsx src/index.ts $args }
my-cli complete powershell |Out-String|Invoke-ExpressionThe function name must match your CLI's program name so the shell resolves the completion callback back to it.
As mentioned earlier, tab provides completions for package managers as well:
# Generate and install completion scripts
tab pnpm zsh >~/.pnpm-completion.zsh &&echo'source ~/.pnpm-completion.zsh'>>~/.zshrc
tab npm bash >~/.npm-completion.bash &&echo'source ~/.npm-completion.bash'>>~/.bashrc
tab yarn fish >~/.config/fish/completions/yarn.fish
tab bun powershell >~/.bun-completion.ps1 &&echo'. ~/.bun-completion.ps1'>>$PROFILEExample in action:
pnpm install --reporter=<TAB># Shows: append-only, default, ndjson, silent
yarn add --emoji=<TAB># Shows: true, falsePackage manager completion does more than complete the package manager's own flags — it also delegates to CLIs installed as local project dependencies. If a CLI implements tab's completion protocol (directly or via a framework adapter), it becomes completable through your package manager without being on your PATH and without installing its completion script separately:
pnpm exec my-cli <TAB># completes my-cli's subcommands and flags
pnpm dlx my-cli <TAB>
pnpm my-cli <TAB># the bare form works tooUnder the hood, tab strips the package-manager wrapper (exec, x, run, dlx), detects whether the target CLI supports completion, and forwards the request to it — falling back to running the CLI through the package manager (e.g. pnpm my-cli complete -- …) so locally-installed binaries resolve. The same works for npm exec, yarn, and bun x.
Note: Completion is registered against the package-manager binary (
npm,pnpm,yarn,bun).npxandbunxare separate commands with no completion of their own, sonpx my-cli <TAB>/bunx my-cli <TAB>won't complete — usenpm exec my-cli/bun x my-cliinstead.
tab provides adapters for popular JavaScript CLI frameworks.
importcacfrom'cac';importtabfrom'@bomb.sh/tab/cac';constcli=cac('my-cli');// Define your CLIcli.command('dev','Start dev server').option('--port <port>','Specify port').option('--host <host>','Specify host');// Initialize tab completionsconstcompletion=awaittab(cli);// Add custom completions for option valuesconstdevCommand=completion.commands.get('dev');constportOption=devCommand?.options.get('port');if(portOption){portOption.handler=(complete)=>{complete('3000','Development port');complete('8080','Production port');};}cli.parse();import{defineCommand,createMain}from'citty';importtabfrom'@bomb.sh/tab/citty';constmain=defineCommand({meta: {name: 'my-cli',description: 'My CLI tool'},subCommands: {dev: defineCommand({meta: {name: 'dev',description: 'Start dev server'},args: {port: {type: 'string',description: 'Specify port'},host: {type: 'string',description: 'Specify host'},},}),},});// Initialize tab completionsconstcompletion=awaittab(main);// Add custom completionsconstdevCommand=completion.commands.get('dev');constportOption=devCommand?.options.get('port');if(portOption){portOption.handler=(complete)=>{complete('3000','Development port');complete('8080','Production port');};}constcli=createMain(main);cli();import{Command}from'commander';importtabfrom'@bomb.sh/tab/commander';constprogram=newCommand('my-cli');program.version('1.0.0');// Define commandsprogram.command('serve').description('Start the server').option('-p, --port <number>','port to use','3000').option('-H, --host <host>','host to use','localhost').action((options)=>{console.log('Starting server...');});// Initialize tab completionsconstcompletion=tab(program);// Add custom completionsconstserveCommand=completion.commands.get('serve');constportOption=serveCommand?.options.get('port');if(portOption){portOption.handler=(complete)=>{complete('3000','Default port');complete('8080','Alternative port');};}program.parse();The Commander integration supports customising the command name to generate the shell completion script. The default is complete. If you use a custom name
like completion then it will be visible in the help as completion <shell>, while the runtime suggestions will be hiddden (complete -- [args...]).
You'll need to use your custom command when following examples on this page to generate the shell completion script.
constcompletion=tab(program,{completionCommandName: 'completion'});tab uses a standardized completion protocol that any CLI can implement:
# Generate shell completion script
my-cli complete zsh
# Parse completion request (called by shell)
my-cli complete -- install --port=""Output Format:
--port=3000 Development port
--port=8080 Production port
:4
See bombshell docs.
We welcome contributions! tab's architecture makes it easy to add support for new package managers or CLI frameworks.
tab was inspired by the great Cobra project, which set the standard for CLI tooling in the Go ecosystem.
We want to make it as easy as possible for the JS ecosystem to enjoy great autocompletions.
We at thundraa would be happy to help any open source CLI utility adopt tab.
If you maintain a CLI and would like autocompletions set up for your users, just drop the details in our Adopting tab discussion.
We’ll gladly help and even open a PR to get you started.







