use crate::application::cli::error::CliError;
use clap::Subcommand;
use colored::Colorize;
use std::path::PathBuf;
#[derive(Debug, Subcommand)]
pub enum ManeuverCommands {
Visualize(ManeuverVisualizeArgs),
Validate(ManeuverValidateArgs),
}
#[derive(Debug, clap::Args)]
pub struct ManeuverVisualizeArgs {
#[arg(short, long)]
pub config: PathBuf,
#[arg(short, long, default_value = "ascii")]
pub format: String,
#[arg(short, long)]
pub output: Option<PathBuf>,
}
#[derive(Debug, clap::Args)]
pub struct ManeuverValidateArgs {
#[arg(short, long)]
pub config: PathBuf,
#[arg(short, long)]
pub verbose: bool,
}
pub async fn handle_maneuver_command(command: ManeuverCommands) -> Result<(), CliError> {
match command {
ManeuverCommands::Visualize(args) => handle_maneuver_visualize(args),
ManeuverCommands::Validate(args) => handle_maneuver_validate(args).await,
}
}
fn handle_maneuver_visualize(args: ManeuverVisualizeArgs) -> Result<(), CliError> {
use crate::application::cli::config::battalion_config::BattalionYamlConfig;
use crate::application::cli::config::loader::load_battalion_config;
use crate::application::services::battalion::flow_visualizer::{
FlowVisualizer, VisualizationFormat,
};
use crate::core::platform::container::battalion::parser::FlowParser;
let battalion_config = load_battalion_config(&args.config)?;
let maneuver_config = match battalion_config {
BattalionYamlConfig::Maneuver(config) => config,
_ => {
return Err(CliError::ValidationError {
message: format!(
"Expected maneuver config, but got: {}",
battalion_config.battalion_type()
),
});
}
};
let flow = FlowParser::parse(&maneuver_config.flow).map_err(|e| CliError::ValidationError {
message: format!("Invalid flow expression '{}': {}", maneuver_config.flow, e),
})?;
let format = match args.format.to_lowercase().as_str() {
"ascii" => VisualizationFormat::Ascii,
"mermaid" => VisualizationFormat::Mermaid,
_ => {
return Err(CliError::InvalidFieldValue {
field: "format".to_string(),
message: format!("must be 'ascii' or 'mermaid', got: {}", args.format),
});
}
};
let visualization = FlowVisualizer::visualize(&flow, format);
if let Some(output_path) = args.output {
std::fs::write(&output_path, &visualization)?;
println!(
"{} Visualization written to: {}",
"✓".green().bold(),
output_path.display()
);
} else {
println!(
"\n{} Flow Visualization ({}):",
"📊".cyan().bold(),
args.format
);
println!("{}", "═".repeat(80));
println!("{}", visualization);
println!("{}", "═".repeat(80));
}
Ok(())
}
async fn handle_maneuver_validate(args: ManeuverValidateArgs) -> Result<(), CliError> {
use crate::application::cli::config::battalion_config::BattalionYamlConfig;
use crate::application::cli::config::loader::load_battalion_config;
use crate::core::platform::container::battalion::parser::FlowParser;
if args.verbose {
println!(
"{} Validating Maneuver configuration: {}",
"→".cyan().bold(),
args.config.display()
);
}
let battalion_config = load_battalion_config(&args.config)?;
let maneuver_config = match battalion_config {
BattalionYamlConfig::Maneuver(config) => config,
_ => {
return Err(CliError::ValidationError {
message: format!(
"Expected maneuver config, but got: {}",
battalion_config.battalion_type()
),
});
}
};
if args.verbose {
println!(
"{} Validating flow expression: {}",
"→".cyan().bold(),
maneuver_config.flow
);
}
let flow = FlowParser::parse(&maneuver_config.flow).map_err(|e| CliError::ValidationError {
message: format!("Invalid flow expression '{}': {}", maneuver_config.flow, e),
})?;
println!("{} Flow expression is valid", "✓".green().bold());
let agent_names = extract_agent_names(&flow);
if args.verbose {
println!(
"{} Agents referenced in flow: {}",
"→".cyan().bold(),
agent_names.join(", ")
);
}
let paladin_names: std::collections::HashSet<_> = maneuver_config
.paladins
.iter()
.filter_map(|p| match p {
crate::application::cli::config::battalion_config::PaladinReference::Inline(inline) => {
Some(inline.name.clone())
}
crate::application::cli::config::battalion_config::PaladinReference::File {
..
} => {
None
}
})
.collect();
let mut missing_agents = Vec::new();
for agent_name in &agent_names {
if !paladin_names.contains(agent_name) {
missing_agents.push(agent_name.clone());
}
}
if !missing_agents.is_empty() {
return Err(CliError::ValidationError {
message: format!(
"Flow references agents not found in configuration: {}",
missing_agents.join(", ")
),
});
}
println!(
"{} All {} referenced agents found in configuration",
"✓".green().bold(),
agent_names.len()
);
if args.verbose {
println!(
"{} Validating {} Paladin configurations...",
"→".cyan().bold(),
maneuver_config.paladins.len()
);
}
println!("{} Configuration is valid", "✓".green().bold());
if args.verbose {
println!("\n{} Validation Summary:", "→".cyan().bold());
println!(" Config file: {}", args.config.display());
println!(" Maneuver name: {}", maneuver_config.name);
println!(" Flow: {}", maneuver_config.flow);
println!(" Agents in flow: {}", agent_names.len());
println!(
" Total Paladins defined: {}",
maneuver_config.paladins.len()
);
}
Ok(())
}
fn extract_agent_names(
flow: &crate::core::platform::container::battalion::parser::FlowExpression,
) -> Vec<String> {
use crate::core::platform::container::battalion::parser::FlowExpression;
let mut names = Vec::new();
match flow {
FlowExpression::Agent(name) => names.push(name.clone()),
FlowExpression::Sequential(exprs) | FlowExpression::Parallel(exprs) => {
for expr in exprs {
names.extend(extract_agent_names(expr));
}
}
}
names
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::platform::container::battalion::parser::FlowParser;
#[test]
fn test_extract_agent_names_simple() {
let flow = FlowParser::parse("agent1").unwrap();
let names = extract_agent_names(&flow);
assert_eq!(names, vec!["agent1"]);
}
#[test]
fn test_extract_agent_names_sequential() {
let flow = FlowParser::parse("agent1 -> agent2 -> agent3").unwrap();
let names = extract_agent_names(&flow);
assert_eq!(names, vec!["agent1", "agent2", "agent3"]);
}
#[test]
fn test_extract_agent_names_parallel() {
let flow = FlowParser::parse("(agent1, agent2)").unwrap();
let names = extract_agent_names(&flow);
assert_eq!(names.len(), 2);
assert!(names.contains(&"agent1".to_string()));
assert!(names.contains(&"agent2".to_string()));
}
#[test]
fn test_extract_agent_names_nested() {
let flow = FlowParser::parse("a -> (b, c) -> d").unwrap();
let names = extract_agent_names(&flow);
assert_eq!(names.len(), 4);
assert_eq!(names[0], "a");
assert_eq!(names[3], "d");
}
}