From 492a63f634a3baea05ee3109f8487b21aec86bf1 Mon Sep 17 00:00:00 2001 From: Trae Robrock Date: Tue, 2 Dec 2025 16:40:52 -0500 Subject: [PATCH 1/3] Allow customizing the new line keybinding in the CLI Signed-off-by: Trae Robrock --- crates/goose-cli/src/session/input.rs | 60 ++++++++++++++++++++++++-- crates/goose-cli/src/session/prompt.rs | 5 ++- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/crates/goose-cli/src/session/input.rs b/crates/goose-cli/src/session/input.rs index 34d385291f26..3d9cb8a98b8d 100644 --- a/crates/goose-cli/src/session/input.rs +++ b/crates/goose-cli/src/session/input.rs @@ -54,12 +54,24 @@ impl rustyline::ConditionalEventHandler for CtrlCHandler { } } +pub fn get_newline_key() -> char { + std::env::var("GOOSE_CLI_NEWLINE_KEY") + .ok() + .and_then(|s| s.chars().next()) + .map(|c| c.to_ascii_lowercase()) + .unwrap_or('j') +} + pub fn get_input( editor: &mut Editor, ) -> Result { - // Ensure Ctrl-J binding is set for newlines + // Ensure Ctrl+ binding is set for newlines (configurable via GOOSE_CLI_NEWLINE_KEY) + let newline_key = get_newline_key(); editor.bind_sequence( - rustyline::KeyEvent(rustyline::KeyCode::Char('j'), rustyline::Modifiers::CTRL), + rustyline::KeyEvent( + rustyline::KeyCode::Char(newline_key), + rustyline::Modifiers::CTRL, + ), rustyline::EventHandler::Simple(rustyline::Cmd::Newline), ); @@ -295,6 +307,7 @@ fn get_input_prompt_string() -> String { } fn print_help() { + let newline_key = get_newline_key().to_ascii_uppercase(); println!( "Available commands: /exit or /quit - Exit the session @@ -319,7 +332,7 @@ fn print_help() { Navigation: Ctrl+C - Clear current line if text is entered, otherwise exit the session -Ctrl+J - Add a newline +Ctrl+{newline_key} - Add a newline (configurable via GOOSE_CLI_NEWLINE_KEY) Up/Down arrows - Navigate through command history" ); } @@ -588,4 +601,45 @@ mod tests { } } } + + #[test] + fn test_get_newline_key_default() { + // Clear the env var to test default behavior + std::env::remove_var("GOOSE_CLI_NEWLINE_KEY"); + assert_eq!(get_newline_key(), 'j'); + } + + #[test] + fn test_get_newline_key_custom() { + // Test setting a custom key + std::env::set_var("GOOSE_CLI_NEWLINE_KEY", "o"); + assert_eq!(get_newline_key(), 'o'); + + // Test uppercase is converted to lowercase + std::env::set_var("GOOSE_CLI_NEWLINE_KEY", "N"); + assert_eq!(get_newline_key(), 'n'); + + // Clean up + std::env::remove_var("GOOSE_CLI_NEWLINE_KEY"); + } + + #[test] + fn test_get_newline_key_empty_string() { + // Test empty string falls back to default + std::env::set_var("GOOSE_CLI_NEWLINE_KEY", ""); + assert_eq!(get_newline_key(), 'j'); + + // Clean up + std::env::remove_var("GOOSE_CLI_NEWLINE_KEY"); + } + + #[test] + fn test_get_newline_key_first_char_only() { + // Test only first character is used + std::env::set_var("GOOSE_CLI_NEWLINE_KEY", "abc"); + assert_eq!(get_newline_key(), 'a'); + + // Clean up + std::env::remove_var("GOOSE_CLI_NEWLINE_KEY"); + } } diff --git a/crates/goose-cli/src/session/prompt.rs b/crates/goose-cli/src/session/prompt.rs index 9c0db7bc1a33..695b28aadf81 100644 --- a/crates/goose-cli/src/session/prompt.rs +++ b/crates/goose-cli/src/session/prompt.rs @@ -1,6 +1,7 @@ /// Returns a system prompt extension that explains CLI-specific functionality pub fn get_cli_prompt() -> String { - String::from( + let newline_key = super::input::get_newline_key().to_ascii_uppercase(); + format!( "You are being accessed through a command-line interface. The following slash commands are available - you can let the user know about them if they need help: @@ -10,7 +11,7 @@ pub fn get_cli_prompt() -> String { Additional keyboard shortcuts: - Ctrl+C - Interrupt the current interaction (resets to before the interrupted request) -- Ctrl+J - Add a newline +- Ctrl+{newline_key} - Add a newline - Up/Down arrows - Navigate command history" ) } From c86007ddb3107f8e3e31a4cf308c45732e30d87c Mon Sep 17 00:00:00 2001 From: Trae Robrock Date: Wed, 3 Dec 2025 10:28:12 -0500 Subject: [PATCH 2/3] Use serial for env var tests Signed-off-by: Trae Robrock --- crates/goose-cli/src/session/input.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/goose-cli/src/session/input.rs b/crates/goose-cli/src/session/input.rs index 3d9cb8a98b8d..58ba5e56528d 100644 --- a/crates/goose-cli/src/session/input.rs +++ b/crates/goose-cli/src/session/input.rs @@ -340,6 +340,7 @@ Up/Down arrows - Navigate through command history" #[cfg(test)] mod tests { use super::*; + use serial_test::serial; #[test] fn test_handle_slash_command() { @@ -603,6 +604,7 @@ mod tests { } #[test] + #[serial] fn test_get_newline_key_default() { // Clear the env var to test default behavior std::env::remove_var("GOOSE_CLI_NEWLINE_KEY"); @@ -610,6 +612,7 @@ mod tests { } #[test] + #[serial] fn test_get_newline_key_custom() { // Test setting a custom key std::env::set_var("GOOSE_CLI_NEWLINE_KEY", "o"); @@ -624,6 +627,7 @@ mod tests { } #[test] + #[serial] fn test_get_newline_key_empty_string() { // Test empty string falls back to default std::env::set_var("GOOSE_CLI_NEWLINE_KEY", ""); @@ -634,6 +638,7 @@ mod tests { } #[test] + #[serial] fn test_get_newline_key_first_char_only() { // Test only first character is used std::env::set_var("GOOSE_CLI_NEWLINE_KEY", "abc"); From 2b915af25f5ce0a7da0191ed1e614981a2b20900 Mon Sep 17 00:00:00 2001 From: Trae Robrock Date: Wed, 17 Dec 2025 14:13:24 -0500 Subject: [PATCH 3/3] Clean up from PR review Signed-off-by: Trae Robrock --- crates/goose-cli/src/session/input.rs | 51 ++------------------------- 1 file changed, 3 insertions(+), 48 deletions(-) diff --git a/crates/goose-cli/src/session/input.rs b/crates/goose-cli/src/session/input.rs index 58ba5e56528d..900f95dbe310 100644 --- a/crates/goose-cli/src/session/input.rs +++ b/crates/goose-cli/src/session/input.rs @@ -1,5 +1,6 @@ use super::completion::GooseCompleter; use anyhow::Result; +use goose::config::Config; use rustyline::Editor; use shlex; use std::collections::HashMap; @@ -55,7 +56,8 @@ impl rustyline::ConditionalEventHandler for CtrlCHandler { } pub fn get_newline_key() -> char { - std::env::var("GOOSE_CLI_NEWLINE_KEY") + Config::global() + .get_param::("GOOSE_CLI_NEWLINE_KEY") .ok() .and_then(|s| s.chars().next()) .map(|c| c.to_ascii_lowercase()) @@ -65,7 +67,6 @@ pub fn get_newline_key() -> char { pub fn get_input( editor: &mut Editor, ) -> Result { - // Ensure Ctrl+ binding is set for newlines (configurable via GOOSE_CLI_NEWLINE_KEY) let newline_key = get_newline_key(); editor.bind_sequence( rustyline::KeyEvent( @@ -340,7 +341,6 @@ Up/Down arrows - Navigate through command history" #[cfg(test)] mod tests { use super::*; - use serial_test::serial; #[test] fn test_handle_slash_command() { @@ -602,49 +602,4 @@ mod tests { } } } - - #[test] - #[serial] - fn test_get_newline_key_default() { - // Clear the env var to test default behavior - std::env::remove_var("GOOSE_CLI_NEWLINE_KEY"); - assert_eq!(get_newline_key(), 'j'); - } - - #[test] - #[serial] - fn test_get_newline_key_custom() { - // Test setting a custom key - std::env::set_var("GOOSE_CLI_NEWLINE_KEY", "o"); - assert_eq!(get_newline_key(), 'o'); - - // Test uppercase is converted to lowercase - std::env::set_var("GOOSE_CLI_NEWLINE_KEY", "N"); - assert_eq!(get_newline_key(), 'n'); - - // Clean up - std::env::remove_var("GOOSE_CLI_NEWLINE_KEY"); - } - - #[test] - #[serial] - fn test_get_newline_key_empty_string() { - // Test empty string falls back to default - std::env::set_var("GOOSE_CLI_NEWLINE_KEY", ""); - assert_eq!(get_newline_key(), 'j'); - - // Clean up - std::env::remove_var("GOOSE_CLI_NEWLINE_KEY"); - } - - #[test] - #[serial] - fn test_get_newline_key_first_char_only() { - // Test only first character is used - std::env::set_var("GOOSE_CLI_NEWLINE_KEY", "abc"); - assert_eq!(get_newline_key(), 'a'); - - // Clean up - std::env::remove_var("GOOSE_CLI_NEWLINE_KEY"); - } }