Skip to main content

rlx_cli/
registry.rs

1// RLX — versatile ML compiler + runtime.
2// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, version 3.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16use anyhow::{Result, bail};
17use std::sync::{Mutex, OnceLock};
18
19/// One CLI entry per model family. Each per-crate `rlx-<family>` binary
20/// calls its own `run` directly; the optional `rlx-run` multiplexer
21/// registers many `ModelRunner` implementations.
22pub 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
98/// Register a model CLI under the multiplexer.
99pub 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}