Skip to content

Examples

This guide provides practical examples of using Tab with different CLI frameworks and common scenarios you might encounter when building CLI tools.

A simple CLI tool with basic autocompletions:

#!/usr/bin/env node
import { RootCommand } from '@bombsh/tab';
const t = new RootCommand();
// Add global options
t.option('--config', 'Use specified config file', function(complete) {
complete('vite.config.ts', 'Vite config file');
complete('vite.config.js', 'Vite config file');
}, 'c');
t.option('--mode', 'Set env mode', function(complete) {
complete('development', 'Development mode');
complete('production', 'Production mode');
}, 'm');
// Add root command argument
t.argument('project', function(complete) {
complete('my-app', 'My application');
complete('my-lib', 'My library');
});
// Add commands with completions
const devCmd = t.command('dev', 'Start development server');
devCmd.option('--port', 'Port number', function(complete) {
complete('3000', 'Development port');
complete('8080', 'Production port');
}, 'p');
devCmd.option('--host', 'Host address', function(complete) {
complete('localhost', 'Local development');
complete('0.0.0.0', 'All interfaces');
}, 'h');
devCmd.option('--verbose', 'Enable verbose logging', 'v');
// Add build command
const buildCmd = t.command('build', 'Build for production');
buildCmd.option('--mode', 'Build mode', function(complete) {
complete('development', 'Development build');
complete('production', 'Production build');
});
buildCmd.option('--out-dir', 'Output directory', function(complete) {
complete('dist/', 'Distribution directory');
complete('build/', 'Build directory');
});
// Add command with arguments
t.command('copy', 'Copy files')
.argument('source', function(complete) {
complete('src/', 'Source directory');
complete('dist/', 'Distribution directory');
})
.argument('destination', function(complete) {
complete('build/', 'Build output');
complete('release/', 'Release directory');
});
// Handle completion requests
if (process.argv[2] === 'complete') {
const shell = process.argv[3];
if (shell && ['zsh', 'bash', 'fish', 'powershell'].includes(shell)) {
t.setup('my-cli', process.execPath, shell);
} else {
// Parse completion arguments (everything after --)
const separatorIndex = process.argv.indexOf('--');
const completionArgs = separatorIndex !== -1 ? process.argv.slice(separatorIndex + 1) : [];
t.parse(completionArgs);
}
} else {
// Regular CLI usage
console.log('My CLI Tool');
console.log('Use "complete" command for shell completion');
}

A more complex CLI using CAC with Tab integration:

#!/usr/bin/env node
import cac from 'cac';
import tab from '@bombsh/tab/cac';
const cli = cac('my-cli');
// Define commands
cli.command('dev', 'Start development server')
.option('--port <port>', 'Specify port', { default: 3000 })
.option('--host <host>', 'Specify host', { default: 'localhost' })
.option('--config <file>', 'Config file')
.action((options) => {
console.log('Starting dev server...', options);
});
cli.command('build', 'Build for production')
.option('--mode <mode>', 'Build mode', { default: 'production' })
.option('--out-dir <dir>', 'Output directory')
.action((options) => {
console.log('Building...', options);
});
cli.command('deploy', 'Deploy application')
.option('--env <environment>', 'Deployment environment')
.option('--region <region>', 'Deployment region')
.action((options) => {
console.log('Deploying...', options);
});
// Initialize Tab completion
const completion = tab(cli);
// Configure custom completions
const 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 configOptionCompletion = devCommandCompletion.options.get('--config');
if (configOptionCompletion) {
configOptionCompletion.handler = async () => {
return [
{ value: 'vite.config.ts', description: 'Vite config file' },
{ value: 'vite.config.js', description: 'Vite config file' },
];
};
}
}
const buildCommandCompletion = completion.commands.get('build');
if (buildCommandCompletion) {
const modeOptionCompletion = buildCommandCompletion.options.get('--mode');
if (modeOptionCompletion) {
modeOptionCompletion.handler = async () => {
return [
{ value: 'development', description: 'Development build' },
{ value: 'production', description: 'Production build' },
];
};
}
}
cli.parse();

A CLI using Citty with Tab integration:

#!/usr/bin/env node
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 development server',
},
args: {
port: { type: 'string', description: 'Specify port' },
host: { type: 'string', description: 'Specify host' },
config: { type: 'string', description: 'Config file' },
},
});
const buildCommand = defineCommand({
meta: {
name: 'build',
description: 'Build for production',
},
args: {
mode: { type: 'string', description: 'Build mode' },
outDir: { type: 'string', description: 'Output directory' },
},
});
const deployCommand = defineCommand({
meta: {
name: 'deploy',
description: 'Deploy application',
},
args: {
env: { type: 'string', description: 'Deployment environment' },
region: { type: 'string', description: 'Deployment region' },
},
});
main.subCommands = {
dev: devCommand,
build: buildCommand,
deploy: deployCommand,
};
const completion = await tab(main);
// Configure completions
const 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();

A CLI using Commander.js with Tab integration:

#!/usr/bin/env node
import { Command } from 'commander';
import tab from '@bombsh/tab/commander';
const program = new Command();
program
.name('my-cli')
.description('My CLI tool')
.version('1.0.0');
program
.command('dev')
.description('Start development server')
.option('-p, --port <port>', 'Specify port')
.option('-h, --host <host>', 'Specify host')
.option('-c, --config <file>', 'Config file')
.action((options) => {
console.log('Starting dev server...', options);
});
program
.command('build')
.description('Build for production')
.option('-m, --mode <mode>', 'Build mode')
.option('-o, --out-dir <dir>', 'Output directory')
.action((options) => {
console.log('Building...', options);
});
program
.command('deploy')
.description('Deploy application')
.option('-e, --env <environment>', 'Deployment environment')
.option('-r, --region <region>', 'Deployment region')
.action((options) => {
console.log('Deploying...', options);
});
const completion = tab(program);
// Configure completions
const 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();

Load completions from the file system:

import { readdir } from 'fs/promises';
import { RootCommand } from '@bombsh/tab';
const t = new RootCommand();
t.command('build', 'Build project')
.option('--config', 'Config file', async function(complete) {
try {
const files = await readdir('.');
const configFiles = files.filter(f =>
f.includes('config') && (f.endsWith('.js') || f.endsWith('.ts'))
);
configFiles.forEach(file => complete(file, `Config file: ${file}`));
} catch (error) {
// Fallback completions
complete('vite.config.ts', 'Vite config file');
complete('vite.config.js', 'Vite config file');
}
});

Provide different completions based on context:

const t = new RootCommand();
t.command('deploy', 'Deploy application')
.option('--env', 'Environment', function(complete) {
// Check if user is typing a specific environment
if (this.toComplete?.startsWith('prod')) {
complete('production', 'Production environment');
return;
}
complete('development', 'Development environment');
complete('staging', 'Staging environment');
complete('production', 'Production environment');
})
.option('--region', 'Region', function(complete) {
// Provide region completions based on environment
const env = this.command?.options?.get('--env')?.value;
if (env === 'production') {
complete('us-east-1', 'US East (N. Virginia)');
complete('us-west-2', 'US West (Oregon)');
complete('eu-west-1', 'Europe (Ireland)');
} else {
complete('us-east-1', 'US East (N. Virginia)');
complete('eu-west-1', 'Europe (Ireland)');
}
});

Dynamically load completions from package.json:

import { readFile } from 'fs/promises';
import { RootCommand } from '@bombsh/tab';
const t = new RootCommand();
t.command('run', 'Run scripts')
.argument('script', async function(complete) {
try {
const packageJson = JSON.parse(await readFile('package.json', 'utf8'));
const scripts = Object.keys(packageJson.scripts || {});
scripts.forEach(script => complete(script, `Run ${script} script`));
} catch (error) {
// Fallback completions
complete('dev', 'Start development server');
complete('build', 'Build for production');
complete('test', 'Run tests');
}
});

Handle monorepo workspace completions:

import { readFile, readdir } from 'fs/promises';
import { RootCommand } from '@bombsh/tab';
const t = new RootCommand();
t.command('workspace', 'Workspace commands')
.option('--filter', 'Filter workspaces', async function(complete) {
try {
const packageJson = JSON.parse(await readFile('package.json', 'utf8'));
const workspaces = packageJson.workspaces || [];
// Get workspace names
const workspaceNames = [];
for (const workspace of workspaces) {
if (workspace.includes('*')) {
// Handle glob patterns
const dir = workspace.replace('/*', '');
const items = await readdir(dir);
workspaceNames.push(...items);
} else {
workspaceNames.push(workspace);
}
}
workspaceNames.forEach(name => complete(name, `Workspace: ${name}`));
} catch (error) {
// Fallback completions
complete('packages/*', 'All packages');
complete('apps/*', 'All applications');
}
});

Test your completions manually:

Terminal window
# Test command completions
my-cli complete -- "dev"
# Test option completions
my-cli complete -- "dev --port"
# Test argument completions
my-cli complete -- "copy src/"
# Generate shell completion script
my-cli complete zsh