use clap::Parser; use hero_supervisor::{Supervisor, SupervisorBuilder, ScriptType}; use log::{error, info}; use colored::Colorize; use std::io::{self, Write}; use std::time::Duration; #[derive(Parser, Debug)] #[command(author, version, about = "Rhai Client - Script execution client", long_about = None)] struct Args { /// Caller ID (your identity) #[arg(short = 'c', long = "caller-id", help = "Caller ID (your identity)")] caller_id: String, /// Context ID (execution context) #[arg(short = 'k', long = "context-id", help = "Context ID (execution context)")] context_id: String, /// Script type to execute (osis, sal, v, python) #[arg(short = 'T', long = "script-type", default_value = "osis", help = "Script type: osis, sal, v, or python")] script_type: String, /// Redis URL #[arg(short, long, default_value = "redis://localhost:6379", help = "Redis connection URL")] redis_url: String, /// Rhai script to execute #[arg(short, long, help = "Rhai script to execute")] script: Option, /// Path to Rhai script file #[arg(short, long, help = "Path to Rhai script file")] file: Option, /// Timeout for script execution (in seconds) #[arg(short, long, default_value = "30", help = "Timeout for script execution in seconds")] timeout: u64, /// Increase verbosity (can be used multiple times) #[arg(short, long, action = clap::ArgAction::Count, help = "Increase verbosity (-v for debug, -vv for trace)")] verbose: u8, /// Disable timestamps in log output #[arg(long, help = "Remove timestamps from log output")] no_timestamp: bool, } #[tokio::main] async fn main() -> Result<(), Box> { let args = Args::parse(); // Configure logging based on verbosity level let log_config = match args.verbose { 0 => "warn,hero_supervisor=warn", 1 => "info,hero_supervisor=info", 2 => "debug,hero_supervisor=debug", _ => "trace,hero_supervisor=trace", }; std::env::set_var("RUST_LOG", log_config); // Configure env_logger with or without timestamps if args.no_timestamp { env_logger::Builder::from_default_env() .format_timestamp(None) .init(); } else { env_logger::init(); } // Validate script type match args.script_type.to_lowercase().as_str() { "osis" | "sal" | "v" | "python" => { // Valid script types - no worker validation needed since we use hardcoded queues } _ => { error!("❌ Invalid script type: {}. Valid types: osis, sal, v, python", args.script_type); return Err(format!("Invalid script type: {}", args.script_type).into()); } } if args.verbose > 0 { info!("🔗 Starting Hero Supervisor"); info!("📋 Configuration:"); info!(" Caller ID: {}", args.caller_id); info!(" Context ID: {}", args.context_id); info!(" Script Type: {}", args.script_type); info!(" Redis URL: {}", args.redis_url); info!(" Timeout: {}s", args.timeout); info!(" Using hardcoded worker queues for script type: {}", args.script_type); info!(""); } // Create the supervisor client let client = SupervisorBuilder::new() .redis_url(&args.redis_url) .build()?; if args.verbose > 0 { info!("✅ Connected to Redis at {}", args.redis_url); } // Determine execution mode if let Some(script_content) = args.script { // Execute inline script if args.verbose > 0 { info!("📜 Executing inline script"); } execute_script(&client, script_content, &args.script_type, args.timeout).await?; } else if let Some(file_path) = args.file { // Execute script from file if args.verbose > 0 { info!("📁 Loading script from file: {}", file_path); } let script_content = std::fs::read_to_string(&file_path) .map_err(|e| format!("Failed to read script file '{}': {}", file_path, e))?; execute_script(&client, script_content, &args.script_type, args.timeout).await?; } else { // Interactive mode info!("🎮 Entering interactive mode"); info!("Type Rhai scripts and press Enter to execute. Type 'exit' or 'quit' to close."); run_interactive_mode(&client, &args.script_type, args.timeout, args.verbose).await?; } Ok(()) } async fn execute_script( client: &Supervisor, script: String, script_type_str: &str, timeout_secs: u64, ) -> Result<(), Box> { info!("⚡ Executing script: {:.50}...", script); // Parse script type let script_type = match script_type_str.to_lowercase().as_str() { "osis" => ScriptType::OSIS, "sal" => ScriptType::SAL, "v" => ScriptType::V, "python" => ScriptType::Python, _ => { error!("❌ Invalid script type: {}. Valid types: osis, sal, v, python", script_type_str); return Err(format!("Invalid script type: {}", script_type_str).into()); } }; let timeout = Duration::from_secs(timeout_secs); match client .new_job() .script_type(script_type) .script(&script) .timeout(timeout) .await_response() .await { Ok(result) => { info!("✅ Script execution completed"); println!("{}", "Result:".green().bold()); println!("{}", result); } Err(e) => { error!("❌ Script execution failed: {}", e); return Err(Box::new(e)); } } Ok(()) } async fn run_interactive_mode( client: &Supervisor, script_type_str: &str, timeout_secs: u64, verbose: u8, ) -> Result<(), Box> { // Parse script type let script_type = match script_type_str.to_lowercase().as_str() { "osis" => ScriptType::OSIS, "sal" => ScriptType::SAL, "v" => ScriptType::V, "python" => ScriptType::Python, _ => { error!("❌ Invalid script type: {}. Valid types: osis, sal, v, python", script_type_str); return Err(format!("Invalid script type: {}", script_type_str).into()); } }; let timeout = Duration::from_secs(timeout_secs); loop { print!("rhai> "); io::stdout().flush()?; let mut input = String::new(); io::stdin().read_line(&mut input)?; let input = input.trim(); if input.is_empty() { continue; } if input == "exit" || input == "quit" { info!("👋 Goodbye!"); break; } if verbose > 0 { info!("⚡ Executing: {}", input); } match client .new_job() .script_type(script_type.clone()) .script(input) .timeout(timeout) .await_response() .await { Ok(result) => { println!("{}", result.green()); } Err(e) => { println!("{}", format!("error: {}", e).red()); } } println!(); // Add blank line for readability } Ok(()) }