use anyhow::Result;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
mod commands;
#[derive(Parser)]
#[command(name = "perspt")]
#[command(author = "Vikrant Rathore, Ronak Rathore")]
#[command(version = env!("CARGO_PKG_VERSION"))]
#[command(about = "AI-powered coding assistant with SRBN stability guarantees", long_about = None)]
struct Cli {
#[arg(short, long, global = true)]
verbose: bool,
#[arg(short, long, global = true)]
config: Option<PathBuf>,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
#[allow(clippy::large_enum_variant)]
enum Commands {
Chat {
#[arg(short, long)]
model: Option<String>,
},
Agent {
task: String,
#[arg(short, long)]
workdir: Option<PathBuf>,
#[arg(short, long)]
yes: bool,
#[arg(long)]
auto_approve_safe: bool,
#[arg(short = 'k', long, default_value = "5")]
complexity: usize,
#[arg(short, long, default_value = "balanced")]
mode: String,
#[arg(long)]
model: Option<String>,
#[arg(long)]
architect_model: Option<String>,
#[arg(long)]
actuator_model: Option<String>,
#[arg(long)]
verifier_model: Option<String>,
#[arg(long)]
speculator_model: Option<String>,
#[arg(long, default_value = "1.0,0.5,2.0")]
energy_weights: String,
#[arg(long, default_value = "0.1")]
stability_threshold: f32,
#[arg(long, default_value = "0")]
max_cost: f32,
#[arg(long, default_value = "0")]
max_steps: usize,
#[arg(long)]
defer_tests: bool,
#[arg(long)]
log_llm: bool,
#[arg(long)]
single_file: bool,
#[arg(long, default_value = "default")]
verifier_strictness: String,
#[arg(long)]
architect_fallback_model: Option<String>,
#[arg(long)]
actuator_fallback_model: Option<String>,
#[arg(long)]
verifier_fallback_model: Option<String>,
#[arg(long)]
speculator_fallback_model: Option<String>,
#[arg(long)]
output_plan: Option<PathBuf>,
},
Init {
#[arg(long)]
memory: bool,
#[arg(long)]
rules: bool,
},
Config {
#[arg(long)]
show: bool,
#[arg(long)]
set: Option<String>,
#[arg(long)]
edit: bool,
},
Ledger {
#[arg(long)]
recent: bool,
#[arg(long)]
rollback: Option<String>,
#[arg(long)]
stats: bool,
},
Status,
Abort {
#[arg(short, long)]
force: bool,
},
Resume {
session_id: Option<String>,
},
Logs {
session_id: Option<String>,
#[arg(long)]
last: bool,
#[arg(long)]
stats: bool,
#[arg(long)]
tui: bool,
},
SimpleChat {
#[arg(short, long)]
model: Option<String>,
#[arg(long)]
log_file: Option<std::path::PathBuf>,
},
Dashboard {
#[arg(short, long, default_value = "3000")]
port: u16,
#[arg(long)]
db_path: Option<std::path::PathBuf>,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let log_level = if cli.verbose {
"debug"
} else if matches!(
cli.command,
None | Some(Commands::Chat { .. }) | Some(Commands::Agent { .. })
) {
"off"
} else if matches!(cli.command, Some(Commands::SimpleChat { .. })) {
"error"
} else {
"info"
};
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(log_level)).init();
match cli.command {
None | Some(Commands::Chat { model: _ }) => commands::chat::run().await,
Some(Commands::Agent {
task,
workdir,
yes,
auto_approve_safe: _,
complexity,
mode,
model,
architect_model,
actuator_model,
verifier_model,
speculator_model,
energy_weights,
stability_threshold,
max_cost,
max_steps,
defer_tests,
log_llm,
single_file,
verifier_strictness,
architect_fallback_model,
actuator_fallback_model,
verifier_fallback_model,
speculator_fallback_model,
output_plan,
}) => {
commands::agent::run(
task,
workdir,
yes,
complexity,
mode,
model,
architect_model,
actuator_model,
verifier_model,
speculator_model,
defer_tests,
log_llm,
single_file,
verifier_strictness,
architect_fallback_model,
actuator_fallback_model,
verifier_fallback_model,
speculator_fallback_model,
output_plan,
energy_weights,
stability_threshold,
max_cost,
max_steps,
)
.await
}
Some(Commands::Init { memory, rules }) => commands::init::run(memory, rules).await,
Some(Commands::Config { show, set, edit }) => commands::config::run(show, set, edit).await,
Some(Commands::Ledger {
recent,
rollback,
stats,
}) => commands::ledger::run(recent, rollback, stats).await,
Some(Commands::Status) => commands::status::run().await,
Some(Commands::Abort { force }) => commands::abort::run(force).await,
Some(Commands::Resume { session_id }) => commands::resume::run(session_id).await,
Some(Commands::Logs {
session_id,
last,
stats,
tui,
}) => commands::logs::run(session_id, last, stats, tui).await,
Some(Commands::SimpleChat { model, log_file }) => {
commands::simple_chat::run(commands::simple_chat::SimpleChatArgs { model, log_file })
.await
}
Some(Commands::Dashboard { port, db_path }) => {
commands::dashboard::run(port, db_path).await
}
}
}