[READ-ONLY] Mirror of https://github.com/bombshell-dev/tab. shell autocompletions for javascript CLIs
4

Configure Feed

Select the types of activity you want to include in your feed.

133 7 17

Clone this repository

https://tangled.org/bomb.sh/tab https://tangled.org/did:plc:l2k56jl3qxhcjsaq3rwdhhbw
git@tangled.org:bomb.sh/tab git@tangled.org:did:plc:l2k56jl3qxhcjsaq3rwdhhbw

For self-hosted knots, clone URLs may differ based on your setup.



README.md

tab CLI autocompletions demo

tab#

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
Cloudflare
Nuxt
Nuxt
Astro
Astro
Vitest
Vitest
Gunshi
Gunshi
Clerc
Clerc

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.

Installation#

For Package Manager Completions#

Note: Global install is recommended

npm install -g @bomb.sh/tab

Then enable completions permanently:

# For zsh
echo 'source <(tab pnpm zsh)' >> ~/.zshrc
source ~/.zshrc

# For bash
echo 'source <(tab pnpm bash)' >> ~/.bashrc
source ~/.bashrc

# The same can be done for other shells!

For CLI Library (Adding Completions to Your CLI)#

npm install @bomb.sh/tab
# or
pnpm add @bomb.sh/tab
# or
yarn add @bomb.sh/tab
# or
bun add @bomb.sh/tab

Quick Start#

Add autocompletions to your CLI tool:

import t from '@bomb.sh/tab';

// Define your CLI structure
const devCmd = t.command('dev', 'Start development server');
devCmd.option('port', 'Specify port', (complete) => {
  complete('3000', 'Development port');
  complete('8080', 'Production port');
});

// Handle completion requests
if (process.argv[2] === 'complete') {
  const shell = process.argv[3];
  if (shell === '--') {
    const args = 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 port

Install for users:

# One-time setup
source <(my-cli complete zsh)

# Permanent setup
my-cli complete zsh > ~/.my-cli-completion.zsh
echo 'source ~/.my-cli-completion.zsh' >> ~/.zshrc

Development Workflow#

Generated 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.

# zsh
my-cli() { pnpm tsx src/index.ts "$@"; }
source <(my-cli complete zsh)
# bash
my-cli() { pnpm tsx src/index.ts "$@"; }
source <(my-cli complete bash)
# fish
function my-cli; pnpm tsx src/index.ts $argv; end
source (my-cli complete fish | psub)
# powershell
function my-cli { pnpm tsx src/index.ts $args }
my-cli complete powershell | Out-String | Invoke-Expression

The function name must match your CLI's program name so the shell resolves the completion callback back to it.

Package Manager Completions#

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' >> $PROFILE

Example in action:

pnpm install --reporter=<TAB>
# Shows: append-only, default, ndjson, silent

yarn add --emoji=<TAB>
# Shows: true, false

Completing locally-installed CLIs#

Package 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 too

Under 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). npx and bunx are separate commands with no completion of their own, so npx my-cli <TAB> / bunx my-cli <TAB> won't complete — use npm exec my-cli / bun x my-cli instead.

Framework Adapters#

tab provides adapters for popular JavaScript CLI frameworks.

CAC Integration#

import cac from 'cac';
import tab from '@bomb.sh/tab/cac';

const cli = cac('my-cli');

// Define your CLI
cli
  .command('dev', 'Start dev server')
  .option('--port <port>', 'Specify port')
  .option('--host <host>', 'Specify host');

// Initialize tab completions
const completion = await tab(cli);

// Add custom completions for option values
const devCommand = completion.commands.get('dev');
const portOption = devCommand?.options.get('port');
if (portOption) {
  portOption.handler = (complete) => {
    complete('3000', 'Development port');
    complete('8080', 'Production port');
  };
}

cli.parse();

Citty Integration#

import { defineCommand, createMain } from 'citty';
import tab from '@bomb.sh/tab/citty';

const main = 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 completions
const completion = await tab(main);

// Add custom completions
const devCommand = completion.commands.get('dev');
const portOption = devCommand?.options.get('port');
if (portOption) {
  portOption.handler = (complete) => {
    complete('3000', 'Development port');
    complete('8080', 'Production port');
  };
}

const cli = createMain(main);
cli();

Commander.js Integration#

import { Command } from 'commander';
import tab from '@bomb.sh/tab/commander';

const program = new Command('my-cli');
program.version('1.0.0');

// Define commands
program
  .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 completions
const completion = tab(program);

// Add custom completions
const serveCommand = completion.commands.get('serve');
const portOption = 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.

const completion = tab(program, { completionCommandName: 'completion' });

Custom Integrations#

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

Documentation#

See bombshell docs.

Contributing#

We welcome contributions! tab's architecture makes it easy to add support for new package managers or CLI frameworks.

Acknowledgments#

tab was inspired by the great Cobra project, which set the standard for CLI tooling in the Go ecosystem.

Adoption Support#

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.