circles/cmd/dispatcher.rs
2025-07-09 23:39:48 +02:00

189 lines
5.9 KiB
Rust

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<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<String>,
/// Path to Rhai script file
#[arg(short, long, help = "Path to Rhai script file")]
file: Option<String>,
/// 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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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(())
}