percli 0.1.0

Offline CLI simulator for the Percolator risk engine
mod commands;
mod format;
mod scenario;

use clap::{Parser, Subcommand};
use format::OutputFormat;

#[derive(Parser)]
#[command(
    name = "percli",
    about = "Offline simulator for the Percolator risk engine",
    long_about = "Simulate haircuts, liquidations, margin checks, and conservation proofs\nfor the Percolator risk engine — no chain required.",
    version
)]
struct Cli {
    #[command(subcommand)]
    command: Commands,

    /// Output format (table or json). Also settable via PERC_FORMAT env var.
    #[arg(long, short, global = true)]
    format: Option<OutputFormat>,
}

#[derive(Subcommand)]
enum Commands {
    /// Create a new scenario template
    Init {
        /// Template to use
        #[arg(long, default_value = "basic")]
        template: String,
    },
    /// Run a scenario file
    Sim {
        /// Path to scenario TOML file
        path: String,
        /// Print state after each step
        #[arg(long)]
        step_by_step: bool,
        /// Disable conservation checks after each step
        #[arg(long)]
        no_check_conservation: bool,
        /// Verbose output
        #[arg(long, short)]
        verbose: bool,
    },
    /// Execute a single operation on a saved engine state
    Step {
        /// Path to engine state JSON file
        #[arg(long)]
        state: String,
        #[command(subcommand)]
        action: StepAction,
    },
    /// Read-only queries on engine state
    Query {
        /// Path to engine state JSON file
        #[arg(long)]
        state: String,
        /// Metric to query
        metric: String,
        /// Account name (for account-specific queries)
        #[arg(long)]
        account: Option<String>,
    },
    /// Validate a scenario file without running it
    Inspect {
        /// Path to scenario TOML file
        path: String,
    },
}

#[derive(Subcommand)]
enum StepAction {
    /// Deposit to an account
    Deposit {
        account: String,
        amount: u128,
    },
    /// Withdraw from an account
    Withdraw {
        account: String,
        amount: u128,
    },
    /// Execute a trade
    Trade {
        long: String,
        short: String,
        #[arg(long)]
        size: i128,
        #[arg(long)]
        price: u64,
    },
    /// Run a keeper crank
    Crank {
        #[arg(long)]
        oracle: u64,
        #[arg(long)]
        slot: u64,
    },
    /// Liquidate an account
    Liquidate {
        account: String,
    },
    /// Set oracle price
    SetOracle {
        price: u64,
    },
    /// Set current slot
    SetSlot {
        slot: u64,
    },
}

fn main() -> anyhow::Result<()> {
    let cli = Cli::parse();
    let fmt = cli.format.unwrap_or_else(OutputFormat::from_env);

    match cli.command {
        Commands::Init { template } => commands::init::run(&template),
        Commands::Sim {
            path,
            step_by_step,
            no_check_conservation,
            verbose,
        } => commands::sim::run(&path, fmt, step_by_step, !no_check_conservation, verbose),
        Commands::Step { state, action } => commands::step::run(&state, action, fmt),
        Commands::Query { state, metric, account } => {
            commands::query::run(&state, &metric, account.as_deref(), fmt)
        }
        Commands::Inspect { path } => commands::inspect::run(&path),
    }
}