feat: Add completions subcommand via clap_complete
`ramekin completions <shell>` prints shell completions for bash, zsh,
fish, elvish, or powershell to stdout.

Assisted-by: Claude Opus 4.6 via pi
change lsuptulmxxpqymzuqxouztrrnkzxlqwt
commit 1010808879315f19acddd169d44eae0c86cd29e9
author Alpha Chen <alpha@kejadlen.dev>
date
parent lmkoquvp
diff --git a/Cargo.lock b/Cargo.lock
index 854f960..0345cd8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -146,6 +146,15 @@ dependencies = [
  "strsim",
 ]
 
+[[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.6.0"
@@ -579,6 +588,7 @@ name = "ramekin"
 version = "0.1.0"
 dependencies = [
  "clap",
+ "clap_complete",
  "fastrand",
  "fs-err",
  "miette",
diff --git a/Cargo.toml b/Cargo.toml
index f74c864..ffeaae1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,12 +7,13 @@ edition = "2024"
 
 [dependencies]
 clap = { version = "*", features = ["derive"] }
+clap_complete = "*"
 miette = { version = "*", features = ["fancy"] }
 fastrand = "*"
 fs-err = "*"
 serde = { version = "*", features = ["derive"] }
 # Pinned to prerelease — alpha.4 fixes serde defaults with bare KDL values.
-serde-kdl2 = "0.1.1-alpha.5"
+serde-kdl2 = "0.1.1-alpha.6"
 serde_yaml = "*"
 shellexpand = "*"
 tracing = "*"
diff --git a/src/main.rs b/src/main.rs
index 195ed4c..62ca8b2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,7 +3,8 @@ mod config;
 use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
 
-use clap::{Parser, Subcommand};
+use clap::{CommandFactory, Parser, Subcommand};
+use clap_complete::Shell;
 use miette::{Context, IntoDiagnostic, Result, bail};
 use serde::Serialize;
 use tracing::{error, info};
@@ -39,6 +40,11 @@ enum Cmd {
     },
     /// Show resolved paths and mount configuration
     Config,
+    /// Generate shell completions
+    Completions {
+        /// Shell to generate completions for
+        shell: Shell,
+    },
 }
 
 fn main() -> Result<()> {
@@ -52,11 +58,19 @@ fn main() -> Result<()> {
 
     let cli = Cli::parse();
     let command = cli.command.unwrap_or(Cmd::Run { rebuild: false });
+
+    // Completions doesn't need workspace resolution.
+    if let Cmd::Completions { shell } = command {
+        clap_complete::generate(shell, &mut Cli::command(), "ramekin", &mut std::io::stdout());
+        return Ok(());
+    }
+
     let ramekin = Ramekin::resolve(cli.workspace)?;
 
     match command {
         Cmd::Run { rebuild } => ramekin.run(rebuild, &cli.pi_args),
         Cmd::Config => ramekin.config(),
+        Cmd::Completions { .. } => unreachable!(),
     }
 }