From 6815c619ac7da1b4fe799dd894a62dea1c452b08 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 21 Sep 2025 22:25:23 +0330 Subject: [PATCH 1/6] update readme --- README.2.md | 237 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 179 insertions(+), 58 deletions(-) diff --git a/README.2.md b/README.2.md index 6415ad2..21937b5 100644 --- a/README.2.md +++ b/README.2.md @@ -1,108 +1,229 @@ -> A video showcasing how pnpm autocompletions work on a test CLI command like `my-cli` +> A video showcasing how pnpm autocompletions work on a test CLI command +like `my-cli` # tab +Shell autocompletions are largely missing in the JavaScript CLI ecosystem. This tool bridges that gap by autocompletions for `pnpm`, `npm`, `yarn`, and `bun` with dynamic option parsing and context-aware suggestions and also Easy-to-use adapters for popular JavaScript CLI frameworks like CAC, Citty, and Commander.js -> Instant feedback for your CLI tool when hitting [TAB] in your terminal +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. -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. +Tab solves this complexity by providing autocompletions that work consistently across `zsh`, `bash`, `fish`, and `powershell`. -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. -`my-cli.ts`: +### Installation -```typescript -import t from '@bomb.sh/tab'; +```bash +npm install @bomb.sh/tab +# or +pnpm add @bomb.sh/tab +# or +yarn add @bomb.sh/tab +# or +bun add @bomb.sh/tab +``` -t.name('my-cli'); +### Package Manager Completions -t.command('start', 'start the development server'); +Get autocompletions for your package manager with zero configuration: -if (process.argv[2] === 'complete') { - const [shell, ...args] = process.argv.slice(3); - if (shell === '--') { - t.parse(args); - } else { - t.setup(shell, x); - } -} +```bash +# this generates a completion script for your shell +npx @bomb.sh/tab pnpm zsh >> ~/.zshrc +npx @bomb.sh/tab npm bash >> ~/.bashrc +npx @bomb.sh/tab yarn fish > ~/.config/fish/completions/yarn.fish +npx @bomb.sh/tab bun powershell >> $PROFILE ``` -This `my-cli.ts` would be equipped with all the tools required to provide autocompletions. +You'd get smart completions for all commands and options, and dynamic option values e.g., `--reporter=`. and its always up-to-date (parsed from live help output) +**Example in action:** ```bash -node my-cli.ts complete -- "st" -``` +pnpm install --reporter= +# Shows append-only, default, ndjson, silent -``` -start start the development server -:0 +npm remove +# Shows the packages from package.json + +yarn add --emoji= +# Show true, false ``` -This output was generated by the `t.parse` method to autocomplete "st" to "start". +### CLI Framework Integration -Obviously, the user won't be running that command directly in their terminal. They'd be running something like this. +For your own CLI tools, tab provides seamless integration with popular frameworks: -```bash -source <(node my-cli.ts complete zsh) -``` +#### Using the Core API -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`. +```typescript +import t from '@bomb.sh/tab'; -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. +t.command('dev', 'Start development server'); +t.option('port', 'Specify port', (complete) => { + complete('3000', 'Development port'); + complete('8080', 'Production port'); +}); -```bash -my-cli complete zsh > ~/completion-for-my-cli.zsh && echo 'source ~/completion-for-my-cli.zsh' >> ~/.zshrc +// handle completion requests +if (process.argv[2] === 'complete') { + const shell = process.argv[3]; + if (shell === '--') { + // parse completion arguments + const args = process.argv.slice(4); + t.parse(args); + } else { + // generate shell script + t.setup('my-cli', 'node my-cli.js', shell); + } +} ``` -Or - +**Test your completions:** ```bash -echo 'source <(npx --offline my-cli complete zsh)' >> ~/.zshrc +node my-cli.js complete -- dev --p +# Output: --port Specify port + +node my-cli.js complete -- dev --port= +# Output: --port=3000 Development port +# --port=8080 Production port ``` -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. +**Install for users:** +```bash +# One-time setup +source <(my-cli complete zsh) ---- +# Permanent setup +my-cli complete zsh >> ~/.zshrc +``` -While working on tab, we came to the realization that most JavaScript CLIs are not global CLI commands but rather, per-project dependencies. +## Framework Adapters -For instance, Vite won't be installed globally and instead it'd be always installed on a project. Here's an example usage: +Tab provides adapters for popular JavaScript CLI frameworks. -```bash -pnpm vite dev -``` +### CAC Integration -Rather than installing it globally. This example is pretty rare: +```typescript +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 ', 'Specify port') + .option('--host ', 'Specify host'); + +// Initialize tab completions +const completion = tab(cli); + +// Add custom completions for option values +const devCommand = completion.commands.get('dev'); +const portOption = devCommand?.options.get('--port'); +if (portOption) { + portOption.handler = async () => [ + { value: '3000', description: 'Development port' }, + { value: '8080', description: 'Production port' }, + ]; +} -```bash -vite dev +cli.parse(); ``` -So in this case, a computer might have hundreds of Vite instances each installed separately and potentially from different versions on different projects. +### Citty Integration -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. +```typescript +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 = async () => [ + { value: '3000', description: 'Development port' }, + { value: '8080', description: 'Production port' }, + ]; +} -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`. +const cli = createMain(main); +cli(); +``` -They'd only have to run this command once and inject it in their shell config. +### Commander.js Integration -```bash -npx @bomb.sh/tab pnpm zsh +```typescript +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 ', 'port to use', '3000') + .option('-H, --host ', 'host to use', 'localhost') + .action((options) => { + console.log('Starting server...'); + }); + +// Initialize tab completions +const completion = tab(program); + +// Add custom completions +for (const command of completion.commands.values()) { + if (command.value === 'serve') { + const portOption = command.options.get('--port'); + if (portOption) { + portOption.handler = async () => [ + { value: '3000', description: 'Default port' }, + { value: '8080', description: 'Alternative port' }, + ]; + } + } +} + +program.parse(); ``` -These autocompletions on top of the normal autocompletions that these package managers provide are going to be way more powerful. -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. +Tab's package manager completions are dynamically generated from the actual help output of each tool: + -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. +Tab uses a standardized completion protocol that any CLI can implement: ```bash -pnpm my-cli complete -- +# Generate shell completion script +my-cli complete zsh + +# Parse completion request (called by shell) +my-cli complete -- install --port="" ``` +**Output Format:** ``` -start start the development server -:0 +--port=3000 Development port +--port=8080 Production port +:4 ``` +## Contributing + +We welcome contributions! Tab's architecture makes it easy to add support for new package managers or CLI frameworks. + -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. From 36d5b2ee5caf8edb283d4780615bc6dfbd612b59 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 21 Sep 2025 22:28:01 +0330 Subject: [PATCH 2/6] update --- README.2.md | 229 ---------------------------------------- README.md | 299 +++++++++++++++++++++++++--------------------------- 2 files changed, 141 insertions(+), 387 deletions(-) delete mode 100644 README.2.md diff --git a/README.2.md b/README.2.md deleted file mode 100644 index 21937b5..0000000 --- a/README.2.md +++ /dev/null @@ -1,229 +0,0 @@ -> A video showcasing how pnpm autocompletions work on a test CLI command -like `my-cli` - -# tab -Shell autocompletions are largely missing in the JavaScript CLI ecosystem. This tool bridges that gap by autocompletions for `pnpm`, `npm`, `yarn`, and `bun` with dynamic option parsing and context-aware suggestions and also Easy-to-use adapters for popular JavaScript CLI frameworks like CAC, Citty, and Commander.js - -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 - -```bash -npm install @bomb.sh/tab -# or -pnpm add @bomb.sh/tab -# or -yarn add @bomb.sh/tab -# or -bun add @bomb.sh/tab -``` - -### Package Manager Completions - -Get autocompletions for your package manager with zero configuration: - -```bash -# this generates a completion script for your shell -npx @bomb.sh/tab pnpm zsh >> ~/.zshrc -npx @bomb.sh/tab npm bash >> ~/.bashrc -npx @bomb.sh/tab yarn fish > ~/.config/fish/completions/yarn.fish -npx @bomb.sh/tab bun powershell >> $PROFILE -``` - -You'd get smart completions for all commands and options, and dynamic option values e.g., `--reporter=`. and its always up-to-date (parsed from live help output) - -**Example in action:** -```bash -pnpm install --reporter= -# Shows append-only, default, ndjson, silent - -npm remove -# Shows the packages from package.json - -yarn add --emoji= -# Show true, false -``` - -### CLI Framework Integration - -For your own CLI tools, tab provides seamless integration with popular frameworks: - -#### Using the Core API - -```typescript -import t from '@bomb.sh/tab'; - -t.command('dev', 'Start development server'); -t.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 === '--') { - // parse completion arguments - const args = process.argv.slice(4); - t.parse(args); - } else { - // generate shell script - t.setup('my-cli', 'node my-cli.js', shell); - } -} -``` - -**Test your completions:** -```bash -node my-cli.js complete -- dev --p -# Output: --port Specify port - -node my-cli.js complete -- dev --port= -# Output: --port=3000 Development port -# --port=8080 Production port -``` - -**Install for users:** -```bash -# One-time setup -source <(my-cli complete zsh) - -# Permanent setup -my-cli complete zsh >> ~/.zshrc -``` - -## Framework Adapters - -Tab provides adapters for popular JavaScript CLI frameworks. - -### CAC Integration - -```typescript -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 ', 'Specify port') - .option('--host ', 'Specify host'); - -// Initialize tab completions -const completion = tab(cli); - -// Add custom completions for option values -const devCommand = completion.commands.get('dev'); -const portOption = devCommand?.options.get('--port'); -if (portOption) { - portOption.handler = async () => [ - { value: '3000', description: 'Development port' }, - { value: '8080', description: 'Production port' }, - ]; -} - -cli.parse(); -``` - -### Citty Integration - -```typescript -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 = async () => [ - { value: '3000', description: 'Development port' }, - { value: '8080', description: 'Production port' }, - ]; -} - -const cli = createMain(main); -cli(); -``` - -### Commander.js Integration - -```typescript -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 ', 'port to use', '3000') - .option('-H, --host ', 'host to use', 'localhost') - .action((options) => { - console.log('Starting server...'); - }); - -// Initialize tab completions -const completion = tab(program); - -// Add custom completions -for (const command of completion.commands.values()) { - if (command.value === 'serve') { - const portOption = command.options.get('--port'); - if (portOption) { - portOption.handler = async () => [ - { value: '3000', description: 'Default port' }, - { value: '8080', description: 'Alternative port' }, - ]; - } - } -} - -program.parse(); -``` - - -Tab's package manager completions are dynamically generated from the actual help output of each tool: - - -Tab uses a standardized completion protocol that any CLI can implement: - -```bash -# 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 -``` -## Contributing - -We welcome contributions! Tab's architecture makes it easy to add support for new package managers or CLI frameworks. - - diff --git a/README.md b/README.md index f4686cc..21937b5 100644 --- a/README.md +++ b/README.md @@ -1,169 +1,179 @@ +> A video showcasing how pnpm autocompletions work on a test CLI command +like `my-cli` + # tab +Shell autocompletions are largely missing in the JavaScript CLI ecosystem. This tool bridges that gap by autocompletions for `pnpm`, `npm`, `yarn`, and `bun` with dynamic option parsing and context-aware suggestions and also Easy-to-use adapters for popular JavaScript CLI frameworks like CAC, Citty, and Commander.js -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. +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. -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. +Tab solves this complexity by providing autocompletions that work consistently across `zsh`, `bash`, `fish`, and `powershell`. -## Examples -Check out the [examples directory](./examples) for complete examples of using Tab with different command-line frameworks: +### Installation -- [CAC](./examples/demo.cac.ts) -- [Citty](./examples/demo.citty.ts) -- [Commander.js](./examples/demo.commander.ts) +```bash +npm install @bomb.sh/tab +# or +pnpm add @bomb.sh/tab +# or +yarn add @bomb.sh/tab +# or +bun add @bomb.sh/tab +``` -## Usage +### Package Manager Completions -```ts -import { Completion, script } from '@bomb.sh/tab'; +Get autocompletions for your package manager with zero configuration: -const name = 'my-cli'; -const completion = new Completion(); +```bash +# this generates a completion script for your shell +npx @bomb.sh/tab pnpm zsh >> ~/.zshrc +npx @bomb.sh/tab npm bash >> ~/.bashrc +npx @bomb.sh/tab yarn fish > ~/.config/fish/completions/yarn.fish +npx @bomb.sh/tab bun powershell >> $PROFILE +``` -completion.addCommand( - 'start', - 'Start the application', - async (previousArgs, toComplete, endsWithSpace) => { - // suggestions - return [ - { value: 'dev', description: 'Start in development mode' }, - { value: 'prod', description: 'Start in production mode' }, - ]; - } -); - -completion.addOption( - 'start', - '--port', - 'Specify the port number', - async (previousArgs, toComplete, endsWithSpace) => { - return [ - { value: '3000', description: 'Development port' }, - { value: '8080', description: 'Production port' }, - ]; - } -); +You'd get smart completions for all commands and options, and dynamic option values e.g., `--reporter=`. and its always up-to-date (parsed from live help output) -// a way of getting the executable path to pass to the shell autocompletion script -function quoteIfNeeded(path: string) { - return path.includes(' ') ? `'${path}'` : path; -} -const execPath = process.execPath; -const processArgs = process.argv.slice(1); -const quotedExecPath = quoteIfNeeded(execPath); -const quotedProcessArgs = processArgs.map(quoteIfNeeded); -const quotedProcessExecArgs = process.execArgv.map(quoteIfNeeded); -const x = `${quotedExecPath} ${quotedProcessExecArgs.join(' ')} ${quotedProcessArgs[0]}`; - -if (process.argv[2] === '--') { - // autocompletion logic - await completion.parse(process.argv.slice(2), 'start'); // TODO: remove "start" -} else { - // process.argv[2] can be "zsh", "bash", "fish", "powershell" - script(process.argv[2], name, x); +**Example in action:** +```bash +pnpm install --reporter= +# Shows append-only, default, ndjson, silent + +npm remove +# Shows the packages from package.json + +yarn add --emoji= +# Show true, false +``` + +### CLI Framework Integration + +For your own CLI tools, tab provides seamless integration with popular frameworks: + +#### Using the Core API + +```typescript +import t from '@bomb.sh/tab'; + +t.command('dev', 'Start development server'); +t.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 === '--') { + // parse completion arguments + const args = process.argv.slice(4); + t.parse(args); + } else { + // generate shell script + t.setup('my-cli', 'node my-cli.js', shell); + } } ``` -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). +**Test your completions:** +```bash +node my-cli.js complete -- dev --p +# Output: --port Specify port + +node my-cli.js complete -- dev --port= +# Output: --port=3000 Development port +# --port=8080 Production port +``` -## Adapters +**Install for users:** +```bash +# One-time setup +source <(my-cli complete zsh) -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. +# Permanent setup +my-cli complete zsh >> ~/.zshrc +``` -### `@bomb.sh/tab/cac` +## Framework Adapters -```ts +Tab provides adapters for popular JavaScript CLI frameworks. + +### CAC Integration + +```typescript import cac from 'cac'; import tab from '@bomb.sh/tab/cac'; const cli = cac('my-cli'); -cli.command('dev', 'Start dev server').option('--port ', 'Specify port'); +// Define your CLI +cli.command('dev', 'Start dev server') + .option('--port ', 'Specify port') + .option('--host ', 'Specify host'); +// Initialize tab completions const completion = tab(cli); -// Get the dev command completion handler -const devCommandCompletion = completion.commands.get('dev'); - -// Get and configure the port option completion handler -const portOptionCompletion = devCommandCompletion.options.get('--port'); -portOptionCompletion.handler = async ( - previousArgs, - toComplete, - endsWithSpace -) => { - return [ +// Add custom completions for option values +const devCommand = completion.commands.get('dev'); +const portOption = devCommand?.options.get('--port'); +if (portOption) { + portOption.handler = async () => [ { value: '3000', description: 'Development port' }, { value: '8080', description: 'Production port' }, ]; -}; +} cli.parse(); ``` -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. +### Citty Integration -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. - -### `@bomb.sh/tab/citty` - -```ts -import citty, { defineCommand, createMain } from 'citty'; +```typescript +import { defineCommand, createMain } from 'citty'; import tab from '@bomb.sh/tab/citty'; const main = defineCommand({ - meta: { - name: 'my-cli', - description: 'My CLI tool', - }, -}); - -const devCommand = defineCommand({ - meta: { - name: 'dev', - description: 'Start dev server', - }, - args: { - port: { type: 'string', description: 'Specify port' }, + 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' }, + }, + }), }, }); -main.subCommands = { - dev: devCommand, -}; - +// Initialize tab completions const completion = await tab(main); -// TODO: addHandler function to export -const devCommandCompletion = completion.commands.get('dev'); - -const portOptionCompletion = devCommandCompletion.options.get('--port'); - -portOptionCompletion.handler = async ( - previousArgs, - toComplete, - endsWithSpace -) => { - return [ +// Add custom completions +const devCommand = completion.commands.get('dev'); +const portOption = devCommand?.options.get('--port'); +if (portOption) { + portOption.handler = async () => [ { value: '3000', description: 'Development port' }, { value: '8080', description: 'Production port' }, ]; -}; +} const cli = createMain(main); cli(); ``` -### `@bomb.sh/tab/commander` +### Commander.js Integration -```ts +```typescript import { Command } from 'commander'; import tab from '@bomb.sh/tab/commander'; const program = new Command('my-cli'); program.version('1.0.0'); -// Add commands +// Define commands program .command('serve') .description('Start the server') @@ -173,21 +183,18 @@ program console.log('Starting server...'); }); -// Initialize tab completion +// Initialize tab completions const completion = tab(program); -// Configure custom completions +// Add custom completions for (const command of completion.commands.values()) { - if (command.name === 'serve') { - for (const [option, config] of command.options.entries()) { - if (option === '--port') { - config.handler = () => { - return [ - { value: '3000', description: 'Default port' }, - { value: '8080', description: 'Alternative port' }, - ]; - }; - } + if (command.value === 'serve') { + const portOption = command.options.get('--port'); + if (portOption) { + portOption.handler = async () => [ + { value: '3000', description: 'Default port' }, + { value: '8080', description: 'Alternative port' }, + ]; } } } @@ -195,52 +202,28 @@ for (const command of completion.commands.values()) { program.parse(); ``` -## Recipe - -`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. -We suggest this approach for the end user that you as a maintainer might want to push. +Tab's package manager completions are dynamically generated from the actual help output of each tool: -``` -my-cli completion zsh > ~/completion-for-my-cli.zsh -echo 'source ~/completion-for-my-cli.zsh' >> ~/.zshrc -``` -For other shells: +Tab uses a standardized completion protocol that any CLI can implement: ```bash -# Bash -my-cli complete bash > ~/.bash_completion.d/my-cli -echo 'source ~/.bash_completion.d/my-cli' >> ~/.bashrc - -# Fish -my-cli complete fish > ~/.config/fish/completions/my-cli.fish +# Generate shell completion script +my-cli complete zsh -# PowerShell -my-cli complete powershell > $PROFILE.CurrentUserAllHosts +# Parse completion request (called by shell) +my-cli complete -- install --port="" ``` -## Autocompletion Server - -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. - -```zsh -my-cli complete -- --po ---port Specify the port number -:0 +**Output Format:** ``` +--port=3000 Development port +--port=8080 Production port +:4 +``` +## Contributing -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+$/`). - -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. - -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. - -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. - -Other package managers like `npm` and `yarn` can decide whether to support this or not too for more universal support. +We welcome contributions! Tab's architecture makes it easy to add support for new package managers or CLI frameworks. -## Inspiration -- git -- [cobra](https://github.com/spf13/cobra/blob/main/shell_completions.go), without cobra, tab would have took 10x longer to build From 36de8cd00667d4e10bf85efaeabe38bc8a25e9f5 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 21 Sep 2025 22:29:27 +0330 Subject: [PATCH 3/6] pretteir --- README.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 21937b5..003ecc8 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -> A video showcasing how pnpm autocompletions work on a test CLI command -like `my-cli` +> A video showcasing how pnpm autocompletions work on a test CLI command +> like `my-cli` # tab + Shell autocompletions are largely missing in the JavaScript CLI ecosystem. This tool bridges that gap by autocompletions for `pnpm`, `npm`, `yarn`, and `bun` with dynamic option parsing and context-aware suggestions and also Easy-to-use adapters for popular JavaScript CLI frameworks like CAC, Citty, and Commander.js 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 ```bash @@ -36,11 +36,12 @@ npx @bomb.sh/tab bun powershell >> $PROFILE You'd get smart completions for all commands and options, and dynamic option values e.g., `--reporter=`. and its always up-to-date (parsed from live help output) **Example in action:** + ```bash pnpm install --reporter= # Shows append-only, default, ndjson, silent -npm remove +npm remove # Shows the packages from package.json yarn add --emoji= @@ -77,25 +78,27 @@ if (process.argv[2] === 'complete') { ``` **Test your completions:** + ```bash node my-cli.js complete -- dev --p # Output: --port Specify port -node my-cli.js complete -- dev --port= +node my-cli.js complete -- dev --port= # Output: --port=3000 Development port # --port=8080 Production port ``` **Install for users:** + ```bash # One-time setup source <(my-cli complete zsh) -# Permanent setup +# Permanent setup my-cli complete zsh >> ~/.zshrc ``` -## Framework Adapters +## Framework Adapters Tab provides adapters for popular JavaScript CLI frameworks. @@ -108,9 +111,10 @@ import tab from '@bomb.sh/tab/cac'; const cli = cac('my-cli'); // Define your CLI -cli.command('dev', 'Start dev server') - .option('--port ', 'Specify port') - .option('--host ', 'Specify host'); +cli + .command('dev', 'Start dev server') + .option('--port ', 'Specify port') + .option('--host ', 'Specify host'); // Initialize tab completions const completion = tab(cli); @@ -202,10 +206,8 @@ for (const command of completion.commands.values()) { program.parse(); ``` - Tab's package manager completions are dynamically generated from the actual help output of each tool: - Tab uses a standardized completion protocol that any CLI can implement: ```bash @@ -217,13 +219,13 @@ my-cli complete -- install --port="" ``` **Output Format:** + ``` --port=3000 Development port ---port=8080 Production port +--port=8080 Production port :4 ``` + ## Contributing We welcome contributions! Tab's architecture makes it easy to add support for new package managers or CLI frameworks. - - From a0a4b252c1be6294215da432060f1a9cd7a38dd8 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 21 Sep 2025 22:35:22 +0330 Subject: [PATCH 4/6] update --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 003ecc8..012ea85 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,7 @@ npx @bomb.sh/tab yarn fish > ~/.config/fish/completions/yarn.fish npx @bomb.sh/tab bun powershell >> $PROFILE ``` -You'd get smart completions for all commands and options, and dynamic option values e.g., `--reporter=`. and its always up-to-date (parsed from live help output) - +You'd get completions for all commands and options, and dynamic option values e.g., `--reporter=`. **Example in action:** ```bash @@ -50,7 +49,7 @@ yarn add --emoji= ### CLI Framework Integration -For your own CLI tools, tab provides seamless integration with popular frameworks: +For your own CLI tools, tab provides integration with popular frameworks: #### Using the Core API @@ -226,6 +225,10 @@ my-cli complete -- install --port="" :4 ``` +## Docs + +For more detailed documentation, please visit [bombshell docs](https://bomb.sh/docs/tab/)! + ## Contributing We welcome contributions! Tab's architecture makes it easy to add support for new package managers or CLI frameworks. From 2e1424f4e329378f8a124377b32029411e208940 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Tue, 23 Sep 2025 12:21:28 +0330 Subject: [PATCH 5/6] update --- README.md | 67 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 012ea85..121f2f4 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,15 @@ # tab -Shell autocompletions are largely missing in the JavaScript CLI ecosystem. This tool bridges that gap by autocompletions for `pnpm`, `npm`, `yarn`, and `bun` with dynamic option parsing and context-aware suggestions and also Easy-to-use adapters for popular JavaScript CLI frameworks like CAC, Citty, and Commander.js +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`. 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 +## Installation ```bash npm install @bomb.sh/tab @@ -21,9 +23,50 @@ yarn add @bomb.sh/tab bun add @bomb.sh/tab ``` -### Package Manager Completions +## Quick Start + +Add autocompletions to your CLI tool: + +```typescript +import t from '@bomb.sh/tab'; + +// Define your CLI structure +t.command('dev', 'Start development server'); +t.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: + +```bash +node my-cli.js complete -- dev --port= +# Output: --port=3000 Development port +# --port=8080 Production port +``` + +Install for users: -Get autocompletions for your package manager with zero configuration: +```bash +source <(my-cli complete zsh) # One-time setup +my-cli complete zsh >> ~/.zshrc # Permanent setup +``` + +## Package Manager Completions + +As mentioned earlier, tab provides completions for package managers as well: ```bash # this generates a completion script for your shell @@ -33,23 +76,19 @@ npx @bomb.sh/tab yarn fish > ~/.config/fish/completions/yarn.fish npx @bomb.sh/tab bun powershell >> $PROFILE ``` -You'd get completions for all commands and options, and dynamic option values e.g., `--reporter=`. -**Example in action:** +Example in action: ```bash pnpm install --reporter= -# Shows append-only, default, ndjson, silent - -npm remove -# Shows the packages from package.json +# Shows: append-only, default, ndjson, silent (with descriptions) yarn add --emoji= -# Show true, false +# Shows: true, false ``` -### CLI Framework Integration +## Framework Integration -For your own CLI tools, tab provides integration with popular frameworks: +Tab includes adapters for popular JavaScript CLI frameworks: #### Using the Core API @@ -205,8 +244,6 @@ for (const command of completion.commands.values()) { program.parse(); ``` -Tab's package manager completions are dynamically generated from the actual help output of each tool: - Tab uses a standardized completion protocol that any CLI can implement: ```bash From 6a34165f411a52d6df9d42b917a6f9b1b1ddf84a Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Tue, 23 Sep 2025 14:16:33 +0330 Subject: [PATCH 6/6] update --- README.md | 113 +++++++++++++----------------------------------------- 1 file changed, 26 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 121f2f4..3c2710e 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ Add autocompletions to your CLI tool: import t from '@bomb.sh/tab'; // Define your CLI structure -t.command('dev', 'Start development server'); -t.option('port', 'Specify port', (complete) => { +const devCmd = t.command('dev', 'Start development server'); +devCmd.option('port', 'Specify port', (complete) => { complete('3000', 'Development port'); complete('8080', 'Production port'); }); @@ -60,8 +60,12 @@ node my-cli.js complete -- dev --port= Install for users: ```bash -source <(my-cli complete zsh) # One-time setup -my-cli complete zsh >> ~/.zshrc # Permanent setup +# 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 ``` ## Package Manager Completions @@ -69,11 +73,11 @@ my-cli complete zsh >> ~/.zshrc # Permanent setup As mentioned earlier, tab provides completions for package managers as well: ```bash -# this generates a completion script for your shell -npx @bomb.sh/tab pnpm zsh >> ~/.zshrc -npx @bomb.sh/tab npm bash >> ~/.bashrc +# Generate and install completion scripts +npx @bomb.sh/tab pnpm zsh > ~/.pnpm-completion.zsh && echo 'source ~/.pnpm-completion.zsh' >> ~/.zshrc +npx @bomb.sh/tab npm bash > ~/.npm-completion.bash && echo 'source ~/.npm-completion.bash' >> ~/.bashrc npx @bomb.sh/tab yarn fish > ~/.config/fish/completions/yarn.fish -npx @bomb.sh/tab bun powershell >> $PROFILE +npx @bomb.sh/tab bun powershell > ~/.bun-completion.ps1 && echo '. ~/.bun-completion.ps1' >> $PROFILE ``` Example in action: @@ -86,56 +90,6 @@ yarn add --emoji= # Shows: true, false ``` -## Framework Integration - -Tab includes adapters for popular JavaScript CLI frameworks: - -#### Using the Core API - -```typescript -import t from '@bomb.sh/tab'; - -t.command('dev', 'Start development server'); -t.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 === '--') { - // parse completion arguments - const args = process.argv.slice(4); - t.parse(args); - } else { - // generate shell script - t.setup('my-cli', 'node my-cli.js', shell); - } -} -``` - -**Test your completions:** - -```bash -node my-cli.js complete -- dev --p -# Output: --port Specify port - -node my-cli.js complete -- dev --port= -# Output: --port=3000 Development port -# --port=8080 Production port -``` - -**Install for users:** - -```bash -# One-time setup -source <(my-cli complete zsh) - -# Permanent setup -my-cli complete zsh >> ~/.zshrc -``` - ## Framework Adapters Tab provides adapters for popular JavaScript CLI frameworks. @@ -158,14 +112,10 @@ cli const completion = tab(cli); // Add custom completions for option values -const devCommand = completion.commands.get('dev'); -const portOption = devCommand?.options.get('--port'); -if (portOption) { - portOption.handler = async () => [ - { value: '3000', description: 'Development port' }, - { value: '8080', description: 'Production port' }, - ]; -} +completion.commands.get('dev')?.options.get('--port')!.handler = async () => [ + { value: '3000', description: 'Development port' }, + { value: '8080', description: 'Production port' }, +]; cli.parse(); ``` @@ -193,14 +143,10 @@ const main = defineCommand({ const completion = await tab(main); // Add custom completions -const devCommand = completion.commands.get('dev'); -const portOption = devCommand?.options.get('--port'); -if (portOption) { - portOption.handler = async () => [ - { value: '3000', description: 'Development port' }, - { value: '8080', description: 'Production port' }, - ]; -} +completion.commands.get('dev')?.options.get('--port')!.handler = async () => [ + { value: '3000', description: 'Development port' }, + { value: '8080', description: 'Production port' }, +]; const cli = createMain(main); cli(); @@ -229,17 +175,10 @@ program const completion = tab(program); // Add custom completions -for (const command of completion.commands.values()) { - if (command.value === 'serve') { - const portOption = command.options.get('--port'); - if (portOption) { - portOption.handler = async () => [ - { value: '3000', description: 'Default port' }, - { value: '8080', description: 'Alternative port' }, - ]; - } - } -} +completion.commands.get('serve')?.options.get('--port')!.handler = async () => [ + { value: '3000', description: 'Default port' }, + { value: '8080', description: 'Alternative port' }, +]; program.parse(); ``` @@ -262,9 +201,9 @@ my-cli complete -- install --port="" :4 ``` -## Docs +## Documentation -For more detailed documentation, please visit [bombshell docs](https://bomb.sh/docs/tab/)! +See [bombshell docs](https://bomb.sh/docs/tab/). ## Contributing