From 6cbd0117055462654cfbecc6517efed67702c02b Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Fri, 1 May 2026 18:13:17 -0400 Subject: [PATCH] cli: add 'completions ' subcommand via clap_complete --- Cargo.lock | 10 ++++++++ crates/relicario-cli/Cargo.toml | 1 + crates/relicario-cli/src/main.rs | 15 ++++++++++- crates/relicario-cli/tests/smart_inputs.rs | 30 ++++++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 crates/relicario-cli/tests/smart_inputs.rs diff --git a/Cargo.lock b/Cargo.lock index 19c769d..c1ef94a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,6 +357,15 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "660c0520455b1013b9bcb0393d5f643d7e4454fb69c915b8d6d2aa0e9a45acc3" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.6.0" @@ -1604,6 +1613,7 @@ dependencies = [ "assert_cmd", "chrono", "clap", + "clap_complete", "data-encoding", "dirs", "ed25519-dalek", diff --git a/crates/relicario-cli/Cargo.toml b/crates/relicario-cli/Cargo.toml index e56ab64..f07da01 100644 --- a/crates/relicario-cli/Cargo.toml +++ b/crates/relicario-cli/Cargo.toml @@ -25,6 +25,7 @@ zeroize = "1" url = "2" data-encoding = "2" tar = { version = "0.4", default-features = false } +clap_complete = "4" [dev-dependencies] assert_cmd = "2" diff --git a/crates/relicario-cli/src/main.rs b/crates/relicario-cli/src/main.rs index 6153420..1989d45 100644 --- a/crates/relicario-cli/src/main.rs +++ b/crates/relicario-cli/src/main.rs @@ -8,7 +8,8 @@ mod session; use std::path::PathBuf; use anyhow::{bail, Context, Result}; -use clap::{Parser, Subcommand}; +use clap::{CommandFactory, Parser, Subcommand}; +use clap_complete::{generate, Shell}; #[derive(Parser)] #[command( @@ -163,6 +164,13 @@ enum Commands { /// Lock the vault (no-op in CLI; present for UX parity with the extension). Lock, + + /// Emit a shell completion script for the given shell. + /// Pipe to your shell's completion file (e.g. `> /etc/bash_completion.d/relicario`). + Completions { + #[arg(value_enum)] + shell: Shell, + }, } #[derive(Subcommand)] @@ -354,6 +362,11 @@ fn main() -> Result<()> { Commands::Status => cmd_status(), Commands::Device { action } => cmd_device(action), Commands::Lock => { eprintln!("no cached session to lock"); Ok(()) } + Commands::Completions { shell } => { + let mut cmd = Cli::command(); + generate(shell, &mut cmd, "relicario", &mut std::io::stdout()); + Ok(()) + } } } diff --git a/crates/relicario-cli/tests/smart_inputs.rs b/crates/relicario-cli/tests/smart_inputs.rs new file mode 100644 index 0000000..c5c60bb --- /dev/null +++ b/crates/relicario-cli/tests/smart_inputs.rs @@ -0,0 +1,30 @@ +use assert_cmd::Command; +use predicates::str::contains; + +#[test] +fn completions_bash_emits_script() { + Command::cargo_bin("relicario").unwrap() + .args(["completions", "bash"]) + .assert() + .success() + .stdout(contains("_relicario")) + .stdout(contains("complete -F")); +} + +#[test] +fn completions_zsh_emits_script() { + Command::cargo_bin("relicario").unwrap() + .args(["completions", "zsh"]) + .assert() + .success() + .stdout(contains("#compdef relicario")); +} + +#[test] +fn completions_fish_emits_script() { + Command::cargo_bin("relicario").unwrap() + .args(["completions", "fish"]) + .assert() + .success() + .stdout(contains("complete -c relicario")); +}