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

chore: update readme (#53)

* update readme

* update

* pretteir

* update

* update

* update

authored by

AmirHossein Sakhravi and committed by
GitHub
(Sep 23, 2025, 2:40 PM +0330) 6ea83b78 9aba06a3

+131 -275
-108
README.2.md
··· 1 - > A video showcasing how pnpm autocompletions work on a test CLI command like `my-cli` 2 - 3 - # tab 4 - 5 - > Instant feedback for your CLI tool when hitting [TAB] in your terminal 6 - 7 - As CLI tooling authors, if we can spare our users a second or two by not checking the documentation or writing the `-h` option, we're doing them a huge favor. The unconscious loves hitting the [TAB] key. It always expects feedback. So it feels disappointing when hitting that key in the terminal but then nothing happens. That frustration is apparent across the whole JavaScript CLI tooling ecosystem. 8 - 9 - Autocompletions are the solution to not break the user's flow. The issue is they're not simple to add. `zsh` expects them in one way, and `bash` in another way. Then where do we provide them so the shell environment parses them? Too many headaches to ease the user's experience. Whether it's worth it or not is out of the question. Because tab is the solution to this complexity. 10 - 11 - `my-cli.ts`: 12 - 13 - ```typescript 14 - import t from '@bomb.sh/tab'; 15 - 16 - t.name('my-cli'); 17 - 18 - t.command('start', 'start the development server'); 19 - 20 - if (process.argv[2] === 'complete') { 21 - const [shell, ...args] = process.argv.slice(3); 22 - if (shell === '--') { 23 - t.parse(args); 24 - } else { 25 - t.setup(shell, x); 26 - } 27 - } 28 - ``` 29 - 30 - This `my-cli.ts` would be equipped with all the tools required to provide autocompletions. 31 - 32 - ```bash 33 - node my-cli.ts complete -- "st" 34 - ``` 35 - 36 - ``` 37 - start start the development server 38 - :0 39 - ``` 40 - 41 - This output was generated by the `t.parse` method to autocomplete "st" to "start". 42 - 43 - Obviously, the user won't be running that command directly in their terminal. They'd be running something like this. 44 - 45 - ```bash 46 - source <(node my-cli.ts complete zsh) 47 - ``` 48 - 49 - Now whenever the shell sees `my-cli`, it would bring the autocompletions we wrote for this CLI tool. The `node my-cli.ts complete zsh` part would output the zsh script that loads the autocompletions provided by `t.parse` which then would be executed using `source`. 50 - 51 - The autocompletions only live through the current shell session. To set them up across all terminal sessions, the autocompletion script should be injected in the `.zshrc` file. 52 - 53 - ```bash 54 - my-cli complete zsh > ~/completion-for-my-cli.zsh && echo 'source ~/completion-for-my-cli.zsh' >> ~/.zshrc 55 - ``` 56 - 57 - Or 58 - 59 - ```bash 60 - echo 'source <(npx --offline my-cli complete zsh)' >> ~/.zshrc 61 - ``` 62 - 63 - This is an example of autocompletions on a global CLI command that is usually installed using the `-g` flag (e.g. `npm add -g my-cli`) which is available across the computer. 64 - 65 - --- 66 - 67 - While working on tab, we came to the realization that most JavaScript CLIs are not global CLI commands but rather, per-project dependencies. 68 - 69 - For instance, Vite won't be installed globally and instead it'd be always installed on a project. Here's an example usage: 70 - 71 - ```bash 72 - pnpm vite dev 73 - ``` 74 - 75 - Rather than installing it globally. This example is pretty rare: 76 - 77 - ```bash 78 - vite dev 79 - ``` 80 - 81 - So in this case, a computer might have hundreds of Vite instances each installed separately and potentially from different versions on different projects. 82 - 83 - We were looking for a fluid strategy that would be able to load the autocompletions from each of these dependencies on a per-project basis. 84 - 85 - And that made us develop our own autocompletion abstraction over npm, pnpm and yarn. This would help tab identify which binaries are available in a project and which of these binaries provide autocompletions. So the user would not have to `source` anything or inject any script in their `.zshrc`. 86 - 87 - They'd only have to run this command once and inject it in their shell config. 88 - 89 - ```bash 90 - npx @bomb.sh/tab pnpm zsh 91 - ``` 92 - 93 - These autocompletions on top of the normal autocompletions that these package managers provide are going to be way more powerful. 94 - 95 - These new autocompletions on top of package managers would help us with autocompletions on commands like `pnpm vite` and other global or per-project binaries. The only requirement would be that the npm binary itself would be a tab-compatible binary. 96 - 97 - What is a tab-compatible binary? It's a tool that provides the `complete` subcommand that was showcased above. Basically any CLI tool that uses tab for its autocompletions is a tab-compatible binary. 98 - 99 - ```bash 100 - pnpm my-cli complete -- 101 - ``` 102 - 103 - ``` 104 - start start the development server 105 - :0 106 - ``` 107 - 108 - We are planning to maintain these package manager autocompletions on our own and turn them into full-fledged autocompletions that touch on every part of our package managers.
+131 -167
README.md
··· 1 + > A video showcasing how pnpm autocompletions work on a test CLI command 2 + > like `my-cli` 3 + 1 4 # tab 2 5 3 - Shell autocompletions are largely missing in the javascript cli ecosystem. This tool is an attempt to make autocompletions come out of the box for any cli tool. 6 + Shell autocompletions are largely missing in the JavaScript CLI ecosystem. Tab provides a simple API for adding autocompletions to any JavaScript CLI tool. 4 7 5 - Tools like git and their autocompletion experience inspired us to build this tool and make the same ability available for any javascript cli project. Developers love hitting the tab key, hence why they prefer tabs over spaces. 8 + Additionally, tab supports autocompletions for `pnpm`, `npm`, `yarn`, and `bun`. 6 9 7 - ## Examples 10 + 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. 8 11 9 - Check out the [examples directory](./examples) for complete examples of using Tab with different command-line frameworks: 12 + Tab solves this complexity by providing autocompletions that work consistently across `zsh`, `bash`, `fish`, and `powershell`. 10 13 11 - - [CAC](./examples/demo.cac.ts) 12 - - [Citty](./examples/demo.citty.ts) 13 - - [Commander.js](./examples/demo.commander.ts) 14 + ## Installation 14 15 15 - ## Usage 16 + ```bash 17 + npm install @bomb.sh/tab 18 + # or 19 + pnpm add @bomb.sh/tab 20 + # or 21 + yarn add @bomb.sh/tab 22 + # or 23 + bun add @bomb.sh/tab 24 + ``` 25 + 26 + ## Quick Start 27 + 28 + Add autocompletions to your CLI tool: 16 29 17 - ```ts 18 - import { Completion, script } from '@bomb.sh/tab'; 30 + ```typescript 31 + import t from '@bomb.sh/tab'; 19 32 20 - const name = 'my-cli'; 21 - const completion = new Completion(); 33 + // Define your CLI structure 34 + const devCmd = t.command('dev', 'Start development server'); 35 + devCmd.option('port', 'Specify port', (complete) => { 36 + complete('3000', 'Development port'); 37 + complete('8080', 'Production port'); 38 + }); 22 39 23 - completion.addCommand( 24 - 'start', 25 - 'Start the application', 26 - async (previousArgs, toComplete, endsWithSpace) => { 27 - // suggestions 28 - return [ 29 - { value: 'dev', description: 'Start in development mode' }, 30 - { value: 'prod', description: 'Start in production mode' }, 31 - ]; 40 + // Handle completion requests 41 + if (process.argv[2] === 'complete') { 42 + const shell = process.argv[3]; 43 + if (shell === '--') { 44 + const args = process.argv.slice(4); 45 + t.parse(args); 46 + } else { 47 + t.setup('my-cli', 'node my-cli.js', shell); 32 48 } 33 - ); 49 + } 50 + ``` 51 + 52 + Test your completions: 53 + 54 + ```bash 55 + node my-cli.js complete -- dev --port=<TAB> 56 + # Output: --port=3000 Development port 57 + # --port=8080 Production port 58 + ``` 59 + 60 + Install for users: 61 + 62 + ```bash 63 + # One-time setup 64 + source <(my-cli complete zsh) 65 + 66 + # Permanent setup 67 + my-cli complete zsh > ~/.my-cli-completion.zsh 68 + echo 'source ~/.my-cli-completion.zsh' >> ~/.zshrc 69 + ``` 34 70 35 - completion.addOption( 36 - 'start', 37 - '--port', 38 - 'Specify the port number', 39 - async (previousArgs, toComplete, endsWithSpace) => { 40 - return [ 41 - { value: '3000', description: 'Development port' }, 42 - { value: '8080', description: 'Production port' }, 43 - ]; 44 - } 45 - ); 71 + ## Package Manager Completions 46 72 47 - // a way of getting the executable path to pass to the shell autocompletion script 48 - function quoteIfNeeded(path: string) { 49 - return path.includes(' ') ? `'${path}'` : path; 50 - } 51 - const execPath = process.execPath; 52 - const processArgs = process.argv.slice(1); 53 - const quotedExecPath = quoteIfNeeded(execPath); 54 - const quotedProcessArgs = processArgs.map(quoteIfNeeded); 55 - const quotedProcessExecArgs = process.execArgv.map(quoteIfNeeded); 56 - const x = `${quotedExecPath} ${quotedProcessExecArgs.join(' ')} ${quotedProcessArgs[0]}`; 73 + As mentioned earlier, tab provides completions for package managers as well: 57 74 58 - if (process.argv[2] === '--') { 59 - // autocompletion logic 60 - await completion.parse(process.argv.slice(2), 'start'); // TODO: remove "start" 61 - } else { 62 - // process.argv[2] can be "zsh", "bash", "fish", "powershell" 63 - script(process.argv[2], name, x); 64 - } 75 + ```bash 76 + # Generate and install completion scripts 77 + npx @bomb.sh/tab pnpm zsh > ~/.pnpm-completion.zsh && echo 'source ~/.pnpm-completion.zsh' >> ~/.zshrc 78 + npx @bomb.sh/tab npm bash > ~/.npm-completion.bash && echo 'source ~/.npm-completion.bash' >> ~/.bashrc 79 + npx @bomb.sh/tab yarn fish > ~/.config/fish/completions/yarn.fish 80 + npx @bomb.sh/tab bun powershell > ~/.bun-completion.ps1 && echo '. ~/.bun-completion.ps1' >> $PROFILE 65 81 ``` 66 82 67 - Now your user can run `source <(my-cli complete zsh)` and they will get completions for the `my-cli` command using the [autocompletion server](#autocompletion-server). 83 + Example in action: 84 + 85 + ```bash 86 + pnpm install --reporter=<TAB> 87 + # Shows: append-only, default, ndjson, silent (with descriptions) 88 + 89 + yarn add --emoji=<TAB> 90 + # Shows: true, false 91 + ``` 68 92 69 - ## Adapters 93 + ## Framework Adapters 70 94 71 - Since we are heavy users of tools like `cac` and `citty`, we have created adapters for both of them. Ideally, tab would be integrated internally into these tools, but for now, this is a good compromise. 95 + Tab provides adapters for popular JavaScript CLI frameworks. 72 96 73 - ### `@bomb.sh/tab/cac` 97 + ### CAC Integration 74 98 75 - ```ts 99 + ```typescript 76 100 import cac from 'cac'; 77 101 import tab from '@bomb.sh/tab/cac'; 78 102 79 103 const cli = cac('my-cli'); 80 104 81 - cli.command('dev', 'Start dev server').option('--port <port>', 'Specify port'); 105 + // Define your CLI 106 + cli 107 + .command('dev', 'Start dev server') 108 + .option('--port <port>', 'Specify port') 109 + .option('--host <host>', 'Specify host'); 82 110 111 + // Initialize tab completions 83 112 const completion = tab(cli); 84 113 85 - // Get the dev command completion handler 86 - const devCommandCompletion = completion.commands.get('dev'); 87 - 88 - // Get and configure the port option completion handler 89 - const portOptionCompletion = devCommandCompletion.options.get('--port'); 90 - portOptionCompletion.handler = async ( 91 - previousArgs, 92 - toComplete, 93 - endsWithSpace 94 - ) => { 95 - return [ 96 - { value: '3000', description: 'Development port' }, 97 - { value: '8080', description: 'Production port' }, 98 - ]; 99 - }; 114 + // Add custom completions for option values 115 + completion.commands.get('dev')?.options.get('--port')!.handler = async () => [ 116 + { value: '3000', description: 'Development port' }, 117 + { value: '8080', description: 'Production port' }, 118 + ]; 100 119 101 120 cli.parse(); 102 121 ``` 103 122 104 - Now autocompletion will be available for any specified command and option in your cac instance. If your user writes `my-cli dev --po`, they will get suggestions for the `--port` option. Or if they write `my-cli d` they will get suggestions for the `dev` command. 123 + ### Citty Integration 105 124 106 - Suggestions are missing in the adapters since yet cac or citty do not have a way to provide suggestions (tab just came out!), we'd have to provide them manually. Mutations do not hurt in this situation. 107 - 108 - ### `@bomb.sh/tab/citty` 109 - 110 - ```ts 111 - import citty, { defineCommand, createMain } from 'citty'; 125 + ```typescript 126 + import { defineCommand, createMain } from 'citty'; 112 127 import tab from '@bomb.sh/tab/citty'; 113 128 114 129 const main = defineCommand({ 115 - meta: { 116 - name: 'my-cli', 117 - description: 'My CLI tool', 130 + meta: { name: 'my-cli', description: 'My CLI tool' }, 131 + subCommands: { 132 + dev: defineCommand({ 133 + meta: { name: 'dev', description: 'Start dev server' }, 134 + args: { 135 + port: { type: 'string', description: 'Specify port' }, 136 + host: { type: 'string', description: 'Specify host' }, 137 + }, 138 + }), 118 139 }, 119 140 }); 120 141 121 - const devCommand = defineCommand({ 122 - meta: { 123 - name: 'dev', 124 - description: 'Start dev server', 125 - }, 126 - args: { 127 - port: { type: 'string', description: 'Specify port' }, 128 - }, 129 - }); 130 - 131 - main.subCommands = { 132 - dev: devCommand, 133 - }; 134 - 142 + // Initialize tab completions 135 143 const completion = await tab(main); 136 144 137 - // TODO: addHandler function to export 138 - const devCommandCompletion = completion.commands.get('dev'); 139 - 140 - const portOptionCompletion = devCommandCompletion.options.get('--port'); 141 - 142 - portOptionCompletion.handler = async ( 143 - previousArgs, 144 - toComplete, 145 - endsWithSpace 146 - ) => { 147 - return [ 148 - { value: '3000', description: 'Development port' }, 149 - { value: '8080', description: 'Production port' }, 150 - ]; 151 - }; 145 + // Add custom completions 146 + completion.commands.get('dev')?.options.get('--port')!.handler = async () => [ 147 + { value: '3000', description: 'Development port' }, 148 + { value: '8080', description: 'Production port' }, 149 + ]; 152 150 153 151 const cli = createMain(main); 154 152 cli(); 155 153 ``` 156 154 157 - ### `@bomb.sh/tab/commander` 155 + ### Commander.js Integration 158 156 159 - ```ts 157 + ```typescript 160 158 import { Command } from 'commander'; 161 159 import tab from '@bomb.sh/tab/commander'; 162 160 163 161 const program = new Command('my-cli'); 164 162 program.version('1.0.0'); 165 163 166 - // Add commands 164 + // Define commands 167 165 program 168 166 .command('serve') 169 167 .description('Start the server') ··· 173 171 console.log('Starting server...'); 174 172 }); 175 173 176 - // Initialize tab completion 174 + // Initialize tab completions 177 175 const completion = tab(program); 178 176 179 - // Configure custom completions 180 - for (const command of completion.commands.values()) { 181 - if (command.name === 'serve') { 182 - for (const [option, config] of command.options.entries()) { 183 - if (option === '--port') { 184 - config.handler = () => { 185 - return [ 186 - { value: '3000', description: 'Default port' }, 187 - { value: '8080', description: 'Alternative port' }, 188 - ]; 189 - }; 190 - } 191 - } 192 - } 193 - } 177 + // Add custom completions 178 + completion.commands.get('serve')?.options.get('--port')!.handler = async () => [ 179 + { value: '3000', description: 'Default port' }, 180 + { value: '8080', description: 'Alternative port' }, 181 + ]; 194 182 195 183 program.parse(); 196 184 ``` 197 185 198 - ## Recipe 199 - 200 - `source <(my-cli complete zsh)` won't be enough since the user would have to run this command each time they spin up a new shell instance. 201 - 202 - We suggest this approach for the end user that you as a maintainer might want to push. 203 - 204 - ``` 205 - my-cli completion zsh > ~/completion-for-my-cli.zsh 206 - echo 'source ~/completion-for-my-cli.zsh' >> ~/.zshrc 207 - ``` 208 - 209 - For other shells: 186 + Tab uses a standardized completion protocol that any CLI can implement: 210 187 211 188 ```bash 212 - # Bash 213 - my-cli complete bash > ~/.bash_completion.d/my-cli 214 - echo 'source ~/.bash_completion.d/my-cli' >> ~/.bashrc 215 - 216 - # Fish 217 - my-cli complete fish > ~/.config/fish/completions/my-cli.fish 189 + # Generate shell completion script 190 + my-cli complete zsh 218 191 219 - # PowerShell 220 - my-cli complete powershell > $PROFILE.CurrentUserAllHosts 192 + # Parse completion request (called by shell) 193 + my-cli complete -- install --port="" 221 194 ``` 222 195 223 - ## Autocompletion Server 196 + **Output Format:** 224 197 225 - By integrating tab into your cli, your cli would have a new command called `complete`. This is where all the magic happens. And the shell would contact this command to get completions. That's why we call it the autocompletion server. 226 - 227 - ```zsh 228 - my-cli complete -- --po 229 - --port Specify the port number 230 - :0 198 + ``` 199 + --port=3000 Development port 200 + --port=8080 Production port 201 + :4 231 202 ``` 232 203 233 - The autocompletion server can be a standard to identify whether a package provides autocompletions. Whether running `tool complete --` would result in an output that ends with `:{Number}` (matching the pattern `/:\d+$/`). 234 - 235 - In situations like `my-cli dev --po` you'd have autocompletions! But in the case of `pnpm my-cli dev --po` which is what most of us use, tab does not inject autocompletions for a tool like pnpm. 236 - 237 - Since pnpm already has its own autocompletion [script](https://pnpm.io/completion), this provides the opportunity to check whether a package provides autocompletions and use those autocompletions if available. 238 - 239 - This would also have users avoid injecting autocompletions in their shell config for any tool that provides its own autocompletion script, since pnpm would already support proxying the autocompletions out of the box. 204 + ## Documentation 240 205 241 - Other package managers like `npm` and `yarn` can decide whether to support this or not too for more universal support. 206 + See [bombshell docs](https://bomb.sh/docs/tab/). 242 207 243 - ## Inspiration 208 + ## Contributing 244 209 245 - - git 246 - - [cobra](https://github.com/spf13/cobra/blob/main/shell_completions.go), without cobra, tab would have took 10x longer to build 210 + We welcome contributions! Tab's architecture makes it easy to add support for new package managers or CLI frameworks.