@bunli/plugin-completions
Shell completion generation plugin for Bunli
Installation
bun add @bunli/plugin-completionsFeatures
- 🐚 Multi-shell support: Generate completions for Bash, Zsh, Fish, and PowerShell
- 🎯 Type-aware: Leverages command metadata from Bunli's type generation
- 📋 Enum completions: Automatically completes enum option values
- ⚡ Zero configuration: Works out of the box with your existing commands
- 🔧 Flexible output: Save to file or output to stdout
- 📝 Shell-specific syntax: Generates idiomatic completions for each shell
- 🚀 Auto-generated: No manual completion script maintenance required
Basic Usage
import { createCLI } from "@bunli/core";
import { completionsPlugin } from "@bunli/plugin-completions";
import build from "./commands/build.js";
import deploy from "./commands/deploy.js";
const cli = await createCLI({
name: "my-cli",
version: "1.0.0",
plugins: [completionsPlugin()] as const,
});
cli.command(deploy);
cli.command(build);
await cli.run();Or in your bunli.config.ts:
import { defineConfig } from "@bunli/core";
import { completionsPlugin } from "@bunli/plugin-completions";
export default defineConfig({
name: "my-cli",
plugins: [completionsPlugin()] as const,
});Generating Completions
The plugin adds a completions command to your CLI:
# Output to stdout with installation instructions
my-cli completions bash
my-cli completions zsh
my-cli completions fish
my-cli completions powershellInstallation Methods
Bash Completions
Option 1 - Current session:
source <(my-cli completions bash)Option 2 - Save to completion directory:
# Linux
my-cli completions bash > /etc/bash_completion.d/my-cli
# macOS with Homebrew
my-cli completions bash > $(brew --prefix)/etc/bash_completion.d/my-cliOption 3 - Add to ~/.bashrc:
echo 'source <(my-cli completions bash)' >> ~/.bashrc
source ~/.bashrcZsh Completions
Option 1 - Current session:
source <(my-cli completions zsh)Option 2 - User completion directory:
mkdir -p ~/.zsh/completions
my-cli completions zsh > ~/.zsh/completions/_my-cli
# Add to ~/.zshrc if not already present:
echo 'fpath=(~/.zsh/completions $fpath)' >> ~/.zshrc
echo 'autoload -U compinit && compinit' >> ~/.zshrc
source ~/.zshrcOption 3 - System-wide:
sudo my-cli completions zsh > /usr/local/share/zsh/site-functions/_my-cli
# Restart your shellFish Completions
User completions:
my-cli completions fish > ~/.config/fish/completions/my-cli.fish
# Completions will be available in new fish sessionsSystem-wide:
sudo my-cli completions fish > /usr/share/fish/vendor_completions.d/my-cli.fishHow It Works
The plugin leverages Bunli's metadata generation system to create accurate completions:
- Metadata Extraction: Reads the
.bunli/commands.gen.tsfile generated by Bunli - Command Structure: Parses command names, descriptions, options, and flags
- Type Information: Uses Zod schema metadata to extract enum values and validation rules
- Shell-Specific Generation: Generates appropriate completion syntax for each shell
Examples
Enum Value Completions
Given a command with enum options:
import { defineCommand, option } from "@bunli/core";
import { z } from "zod";
export default defineCommand({
name: "deploy",
description: "Deploy application",
options: {
environment: option(z.enum(["development", "staging", "production"]), {
short: "e",
description: "Target environment",
}),
region: option(z.enum(["us-east-1", "us-west-2", "eu-west-1"]), {
short: "r",
description: "AWS region",
}),
},
handler: async ({ flags }) => {
console.log(`Deploying to ${flags.environment} in ${flags.region}`);
},
});The completions plugin will generate:
Bash:
# Provides enum completions for --environment
if [[ "$prev" == "--environment" || "$prev" == "-e" ]]; then
COMPREPLY=($(compgen -W "development staging production" -- "$cur"))
return
fi
# Provides enum completions for --region
if [[ "$prev" == "--region" || "$prev" == "-r" ]]; then
COMPREPLY=($(compgen -W "us-east-1 us-west-2 eu-west-1" -- "$cur"))
return
fiZsh:
"(-e --environment)"{-e,--environment}[Target environment]:(development staging production)
"(-r --region)"{-r,--region}[AWS region]:(us-east-1 us-west-2 eu-west-1)Fish:
complete -c my-cli -n '__fish_seen_subcommand_from deploy' \
-l environment -s e -d 'Target environment' -r -a 'development staging production'
complete -c my-cli -n '__fish_seen_subcommand_from deploy' \
-l region -s r -d 'AWS region' -r -a 'us-east-1 us-west-2 eu-west-1'Multiple Commands
// commands/build.ts
export default defineCommand({
name: "build",
description: "Build the project",
options: {
mode: option(z.enum(["development", "production"]), {
short: "m",
description: "Build mode",
}),
},
handler: async ({ flags }) => {
console.log(`Building in ${flags.mode} mode`);
},
});
// commands/test.ts
export default defineCommand({
name: "test",
description: "Run tests",
options: {
coverage: option(z.boolean().optional(), {
short: "c",
description: "Generate coverage report",
}),
},
handler: async ({ flags }) => {
console.log("Running tests...");
},
});Completions will include both commands:
# Bash
my-cli <TAB>
# Shows: build test --help --version
my-cli build --mode <TAB>
# Shows: development productionBoolean Flags
Boolean flags are automatically detected and don't require values:
export default defineCommand({
name: "serve",
options: {
watch: option(z.boolean().optional(), {
short: "w",
description: "Watch for changes",
}),
open: option(z.boolean().optional(), {
short: "o",
description: "Open in browser",
}),
},
handler: async ({ flags }) => {
// ...
},
});my-cli serve --watch --open # No values required
my-cli serve -w -o # Short flags work tooPlugin Options
interface CompletionsPluginOptions {
/**
* Path to Bunli-generated command metadata.
* Default: ".bunli/commands.gen.ts" (relative to process.cwd()).
*/
generatedPath?: string;
/**
* Name registered with the shell completion script (usually the package.json bin key).
*/
commandName?: string;
/**
* Executable invocation used by shell scripts (can include args, e.g. "bunx my-cli").
* Default: commandName.
*/
executable?: string;
/**
* Whether to register command aliases from generated metadata.
* Default: true.
*/
includeAliases?: boolean;
/**
* Whether to include Bunli global flags on every command.
* Default: true.
*/
includeGlobalFlags?: boolean;
}Shell Types
The plugin supports four shells:
PowerShell Completions
User profile:
my-cli completions powershell >> $PROFILE
# Reload profile:
. $PROFILEOr save to a file:
my-cli completions powershell > ~/Documents/PowerShell/my-cli_completions.ps1
# Add to $PROFILE:
. ~/Documents/PowerShell/my-cli_completions.ps1Best Practices
1. Use Enum Types for Fixed Values
// ✅ Good - generates completions
environment: option(z.enum(["dev", "staging", "prod"]), {
description: "Target environment",
});
// ❌ Less useful - no completions
environment: option(z.string(), {
description: "Target environment",
});2. Provide Descriptions
// ✅ Good - shows helpful descriptions
preset: option(z.enum(["minimal", "standard", "full"]), {
description: "Configuration preset",
});
// ❌ Less helpful - no context
preset: option(z.enum(["minimal", "standard", "full"]));3. Use Short Flags
// ✅ Good - both long and short forms complete
force: option(z.boolean().optional(), {
short: "f",
description: "Force operation",
});4. Generate After Command Changes
Always regenerate completions when you add or modify commands:
# Make changes to commands
# ...
# Regenerate type metadata
bun run generate
# Update installed completions
my-cli completions bash > ~/.bash_completion.d/my-cliDistribution
Including Completions in Your Package
You can include pre-generated completions in your package:
{
"name": "my-cli",
"files": ["dist", "completions"],
"scripts": {
"completions": "bun run cli.js completions",
"postinstall": "node scripts/install-completions.js"
}
}Create a postinstall script to prompt users:
// scripts/install-completions.js
import { execSync } from "child_process";
import { existsSync, writeFileSync, mkdirSync } from "fs";
import { homedir } from "os";
import { join } from "path";
const shell = process.env.SHELL?.includes("zsh")
? "zsh"
: process.env.SHELL?.includes("fish")
? "fish"
: "bash";
console.log(`\n🐚 Shell completions are available for ${shell}`);
console.log(`Run: my-cli completions ${shell}\n`);Documentation for Users
Include installation instructions in your README:
## Shell Completions
Enable tab completion for your shell:
### Bash
```bash
my-cli completions bash > ~/.bash_completion.d/my-cli
source ~/.bashrc
```
### Zsh
```bash
my-cli completions zsh > ~/.zsh/completions/_my-cli
# Add to ~/.zshrc:
fpath=(~/.zsh/completions $fpath)
autoload -U compinit && compinit
```
### Fish
```bash
my-cli completions fish > ~/.config/fish/completions/my-cli.fish
```Limitations
- No subcommand completions: Currently only supports top-level commands
- No dynamic completions: Completions are static based on command metadata
- No positional argument completions: Only flags and options are completed
- No custom completion functions: Cannot define custom completion logic
Troubleshooting
Completions Not Working
-
Check shell type: Verify you generated completions for the correct shell
echo $SHELL -
Verify installation: Make sure the completion file is in the correct location
# Bash ls ~/.bash_completion.d/ # Zsh ls ~/.zsh/completions/ # Fish ls ~/.config/fish/completions/ -
Reload shell: Source your shell config or restart your shell
# Bash source ~/.bashrc # Zsh source ~/.zshrc # Fish - restart fish
Enum Values Not Completing
- Regenerate metadata: Run
bun run generateto update command metadata - Check schema: Verify you're using
z.enum()notz.string() - Test output: Check the generated completion script includes enum values
Wrong Command Name
The plugin uses the CLI name from createCLI() or bunli.config.ts. Update it:
const cli = await createCLI({
name: "correct-cli-name", // This name is used in completions
// ...
});