use crate::cli::RedteamAction;
use crate::config::TuiConfig;
use super::common::ensure_engine;
pub async fn run_redteam_command(action: &RedteamAction, config: &TuiConfig) -> i32 {
match action {
RedteamAction::Run { agent, categories, max_probes, json } => {
run_redteam_run(agent, categories, *max_probes, *json, config).await
}
RedteamAction::Last { json } => {
run_redteam_last(*json, config).await
}
RedteamAction::Target { url, json, ci, threshold } => {
super::eval::run_eval_command(
url, false, false, true, false, None, &[], *json, *ci, *threshold,
None, None, None, None, None, false, 5, false, false, None, config,
).await
}
}
}
async fn run_redteam_run(
agent: &str,
categories: &[String],
max_probes: Option<u32>,
json: bool,
config: &TuiConfig,
) -> i32 {
let client = match ensure_engine(config).await {
Ok(c) => c,
Err(code) => return code,
};
let mut body = serde_json::json!({ "agentName": agent });
if !categories.is_empty() {
body["categories"] = serde_json::json!(categories);
}
if let Some(max) = max_probes {
body["maxProbes"] = serde_json::json!(max);
}
eprintln!("Running red-team probes against agent '{agent}'...");
match client.post_json("/redteam/run", &body).await {
Ok(result) => {
if let Some(err_msg) = result.get("error").and_then(|v| v.as_str()) {
let msg = result.get("message").and_then(|v| v.as_str()).unwrap_or(err_msg);
eprintln!("Error: {msg}");
return 1;
}
if json {
println!("{}", serde_json::to_string_pretty(&result).unwrap_or_default());
return 0;
}
format_redteam_report(&result);
0
}
Err(e) => {
eprintln!("Error: {e}");
1
}
}
}
async fn run_redteam_last(json: bool, config: &TuiConfig) -> i32 {
let client = match ensure_engine(config).await {
Ok(c) => c,
Err(code) => return code,
};
match client.get_json("/redteam/last").await {
Ok(result) => {
if let Some(err_msg) = result.get("error").and_then(|v| v.as_str()) {
let msg = result.get("message").and_then(|v| v.as_str()).unwrap_or(err_msg);
eprintln!("Error: {msg}");
return 1;
}
if json {
println!("{}", serde_json::to_string_pretty(&result).unwrap_or_default());
return 0;
}
format_redteam_report(&result);
0
}
Err(e) => {
eprintln!("Error: {e}");
1
}
}
}
fn format_redteam_report(report: &serde_json::Value) {
let agent = report.get("agentName").and_then(|v| v.as_str()).unwrap_or("?");
let total = report.get("totalProbes").and_then(serde_json::Value::as_u64).unwrap_or(0);
let passed = report.get("passCount").and_then(serde_json::Value::as_u64).unwrap_or(0);
let failed = report.get("failCount").and_then(serde_json::Value::as_u64).unwrap_or(0);
let inconclusive = report.get("inconclusiveCount").and_then(serde_json::Value::as_u64).unwrap_or(0);
let duration = report.get("duration").and_then(serde_json::Value::as_u64).unwrap_or(0);
let score = report.get("securityScore").and_then(|s| s.get("score")).and_then(serde_json::Value::as_f64).unwrap_or(0.0);
let grade = report.get("securityScore").and_then(|s| s.get("grade")).and_then(|v| v.as_str()).unwrap_or("?");
let capped = report.get("securityScore").and_then(|s| s.get("criticalCapped")).and_then(serde_json::Value::as_bool).unwrap_or(false);
println!();
println!(" Red-Team Security Report: {agent}");
println!(" {}", "=".repeat(50));
println!();
let filled = (score / 100.0 * 30.0) as usize;
let bar: String = format!("[{}{}] {:.0}/100 ({})",
"#".repeat(filled),
"-".repeat(30 - filled),
score,
grade,
);
println!(" Security Score: {bar}");
if capped {
println!(" WARNING: Score capped due to critical gap");
}
println!();
println!(" Probes: {total} total | {passed} passed | {failed} failed | {inconclusive} inconclusive");
println!(" Duration: {:.1}s", duration as f64 / 1000.0);
if let Some(mapping) = report.get("owaspMapping").and_then(|v| v.as_object()) {
println!();
println!(" {:<8} {:<28} {:>6} {:>7} {:>7} {:>7}", "ID", "CATEGORY", "SCORE", "PASS", "FAIL", "TOTAL");
println!(" {}", "-".repeat(68));
let mut entries: Vec<_> = mapping.iter().collect();
entries.sort_by_key(|(k, _)| (*k).clone());
for (_, cat) in entries {
let cat_id = cat.get("categoryId").and_then(|v| v.as_str()).unwrap_or("?");
let cat_name = cat.get("categoryName").and_then(|v| v.as_str()).unwrap_or("?");
let cat_score = cat.get("score").and_then(serde_json::Value::as_f64).unwrap_or(0.0);
let cat_pass = cat.get("passed").and_then(serde_json::Value::as_u64).unwrap_or(0);
let cat_fail = cat.get("failed").and_then(serde_json::Value::as_u64).unwrap_or(0);
let cat_total = cat.get("total").and_then(serde_json::Value::as_u64).unwrap_or(0);
let name = if cat_name.len() > 26 { &cat_name[..26] } else { cat_name };
println!(" {cat_id:<8} {name:<28} {cat_score:>5.0}% {cat_pass:>7} {cat_fail:>7} {cat_total:>7}");
}
}
println!();
}