Shell Completions
Shell completions let users press Tab to auto-complete subcommands, flags, and arguments. For CLI tools with dozens of flags and nested subcommands, good completions dramatically reduce friction. All three reference implementations generate completion scripts at runtime rather than shipping static files, but the mechanisms vary: Python’s shtab, Rust’s clap_complete, and Node’s yargs built-in completion.
1. Aider Implementation
Section titled “1. Aider Implementation”Reference: references/aider/ at commit b9050e1d
Library: shtab
Section titled “Library: shtab”Aider uses shtab, a Python library that generates shell completion scripts from argparse/configargparse parser definitions. The integration is minimal — shtab introspects the parser’s argument definitions and produces a completion script for the requested shell.
CLI Entry Point
Section titled “CLI Entry Point”The --shell-completions flag is defined in aider/args.py:853-862:
supported_shells_list = sorted(list(shtab.SUPPORTED_SHELLS))group.add_argument( "--shell-completions", metavar="SHELL", choices=supported_shells_list, help=( "Print shell completion script for the specified SHELL and exit. Supported shells:" f" {', '.join(supported_shells_list)}. Example: aider --shell-completions bash" ),)The handler in aider/main.py:506-509 catches the flag early and exits:
if args.shell_completions: parser.prog = "aider" print(shtab.complete(parser, shell=args.shell_completions)) sys.exit(0)Supported Shells
Section titled “Supported Shells”bash, zsh, and tcsh (the set defined by shtab.SUPPORTED_SHELLS). Fish is not supported by shtab.
File Path Completion Markers
Section titled “File Path Completion Markers”Aider annotates 15 arguments that accept file paths with shtab.FILE markers. For example, from aider/args.py:58:
parser.add_argument( "--model-settings-file", ...).complete = shtab.FILEThis tells shtab to generate file path completion logic for these specific arguments. Other arguments (model names, color codes, format strings) have no special completion — they fall back to the shell’s default behavior (no suggestions).
Static Completions Only
Section titled “Static Completions Only”The generated script is entirely static. It encodes the argument names and their types (file, choice list, etc.) at generation time. If Aider adds new flags in a newer version, the user must regenerate the completion script. There is no mechanism for dynamic model name completion or lazy fetching of available options.
Installation
Section titled “Installation”Manual. The user runs aider --shell-completions bash >> ~/.bashrc (or the equivalent for their shell) and sources the updated config. Aider’s installer and pip package do not install completions automatically.
2. Codex Implementation
Section titled “2. Codex Implementation”Reference: references/codex/ at commit 4ab44e2c
Library: clap_complete
Section titled “Library: clap_complete”Codex uses clap_complete, the standard completion generation crate for Rust’s clap argument parser. The dependency is declared in codex-rs/cli/Cargo.toml:
clap = { workspace = true, features = ["derive"] }clap_complete = { workspace = true }CLI Entry Point
Section titled “CLI Entry Point”Codex exposes a dedicated completion subcommand rather than a flag. The subcommand is defined in codex-rs/cli/src/main.rs:109-110:
/// Generate shell completion scripts.Completion(CompletionCommand),The CompletionCommand struct (main.rs:148-153):
#[derive(Debug, Parser)]struct CompletionCommand { /// Shell to generate completions for #[clap(value_enum, default_value_t = Shell::Bash)] shell: Shell,}The handler (main.rs:1027-1031):
fn print_completion(cmd: CompletionCommand) { let mut app = MultitoolCli::command(); let name = "codex"; generate(cmd.shell, &mut app, name, &mut std::io::stdout());}Supported Shells
Section titled “Supported Shells”bash, zsh, fish, PowerShell, and elvish (the full clap_complete::Shell enum). The default is bash if no shell is specified.
What Gets Completed
Section titled “What Gets Completed”clap_complete generates completions for all subcommands, flags, and value_enum arguments automatically from the clap derive macros. This includes:
- Subcommands:
codex completion,codex sandbox,codex debug,codex resume,codex fork,codex apply,codex review - Flags:
--model,--approval-policy,--sandbox,-c,--full-auto,--quiet, etc. - Enum values: Shell names for
completion, sandbox modes, approval policies — any argument defined with#[clap(value_enum)]gets its valid values embedded in the completion script
File path completion is handled automatically by clap for arguments that accept paths.
The Node Wrapper
Section titled “The Node Wrapper”The codex-cli npm package (codex-cli/bin/codex.js) is a thin wrapper that spawns the Rust binary. It does not add its own shell completions — users must use the Rust binary’s codex completion command directly.
Installation
Section titled “Installation”Manual, like Aider. Typical installation patterns:
# Bashcodex completion bash > /usr/share/bash-completion/completions/codex# Zshcodex completion zsh > ~/.zfunc/_codex# Fishcodex completion fish > ~/.config/fish/completions/codex.fishOr dynamic eval for development:
eval "$(codex completion zsh)"3. OpenCode Implementation
Section titled “3. OpenCode Implementation”Reference: references/opencode/ at commit 7ed44997
Library: yargs Built-in
Section titled “Library: yargs Built-in”OpenCode uses yargs, a Node.js CLI framework with built-in completion support. The completion subcommand is registered in packages/opencode/src/index.ts:121:
const cli = yargs(hideBin(process.argv)) .scriptName("opencode") // ... options and commands ... .completion("completion", "generate shell completion script")How It Works
Section titled “How It Works”Yargs’ .completion() method generates a bash/zsh completion script that, when sourced, calls opencode --get-yargs-completions at Tab-press time. This is fundamentally different from Aider and Codex: yargs completions are dynamic. When the user presses Tab, the shell calls back into the running opencode binary, which introspects its own command structure and returns matching options.
Supported Shells
Section titled “Supported Shells”bash and zsh (yargs’ standard support). Fish is not directly supported by yargs’ built-in completion.
Auto-Discovery
Section titled “Auto-Discovery”Because yargs introspects the command structure at runtime, completions automatically stay in sync with the codebase. When a new command is added via .command(), it appears in completions without any manual registration. This is a significant advantage over shtab and clap_complete, which produce static scripts that must be regenerated.
The Trade-off
Section titled “The Trade-off”Dynamic completions require the CLI binary to be invoked on every Tab press. For a Bun-based TypeScript tool, this means a cold-start latency on each completion request. In practice, yargs completions finish in under 100ms on most systems, but it’s noticeably slower than sourcing a pre-generated static script.
Installation
Section titled “Installation”eval "$(opencode completion)"Or pipe to a file for persistent installation. The opencode package does not install completions during npm install.
4. Pitfalls & Hard Lessons
Section titled “4. Pitfalls & Hard Lessons”Static vs Dynamic: The Core Trade-off
Section titled “Static vs Dynamic: The Core Trade-off”Static completions (shtab, clap_complete) are fast (no binary invocation at Tab time) but go stale when the CLI changes. Dynamic completions (yargs) are always current but add latency. For a Rust binary with sub-millisecond startup, dynamic completions could be viable, but the completion protocol itself (shell calls binary, binary prints options, shell displays) adds overhead regardless.
No Tool Auto-Installs Completions
Section titled “No Tool Auto-Installs Completions”None of the three reference implementations install completions automatically. Users must manually run the generation command and source the output. This is a deliberate choice — modifying shell config files without consent is invasive — but it means most users never set up completions at all.
Fish Support Is Inconsistent
Section titled “Fish Support Is Inconsistent”Aider (shtab) doesn’t support fish. Codex (clap_complete) does. OpenCode (yargs) doesn’t natively. Fish is increasingly popular among developer tools users, so lacking support is a meaningful gap. The clap_complete crate handles fish out of the box, which is one advantage of the Rust ecosystem.
Model Name Completion Is Missing Everywhere
Section titled “Model Name Completion Is Missing Everywhere”None of the three tools complete model names dynamically. When a user types --model gpt-4 and presses Tab, nothing happens. This is a missed opportunity — the list of valid models is finite and could be fetched from cached provider metadata. Codex’s ModelsManager with ETag-cached model lists would be the natural data source for this, but no completion hook exists.
Subcommand vs Flag Design
Section titled “Subcommand vs Flag Design”Aider uses a flag (--shell-completions bash) while Codex uses a subcommand (codex completion bash). The subcommand approach is cleaner: it’s discoverable via codex --help as a first-class entry, and it avoids polluting the main flag namespace. The flag approach requires the parser to handle --shell-completions before any other argument processing, which is fragile.
Completion Script Versioning
Section titled “Completion Script Versioning”When a user upgrades their CLI tool, the old completion script may reference flags or subcommands that no longer exist, or miss new ones. None of the tools include a version check in the completion script. A simple # Generated by codex v1.2.3 comment would let users detect staleness, but nobody does this.
5. OpenOxide Blueprint
Section titled “5. OpenOxide Blueprint”Library: clap_complete
Section titled “Library: clap_complete”Use clap_complete as the primary generation engine. It covers bash, zsh, fish, PowerShell, and elvish with zero custom code. Completion scripts are generated from the same clap derive macros that define the CLI structure, so they stay synchronized with the implementation by construction.
Subcommand Design
Section titled “Subcommand Design”Expose completions as a subcommand, following Codex’s pattern:
openoxide completion [SHELL]Default to the user’s current shell (detected from $SHELL) if no argument is given. This avoids the common user error of running completion bash when they’re actually using zsh.
Shell Auto-Detection
Section titled “Shell Auto-Detection”fn default_shell() -> Shell { std::env::var("SHELL") .ok() .and_then(|s| s.rsplit('/').next().map(String::from)) .and_then(|name| name.parse::<Shell>().ok()) .unwrap_or(Shell::Bash)}Dynamic Model Completion (Future)
Section titled “Dynamic Model Completion (Future)”Consider adding a clap_complete::DynComplete integration that calls into the cached model registry to complete --model arguments at Tab time. This would require the binary to perform a fast lookup against the cached provider metadata (no network call). The ModelsManager cache (see Provider Abstraction) would be the data source.
Installation Helper
Section titled “Installation Helper”Provide an openoxide completion install subcommand that:
- Detects the user’s shell
- Generates the completion script
- Prints the exact command needed to install it (e.g.,
source <(openoxide completion zsh)) - Optionally appends to the shell config file with
--writeflag, after explicit user confirmation
This lowers the friction without being invasive.
Crates
Section titled “Crates”clap_complete— Completion script generation for 5 shellsclapwithderivefeature — CLI definition that doubles as the completion schema