use firstpass_proxy::calibrate::{calibrate_from_store, calibrate_from_store_ltt};
use firstpass_proxy::ope::{CandidatePolicy, ips_from_store, ope_from_store};
use firstpass_proxy::{ProxyConfig, cli, run, store};
const HELP: &str = "\
firstpass — the cheapest model that provably passes, with a receipt for every call.
USAGE:
firstpass onboard [--apply] agentic setup: detect env, start proxy, route your agent, verify
firstpass offboard undo it: strip the rc line, stop the proxy, print the unset
firstpass up start the proxy (serves until Ctrl-C)
firstpass doctor validate config, provider key, and gate binaries
firstpass trace [--limit N] print recent audit traces as JSON lines (default 20)
firstpass savings [--json] spend vs the always-top counterfactual, from your own receipts
firstpass evals [--json] per-gate verdict rates + escalation + serve-by-rung, from receipts
firstpass calibrate [--alpha A] [--delta D] [--min-n N] [--method conformal|ltt]
recalibrate the serving threshold from deferred feedback
(default method: conformal; ltt = Learn-then-Test / RCPS)
firstpass ope --config <candidate.toml> [--db <path>] [--tenant <id>]
evaluate a candidate policy against logged traffic before enforcing
firstpass ope --start-rung N [--db <path>] [--tenant <id>]
IPS/SNIPS/DR estimate for a fixed start-rung (requires propensity-logged traffic)
firstpass mcp serve MCP over stdio (agent reads traces + submits feedback)
firstpass --help | --version
ENVIRONMENT (shared by every subcommand):
FIRSTPASS_MODE=observe|enforce FIRSTPASS_BIND=127.0.0.1:8080
FIRSTPASS_CONFIG=./firstpass.toml (or FIRSTPASS_CONFIG_TOML=<inline>)
FIRSTPASS_DB=./firstpass.db RUST_LOG=info
Point your agent at it: export ANTHROPIC_BASE_URL=http://127.0.0.1:8080
Offboard any time: unset ANTHROPIC_BASE_URL";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
match args.get(1).map(String::as_str).unwrap_or("--help") {
"up" => {
run::init_tracing();
run::serve(ProxyConfig::from_env()?).await
}
"onboard" => cmd_onboard(args.iter().any(|a| a == "--apply")),
"offboard" => cmd_offboard(),
"doctor" => cmd_doctor(),
"trace" => cmd_trace(&args),
"savings" => cmd_savings(&args),
"evals" => cmd_evals(&args),
"calibrate" => cmd_calibrate(&args),
"ope" => cmd_ope(&args),
"mcp" => {
let config = ProxyConfig::from_env()?;
let tenant = tenant_arg(&args, &config);
let db = config.db_path;
tokio::task::spawn_blocking(move || firstpass_proxy::mcp::serve_stdio(&db, &tenant))
.await??;
Ok(())
}
"--help" | "-h" | "help" => {
println!("{HELP}");
Ok(())
}
"--version" | "-V" => {
println!("firstpass {}", env!("CARGO_PKG_VERSION"));
Ok(())
}
other => {
eprintln!("firstpass: unknown command `{other}`\n\n{HELP}");
std::process::exit(2);
}
}
}
fn cmd_doctor() -> Result<(), Box<dyn std::error::Error>> {
match ProxyConfig::from_env() {
Ok(config) => {
let report = cli::doctor(&config, |k| std::env::var(k).ok());
print!("{}", report.render());
if report.healthy() {
Ok(())
} else {
std::process::exit(1);
}
}
Err(e) => {
println!("✗ config: {e}\n\nnot healthy — fix the config error above.");
std::process::exit(1);
}
}
}
fn cmd_onboard(apply: bool) -> Result<(), Box<dyn std::error::Error>> {
use firstpass_proxy::onboard;
let env = onboard::detect(
|k| std::env::var(k).ok(),
|bin| {
std::env::var("PATH")
.is_ok_and(|p| std::env::split_paths(&p).any(|d| d.join(bin).is_file()))
},
|| {
let bind = std::env::var("FIRSTPASS_BIND").unwrap_or_else(|_| "127.0.0.1:8080".into());
std::net::TcpStream::connect_timeout(
&bind
.parse()
.unwrap_or_else(|_| ([127, 0, 0, 1], 8080).into()),
std::time::Duration::from_millis(400),
)
.is_ok()
},
);
let home = std::path::PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".into()));
let (rc, _) = onboard::shell_wiring(&env.shell, &home, &env.bind);
let steps = onboard::plan(&env, &home, onboard::rc_wired(&rc));
print!("{}", onboard::render(&env, &steps, apply));
if apply {
print!("\n{}", onboard::execute(&env, &steps)?);
}
Ok(())
}
fn cmd_offboard() -> Result<(), Box<dyn std::error::Error>> {
let home = std::path::PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".into()));
print!("{}", firstpass_proxy::onboard::offboard(&home)?);
Ok(())
}
fn tenant_arg(args: &[String], config: &ProxyConfig) -> String {
args.iter()
.position(|a| a == "--tenant")
.and_then(|i| args.get(i + 1))
.cloned()
.unwrap_or_else(|| config.tenant_id.clone())
}
fn cmd_trace(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let limit = args
.iter()
.position(|a| a == "--limit")
.and_then(|i| args.get(i + 1))
.and_then(|n| n.parse::<usize>().ok())
.unwrap_or(20);
let config = ProxyConfig::from_env()?;
let tenant = tenant_arg(args, &config);
let traces = store::load_tenant_traces(std::path::Path::new(&config.db_path), &tenant)
.unwrap_or_default();
println!("{}", cli::format_traces(&traces, limit));
Ok(())
}
fn cmd_savings(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let config = ProxyConfig::from_env()?;
let tenant = tenant_arg(args, &config);
let traces = store::load_tenant_traces(std::path::Path::new(&config.db_path), &tenant)
.unwrap_or_default();
let summary = cli::summarize_savings(&traces);
if args.iter().any(|a| a == "--json") {
println!("{}", serde_json::to_string_pretty(&summary)?);
} else {
println!("{}", cli::format_savings(&summary));
}
Ok(())
}
fn cmd_evals(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let config = ProxyConfig::from_env()?;
let tenant = tenant_arg(args, &config);
let traces = store::load_tenant_traces(std::path::Path::new(&config.db_path), &tenant)
.unwrap_or_default();
let summary = cli::summarize_evals(&traces);
if args.iter().any(|a| a == "--json") {
println!("{}", serde_json::to_string_pretty(&summary)?);
} else {
println!("{}", cli::format_evals(&summary));
}
Ok(())
}
fn cmd_calibrate(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let flag = |name: &str, default: f64| -> f64 {
args.iter()
.position(|a| a == name)
.and_then(|i| args.get(i + 1))
.and_then(|v| v.parse::<f64>().ok())
.unwrap_or(default)
};
let alpha = flag("--alpha", 0.1);
let delta = flag("--delta", 0.05);
let min_n = flag("--min-n", 30.0) as usize;
let method = args
.iter()
.position(|a| a == "--method")
.and_then(|i| args.get(i + 1))
.map(String::as_str)
.unwrap_or("conformal");
let config = ProxyConfig::from_env()?;
let tenant = tenant_arg(args, &config);
match method {
"ltt" => {
let report = calibrate_from_store_ltt(&config.db_path, &tenant, alpha, delta, min_n)?;
print!("{}", report.render());
}
_ => {
let report = calibrate_from_store(&config.db_path, &tenant, alpha, delta, min_n)?;
print!("{}", report.render());
}
}
Ok(())
}
fn cmd_ope(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let env_config = ProxyConfig::from_env()?;
let db_path = args
.iter()
.position(|a| a == "--db")
.and_then(|i| args.get(i + 1))
.cloned()
.unwrap_or_else(|| env_config.db_path.clone());
let tenant = tenant_arg(args, &env_config);
if let Some(n_str) = args
.iter()
.position(|a| a == "--start-rung")
.and_then(|i| args.get(i + 1))
{
let start_rung: u32 = n_str.parse().map_err(|e| {
format!("firstpass ope: --start-rung must be a non-negative integer: {e}")
})?;
let report = ips_from_store(&db_path, &tenant, start_rung)?;
print!("{}", report.render());
return Ok(());
}
let config_path = args
.iter()
.position(|a| a == "--config")
.and_then(|i| args.get(i + 1))
.ok_or_else(|| {
eprintln!(
"firstpass ope: one of --config <candidate.toml> or --start-rung N is required"
);
std::process::exit(1);
#[allow(unreachable_code)]
"unreachable"
})?;
let toml = std::fs::read_to_string(config_path)
.map_err(|e| format!("firstpass ope: cannot read --config {config_path:?}: {e}"))?;
let policy = CandidatePolicy::from_toml(&toml)
.map_err(|e| format!("firstpass ope: invalid candidate config: {e}"))?;
let report = ope_from_store(&db_path, &tenant, &policy)?;
print!("{}", report.render());
Ok(())
}