Adapters
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.
CAC Adapter
Section titled “CAC Adapter”The CAC adapter automatically detects commands and options from your CAC instance and provides completion handlers for customization.
import cac from 'cac';import tab from '@bombsh/tab/cac';
const cli = cac('my-cli');
cli.command('dev', 'Start dev server').option('--port <port>', 'Specify port');cli.command('build', 'Build for production').option('--mode <mode>', 'Build mode');
const completion = tab(cli);
// Get the dev command completion handlerconst devCommandCompletion = completion.commands.get('dev');
// Get and configure the port option completion handlerconst portOptionCompletion = devCommandCompletion.options.get('--port');portOptionCompletion.handler = async () => { return [ { value: '3000', description: 'Development port' }, { value: '8080', description: 'Production port' }, ];};
// Configure build mode completionsconst buildCommandCompletion = completion.commands.get('build');const modeOptionCompletion = buildCommandCompletion.options.get('--mode');modeOptionCompletion.handler = async () => { return [ { value: 'development', description: 'Development build' }, { value: 'production', description: 'Production build' }, ];};
cli.parse();
How the CAC Adapter Works
Section titled “How the CAC Adapter Works”The CAC adapter:
- Extracts Commands: Automatically detects all commands defined with
cli.command()
- Extracts Options: Identifies options defined with
.option()
for each command - Provides Handlers: Gives you access to completion handlers for each command and option
- Maintains Structure: Preserves the command hierarchy and option relationships
Citty Adapter
Section titled “Citty Adapter”The Citty adapter works with Citty’s command definitions and provides a similar interface for adding completions.
import { defineCommand, createMain } from 'citty';import tab from '@bombsh/tab/citty';
const main = defineCommand({ meta: { name: 'my-cli', description: 'My CLI tool', },});
const devCommand = defineCommand({ meta: { name: 'dev', description: 'Start dev server', }, args: { port: { type: 'string', description: 'Specify port' }, host: { type: 'string', description: 'Specify host' }, },});
const buildCommand = defineCommand({ meta: { name: 'build', description: 'Build for production', }, args: { mode: { type: 'string', description: 'Build mode' }, },});
main.subCommands = { dev: devCommand, build: buildCommand,};
const completion = await tab(main);
// Configure completionsconst devCommandCompletion = completion.commands.get('dev');if (devCommandCompletion) { const portOptionCompletion = devCommandCompletion.options.get('--port'); if (portOptionCompletion) { portOptionCompletion.handler = async () => { return [ { value: '3000', description: 'Development port' }, { value: '8080', description: 'Production port' }, ]; }; }}
const cli = createMain(main);cli();
How the Citty Adapter Works
Section titled “How the Citty Adapter Works”The Citty adapter:
- Processes Command Definitions: Analyzes the command structure defined with
defineCommand()
- Extracts Arguments: Identifies arguments defined in the
args
object - Handles Subcommands: Recursively processes nested subcommands
- Provides Type Safety: Leverages Citty’s type system for better completion accuracy
Commander Adapter
Section titled “Commander Adapter”The Commander adapter works with Commander.js and provides completion handlers for its command structure.
import { Command } from 'commander';import tab from '@bombsh/tab/commander';
const program = new Command();
program .name('my-cli') .description('My CLI tool');
program .command('dev') .description('Start development server') .option('-p, --port <port>', 'Specify port') .option('-h, --host <host>', 'Specify host');
program .command('build') .description('Build for production') .option('-m, --mode <mode>', 'Build mode');
const completion = tab(program);
// Configure completionsconst devCommandCompletion = completion.commands.get('dev');if (devCommandCompletion) { const portOptionCompletion = devCommandCompletion.options.get('--port'); if (portOptionCompletion) { portOptionCompletion.handler = async () => { return [ { value: '3000', description: 'Development port' }, { value: '8080', description: 'Production port' }, ]; }; }}
program.parse();
How the Commander Adapter Works
Section titled “How the Commander Adapter Works”The Commander adapter:
- Processes Commands: Analyzes the command structure defined with
program.command()
- Extracts Options: Identifies options defined with
.option()
- Handles Aliases: Automatically processes short and long option aliases
- Maintains Hierarchy: Preserves the command hierarchy and option relationships
Configuration
Section titled “Configuration”All adapters support a configuration object to customize completion behavior:
import { CompletionConfig } from '@bombsh/tab';
const config: CompletionConfig = { handler: async (previousArgs, toComplete, endsWithSpace) => { // Default handler for all commands return []; }, options: { port: { handler: async () => { return [ { value: '3000', description: 'Development port' }, { value: '8080', description: 'Production port' }, ]; }, }, }, subCommands: { dev: { handler: async () => { return [ { value: 'dev', description: 'Development mode' }, ]; }, options: { port: { handler: async () => { return [ { value: '3000', description: 'Dev port' }, ]; }, }, }, }, },};
// Use with any adapterconst completion = await tab(cli, config);
Best Practices
Section titled “Best Practices”1. Error Handling
Section titled “1. Error Handling”Always handle errors gracefully in your completion handlers:
portOptionCompletion.handler = async () => { try { // Expensive operation const results = await getPorts(); return results.map(port => ({ value: port.toString(), description: `Port ${port}` })); } catch (error) { // Return empty array instead of throwing console.error('Error in completion handler:', error); return []; }};
2. Context-Aware Completions
Section titled “2. Context-Aware Completions”Make your completions responsive to what the user is typing:
portOptionCompletion.handler = async (previousArgs, toComplete, endsWithSpace) => { if (toComplete.startsWith('30')) { return [ { value: '3000', description: 'Development port' }, ]; }
return [ { value: '3000', description: 'Development port' }, { value: '8080', description: 'Production port' }, { value: '9000', description: 'Alternative port' }, ];};
3. Performance Optimization
Section titled “3. Performance Optimization”Cache expensive operations and limit results:
let cachedPorts: Item[] | null = null;
portOptionCompletion.handler = async () => { if (cachedPorts) { return cachedPorts; }
const ports = await getAvailablePorts(); cachedPorts = ports.slice(0, 20).map(port => ({ value: port.toString(), description: `Port ${port}` }));
return cachedPorts;};
4. Framework-Specific Considerations
Section titled “4. Framework-Specific Considerations”- CAC automatically handles option parsing, so your completion handlers should focus on providing relevant suggestions
- Use the option name as defined in your CAC command (e.g.,
--port <port>
becomes--port
)
- Citty’s type system provides better type safety for completions
- Arguments defined in the
args
object are automatically converted to options - Subcommands are processed recursively
Commander.js
Section titled “Commander.js”- Commander.js supports both short and long option aliases
- The adapter automatically handles both forms
- Commands can have multiple aliases
Integration with Shell Setup
Section titled “Integration with Shell Setup”After setting up your adapter, you still need to handle shell completion setup:
// Add this to your CLI entry pointif (process.argv[2] === 'complete') { const shell = process.argv[3]; if (shell && ['zsh', 'bash', 'fish', 'powershell'].includes(shell)) { // Generate shell completion script const script = generateCompletionScript(shell, 'my-cli', process.execPath); console.log(script); } else { // Handle completion requests const separatorIndex = process.argv.indexOf('--'); const completionArgs = separatorIndex !== -1 ? process.argv.slice(separatorIndex + 1) : []; await completion.parse(completionArgs); }}
Next Steps
Section titled “Next Steps”- Learn about Best Practices for effective autocompletions
- Check out Examples for practical use cases
- Explore the API Reference for advanced usage