From cb2e17a4a56be1434a42532d37c99fe8d7b60407 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Sun, 26 Jul 2026 14:29:21 +0300 Subject: [PATCH] feat: print bundled agent skill and hint next cli steps --- src/cli/spec.rs | 42 ++++++++++++++++++++++++++++++++++++++++-- src/main.rs | 13 ++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/cli/spec.rs b/src/cli/spec.rs index f3d0d271..1f2e85a4 100644 --- a/src/cli/spec.rs +++ b/src/cli/spec.rs @@ -18,6 +18,7 @@ pub(super) fn command() -> Command { ) .arg(flag("handoff").help("Opt into live handoff for update or remote attach")) .arg(flag("default-config").help("Print default configuration and exit")) + .arg(flag("skill").help("Print the agent skill file and exit")) .arg( Arg::new("version") .short('V') @@ -426,7 +427,7 @@ fn agent_command() -> Command { .last(true), ) .after_help( - "The pane must be at its interactive shell prompt. Success means the expected agent was detected in the same terminal and is ready for input.", + "The pane must be at its interactive shell prompt. Success means the expected agent was detected in the same terminal and is ready for input.\n\nnext: herdr agent prompt --wait", ), ) .subcommand( @@ -568,7 +569,10 @@ fn pane_command() -> Command { Command::new("send-text") .about("Send literal text to a pane") .arg(required("pane_id", "PANE_ID")) - .arg(required("text", "TEXT")), + .arg(required("text", "TEXT")) + .after_help( + "next: herdr pane run sends text and Enter in one call", + ), ) .subcommand( Command::new("send-keys") @@ -1269,6 +1273,40 @@ mod tests { .any(|arg| arg.get_id() == "agent_args")); } + fn long_help(path: &[&str]) -> String { + let mut args = vec!["herdr".to_string()]; + args.extend(path.iter().map(|segment| segment.to_string())); + args.push("--help".to_string()); + let mut output = Vec::new(); + assert!( + super::write_requested_help(&args, &mut output).unwrap(), + "help was not handled for herdr {}", + path.join(" ") + ); + String::from_utf8(output).unwrap() + } + + #[test] + fn next_step_hints_render_without_replacing_existing_after_help() { + let agent_start = long_help(&["agent", "start"]); + assert!( + agent_start.contains("The pane must be at its interactive shell prompt."), + "agent start dropped its existing after_help: {agent_start}" + ); + assert!( + agent_start.contains("next: herdr agent prompt --wait"), + "agent start is missing its next-step hint: {agent_start}" + ); + + let pane_send_text = long_help(&["pane", "send-text"]); + assert!( + pane_send_text.contains( + "next: herdr pane run sends text and Enter in one call" + ), + "pane send-text is missing its next-step hint: {pane_send_text}" + ); + } + #[test] fn completion_generation_succeeds_for_every_supported_shell() { for shell in [ diff --git a/src/main.rs b/src/main.rs index 3f1f7930..65dc7c11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -424,6 +424,9 @@ pane_history = false # scrollback_limit_bytes = 10000000 "##; +// Bundled at build time so the printed skill always matches this binary's release. +const SKILL: &str = include_str!("../SKILL.md"); + fn should_block_nested(config: &config::Config) -> bool { should_block_nested_for_env(config, std::env::var(HERDR_ENV_VAR).ok().as_deref()) } @@ -477,7 +480,7 @@ fn main() -> io::Result<()> { && !args.iter().any(|a| { matches!( a.as_str(), - "--help" | "-h" | "--version" | "-V" | "--default-config" + "--help" | "-h" | "--version" | "-V" | "--default-config" | "--skill" ) }) { @@ -637,6 +640,7 @@ fn main() -> io::Result<()> { println!(" Keybindings for --remote app attach (default: local)"); println!(" --handoff Opt into live handoff for update or remote attach"); println!(" --default-config Print default configuration and exit"); + println!(" --skill Print the agent skill file and exit"); println!(" --version, -V Print version and exit"); println!(" --help, -h Show this help"); println!(); @@ -644,6 +648,7 @@ fn main() -> io::Result<()> { println!("Logs: {}", logging::help_log_paths_summary()); println!("Env: HERDR_CONFIG_PATH overrides config file path"); println!("Home: https://herdr.dev"); + println!("Skill: herdr --skill prints agent instructions for driving herdr from a pane"); return Ok(()); } @@ -657,6 +662,11 @@ fn main() -> io::Result<()> { return Ok(()); } + if args.iter().any(|a| a == "--skill") { + print!("{SKILL}"); + return Ok(()); + } + // Reject unknown flags let known_flags = [ "--no-session", @@ -666,6 +676,7 @@ fn main() -> io::Result<()> { "--version", "-V", "--default-config", + "--skill", "--help", "-h", ];