diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-19 23:52:20 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-19 23:56:07 +0300 |
| commit | 3f80947678a9723434aa7fddfe23750b5393e24c (patch) | |
| tree | 6f463797132acfc51dfb6e135820a052954b4f13 /src/cli.rs | |
| parent | f76f895efe5b9f19aa9db909354c6c0f2195630b (diff) | |
feat: make agent automation a first-class cli primitive
Diffstat (limited to 'src/cli.rs')
| -rw-r--r-- | src/cli.rs | 258 |
1 files changed, 17 insertions, 241 deletions
@@ -4,10 +4,8 @@ use serde::Serialize; use crate::api::client::{ApiClient, ApiClientError}; use crate::api::schema::{ - AgentStatus, ClientWindowTitleSetParams, EmptyParams, EventData, EventMatch, EventsWaitParams, - Method, OutputMatch, PaneAgentState, PaneWaitForOutputParams, ReadFormat, ReadSource, Request, - ResponseResult, SplitDirection, SubscriptionEventData, SubscriptionEventEnvelope, - SubscriptionEventKind, + AgentStatus, ClientWindowTitleSetParams, EmptyParams, Method, PaneAgentState, ReadFormat, + ReadSource, Request, SplitDirection, }; mod agent; @@ -59,11 +57,26 @@ pub enum CommandOutcome { NotCli, } +pub(super) fn print_read_response(response: &serde_json::Value) -> std::io::Result<i32> { + if response.get("error").is_some() { + eprintln!("{response}"); + return Ok(1); + } + if let Some(text) = response["result"]["read"]["text"].as_str() { + print!("{text}"); + } + Ok(0) +} + pub fn maybe_run(args: &[String]) -> std::io::Result<CommandOutcome> { let Some(command) = args.get(1).map(|arg| arg.as_str()) else { return Ok(CommandOutcome::NotCli); }; + if spec::print_requested_help(args)? { + return Ok(CommandOutcome::Handled(0)); + } + let exit_code = match command { "server" => { let Some(exit_code) = server::run_server_command(&args[2..])? else { @@ -84,7 +97,6 @@ pub fn maybe_run(args: &[String]) -> std::io::Result<CommandOutcome> { "terminal" => run_terminal_command(&args[2..])?, "pane" => pane::run_pane_command(&args[2..])?, "plugin" => plugin::run_plugin_command(&args[2..])?, - "wait" => run_wait_command(&args[2..])?, "integration" => integration::run_integration_command(&args[2..])?, "session" => run_session_command(&args[2..])?, _ => return Ok(CommandOutcome::NotCli), @@ -384,26 +396,6 @@ fn run_terminal_command(args: &[String]) -> std::io::Result<i32> { } } -fn run_wait_command(args: &[String]) -> std::io::Result<i32> { - let Some(subcommand) = args.first().map(|arg| arg.as_str()) else { - print_wait_help(); - return Ok(2); - }; - - match subcommand { - "output" => wait_output(&args[1..]), - "agent-status" => wait_agent_status(&args[1..]), - "help" | "--help" | "-h" => { - print_wait_help(); - Ok(0) - } - _ => { - print_wait_help(); - Ok(2) - } - } -} - fn run_session_command(args: &[String]) -> std::io::Result<i32> { let Some(subcommand) = args.first().map(|arg| arg.as_str()) else { print_session_help(); @@ -724,214 +716,6 @@ pub(super) fn parse_attach_target(args: &[String], usage: &str) -> Result<(Strin Ok((target.clone(), takeover)) } -fn wait_output(args: &[String]) -> std::io::Result<i32> { - let Some(raw_pane_id) = args.first() else { - eprintln!("usage: herdr wait output <pane_id> --match <text> [--source visible|recent|recent-unwrapped] [--lines N] [--timeout MS] [--regex]"); - return Ok(2); - }; - - let pane_id = normalize_pane_id(raw_pane_id); - let mut source = ReadSource::Recent; - let mut lines = None; - let mut timeout_ms = None; - let mut strip_ansi = true; - let mut regex = false; - let mut match_value = None; - - let mut index = 1; - while index < args.len() { - match args[index].as_str() { - "--match" => { - let Some(value) = args.get(index + 1) else { - eprintln!("missing value for --match"); - return Ok(2); - }; - match_value = Some(value.clone()); - index += 2; - } - "--source" => { - let Some(value) = args.get(index + 1) else { - eprintln!("missing value for --source"); - return Ok(2); - }; - source = parse_read_source(value)?; - index += 2; - } - "--lines" => { - let Some(value) = args.get(index + 1) else { - eprintln!("missing value for --lines"); - return Ok(2); - }; - lines = Some(parse_u32_flag("--lines", value)?); - index += 2; - } - "--timeout" => { - let Some(value) = args.get(index + 1) else { - eprintln!("missing value for --timeout"); - return Ok(2); - }; - timeout_ms = Some(parse_u64_flag("--timeout", value)?); - index += 2; - } - "--regex" => { - regex = true; - index += 1; - } - "--raw" => { - strip_ansi = false; - index += 1; - } - other => { - eprintln!("unknown option: {other}"); - return Ok(2); - } - } - } - - let Some(match_value) = match_value else { - eprintln!("missing required --match"); - return Ok(2); - }; - - let matcher = if regex { - OutputMatch::Regex { value: match_value } - } else { - OutputMatch::Substring { value: match_value } - }; - - let response = send_request(&Request { - id: "cli:wait:output".into(), - method: Method::PaneWaitForOutput(PaneWaitForOutputParams { - pane_id, - source, - lines, - r#match: matcher, - timeout_ms, - strip_ansi, - }), - })?; - - if response.get("error").is_some() { - eprintln!("{}", serde_json::to_string(&response).unwrap()); - return Ok(1); - } - - println!("{}", serde_json::to_string(&response).unwrap()); - Ok(0) -} - -fn wait_agent_status(args: &[String]) -> std::io::Result<i32> { - let Some(raw_pane_id) = args.first() else { - eprintln!("usage: herdr wait agent-status <pane_id> --status <idle|working|blocked|done|unknown> [--timeout MS]"); - return Ok(2); - }; - - let pane_id = normalize_pane_id(raw_pane_id); - let mut timeout_ms = None; - let mut desired_status = None; - - let mut index = 1; - while index < args.len() { - match args[index].as_str() { - "--status" => { - let Some(value) = args.get(index + 1) else { - eprintln!("missing value for --status"); - return Ok(2); - }; - desired_status = Some(parse_agent_status(value)?); - index += 2; - } - "--timeout" => { - let Some(value) = args.get(index + 1) else { - eprintln!("missing value for --timeout"); - return Ok(2); - }; - timeout_ms = Some(parse_u64_flag("--timeout", value)?); - index += 2; - } - other => { - eprintln!("unknown option: {other}"); - return Ok(2); - } - } - } - - let Some(agent_status) = desired_status else { - eprintln!("missing required --status"); - return Ok(2); - }; - - wait_for_agent_status_change(pane_id, agent_status, timeout_ms) -} - -fn wait_for_agent_status_change( - pane_id: String, - agent_status: AgentStatus, - timeout_ms: Option<u64>, -) -> std::io::Result<i32> { - let request = Request { - id: "cli:wait:agent-status".into(), - method: Method::EventsWait(EventsWaitParams { - match_event: EventMatch::PaneAgentStatusChanged { - pane_id, - agent_status, - }, - timeout_ms, - }), - }; - let response = send_request(&request)?; - match crate::api::client::parse_response_value(response) { - Ok(success) => { - let ResponseResult::WaitMatched { event } = success.result else { - return Err(std::io::Error::other("unexpected wait response result")); - }; - let EventData::PaneAgentStatusChanged { - pane_id, - workspace_id, - agent_status, - agent, - title, - display_agent, - state_labels, - } = event.data - else { - return Err(std::io::Error::other("unexpected wait event data")); - }; - let event = SubscriptionEventEnvelope { - event: SubscriptionEventKind::PaneAgentStatusChanged, - data: SubscriptionEventData::PaneAgentStatusChanged( - crate::api::schema::PaneAgentStatusChangedEvent { - pane_id, - workspace_id, - agent_status, - agent, - title, - display_agent, - state_labels, - }, - ), - }; - println!( - "{}", - serde_json::to_string(&event).map_err(std::io::Error::other)? - ); - Ok(0) - } - Err(ApiClientError::ErrorResponse(response)) => { - if response.error.code == "timeout" { - eprintln!("timed out waiting for agent status change"); - } else { - eprintln!( - "{}", - serde_json::to_string(&response).map_err(std::io::Error::other)? - ); - } - Ok(1) - } - Err(err) => Err(api_client_error_to_io(err)), - } -} - pub(super) fn print_response(response: &serde_json::Value) -> std::io::Result<i32> { if response.get("error").is_some() { eprintln!("{}", serde_json::to_string(response).unwrap()); @@ -1160,14 +944,6 @@ fn print_terminal_help() { eprintln!(" detach from direct attach with ctrl+b q; send literal ctrl+b with ctrl+b ctrl+b"); } -fn print_wait_help() { - eprintln!("herdr wait commands:"); - eprintln!(" herdr wait output <pane_id> --match <text> [--source visible|recent|recent-unwrapped] [--lines N] [--timeout MS] [--regex] [--raw]"); - eprintln!( - " herdr wait agent-status <pane_id> --status <idle|working|blocked|done|unknown> [--timeout MS]" - ); -} - fn print_session_help() { eprintln!("herdr session commands:"); eprintln!(" herdr session list [--json]"); |
