#![cfg(feature = "cli")]
use std::path::PathBuf;
use std::process::ExitCode;
use clap::{Parser, Subcommand};
#[derive(Debug, Parser)]
#[command(
name = "proserpina",
version,
about = "Multi-agent critique and cross-examination pipeline",
long_about = None
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
Critique {
input: PathBuf,
#[arg(short, long)]
out: Option<PathBuf>,
#[arg(long)]
echo: bool,
#[arg(long)]
seed: Option<u64>,
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
json: bool,
#[arg(long)]
dry_run: bool,
#[arg(long)]
panel: Option<String>,
#[arg(long)]
max_attempts: Option<u32>,
#[arg(long)]
timeout: Option<u64>,
},
Capabilities,
}
fn main() -> ExitCode {
let cli = Cli::parse();
match cli.command {
Command::Capabilities => {
#[cfg(feature = "backend-http")]
{
let caps = proserpina::Capabilities::with_current_auth();
let json = serde_json::to_string_pretty(&caps).unwrap_or_else(|_| "{}".to_owned());
println!("{json}");
ExitCode::SUCCESS
}
#[cfg(not(feature = "backend-http"))]
{
let caps = proserpina::Capabilities::static_info();
let json = serde_json::to_string_pretty(&caps).unwrap_or_else(|_| "{}".to_owned());
println!("{json}");
ExitCode::SUCCESS
}
}
Command::Critique {
input,
out,
echo,
seed,
config,
json,
dry_run,
panel,
max_attempts,
timeout,
} => match run(
&input,
echo,
seed,
config.as_deref(),
json,
dry_run,
panel.as_deref(),
max_attempts,
timeout,
) {
Ok(output) => {
match out {
Some(path) => {
if let Err(e) = std::fs::write(&path, output) {
eprintln!("failed to write {path:?}: {e}");
return ExitCode::FAILURE;
}
}
None => print!("{output}"),
}
ExitCode::SUCCESS
}
Err(err) => {
emit_error(&err, json);
ExitCode::from(err.exit_code())
}
},
}
}
fn emit_error(err: &proserpina::ProserpinaError, json: bool) {
if json {
#[cfg(feature = "json")]
{
eprintln!("{}", err.to_error_json());
return;
}
}
eprintln!("proserpina: {err}");
}
#[allow(clippy::too_many_arguments)]
fn run(
input: &std::path::Path,
echo: bool,
seed: Option<u64>,
config: Option<&std::path::Path>,
json: bool,
dry_run: bool,
panel: Option<&str>,
max_attempts: Option<u32>,
timeout: Option<u64>,
) -> Result<String, proserpina::ProserpinaError> {
let source = input.to_string_lossy().to_string();
let text = std::fs::read_to_string(input).map_err(|e| {
proserpina::ProserpinaError::agent_failure(input.to_string_lossy(), e.to_string())
})?;
if echo {
return proserpina::cli::run_critique_echo(&text, &source);
}
#[cfg(feature = "backend-http")]
{
let seed = seed.unwrap_or_else(rand::random);
let retry_config = proserpina::backend::credentials::Credentials::discover_or(config)
.map(|c| c.retry().clone())
.unwrap_or_default();
let policy =
proserpina::backend::http::RetryPolicy::resolve(&retry_config, max_attempts, timeout);
if dry_run {
return proserpina::cli::plan_critique(&text, &source, seed, config, json, panel);
}
proserpina::cli::run_critique(&text, &source, seed, config, json, panel, policy)
}
#[cfg(not(feature = "backend-http"))]
{
let _ = (seed, config, json, dry_run, panel, max_attempts, timeout);
let mut report = proserpina::cli::run_critique_echo(&text, &source)?;
report.push_str("\n_(built without `backend-http`; used the echo backend)_\n");
Ok(report)
}
}