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

more updates (#19)

authored by

paul and committed by
GitHub
(Aug 20, 2025, 1:59 PM -0600) 4d740a99 65591645

+1343 -744
+259 -152
src/content/docs/tab/api/core.mdx
··· 7 7 8 8 ## Classes 9 9 10 - ### Completion 10 + ### RootCommand 11 11 12 - The main class for managing command and option completions. 12 + The main class for managing command and option completions. This is the primary entry point for defining your CLI structure. 13 13 14 14 ```ts 15 - import { Completion } from '@bombsh/tab'; 15 + import { RootCommand } from '@bombsh/tab'; 16 16 17 - const completion = new Completion(); 17 + const t = new RootCommand(); 18 18 ``` 19 19 20 20 #### Methods 21 21 22 - ##### addCommand 22 + ##### command 23 23 24 - Adds a completion handler for a command. 24 + Adds a command with optional description and returns a Command instance for further configuration. 25 25 26 26 **Parameters:** 27 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 28 + - `description` (string, optional): Command description 32 29 33 - **Returns:** string - The command key 30 + **Returns:** Command - A Command instance for chaining 34 31 35 32 **Example:** 36 33 ```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 - }); 34 + // Simple command 35 + const devCmd = t.command('dev', 'Start development server'); 44 36 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 - }); 37 + // Nested command 38 + const buildCmd = t.command('dev build', 'Build project'); 39 + ``` 40 + 41 + ##### option 51 42 52 - // Nested command: "vite dev build" 53 - completion.addCommand('build', 'Build project', [], async () => { 54 - return [ 55 - { value: 'build', description: 'Build command' }, 56 - ]; 57 - }, 'dev'); 43 + Adds a global option to the root command. 44 + 45 + **Parameters:** 46 + - `name` (string): The option name (e.g., '--config') 47 + - `description` (string): Option description 48 + - `handler` (OptionHandler, optional): Function that provides completion suggestions 49 + - `alias` (string, optional): Short flag alias (e.g., 'c' for '--config') 50 + 51 + **Returns:** RootCommand - For method chaining 52 + 53 + **Example:** 54 + ```ts 55 + t.option('--config', 'Use specified config file', function(complete) { 56 + complete('vite.config.ts', 'Vite config file'); 57 + complete('vite.config.js', 'Vite config file'); 58 + }, 'c'); 58 59 ``` 59 60 60 - ##### addOption 61 + ##### argument 61 62 62 - Adds a completion handler for an option of a specific command. 63 + Adds a positional argument to the root command. 63 64 64 65 **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') 66 + - `name` (string): The argument name 67 + - `handler` (ArgumentHandler, optional): Function that provides completion suggestions 68 + - `variadic` (boolean, optional): Whether this is a variadic argument (default: false) 70 69 71 - **Returns:** string - The option name 70 + **Returns:** RootCommand - For method chaining 72 71 73 72 **Example:** 74 73 ```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'); 74 + t.argument('project', function(complete) { 75 + complete('my-app', 'My application'); 76 + complete('my-lib', 'My library'); 77 + }); 81 78 ``` 82 79 83 80 ##### parse ··· 89 86 90 87 **Example:** 91 88 ```ts 92 - await completion.parse(['--port']); 93 - // Outputs completion suggestions to stdout 89 + const separatorIndex = process.argv.indexOf('--'); 90 + const completionArgs = separatorIndex !== -1 ? process.argv.slice(separatorIndex + 1) : []; 91 + t.parse(completionArgs); 94 92 ``` 95 93 96 - ### script 94 + ##### setup 97 95 98 96 Generates shell completion scripts. 99 97 100 98 **Parameters:** 101 - - `shell` (string): Target shell ('zsh', 'bash', 'fish', 'powershell') 102 99 - `name` (string): CLI tool name 103 - - `execPath` (string): Executable path for the CLI tool 100 + - `executable` (string): Executable path for the CLI tool 101 + - `shell` (string): Target shell ('zsh', 'bash', 'fish', 'powershell') 104 102 105 103 **Example:** 106 104 ```ts 107 - import { script } from '@bombsh/tab'; 105 + t.setup('my-cli', process.execPath, 'zsh'); 106 + ``` 108 107 109 - script('zsh', 'my-cli', '/usr/bin/node /path/to/my-cli'); 108 + ### Command 109 + 110 + Represents a command with its options and arguments. 111 + 112 + ```ts 113 + const cmd = t.command('dev', 'Start development server'); 110 114 ``` 111 115 112 - ## Types 116 + #### Methods 113 117 114 - ### Handler 118 + ##### option 115 119 116 - Function type for completion handlers. 120 + Adds an option to this command. 117 121 122 + **Parameters:** 123 + - `name` (string): The option name (e.g., '--port') 124 + - `description` (string): Option description 125 + - `handler` (OptionHandler, optional): Function that provides completion suggestions 126 + - `alias` (string, optional): Short flag alias (e.g., 'p' for '--port') 127 + 128 + **Returns:** Command - For method chaining 129 + 130 + **Example:** 118 131 ```ts 119 - type Handler = ( 120 - previousArgs: string[], 121 - toComplete: string, 122 - endsWithSpace: boolean 123 - ) => Item[] | Promise<Item[]>; 132 + cmd.option('--port', 'Port number', function(complete) { 133 + complete('3000', 'Development port'); 134 + complete('8080', 'Production port'); 135 + }, 'p'); 124 136 ``` 125 137 138 + ##### argument 139 + 140 + Adds a positional argument to this command. 141 + 126 142 **Parameters:** 127 - - `previousArgs` (string[]): Previously typed arguments 128 - - `toComplete` (string): The text being completed 129 - - `endsWithSpace` (boolean): Whether the input ends with a space 143 + - `name` (string): The argument name 144 + - `handler` (ArgumentHandler, optional): Function that provides completion suggestions 145 + - `variadic` (boolean, optional): Whether this is a variadic argument (default: false) 130 146 131 - **Returns:** Item[] | Promise&lt;Item[]&gt; 147 + **Returns:** Command - For method chaining 132 148 133 149 **Example:** 134 150 ```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 - }; 151 + cmd.argument('entry', function(complete) { 152 + complete('src/main.ts', 'Main entry point'); 153 + complete('src/index.ts', 'Index entry point'); 154 + }); 149 155 ``` 150 156 151 - ### Item 157 + ## Types 158 + 159 + ### OptionHandler 152 160 153 - Object representing a completion suggestion. 161 + Function type for option completion handlers. 154 162 155 163 ```ts 156 - type Item = { 157 - value: string; 158 - description: string; 159 - }; 164 + type OptionHandler = ( 165 + this: Option, 166 + complete: Complete, 167 + options: OptionsMap 168 + ) => void; 160 169 ``` 161 170 162 - **Properties:** 163 - - `value` (string): The completion value 164 - - `description` (string): Description of the completion 171 + **Parameters:** 172 + - `this`: The Option instance 173 + - `complete`: Function to call with completion suggestions 174 + - `options`: Map of all options for this command 165 175 166 176 **Example:** 167 177 ```ts 168 - const item: Item = { 169 - value: '3000', 170 - description: 'Development port' 178 + const handler: OptionHandler = function(complete) { 179 + complete('3000', 'Development port'); 180 + complete('8080', 'Production port'); 171 181 }; 172 182 ``` 173 183 174 - ### `Positional` 184 + ### ArgumentHandler 185 + 186 + Function type for argument completion handlers. 187 + 188 + ```ts 189 + type ArgumentHandler = ( 190 + this: Argument, 191 + complete: Complete, 192 + options: OptionsMap 193 + ) => void; 194 + ``` 175 195 176 - Type for positional argument configuration. 196 + **Parameters:** 197 + - `this`: The Argument instance 198 + - `complete`: Function to call with completion suggestions 199 + - `options`: Map of all options for this command 177 200 201 + **Example:** 178 202 ```ts 179 - type Positional = { 180 - required: boolean; 181 - variadic: boolean; 182 - completion: Handler; 203 + const handler: ArgumentHandler = function(complete) { 204 + complete('src/main.ts', 'Main entry point'); 205 + complete('src/index.ts', 'Index entry point'); 183 206 }; 184 207 ``` 185 208 186 - ## Constants 209 + ### Complete 187 210 188 - ### ShellCompRequestCmd 211 + Function type for providing completion suggestions. 189 212 190 - The name of the hidden command used to request completion results. 213 + ```ts 214 + type Complete = (value: string, description: string) => void; 215 + ``` 191 216 217 + **Parameters:** 218 + - `value` (string): The completion value 219 + - `description` (string): Description of the completion 220 + 221 + **Example:** 192 222 ```ts 193 - export const ShellCompRequestCmd: string = '__complete'; 223 + function(complete) { 224 + complete('3000', 'Development port'); 225 + complete('8080', 'Production port'); 226 + } 194 227 ``` 195 228 196 - ### ShellCompNoDescRequestCmd 229 + ### Completion 197 230 198 - The name of the hidden command used to request completion results without descriptions. 231 + Object representing a completion suggestion. 199 232 200 233 ```ts 201 - export const ShellCompNoDescRequestCmd: string = '__completeNoDesc'; 234 + type Completion = { 235 + value: string; 236 + description?: string; 237 + }; 202 238 ``` 203 239 240 + **Properties:** 241 + - `value` (string): The completion value 242 + - `description` (string, optional): Description of the completion 243 + 244 + **Example:** 245 + ```ts 246 + const completion: Completion = { 247 + value: '3000', 248 + description: 'Development port' 249 + }; 250 + ``` 251 + 252 + ## Constants 253 + 204 254 ### ShellCompDirective 205 255 206 256 Bit map representing different behaviors the shell can be instructed to have. ··· 213 263 ShellCompDirectiveFilterFileExt: 1 << 3, 214 264 ShellCompDirectiveFilterDirs: 1 << 4, 215 265 ShellCompDirectiveKeepOrder: 1 << 5, 266 + shellCompDirectiveMaxValue: 1 << 6, 216 267 ShellCompDirectiveDefault: 0, 217 268 }; 218 269 ``` ··· 223 274 224 275 ```ts 225 276 #!/usr/bin/env node 226 - import { Completion, script } from '@bombsh/tab'; 277 + import { RootCommand } from '@bombsh/tab'; 278 + 279 + const t = new RootCommand(); 280 + 281 + // Add global options 282 + t.option('--config', 'Use specified config file', function(complete) { 283 + complete('vite.config.ts', 'Vite config file'); 284 + complete('vite.config.js', 'Vite config file'); 285 + }, 'c'); 286 + 287 + t.option('--mode', 'Set env mode', function(complete) { 288 + complete('development', 'Development mode'); 289 + complete('production', 'Production mode'); 290 + }, 'm'); 291 + 292 + // Add root command argument 293 + t.argument('project', function(complete) { 294 + complete('my-app', 'My application'); 295 + complete('my-lib', 'My library'); 296 + }); 297 + 298 + // Add commands with completions 299 + const devCmd = t.command('dev', 'Start development server'); 300 + devCmd.option('--port', 'Port number', function(complete) { 301 + complete('3000', 'Development port'); 302 + complete('8080', 'Production port'); 303 + }, 'p'); 304 + 305 + devCmd.option('--host', 'Hostname', function(complete) { 306 + complete('localhost', 'Localhost'); 307 + complete('0.0.0.0', 'All interfaces'); 308 + }, 'H'); 309 + 310 + devCmd.option('--verbose', 'Enable verbose logging', 'v'); 311 + 312 + // Add nested commands 313 + t.command('dev build', 'Build project'); 314 + t.command('dev start', 'Start development server'); 315 + 316 + // Add command with arguments 317 + t.command('copy', 'Copy files') 318 + .argument('source', function(complete) { 319 + complete('src/', 'Source directory'); 320 + complete('dist/', 'Distribution directory'); 321 + }) 322 + .argument('destination', function(complete) { 323 + complete('build/', 'Build output'); 324 + complete('release/', 'Release directory'); 325 + }); 227 326 228 - const name = 'my-cli'; 229 - const completion = new Completion(); 327 + // Add command with variadic arguments 328 + t.command('lint', 'Lint project') 329 + .argument('files', function(complete) { 330 + complete('main.ts', 'Main file'); 331 + complete('src/', 'Source directory'); 332 + }, true); // true = variadic argument 230 333 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 - ]; 334 + // Handle completion requests 335 + if (process.argv[2] === 'complete') { 336 + const shell = process.argv[3]; 337 + if (shell && ['zsh', 'bash', 'fish', 'powershell'].includes(shell)) { 338 + t.setup('my-cli', process.execPath, shell); 339 + } else { 340 + // Parse completion arguments (everything after --) 341 + const separatorIndex = process.argv.indexOf('--'); 342 + const completionArgs = separatorIndex !== -1 ? process.argv.slice(separatorIndex + 1) : []; 343 + t.parse(completionArgs); 237 344 } 238 - 239 - return [ 240 - { value: 'dev', description: 'Start in development mode' }, 241 - { value: 'prod', description: 'Start in production mode' }, 242 - ]; 243 - }); 345 + } else { 346 + // Regular CLI usage 347 + console.log('My CLI Tool'); 348 + console.log('Use "complete" command for shell completion'); 349 + } 350 + ``` 351 + 352 + ## Advanced Usage 353 + 354 + ### Context-Aware Completions 355 + 356 + Make your completions responsive to what the user is typing: 244 357 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 - ]; 358 + ```ts 359 + devCmd.option('--port', 'Port number', function(complete) { 360 + // Check if user is typing a specific port 361 + if (this.toComplete?.startsWith('30')) { 362 + complete('3000', 'Development port'); 363 + return; 251 364 } 252 365 253 - return [ 254 - { value: '3000', description: 'Development port' }, 255 - { value: '8080', description: 'Production port' }, 256 - ]; 366 + complete('3000', 'Development port'); 367 + complete('8080', 'Production port'); 368 + complete('9000', 'Alternative port'); 257 369 }, 'p'); 370 + ``` 258 371 259 - // Helper function to quote paths with spaces 260 - function quoteIfNeeded(path: string) { 261 - return path.includes(' ') ? `'${path}'` : path; 262 - } 372 + ### Dynamic Completions 263 373 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]}`; 374 + Load completions from external sources: 271 375 272 - // Handle completion requests 273 - if (process.argv[2] === '--') { 376 + ```ts 377 + devCmd.option('--config', 'Config file', async function(complete) { 274 378 try { 275 - await completion.parse(process.argv.slice(2)); 379 + const files = await fs.readdir('.'); 380 + const configFiles = files.filter(f => f.includes('config')); 381 + configFiles.forEach(file => complete(file, `Config file: ${file}`)); 276 382 } catch (error) { 277 - console.error('Completion error:', error.message); 278 - process.exit(1); 383 + // Fallback completions 384 + complete('vite.config.ts', 'Vite config file'); 279 385 } 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 - } 386 + }); 387 + ``` 388 + 389 + ### Boolean Options 390 + 391 + For boolean flags, you don't need a handler: 392 + 393 + ```ts 394 + devCmd.option('--verbose', 'Enable verbose logging', 'v'); 395 + devCmd.option('--quiet', 'Suppress output'); 289 396 ``` 290 397 291 398 ## Next Steps
+120 -56
src/content/docs/tab/basics/getting-started.mdx
··· 34 34 Here's a simple example of how to add autocompletions to your CLI: 35 35 36 36 ```ts 37 - import { Completion, script } from '@bombsh/tab'; 37 + import { RootCommand } from '@bombsh/tab'; 38 38 39 - const name = 'my-cli'; 40 - const completion = new Completion(); 39 + const t = new RootCommand(); 41 40 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 - ); 41 + // Add global options 42 + t.option('--config', 'Use specified config file', function(complete) { 43 + complete('vite.config.ts', 'Vite config file'); 44 + complete('vite.config.js', 'Vite config file'); 45 + }, 'c'); 54 46 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 - ); 47 + // Add commands with completions 48 + const devCmd = t.command('dev', 'Start development server'); 49 + devCmd.option('--port', 'Port number', function(complete) { 50 + complete('3000', 'Development port'); 51 + complete('8080', 'Production port'); 52 + }, 'p'); 68 53 69 - // Helper function to quote paths with spaces 70 - function quoteIfNeeded(path: string) { 71 - return path.includes(' ') ? `'${path}'` : path; 72 - } 54 + devCmd.option('--host', 'Hostname', function(complete) { 55 + complete('localhost', 'Localhost'); 56 + complete('0.0.0.0', 'All interfaces'); 57 + }, 'H'); 73 58 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]}`; 59 + // Add positional arguments 60 + t.command('copy', 'Copy files') 61 + .argument('source', function(complete) { 62 + complete('src/', 'Source directory'); 63 + complete('dist/', 'Distribution directory'); 64 + }) 65 + .argument('destination', function(complete) { 66 + complete('build/', 'Build output'); 67 + complete('release/', 'Release directory'); 68 + }); 81 69 82 70 // Handle completion requests 83 - if (process.argv[2] === '--') { 84 - // Autocompletion logic 85 - await completion.parse(process.argv.slice(2)); 71 + if (process.argv[2] === 'complete') { 72 + const shell = process.argv[3]; 73 + if (shell && ['zsh', 'bash', 'fish', 'powershell'].includes(shell)) { 74 + t.setup('my-cli', process.execPath, shell); 75 + } else { 76 + // Parse completion arguments (everything after --) 77 + const separatorIndex = process.argv.indexOf('--'); 78 + const completionArgs = separatorIndex !== -1 ? process.argv.slice(separatorIndex + 1) : []; 79 + t.parse(completionArgs); 80 + } 86 81 } else { 87 - // Generate shell completion script 88 - script(process.argv[2], name, x); 82 + // Regular CLI usage 83 + console.log('My CLI Tool'); 84 + console.log('Use "complete" command for shell completion'); 89 85 } 90 86 ``` 91 87 92 - ## Understanding Positional Arguments 88 + ## Understanding the API 93 89 94 - The `args` parameter in `addCommand` is an array of booleans that indicates which arguments are required or optional: 90 + ### RootCommand Class 91 + 92 + The `RootCommand` class is the main entry point for defining your CLI structure: 95 93 96 94 ```ts 97 - // Command: "my-cli start <entry>" 98 - completion.addCommand('start', 'Start the app', [false], handler); 95 + const t = new RootCommand(); 96 + ``` 99 97 100 - // Command: "my-cli serve [entry]" 101 - completion.addCommand('serve', 'Serve the app', [true], handler); 98 + ### Adding Commands 102 99 103 - // Command: "my-cli build <entry> [output]" 104 - completion.addCommand('build', 'Build the app', [false, true], handler); 100 + Use the `command()` method to add commands with descriptions: 105 101 106 - // Command: "my-cli dev [...files]" 107 - completion.addCommand('dev', 'Dev mode', [true], handler); // Variadic argument 102 + ```ts 103 + const devCmd = t.command('dev', 'Start development server'); 104 + ``` 105 + 106 + ### Adding Options 107 + 108 + Add options to commands with completion handlers: 109 + 110 + ```ts 111 + devCmd.option('--port', 'Port number', function(complete) { 112 + complete('3000', 'Development port'); 113 + complete('8080', 'Production port'); 114 + }, 'p'); // Short flag alias 115 + ``` 116 + 117 + ### Adding Arguments 118 + 119 + Add positional arguments with completion handlers: 120 + 121 + ```ts 122 + t.command('copy', 'Copy files') 123 + .argument('source', function(complete) { 124 + complete('src/', 'Source directory'); 125 + complete('dist/', 'Distribution directory'); 126 + }); 127 + ``` 128 + 129 + ### Variadic Arguments 130 + 131 + For commands that accept multiple arguments: 132 + 133 + ```ts 134 + t.command('lint', 'Lint project') 135 + .argument('files', function(complete) { 136 + complete('main.ts', 'Main file'); 137 + complete('src/', 'Source directory'); 138 + }, true); // true = variadic argument 108 139 ``` 109 140 110 141 ## Shell Setup ··· 145 176 my-cli complete powershell > $PROFILE.CurrentUserAllHosts 146 177 ``` 147 178 179 + ## Package Manager Integration 180 + 181 + Tab also provides a standalone CLI tool that enhances package manager completions with automatic CLI discovery: 182 + 183 + ### Installing the Tab CLI 184 + 185 + ```bash 186 + npm install -g @bombsh/tab 187 + ``` 188 + 189 + ### Setting Up Package Manager Completions 190 + 191 + ```bash 192 + # For pnpm 193 + tab pnpm zsh > ~/.zsh_completions/tab-pnpm.zsh 194 + echo 'source ~/.zsh_completions/tab-pnpm.zsh' >> ~/.zshrc 195 + 196 + # For npm 197 + tab npm zsh > ~/.zsh_completions/tab-npm.zsh 198 + echo 'source ~/.zsh_completions/tab-npm.zsh' >> ~/.zshrc 199 + 200 + # For yarn 201 + tab yarn zsh > ~/.zsh_completions/tab-yarn.zsh 202 + echo 'source ~/.zsh_completions/tab-yarn.zsh' >> ~/.zshrc 203 + 204 + # For bun 205 + tab bun zsh > ~/.zsh_completions/tab-bun.zsh 206 + echo 'source ~/.zsh_completions/tab-bun.zsh' >> ~/.zshrc 207 + ``` 208 + 209 + This provides enhanced completions for all CLI tools that support Tab completions, automatically detecting and providing completions for tools like Vite, TypeScript, and any other Tab-compatible CLI. 210 + 148 211 ## How It Works 149 212 150 213 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. ··· 158 221 For example: 159 222 ```bash 160 223 my-cli complete -- --po 161 - --port Specify the port number 224 + --port Port number 162 225 :0 163 226 ``` 164 227 165 228 ## Next Steps 166 229 167 230 - 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 231 + - Explore [Package Manager Integration](/docs/tab/guides/package-managers/) for enhanced completions 232 + - Check out [Best Practices](/docs/tab/guides/best-practices/) for implementing effective autocompletions 169 233 - Check out the [API Reference](/docs/tab/api/core/) for detailed documentation
+42 -6
src/content/docs/tab/guides/adapters.mdx
··· 18 18 cli.command('dev', 'Start dev server').option('--port <port>', 'Specify port'); 19 19 cli.command('build', 'Build for production').option('--mode <mode>', 'Build mode'); 20 20 21 - const completion = await tab(cli); 21 + const completion = tab(cli); 22 22 23 23 // Get the dev command completion handler 24 24 const devCommandCompletion = completion.commands.get('dev'); 25 25 26 26 // Get and configure the port option completion handler 27 27 const portOptionCompletion = devCommandCompletion.options.get('--port'); 28 - portOptionCompletion.handler = async ( 29 - previousArgs, 30 - toComplete, 31 - endsWithSpace 32 - ) => { 28 + portOptionCompletion.handler = async () => { 33 29 return [ 34 30 { value: '3000', description: 'Development port' }, 35 31 { value: '8080', description: 'Production port' }, ··· 290 286 291 287 return cachedPorts; 292 288 }; 289 + ``` 290 + 291 + ### 4. Framework-Specific Considerations 292 + 293 + #### CAC 294 + 295 + - CAC automatically handles option parsing, so your completion handlers should focus on providing relevant suggestions 296 + - Use the option name as defined in your CAC command (e.g., `--port <port>` becomes `--port`) 297 + 298 + #### Citty 299 + 300 + - Citty's type system provides better type safety for completions 301 + - Arguments defined in the `args` object are automatically converted to options 302 + - Subcommands are processed recursively 303 + 304 + #### Commander.js 305 + 306 + - Commander.js supports both short and long option aliases 307 + - The adapter automatically handles both forms 308 + - Commands can have multiple aliases 309 + 310 + ## Integration with Shell Setup 311 + 312 + After setting up your adapter, you still need to handle shell completion setup: 313 + 314 + ```ts 315 + // Add this to your CLI entry point 316 + if (process.argv[2] === 'complete') { 317 + const shell = process.argv[3]; 318 + if (shell && ['zsh', 'bash', 'fish', 'powershell'].includes(shell)) { 319 + // Generate shell completion script 320 + const script = generateCompletionScript(shell, 'my-cli', process.execPath); 321 + console.log(script); 322 + } else { 323 + // Handle completion requests 324 + const separatorIndex = process.argv.indexOf('--'); 325 + const completionArgs = separatorIndex !== -1 ? process.argv.slice(separatorIndex + 1) : []; 326 + await completion.parse(completionArgs); 327 + } 328 + } 293 329 ``` 294 330 295 331 ## Next Steps
+380 -207
src/content/docs/tab/guides/best-practices.mdx
··· 3 3 description: Learn best practices for implementing effective autocompletions with Tab 4 4 --- 5 5 6 - This guide covers best practices for implementing autocompletions that provide a great user experience and integrate seamlessly with your CLI tool. 6 + This guide covers best practices for implementing effective autocompletions with Tab, including performance optimization, user experience considerations, and common patterns. 7 7 8 - ## Completion Handler Design 8 + ## General Best Practices 9 9 10 - ### Keep Handlers Fast 10 + ### 1. Provide Meaningful Descriptions 11 11 12 - Completion handlers should return results quickly since they're called frequently as users type: 12 + Always include descriptive text for your completions to help users understand what each option does: 13 13 14 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 - }); 15 + // Good 16 + devCmd.option('--port', 'Port number', function(complete) { 17 + complete('3000', 'Development port (default)'); 18 + complete('8080', 'Production port'); 19 + complete('9000', 'Alternative port'); 20 + }, 'p'); 22 21 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 - }); 22 + // Avoid 23 + devCmd.option('--port', 'Port number', function(complete) { 24 + complete('3000', ''); 25 + complete('8080', ''); 26 + }, 'p'); 28 27 ``` 29 28 30 - ### Use Context Appropriately 29 + ### 2. Use Context-Aware Completions 31 30 32 - Leverage the completion context to provide relevant suggestions: 31 + Make your completions responsive to what the user is typing: 33 32 34 33 ```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 - ]; 34 + devCmd.option('--mode', 'Build mode', function(complete) { 35 + // If user is typing 'dev', suggest development 36 + if (this.toComplete?.startsWith('dev')) { 37 + complete('development', 'Development mode'); 38 + return; 39 + } 40 + 41 + // If user is typing 'prod', suggest production 42 + if (this.toComplete?.startsWith('prod')) { 43 + complete('production', 'Production mode'); 44 + return; 41 45 } 42 46 43 - return [ 44 - { value: 'development', description: 'Development environment' }, 45 - { value: 'staging', description: 'Staging environment' }, 46 - { value: 'production', description: 'Production environment' }, 47 - ]; 47 + // Default suggestions 48 + complete('development', 'Development mode'); 49 + complete('production', 'Production mode'); 50 + complete('staging', 'Staging mode'); 48 51 }); 49 52 ``` 50 53 51 - ### Provide Meaningful Descriptions 54 + ### 3. Handle Errors Gracefully 52 55 53 - Always include descriptions to help users understand what each completion means: 56 + Always handle errors in your completion handlers to prevent crashes: 54 57 55 58 ```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 - ]; 59 + devCmd.option('--config', 'Config file', async function(complete) { 60 + try { 61 + const files = await fs.readdir('.'); 62 + const configFiles = files.filter(f => f.includes('config')); 63 + configFiles.forEach(file => complete(file, `Config file: ${file}`)); 64 + } catch (error) { 65 + // Provide fallback completions instead of failing 66 + complete('vite.config.ts', 'Vite config file'); 67 + complete('vite.config.js', 'Vite config file'); 68 + } 63 69 }); 70 + ``` 64 71 65 - // ❌ Avoid: No descriptions 66 - completion.addCommand('deploy', 'Deploy application', () => { 67 - return [ 68 - { value: 'dev' }, 69 - { value: 'staging' }, 70 - { value: 'prod' }, 71 - ]; 72 - }); 72 + ### 4. Optimize Performance 73 + 74 + Cache expensive operations and limit results to maintain responsiveness: 75 + 76 + ```ts 77 + let cachedScripts: string[] | null = null; 78 + 79 + t.command('run', 'Run scripts') 80 + .argument('script', async function(complete) { 81 + if (cachedScripts) { 82 + cachedScripts.forEach(script => complete(script, `Run ${script} script`)); 83 + return; 84 + } 85 + 86 + try { 87 + const packageJson = JSON.parse(await fs.readFile('package.json', 'utf8')); 88 + const scripts = Object.keys(packageJson.scripts || {}); 89 + cachedScripts = scripts.slice(0, 20); // Limit to 20 scripts 90 + cachedScripts.forEach(script => complete(script, `Run ${script} script`)); 91 + } catch (error) { 92 + // Fallback completions 93 + complete('dev', 'Start development server'); 94 + complete('build', 'Build for production'); 95 + } 96 + }); 73 97 ``` 74 98 75 - ## Shell Integration 99 + ## Command Structure Best Practices 76 100 77 - ### Handle Spaces Correctly 101 + ### 1. Use Consistent Naming 78 102 79 - Pay attention to the `endsWithSpace` parameter to provide appropriate completions: 103 + Follow consistent naming conventions for commands and options: 80 104 81 105 ```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 - }); 106 + // Good - consistent with common CLI patterns 107 + t.command('dev', 'Start development server'); 108 + t.command('build', 'Build for production'); 109 + t.command('deploy', 'Deploy application'); 110 + 111 + // Avoid - inconsistent naming 112 + t.command('start-dev', 'Start development server'); 113 + t.command('build-prod', 'Build for production'); 114 + t.command('deploy-app', 'Deploy application'); 99 115 ``` 100 116 101 - ### Support Multiple Shells 117 + ### 2. Group Related Commands 118 + 119 + Organize related commands logically: 120 + 121 + ```ts 122 + // Development commands 123 + t.command('dev', 'Start development server'); 124 + t.command('dev build', 'Build in development mode'); 125 + t.command('dev test', 'Run tests in development mode'); 126 + 127 + // Production commands 128 + t.command('build', 'Build for production'); 129 + t.command('deploy', 'Deploy to production'); 130 + t.command('deploy staging', 'Deploy to staging'); 131 + ``` 132 + 133 + ### 3. Use Short Aliases Sparingly 134 + 135 + Provide short aliases only for commonly used options: 136 + 137 + ```ts 138 + // Good - short aliases for common options 139 + devCmd.option('--port', 'Port number', handler, 'p'); 140 + devCmd.option('--host', 'Host address', handler, 'h'); 141 + devCmd.option('--verbose', 'Enable verbose logging', 'v'); 142 + 143 + // Avoid - too many short aliases 144 + devCmd.option('--config', 'Config file', handler, 'c'); 145 + devCmd.option('--mode', 'Build mode', handler, 'm'); 146 + devCmd.option('--output', 'Output directory', handler, 'o'); 147 + devCmd.option('--source', 'Source directory', handler, 's'); 148 + ``` 149 + 150 + ## Option Design Best Practices 151 + 152 + ### 1. Use Boolean Flags Appropriately 153 + 154 + Use boolean flags for simple on/off options: 155 + 156 + ```ts 157 + // Good - boolean flags for simple options 158 + devCmd.option('--verbose', 'Enable verbose logging', 'v'); 159 + devCmd.option('--quiet', 'Suppress output', 'q'); 160 + devCmd.option('--watch', 'Watch for changes', 'w'); 161 + 162 + // Good - value options for complex data 163 + devCmd.option('--port', 'Port number', function(complete) { 164 + complete('3000', 'Development port'); 165 + complete('8080', 'Production port'); 166 + }, 'p'); 167 + ``` 168 + 169 + ### 2. Provide Sensible Defaults 170 + 171 + When possible, provide sensible default values in your suggestions: 172 + 173 + ```ts 174 + devCmd.option('--port', 'Port number', function(complete) { 175 + complete('3000', 'Development port (default)'); 176 + complete('8080', 'Production port'); 177 + complete('9000', 'Alternative port'); 178 + }, 'p'); 179 + ``` 180 + 181 + ### 3. Use Descriptive Option Names 182 + 183 + Choose option names that clearly indicate their purpose: 184 + 185 + ```ts 186 + // Good - descriptive names 187 + devCmd.option('--output-dir', 'Output directory', handler); 188 + devCmd.option('--source-map', 'Generate source maps', handler); 189 + 190 + // Avoid - ambiguous names 191 + devCmd.option('--output', 'Output directory', handler); 192 + devCmd.option('--map', 'Generate source maps', handler); 193 + ``` 194 + 195 + ## Argument Design Best Practices 196 + 197 + ### 1. Use Variadic Arguments for File Lists 198 + 199 + Use variadic arguments when users might want to specify multiple files: 200 + 201 + ```ts 202 + // Good - variadic argument for multiple files 203 + t.command('lint', 'Lint files') 204 + .argument('files', function(complete) { 205 + complete('src/', 'Source directory'); 206 + complete('tests/', 'Tests directory'); 207 + complete('*.ts', 'TypeScript files'); 208 + }, true); // true = variadic argument 209 + ``` 210 + 211 + ### 2. Provide Contextual Suggestions 212 + 213 + Make argument suggestions contextual to the command: 214 + 215 + ```ts 216 + t.command('copy', 'Copy files') 217 + .argument('source', function(complete) { 218 + // Suggest source locations 219 + complete('src/', 'Source directory'); 220 + complete('dist/', 'Distribution directory'); 221 + complete('public/', 'Public assets'); 222 + }) 223 + .argument('destination', function(complete) { 224 + // Suggest destination locations 225 + complete('build/', 'Build output'); 226 + complete('release/', 'Release directory'); 227 + complete('backup/', 'Backup location'); 228 + }); 229 + ``` 230 + 231 + ## Package Manager Integration Best Practices 102 232 103 - Test your completions across different shells to ensure compatibility: 233 + ### 1. Test CLI Compatibility 234 + 235 + When building CLI tools, test that they work with Tab's package manager integration: 236 + 237 + ```bash 238 + # Test if your CLI supports Tab completions 239 + my-cli complete -- --help 240 + 241 + # Expected output format: 242 + --help Show help information 243 + :0 244 + ``` 245 + 246 + ### 2. Follow the Tab Protocol 247 + 248 + Ensure your CLI follows the Tab completion protocol: 104 249 105 250 ```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); 251 + // In your CLI entry point 252 + if (process.argv[2] === 'complete') { 253 + const shell = process.argv[3]; 254 + if (shell && ['zsh', 'bash', 'fish', 'powershell'].includes(shell)) { 255 + t.setup('my-cli', process.execPath, shell); 113 256 } else { 114 - console.error(`Unsupported shell: ${shell}`); 115 - process.exit(1); 257 + const separatorIndex = process.argv.indexOf('--'); 258 + const completionArgs = separatorIndex !== -1 ? process.argv.slice(separatorIndex + 1) : []; 259 + t.parse(completionArgs); 116 260 } 117 261 } 118 262 ``` 119 263 120 - ## User Experience 264 + ### 3. Provide Comprehensive Completions 121 265 122 - ### Progressive Disclosure 266 + For package manager integration, provide completions for all major commands: 123 267 124 - Show relevant completions based on what the user has already typed: 268 + ```ts 269 + // Example: Comprehensive pnpm completions 270 + const addCmd = t.command('add', 'Install packages'); 271 + addCmd.option('--save-dev', 'Save to devDependencies', 'D'); 272 + addCmd.option('--save-optional', 'Save to optionalDependencies', 'O'); 273 + addCmd.option('--global', 'Install globally', 'g'); 125 274 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 - ]; 275 + const runCmd = t.command('run', 'Run scripts'); 276 + runCmd.argument('script', async function(complete) { 277 + try { 278 + const packageJson = JSON.parse(await fs.readFile('package.json', 'utf8')); 279 + const scripts = Object.keys(packageJson.scripts || {}); 280 + scripts.forEach(script => complete(script, `Run ${script} script`)); 281 + } catch (error) { 282 + // Fallback completions 283 + complete('dev', 'Start development server'); 284 + complete('build', 'Build for production'); 133 285 } 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 - }); 286 + }, true); 142 287 ``` 143 288 144 - ### Consistent Naming 289 + ## Performance Best Practices 290 + 291 + ### 1. Cache Expensive Operations 145 292 146 - Use consistent naming patterns across your CLI: 293 + Cache results of expensive operations to improve responsiveness: 147 294 148 295 ```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 - }); 296 + let cachedDependencies: string[] | null = null; 157 297 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 - }); 298 + t.command('remove', 'Remove packages') 299 + .argument('package', async function(complete) { 300 + if (cachedDependencies) { 301 + cachedDependencies.forEach(dep => complete(dep, 'Installed package')); 302 + return; 303 + } 304 + 305 + try { 306 + const packageJson = JSON.parse(await fs.readFile('package.json', 'utf8')); 307 + const deps = { 308 + ...packageJson.dependencies, 309 + ...packageJson.devDependencies, 310 + }; 311 + cachedDependencies = Object.keys(deps); 312 + cachedDependencies.forEach(dep => complete(dep, 'Installed package')); 313 + } catch (error) { 314 + // Fallback completions 315 + complete('react', 'React library'); 316 + complete('typescript', 'TypeScript compiler'); 317 + } 318 + }); 165 319 ``` 166 320 167 - ### Error Handling 321 + ### 2. Limit Result Sets 168 322 169 - Handle errors gracefully in completion handlers: 323 + Limit the number of completions to maintain performance: 170 324 171 325 ```ts 172 - completion.addOption('deploy', '--config', 'Config file', async () => { 326 + devCmd.option('--config', 'Config file', async function(complete) { 173 327 try { 174 328 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}` })); 329 + const configFiles = files.filter(f => f.includes('config')); 330 + // Limit to 10 results for performance 331 + configFiles.slice(0, 10).forEach(file => complete(file, `Config file: ${file}`)); 178 332 } catch (error) { 179 - // Return empty array instead of throwing 180 - console.error('Error reading config files:', error); 181 - return []; 333 + complete('vite.config.ts', 'Vite config file'); 182 334 } 183 335 }); 184 336 ``` 185 337 186 - ## Performance Considerations 338 + ### 3. Use Async Operations Sparingly 339 + 340 + Only use async operations when necessary: 341 + 342 + ```ts 343 + // Good - sync operations for simple completions 344 + devCmd.option('--mode', 'Build mode', function(complete) { 345 + complete('development', 'Development mode'); 346 + complete('production', 'Production mode'); 347 + complete('staging', 'Staging mode'); 348 + }); 187 349 188 - ### Cache Expensive Operations 350 + // Good - async operations for dynamic data 351 + devCmd.option('--config', 'Config file', async function(complete) { 352 + const files = await fs.readdir('.'); 353 + const configFiles = files.filter(f => f.includes('config')); 354 + configFiles.forEach(file => complete(file, `Config file: ${file}`)); 355 + }); 356 + ``` 189 357 190 - Cache results for expensive operations that don't change frequently: 358 + ## User Experience Best Practices 191 359 192 - ```ts 193 - let cachedPorts: Array<{ value: string; description: string }> | null = null; 360 + ### 1. Provide Progressive Disclosure 194 361 195 - completion.addOption('dev', '--port', 'Port number', async () => { 196 - if (cachedPorts) { 197 - return cachedPorts; 362 + Start with common options and provide more specific ones as users type: 363 + 364 + ```ts 365 + devCmd.option('--mode', 'Build mode', function(complete) { 366 + if (this.toComplete?.startsWith('dev')) { 367 + complete('development', 'Development mode'); 368 + return; 198 369 } 199 370 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 - })); 371 + if (this.toComplete?.startsWith('prod')) { 372 + complete('production', 'Production mode'); 373 + return; 374 + } 206 375 207 - return cachedPorts; 376 + // Show all options initially 377 + complete('development', 'Development mode'); 378 + complete('production', 'Production mode'); 379 + complete('staging', 'Staging mode'); 380 + complete('test', 'Test mode'); 208 381 }); 209 382 ``` 210 383 211 - ### Limit Result Sets 384 + ### 2. Use Consistent Descriptions 212 385 213 - Don't overwhelm users with too many completions: 386 + Maintain consistent description formatting: 214 387 215 388 ```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 - }); 389 + // Good - consistent formatting 390 + devCmd.option('--port', 'Port number', function(complete) { 391 + complete('3000', 'Development port (default)'); 392 + complete('8080', 'Production port'); 393 + complete('9000', 'Alternative port'); 394 + }, 'p'); 395 + 396 + // Avoid - inconsistent formatting 397 + devCmd.option('--port', 'Port number', function(complete) { 398 + complete('3000', 'dev port'); 399 + complete('8080', 'Production port'); 400 + complete('9000', 'alt port'); 401 + }, 'p'); 225 402 ``` 226 403 227 - ## Testing 404 + ### 3. Provide Helpful Defaults 228 405 229 - ### Test Completion Handlers 230 - 231 - Write tests for your completion handlers to ensure they work correctly: 406 + Include default values in descriptions when helpful: 232 407 233 408 ```ts 234 - import { Completion } from '@bombsh/tab'; 409 + devCmd.option('--port', 'Port number', function(complete) { 410 + complete('3000', 'Development port (default)'); 411 + complete('8080', 'Production port'); 412 + }, 'p'); 235 413 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 - }); 414 + devCmd.option('--host', 'Host address', function(complete) { 415 + complete('localhost', 'Localhost (default)'); 416 + complete('0.0.0.0', 'All interfaces'); 417 + }, 'h'); 255 418 ``` 256 419 257 - ### Test Shell Integration 420 + ## Testing Best Practices 258 421 259 - Test that your completion scripts work correctly in different shells: 422 + ### 1. Test Completions Manually 260 423 261 - ```bash 262 - # Test zsh completion 263 - source <(my-cli complete zsh) 264 - my-cli dev --po<TAB> # Should suggest --port 424 + Regularly test your completions to ensure they work correctly: 265 425 266 - # Test bash completion 267 - source <(my-cli complete bash) 268 - my-cli dev --po<TAB> # Should suggest --port 269 - ``` 426 + ```bash 427 + # Test command completions 428 + my-cli complete -- "dev" 270 429 271 - ## Common Patterns 430 + # Test option completions 431 + my-cli complete -- "dev --port" 272 432 273 - ### File Completions 433 + # Test argument completions 434 + my-cli complete -- "copy src/" 274 435 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 - }); 436 + # Test with package managers 437 + pnpm my-cli complete -- "dev --port" 282 438 ``` 283 439 284 - ### Environment Completions 440 + ### 2. Test Error Scenarios 441 + 442 + Test how your completions handle error conditions: 285 443 286 444 ```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 - ]; 445 + // Test with missing files 446 + devCmd.option('--config', 'Config file', async function(complete) { 447 + try { 448 + const files = await fs.readdir('.'); 449 + const configFiles = files.filter(f => f.includes('config')); 450 + configFiles.forEach(file => complete(file, `Config file: ${file}`)); 451 + } catch (error) { 452 + // Ensure fallback completions work 453 + complete('vite.config.ts', 'Vite config file'); 454 + complete('vite.config.js', 'Vite config file'); 455 + } 293 456 }); 294 457 ``` 295 458 296 - ### Command Completions 459 + ### 3. Test Performance 460 + 461 + Monitor completion performance, especially for async operations: 297 462 298 463 ```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 - ]; 464 + // Add timing for performance monitoring 465 + devCmd.option('--config', 'Config file', async function(complete) { 466 + const start = Date.now(); 467 + try { 468 + const files = await fs.readdir('.'); 469 + const configFiles = files.filter(f => f.includes('config')); 470 + configFiles.forEach(file => complete(file, `Config file: ${file}`)); 471 + } catch (error) { 472 + complete('vite.config.ts', 'Vite config file'); 473 + } 474 + const duration = Date.now() - start; 475 + if (duration > 100) { 476 + console.warn(`Slow completion: ${duration}ms`); 477 + } 305 478 }); 306 479 ``` 307 480 308 481 ## Next Steps 309 482 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 483 + - Check out [Examples](/docs/tab/guides/examples/) for practical use cases 484 + - Explore the [API Reference](/docs/tab/api/core/) for advanced usage 485 + - Learn about [Package Manager Integration](/docs/tab/guides/package-managers/) for enhanced completions
+240 -299
src/content/docs/tab/guides/examples.mdx
··· 11 11 12 12 ```ts 13 13 #!/usr/bin/env node 14 - import { Completion, script } from '@bombsh/tab'; 14 + import { RootCommand } from '@bombsh/tab'; 15 15 16 - const name = 'my-cli'; 17 - const completion = new Completion(); 16 + const t = new RootCommand(); 18 17 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 - }); 18 + // Add global options 19 + t.option('--config', 'Use specified config file', function(complete) { 20 + complete('vite.config.ts', 'Vite config file'); 21 + complete('vite.config.js', 'Vite config file'); 22 + }, 'c'); 26 23 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 - ]; 24 + t.option('--mode', 'Set env mode', function(complete) { 25 + complete('development', 'Development mode'); 26 + complete('production', 'Production mode'); 27 + }, 'm'); 28 + 29 + // Add root command argument 30 + t.argument('project', function(complete) { 31 + complete('my-app', 'My application'); 32 + complete('my-lib', 'My library'); 32 33 }); 33 34 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 - ]; 35 + // Add commands with completions 36 + const devCmd = t.command('dev', 'Start development server'); 37 + devCmd.option('--port', 'Port number', function(complete) { 38 + complete('3000', 'Development port'); 39 + complete('8080', 'Production port'); 40 40 }, 'p'); 41 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 - ]; 42 + devCmd.option('--host', 'Host address', function(complete) { 43 + complete('localhost', 'Local development'); 44 + complete('0.0.0.0', 'All interfaces'); 47 45 }, 'h'); 48 46 49 - // Helper function to quote paths with spaces 50 - function quoteIfNeeded(path: string) { 51 - return path.includes(' ') ? `'${path}'` : path; 52 - } 47 + devCmd.option('--verbose', 'Enable verbose logging', 'v'); 48 + 49 + // Add build command 50 + const buildCmd = t.command('build', 'Build for production'); 51 + buildCmd.option('--mode', 'Build mode', function(complete) { 52 + complete('development', 'Development build'); 53 + complete('production', 'Production build'); 54 + }); 55 + 56 + buildCmd.option('--out-dir', 'Output directory', function(complete) { 57 + complete('dist/', 'Distribution directory'); 58 + complete('build/', 'Build directory'); 59 + }); 53 60 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 + // Add command with arguments 62 + t.command('copy', 'Copy files') 63 + .argument('source', function(complete) { 64 + complete('src/', 'Source directory'); 65 + complete('dist/', 'Distribution directory'); 66 + }) 67 + .argument('destination', function(complete) { 68 + complete('build/', 'Build output'); 69 + complete('release/', 'Release directory'); 70 + }); 61 71 62 72 // Handle completion requests 63 - if (process.argv[2] === '--') { 64 - await completion.parse(process.argv.slice(2)); 73 + if (process.argv[2] === 'complete') { 74 + const shell = process.argv[3]; 75 + if (shell && ['zsh', 'bash', 'fish', 'powershell'].includes(shell)) { 76 + t.setup('my-cli', process.execPath, shell); 77 + } else { 78 + // Parse completion arguments (everything after --) 79 + const separatorIndex = process.argv.indexOf('--'); 80 + const completionArgs = separatorIndex !== -1 ? process.argv.slice(separatorIndex + 1) : []; 81 + t.parse(completionArgs); 82 + } 65 83 } else { 66 - script(process.argv[2], name, x); 84 + // Regular CLI usage 85 + console.log('My CLI Tool'); 86 + console.log('Use "complete" command for shell completion'); 67 87 } 68 88 ``` 69 89 ··· 101 121 console.log('Deploying...', options); 102 122 }); 103 123 104 - // Initialize tab completion 105 - const completion = await tab(cli); 124 + // Initialize Tab completion 125 + const completion = tab(cli); 106 126 107 127 // 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 - } 128 + const devCommandCompletion = completion.commands.get('dev'); 129 + if (devCommandCompletion) { 130 + const portOptionCompletion = devCommandCompletion.options.get('--port'); 131 + if (portOptionCompletion) { 132 + portOptionCompletion.handler = async () => { 133 + return [ 134 + { value: '3000', description: 'Development port' }, 135 + { value: '8080', description: 'Production port' }, 136 + ]; 137 + }; 139 138 } 140 139 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 - } 140 + const configOptionCompletion = devCommandCompletion.options.get('--config'); 141 + if (configOptionCompletion) { 142 + configOptionCompletion.handler = async () => { 143 + return [ 144 + { value: 'vite.config.ts', description: 'Vite config file' }, 145 + { value: 'vite.config.js', description: 'Vite config file' }, 146 + ]; 147 + }; 161 148 } 149 + } 162 150 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 - } 151 + const buildCommandCompletion = completion.commands.get('build'); 152 + if (buildCommandCompletion) { 153 + const modeOptionCompletion = buildCommandCompletion.options.get('--mode'); 154 + if (modeOptionCompletion) { 155 + modeOptionCompletion.handler = async () => { 156 + return [ 157 + { value: 'development', description: 'Development build' }, 158 + { value: 'production', description: 'Production build' }, 159 + ]; 160 + }; 184 161 } 185 162 } 186 163 ··· 226 203 }, 227 204 }); 228 205 206 + const deployCommand = defineCommand({ 207 + meta: { 208 + name: 'deploy', 209 + description: 'Deploy application', 210 + }, 211 + args: { 212 + env: { type: 'string', description: 'Deployment environment' }, 213 + region: { type: 'string', description: 'Deployment region' }, 214 + }, 215 + }); 216 + 229 217 main.subCommands = { 230 218 dev: devCommand, 231 219 build: buildCommand, 220 + deploy: deployCommand, 232 221 }; 233 222 234 223 const completion = await tab(main); ··· 236 225 // Configure completions 237 226 const devCommandCompletion = completion.commands.get('dev'); 238 227 if (devCommandCompletion) { 239 - const portOption = devCommandCompletion.options.get('--port'); 240 - if (portOption) { 241 - portOption.handler = async () => { 228 + const portOptionCompletion = devCommandCompletion.options.get('--port'); 229 + if (portOptionCompletion) { 230 + portOptionCompletion.handler = async () => { 242 231 return [ 243 232 { value: '3000', description: 'Development port' }, 244 233 { value: '8080', description: 'Production port' }, 245 234 ]; 246 235 }; 247 236 } 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 237 } 292 238 293 239 const cli = createMain(main); 294 240 cli(); 295 241 ``` 296 242 297 - ## Commander Framework Example 243 + ## Commander.js Example 298 244 299 245 A CLI using Commander.js with Tab integration: 300 246 ··· 313 259 program 314 260 .command('dev') 315 261 .description('Start development server') 316 - .option('-p, --port <port>', 'Specify port', '3000') 317 - .option('-h, --host <host>', 'Specify host', 'localhost') 262 + .option('-p, --port <port>', 'Specify port') 263 + .option('-h, --host <host>', 'Specify host') 318 264 .option('-c, --config <file>', 'Config file') 319 265 .action((options) => { 320 266 console.log('Starting dev server...', options); ··· 323 269 program 324 270 .command('build') 325 271 .description('Build for production') 326 - .option('-m, --mode <mode>', 'Build mode', 'production') 327 - .option('-o, --out-dir <dir>', 'Output directory', 'dist') 272 + .option('-m, --mode <mode>', 'Build mode') 273 + .option('-o, --out-dir <dir>', 'Output directory') 328 274 .action((options) => { 329 275 console.log('Building...', options); 330 276 }); ··· 338 284 console.log('Deploying...', options); 339 285 }); 340 286 341 - // Initialize tab completion 342 287 const completion = tab(program); 343 288 344 - // Configure custom completions 289 + // Configure completions 345 290 const devCommandCompletion = completion.commands.get('dev'); 346 291 if (devCommandCompletion) { 347 - const portOption = devCommandCompletion.options.get('--port'); 348 - if (portOption) { 349 - portOption.handler = async () => { 292 + const portOptionCompletion = devCommandCompletion.options.get('--port'); 293 + if (portOptionCompletion) { 294 + portOptionCompletion.handler = async () => { 350 295 return [ 351 296 { value: '3000', description: 'Development port' }, 352 297 { value: '8080', description: 'Production port' }, 353 298 ]; 354 299 }; 355 300 } 301 + } 356 302 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 - } 303 + program.parse(); 304 + ``` 366 305 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 - } 306 + ## Advanced Examples 377 307 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 - } 308 + ### Dynamic File Completions 389 309 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 - } 310 + Load completions from the file system: 400 311 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 - } 312 + ```ts 313 + import { readdir } from 'fs/promises'; 314 + import { RootCommand } from '@bombsh/tab'; 412 315 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 - } 316 + const t = new RootCommand(); 424 317 425 - program.parse(); 318 + t.command('build', 'Build project') 319 + .option('--config', 'Config file', async function(complete) { 320 + try { 321 + const files = await readdir('.'); 322 + const configFiles = files.filter(f => 323 + f.includes('config') && (f.endsWith('.js') || f.endsWith('.ts')) 324 + ); 325 + configFiles.forEach(file => complete(file, `Config file: ${file}`)); 326 + } catch (error) { 327 + // Fallback completions 328 + complete('vite.config.ts', 'Vite config file'); 329 + complete('vite.config.js', 'Vite config file'); 330 + } 331 + }); 426 332 ``` 427 333 428 - ## Advanced Examples 334 + ### Context-Aware Completions 429 335 430 - ### Dynamic File Completions 336 + Provide different completions based on context: 431 337 432 338 ```ts 433 - import { readdir } from 'fs/promises'; 434 - import { join } from 'path'; 339 + const t = new RootCommand(); 435 340 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 - ); 341 + t.command('deploy', 'Deploy application') 342 + .option('--env', 'Environment', function(complete) { 343 + // Check if user is typing a specific environment 344 + if (this.toComplete?.startsWith('prod')) { 345 + complete('production', 'Production environment'); 346 + return; 347 + } 348 + 349 + complete('development', 'Development environment'); 350 + complete('staging', 'Staging environment'); 351 + complete('production', 'Production environment'); 352 + }) 353 + .option('--region', 'Region', function(complete) { 354 + // Provide region completions based on environment 355 + const env = this.command?.options?.get('--env')?.value; 443 356 444 - return configFiles.map(f => ({ 445 - value: f, 446 - description: `Config file: ${f}` 447 - })); 448 - } catch (error) { 449 - return []; 450 - } 451 - }; 357 + if (env === 'production') { 358 + complete('us-east-1', 'US East (N. Virginia)'); 359 + complete('us-west-2', 'US West (Oregon)'); 360 + complete('eu-west-1', 'Europe (Ireland)'); 361 + } else { 362 + complete('us-east-1', 'US East (N. Virginia)'); 363 + complete('eu-west-1', 'Europe (Ireland)'); 364 + } 365 + }); 452 366 ``` 453 367 454 - ### Context-Aware Completions 368 + ### Package.json Script Completions 369 + 370 + Dynamically load completions from package.json: 455 371 456 372 ```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 - ]; 373 + import { readFile } from 'fs/promises'; 374 + import { RootCommand } from '@bombsh/tab'; 375 + 376 + const t = new RootCommand(); 377 + 378 + t.command('run', 'Run scripts') 379 + .argument('script', async function(complete) { 380 + try { 381 + const packageJson = JSON.parse(await readFile('package.json', 'utf8')); 382 + const scripts = Object.keys(packageJson.scripts || {}); 383 + scripts.forEach(script => complete(script, `Run ${script} script`)); 384 + } catch (error) { 385 + // Fallback completions 386 + complete('dev', 'Start development server'); 387 + complete('build', 'Build for production'); 388 + complete('test', 'Run tests'); 475 389 } 476 - } 477 - 478 - return [ 479 - { value: '3000', description: 'Development port' }, 480 - { value: '8080', description: 'Production port' }, 481 - { value: '9000', description: 'Alternative port' }, 482 - ]; 483 - }; 390 + }); 484 391 ``` 485 392 486 - ### Cached Completions 393 + ### Workspace Completions 394 + 395 + Handle monorepo workspace completions: 487 396 488 397 ```ts 489 - // Cache expensive operations 490 - let cachedPorts: Item[] | null = null; 398 + import { readFile, readdir } from 'fs/promises'; 399 + import { RootCommand } from '@bombsh/tab'; 491 400 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 - }; 401 + const t = new RootCommand(); 402 + 403 + t.command('workspace', 'Workspace commands') 404 + .option('--filter', 'Filter workspaces', async function(complete) { 405 + try { 406 + const packageJson = JSON.parse(await readFile('package.json', 'utf8')); 407 + const workspaces = packageJson.workspaces || []; 408 + 409 + // Get workspace names 410 + const workspaceNames = []; 411 + for (const workspace of workspaces) { 412 + if (workspace.includes('*')) { 413 + // Handle glob patterns 414 + const dir = workspace.replace('/*', ''); 415 + const items = await readdir(dir); 416 + workspaceNames.push(...items); 417 + } else { 418 + workspaceNames.push(workspace); 419 + } 420 + } 421 + 422 + workspaceNames.forEach(name => complete(name, `Workspace: ${name}`)); 423 + } catch (error) { 424 + // Fallback completions 425 + complete('packages/*', 'All packages'); 426 + complete('apps/*', 'All applications'); 427 + } 428 + }); 429 + ``` 430 + 431 + ## Testing Completions 432 + 433 + Test your completions manually: 434 + 435 + ```bash 436 + # Test command completions 437 + my-cli complete -- "dev" 438 + 439 + # Test option completions 440 + my-cli complete -- "dev --port" 441 + 442 + # Test argument completions 443 + my-cli complete -- "copy src/" 444 + 445 + # Generate shell completion script 446 + my-cli complete zsh 506 447 ``` 507 448 508 449 ## Next Steps 509 450 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 451 + - Learn about [Framework Adapters](/docs/tab/guides/adapters/) for easier integration 452 + - Check out [Best Practices](/docs/tab/guides/best-practices/) for effective autocompletions 453 + - Explore the [API Reference](/docs/tab/api/core/) for advanced usage
+261
src/content/docs/tab/guides/package-managers.mdx
··· 1 + --- 2 + title: Package Manager Integration 3 + description: Enhanced completions for npm, pnpm, yarn, and bun with automatic CLI discovery 4 + --- 5 + 6 + Tab provides a standalone CLI tool that enhances package manager completions with automatic discovery of CLI tools that support Tab completions. This guide focuses on the advanced features and unique capabilities of Tab's package manager integration. 7 + 8 + ## Quick Setup 9 + 10 + For basic setup instructions, see the [Getting Started](/docs/tab/basics/getting-started/) guide. Here's the minimal setup: 11 + 12 + ```bash 13 + # Install and configure 14 + npm install -g @bombsh/tab 15 + tab pnpm zsh > ~/.zsh_completions/tab-pnpm.zsh 16 + echo 'source ~/.zsh_completions/tab-pnpm.zsh' >> ~/.zshrc 17 + ``` 18 + 19 + ## Advanced Features 20 + 21 + ### Automatic CLI Discovery 22 + 23 + Tab automatically detects and provides completions for CLI tools in your project: 24 + 25 + ```bash 26 + # Tab automatically discovers and provides completions for: 27 + pnpm vite dev --port # ← Vite completions 28 + pnpm tsc --target # ← TypeScript completions 29 + pnpm eslint --ext # ← ESLint completions 30 + pnpm jest --config # ← Jest completions 31 + ``` 32 + 33 + The discovery process: 34 + 1. **Scans** `node_modules/.bin/` for available CLI tools 35 + 2. **Tests** each tool for Tab compatibility (has a `complete` command) 36 + 3. **Provides** completions automatically for compatible tools 37 + 38 + ### Tab-Compatible CLIs 39 + 40 + A CLI tool is "Tab-compatible" if it follows the Tab protocol: 41 + 42 + ```bash 43 + # Test compatibility 44 + my-cli complete -- --help 45 + 46 + # Expected output: 47 + --help Show help information 48 + :0 49 + ``` 50 + 51 + ### Dynamic Script Completions 52 + 53 + Tab reads your `package.json` to provide intelligent completions: 54 + 55 + ```bash 56 + # Automatically suggests scripts from package.json 57 + pnpm run <TAB> # Shows: dev, build, test, lint, etc. 58 + 59 + # Suggests dependencies for add/remove commands 60 + pnpm add <TAB> # Shows installed packages 61 + pnpm remove <TAB> # Shows installed packages 62 + ``` 63 + 64 + ### Workspace Support 65 + 66 + Enhanced completions for monorepo setups: 67 + 68 + ```bash 69 + # Workspace filtering 70 + pnpm --filter my-app run build 71 + pnpm --filter "packages/*" run test 72 + pnpm --filter "!packages/docs" run lint 73 + 74 + # Tab provides workspace-aware completions for: 75 + # - Workspace names 76 + # - Workspace patterns 77 + # - Workspace-specific scripts 78 + ``` 79 + 80 + ### Context-Aware Completions 81 + 82 + Completions adapt based on your project context: 83 + 84 + ```bash 85 + # In a TypeScript project 86 + pnpm tsc --target <TAB> # Shows: es2020, es2021, es2022, etc. 87 + 88 + # In a Vite project 89 + pnpm vite build --mode <TAB> # Shows: development, production, etc. 90 + 91 + # In a React project 92 + pnpm create-react-app <TAB> # Shows: my-app, my-component, etc. 93 + ``` 94 + 95 + ## Package Manager Specific Features 96 + 97 + ### pnpm Enhancements 98 + 99 + ```bash 100 + # Enhanced workspace commands 101 + pnpm workspace <TAB> # Shows workspace commands 102 + pnpm --filter <TAB> # Shows workspace names and patterns 103 + 104 + # Advanced install options 105 + pnpm add --save-dev <TAB> # Shows dependency types 106 + pnpm install --frozen-lockfile # Shows install options 107 + ``` 108 + 109 + ### npm Enhancements 110 + 111 + ```bash 112 + # Config management 113 + npm config get <TAB> # Shows config keys 114 + npm config set <TAB> # Shows config keys 115 + 116 + # Package management 117 + npm install --save-dev <TAB> # Shows dependency types 118 + npm run <TAB> # Shows scripts with descriptions 119 + ``` 120 + 121 + ### yarn Enhancements 122 + 123 + ```bash 124 + # Workspace operations 125 + yarn workspace <TAB> # Shows workspace commands 126 + yarn workspaces <TAB> # Shows workspace operations 127 + 128 + # Cache management 129 + yarn cache <TAB> # Shows cache operations 130 + yarn cache list # Shows cached packages 131 + ``` 132 + 133 + ### bun Enhancements 134 + 135 + ```bash 136 + # Runtime commands 137 + bun run <TAB> # Shows scripts 138 + bun x <TAB> # Shows available binaries 139 + 140 + # Development features 141 + bun dev <TAB> # Shows dev commands 142 + bun test <TAB> # Shows test options 143 + ``` 144 + 145 + ## Configuration 146 + 147 + ### Environment Variables 148 + 149 + ```bash 150 + # Debug mode for troubleshooting 151 + export DEBUG=1 152 + 153 + # Custom timeout for slow completions 154 + export TAB_COMPLETION_TIMEOUT=3000 155 + 156 + # Disable automatic discovery (fallback to basic completions) 157 + export TAB_DISABLE_DISCOVERY=1 158 + ``` 159 + 160 + ### Performance Optimization 161 + 162 + For large projects, you can optimize performance: 163 + 164 + ```bash 165 + # Increase timeout for projects with many dependencies 166 + export TAB_COMPLETION_TIMEOUT=5000 167 + 168 + # Cache completions (experimental) 169 + export TAB_CACHE_COMPLETIONS=1 170 + ``` 171 + 172 + ## Troubleshooting 173 + 174 + ### Common Issues 175 + 176 + #### Completions are slow 177 + ```bash 178 + # Increase timeout 179 + export TAB_COMPLETION_TIMEOUT=3000 180 + 181 + # Check for slow CLI tools 182 + DEBUG=1 tab pnpm complete -- --help 183 + ``` 184 + 185 + #### Some CLIs don't show completions 186 + ```bash 187 + # Test CLI compatibility 188 + my-cli complete -- --help 189 + 190 + # Check if CLI follows Tab protocol 191 + # Should output completions ending with :{number} 192 + ``` 193 + 194 + #### Workspace completions not working 195 + ```bash 196 + # Verify workspace configuration 197 + cat package.json | grep workspaces 198 + 199 + # Check workspace structure 200 + ls packages/ # Should show workspace directories 201 + ``` 202 + 203 + ### Debug Mode 204 + 205 + Enable detailed logging: 206 + 207 + ```bash 208 + # Full debug output 209 + DEBUG=1 tab pnpm complete -- --help 210 + 211 + # Check discovery process 212 + DEBUG=1 tab pnpm complete -- vite dev 213 + ``` 214 + 215 + ## Advanced Usage 216 + 217 + ### Custom Completion Handlers 218 + 219 + For advanced users, you can extend Tab's completion handlers by modifying the source: 220 + 221 + ```bash 222 + # The completion handlers are in: 223 + # tab/bin/completion-handlers.ts 224 + # tab/bin/package-manager-completion.ts 225 + ``` 226 + 227 + ### Integration with Other Tools 228 + 229 + Tab works alongside other completion tools: 230 + 231 + ```bash 232 + # Use with existing pnpm completions 233 + source <(pnpm completion) 234 + 235 + # Tab enhances existing completions 236 + # rather than replacing them 237 + ``` 238 + 239 + ## Best Practices 240 + 241 + ### 1. Use Consistent Package Manager 242 + Stick to one package manager per project for the best experience. 243 + 244 + ### 2. Keep CLI Tools Updated 245 + Regularly update your CLI tools to ensure Tab compatibility. 246 + 247 + ### 3. Test in Your Projects 248 + ```bash 249 + # Test completions in your specific projects 250 + tab pnpm complete -- --help 251 + pnpm vite complete -- --help 252 + ``` 253 + 254 + ### 4. Share with Team 255 + Share your Tab setup to ensure consistent experience across your team. 256 + 257 + ## Next Steps 258 + 259 + - Learn about [Framework Adapters](/docs/tab/guides/adapters/) for building Tab-compatible CLIs 260 + - Check out [Best Practices](/docs/tab/guides/best-practices/) for effective autocompletions 261 + - Explore the [API Reference](/docs/tab/api/core/) for building your own CLI tools
+41 -24
src/content/docs/tab/index.mdx
··· 19 19 Built-in adapters for popular CLI frameworks including CAC, Citty, and Commander.js for easy integration. 20 20 </Card> 21 21 22 + <Card title="Package Manager Support" icon="seti:npm"> 23 + Built-in completions for npm, pnpm, yarn, and bun with automatic CLI discovery and completion detection. 24 + </Card> 25 + 22 26 <Card title="Type-Safe" icon="rocket"> 23 27 Full TypeScript support with type-safe completion handlers and autocomplete suggestions. 24 28 </Card> ··· 27 31 Define custom completion logic for commands and options with dynamic suggestions based on context. 28 32 </Card> 29 33 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 34 <Card title="Easy Setup" icon="sun"> 35 35 Simple installation and configuration with minimal code changes to add autocompletion to your CLI. 36 36 </Card> ··· 39 39 40 40 ## Quick Start 41 41 42 + ### For CLI Tool Authors 43 + 42 44 Add shell autocompletions to your CLI in just a few lines: 43 45 44 46 ```ts 45 - import { Completion, script } from '@bombsh/tab'; 47 + import { RootCommand, script } from '@bombsh/tab'; 48 + 49 + const t = new RootCommand(); 50 + 51 + // Add commands with completions 52 + t.command('dev', 'Start development server') 53 + .option('--port', 'Port number', function(complete) { 54 + complete('3000', 'Development port'); 55 + complete('8080', 'Production port'); 56 + }, 'p'); 57 + 58 + // Handle completion requests 59 + if (process.argv[2] === 'complete') { 60 + const shell = process.argv[3]; 61 + if (shell && ['zsh', 'bash', 'fish', 'powershell'].includes(shell)) { 62 + t.setup('my-cli', process.execPath, shell); 63 + } else { 64 + const separatorIndex = process.argv.indexOf('--'); 65 + const completionArgs = separatorIndex !== -1 ? process.argv.slice(separatorIndex + 1) : []; 66 + t.parse(completionArgs); 67 + } 68 + } 69 + ``` 46 70 47 - const completion = new Completion(); 71 + ### For End Users 48 72 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 - }); 73 + Get enhanced package manager completions with automatic CLI discovery: 55 74 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'); 75 + ```bash 76 + # Install tab CLI 77 + npm install -g @bombsh/tab 62 78 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 - } 79 + # Generate completions for your preferred package manager 80 + tab pnpm zsh > ~/.zsh_completions/tab-pnpm.zsh 81 + echo 'source ~/.zsh_completions/tab-pnpm.zsh' >> ~/.zshrc 82 + 83 + # Now you get enhanced completions for all CLI tools! 84 + pnpm vite dev --port # ← Tab completion works here 69 85 ``` 70 86 71 87 ## What's Next? 72 88 73 89 - [Getting Started](/docs/tab/basics/getting-started/) - Learn how to add autocompletions to your CLI 74 90 - [Framework Adapters](/docs/tab/guides/adapters/) - Use Tab with CAC, Citty, and Commander.js 91 + - [Package Manager Integration](/docs/tab/guides/package-managers/) - Enhanced completions for npm, pnpm, yarn, and bun 75 92 - [Best Practices](/docs/tab/guides/best-practices/) - Learn best practices for implementing autocompletions 76 93 - [API Reference](/docs/tab/api/core/) - Detailed API documentation