use std::io::{self, BufRead}; use sgclaw::service::{ClientMessage, ServiceMessage}; use tungstenite::{connect, Message}; fn main() -> std::process::ExitCode { match run() { Ok(()) => std::process::ExitCode::SUCCESS, Err(err) => { eprintln!("sg_claw_client failed: {err}"); std::process::ExitCode::FAILURE } } } fn parse_request(input: &str) -> (ClientMessage, bool) { match input.trim() { "/connect" => (ClientMessage::Connect, true), "/start" => (ClientMessage::Start, true), "/stop" => (ClientMessage::Stop, true), instruction => ( ClientMessage::SubmitTask { instruction: instruction.to_string(), conversation_id: String::new(), messages: vec![], page_url: String::new(), page_title: String::new(), }, false, ), } } fn run() -> Result<(), String> { let service_url = std::env::var("SG_CLAW_SERVICE_WS_URL") .unwrap_or_else(|_| "ws://127.0.0.1:42321".to_string()); let (mut socket, _) = connect(service_url.as_str()).map_err(|err| err.to_string())?; let stdin = io::stdin(); loop { eprint!("> "); let mut input = String::new(); let bytes_read = stdin .lock() .read_line(&mut input) .map_err(|err| err.to_string())?; if bytes_read == 0 { break; // EOF — graceful exit } if input.trim().is_empty() { continue; } let (request, exit_on_status) = parse_request(&input); let payload = serde_json::to_string(&request).map_err(|err| err.to_string())?; socket .send(Message::Text(payload.into())) .map_err(|err| err.to_string())?; // Inner loop: consume service messages until the task finishes. loop { match socket.read().map_err(|err| err.to_string())? { Message::Text(text) => { let message: ServiceMessage = serde_json::from_str(&text).map_err(|err| err.to_string())?; match message { ServiceMessage::StatusChanged { state } => { println!("status: {state}"); if exit_on_status { break; } } ServiceMessage::LogEntry { level: _, message } => { println!("{message}"); } ServiceMessage::TaskComplete { success: _, summary } => { println!("{summary}"); break; } ServiceMessage::Busy { message } => { eprintln!("busy: {message}"); break; } ServiceMessage::Pong => {} ServiceMessage::ConfigUpdated { success, message } => { if success { println!("config updated: {message}"); } else { eprintln!("config update failed: {message}"); } } } } Message::Close(_) => { return Err("service disconnected".to_string()); } _ => {} } } } Ok(()) }