1use anyhow::{Result, bail};
17use std::sync::{Mutex, OnceLock};
18
19pub trait ModelRunner: Send + Sync + 'static {
23 fn name(&self) -> &'static str;
24 fn description(&self) -> &'static str;
25 fn run(&self, args: &[String]) -> Result<()>;
26}
27
28type RegistryInner = Vec<Box<dyn ModelRunner>>;
29
30fn registry() -> &'static Mutex<RegistryInner> {
31 static R: OnceLock<Mutex<RegistryInner>> = OnceLock::new();
32 R.get_or_init(|| Mutex::new(Vec::new()))
33}
34
35pub fn register_runner(runner: Box<dyn ModelRunner>) {
36 let mut g = registry().lock().expect("runner registry poisoned");
37 let name = runner.name();
38 if let Some(idx) = g.iter().position(|r| r.name() == name) {
39 g[idx] = runner;
40 } else {
41 g.push(runner);
42 }
43}
44
45pub fn registered_runners() -> Vec<(&'static str, &'static str)> {
46 let g = registry().lock().expect("runner registry poisoned");
47 g.iter().map(|r| (r.name(), r.description())).collect()
48}
49
50pub fn run_registered(name: &str, args: &[String]) -> Result<Option<()>> {
51 let g = registry().lock().expect("runner registry poisoned");
52 for runner in g.iter() {
53 if runner.name() == name {
54 return runner.run(args).map(Some);
55 }
56 }
57 Ok(None)
58}
59
60pub fn dispatch(args: &[String]) -> Result<()> {
61 let Some(sub) = args.first() else {
62 eprintln!("{}", dispatch_help());
63 return Ok(());
64 };
65 match sub.as_str() {
66 "help" | "--help" | "-h" => {
67 println!("{}", dispatch_help());
68 return Ok(());
69 }
70 _ => {}
71 }
72 match run_registered(sub, &args[1..])? {
73 Some(()) => Ok(()),
74 None => {
75 eprintln!("{}", dispatch_help());
76 bail!("unknown subcommand: {sub}");
77 }
78 }
79}
80
81pub fn dispatch_help() -> String {
82 let mut s = String::from(
83 "rlx-run — multi-model launcher (or use per-model binaries: rlx-qwen3, rlx-flux2, …)\n\
84 USAGE:\n rlx-run <subcommand> [flags]\n\nSUBCOMMANDS:\n",
85 );
86 let mut any = false;
87 for (name, desc) in registered_runners() {
88 s.push_str(&format!(" {name:<14} {desc}\n"));
89 any = true;
90 }
91 if !any {
92 s.push_str(" (no runners registered)\n");
93 }
94 s.push_str(" help print this help\n");
95 s
96}
97
98pub fn register_cli(
100 name: &'static str,
101 description: &'static str,
102 run: fn(&[String]) -> Result<()>,
103) {
104 struct R {
105 name: &'static str,
106 description: &'static str,
107 run: fn(&[String]) -> Result<()>,
108 }
109 impl ModelRunner for R {
110 fn name(&self) -> &'static str {
111 self.name
112 }
113 fn description(&self) -> &'static str {
114 self.description
115 }
116 fn run(&self, args: &[String]) -> Result<()> {
117 (self.run)(args)
118 }
119 }
120 register_runner(Box::new(R {
121 name,
122 description,
123 run,
124 }));
125}