pub mod color;
pub mod commands;
pub mod header;
pub mod output;
use clap::{Args, Parser, Subcommand, ValueEnum};
use crate::cli::output::OutputConfig;
#[derive(Parser)]
#[command(
name = "openlatch",
// `version` alone resolves to CARGO_PKG_VERSION, which cannot tell a build
// from `main` apart from the release it post-dates — the exact question
// `openlatch --version` is run to answer. Same source as `status` and the
// wire `clientversion` (see build.rs).
version = env!("OPENLATCH_VERSION"),
about = "OpenLatch runtime enforcement node — capture and enforce inside every AI agent",
after_help = "Run 'openlatch <command> --help' for more information on a command."
)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Commands>,
#[arg(long, global = true, default_value = "human")]
pub format: OutputFormat,
#[arg(long, global = true)]
pub json: bool,
#[arg(long, short = 'v', global = true)]
pub verbose: bool,
#[arg(long, global = true)]
pub debug: bool,
#[arg(long, short = 'q', global = true)]
pub quiet: bool,
#[arg(long, global = true)]
pub no_color: bool,
}
#[derive(Clone, ValueEnum)]
pub enum OutputFormat {
Human,
Json,
}
#[derive(Subcommand)]
pub enum Commands {
#[command(visible_alias = "setup")]
Init(InitArgs),
Status,
Start(StartArgs),
Stop,
Restart,
Logs(LogsArgs),
Doctor(DoctorArgs),
Uninstall(UninstallArgs),
Docs,
Hooks {
#[command(subcommand)]
cmd: HooksCommands,
},
#[command(hide = true)]
Daemon {
#[command(subcommand)]
cmd: DaemonCommands,
},
Auth {
#[command(subcommand)]
cmd: AuthCommands,
},
Telemetry {
#[command(subcommand)]
cmd: TelemetryCommands,
},
Supervision {
#[command(subcommand)]
cmd: SupervisionCommands,
},
Update(commands::update::UpdateArgs),
Inventory {
#[command(subcommand)]
cmd: InventoryCommands,
},
#[command(name = "__spike-update", hide = true)]
SpikeUpdate(commands::spike_update::SpikeUpdateArgs),
Boundary {
#[command(subcommand)]
cmd: BoundaryCommands,
},
#[command(hide = true)]
Bench {
#[command(subcommand)]
cmd: BenchCommands,
},
}
#[derive(Subcommand, Clone)]
pub enum BoundaryCommands {
Status,
Explain {
finding_id: String,
},
}
#[derive(Subcommand, Clone)]
pub enum BenchCommands {
PanicIsolation(BenchPanicArgs),
PortStability,
ExportFixtures,
TransformEgress,
Replay(BenchReplayArgs),
CacheBaseline(BenchCacheBaselineArgs),
Compare(BenchCompareArgs),
}
#[derive(Args, Clone)]
pub struct BenchPanicArgs {
#[arg(long, default_value = "observe")]
pub inject: String,
}
#[derive(Args, Clone)]
pub struct BenchReplayArgs {
#[arg(long, default_value = "OL-ECO-001")]
pub rule: String,
#[arg(long, default_value = "100")]
pub runs: u64,
}
#[derive(Args, Clone)]
pub struct BenchCacheBaselineArgs {
#[arg(long, default_value = "100")]
pub turns: usize,
#[arg(long)]
pub with_mcp: bool,
#[arg(long)]
pub direct: bool,
#[arg(long, value_name = "FILE")]
pub out: Option<std::path::PathBuf>,
#[arg(value_name = "OUT")]
pub out_positional: Option<std::path::PathBuf>,
}
#[derive(Args, Clone)]
pub struct BenchCompareArgs {
#[arg(value_name = "A")]
pub a: std::path::PathBuf,
#[arg(value_name = "B")]
pub b: std::path::PathBuf,
}
#[derive(Subcommand, Clone)]
pub enum InventoryCommands {
List(commands::inventory::ListArgs),
Log(commands::inventory::LogArgs),
Rescan(commands::inventory::RescanArgs),
Status(commands::inventory::StatusArgs),
Inspect(commands::inventory::InspectArgs),
Projects(commands::inventory::ProjectsArgs),
Ack(commands::inventory::AckArgs),
}
#[derive(Subcommand)]
pub enum TelemetryCommands {
Status,
Enable,
Disable,
Purge,
Debug,
}
#[derive(Subcommand)]
pub enum SupervisionCommands {
Install,
Uninstall,
Status,
Enable,
Disable,
}
#[derive(Subcommand)]
pub enum HooksCommands {
Install(InitArgs),
Uninstall(UninstallArgs),
Status,
}
#[derive(Subcommand)]
pub enum DaemonCommands {
Start(StartArgs),
Stop,
Restart,
}
#[derive(Subcommand)]
pub enum AuthCommands {
Login(AuthLoginArgs),
Logout,
Status,
}
#[derive(Args, Clone)]
pub struct AuthLoginArgs {
#[arg(long)]
pub no_browser: bool,
}
#[derive(Args, Clone)]
pub struct InitArgs {
#[arg(long)]
pub foreground: bool,
#[arg(long)]
pub reconfig: bool,
#[arg(long)]
pub no_start: bool,
#[arg(long, alias = "yes-telemetry", conflicts_with = "no_telemetry")]
pub telemetry: bool,
#[arg(long)]
pub no_telemetry: bool,
#[arg(long)]
pub no_persistence: bool,
#[arg(long)]
pub no_boundary: bool,
#[arg(long, value_name = "URL")]
pub api_url: Option<String>,
}
#[derive(Args, Clone)]
pub struct StartArgs {
#[arg(long)]
pub foreground: bool,
#[arg(long)]
pub port: Option<u16>,
#[arg(long, value_name = "PORT")]
pub boundary_port: Option<u16>,
}
#[derive(Args, Clone, Default)]
pub struct DoctorArgs {
#[arg(long, hide = true)]
pub trigger_panic: bool,
#[arg(long, conflicts_with = "restore")]
pub fix: bool,
#[arg(long, conflicts_with = "fix")]
pub restore: bool,
#[arg(long)]
pub rescue: bool,
#[arg(long, value_name = "DURATION", requires = "rescue")]
pub since: Option<String>,
#[arg(long, short = 'y', requires = "rescue")]
pub yes: bool,
#[arg(long, value_name = "PATH", requires = "rescue")]
pub output: Option<std::path::PathBuf>,
}
#[derive(Args, Clone)]
pub struct LogsArgs {
#[arg(long, short = 'f')]
pub follow: bool,
#[arg(long)]
pub since: Option<String>,
#[arg(long, short = 'n', default_value = "20")]
pub lines: usize,
#[arg(long)]
pub tamper: bool,
}
#[derive(Args, Clone)]
pub struct UninstallArgs {
#[arg(long)]
pub purge: bool,
#[arg(long, short = 'y')]
pub yes: bool,
}
pub fn build_output_config(cli: &Cli) -> OutputConfig {
let format = if cli.json {
output::OutputFormat::Json
} else {
match cli.format {
OutputFormat::Json => output::OutputFormat::Json,
OutputFormat::Human => output::OutputFormat::Human,
}
};
let color_enabled = color::is_color_enabled(cli.no_color);
OutputConfig {
format,
verbose: cli.verbose || cli.debug,
debug: cli.debug,
quiet: cli.quiet,
color: color_enabled,
}
}