use clap::Parser;
use modular_agent_core::{AgentError, AgentValue, ModularAgent, ModularAgentEvent};
use std::path::Path;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::select;
#[derive(Parser)]
#[command(name = "cli")]
#[command(about = "Run a modular agent preset with stdin/stdout")]
struct Args {
preset: String,
#[arg(short, long, default_value = "input")]
input: String,
#[arg(short, long, default_value = "output")]
output: String,
#[arg(short, long)]
verbose: bool,
}
#[tokio::main]
async fn main() -> Result<(), AgentError> {
let args = Args::parse();
if args.verbose {
env_logger::Builder::new()
.filter_level(log::LevelFilter::Info)
.init();
}
if !Path::new(&args.preset).exists() {
return Err(AgentError::IoError(format!(
"Preset file not found: {}",
args.preset
)));
}
let ma = ModularAgent::init()?;
ma.ready().await?;
let output_channel = args.output.clone();
let mut output_rx = ma.subscribe_to_event(move |envelope| {
if let ModularAgentEvent::ExternalOutput(name, value) = envelope.event
&& name == output_channel
{
return Some(value);
}
None
});
let preset_id = ma.open_preset_from_file(&args.preset, None).await?;
ma.start_preset(&preset_id).await?;
if args.verbose {
eprintln!("Preset loaded: {}", args.preset);
eprintln!(
"Input channel: {}, Output channel: {}",
args.input, args.output
);
}
let stdin = tokio::io::stdin();
let reader = BufReader::new(stdin);
let mut lines = reader.lines();
loop {
select! {
_ = tokio::signal::ctrl_c() => {
if args.verbose {
eprintln!("\nShutting down...");
}
break;
}
result = lines.next_line() => {
match result {
Ok(Some(line)) => {
ma.write_external_input(
args.input.clone(),
AgentValue::string(line)
).await?;
}
Ok(None) => break, Err(e) => {
eprintln!("Error reading stdin: {}", e);
break;
}
}
}
Some(value) = output_rx.recv() => {
println!("{}", format_value(&value));
}
}
}
ma.stop_preset(&preset_id).await?;
ma.shutdown().await?;
while let Ok(value) = output_rx.try_recv() {
println!("{}", format_value(&value));
}
Ok(())
}
fn format_value(value: &AgentValue) -> String {
match value {
AgentValue::String(s) => s.to_string(),
_ => serde_json::to_string(value).unwrap_or_else(|_| format!("{:?}", value)),
}
}