[READ-ONLY] Mirror of https://github.com/bombshell-dev/docs. bomb.sh/docs
0

Configure Feed

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

feat: Add documentation for Tab (#17)

authored by

Paul Valladares and committed by
GitHub
(Aug 7, 2025, 11:57 AM -0600) b69ff49b 255c6eac

+1675 -1
+12 -1
astro.config.mjs
··· 101 101 link: "args/api", 102 102 }, 103 103 ], 104 - } 104 + }, 105 + { 106 + label: "Tab", 107 + id: "tab", 108 + icon: "right-caret", 109 + link: "/tab", 110 + items: [ 111 + { label: "Basics", autogenerate: { directory: "tab/basics" } }, 112 + { label: "API", autogenerate: { directory: "tab/api" } }, 113 + { label: "Guides", autogenerate: { directory: "tab/guides" } }, 114 + ] 115 + }, 105 116 ]), 106 117 ], 107 118 }),
+295
src/content/docs/tab/api/core.mdx
··· 1 + --- 2 + title: Core API 3 + description: Complete API reference for the Tab core package 4 + --- 5 + 6 + This page provides the complete API reference for the Tab core package, including all classes, methods, types, and interfaces. 7 + 8 + ## Classes 9 + 10 + ### Completion 11 + 12 + The main class for managing command and option completions. 13 + 14 + ```ts 15 + import { Completion } from '@bombsh/tab'; 16 + 17 + const completion = new Completion(); 18 + ``` 19 + 20 + #### Methods 21 + 22 + ##### addCommand 23 + 24 + Adds a completion handler for a command. 25 + 26 + **Parameters:** 27 + - `name` (string): The command name 28 + - `description` (string): Command description 29 + - `args` (boolean[]): Array indicating which arguments are required (false) or optional (true) 30 + - `handler` (Handler): Function that returns completion suggestions 31 + - `parent` (string, optional): Parent command name for nested commands 32 + 33 + **Returns:** string - The command key 34 + 35 + **Example:** 36 + ```ts 37 + // Command with required argument: "vite <entry>" 38 + completion.addCommand('dev', 'Start development server', [false], async () => { 39 + return [ 40 + { value: 'src/main.ts', description: 'Main entry point' }, 41 + { value: 'src/index.ts', description: 'Index entry point' }, 42 + ]; 43 + }); 44 + 45 + // Command with optional argument: "vite [entry]" 46 + completion.addCommand('serve', 'Start the server', [true], async () => { 47 + return [ 48 + { value: 'dist', description: 'Distribution directory' }, 49 + ]; 50 + }); 51 + 52 + // Nested command: "vite dev build" 53 + completion.addCommand('build', 'Build project', [], async () => { 54 + return [ 55 + { value: 'build', description: 'Build command' }, 56 + ]; 57 + }, 'dev'); 58 + ``` 59 + 60 + ##### addOption 61 + 62 + Adds a completion handler for an option of a specific command. 63 + 64 + **Parameters:** 65 + - `command` (string): The command name 66 + - `option` (string): The option name (e.g., '--port') 67 + - `description` (string): Option description 68 + - `handler` (Handler): Function that returns completion suggestions 69 + - `alias` (string, optional): Short flag alias (e.g., 'p' for '--port') 70 + 71 + **Returns:** string - The option name 72 + 73 + **Example:** 74 + ```ts 75 + completion.addOption('dev', '--port', 'Port number', async () => { 76 + return [ 77 + { value: '3000', description: 'Development port' }, 78 + { value: '8080', description: 'Production port' }, 79 + ]; 80 + }, 'p'); 81 + ``` 82 + 83 + ##### parse 84 + 85 + Parses command line arguments and outputs completion suggestions to stdout. 86 + 87 + **Parameters:** 88 + - `args` (string[]): Command line arguments 89 + 90 + **Example:** 91 + ```ts 92 + await completion.parse(['--port']); 93 + // Outputs completion suggestions to stdout 94 + ``` 95 + 96 + ### script 97 + 98 + Generates shell completion scripts. 99 + 100 + **Parameters:** 101 + - `shell` (string): Target shell ('zsh', 'bash', 'fish', 'powershell') 102 + - `name` (string): CLI tool name 103 + - `execPath` (string): Executable path for the CLI tool 104 + 105 + **Example:** 106 + ```ts 107 + import { script } from '@bombsh/tab'; 108 + 109 + script('zsh', 'my-cli', '/usr/bin/node /path/to/my-cli'); 110 + ``` 111 + 112 + ## Types 113 + 114 + ### Handler 115 + 116 + Function type for completion handlers. 117 + 118 + ```ts 119 + type Handler = ( 120 + previousArgs: string[], 121 + toComplete: string, 122 + endsWithSpace: boolean 123 + ) => Item[] | Promise<Item[]>; 124 + ``` 125 + 126 + **Parameters:** 127 + - `previousArgs` (string[]): Previously typed arguments 128 + - `toComplete` (string): The text being completed 129 + - `endsWithSpace` (boolean): Whether the input ends with a space 130 + 131 + **Returns:** Item[] | Promise&lt;Item[]&gt; 132 + 133 + **Example:** 134 + ```ts 135 + const handler: Handler = async (previousArgs, toComplete, endsWithSpace) => { 136 + // Check if user is typing 'prod' to suggest production 137 + if (toComplete.startsWith('prod')) { 138 + return [ 139 + { value: 'production', description: 'Production environment' }, 140 + ]; 141 + } 142 + 143 + return [ 144 + { value: 'development', description: 'Development environment' }, 145 + { value: 'staging', description: 'Staging environment' }, 146 + { value: 'production', description: 'Production environment' }, 147 + ]; 148 + }; 149 + ``` 150 + 151 + ### Item 152 + 153 + Object representing a completion suggestion. 154 + 155 + ```ts 156 + type Item = { 157 + value: string; 158 + description: string; 159 + }; 160 + ``` 161 + 162 + **Properties:** 163 + - `value` (string): The completion value 164 + - `description` (string): Description of the completion 165 + 166 + **Example:** 167 + ```ts 168 + const item: Item = { 169 + value: '3000', 170 + description: 'Development port' 171 + }; 172 + ``` 173 + 174 + ### `Positional` 175 + 176 + Type for positional argument configuration. 177 + 178 + ```ts 179 + type Positional = { 180 + required: boolean; 181 + variadic: boolean; 182 + completion: Handler; 183 + }; 184 + ``` 185 + 186 + ## Constants 187 + 188 + ### ShellCompRequestCmd 189 + 190 + The name of the hidden command used to request completion results. 191 + 192 + ```ts 193 + export const ShellCompRequestCmd: string = '__complete'; 194 + ``` 195 + 196 + ### ShellCompNoDescRequestCmd 197 + 198 + The name of the hidden command used to request completion results without descriptions. 199 + 200 + ```ts 201 + export const ShellCompNoDescRequestCmd: string = '__completeNoDesc'; 202 + ``` 203 + 204 + ### ShellCompDirective 205 + 206 + Bit map representing different behaviors the shell can be instructed to have. 207 + 208 + ```ts 209 + export const ShellCompDirective = { 210 + ShellCompDirectiveError: 1 << 0, 211 + ShellCompDirectiveNoSpace: 1 << 1, 212 + ShellCompDirectiveNoFileComp: 1 << 2, 213 + ShellCompDirectiveFilterFileExt: 1 << 3, 214 + ShellCompDirectiveFilterDirs: 1 << 4, 215 + ShellCompDirectiveKeepOrder: 1 << 5, 216 + ShellCompDirectiveDefault: 0, 217 + }; 218 + ``` 219 + 220 + ## Complete Example 221 + 222 + Here's a complete example showing all the core API features: 223 + 224 + ```ts 225 + #!/usr/bin/env node 226 + import { Completion, script } from '@bombsh/tab'; 227 + 228 + const name = 'my-cli'; 229 + const completion = new Completion(); 230 + 231 + // Add command completions with positional arguments 232 + completion.addCommand('dev', 'Start development server', [false], async (previousArgs, toComplete, endsWithSpace) => { 233 + if (toComplete.startsWith('dev')) { 234 + return [ 235 + { value: 'dev', description: 'Start in development mode' }, 236 + ]; 237 + } 238 + 239 + return [ 240 + { value: 'dev', description: 'Start in development mode' }, 241 + { value: 'prod', description: 'Start in production mode' }, 242 + ]; 243 + }); 244 + 245 + // Add option completions 246 + completion.addOption('dev', '--port', 'Port number', async (previousArgs, toComplete, endsWithSpace) => { 247 + if (toComplete.startsWith('30')) { 248 + return [ 249 + { value: '3000', description: 'Development port' }, 250 + ]; 251 + } 252 + 253 + return [ 254 + { value: '3000', description: 'Development port' }, 255 + { value: '8080', description: 'Production port' }, 256 + ]; 257 + }, 'p'); 258 + 259 + // Helper function to quote paths with spaces 260 + function quoteIfNeeded(path: string) { 261 + return path.includes(' ') ? `'${path}'` : path; 262 + } 263 + 264 + // Get the executable path for shell completion 265 + const execPath = process.execPath; 266 + const processArgs = process.argv.slice(1); 267 + const quotedExecPath = quoteIfNeeded(execPath); 268 + const quotedProcessArgs = processArgs.map(quoteIfNeeded); 269 + const quotedProcessExecArgs = process.execArgv.map(quoteIfNeeded); 270 + const x = `${quotedExecPath} ${quotedProcessExecArgs.join(' ')} ${quotedProcessArgs[0]}`; 271 + 272 + // Handle completion requests 273 + if (process.argv[2] === '--') { 274 + try { 275 + await completion.parse(process.argv.slice(2)); 276 + } catch (error) { 277 + console.error('Completion error:', error.message); 278 + process.exit(1); 279 + } 280 + } else { 281 + const shell = process.argv[2]; 282 + if (['zsh', 'bash', 'fish', 'powershell'].includes(shell)) { 283 + script(shell, name, x); 284 + } else { 285 + console.error(`Unsupported shell: ${shell}`); 286 + process.exit(1); 287 + } 288 + } 289 + ``` 290 + 291 + ## Next Steps 292 + 293 + - Learn about [Framework Adapters](/docs/tab/guides/adapters/) for easier integration 294 + - Check out [Examples](/docs/tab/guides/examples/) for practical use cases 295 + - Explore [Best Practices](/docs/tab/guides/best-practices/) for effective implementations
+169
src/content/docs/tab/basics/getting-started.mdx
··· 1 + --- 2 + title: Getting Started 3 + description: Learn how to add shell autocompletions to your CLI with Tab 4 + --- 5 + 6 + import { Tabs, TabItem } from '@astrojs/starlight/components'; 7 + 8 + Tab provides shell autocompletions for JavaScript CLI tools, making your CLI feel as polished as native tools like git. This guide will walk you through adding autocompletions to your CLI tool. 9 + 10 + ## Installation 11 + 12 + Install Tab using your preferred package manager: 13 + 14 + <Tabs> 15 + <TabItem label="npm" icon="seti:npm"> 16 + ```bash 17 + npm install @bombsh/tab 18 + ``` 19 + </TabItem> 20 + <TabItem label="pnpm" icon="pnpm"> 21 + ```bash 22 + pnpm add @bombsh/tab 23 + ``` 24 + </TabItem> 25 + <TabItem label="Yarn" icon="seti:yarn"> 26 + ```bash 27 + yarn add @bombsh/tab 28 + ``` 29 + </TabItem> 30 + </Tabs> 31 + 32 + ## Basic Usage 33 + 34 + Here's a simple example of how to add autocompletions to your CLI: 35 + 36 + ```ts 37 + import { Completion, script } from '@bombsh/tab'; 38 + 39 + const name = 'my-cli'; 40 + const completion = new Completion(); 41 + 42 + // Add command completions with positional arguments 43 + completion.addCommand( 44 + 'start', 45 + 'Start the application', 46 + [false], // Required argument 47 + async (previousArgs, toComplete, endsWithSpace) => { 48 + return [ 49 + { value: 'dev', description: 'Start in development mode' }, 50 + { value: 'prod', description: 'Start in production mode' }, 51 + ]; 52 + } 53 + ); 54 + 55 + // Add option completions 56 + completion.addOption( 57 + 'start', 58 + '--port', 59 + 'Specify the port number', 60 + async (previousArgs, toComplete, endsWithSpace) => { 61 + return [ 62 + { value: '3000', description: 'Development port' }, 63 + { value: '8080', description: 'Production port' }, 64 + ]; 65 + }, 66 + 'p' // Short flag alias 67 + ); 68 + 69 + // Helper function to quote paths with spaces 70 + function quoteIfNeeded(path: string) { 71 + return path.includes(' ') ? `'${path}'` : path; 72 + } 73 + 74 + // Get the executable path for shell completion 75 + const execPath = process.execPath; 76 + const processArgs = process.argv.slice(1); 77 + const quotedExecPath = quoteIfNeeded(execPath); 78 + const quotedProcessArgs = processArgs.map(quoteIfNeeded); 79 + const quotedProcessExecArgs = process.execArgv.map(quoteIfNeeded); 80 + const x = `${quotedExecPath} ${quotedProcessExecArgs.join(' ')} ${quotedProcessArgs[0]}`; 81 + 82 + // Handle completion requests 83 + if (process.argv[2] === '--') { 84 + // Autocompletion logic 85 + await completion.parse(process.argv.slice(2)); 86 + } else { 87 + // Generate shell completion script 88 + script(process.argv[2], name, x); 89 + } 90 + ``` 91 + 92 + ## Understanding Positional Arguments 93 + 94 + The `args` parameter in `addCommand` is an array of booleans that indicates which arguments are required or optional: 95 + 96 + ```ts 97 + // Command: "my-cli start <entry>" 98 + completion.addCommand('start', 'Start the app', [false], handler); 99 + 100 + // Command: "my-cli serve [entry]" 101 + completion.addCommand('serve', 'Serve the app', [true], handler); 102 + 103 + // Command: "my-cli build <entry> [output]" 104 + completion.addCommand('build', 'Build the app', [false, true], handler); 105 + 106 + // Command: "my-cli dev [...files]" 107 + completion.addCommand('dev', 'Dev mode', [true], handler); // Variadic argument 108 + ``` 109 + 110 + ## Shell Setup 111 + 112 + After adding Tab to your CLI, users need to set up shell completion. Here are the recommended approaches for different shells: 113 + 114 + ### Zsh 115 + 116 + ```bash 117 + # Generate completion script 118 + my-cli complete zsh > ~/completion-for-my-cli.zsh 119 + 120 + # Add to your .zshrc 121 + echo 'source ~/completion-for-my-cli.zsh' >> ~/.zshrc 122 + ``` 123 + 124 + ### Bash 125 + 126 + ```bash 127 + # Generate completion script 128 + my-cli complete bash > ~/.bash_completion.d/my-cli 129 + 130 + # Add to your .bashrc 131 + echo 'source ~/.bash_completion.d/my-cli' >> ~/.bashrc 132 + ``` 133 + 134 + ### Fish 135 + 136 + ```bash 137 + # Generate completion script 138 + my-cli complete fish > ~/.config/fish/completions/my-cli.fish 139 + ``` 140 + 141 + ### PowerShell 142 + 143 + ```powershell 144 + # Generate completion script 145 + my-cli complete powershell > $PROFILE.CurrentUserAllHosts 146 + ``` 147 + 148 + ## How It Works 149 + 150 + Tab works by adding a `complete` command to your CLI that generates shell-specific completion scripts. When users type `my-cli complete zsh`, Tab generates a completion script that the shell can source. 151 + 152 + The completion script communicates with your CLI through the autocompletion server. When users press Tab, the shell calls your CLI with special arguments, and Tab returns the appropriate completions. 153 + 154 + ### Autocompletion Server 155 + 156 + Your CLI becomes an autocompletion server when you add Tab. The server responds to completion requests with suggestions and ends its output with `:{Number}` to indicate the number of completions. 157 + 158 + For example: 159 + ```bash 160 + my-cli complete -- --po 161 + --port Specify the port number 162 + :0 163 + ``` 164 + 165 + ## Next Steps 166 + 167 + - Learn about [Framework Adapters](/docs/tab/guides/adapters/) for easier integration with CAC, Citty, and Commander.js 168 + - Explore [Best Practices](/docs/tab/guides/best-practices/) for implementing effective autocompletions 169 + - Check out the [API Reference](/docs/tab/api/core/) for detailed documentation
+299
src/content/docs/tab/guides/adapters.mdx
··· 1 + --- 2 + title: Adapters 3 + description: Use Tab with popular CLI frameworks like CAC, Citty, and Commander.js 4 + --- 5 + 6 + Tab provides adapters for popular CLI frameworks to make integration even easier. These adapters automatically extract commands and options from your CLI framework and allow you to add custom completion handlers. 7 + 8 + ## CAC Adapter 9 + 10 + The CAC adapter automatically detects commands and options from your CAC instance and provides completion handlers for customization. 11 + 12 + ```ts 13 + import cac from 'cac'; 14 + import tab from '@bombsh/tab/cac'; 15 + 16 + const cli = cac('my-cli'); 17 + 18 + cli.command('dev', 'Start dev server').option('--port <port>', 'Specify port'); 19 + cli.command('build', 'Build for production').option('--mode <mode>', 'Build mode'); 20 + 21 + const completion = await tab(cli); 22 + 23 + // Get the dev command completion handler 24 + const devCommandCompletion = completion.commands.get('dev'); 25 + 26 + // Get and configure the port option completion handler 27 + const portOptionCompletion = devCommandCompletion.options.get('--port'); 28 + portOptionCompletion.handler = async ( 29 + previousArgs, 30 + toComplete, 31 + endsWithSpace 32 + ) => { 33 + return [ 34 + { value: '3000', description: 'Development port' }, 35 + { value: '8080', description: 'Production port' }, 36 + ]; 37 + }; 38 + 39 + // Configure build mode completions 40 + const buildCommandCompletion = completion.commands.get('build'); 41 + const modeOptionCompletion = buildCommandCompletion.options.get('--mode'); 42 + modeOptionCompletion.handler = async () => { 43 + return [ 44 + { value: 'development', description: 'Development build' }, 45 + { value: 'production', description: 'Production build' }, 46 + ]; 47 + }; 48 + 49 + cli.parse(); 50 + ``` 51 + 52 + ### How the CAC Adapter Works 53 + 54 + The CAC adapter: 55 + 56 + 1. **Extracts Commands**: Automatically detects all commands defined with `cli.command()` 57 + 2. **Extracts Options**: Identifies options defined with `.option()` for each command 58 + 3. **Provides Handlers**: Gives you access to completion handlers for each command and option 59 + 4. **Maintains Structure**: Preserves the command hierarchy and option relationships 60 + 61 + ## Citty Adapter 62 + 63 + The Citty adapter works with Citty's command definitions and provides a similar interface for adding completions. 64 + 65 + ```ts 66 + import { defineCommand, createMain } from 'citty'; 67 + import tab from '@bombsh/tab/citty'; 68 + 69 + const main = defineCommand({ 70 + meta: { 71 + name: 'my-cli', 72 + description: 'My CLI tool', 73 + }, 74 + }); 75 + 76 + const devCommand = defineCommand({ 77 + meta: { 78 + name: 'dev', 79 + description: 'Start dev server', 80 + }, 81 + args: { 82 + port: { type: 'string', description: 'Specify port' }, 83 + host: { type: 'string', description: 'Specify host' }, 84 + }, 85 + }); 86 + 87 + const buildCommand = defineCommand({ 88 + meta: { 89 + name: 'build', 90 + description: 'Build for production', 91 + }, 92 + args: { 93 + mode: { type: 'string', description: 'Build mode' }, 94 + }, 95 + }); 96 + 97 + main.subCommands = { 98 + dev: devCommand, 99 + build: buildCommand, 100 + }; 101 + 102 + const completion = await tab(main); 103 + 104 + // Configure completions 105 + const devCommandCompletion = completion.commands.get('dev'); 106 + if (devCommandCompletion) { 107 + const portOptionCompletion = devCommandCompletion.options.get('--port'); 108 + if (portOptionCompletion) { 109 + portOptionCompletion.handler = async () => { 110 + return [ 111 + { value: '3000', description: 'Development port' }, 112 + { value: '8080', description: 'Production port' }, 113 + ]; 114 + }; 115 + } 116 + } 117 + 118 + const cli = createMain(main); 119 + cli(); 120 + ``` 121 + 122 + ### How the Citty Adapter Works 123 + 124 + The Citty adapter: 125 + 126 + 1. **Processes Command Definitions**: Analyzes the command structure defined with `defineCommand()` 127 + 2. **Extracts Arguments**: Identifies arguments defined in the `args` object 128 + 3. **Handles Subcommands**: Recursively processes nested subcommands 129 + 4. **Provides Type Safety**: Leverages Citty's type system for better completion accuracy 130 + 131 + ## Commander Adapter 132 + 133 + The Commander adapter works with Commander.js and provides completion handlers for its command structure. 134 + 135 + ```ts 136 + import { Command } from 'commander'; 137 + import tab from '@bombsh/tab/commander'; 138 + 139 + const program = new Command(); 140 + 141 + program 142 + .name('my-cli') 143 + .description('My CLI tool'); 144 + 145 + program 146 + .command('dev') 147 + .description('Start development server') 148 + .option('-p, --port <port>', 'Specify port') 149 + .option('-h, --host <host>', 'Specify host'); 150 + 151 + program 152 + .command('build') 153 + .description('Build for production') 154 + .option('-m, --mode <mode>', 'Build mode'); 155 + 156 + const completion = tab(program); 157 + 158 + // Configure completions 159 + const devCommandCompletion = completion.commands.get('dev'); 160 + if (devCommandCompletion) { 161 + const portOptionCompletion = devCommandCompletion.options.get('--port'); 162 + if (portOptionCompletion) { 163 + portOptionCompletion.handler = async () => { 164 + return [ 165 + { value: '3000', description: 'Development port' }, 166 + { value: '8080', description: 'Production port' }, 167 + ]; 168 + }; 169 + } 170 + } 171 + 172 + program.parse(); 173 + ``` 174 + 175 + ### How the Commander Adapter Works 176 + 177 + The Commander adapter: 178 + 179 + 1. **Processes Commands**: Analyzes the command structure defined with `program.command()` 180 + 2. **Extracts Options**: Identifies options defined with `.option()` 181 + 3. **Handles Aliases**: Automatically processes short and long option aliases 182 + 4. **Maintains Hierarchy**: Preserves the command hierarchy and option relationships 183 + 184 + ## Configuration 185 + 186 + All adapters support a configuration object to customize completion behavior: 187 + 188 + ```ts 189 + import { CompletionConfig } from '@bombsh/tab'; 190 + 191 + const config: CompletionConfig = { 192 + handler: async (previousArgs, toComplete, endsWithSpace) => { 193 + // Default handler for all commands 194 + return []; 195 + }, 196 + options: { 197 + port: { 198 + handler: async () => { 199 + return [ 200 + { value: '3000', description: 'Development port' }, 201 + { value: '8080', description: 'Production port' }, 202 + ]; 203 + }, 204 + }, 205 + }, 206 + subCommands: { 207 + dev: { 208 + handler: async () => { 209 + return [ 210 + { value: 'dev', description: 'Development mode' }, 211 + ]; 212 + }, 213 + options: { 214 + port: { 215 + handler: async () => { 216 + return [ 217 + { value: '3000', description: 'Dev port' }, 218 + ]; 219 + }, 220 + }, 221 + }, 222 + }, 223 + }, 224 + }; 225 + 226 + // Use with any adapter 227 + const completion = await tab(cli, config); 228 + ``` 229 + 230 + ## Best Practices 231 + 232 + ### 1. Error Handling 233 + 234 + Always handle errors gracefully in your completion handlers: 235 + 236 + ```ts 237 + portOptionCompletion.handler = async () => { 238 + try { 239 + // Expensive operation 240 + const results = await getPorts(); 241 + return results.map(port => ({ 242 + value: port.toString(), 243 + description: `Port ${port}` 244 + })); 245 + } catch (error) { 246 + // Return empty array instead of throwing 247 + console.error('Error in completion handler:', error); 248 + return []; 249 + } 250 + }; 251 + ``` 252 + 253 + ### 2. Context-Aware Completions 254 + 255 + Make your completions responsive to what the user is typing: 256 + 257 + ```ts 258 + portOptionCompletion.handler = async (previousArgs, toComplete, endsWithSpace) => { 259 + if (toComplete.startsWith('30')) { 260 + return [ 261 + { value: '3000', description: 'Development port' }, 262 + ]; 263 + } 264 + 265 + return [ 266 + { value: '3000', description: 'Development port' }, 267 + { value: '8080', description: 'Production port' }, 268 + { value: '9000', description: 'Alternative port' }, 269 + ]; 270 + }; 271 + ``` 272 + 273 + ### 3. Performance Optimization 274 + 275 + Cache expensive operations and limit results: 276 + 277 + ```ts 278 + let cachedPorts: Item[] | null = null; 279 + 280 + portOptionCompletion.handler = async () => { 281 + if (cachedPorts) { 282 + return cachedPorts; 283 + } 284 + 285 + const ports = await getAvailablePorts(); 286 + cachedPorts = ports.slice(0, 20).map(port => ({ 287 + value: port.toString(), 288 + description: `Port ${port}` 289 + })); 290 + 291 + return cachedPorts; 292 + }; 293 + ``` 294 + 295 + ## Next Steps 296 + 297 + - Learn about [Best Practices](/docs/tab/guides/best-practices/) for effective autocompletions 298 + - Check out [Examples](/docs/tab/guides/examples/) for practical use cases 299 + - Explore the [API Reference](/docs/tab/api/core/) for advanced usage
+312
src/content/docs/tab/guides/best-practices.mdx
··· 1 + --- 2 + title: Best Practices 3 + description: Learn best practices for implementing effective autocompletions with Tab 4 + --- 5 + 6 + This guide covers best practices for implementing autocompletions that provide a great user experience and integrate seamlessly with your CLI tool. 7 + 8 + ## Completion Handler Design 9 + 10 + ### Keep Handlers Fast 11 + 12 + Completion handlers should return results quickly since they're called frequently as users type: 13 + 14 + ```ts 15 + // ✅ Good: Fast, synchronous completion 16 + completion.addOption('dev', '--port', 'Port number', () => { 17 + return [ 18 + { value: '3000', description: 'Development port' }, 19 + { value: '8080', description: 'Production port' }, 20 + ]; 21 + }); 22 + 23 + // ❌ Avoid: Slow, async operations in completion handlers 24 + completion.addOption('dev', '--file', 'File path', async () => { 25 + const files = await fs.readdir('.'); // This can be slow 26 + return files.map(f => ({ value: f, description: 'File' })); 27 + }); 28 + ``` 29 + 30 + ### Use Context Appropriately 31 + 32 + Leverage the completion context to provide relevant suggestions: 33 + 34 + ```ts 35 + completion.addOption('deploy', '--env', 'Environment', async (previousArgs, toComplete, endsWithSpace) => { 36 + // Check if user is completing an environment name 37 + if (toComplete.startsWith('prod')) { 38 + return [ 39 + { value: 'production', description: 'Production environment' }, 40 + ]; 41 + } 42 + 43 + return [ 44 + { value: 'development', description: 'Development environment' }, 45 + { value: 'staging', description: 'Staging environment' }, 46 + { value: 'production', description: 'Production environment' }, 47 + ]; 48 + }); 49 + ``` 50 + 51 + ### Provide Meaningful Descriptions 52 + 53 + Always include descriptions to help users understand what each completion means: 54 + 55 + ```ts 56 + // ✅ Good: Clear descriptions 57 + completion.addCommand('deploy', 'Deploy application', () => { 58 + return [ 59 + { value: 'dev', description: 'Deploy to development environment' }, 60 + { value: 'staging', description: 'Deploy to staging environment' }, 61 + { value: 'prod', description: 'Deploy to production environment' }, 62 + ]; 63 + }); 64 + 65 + // ❌ Avoid: No descriptions 66 + completion.addCommand('deploy', 'Deploy application', () => { 67 + return [ 68 + { value: 'dev' }, 69 + { value: 'staging' }, 70 + { value: 'prod' }, 71 + ]; 72 + }); 73 + ``` 74 + 75 + ## Shell Integration 76 + 77 + ### Handle Spaces Correctly 78 + 79 + Pay attention to the `endsWithSpace` parameter to provide appropriate completions: 80 + 81 + ```ts 82 + completion.addOption('dev', '--config', 'Config file', async (previousArgs, toComplete, endsWithSpace) => { 83 + if (endsWithSpace) { 84 + // User typed a space, suggest files 85 + return [ 86 + { value: 'config.json', description: 'JSON config file' }, 87 + { value: 'config.yaml', description: 'YAML config file' }, 88 + ]; 89 + } else { 90 + // User is typing, filter based on input 91 + const suggestions = [ 92 + { value: 'config.json', description: 'JSON config file' }, 93 + { value: 'config.yaml', description: 'YAML config file' }, 94 + ]; 95 + 96 + return suggestions.filter(s => s.value.startsWith(toComplete)); 97 + } 98 + }); 99 + ``` 100 + 101 + ### Support Multiple Shells 102 + 103 + Test your completions across different shells to ensure compatibility: 104 + 105 + ```ts 106 + // Generate completion scripts for all supported shells 107 + if (process.argv[2] === '--') { 108 + await completion.parse(process.argv.slice(2), 'start'); 109 + } else { 110 + const shell = process.argv[2]; 111 + if (['zsh', 'bash', 'fish', 'powershell'].includes(shell)) { 112 + script(shell, 'my-cli', execPath); 113 + } else { 114 + console.error(`Unsupported shell: ${shell}`); 115 + process.exit(1); 116 + } 117 + } 118 + ``` 119 + 120 + ## User Experience 121 + 122 + ### Progressive Disclosure 123 + 124 + Show relevant completions based on what the user has already typed: 125 + 126 + ```ts 127 + completion.addCommand('deploy', 'Deploy application', async (previousArgs, toComplete, endsWithSpace) => { 128 + // If user typed 'prod', only show production-related options 129 + if (toComplete.startsWith('prod')) { 130 + return [ 131 + { value: 'production', description: 'Production environment' }, 132 + ]; 133 + } 134 + 135 + // Show all options otherwise 136 + return [ 137 + { value: 'development', description: 'Development environment' }, 138 + { value: 'staging', description: 'Staging environment' }, 139 + { value: 'production', description: 'Production environment' }, 140 + ]; 141 + }); 142 + ``` 143 + 144 + ### Consistent Naming 145 + 146 + Use consistent naming patterns across your CLI: 147 + 148 + ```ts 149 + // ✅ Good: Consistent naming 150 + completion.addCommand('deploy', 'Deploy application', () => { 151 + return [ 152 + { value: 'dev', description: 'Deploy to development' }, 153 + { value: 'staging', description: 'Deploy to staging' }, 154 + { value: 'prod', description: 'Deploy to production' }, 155 + ]; 156 + }); 157 + 158 + completion.addCommand('build', 'Build application', () => { 159 + return [ 160 + { value: 'dev', description: 'Development build' }, 161 + { value: 'staging', description: 'Staging build' }, 162 + { value: 'prod', description: 'Production build' }, 163 + ]; 164 + }); 165 + ``` 166 + 167 + ### Error Handling 168 + 169 + Handle errors gracefully in completion handlers: 170 + 171 + ```ts 172 + completion.addOption('deploy', '--config', 'Config file', async () => { 173 + try { 174 + const files = await fs.readdir('.'); 175 + return files 176 + .filter(f => f.endsWith('.json') || f.endsWith('.yaml')) 177 + .map(f => ({ value: f, description: `Config file: ${f}` })); 178 + } catch (error) { 179 + // Return empty array instead of throwing 180 + console.error('Error reading config files:', error); 181 + return []; 182 + } 183 + }); 184 + ``` 185 + 186 + ## Performance Considerations 187 + 188 + ### Cache Expensive Operations 189 + 190 + Cache results for expensive operations that don't change frequently: 191 + 192 + ```ts 193 + let cachedPorts: Array<{ value: string; description: string }> | null = null; 194 + 195 + completion.addOption('dev', '--port', 'Port number', async () => { 196 + if (cachedPorts) { 197 + return cachedPorts; 198 + } 199 + 200 + // Expensive operation (e.g., reading from config) 201 + const ports = await getAvailablePorts(); 202 + cachedPorts = ports.map(p => ({ 203 + value: p.toString(), 204 + description: `Port ${p}` 205 + })); 206 + 207 + return cachedPorts; 208 + }); 209 + ``` 210 + 211 + ### Limit Result Sets 212 + 213 + Don't overwhelm users with too many completions: 214 + 215 + ```ts 216 + completion.addOption('search', '--file', 'File pattern', async (previousArgs, toComplete, endsWithSpace) => { 217 + const files = await getMatchingFiles(toComplete); 218 + 219 + // Limit to 20 results to avoid overwhelming the user 220 + return files.slice(0, 20).map(f => ({ 221 + value: f, 222 + description: `File: ${f}` 223 + })); 224 + }); 225 + ``` 226 + 227 + ## Testing 228 + 229 + ### Test Completion Handlers 230 + 231 + Write tests for your completion handlers to ensure they work correctly: 232 + 233 + ```ts 234 + import { Completion } from '@bombsh/tab'; 235 + 236 + describe('CLI Completions', () => { 237 + let completion: Completion; 238 + 239 + beforeEach(() => { 240 + completion = new Completion(); 241 + // Setup your completion handlers 242 + }); 243 + 244 + test('should suggest ports for --port option', async () => { 245 + const result = await completion.parse(['--port'], 'dev'); 246 + expect(result).toContainEqual({ value: '3000', description: 'Development port' }); 247 + }); 248 + 249 + test('should filter suggestions based on input', async () => { 250 + const result = await completion.parse(['--port', '30'], 'dev'); 251 + expect(result).toContainEqual({ value: '3000', description: 'Development port' }); 252 + expect(result).not.toContainEqual({ value: '8080', description: 'Production port' }); 253 + }); 254 + }); 255 + ``` 256 + 257 + ### Test Shell Integration 258 + 259 + Test that your completion scripts work correctly in different shells: 260 + 261 + ```bash 262 + # Test zsh completion 263 + source <(my-cli complete zsh) 264 + my-cli dev --po<TAB> # Should suggest --port 265 + 266 + # Test bash completion 267 + source <(my-cli complete bash) 268 + my-cli dev --po<TAB> # Should suggest --port 269 + ``` 270 + 271 + ## Common Patterns 272 + 273 + ### File Completions 274 + 275 + ```ts 276 + completion.addOption('build', '--config', 'Config file', async (previousArgs, toComplete, endsWithSpace) => { 277 + const files = await fs.readdir('.'); 278 + return files 279 + .filter(f => f.endsWith('.json') || f.endsWith('.yaml')) 280 + .map(f => ({ value: f, description: `Config: ${f}` })); 281 + }); 282 + ``` 283 + 284 + ### Environment Completions 285 + 286 + ```ts 287 + completion.addOption('deploy', '--env', 'Environment', () => { 288 + return [ 289 + { value: 'development', description: 'Development environment' }, 290 + { value: 'staging', description: 'Staging environment' }, 291 + { value: 'production', description: 'Production environment' }, 292 + ]; 293 + }); 294 + ``` 295 + 296 + ### Command Completions 297 + 298 + ```ts 299 + completion.addCommand('deploy', 'Deploy application', () => { 300 + return [ 301 + { value: 'dev', description: 'Deploy to development' }, 302 + { value: 'staging', description: 'Deploy to staging' }, 303 + { value: 'prod', description: 'Deploy to production' }, 304 + ]; 305 + }); 306 + ``` 307 + 308 + ## Next Steps 309 + 310 + - Check out [Examples](/docs/tab/guides/examples/) for more practical use cases 311 + - Explore the [API Reference](/docs/tab/api/core/) for detailed documentation 312 + - Learn about [Framework Adapters](/docs/tab/guides/adapters/) for easier integration
+512
src/content/docs/tab/guides/examples.mdx
··· 1 + --- 2 + title: Examples 3 + description: Practical examples of using Tab with different CLI frameworks and scenarios 4 + --- 5 + 6 + This guide provides practical examples of using Tab with different CLI frameworks and common scenarios you might encounter when building CLI tools. 7 + 8 + ## Basic CLI with Core API 9 + 10 + A simple CLI tool with basic autocompletions: 11 + 12 + ```ts 13 + #!/usr/bin/env node 14 + import { Completion, script } from '@bombsh/tab'; 15 + 16 + const name = 'my-cli'; 17 + const completion = new Completion(); 18 + 19 + // Add command completions with positional arguments 20 + completion.addCommand('dev', 'Start development server', [false], () => { 21 + return [ 22 + { value: 'dev', description: 'Start in development mode' }, 23 + { value: 'prod', description: 'Start in production mode' }, 24 + ]; 25 + }); 26 + 27 + completion.addCommand('build', 'Build for production', [false], () => { 28 + return [ 29 + { value: 'build', description: 'Build for production' }, 30 + { value: 'build:dev', description: 'Build for development' }, 31 + ]; 32 + }); 33 + 34 + // Add option completions 35 + completion.addOption('dev', '--port', 'Port number', () => { 36 + return [ 37 + { value: '3000', description: 'Development port' }, 38 + { value: '8080', description: 'Production port' }, 39 + ]; 40 + }, 'p'); 41 + 42 + completion.addOption('dev', '--host', 'Host address', () => { 43 + return [ 44 + { value: 'localhost', description: 'Local development' }, 45 + { value: '0.0.0.0', description: 'All interfaces' }, 46 + ]; 47 + }, 'h'); 48 + 49 + // Helper function to quote paths with spaces 50 + function quoteIfNeeded(path: string) { 51 + return path.includes(' ') ? `'${path}'` : path; 52 + } 53 + 54 + // Get the executable path for shell completion 55 + const execPath = process.execPath; 56 + const processArgs = process.argv.slice(1); 57 + const quotedExecPath = quoteIfNeeded(execPath); 58 + const quotedProcessArgs = processArgs.map(quoteIfNeeded); 59 + const quotedProcessExecArgs = process.execArgv.map(quoteIfNeeded); 60 + const x = `${quotedExecPath} ${quotedProcessExecArgs.join(' ')} ${quotedProcessArgs[0]}`; 61 + 62 + // Handle completion requests 63 + if (process.argv[2] === '--') { 64 + await completion.parse(process.argv.slice(2)); 65 + } else { 66 + script(process.argv[2], name, x); 67 + } 68 + ``` 69 + 70 + ## CAC Framework Example 71 + 72 + A more complex CLI using CAC with Tab integration: 73 + 74 + ```ts 75 + #!/usr/bin/env node 76 + import cac from 'cac'; 77 + import tab from '@bombsh/tab/cac'; 78 + 79 + const cli = cac('my-cli'); 80 + 81 + // Define commands 82 + cli.command('dev', 'Start development server') 83 + .option('--port <port>', 'Specify port', { default: 3000 }) 84 + .option('--host <host>', 'Specify host', { default: 'localhost' }) 85 + .option('--config <file>', 'Config file') 86 + .action((options) => { 87 + console.log('Starting dev server...', options); 88 + }); 89 + 90 + cli.command('build', 'Build for production') 91 + .option('--mode <mode>', 'Build mode', { default: 'production' }) 92 + .option('--out-dir <dir>', 'Output directory') 93 + .action((options) => { 94 + console.log('Building...', options); 95 + }); 96 + 97 + cli.command('deploy', 'Deploy application') 98 + .option('--env <environment>', 'Deployment environment') 99 + .option('--region <region>', 'Deployment region') 100 + .action((options) => { 101 + console.log('Deploying...', options); 102 + }); 103 + 104 + // Initialize tab completion 105 + const completion = await tab(cli); 106 + 107 + // Configure custom completions 108 + for (const command of completion.commands.values()) { 109 + if (command.name === 'dev') { 110 + const portOption = command.options.get('--port'); 111 + if (portOption) { 112 + portOption.handler = async () => { 113 + return [ 114 + { value: '3000', description: 'Development port' }, 115 + { value: '8080', description: 'Production port' }, 116 + ]; 117 + }; 118 + } 119 + 120 + const hostOption = command.options.get('--host'); 121 + if (hostOption) { 122 + hostOption.handler = async () => { 123 + return [ 124 + { value: 'localhost', description: 'Local development' }, 125 + { value: '0.0.0.0', description: 'All interfaces' }, 126 + ]; 127 + }; 128 + } 129 + 130 + const configOption = command.options.get('--config'); 131 + if (configOption) { 132 + configOption.handler = async () => { 133 + return [ 134 + { value: 'vite.config.ts', description: 'Vite config' }, 135 + { value: 'vite.config.js', description: 'Vite config' }, 136 + ]; 137 + }; 138 + } 139 + } 140 + 141 + if (command.name === 'build') { 142 + const modeOption = command.options.get('--mode'); 143 + if (modeOption) { 144 + modeOption.handler = async () => { 145 + return [ 146 + { value: 'development', description: 'Development build' }, 147 + { value: 'production', description: 'Production build' }, 148 + ]; 149 + }; 150 + } 151 + 152 + const outDirOption = command.options.get('--out-dir'); 153 + if (outDirOption) { 154 + outDirOption.handler = async () => { 155 + return [ 156 + { value: 'dist', description: 'Distribution directory' }, 157 + { value: 'build', description: 'Build directory' }, 158 + ]; 159 + }; 160 + } 161 + } 162 + 163 + if (command.name === 'deploy') { 164 + const envOption = command.options.get('--env'); 165 + if (envOption) { 166 + envOption.handler = async () => { 167 + return [ 168 + { value: 'staging', description: 'Staging environment' }, 169 + { value: 'production', description: 'Production environment' }, 170 + ]; 171 + }; 172 + } 173 + 174 + const regionOption = command.options.get('--region'); 175 + if (regionOption) { 176 + regionOption.handler = async () => { 177 + return [ 178 + { value: 'us-east-1', description: 'US East (N. Virginia)' }, 179 + { value: 'us-west-2', description: 'US West (Oregon)' }, 180 + { value: 'eu-west-1', description: 'Europe (Ireland)' }, 181 + ]; 182 + }; 183 + } 184 + } 185 + } 186 + 187 + cli.parse(); 188 + ``` 189 + 190 + ## Citty Framework Example 191 + 192 + A CLI using Citty with Tab integration: 193 + 194 + ```ts 195 + #!/usr/bin/env node 196 + import { defineCommand, createMain } from 'citty'; 197 + import tab from '@bombsh/tab/citty'; 198 + 199 + const main = defineCommand({ 200 + meta: { 201 + name: 'my-cli', 202 + description: 'My CLI tool', 203 + }, 204 + }); 205 + 206 + const devCommand = defineCommand({ 207 + meta: { 208 + name: 'dev', 209 + description: 'Start development server', 210 + }, 211 + args: { 212 + port: { type: 'string', description: 'Specify port' }, 213 + host: { type: 'string', description: 'Specify host' }, 214 + config: { type: 'string', description: 'Config file' }, 215 + }, 216 + }); 217 + 218 + const buildCommand = defineCommand({ 219 + meta: { 220 + name: 'build', 221 + description: 'Build for production', 222 + }, 223 + args: { 224 + mode: { type: 'string', description: 'Build mode' }, 225 + outDir: { type: 'string', description: 'Output directory' }, 226 + }, 227 + }); 228 + 229 + main.subCommands = { 230 + dev: devCommand, 231 + build: buildCommand, 232 + }; 233 + 234 + const completion = await tab(main); 235 + 236 + // Configure completions 237 + const devCommandCompletion = completion.commands.get('dev'); 238 + if (devCommandCompletion) { 239 + const portOption = devCommandCompletion.options.get('--port'); 240 + if (portOption) { 241 + portOption.handler = async () => { 242 + return [ 243 + { value: '3000', description: 'Development port' }, 244 + { value: '8080', description: 'Production port' }, 245 + ]; 246 + }; 247 + } 248 + 249 + const hostOption = devCommandCompletion.options.get('--host'); 250 + if (hostOption) { 251 + hostOption.handler = async () => { 252 + return [ 253 + { value: 'localhost', description: 'Local development' }, 254 + { value: '0.0.0.0', description: 'All interfaces' }, 255 + ]; 256 + }; 257 + } 258 + 259 + const configOption = devCommandCompletion.options.get('--config'); 260 + if (configOption) { 261 + configOption.handler = async () => { 262 + return [ 263 + { value: 'vite.config.ts', description: 'Vite config' }, 264 + { value: 'vite.config.js', description: 'Vite config' }, 265 + ]; 266 + }; 267 + } 268 + } 269 + 270 + const buildCommandCompletion = completion.commands.get('build'); 271 + if (buildCommandCompletion) { 272 + const modeOption = buildCommandCompletion.options.get('--mode'); 273 + if (modeOption) { 274 + modeOption.handler = async () => { 275 + return [ 276 + { value: 'development', description: 'Development build' }, 277 + { value: 'production', description: 'Production build' }, 278 + ]; 279 + }; 280 + } 281 + 282 + const outDirOption = buildCommandCompletion.options.get('--out-dir'); 283 + if (outDirOption) { 284 + outDirOption.handler = async () => { 285 + return [ 286 + { value: 'dist', description: 'Distribution directory' }, 287 + { value: 'build', description: 'Build directory' }, 288 + ]; 289 + }; 290 + } 291 + } 292 + 293 + const cli = createMain(main); 294 + cli(); 295 + ``` 296 + 297 + ## Commander Framework Example 298 + 299 + A CLI using Commander.js with Tab integration: 300 + 301 + ```ts 302 + #!/usr/bin/env node 303 + import { Command } from 'commander'; 304 + import tab from '@bombsh/tab/commander'; 305 + 306 + const program = new Command(); 307 + 308 + program 309 + .name('my-cli') 310 + .description('My CLI tool') 311 + .version('1.0.0'); 312 + 313 + program 314 + .command('dev') 315 + .description('Start development server') 316 + .option('-p, --port <port>', 'Specify port', '3000') 317 + .option('-h, --host <host>', 'Specify host', 'localhost') 318 + .option('-c, --config <file>', 'Config file') 319 + .action((options) => { 320 + console.log('Starting dev server...', options); 321 + }); 322 + 323 + program 324 + .command('build') 325 + .description('Build for production') 326 + .option('-m, --mode <mode>', 'Build mode', 'production') 327 + .option('-o, --out-dir <dir>', 'Output directory', 'dist') 328 + .action((options) => { 329 + console.log('Building...', options); 330 + }); 331 + 332 + program 333 + .command('deploy') 334 + .description('Deploy application') 335 + .option('-e, --env <environment>', 'Deployment environment') 336 + .option('-r, --region <region>', 'Deployment region') 337 + .action((options) => { 338 + console.log('Deploying...', options); 339 + }); 340 + 341 + // Initialize tab completion 342 + const completion = tab(program); 343 + 344 + // Configure custom completions 345 + const devCommandCompletion = completion.commands.get('dev'); 346 + if (devCommandCompletion) { 347 + const portOption = devCommandCompletion.options.get('--port'); 348 + if (portOption) { 349 + portOption.handler = async () => { 350 + return [ 351 + { value: '3000', description: 'Development port' }, 352 + { value: '8080', description: 'Production port' }, 353 + ]; 354 + }; 355 + } 356 + 357 + const hostOption = devCommandCompletion.options.get('--host'); 358 + if (hostOption) { 359 + hostOption.handler = async () => { 360 + return [ 361 + { value: 'localhost', description: 'Local development' }, 362 + { value: '0.0.0.0', description: 'All interfaces' }, 363 + ]; 364 + }; 365 + } 366 + 367 + const configOption = devCommandCompletion.options.get('--config'); 368 + if (configOption) { 369 + configOption.handler = async () => { 370 + return [ 371 + { value: 'vite.config.ts', description: 'Vite config' }, 372 + { value: 'vite.config.js', description: 'Vite config' }, 373 + ]; 374 + }; 375 + } 376 + } 377 + 378 + const buildCommandCompletion = completion.commands.get('build'); 379 + if (buildCommandCompletion) { 380 + const modeOption = buildCommandCompletion.options.get('--mode'); 381 + if (modeOption) { 382 + modeOption.handler = async () => { 383 + return [ 384 + { value: 'development', description: 'Development build' }, 385 + { value: 'production', description: 'Production build' }, 386 + ]; 387 + }; 388 + } 389 + 390 + const outDirOption = buildCommandCompletion.options.get('--out-dir'); 391 + if (outDirOption) { 392 + outDirOption.handler = async () => { 393 + return [ 394 + { value: 'dist', description: 'Distribution directory' }, 395 + { value: 'build', description: 'Build directory' }, 396 + ]; 397 + }; 398 + } 399 + } 400 + 401 + const deployCommandCompletion = completion.commands.get('deploy'); 402 + if (deployCommandCompletion) { 403 + const envOption = deployCommandCompletion.options.get('--env'); 404 + if (envOption) { 405 + envOption.handler = async () => { 406 + return [ 407 + { value: 'staging', description: 'Staging environment' }, 408 + { value: 'production', description: 'Production environment' }, 409 + ]; 410 + }; 411 + } 412 + 413 + const regionOption = deployCommandCompletion.options.get('--region'); 414 + if (regionOption) { 415 + regionOption.handler = async () => { 416 + return [ 417 + { value: 'us-east-1', description: 'US East (N. Virginia)' }, 418 + { value: 'us-west-2', description: 'US West (Oregon)' }, 419 + { value: 'eu-west-1', description: 'Europe (Ireland)' }, 420 + ]; 421 + }; 422 + } 423 + } 424 + 425 + program.parse(); 426 + ``` 427 + 428 + ## Advanced Examples 429 + 430 + ### Dynamic File Completions 431 + 432 + ```ts 433 + import { readdir } from 'fs/promises'; 434 + import { join } from 'path'; 435 + 436 + // Dynamic file completion 437 + configOption.handler = async () => { 438 + try { 439 + const files = await readdir('.'); 440 + const configFiles = files.filter(f => 441 + f.endsWith('.json') || f.endsWith('.js') || f.endsWith('.ts') 442 + ); 443 + 444 + return configFiles.map(f => ({ 445 + value: f, 446 + description: `Config file: ${f}` 447 + })); 448 + } catch (error) { 449 + return []; 450 + } 451 + }; 452 + ``` 453 + 454 + ### Context-Aware Completions 455 + 456 + ```ts 457 + // Context-aware completion based on previous arguments 458 + portOption.handler = async (previousArgs, toComplete, endsWithSpace) => { 459 + // Check if user is typing a specific port 460 + if (toComplete.startsWith('30')) { 461 + return [ 462 + { value: '3000', description: 'Development port' }, 463 + ]; 464 + } 465 + 466 + // Check if user specified a host that affects port suggestions 467 + const hostIndex = previousArgs.indexOf('--host'); 468 + if (hostIndex !== -1 && hostIndex < previousArgs.length - 1) { 469 + const host = previousArgs[hostIndex + 1]; 470 + if (host === '0.0.0.0') { 471 + return [ 472 + { value: '8080', description: 'Production port' }, 473 + { value: '9000', description: 'Alternative port' }, 474 + ]; 475 + } 476 + } 477 + 478 + return [ 479 + { value: '3000', description: 'Development port' }, 480 + { value: '8080', description: 'Production port' }, 481 + { value: '9000', description: 'Alternative port' }, 482 + ]; 483 + }; 484 + ``` 485 + 486 + ### Cached Completions 487 + 488 + ```ts 489 + // Cache expensive operations 490 + let cachedPorts: Item[] | null = null; 491 + 492 + portOption.handler = async () => { 493 + if (cachedPorts) { 494 + return cachedPorts; 495 + } 496 + 497 + // Expensive operation to get available ports 498 + const ports = await getAvailablePorts(); 499 + cachedPorts = ports.slice(0, 20).map(port => ({ 500 + value: port.toString(), 501 + description: `Port ${port}` 502 + })); 503 + 504 + return cachedPorts; 505 + }; 506 + ``` 507 + 508 + ## Next Steps 509 + 510 + - Learn about [Best Practices](/docs/tab/guides/best-practices/) for effective autocompletions 511 + - Explore the [API Reference](/docs/tab/api/core/) for advanced usage 512 + - Check out the [Framework Adapters](/docs/tab/guides/adapters/) for easier integration
+76
src/content/docs/tab/index.mdx
··· 1 + --- 2 + title: Tab 3 + description: Shell autocompletions for JavaScript CLI tools 4 + --- 5 + 6 + import { CardGrid, Card } from '@astrojs/starlight/components'; 7 + 8 + Tab is a powerful tool that brings shell autocompletions to JavaScript CLI tools. Inspired by tools like git and their excellent autocompletion experience, Tab makes the same functionality available for any JavaScript CLI project. 9 + 10 + ## Features 11 + 12 + <CardGrid> 13 + 14 + <Card title="Shell Integration" icon="seti:shell"> 15 + Seamless integration with zsh, bash, fish, and PowerShell. Get native autocompletion experience for your CLI tools. 16 + </Card> 17 + 18 + <Card title="Framework Adapters" icon="seti:smarty"> 19 + Built-in adapters for popular CLI frameworks including CAC, Citty, and Commander.js for easy integration. 20 + </Card> 21 + 22 + <Card title="Type-Safe" icon="rocket"> 23 + Full TypeScript support with type-safe completion handlers and autocomplete suggestions. 24 + </Card> 25 + 26 + <Card title="Custom Completions" icon="approve-check"> 27 + Define custom completion logic for commands and options with dynamic suggestions based on context. 28 + </Card> 29 + 30 + <Card title="Universal Support" icon="information"> 31 + Works with any JavaScript CLI tool, regardless of the underlying framework or architecture. 32 + </Card> 33 + 34 + <Card title="Easy Setup" icon="sun"> 35 + Simple installation and configuration with minimal code changes to add autocompletion to your CLI. 36 + </Card> 37 + 38 + </CardGrid> 39 + 40 + ## Quick Start 41 + 42 + Add shell autocompletions to your CLI in just a few lines: 43 + 44 + ```ts 45 + import { Completion, script } from '@bombsh/tab'; 46 + 47 + const completion = new Completion(); 48 + 49 + completion.addCommand('dev', 'Start development server', [false], async () => { 50 + return [ 51 + { value: 'dev', description: 'Start in development mode' }, 52 + { value: 'prod', description: 'Start in production mode' }, 53 + ]; 54 + }); 55 + 56 + completion.addOption('dev', '--port', 'Port number', async () => { 57 + return [ 58 + { value: '3000', description: 'Development port' }, 59 + { value: '8080', description: 'Production port' }, 60 + ]; 61 + }, 'p'); 62 + 63 + // Generate shell completion script 64 + if (process.argv[2] === '--') { 65 + await completion.parse(process.argv.slice(2)); 66 + } else { 67 + script(process.argv[2], 'my-cli', process.execPath); 68 + } 69 + ``` 70 + 71 + ## What's Next? 72 + 73 + - [Getting Started](/docs/tab/basics/getting-started/) - Learn how to add autocompletions to your CLI 74 + - [Framework Adapters](/docs/tab/guides/adapters/) - Use Tab with CAC, Citty, and Commander.js 75 + - [Best Practices](/docs/tab/guides/best-practices/) - Learn best practices for implementing autocompletions 76 + - [API Reference](/docs/tab/api/core/) - Detailed API documentation