use anyhow::{Result, bail};
use std::sync::{Mutex, OnceLock};
pub trait ModelRunner: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
fn run(&self, args: &[String]) -> Result<()>;
}
type RegistryInner = Vec<Box<dyn ModelRunner>>;
fn registry() -> &'static Mutex<RegistryInner> {
static R: OnceLock<Mutex<RegistryInner>> = OnceLock::new();
R.get_or_init(|| Mutex::new(Vec::new()))
}
pub fn register_runner(runner: Box<dyn ModelRunner>) {
let mut g = registry().lock().expect("runner registry poisoned");
let name = runner.name();
if let Some(idx) = g.iter().position(|r| r.name() == name) {
g[idx] = runner;
} else {
g.push(runner);
}
}
pub fn registered_runners() -> Vec<(&'static str, &'static str)> {
let g = registry().lock().expect("runner registry poisoned");
g.iter().map(|r| (r.name(), r.description())).collect()
}
pub fn run_registered(name: &str, args: &[String]) -> Result<Option<()>> {
let g = registry().lock().expect("runner registry poisoned");
for runner in g.iter() {
if runner.name() == name {
return runner.run(args).map(Some);
}
}
Ok(None)
}
pub fn dispatch(args: &[String]) -> Result<()> {
let Some(sub) = args.first() else {
eprintln!("{}", dispatch_help());
return Ok(());
};
match sub.as_str() {
"help" | "--help" | "-h" => {
println!("{}", dispatch_help());
return Ok(());
}
_ => {}
}
match run_registered(sub, &args[1..])? {
Some(()) => Ok(()),
None => {
eprintln!("{}", dispatch_help());
bail!("unknown subcommand: {sub}");
}
}
}
pub fn dispatch_help() -> String {
let mut s = String::from(
"rlx-run — multi-model launcher (or use per-model binaries: rlx-qwen3, rlx-flux2, …)\n\
USAGE:\n rlx-run <subcommand> [flags]\n\nSUBCOMMANDS:\n",
);
let mut any = false;
for (name, desc) in registered_runners() {
s.push_str(&format!(" {name:<14} {desc}\n"));
any = true;
}
if !any {
s.push_str(" (no runners registered)\n");
}
s.push_str(" help print this help\n");
s
}
pub fn register_cli(
name: &'static str,
description: &'static str,
run: fn(&[String]) -> Result<()>,
) {
struct R {
name: &'static str,
description: &'static str,
run: fn(&[String]) -> Result<()>,
}
impl ModelRunner for R {
fn name(&self) -> &'static str {
self.name
}
fn description(&self) -> &'static str {
self.description
}
fn run(&self, args: &[String]) -> Result<()> {
(self.run)(args)
}
}
register_runner(Box::new(R {
name,
description,
run,
}));
}