use crate::config::{ENV_CONFIG_DIR, resolve_config_dir_with};
use anyhow::{Result, anyhow};
use std::process::Command;
pub fn run(name_and_args: &[String], cli_config_dir: Option<&std::path::Path>) -> Result<()> {
let Some(name) = name_and_args.first() else {
return Ok(());
};
let rest: Vec<&String> = name_and_args[1..].iter().collect();
let Some(executable) = llman_core::ext_subcommand::resolve(name) else {
return Err(anyhow!("{}", not_found_message(name)));
};
let mut cmd = Command::new(&executable);
cmd.args(rest);
if let Some(dir) = cli_config_dir {
let resolved = resolve_config_dir_with(Some(dir), None)?;
cmd.env(ENV_CONFIG_DIR, resolved);
}
let mut child = cmd.spawn().map_err(|e| {
anyhow!(
"{}: {}",
t!(
"ext_subcommand.spawn_failed",
path = executable.display().to_string()
),
e
)
})?;
let status = child.wait()?;
let code = exit_code_of(&status);
std::process::exit(code);
}
fn exit_code_of(status: &std::process::ExitStatus) -> i32 {
status.code().unwrap_or_else(|| {
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
status.signal().map(|s| 128 + s).unwrap_or(1)
}
#[cfg(not(unix))]
{
1
}
})
}
fn not_found_message(name: &str) -> String {
let mut message = t!("ext_subcommand.not_found", name = name).to_string();
if name == "sdd" {
message.push('\n');
message.push_str(&t!("ext_subcommand.sdd_migration_hint"));
}
let discovered = llman_core::ext_subcommand::discover();
if !discovered.is_empty() {
let list = discovered
.iter()
.map(|n| format!("llman-{n}"))
.collect::<Vec<_>>()
.join(", ");
message.push('\n');
message.push_str(&t!("ext_subcommand.hint", list = list));
}
message
}