aboutsummaryrefslogtreecommitdiff
path: root/src/cli.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cli.rs')
-rw-r--r--src/cli.rs234
1 files changed, 103 insertions, 131 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 620a592f..feb24c70 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -6,7 +6,8 @@ use serde::Serialize;
use crate::api;
use crate::api::schema::{
- AgentStatus, EmptyParams, Method, OutputMatch, PaneListParams, PaneReadParams,
+ AgentStatus, EmptyParams, IntegrationInstallParams, IntegrationTarget,
+ IntegrationUninstallParams, Method, OutputMatch, PaneListParams, PaneReadParams,
PaneSendInputParams, PaneSendKeysParams, PaneSendTextParams, PaneSplitParams, PaneTarget,
PaneWaitForOutputParams, ReadSource, Request, SplitDirection, Subscription, TabCreateParams,
TabListParams, TabRenameParams, TabTarget, WorkspaceCreateParams, WorkspaceRenameParams,
@@ -24,6 +25,12 @@ pub fn maybe_run(args: &[String]) -> std::io::Result<CommandOutcome> {
};
let exit_code = match command {
+ "server" => {
+ let Some(exit_code) = run_server_command(&args[2..])? else {
+ return Ok(CommandOutcome::NotCli);
+ };
+ exit_code
+ }
"workspace" => run_workspace_command(&args[2..])?,
"tab" => run_tab_command(&args[2..])?,
"pane" => run_pane_command(&args[2..])?,
@@ -35,6 +42,24 @@ pub fn maybe_run(args: &[String]) -> std::io::Result<CommandOutcome> {
Ok(CommandOutcome::Handled(exit_code))
}
+fn run_server_command(args: &[String]) -> std::io::Result<Option<i32>> {
+ let Some(subcommand) = args.first().map(|arg| arg.as_str()) else {
+ return Ok(None);
+ };
+
+ match subcommand {
+ "stop" => server_stop(&args[1..]).map(Some),
+ "help" | "--help" | "-h" => {
+ print_server_help();
+ Ok(Some(0))
+ }
+ _ => {
+ print_server_help();
+ Ok(Some(2))
+ }
+ }
+}
+
fn run_workspace_command(args: &[String]) -> std::io::Result<i32> {
let Some(subcommand) = args.first().map(|arg| arg.as_str()) else {
print_workspace_help();
@@ -129,6 +154,15 @@ fn run_integration_command(args: &[String]) -> std::io::Result<i32> {
}
}
+fn server_stop(args: &[String]) -> std::io::Result<i32> {
+ if !args.is_empty() {
+ eprintln!("usage: herdr server stop");
+ return Ok(2);
+ }
+
+ send_ok_request(Method::ServerStop(EmptyParams::default()))
+}
+
fn workspace_list(args: &[String]) -> std::io::Result<i32> {
if !args.is_empty() {
eprintln!("usage: herdr workspace list");
@@ -625,153 +659,85 @@ fn pane_run(args: &[String]) -> std::io::Result<i32> {
}
fn integration_install(args: &[String]) -> std::io::Result<i32> {
- let Some(target) = args.first().map(|arg| arg.as_str()) else {
- eprintln!("usage: herdr integration install <pi|claude|codex|opencode>");
+ let Some(target) = parse_integration_target(args, "install")? else {
return Ok(2);
};
- if args.len() != 1 {
- eprintln!("usage: herdr integration install <pi|claude|codex|opencode>");
- return Ok(2);
+
+ let response = send_request(&Request {
+ id: "cli:integration:install".into(),
+ method: Method::IntegrationInstall(IntegrationInstallParams { target }),
+ })?;
+
+ if let Some(error) = response.get("error") {
+ eprintln!("{}", serde_json::to_string(error).unwrap());
+ return Ok(1);
}
- match target {
- "pi" => {
- let path = crate::integration::install_pi()?;
- println!("installed pi integration to {}", path.display());
- Ok(0)
- }
- "claude" => {
- let installed = crate::integration::install_claude()?;
- println!(
- "installed claude integration hook to {}",
- installed.hook_path.display()
- );
- println!(
- "ensured claude settings at {}",
- installed.settings_path.display()
- );
- Ok(0)
- }
- "codex" => {
- let installed = crate::integration::install_codex()?;
- println!(
- "installed codex integration hook to {}",
- installed.hook_path.display()
- );
- println!("ensured codex hooks at {}", installed.hooks_path.display());
- println!(
- "ensured codex config at {}",
- installed.config_path.display()
- );
- Ok(0)
- }
- "opencode" => {
- let installed = crate::integration::install_opencode()?;
- println!(
- "installed opencode integration plugin to {}",
- installed.plugin_path.display()
- );
- Ok(0)
- }
- _ => {
- eprintln!("unknown integration target: {target}");
- eprintln!("currently supported: pi, claude, codex, opencode");
- Ok(2)
- }
+ let Some(messages) = response["result"]["details"]["messages"].as_array() else {
+ eprintln!("invalid integration install response");
+ return Ok(1);
+ };
+
+ for message in messages.iter().filter_map(|entry| entry.as_str()) {
+ println!("{message}");
}
+
+ Ok(0)
}
fn integration_uninstall(args: &[String]) -> std::io::Result<i32> {
- let Some(target) = args.first().map(|arg| arg.as_str()) else {
- eprintln!("usage: herdr integration uninstall <pi|claude|codex|opencode>");
+ let Some(target) = parse_integration_target(args, "uninstall")? else {
return Ok(2);
};
+
+ let response = send_request(&Request {
+ id: "cli:integration:uninstall".into(),
+ method: Method::IntegrationUninstall(IntegrationUninstallParams { target }),
+ })?;
+
+ if let Some(error) = response.get("error") {
+ eprintln!("{}", serde_json::to_string(error).unwrap());
+ return Ok(1);
+ }
+
+ let Some(messages) = response["result"]["details"]["messages"].as_array() else {
+ eprintln!("invalid integration uninstall response");
+ return Ok(1);
+ };
+
+ for message in messages.iter().filter_map(|entry| entry.as_str()) {
+ println!("{message}");
+ }
+
+ Ok(0)
+}
+
+fn parse_integration_target(
+ args: &[String],
+ action: &str,
+) -> std::io::Result<Option<IntegrationTarget>> {
+ let Some(target) = args.first().map(|arg| arg.as_str()) else {
+ eprintln!("usage: herdr integration {action} <pi|claude|codex|opencode>");
+ return Ok(None);
+ };
if args.len() != 1 {
- eprintln!("usage: herdr integration uninstall <pi|claude|codex|opencode>");
- return Ok(2);
+ eprintln!("usage: herdr integration {action} <pi|claude|codex|opencode>");
+ return Ok(None);
}
- match target {
- "pi" => {
- let result = crate::integration::uninstall_pi()?;
- if result.removed_extension {
- println!(
- "removed pi integration extension at {}",
- result.extension_path.display()
- );
- } else {
- println!(
- "no pi integration extension found at {}",
- result.extension_path.display()
- );
- }
- Ok(0)
- }
- "claude" => {
- let result = crate::integration::uninstall_claude()?;
- if result.removed_hook_file {
- println!("removed claude hook at {}", result.hook_path.display());
- } else {
- println!("no claude hook found at {}", result.hook_path.display());
- }
- if result.updated_settings {
- println!(
- "removed herdr claude hook entries from {}",
- result.settings_path.display()
- );
- } else {
- println!(
- "no herdr claude hook entries found in {}",
- result.settings_path.display()
- );
- }
- Ok(0)
- }
- "codex" => {
- let result = crate::integration::uninstall_codex()?;
- if result.removed_hook_file {
- println!("removed codex hook at {}", result.hook_path.display());
- } else {
- println!("no codex hook found at {}", result.hook_path.display());
- }
- if result.updated_hooks {
- println!(
- "removed herdr codex hook entries from {}",
- result.hooks_path.display()
- );
- } else {
- println!(
- "no herdr codex hook entries found in {}",
- result.hooks_path.display()
- );
- }
- println!(
- "left codex config unchanged at {}",
- result.config_path.display()
- );
- Ok(0)
- }
- "opencode" => {
- let result = crate::integration::uninstall_opencode()?;
- if result.removed_plugin {
- println!(
- "removed opencode integration plugin at {}",
- result.plugin_path.display()
- );
- } else {
- println!(
- "no opencode integration plugin found at {}",
- result.plugin_path.display()
- );
- }
- Ok(0)
- }
+ let parsed = match target {
+ "pi" => IntegrationTarget::Pi,
+ "claude" => IntegrationTarget::Claude,
+ "codex" => IntegrationTarget::Codex,
+ "opencode" => IntegrationTarget::Opencode,
_ => {
eprintln!("unknown integration target: {target}");
eprintln!("currently supported: pi, claude, codex, opencode");
- Ok(2)
+ return Ok(None);
}
- }
+ };
+
+ Ok(Some(parsed))
}
fn wait_output(args: &[String]) -> std::io::Result<i32> {
@@ -1071,6 +1037,12 @@ fn parse_u64_flag(flag: &str, value: &str) -> std::io::Result<u64> {
.map_err(|_| std::io::Error::other(format!("invalid value for {flag}: {value}")))
}
+fn print_server_help() {
+ eprintln!("herdr server commands:");
+ eprintln!(" herdr server run as headless server");
+ eprintln!(" herdr server stop stop the running server via the api socket");
+}
+
fn print_workspace_help() {
eprintln!("herdr workspace commands:");
eprintln!(" herdr workspace list");