aboutsummaryrefslogtreecommitdiff
path: root/src/cli.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-19 23:52:20 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-19 23:56:07 +0300
commit3f80947678a9723434aa7fddfe23750b5393e24c (patch)
tree6f463797132acfc51dfb6e135820a052954b4f13 /src/cli.rs
parentf76f895efe5b9f19aa9db909354c6c0f2195630b (diff)
feat: make agent automation a first-class cli primitive
Diffstat (limited to 'src/cli.rs')
-rw-r--r--src/cli.rs258
1 files changed, 17 insertions, 241 deletions
diff --git a/src/cli.rs b/src/cli.rs
index ecca934e..9d7389b5 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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]");