use crate::config::Config;
use anyhow::Result;
use clap::CommandFactory;
use std::collections::BTreeMap;
use std::fs;
fn campaigns_by_harness(config: &Config) -> BTreeMap<String, Vec<String>> {
let mut out: BTreeMap<String, Vec<String>> = BTreeMap::new();
for harness_name in config.harnesses.keys() {
let Ok(dir) = config.resolve_dir(harness_name) else {
continue;
};
let campaigns_dir = dir.join("campaigns");
if !campaigns_dir.is_dir() {
continue;
}
let mut slugs: Vec<String> = fs::read_dir(&campaigns_dir)
.ok()
.into_iter()
.flatten()
.flatten()
.filter(|e| e.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
.filter_map(|e| e.file_name().into_string().ok())
.filter(|name| name != "TEMPLATE")
.filter(|name| {
campaigns_dir.join(name).join("campaign.md").is_file()
})
.collect();
slugs.sort();
out.insert(harness_name.clone(), slugs);
}
out
}
fn derived_commands() -> Vec<(String, String)> {
let cmd = crate::Cli::command();
cmd.get_subcommands()
.map(|sub| {
let name = sub.get_name().to_string();
let about = sub
.get_about()
.map(|a| a.to_string())
.unwrap_or_default();
(name, about)
})
.collect()
}
pub fn generate(shell: &str) -> Result<()> {
match shell {
"zsh" => generate_zsh(),
"bash" => generate_bash(),
"fish" => generate_fish(),
_ => anyhow::bail!("Unsupported shell: {shell}. Use: zsh, bash, or fish"),
}
}
fn generate_zsh() -> Result<()> {
let commands = derived_commands();
let config = Config::load().ok();
let all_names = config
.as_ref()
.map(|c| {
c.all_names()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
})
.unwrap_or_default();
let tool_names = config
.as_ref()
.map(|c| c.tool_names())
.unwrap_or_default();
let campaigns = config
.as_ref()
.map(campaigns_by_harness)
.unwrap_or_default();
let harness_list = all_names.join(" ");
let tool_list = tool_names.join(" ");
let command_entries: String = commands
.iter()
.map(|(name, desc)| format!(" '{name}:{desc}'"))
.collect::<Vec<_>>()
.join("\n");
let campaigns_arrays: String = campaigns
.iter()
.map(|(h, slugs)| {
let list = slugs.join(" ");
format!(" campaigns_{h}=({list})")
})
.collect::<Vec<_>>()
.join("\n");
print!(
r##"#compdef muxr
_muxr() {{
local -a commands harnesses tools
commands=(
{command_entries}
)
harnesses=({harness_list})
tools=({tool_list})
{campaigns_arrays}
if (( CURRENT == 2 )); then
_alternative \
'commands:command:compadd -a commands' \
'harnesses:harness:compadd -a harnesses' \
'tools:tool:compadd -a tools'
return
fi
# If first arg is a tool, complete with tool subcommands
if (( ${{+tools[(r)$words[2]]}} )); then
compadd upgrade status compact model
return
fi
# If first arg is a harness, complete with that harness's campaigns
local harness=$words[2]
if (( ${{+harnesses[(r)$harness]}} )); then
local var="campaigns_${{harness}}"
local -a slugs
slugs=(${{(P)var}})
if (( $#slugs )); then
compadd -a slugs
fi
fi
}}
_muxr "$@"
"##
);
Ok(())
}
fn generate_bash() -> Result<()> {
let commands = derived_commands();
let config = Config::load().ok();
let all_names = config
.as_ref()
.map(|c| {
c.all_names()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
})
.unwrap_or_default();
let tool_names = config
.as_ref()
.map(|c| c.tool_names())
.unwrap_or_default();
let campaigns = config
.as_ref()
.map(campaigns_by_harness)
.unwrap_or_default();
let harness_list = all_names.join(" ");
let tool_list = tool_names.join(" ");
let command_list: String = commands
.iter()
.map(|(name, _)| name.as_str())
.collect::<Vec<_>>()
.join(" ");
print!(
r#"_muxr_completions() {{
local cur prev
cur="${{COMP_WORDS[COMP_CWORD]}}"
prev="${{COMP_WORDS[COMP_CWORD-1]}}"
local commands="{command_list}"
local harnesses="{harness_list}"
local tools="{tool_list}"
if [[ $COMP_CWORD -eq 1 ]]; then
COMPREPLY=($(compgen -W "$commands $harnesses $tools" -- "$cur"))
return
fi
# If first arg is a tool, offer tool subcommands
case " $tools " in
*" ${{COMP_WORDS[1]}} "*)
COMPREPLY=($(compgen -W "upgrade status compact model" -- "$cur"))
return
;;
esac
# If first arg is a harness, offer that harness's campaigns
local harness="${{COMP_WORDS[1]}}"
local campaigns=""
case " $harnesses " in
*" $harness "*)
"#
);
for (h, slugs) in &campaigns {
let list = slugs.join(" ");
println!(
" [[ \"$harness\" == \"{h}\" ]] && campaigns=\"{list}\""
);
}
print!(
r#" COMPREPLY=($(compgen -W "$campaigns" -- "$cur"))
;;
esac
}}
complete -F _muxr_completions muxr
"#
);
Ok(())
}
fn generate_fish() -> Result<()> {
let commands = derived_commands();
let config = Config::load().ok();
let verticals = config
.as_ref()
.map(|c| {
c.all_names()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
})
.unwrap_or_default();
let harness_names = config
.as_ref()
.map(|c| c.tool_names())
.unwrap_or_default();
println!("# muxr fish completions");
println!("complete -c muxr -f");
println!();
for (name, desc) in &commands {
println!("complete -c muxr -n '__fish_use_subcommand' -a '{name}' -d '{desc}'");
}
for v in &verticals {
println!("complete -c muxr -n '__fish_use_subcommand' -a '{v}' -d 'Open {v} session'");
}
for h in &harness_names {
println!("complete -c muxr -n '__fish_use_subcommand' -a '{h}' -d '{h} harness'");
println!("complete -c muxr -n '__fish_seen_subcommand_from {h}' -a 'upgrade' -d 'Restart sessions'");
println!("complete -c muxr -n '__fish_seen_subcommand_from {h}' -a 'status' -d 'Show status'");
}
Ok(())
}