aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cli.rs15
-rw-r--r--src/cli/completion.rs2
-rw-r--r--src/cli/spec.rs14
-rw-r--r--src/main.rs4
-rw-r--r--src/platform/mod.rs8
-rw-r--r--src/platform/unix_common.rs19
6 files changed, 58 insertions, 4 deletions
diff --git a/src/cli.rs b/src/cli.rs
index ac97edc5..b432528d 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -8,6 +8,20 @@ use crate::api::schema::{
ReadSource, Request, SplitDirection,
};
+macro_rules! print {
+ ($($arg:tt)*) => {{
+ crate::platform::begin_cli_output();
+ std::print!($($arg)*);
+ }};
+}
+
+macro_rules! println {
+ ($($arg:tt)*) => {{
+ crate::platform::begin_cli_output();
+ std::println!($($arg)*);
+ }};
+}
+
mod agent;
mod api;
mod completion;
@@ -194,6 +208,7 @@ fn channel_set(args: &[String]) -> std::io::Result<i32> {
ChannelSetInstallAction::RunSelfUpdate => {}
}
+ crate::platform::end_cli_output();
if let Err(err) = crate::update::self_update(crate::update::SelfUpdateOptions::default()) {
eprintln!("update failed: {err}");
eprintln!("Run `herdr update` to retry.");
diff --git a/src/cli/completion.rs b/src/cli/completion.rs
index f049175d..02391ec8 100644
--- a/src/cli/completion.rs
+++ b/src/cli/completion.rs
@@ -35,8 +35,10 @@ pub(super) fn run_completion_command(args: &[String]) -> std::io::Result<i32> {
let script = String::from_utf8(output).map_err(|err| {
std::io::Error::new(std::io::ErrorKind::InvalidData, err.utf8_error())
})?;
+ crate::platform::begin_cli_output();
std::io::stdout().write_all(space_separated_zsh_long_options(&script).as_bytes())?;
} else {
+ crate::platform::begin_cli_output();
generate(shell, &mut command, "herdr", &mut std::io::stdout());
}
Ok(0)
diff --git a/src/cli/spec.rs b/src/cli/spec.rs
index b2b6cb03..134bf0bf 100644
--- a/src/cli/spec.rs
+++ b/src/cli/spec.rs
@@ -64,10 +64,14 @@ fn configure_help(command: Command, depth: usize) -> Command {
pub(super) fn print_requested_help(args: &[String]) -> std::io::Result<bool> {
let mut stdout = std::io::stdout().lock();
- write_requested_help(args, &mut stdout)
+ write_requested_help(args, &mut stdout, crate::platform::begin_cli_output)
}
-fn write_requested_help(args: &[String], output: &mut impl Write) -> std::io::Result<bool> {
+fn write_requested_help(
+ args: &[String],
+ output: &mut impl Write,
+ before_write: impl FnOnce(),
+) -> std::io::Result<bool> {
let Some(help_index) = args
.iter()
.position(|arg| matches!(arg.as_str(), "--help" | "-h"))
@@ -99,6 +103,7 @@ fn write_requested_help(args: &[String], output: &mut impl Write) -> std::io::Re
}
selected.set_bin_name(path.join(" "));
+ before_write();
selected.write_long_help(&mut *output)?;
writeln!(output)?;
Ok(true)
@@ -1083,7 +1088,7 @@ mod tests {
args.push(flag.to_string());
let mut output = Vec::new();
assert!(
- super::write_requested_help(&args, &mut output).unwrap(),
+ super::write_requested_help(&args, &mut output, || {}).unwrap(),
"help was not handled for herdr {} {flag}",
path.join(" ")
);
@@ -1197,6 +1202,7 @@ mod tests {
"--help".to_string(),
],
&mut help,
+ || {},
)
.unwrap();
assert!(String::from_utf8(help)
@@ -1304,7 +1310,7 @@ mod tests {
args.push("--help".to_string());
let mut output = Vec::new();
assert!(
- super::write_requested_help(&args, &mut output).unwrap(),
+ super::write_requested_help(&args, &mut output, || {}).unwrap(),
"help was not handled for herdr {}",
path.join(" ")
);
diff --git a/src/main.rs b/src/main.rs
index 36b1d021..56dbb8df 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -614,6 +614,7 @@ fn main() -> io::Result<()> {
}
if args.iter().any(|a| a == "--help" || a == "-h") {
+ platform::begin_cli_output();
println!("herdr — terminal workspace manager for AI coding agents");
println!();
println!("Usage: herdr [options]");
@@ -729,16 +730,19 @@ fn main() -> io::Result<()> {
}
if args.iter().any(|a| a == "--version" || a == "-V") {
+ platform::begin_cli_output();
println!("herdr {}", crate::build_info::version());
return Ok(());
}
if args.iter().any(|a| a == "--default-config") {
+ platform::begin_cli_output();
print!("{DEFAULT_CONFIG}");
return Ok(());
}
if args.iter().any(|a| a == "--skill") {
+ platform::begin_cli_output();
print!("{SKILL}");
return Ok(());
}
diff --git a/src/platform/mod.rs b/src/platform/mod.rs
index e3f9c7f4..a63b7565 100644
--- a/src/platform/mod.rs
+++ b/src/platform/mod.rs
@@ -198,6 +198,14 @@ pub(crate) struct RemoteSshConfigPaths {
#[cfg(unix)]
mod unix_common;
+#[cfg(unix)]
+pub(crate) use unix_common::{begin_cli_output, end_cli_output};
+
+#[cfg(not(unix))]
+pub(crate) fn begin_cli_output() {}
+
+#[cfg(not(unix))]
+pub(crate) fn end_cli_output() {}
#[cfg(target_os = "linux")]
mod linux;
diff --git a/src/platform/unix_common.rs b/src/platform/unix_common.rs
index fa7b6d20..b997049f 100644
--- a/src/platform/unix_common.rs
+++ b/src/platform/unix_common.rs
@@ -1,5 +1,24 @@
use std::path::{Path, PathBuf};
+fn set_sigpipe_disposition(handler: libc::sighandler_t) {
+ let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
+ action.sa_sigaction = handler;
+ unsafe {
+ libc::sigemptyset(&mut action.sa_mask);
+ // Rust starts with SIGPIPE ignored. If this best-effort transition
+ // fails, stdout retains the existing Rust behavior.
+ libc::sigaction(libc::SIGPIPE, &action, std::ptr::null_mut());
+ }
+}
+
+pub(crate) fn begin_cli_output() {
+ set_sigpipe_disposition(libc::SIG_DFL);
+}
+
+pub(crate) fn end_cli_output() {
+ set_sigpipe_disposition(libc::SIG_IGN);
+}
+
pub(crate) fn remote_ssh_config_paths() -> super::RemoteSshConfigPaths {
super::RemoteSshConfigPaths {
user_config: std::env::var_os("HOME")