use clap::Parser; use rhai_dispatcher::{RhaiDispatcher, RhaiDispatcherBuilder}; use log::{error, info}; use std::io::{self, Write}; use std::time::Duration; #[derive(Parser, Debug)] #[command(author, version, about = "Circles Client - Rhai script execution client", long_about = None)] struct Args { /// Caller public key (caller ID) #[arg(short = 'c', long = "caller-key", help = "Caller public key (your identity)")] caller_public_key: String, /// Circle public key (context ID) #[arg(short = 'k', long = "circle-key", help = "Circle public key (execution context)")] circle_public_key: String, /// Worker public key (defaults to circle public key if not provided) #[arg(short = 'w', long = "worker-key", help = "Worker public key (defaults to circle key)")] worker_public_key: Option, /// 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, } #[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,circles_client=info,rhai_dispatcher=info", 1 => "info,circles_client=debug,rhai_dispatcher=debug", 2 => "debug", _ => "trace", }; std::env::set_var("RUST_LOG", log_config); env_logger::init(); // Use worker key or default to circle key let worker_key = args.worker_public_key.unwrap_or_else(|| args.circle_public_key.clone()); info!("🔗 Starting Circles Client"); info!("📋 Configuration:"); info!(" Caller Key: {}", args.caller_public_key); info!(" Circle Key: {}", args.circle_public_key); info!(" Worker Key: {}", worker_key); info!(" Redis URL: {}", args.redis_url); info!(" Timeout: {}s", args.timeout); info!(); // Create the Rhai client let client = RhaiDispatcherBuilder::new() .caller_id(&args.caller_public_key) .redis_url(&args.redis_url) .build()?; info!("✅ Connected to Redis at {}", args.redis_url); // Determine execution mode if let Some(script_content) = args.script { // Execute inline script info!("📜 Executing inline script"); execute_script(&client, &worker_key, script_content, args.timeout).await?; } else if let Some(file_path) = args.file { // Execute script from file 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, &worker_key, script_content, 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, &worker_key, args.timeout).await?; } Ok(()) } async fn execute_script( client: &RhaiDispatcher, worker_key: &str, script: String, timeout_secs: u64, ) -> Result<(), Box> { info!("⚡ Executing script: {:.50}...", script); let timeout = Duration::from_secs(timeout_secs); match client .new_play_request() .recipient_id(worker_key) .script(&script) .timeout(timeout) .await_response() .await { Ok(result) => { info!("✅ Script execution completed"); println!("Status: {}", result.status); if let Some(output) = result.output { println!("Output: {}", output); } if let Some(error) = result.error { println!("Error: {}", error); } } Err(e) => { error!("❌ Script execution failed: {}", e); return Err(Box::new(e)); } } Ok(()) } async fn run_interactive_mode( client: &RhaiDispatcher, worker_key: &str, timeout_secs: u64, ) -> Result<(), Box> { 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; } info!("⚡ Executing: {}", input); match client .new_play_request() .recipient_id(worker_key) .script(input) .timeout(timeout) .await_response() .await { Ok(result) => { println!("Status: {}", result.status); if let Some(output) = result.output { println!("Output: {}", output); } if let Some(error) = result.error { println!("Error: {}", error); } } Err(e) => { error!("❌ Execution failed: {}", e); } } println!(); // Add blank line for readability } Ok(()) }