use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use llm_assisted_api_debugging_lab::cases::{load_case, log_path_for, KNOWN_CASES};
use llm_assisted_api_debugging_lab::{
collect_evidence, diagnose, parse_log, render_prompt, render_prompt_json, render_report,
render_short, Diagnosis,
};
use std::path::{Path, PathBuf};
use std::process::ExitCode;
#[derive(Parser, Debug)]
#[command(
name = "llm-assisted-api-debugging-lab",
version,
about = "LLM-Assisted API Debugging Lab CLI"
)]
struct Cli {
#[arg(long, default_value = "fixtures", global = true)]
fixtures_dir: PathBuf,
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Subcommand, Debug)]
enum Cmd {
ListCases,
Diagnose { name: String },
DiagnoseLog { path: PathBuf },
Report { name: String },
Prompt { name: String },
PromptJson { name: String },
}
fn main() -> ExitCode {
let cli = Cli::parse();
match run(&cli) {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("error: {e:#}");
ExitCode::from(exit_code_for(&e))
}
}
}
fn run(cli: &Cli) -> Result<()> {
match &cli.cmd {
Cmd::ListCases => {
for name in KNOWN_CASES {
println!("{name}");
}
Ok(())
}
Cmd::Diagnose { name } => {
let d = build_diagnosis(&cli.fixtures_dir, name)?;
print!("{}", render_short(&d));
Ok(())
}
Cmd::DiagnoseLog { path } => {
let log_text = std::fs::read_to_string(path)
.with_context(|| format!("reading {}", path.display()))?;
let evidence = parse_log(&log_text);
let name = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("log")
.to_string();
let d = diagnose(&name, &evidence);
print!("{}", render_short(&d));
Ok(())
}
Cmd::Report { name } => {
let d = build_diagnosis(&cli.fixtures_dir, name)?;
print!("{}", render_report(&d));
Ok(())
}
Cmd::Prompt { name } => {
let d = build_diagnosis(&cli.fixtures_dir, name)?;
print!("{}", render_prompt(&d));
Ok(())
}
Cmd::PromptJson { name } => {
let d = build_diagnosis(&cli.fixtures_dir, name)?;
let value = render_prompt_json(&d);
println!(
"{}",
serde_json::to_string_pretty(&value)
.with_context(|| format!("serializing prompt-json for {name}"))?
);
Ok(())
}
}
}
fn build_diagnosis(fixtures_dir: &Path, name: &str) -> Result<Diagnosis> {
let case = load_case(fixtures_dir, name).with_context(|| format!("loading case {name}"))?;
let project_root = fixtures_dir
.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| PathBuf::from("."));
let log_text = match log_path_for(&case, &project_root) {
Some(p) => {
std::fs::read_to_string(&p).with_context(|| format!("reading log {}", p.display()))?
}
None => String::new(),
};
let evidence = collect_evidence(&case, &log_text);
Ok(diagnose(name, &evidence))
}
fn exit_code_for(err: &anyhow::Error) -> u8 {
use llm_assisted_api_debugging_lab::cases::CaseError;
let mapped = err
.downcast_ref::<CaseError>()
.or_else(|| err.chain().find_map(|e| e.downcast_ref::<CaseError>()));
match mapped {
Some(CaseError::Unknown(_)) => 2,
Some(CaseError::Io(_, _) | CaseError::Parse(_, _) | CaseError::NameMismatch { .. }) => 3,
None => 1,
}
}