Skip to main content

domi_server/tools/
cli.rs

1// `domi` top-level CLI surface.
2//!
3//! Phase 2d Task 2 freezes the clap derive shape so that `domi --help`,
4//! `domi tail --help`, `domi replay --help`, and `domi push --help` render
5//! the flags the brief mandates. Per-handler `run` functions are still
6//! `unimplemented!()` stubs — Tasks 3 (push), 4 (replay), 5 (tail) fill
7//! them in. Stub `run` signatures take `&url::Url` so the URL parsing in
8//! `cli::run` is the single entry point that activates `parse_server`
9//! (Task 1 review Minor M1).
10//!
11//! The per-subcommand arg structs (`TailArgs`, `ReplayArgs`, `PushArgs`)
12//! live in their own files (`tail.rs`, `replay.rs`, `push.rs`) because
13//! Tasks 3-5 own the implementations and want a single source of truth
14//! per subcommand. This file only owns the top-level `Cli` and `Command`
15//! derive types plus the `run()` dispatch.
16
17use clap::{Parser, Subcommand};
18use url::Url;
19
20use crate::tools::push::PushArgs;
21use crate::tools::replay::ReplayArgs;
22use crate::tools::tail::TailArgs;
23use crate::tools::types;
24
25/// `domi` — agent CLI for the DOMicile live feedback server.
26#[derive(Debug, Parser)]
27#[command(
28    name = "domi",
29    version,
30    about = "DOMicile agent CLI — tail, replay, and push audit events",
31    long_about = None,
32)]
33pub struct Cli {
34    /// Base URL of the running `domi-server` instance.
35    ///
36    /// Defaults to `http://127.0.0.1:4173` (the 2c-γ server's default bind).
37    /// This flag is `global` so it appears in every subcommand's `--help`.
38    #[arg(long, global = true, default_value = types::DEFAULT_SERVER)]
39    pub server: String,
40
41    #[command(subcommand)]
42    pub command: Command,
43}
44
45#[derive(Debug, Subcommand)]
46pub enum Command {
47    /// Subscribe to the WebSocket event stream and print new events.
48    Tail(TailArgs),
49    /// Fetch historical events from `GET /api/events` and print them.
50    Replay(ReplayArgs),
51    /// `POST` a single event to `POST /api/events`.
52    Push(PushArgs),
53}
54
55/// Parse `Cli` from `std::env::args()` and dispatch to the requested subcommand.
56///
57/// **Exit codes:**
58/// - `0` — success.
59/// - `1` — connection / I/O / network failure (no server, DNS, TLS, etc.).
60/// - `2` — protocol / parse failure (invalid `--server`, server returned
61///   an error status, malformed `--json`, or any `clap` argument error).
62///
63/// Clap argument parsing errors are routed to clap's default error
64/// printer (which itself exits `2`), so user-facing misuse is consistent
65/// with the broader CLI convention.
66pub async fn run() -> i32 {
67    let cli = Cli::parse();
68
69    // Parse --server through the shared `parse_server` helper (Task 1
70    // review Minor M1 — activate the dormant boundary). Empty input
71    // falls back to `DEFAULT_SERVER` inside `parse_server`.
72    let server: Url = match types::parse_server(&cli.server) {
73        Ok(u) => u,
74        Err(e) => {
75            eprintln!("invalid --server: {e}");
76            return 2;
77        }
78    };
79
80    match cli.command {
81        Command::Tail(args) => crate::tools::tail::run(args, &server).await,
82        Command::Replay(args) => crate::tools::replay::run(args, &server).await,
83        Command::Push(args) => crate::tools::push::run(args, &server).await,
84    }
85}