Add fabro completion subcommand for shell completions

Uses clap_complete to generate tab-completion scripts for zsh, fish,
elvish, and PowerShell. Bash generation is caught gracefully since
clap_complete panics with #[command(flatten)] subcommands.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-30 12:36:45 -04:00
parent 41f7b0cda6
commit 93aa239056
10 changed files with 177 additions and 1 deletions

10
Cargo.lock generated
View file

@ -628,6 +628,15 @@ dependencies = [
"strsim 0.11.1",
]
[[package]]
name = "clap_complete"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "4.5.55"
@ -1448,6 +1457,7 @@ dependencies = [
"base64",
"chrono",
"clap",
"clap_complete",
"cli-table",
"console 0.15.11",
"core-foundation 0.9.4",

View file

@ -26,6 +26,7 @@ base64 = "0.22"
bytes = "1"
tokio-util = "0.7"
clap = { version = "4", features = ["derive", "env"] }
clap_complete = "4"
jsonschema = { version = "0.42", default-features = false }
chrono = { version = "0.4", features = ["clock"] }
bollard = "0.18"

View file

@ -103,6 +103,7 @@
"pages": [
"reference/dot-language",
"reference/cli",
"reference/shell-completions",
"reference/user-configuration",
"reference/run-directory",
"reference/sdk",

View file

@ -888,6 +888,24 @@ fabro store dump abc123 -o ./debug-output
---
## `fabro completion`
Generate shell completion scripts for bash, zsh, fish, elvish, and PowerShell.
```bash
fabro completion bash
fabro completion zsh
fabro completion fish
```
| Argument | Description |
|---|---|
| `<SHELL>` | Shell to generate completions for: `bash`, `zsh`, `fish`, `elvish`, `powershell` (required) |
See [Shell Completions](/reference/shell-completions) for installation instructions for each shell.
---
## `fabro docs`
Open the Fabro documentation website in your default browser.

View file

@ -0,0 +1,70 @@
---
title: "Shell Completions"
description: "Set up tab completion for the fabro CLI in your shell"
---
The `fabro completion` command generates shell completion scripts for tab-completing commands, flags, and arguments.
## Bash
Add to your `~/.bashrc`:
```bash
eval "$(fabro completion bash)"
```
Or generate a file and source it:
```bash
fabro completion bash > ~/.local/share/bash-completion/completions/fabro
```
## Zsh
Add to your `~/.zshrc` (before `compinit`):
```bash
eval "$(fabro completion zsh)"
```
Or generate a file:
```bash
fabro completion zsh > "${fpath[1]}/_fabro"
```
You may need to run `compinit` or start a new shell session for changes to take effect.
## Fish
```bash
fabro completion fish | source
```
Or persist to the completions directory:
```bash
fabro completion fish > ~/.config/fish/completions/fabro.fish
```
## PowerShell
Add to your PowerShell profile:
```powershell
fabro completion powershell | Out-String | Invoke-Expression
```
## Elvish
```bash
eval (fabro completion elvish | slurp)
```
## Supported shells
Run `fabro completion --help` to see all supported shells:
```bash
fabro completion --help
```

View file

@ -42,6 +42,7 @@ fabro-store = { path = "../fabro-store" }
fabro-types = { path = "../fabro-types" }
fabro-util = { path = "../fabro-util" }
clap.workspace = true
clap_complete.workspace = true
cli-table.workspace = true
console.workspace = true
indicatif.workspace = true

View file

@ -812,6 +812,8 @@ pub(crate) enum Commands {
#[command(subcommand)]
command: SandboxCommand,
},
/// Generate shell completions
Completion(CompletionArgs),
/// System maintenance commands
System(SystemNamespace),
/// Send a queued analytics event (internal)
@ -893,6 +895,7 @@ impl Commands {
ProviderCommand::Login(_) => "provider login",
},
Self::Sandbox { command } => command.name(),
Self::Completion(_) => "completion",
Self::System(ns) => match &ns.command {
SystemCommand::Prune(_) => "system prune",
SystemCommand::Df(_) => "system df",
@ -1040,6 +1043,12 @@ pub(crate) enum ProviderCommand {
Login(ProviderLoginArgs),
}
#[derive(Args)]
pub(crate) struct CompletionArgs {
/// Shell to generate completions for
pub shell: clap_complete::Shell,
}
#[derive(Args)]
pub(crate) struct LlmNamespace {
#[command(subcommand)]

View file

@ -13,7 +13,7 @@ use anyhow::Result;
use args::{Commands, GlobalArgs, LONG_VERSION, RunCommands};
#[cfg(feature = "server")]
use args::{ServerCommand, ServerNamespace};
use clap::Parser;
use clap::{CommandFactory, Parser};
use fabro_telemetry::{git, panic as tel_panic, sanitize, sender};
use fabro_util::printer::Printer;
use fabro_util::terminal::Styles;
@ -225,6 +225,27 @@ async fn main_inner() -> (String, Result<()>) {
Commands::Provider(ns) => commands::provider::dispatch(ns).await?,
Commands::Sandbox { command } => commands::sandbox::dispatch(command, &globals).await?,
Commands::System(ns) => commands::system::dispatch(ns, &globals).await?,
Commands::Completion(args) => {
let mut cmd = Cli::command();
let shell = args.shell;
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let mut buf = Vec::new();
clap_complete::generate(shell, &mut cmd, "fabro", &mut buf);
buf
}));
match result {
Ok(buf) => {
use std::io::Write;
std::io::stdout().write_all(&buf)?;
}
Err(_) => {
anyhow::bail!(
"Failed to generate completions for {shell}. \
Try zsh, fish, elvish, or powershell instead."
);
}
}
}
Commands::SendAnalytics { path } => {
let result = sender::upload(&path).await;
let _ = std::fs::remove_file(&path);

View file

@ -0,0 +1,44 @@
use fabro_test::{fabro_snapshot, test_context};
#[test]
fn help() {
let context = test_context!();
let mut cmd = context.command();
cmd.args(["completion", "--help"]);
fabro_snapshot!(context.filters(), cmd, @"
success: true
exit_code: 0
----- stdout -----
Generate shell completions
Usage: fabro completion [OPTIONS] <SHELL>
Arguments:
<SHELL> Shell to generate completions for [possible values: bash, elvish, fish, powershell, zsh]
Options:
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
--quiet Suppress non-essential output [env: FABRO_QUIET=]
--verbose Enable verbose output [env: FABRO_VERBOSE=]
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
-h, --help Print help
----- stderr -----
");
}
#[test]
fn generates_zsh_completions() {
let context = test_context!();
let mut cmd = context.command();
cmd.args(["completion", "zsh"]);
cmd.assert().success();
}
#[test]
fn generates_fish_completions() {
let context = test_context!();
let mut cmd = context.command();
cmd.args(["completion", "fish"]);
cmd.assert().success();
}

View file

@ -2,6 +2,7 @@ mod asset;
mod asset_cp;
mod asset_list;
mod attach;
mod completion;
mod config;
mod config_show;
mod cp;