Skip to main content

rtb_cli/
builtins.rs

1//! Built-in commands shipped with `rtb-cli`.
2//!
3//! Every built-in is an `impl Command` registered via
4//! [`rtb_app::command::BUILTIN_COMMANDS`]. `Application::run`
5//! filters them by the runtime [`Features`](rtb_app::features::Features)
6//! set before handing them to `clap`.
7
8use async_trait::async_trait;
9use linkme::distributed_slice;
10use rtb_app::app::App;
11use rtb_app::command::{Command, CommandSpec, BUILTIN_COMMANDS};
12use rtb_app::features::Feature;
13
14use crate::health;
15use crate::init::INITIALISERS;
16
17// =====================================================================
18// version — prints version + commit + date.
19// =====================================================================
20
21/// The `version` subcommand.
22pub struct VersionCmd;
23
24#[async_trait]
25impl Command for VersionCmd {
26    fn spec(&self) -> &CommandSpec {
27        static SPEC: CommandSpec = CommandSpec {
28            name: "version",
29            about: "Print tool version information",
30            aliases: &[],
31            feature: Some(Feature::Version),
32        };
33        &SPEC
34    }
35
36    async fn run(&self, app: App) -> miette::Result<()> {
37        let v = &app.version;
38        println!("{} {}", app.metadata.name, v.version);
39        if let Some(commit) = v.commit.as_deref() {
40            println!("  commit: {commit}");
41        }
42        if let Some(date) = v.date.as_deref() {
43            println!("  built:  {date}");
44        }
45        println!("  target: {}-{}", std::env::consts::ARCH, std::env::consts::OS);
46        Ok(())
47    }
48}
49
50#[distributed_slice(BUILTIN_COMMANDS)]
51fn __register_version() -> Box<dyn Command> {
52    Box::new(VersionCmd)
53}
54
55// =====================================================================
56// doctor — runs HEALTH_CHECKS and reports.
57// =====================================================================
58
59/// The `doctor` subcommand.
60pub struct DoctorCmd;
61
62#[async_trait]
63impl Command for DoctorCmd {
64    fn spec(&self) -> &CommandSpec {
65        static SPEC: CommandSpec = CommandSpec {
66            name: "doctor",
67            about: "Run diagnostic health checks",
68            aliases: &[],
69            feature: Some(Feature::Doctor),
70        };
71        &SPEC
72    }
73
74    async fn run(&self, app: App) -> miette::Result<()> {
75        let report = health::run_all(&app).await;
76        print!("{}", report.render());
77        if report.is_ok() {
78            Ok(())
79        } else {
80            Err(miette::miette!(
81                code = "rtb::doctor::failed",
82                help = "see the report above for which checks failed",
83                "one or more health checks failed"
84            ))
85        }
86    }
87}
88
89#[distributed_slice(BUILTIN_COMMANDS)]
90fn __register_doctor() -> Box<dyn Command> {
91    Box::new(DoctorCmd)
92}
93
94// =====================================================================
95// init — iterates INITIALISERS.
96// =====================================================================
97
98/// The `init` subcommand.
99pub struct InitCmd;
100
101#[async_trait]
102impl Command for InitCmd {
103    fn spec(&self) -> &CommandSpec {
104        static SPEC: CommandSpec = CommandSpec {
105            name: "init",
106            about: "Run first-time bootstrap and setup",
107            aliases: &[],
108            feature: Some(Feature::Init),
109        };
110        &SPEC
111    }
112
113    async fn run(&self, app: App) -> miette::Result<()> {
114        if INITIALISERS.is_empty() {
115            println!("no initialisers registered — nothing to do");
116            return Ok(());
117        }
118        for factory in INITIALISERS {
119            let init = factory();
120            if init.is_configured(&app).await {
121                println!("  [SKIP] {} — already configured", init.name());
122                continue;
123            }
124            println!("  [RUN]  {}", init.name());
125            init.configure(&app).await?;
126        }
127        Ok(())
128    }
129}
130
131#[distributed_slice(BUILTIN_COMMANDS)]
132fn __register_init() -> Box<dyn Command> {
133    Box::new(InitCmd)
134}
135
136// The `config` subtree (show / get / set / schema / validate) lives
137// in `crate::config_cmd`. The `update`, `docs`, and `mcp` stubs have
138// been removed —
139// `rtb-update`, `rtb-docs`, and `rtb-mcp` register the real
140// commands. Downstream tools that disable the corresponding rtb
141// features still get the stub-equivalent behaviour: no command
142// registered, clap reports "unknown subcommand" if invoked.