mod probe;
mod report;
mod scan;
use anyhow::Result;
use clap::{Parser, Subcommand, ValueEnum};
use report::Report;
use std::io::IsTerminal;
use std::path::PathBuf;
#[derive(Parser)]
#[command(
name = "aegis",
version,
about = "Production readiness audit: scan a codebase or probe a live URL",
long_about = "Aegis grades a project the way a production-rescue engineer does on day one:\n\
what leaks, what's unpinned, what's untested, what's misconfigured, and\n\
what the live endpoint tells an attacker for free."
)]
struct Cli {
#[command(subcommand)]
command: Command,
#[arg(long, global = true, value_enum, default_value_t = Format::Text)]
format: Format,
#[arg(long, global = true)]
fail_under: Option<f64>,
}
#[derive(Subcommand)]
enum Command {
Scan {
path: PathBuf,
},
Probe {
url: String,
},
}
#[derive(Clone, Copy, ValueEnum)]
enum Format {
Text,
Json,
Markdown,
}
fn main() {
let cli = Cli::parse();
match run(&cli) {
Ok(code) => std::process::exit(code),
Err(e) => {
eprintln!("aegis: error: {e:#}");
std::process::exit(2);
}
}
}
fn run(cli: &Cli) -> Result<i32> {
let report = match &cli.command {
Command::Scan { path } => scan::run(path)?,
Command::Probe { url } => {
let resp = probe::fetch(url)?;
probe::analyze(url, &resp)
}
};
emit(&report, cli.format);
if let Some(threshold) = cli.fail_under {
if report.score < threshold {
eprintln!(
"aegis: score {:.0} is below threshold {:.0}",
report.score, threshold
);
return Ok(1);
}
}
Ok(0)
}
fn emit(report: &Report, format: Format) {
match format {
Format::Json => println!("{}", report.to_json()),
Format::Markdown => println!("{}", report.to_markdown()),
Format::Text => {
let color = std::io::stdout().is_terminal();
print!("{}", report.render_text(color));
}
}
}