diff --git a/crates/goose-cli/src/cli.rs b/crates/goose-cli/src/cli.rs index bb9a0c12df..e7f0ef8584 100644 --- a/crates/goose-cli/src/cli.rs +++ b/crates/goose-cli/src/cli.rs @@ -35,14 +35,6 @@ use std::io::Read; use std::path::PathBuf; use tracing::warn; -fn non_empty_string(s: &str) -> Result { - if s.trim().is_empty() { - Err("Prompt cannot be empty".to_string()) - } else { - Ok(s.to_string()) - } -} - #[derive(Parser)] #[command(author, version, display_name = "", about, long_about = None)] struct Cli { @@ -871,6 +863,14 @@ enum TermCommand { /// Shell type (bash, zsh, fish, powershell) #[arg(value_enum)] shell: Shell, + + /// Enable command_not_found_handler (sends unknown commands to goose) + #[arg( + long = "with-command-not-found", + help = "Enable command_not_found_handler for automatic goose invocation", + long_help = "When enabled, any command not found will be automatically sent to goose for interpretation. Only supported for zsh and bash." + )] + with_command_not_found: bool, }, /// Log a shell command (called by shell hook) @@ -885,13 +885,14 @@ enum TermCommand { about = "Run a prompt in the terminal session", long_about = "Run a prompt in the terminal-integrated session.\n\n\ Examples:\n \ - goose term run \"list files in this directory\"\n \ - goose term run \"create a python script that prints hello world\"" + goose term run list files in this directory\n \ + goose term run create a python script that prints hello world\n \ + gt list files # using alias" )] Run { - /// The prompt to send to goose - #[arg(value_parser = non_empty_string)] - prompt: String, + /// The prompt to send to goose (multiple words allowed without quotes) + #[arg(required = true, num_args = 1..)] + prompt: Vec, }, /// Print session info for prompt integration @@ -1470,14 +1471,17 @@ pub async fn cli() -> anyhow::Result<()> { } Some(Command::Term { command }) => { match command { - TermCommand::Init { shell } => { + TermCommand::Init { + shell, + with_command_not_found, + } => { let shell_str = match shell { Shell::Bash => "bash", Shell::Zsh => "zsh", Shell::Fish => "fish", Shell::Powershell => "powershell", }; - handle_term_init(shell_str)?; + handle_term_init(shell_str, with_command_not_found)?; } TermCommand::Log { command } => { handle_term_log(command).await?; diff --git a/crates/goose-cli/src/commands/term.rs b/crates/goose-cli/src/commands/term.rs index 6f6e628ac9..ea3a13fbea 100644 --- a/crates/goose-cli/src/commands/term.rs +++ b/crates/goose-cli/src/commands/term.rs @@ -33,7 +33,7 @@ async fn ensure_terminal_session( } /// Handle `goose term init ` - print shell initialization script -pub fn handle_term_init(shell: &str) -> Result<()> { +pub fn handle_term_init(shell: &str, with_command_not_found: bool) -> Result<()> { let terminal_id = Uuid::new_v4().to_string(); // Get the path to the current goose binary @@ -41,6 +41,34 @@ pub fn handle_term_init(shell: &str) -> Result<()> { .map(|p| p.to_string_lossy().into_owned()) .unwrap_or_else(|_| "goose".to_string()); + let command_not_found_handler = if with_command_not_found { + match shell.to_lowercase().as_str() { + "bash" => format!( + r#" + +# Command not found handler - sends unknown commands to goose +command_not_found_handle() {{ + echo "🪿 Command '$1' not found. Asking goose..." + '{goose_bin}' term run "$@" + return 0 +}}"# + ), + "zsh" => format!( + r#" + +# Command not found handler - sends unknown commands to goose +command_not_found_handler() {{ + echo "🪿 Command '$1' not found. Asking goose..." + '{goose_bin}' term run "$@" + return 0 +}}"# + ), + _ => String::new(), + } + } else { + String::new() + }; + let script = match shell.to_lowercase().as_str() { "bash" => { format!( @@ -58,7 +86,7 @@ goose_preexec() {{ if [[ -z "$goose_preexec_installed" ]]; then goose_preexec_installed=1 trap 'goose_preexec "$BASH_COMMAND"' DEBUG -fi"# +fi{command_not_found_handler}"# ) } "zsh" => { @@ -81,7 +109,7 @@ add-zsh-hook preexec goose_preexec if [[ -z "$GOOSE_PROMPT_INSTALLED" ]]; then export GOOSE_PROMPT_INSTALLED=1 PROMPT='%F{{cyan}}🪿%f '$PROMPT -fi"# +fi{command_not_found_handler}"# ) } "fish" => { @@ -140,7 +168,8 @@ pub async fn handle_term_log(command: String) -> Result<()> { } /// Handle `goose term run ` - run a prompt in the terminal session -pub async fn handle_term_run(prompt: String) -> Result<()> { +pub async fn handle_term_run(prompt: Vec) -> Result<()> { + let prompt = prompt.join(" "); let terminal_id = std::env::var("GOOSE_TERMINAL_ID").map_err(|_| { anyhow!( "GOOSE_TERMINAL_ID not set.\n\n\