OXYGENHUB
# 36537112fcac spectra / src / cli / mod.rs
3.3 KiB · Rust · last changed The ledger: hash-chained events in the frozen envelope, signed checkpoints, admin verify, check and rebuild (#3) 2 days ago source blame history raw
1
//! The `spectra` command line (spec §19). Output follows `oxy`: colour and
2
//! glyphs on a UTF-8 terminal, plain when piped, with `--plain` or with
3
//! `NO_COLOR`; `--json` prints one document on stdout and nothing else.
4
5
pub mod admin;
6
pub mod format;
7
pub mod json;
8
pub mod ui;
9
10
use anyhow::{bail, Result};
11
use clap::{Parser, Subcommand};
12
use std::path::PathBuf;
13
14
#[derive(Parser)]
15
#[command(name = "spectra", version = env!("SPECTRA_VERSION_LONG"), about = "A control plane for AI coding agents")]
16
pub struct Cli {
17
    /// Plain output: no colour or glyphs (also NO_COLOR, or when not a terminal)
18
    #[arg(long, global = true)]
19
    pub plain: bool,
20
    /// The data directory (default: $XDG_DATA_HOME/spectra, else ~/.local/share/spectra)
21
    #[arg(long, global = true, env = "SPECTRA_DATA")]
22
    pub data: Option<PathBuf>,
23
    #[command(subcommand)]
24
    pub command: Command,
25
}
26
27
#[derive(Subcommand)]
28
pub enum Command {
29
    /// The version and build of this binary
30
    Version {
31
        /// Machine-readable output
32
        #[arg(long)]
33
        json: bool,
34
    },
35
    /// The ledger's own checks: verify, check, rebuild
36
    Admin {
37
        #[command(subcommand)]
38
        command: Admin,
39
    },
40
}
41
42
#[derive(Subcommand)]
43
pub enum Admin {
44
    /// Recompute the hash chain and check every checkpoint and proof (exit 1 on any finding)
45
    Verify {
46
        /// Machine-readable output
47
        #[arg(long)]
48
        json: bool,
49
    },
50
    /// Whether the projections equal a rebuild from the ledger
51
    Check,
52
    /// Replay the ledger into every projection
53
    Rebuild,
54
}
55
56
/// What every command needs: where the data lives.
57
pub struct Ctx {
58
    pub data: PathBuf,
59
}
60
61
impl Ctx {
62
    /// The ledger's database, which must exist.
63
    pub fn db(&self) -> Result<PathBuf> {
64
        let db = self.data.join("spectra.db");
65
        if !db.exists() {
66
            bail!("no Spectra in {} (run `spectra init`, or pass --data)", self.data.display());
67
        }
68
        Ok(db)
69
    }
70
}
71
72
fn default_data() -> PathBuf {
73
    if let Ok(x) = std::env::var("XDG_DATA_HOME") {
74
        return PathBuf::from(x).join("spectra");
75
    }
76
    let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
77
    PathBuf::from(home).join(".local").join("share").join("spectra")
78
}
79
80
fn runtime() -> Result<tokio::runtime::Runtime> {
81
    Ok(tokio::runtime::Builder::new_multi_thread().enable_all().build()?)
82
}
83
84
pub fn run() -> Result<i32> {
85
    let cli = Cli::parse();
86
    ui::init(cli.plain);
87
    let ctx = Ctx { data: cli.data.unwrap_or_else(default_data) };
88
    match cli.command {
89
        Command::Version { json } => version(json),
90
        Command::Admin { command } => runtime()?.block_on(async {
91
            match command {
92
                Admin::Verify { json } => admin::verify(&ctx, json).await,
93
                Admin::Check => admin::check(&ctx).await,
94
                Admin::Rebuild => admin::rebuild(&ctx).await,
95
            }
96
        }),
97
    }
98
}
99
100
fn version(as_json: bool) -> Result<i32> {
101
    if as_json {
102
        println!(
103
            "{}",
104
            serde_json::json!({
105
                "version": env!("CARGO_PKG_VERSION"),
106
                "build": env!("SPECTRA_BUILD_ID"),
107
                "mcp_protocol_versions": spectra_mcp::PROTOCOL_VERSIONS,
108
                "ledger_format": spectra_core::LEDGER_FORMAT,
109
            })
110
        );
111
    } else {
112
        println!("spectra {}", env!("SPECTRA_VERSION_LONG"));
113
    }
114
    Ok(0)
115
}