use clap::CommandFactory;
use clap_complete::Shell;
#[derive(clap::Parser)]
#[command(
name = "csm",
about = "Cross-platform Claude Code smart session manager",
long_about = "csm — the claude-smart session launcher. Wraps `claude` with \
smart session selection, account auto-switching, and \
limit-detection relaunch."
)]
pub struct CsmCompletionsApp {
#[command(subcommand)]
pub command: CompletionsSubcmd,
}
#[derive(clap::Subcommand)]
pub enum CompletionsSubcmd {
#[command(name = "run")]
Run {
#[arg(short = 'i', long)]
interactive: bool,
#[arg(short = 'n', long)]
new: bool,
#[arg(short = 'c', long)]
continue_: bool,
#[arg(short = 'A', long = "pick-account")]
pick_account: bool,
#[arg(long)]
no_pick: bool,
#[arg(short = 'r', long, value_name = "ID_OR_ALIAS")]
resume: Option<String>,
#[arg(long, value_name = "MODE")]
permission_mode: Option<String>,
#[arg(long, value_name = "LEVEL")]
effort: Option<String>,
#[arg(long, value_name = "MODEL")]
model: Option<String>,
#[arg(long, value_name = "UUID")]
session_id: Option<String>,
#[arg(long, value_name = "PROFILE")]
profile: Option<String>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
passthru: Vec<String>,
},
#[command(name = "hook")]
Hook {
#[arg(long, value_name = "DIR")]
owner: Option<String>,
},
#[command(name = "cas")]
Cas {
#[arg(long)]
eval: bool,
#[arg(long, value_name = "SHELL")]
shell: Option<String>,
#[arg(long)]
print_default_dir: bool,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
op_args: Vec<String>,
},
#[command(name = "profiles")]
Profiles {
#[command(subcommand)]
verb: Option<ProfilesVerb>,
},
#[command(name = "usage")]
Usage {
#[arg(long)]
json: bool,
#[arg(long)]
no_fetch: bool,
},
#[command(name = "pick-account")]
PickAccount {
#[arg(value_name = "CURRENT")]
current: Option<String>,
#[arg(long)]
include_current: bool,
},
#[command(name = "scan")]
Scan {
#[arg(value_name = "CWD")]
cwd: Option<String>,
},
#[command(name = "current-usage")]
CurrentUsage {
#[arg(value_name = "PROFILE")]
profile: String,
},
#[command(name = "sidecar")]
Sidecar {
#[arg(value_name = "OP")]
op: String,
#[arg(value_name = "SID")]
sid: String,
#[arg(value_name = "KEY=VALUE")]
kv_args: Vec<String>,
},
#[command(name = "statusline")]
Statusline,
#[command(name = "completions")]
Completions {
shell: Shell,
},
#[command(name = "newuuid")]
Newuuid,
}
#[derive(clap::Subcommand)]
pub enum ProfilesVerb {
#[command(name = "list")]
List,
#[command(name = "add")]
Add {
name: String,
dir: Option<String>,
},
#[command(name = "set")]
Set {
name: String,
dir: String,
},
#[command(name = "rm", alias = "remove")]
Rm {
name: String,
},
#[command(name = "use")]
Use {
name: String,
},
#[command(name = "edit")]
Edit,
#[command(name = "dir")]
Dir {
name: Option<String>,
},
}
pub fn generate(shell: Shell, out: &mut impl std::io::Write) {
let mut cmd = CsmCompletionsApp::command();
clap_complete::generate(shell, &mut cmd, "csm", out);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_zsh_completions_is_non_empty() {
let mut buf = Vec::new();
generate(Shell::Zsh, &mut buf);
assert!(!buf.is_empty(), "zsh completions should not be empty");
}
#[test]
fn generate_bash_completions_is_non_empty() {
let mut buf = Vec::new();
generate(Shell::Bash, &mut buf);
assert!(!buf.is_empty(), "bash completions should not be empty");
}
#[test]
fn generate_powershell_completions_is_non_empty() {
let mut buf = Vec::new();
generate(Shell::PowerShell, &mut buf);
assert!(
!buf.is_empty(),
"powershell completions should not be empty"
);
}
#[test]
fn zsh_completions_mention_run_subcommand() {
let mut buf = Vec::new();
generate(Shell::Zsh, &mut buf);
let out = String::from_utf8_lossy(&buf);
assert!(
out.contains("run") || out.contains("csm"),
"zsh completions should reference the run subcommand or binary name"
);
}
#[test]
fn bash_completions_mention_hook_subcommand() {
let mut buf = Vec::new();
generate(Shell::Bash, &mut buf);
let out = String::from_utf8_lossy(&buf);
assert!(
out.contains("hook"),
"bash completions should mention 'hook' subcommand"
);
}
#[test]
fn zsh_completions_mention_completions_subcommand() {
let mut buf = Vec::new();
generate(Shell::Zsh, &mut buf);
let out = String::from_utf8_lossy(&buf);
assert!(
out.contains("completions"),
"zsh completions should mention 'completions' subcommand"
);
}
#[test]
fn zsh_completions_include_all_subcommands() {
let mut buf = Vec::new();
generate(Shell::Zsh, &mut buf);
let out = String::from_utf8_lossy(&buf);
for sub in &[
"run",
"hook",
"profiles",
"usage",
"cas",
"pick-account",
"scan",
"current-usage",
"sidecar",
"statusline",
"completions",
"newuuid",
] {
assert!(
out.contains(sub),
"zsh completions missing subcommand {sub:?}"
);
}
}
#[test]
fn generate_is_idempotent() {
let mut buf1 = Vec::new();
let mut buf2 = Vec::new();
generate(Shell::Zsh, &mut buf1);
generate(Shell::Zsh, &mut buf2);
assert_eq!(
buf1, buf2,
"repeated generate calls must produce identical output"
);
}
}