From 96d1394fbd502d22fc07b03e18963a923fc640de Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Fri, 1 Aug 2025 12:13:33 +0200 Subject: [PATCH 1/4] Suppress ansi in non terminal situations --- .gitignore | 2 + Cargo.lock | 11 +- crates/goose-cli/Cargo.toml | 1 + crates/goose-cli/src/recipes/print_recipe.rs | 31 +-- crates/goose-cli/src/session/output.rs | 209 +++++++++++-------- 5 files changed, 146 insertions(+), 108 deletions(-) diff --git a/.gitignore b/.gitignore index 8c6129ab3b22..f9edb850c007 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,5 @@ do_not_version/ /ui/desktop/src/bin/goose-scheduler-executor /ui/desktop/src/bin/goose /.env + +/working_dir diff --git a/Cargo.lock b/Cargo.lock index b434baefb901..c6ce077fa667 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3344,6 +3344,7 @@ dependencies = [ "goose-mcp", "http 1.2.0", "indicatif", + "is-terminal", "jsonschema", "mcp-client", "mcp-core", @@ -3578,9 +3579,9 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -4180,11 +4181,11 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi 0.5.2", "libc", "windows-sys 0.59.0", ] diff --git a/crates/goose-cli/Cargo.toml b/crates/goose-cli/Cargo.toml index bb50020858c0..c48fad3e7cac 100644 --- a/crates/goose-cli/Cargo.toml +++ b/crates/goose-cli/Cargo.toml @@ -56,6 +56,7 @@ http = "1.0" webbrowser = "1.0" indicatif = "0.17.11" tokio-util = "0.7.15" +is-terminal = "0.4.16" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["wincred"] } diff --git a/crates/goose-cli/src/recipes/print_recipe.rs b/crates/goose-cli/src/recipes/print_recipe.rs index be4a2339875f..5406f3210f37 100644 --- a/crates/goose-cli/src/recipes/print_recipe.rs +++ b/crates/goose-cli/src/recipes/print_recipe.rs @@ -1,26 +1,27 @@ use std::collections::HashMap; +use crate::g_println; use console::style; use goose::recipe::{Recipe, BUILT_IN_RECIPE_DIR_PARAM}; pub fn print_recipe_explanation(recipe: &Recipe) { - println!( + g_println!( "{} {}", style("🔍 Loading recipe:").bold().green(), style(&recipe.title).green() ); - println!("{}", style("📄 Description:").bold()); - println!(" {}", recipe.description); + g_println!("{}", style("📄 Description:").bold()); + g_println!(" {}", recipe.description); if let Some(params) = &recipe.parameters { if !params.is_empty() { - println!("{}", style("⚙️ Recipe Parameters:").bold()); + g_println!("{}", style("⚙️ Recipe Parameters:").bold()); for param in params { let default_display = match ¶m.default { Some(val) => format!(" (default: {})", val), None => String::new(), }; - println!( + g_println!( " - {} ({}, {}){}: {}", style(¶m.key).cyan(), param.input_type, @@ -40,7 +41,7 @@ pub fn print_parameters_with_values(params: HashMap) { } else { "" }; - println!(" {}{}: {}", key, label, value); + g_println!(" {}{}: {}", key, label, value); } } @@ -49,26 +50,26 @@ pub fn print_required_parameters_for_template( missing_params: Vec, ) { if !params_for_template.is_empty() { - println!( + g_println!( "{}", style("📥 Parameters used to load this recipe:").bold() ); print_parameters_with_values(params_for_template) } if !missing_params.is_empty() { - println!( + g_println!( "{}", style("🔴 Missing parameters in the command line if you want to run the recipe:") .bold() ); for param in missing_params.iter() { - println!(" - {}", param); + g_println!(" - {}", param); } - println!( + g_println!( "📩 {}:", style("Please provide the following parameters in the command line if you want to run the recipe:").bold() ); - println!(" {}", missing_parameters_command_line(missing_params)); + g_println!(" {}", missing_parameters_command_line(missing_params)); } } @@ -81,16 +82,16 @@ pub fn missing_parameters_command_line(missing_params: Vec) -> String { } pub fn print_recipe_info(recipe: &Recipe, params: Vec<(String, String)>) { - println!( + eprintln!( "{} {}", style("Loading recipe:").green().bold(), style(&recipe.title).green() ); - println!("{} {}", style("Description:").bold(), &recipe.description); + eprintln!("{} {}", style("Description:").bold(), &recipe.description); if !params.is_empty() { - println!("{}", style("Parameters used to load this recipe:").bold()); + eprintln!("{}", style("Parameters used to load this recipe:").bold()); print_parameters_with_values(params.into_iter().collect()); } - println!(); + eprintln!(); } diff --git a/crates/goose-cli/src/session/output.rs b/crates/goose-cli/src/session/output.rs index 3b0420a30bac..29460a855237 100644 --- a/crates/goose-cli/src/session/output.rs +++ b/crates/goose-cli/src/session/output.rs @@ -11,11 +11,25 @@ use rmcp::model::PromptArgument; use serde_json::Value; use std::cell::RefCell; use std::collections::HashMap; -use std::io::{Error, Write}; +use std::io::{Error, IsTerminal, Write}; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; +// g_println macro removes colors again when we are not on a terminal +#[macro_export] +macro_rules! g_println { + () => { + println!() + }; + ($($arg:tt)*) => { + if is_terminal::IsTerminal::is_terminal(&std::io::stdout()) { + println!($($arg)*) + } else { + println!("{}", console::strip_ansi_codes(&format!($($arg)*)).to_string()) + } + }; +} // Re-export theme for use in main #[derive(Clone, Copy)] pub enum Theme { @@ -131,20 +145,26 @@ thread_local! { } pub fn show_thinking() { - THINKING.with(|t| t.borrow_mut().show()); + if std::io::stdout().is_terminal() { + THINKING.with(|t| t.borrow_mut().show()); + } } pub fn hide_thinking() { - THINKING.with(|t| t.borrow_mut().hide()); + if std::io::stdout().is_terminal() { + THINKING.with(|t| t.borrow_mut().hide()); + } } #[allow(dead_code)] pub fn set_thinking_message(s: &String) { - THINKING.with(|t| { - if let Some(spinner) = t.borrow_mut().spinner.as_mut() { - spinner.set_message(s); - } - }); + if std::io::stdout().is_terminal() { + THINKING.with(|t| { + if let Some(spinner) = t.borrow_mut().spinner.as_mut() { + spinner.set_message(s); + } + }); + } } pub fn render_message(message: &Message, debug: bool) { @@ -156,21 +176,23 @@ pub fn render_message(message: &Message, debug: bool) { MessageContent::ToolRequest(req) => render_tool_request(req, theme, debug), MessageContent::ToolResponse(resp) => render_tool_response(resp, theme, debug), MessageContent::Image(image) => { - println!("Image: [data: {}, type: {}]", image.data, image.mime_type); + g_println!("Image: [data: {}, type: {}]", image.data, image.mime_type); } MessageContent::Thinking(thinking) => { - if std::env::var("GOOSE_CLI_SHOW_THINKING").is_ok() { - println!("\n{}", style("Thinking:").dim().italic()); + if std::env::var("GOOSE_CLI_SHOW_THINKING").is_ok() + && std::io::stdout().is_terminal() + { + g_println!("\n{}", style("Thinking:").dim().italic()); print_markdown(&thinking.thinking, theme); } } MessageContent::RedactedThinking(_) => { // For redacted thinking, print thinking was redacted - println!("\n{}", style("Thinking:").dim().italic()); + g_println!("\n{}", style("Thinking:").dim().italic()); print_markdown("Thinking was redacted", theme); } _ => { - println!("WARNING: Message content type could not be rendered"); + g_println!("WARNING: Message content type could not be rendered"); } } } @@ -183,6 +205,10 @@ pub fn render_text(text: &str, color: Option, dim: bool) { } pub fn render_text_no_newlines(text: &str, color: Option, dim: bool) { + if !std::io::stdout().is_terminal() { + println!("{}", text); + return; + } let mut styled_text = style(text); if dim { styled_text = styled_text.dim(); @@ -196,7 +222,7 @@ pub fn render_text_no_newlines(text: &str, color: Option, dim: bool) { } pub fn render_enter_plan_mode() { - println!( + g_println!( "\n{} {}\n", style("Entering plan mode.").green().bold(), style("You can provide instructions to create a plan and then act on it. To exit early, type /endplan") @@ -206,7 +232,7 @@ pub fn render_enter_plan_mode() { } pub fn render_act_on_plan() { - println!( + g_println!( "\n{}\n", style("Exiting plan mode and acting on the above plan") .green() @@ -215,11 +241,11 @@ pub fn render_act_on_plan() { } pub fn render_exit_plan_mode() { - println!("\n{}\n", style("Exiting plan mode.").green().bold()); + g_println!("\n{}\n", style("Exiting plan mode.").green().bold()); } pub fn goose_mode_message(text: &str) { - println!("\n{}", style(text).yellow(),); + g_println!("\n{}", style(text).yellow(),); } fn render_tool_request(req: &ToolRequest, theme: Theme, debug: bool) { @@ -260,7 +286,7 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) { } if debug { - println!("{:#?}", content); + g_println!("{:#?}", content); } else if let Some(text) = content.as_text() { print_markdown(&text.text, theme); } @@ -271,35 +297,35 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) { } pub fn render_error(message: &str) { - println!("\n {} {}\n", style("error:").red().bold(), message); + g_println!("\n {} {}\n", style("error:").red().bold(), message); } pub fn render_prompts(prompts: &HashMap>) { - println!(); + g_println!(); for (extension, prompts) in prompts { - println!(" {}", style(extension).green()); + g_println!(" {}", style(extension).green()); for prompt in prompts { - println!(" - {}", style(prompt).cyan()); + g_println!(" - {}", style(prompt).cyan()); } } - println!(); + g_println!(); } pub fn render_prompt_info(info: &PromptInfo) { - println!(); + g_println!(); if let Some(ext) = &info.extension { - println!(" {}: {}", style("Extension").green(), ext); + g_println!(" {}: {}", style("Extension").green(), ext); } - println!(" Prompt: {}", style(&info.name).cyan().bold()); + g_println!(" Prompt: {}", style(&info.name).cyan().bold()); if let Some(desc) = &info.description { - println!("\n {}", desc); + g_println!("\n {}", desc); } if let Some(args) = &info.arguments { - println!("\n Arguments:"); + g_println!("\n Arguments:"); for arg in args { let required = arg.required.unwrap_or(false); let req_str = if required { @@ -308,7 +334,7 @@ pub fn render_prompt_info(info: &PromptInfo) { style("(optional)").dim() }; - println!( + g_println!( " {} {} {}", style(&arg.name).yellow(), req_str, @@ -316,53 +342,53 @@ pub fn render_prompt_info(info: &PromptInfo) { ); } } - println!(); + g_println!(); } pub fn render_extension_success(name: &str) { - println!(); - println!( + g_println!(); + g_println!( " {} extension `{}`", style("added").green(), style(name).cyan(), ); - println!(); + g_println!(); } pub fn render_extension_error(name: &str, error: &str) { - println!(); - println!( + g_println!(); + g_println!( " {} to add extension {}", style("failed").red(), style(name).red() ); - println!(); - println!("{}", style(error).dim()); - println!(); + g_println!(); + g_println!("{}", style(error).dim()); + g_println!(); } pub fn render_builtin_success(names: &str) { - println!(); - println!( + g_println!(); + g_println!( " {} builtin{}: {}", style("added").green(), if names.contains(',') { "s" } else { "" }, style(names).cyan() ); - println!(); + g_println!(); } pub fn render_builtin_error(names: &str, error: &str) { - println!(); - println!( + g_println!(); + g_println!( " {} to add builtin{}: {}", style("failed").red(), if names.contains(',') { "s" } else { "" }, style(names).red() ); - println!(); - println!("{}", style(error).dim()); - println!(); + g_println!(); + g_println!("{}", style(error).dim()); + g_println!(); } fn render_text_editor_request(call: &ToolCall, debug: bool) { @@ -370,7 +396,7 @@ fn render_text_editor_request(call: &ToolCall, debug: bool) { // Print path first with special formatting if let Some(Value::String(path)) = call.arguments.get("path") { - println!( + g_println!( "{}: {}", style("path").dim(), style(shorten_path(path, debug)).green() @@ -387,7 +413,7 @@ fn render_text_editor_request(call: &ToolCall, debug: bool) { } print_params(&Value::Object(other_args), 0, debug); } - println!(); + g_println!(); } fn render_shell_request(call: &ToolCall, debug: bool) { @@ -395,7 +421,7 @@ fn render_shell_request(call: &ToolCall, debug: bool) { match call.arguments.get("command") { Some(Value::String(s)) => { - println!("{}: {}", style("command").dim(), style(s).green()); + g_println!("{}: {}", style("command").dim(), style(s).green()); } _ => print_params(&call.arguments, 0, debug), } @@ -406,17 +432,17 @@ fn render_dynamic_task_request(call: &ToolCall, debug: bool) { // Print task_parameters array if let Some(Value::Array(task_parameters)) = call.arguments.get("task_parameters") { - println!("{}:", style("task_parameters").dim()); + g_println!("{}:", style("task_parameters").dim()); for task_param in task_parameters.iter() { - println!(" -"); + g_println!(" -"); if let Some(param_obj) = task_param.as_object() { for (key, value) in param_obj { match value { Value::String(s) => { // For strings, print the full content without truncation - println!(" {}: {}", style(key).dim(), style(s).green()); + g_println!(" {}: {}", style(key).dim(), style(s).green()); } _ => { // For everything else, use print_params @@ -429,13 +455,13 @@ fn render_dynamic_task_request(call: &ToolCall, debug: bool) { } } - println!(); + g_println!(); } fn render_default_request(call: &ToolCall, debug: bool) { print_tool_header(call); print_params(&call.arguments, 0, debug); - println!(); + g_println!(); } // Helper functions @@ -454,8 +480,8 @@ fn print_tool_header(call: &ToolCall) { .magenta() .dim(), ); - println!(); - println!("{}", tool_header); + g_println!(); + g_println!("{}", tool_header); } // Respect NO_COLOR, as https://crates.io/crates/console already does @@ -465,14 +491,18 @@ pub fn env_no_color() -> bool { } fn print_markdown(content: &str, theme: Theme) { - bat::PrettyPrinter::new() - .input(bat::Input::from_bytes(content.as_bytes())) - .theme(theme.as_str()) - .colored_output(env_no_color()) - .language("Markdown") - .wrapping_mode(WrappingMode::NoWrapping(true)) - .print() - .unwrap(); + if std::io::stdout().is_terminal() { + bat::PrettyPrinter::new() + .input(bat::Input::from_bytes(content.as_bytes())) + .theme(theme.as_str()) + .colored_output(env_no_color()) + .language("Markdown") + .wrapping_mode(WrappingMode::NoWrapping(true)) + .print() + .unwrap(); + } else { + println!("{}", content); + } } const INDENT: &str = " "; @@ -492,60 +522,60 @@ fn print_params(value: &Value, depth: usize, debug: bool) { for (key, val) in map { match val { Value::Object(_) => { - println!("{}{}:", indent, style(key).dim()); + g_println!("{}{}:", indent, style(key).dim()); print_params(val, depth + 1, debug); } Value::Array(arr) => { - println!("{}{}:", indent, style(key).dim()); + g_println!("{}{}:", indent, style(key).dim()); for item in arr.iter() { - println!("{}{}- ", indent, INDENT); + g_println!("{}{}- ", indent, INDENT); print_params(item, depth + 2, debug); } } Value::String(s) => { if !debug && s.len() > get_tool_params_max_length() { - println!("{}{}: {}", indent, style(key).dim(), style("...").dim()); + g_println!("{}{}: {}", indent, style(key).dim(), style("...").dim()); } else { - println!("{}{}: {}", indent, style(key).dim(), style(s).green()); + g_println!("{}{}: {}", indent, style(key).dim(), style(s).green()); } } Value::Number(n) => { - println!("{}{}: {}", indent, style(key).dim(), style(n).blue()); + g_println!("{}{}: {}", indent, style(key).dim(), style(n).blue()); } Value::Bool(b) => { - println!("{}{}: {}", indent, style(key).dim(), style(b).blue()); + g_println!("{}{}: {}", indent, style(key).dim(), style(b).blue()); } Value::Null => { - println!("{}{}: {}", indent, style(key).dim(), style("null").dim()); + g_println!("{}{}: {}", indent, style(key).dim(), style("null").dim()); } } } } Value::Array(arr) => { for (i, item) in arr.iter().enumerate() { - println!("{}{}.", indent, i + 1); + g_println!("{}{}.", indent, i + 1); print_params(item, depth + 1, debug); } } Value::String(s) => { if !debug && s.len() > get_tool_params_max_length() { - println!( + g_println!( "{}{}", indent, style(format!("[REDACTED: {} chars]", s.len())).yellow() ); } else { - println!("{}{}", indent, style(s).green()); + g_println!("{}{}", indent, style(s).green()); } } Value::Number(n) => { - println!("{}{}", indent, style(n).yellow()); + g_println!("{}{}", indent, style(n).yellow()); } Value::Bool(b) => { - println!("{}{}", indent, style(b).yellow()); + g_println!("{}{}", indent, style(b).yellow()); } Value::Null => { - println!("{}{}", indent, style("null").dim()); + g_println!("{}{}", indent, style("null").dim()); } } } @@ -619,7 +649,7 @@ pub fn display_session_info( if let Some(provider_inst) = provider_instance { if let Some(lead_worker) = provider_inst.as_lead_worker() { let (lead_model, worker_model) = lead_worker.get_model_info(); - println!( + g_println!( "{} {} {} {} {} {} {}", style(start_session_msg).dim(), style("provider:").dim(), @@ -630,7 +660,7 @@ pub fn display_session_info( style(&worker_model).cyan().dim(), ); } else { - println!( + g_println!( "{} {} {} {} {}", style(start_session_msg).dim(), style("provider:").dim(), @@ -641,7 +671,7 @@ pub fn display_session_info( } } else { // Fallback to original behavior if no provider instance - println!( + g_println!( "{} {} {} {} {}", style(start_session_msg).dim(), style("provider:").dim(), @@ -652,14 +682,14 @@ pub fn display_session_info( } if let Some(session_file) = session_file { - println!( + g_println!( " {} {}", style("logging to").dim(), style(session_file.display()).dim().cyan(), ); } - println!( + g_println!( " {} {}", style("working directory:").dim(), style(std::env::current_dir().unwrap().display()) @@ -669,7 +699,7 @@ pub fn display_session_info( } pub fn display_greeting() { - println!("\nGoose is running! Enter your instructions, or try asking what goose can do.\n"); + g_println!("\nGoose is running! Enter your instructions, or try asking what goose can do.\n"); } /// Display context window usage with both current and session totals @@ -677,7 +707,7 @@ pub fn display_context_usage(total_tokens: usize, context_limit: usize) { use console::style; if context_limit == 0 { - println!("Context: Error - context limit is zero"); + g_println!("Context: Error - context limit is zero"); return; } @@ -705,9 +735,12 @@ pub fn display_context_usage(total_tokens: usize, context_limit: usize) { }; // Print the status line - println!( + g_println!( "Context: {} {}% ({}/{} tokens)", - colored_dots, percentage, total_tokens, context_limit + colored_dots, + percentage, + total_tokens, + context_limit ); } @@ -775,7 +808,7 @@ pub async fn display_cost_usage( ) { if let Some(cost) = estimate_cost_usd(provider, model, input_tokens, output_tokens).await { use console::style; - println!( + eprintln!( "Cost: {} USD ({} tokens: in {}, out {})", style(format!("${:.4}", cost)).cyan(), input_tokens + output_tokens, From cc8cf7672dc13acc45696208453b3ba974512a7b Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Fri, 1 Aug 2025 12:27:15 +0200 Subject: [PATCH 2/4] No newline --- crates/goose-cli/src/session/output.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/goose-cli/src/session/output.rs b/crates/goose-cli/src/session/output.rs index 29460a855237..a6476f698e0d 100644 --- a/crates/goose-cli/src/session/output.rs +++ b/crates/goose-cli/src/session/output.rs @@ -501,7 +501,7 @@ fn print_markdown(content: &str, theme: Theme) { .print() .unwrap(); } else { - println!("{}", content); + print!("{}", content); } } From 77d52890dca69a67d4bf97cc7e71831bdf25528b Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Sun, 3 Aug 2025 15:27:52 +0200 Subject: [PATCH 3/4] Use anstream --- Cargo.lock | 1 + crates/goose-cli/Cargo.toml | 1 + crates/goose-cli/src/recipes/print_recipe.rs | 24 +-- crates/goose-cli/src/session/output.rs | 159 +++++++++---------- 4 files changed, 85 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6ce077fa667..96f10e6ed272 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3327,6 +3327,7 @@ dependencies = [ name = "goose-cli" version = "1.1.0" dependencies = [ + "anstream", "anyhow", "async-trait", "axum", diff --git a/crates/goose-cli/Cargo.toml b/crates/goose-cli/Cargo.toml index c48fad3e7cac..89b4b0e54650 100644 --- a/crates/goose-cli/Cargo.toml +++ b/crates/goose-cli/Cargo.toml @@ -57,6 +57,7 @@ webbrowser = "1.0" indicatif = "0.17.11" tokio-util = "0.7.15" is-terminal = "0.4.16" +anstream = "0.6.18" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["wincred"] } diff --git a/crates/goose-cli/src/recipes/print_recipe.rs b/crates/goose-cli/src/recipes/print_recipe.rs index 5406f3210f37..98609a7412f9 100644 --- a/crates/goose-cli/src/recipes/print_recipe.rs +++ b/crates/goose-cli/src/recipes/print_recipe.rs @@ -1,27 +1,27 @@ use std::collections::HashMap; -use crate::g_println; +use anstream::println; use console::style; use goose::recipe::{Recipe, BUILT_IN_RECIPE_DIR_PARAM}; pub fn print_recipe_explanation(recipe: &Recipe) { - g_println!( + println!( "{} {}", style("🔍 Loading recipe:").bold().green(), style(&recipe.title).green() ); - g_println!("{}", style("📄 Description:").bold()); - g_println!(" {}", recipe.description); + println!("{}", style("📄 Description:").bold()); + println!(" {}", recipe.description); if let Some(params) = &recipe.parameters { if !params.is_empty() { - g_println!("{}", style("⚙️ Recipe Parameters:").bold()); + println!("{}", style("⚙️ Recipe Parameters:").bold()); for param in params { let default_display = match ¶m.default { Some(val) => format!(" (default: {})", val), None => String::new(), }; - g_println!( + println!( " - {} ({}, {}){}: {}", style(¶m.key).cyan(), param.input_type, @@ -41,7 +41,7 @@ pub fn print_parameters_with_values(params: HashMap) { } else { "" }; - g_println!(" {}{}: {}", key, label, value); + println!(" {}{}: {}", key, label, value); } } @@ -50,26 +50,26 @@ pub fn print_required_parameters_for_template( missing_params: Vec, ) { if !params_for_template.is_empty() { - g_println!( + println!( "{}", style("📥 Parameters used to load this recipe:").bold() ); print_parameters_with_values(params_for_template) } if !missing_params.is_empty() { - g_println!( + println!( "{}", style("🔴 Missing parameters in the command line if you want to run the recipe:") .bold() ); for param in missing_params.iter() { - g_println!(" - {}", param); + println!(" - {}", param); } - g_println!( + println!( "📩 {}:", style("Please provide the following parameters in the command line if you want to run the recipe:").bold() ); - g_println!(" {}", missing_parameters_command_line(missing_params)); + println!(" {}", missing_parameters_command_line(missing_params)); } } diff --git a/crates/goose-cli/src/session/output.rs b/crates/goose-cli/src/session/output.rs index a6476f698e0d..8244560184be 100644 --- a/crates/goose-cli/src/session/output.rs +++ b/crates/goose-cli/src/session/output.rs @@ -1,3 +1,4 @@ +use anstream::println; use bat::WrappingMode; use console::{style, Color}; use goose::config::Config; @@ -15,21 +16,6 @@ use std::io::{Error, IsTerminal, Write}; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; - -// g_println macro removes colors again when we are not on a terminal -#[macro_export] -macro_rules! g_println { - () => { - println!() - }; - ($($arg:tt)*) => { - if is_terminal::IsTerminal::is_terminal(&std::io::stdout()) { - println!($($arg)*) - } else { - println!("{}", console::strip_ansi_codes(&format!($($arg)*)).to_string()) - } - }; -} // Re-export theme for use in main #[derive(Clone, Copy)] pub enum Theme { @@ -176,23 +162,23 @@ pub fn render_message(message: &Message, debug: bool) { MessageContent::ToolRequest(req) => render_tool_request(req, theme, debug), MessageContent::ToolResponse(resp) => render_tool_response(resp, theme, debug), MessageContent::Image(image) => { - g_println!("Image: [data: {}, type: {}]", image.data, image.mime_type); + println!("Image: [data: {}, type: {}]", image.data, image.mime_type); } MessageContent::Thinking(thinking) => { if std::env::var("GOOSE_CLI_SHOW_THINKING").is_ok() && std::io::stdout().is_terminal() { - g_println!("\n{}", style("Thinking:").dim().italic()); + println!("\n{}", style("Thinking:").dim().italic()); print_markdown(&thinking.thinking, theme); } } MessageContent::RedactedThinking(_) => { // For redacted thinking, print thinking was redacted - g_println!("\n{}", style("Thinking:").dim().italic()); + println!("\n{}", style("Thinking:").dim().italic()); print_markdown("Thinking was redacted", theme); } _ => { - g_println!("WARNING: Message content type could not be rendered"); + println!("WARNING: Message content type could not be rendered"); } } } @@ -222,7 +208,7 @@ pub fn render_text_no_newlines(text: &str, color: Option, dim: bool) { } pub fn render_enter_plan_mode() { - g_println!( + println!( "\n{} {}\n", style("Entering plan mode.").green().bold(), style("You can provide instructions to create a plan and then act on it. To exit early, type /endplan") @@ -232,7 +218,7 @@ pub fn render_enter_plan_mode() { } pub fn render_act_on_plan() { - g_println!( + println!( "\n{}\n", style("Exiting plan mode and acting on the above plan") .green() @@ -241,11 +227,11 @@ pub fn render_act_on_plan() { } pub fn render_exit_plan_mode() { - g_println!("\n{}\n", style("Exiting plan mode.").green().bold()); + println!("\n{}\n", style("Exiting plan mode.").green().bold()); } pub fn goose_mode_message(text: &str) { - g_println!("\n{}", style(text).yellow(),); + println!("\n{}", style(text).yellow(),); } fn render_tool_request(req: &ToolRequest, theme: Theme, debug: bool) { @@ -286,7 +272,7 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) { } if debug { - g_println!("{:#?}", content); + println!("{:#?}", content); } else if let Some(text) = content.as_text() { print_markdown(&text.text, theme); } @@ -297,35 +283,35 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) { } pub fn render_error(message: &str) { - g_println!("\n {} {}\n", style("error:").red().bold(), message); + println!("\n {} {}\n", style("error:").red().bold(), message); } pub fn render_prompts(prompts: &HashMap>) { - g_println!(); + println!(); for (extension, prompts) in prompts { - g_println!(" {}", style(extension).green()); + println!(" {}", style(extension).green()); for prompt in prompts { - g_println!(" - {}", style(prompt).cyan()); + println!(" - {}", style(prompt).cyan()); } } - g_println!(); + println!(); } pub fn render_prompt_info(info: &PromptInfo) { - g_println!(); + println!(); if let Some(ext) = &info.extension { - g_println!(" {}: {}", style("Extension").green(), ext); + println!(" {}: {}", style("Extension").green(), ext); } - g_println!(" Prompt: {}", style(&info.name).cyan().bold()); + println!(" Prompt: {}", style(&info.name).cyan().bold()); if let Some(desc) = &info.description { - g_println!("\n {}", desc); + println!("\n {}", desc); } if let Some(args) = &info.arguments { - g_println!("\n Arguments:"); + println!("\n Arguments:"); for arg in args { let required = arg.required.unwrap_or(false); let req_str = if required { @@ -334,7 +320,7 @@ pub fn render_prompt_info(info: &PromptInfo) { style("(optional)").dim() }; - g_println!( + println!( " {} {} {}", style(&arg.name).yellow(), req_str, @@ -342,53 +328,53 @@ pub fn render_prompt_info(info: &PromptInfo) { ); } } - g_println!(); + println!(); } pub fn render_extension_success(name: &str) { - g_println!(); - g_println!( + println!(); + println!( " {} extension `{}`", style("added").green(), style(name).cyan(), ); - g_println!(); + println!(); } pub fn render_extension_error(name: &str, error: &str) { - g_println!(); - g_println!( + println!(); + println!( " {} to add extension {}", style("failed").red(), style(name).red() ); - g_println!(); - g_println!("{}", style(error).dim()); - g_println!(); + println!(); + println!("{}", style(error).dim()); + println!(); } pub fn render_builtin_success(names: &str) { - g_println!(); - g_println!( + println!(); + println!( " {} builtin{}: {}", style("added").green(), if names.contains(',') { "s" } else { "" }, style(names).cyan() ); - g_println!(); + println!(); } pub fn render_builtin_error(names: &str, error: &str) { - g_println!(); - g_println!( + println!(); + println!( " {} to add builtin{}: {}", style("failed").red(), if names.contains(',') { "s" } else { "" }, style(names).red() ); - g_println!(); - g_println!("{}", style(error).dim()); - g_println!(); + println!(); + println!("{}", style(error).dim()); + println!(); } fn render_text_editor_request(call: &ToolCall, debug: bool) { @@ -396,7 +382,7 @@ fn render_text_editor_request(call: &ToolCall, debug: bool) { // Print path first with special formatting if let Some(Value::String(path)) = call.arguments.get("path") { - g_println!( + println!( "{}: {}", style("path").dim(), style(shorten_path(path, debug)).green() @@ -413,7 +399,7 @@ fn render_text_editor_request(call: &ToolCall, debug: bool) { } print_params(&Value::Object(other_args), 0, debug); } - g_println!(); + println!(); } fn render_shell_request(call: &ToolCall, debug: bool) { @@ -421,7 +407,7 @@ fn render_shell_request(call: &ToolCall, debug: bool) { match call.arguments.get("command") { Some(Value::String(s)) => { - g_println!("{}: {}", style("command").dim(), style(s).green()); + println!("{}: {}", style("command").dim(), style(s).green()); } _ => print_params(&call.arguments, 0, debug), } @@ -432,17 +418,17 @@ fn render_dynamic_task_request(call: &ToolCall, debug: bool) { // Print task_parameters array if let Some(Value::Array(task_parameters)) = call.arguments.get("task_parameters") { - g_println!("{}:", style("task_parameters").dim()); + println!("{}:", style("task_parameters").dim()); for task_param in task_parameters.iter() { - g_println!(" -"); + println!(" -"); if let Some(param_obj) = task_param.as_object() { for (key, value) in param_obj { match value { Value::String(s) => { // For strings, print the full content without truncation - g_println!(" {}: {}", style(key).dim(), style(s).green()); + println!(" {}: {}", style(key).dim(), style(s).green()); } _ => { // For everything else, use print_params @@ -455,13 +441,13 @@ fn render_dynamic_task_request(call: &ToolCall, debug: bool) { } } - g_println!(); + println!(); } fn render_default_request(call: &ToolCall, debug: bool) { print_tool_header(call); print_params(&call.arguments, 0, debug); - g_println!(); + println!(); } // Helper functions @@ -480,8 +466,8 @@ fn print_tool_header(call: &ToolCall) { .magenta() .dim(), ); - g_println!(); - g_println!("{}", tool_header); + println!(); + println!("{}", tool_header); } // Respect NO_COLOR, as https://crates.io/crates/console already does @@ -522,60 +508,60 @@ fn print_params(value: &Value, depth: usize, debug: bool) { for (key, val) in map { match val { Value::Object(_) => { - g_println!("{}{}:", indent, style(key).dim()); + println!("{}{}:", indent, style(key).dim()); print_params(val, depth + 1, debug); } Value::Array(arr) => { - g_println!("{}{}:", indent, style(key).dim()); + println!("{}{}:", indent, style(key).dim()); for item in arr.iter() { - g_println!("{}{}- ", indent, INDENT); + println!("{}{}- ", indent, INDENT); print_params(item, depth + 2, debug); } } Value::String(s) => { if !debug && s.len() > get_tool_params_max_length() { - g_println!("{}{}: {}", indent, style(key).dim(), style("...").dim()); + println!("{}{}: {}", indent, style(key).dim(), style("...").dim()); } else { - g_println!("{}{}: {}", indent, style(key).dim(), style(s).green()); + println!("{}{}: {}", indent, style(key).dim(), style(s).green()); } } Value::Number(n) => { - g_println!("{}{}: {}", indent, style(key).dim(), style(n).blue()); + println!("{}{}: {}", indent, style(key).dim(), style(n).blue()); } Value::Bool(b) => { - g_println!("{}{}: {}", indent, style(key).dim(), style(b).blue()); + println!("{}{}: {}", indent, style(key).dim(), style(b).blue()); } Value::Null => { - g_println!("{}{}: {}", indent, style(key).dim(), style("null").dim()); + println!("{}{}: {}", indent, style(key).dim(), style("null").dim()); } } } } Value::Array(arr) => { for (i, item) in arr.iter().enumerate() { - g_println!("{}{}.", indent, i + 1); + println!("{}{}.", indent, i + 1); print_params(item, depth + 1, debug); } } Value::String(s) => { if !debug && s.len() > get_tool_params_max_length() { - g_println!( + println!( "{}{}", indent, style(format!("[REDACTED: {} chars]", s.len())).yellow() ); } else { - g_println!("{}{}", indent, style(s).green()); + println!("{}{}", indent, style(s).green()); } } Value::Number(n) => { - g_println!("{}{}", indent, style(n).yellow()); + println!("{}{}", indent, style(n).yellow()); } Value::Bool(b) => { - g_println!("{}{}", indent, style(b).yellow()); + println!("{}{}", indent, style(b).yellow()); } Value::Null => { - g_println!("{}{}", indent, style("null").dim()); + println!("{}{}", indent, style("null").dim()); } } } @@ -649,7 +635,7 @@ pub fn display_session_info( if let Some(provider_inst) = provider_instance { if let Some(lead_worker) = provider_inst.as_lead_worker() { let (lead_model, worker_model) = lead_worker.get_model_info(); - g_println!( + println!( "{} {} {} {} {} {} {}", style(start_session_msg).dim(), style("provider:").dim(), @@ -660,7 +646,7 @@ pub fn display_session_info( style(&worker_model).cyan().dim(), ); } else { - g_println!( + println!( "{} {} {} {} {}", style(start_session_msg).dim(), style("provider:").dim(), @@ -671,7 +657,7 @@ pub fn display_session_info( } } else { // Fallback to original behavior if no provider instance - g_println!( + println!( "{} {} {} {} {}", style(start_session_msg).dim(), style("provider:").dim(), @@ -682,14 +668,14 @@ pub fn display_session_info( } if let Some(session_file) = session_file { - g_println!( + println!( " {} {}", style("logging to").dim(), style(session_file.display()).dim().cyan(), ); } - g_println!( + println!( " {} {}", style("working directory:").dim(), style(std::env::current_dir().unwrap().display()) @@ -699,7 +685,7 @@ pub fn display_session_info( } pub fn display_greeting() { - g_println!("\nGoose is running! Enter your instructions, or try asking what goose can do.\n"); + println!("\nGoose is running! Enter your instructions, or try asking what goose can do.\n"); } /// Display context window usage with both current and session totals @@ -707,7 +693,7 @@ pub fn display_context_usage(total_tokens: usize, context_limit: usize) { use console::style; if context_limit == 0 { - g_println!("Context: Error - context limit is zero"); + println!("Context: Error - context limit is zero"); return; } @@ -735,12 +721,9 @@ pub fn display_context_usage(total_tokens: usize, context_limit: usize) { }; // Print the status line - g_println!( + println!( "Context: {} {}% ({}/{} tokens)", - colored_dots, - percentage, - total_tokens, - context_limit + colored_dots, percentage, total_tokens, context_limit ); } From acf63a41009cb6d37c12b88b3ce4658a69d868fa Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Mon, 4 Aug 2025 12:07:50 +0200 Subject: [PATCH 4/4] Complexity --- crates/goose-cli/src/session/output.rs | 64 ++++++++++---------------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/crates/goose-cli/src/session/output.rs b/crates/goose-cli/src/session/output.rs index 8244560184be..880416b5d0a7 100644 --- a/crates/goose-cli/src/session/output.rs +++ b/crates/goose-cli/src/session/output.rs @@ -299,17 +299,18 @@ pub fn render_prompts(prompts: &HashMap>) { pub fn render_prompt_info(info: &PromptInfo) { println!(); - if let Some(ext) = &info.extension { println!(" {}: {}", style("Extension").green(), ext); } - println!(" Prompt: {}", style(&info.name).cyan().bold()); - if let Some(desc) = &info.description { println!("\n {}", desc); } + render_arguments(info); + println!(); +} +fn render_arguments(info: &PromptInfo) { if let Some(args) = &info.arguments { println!("\n Arguments:"); for arg in args { @@ -328,7 +329,6 @@ pub fn render_prompt_info(info: &PromptInfo) { ); } } - println!(); } pub fn render_extension_success(name: &str) { @@ -500,6 +500,23 @@ fn get_tool_params_max_length() -> usize { .unwrap_or(40) } +fn print_value(value: &Value, debug: bool) { + let formatted = match value { + Value::String(s) => { + if !debug && s.len() > get_tool_params_max_length() { + style(format!("[REDACTED: {} chars]", s.len())).yellow() + } else { + style(s.to_string()).green() + } + } + Value::Number(n) => style(n.to_string()).yellow(), + Value::Bool(b) => style(b.to_string()).yellow(), + Value::Null => style("null".to_string()).dim(), + _ => unreachable!(), + }; + println!("{}", formatted); +} + fn print_params(value: &Value, depth: usize, debug: bool) { let indent = INDENT.repeat(depth); @@ -518,21 +535,9 @@ fn print_params(value: &Value, depth: usize, debug: bool) { print_params(item, depth + 2, debug); } } - Value::String(s) => { - if !debug && s.len() > get_tool_params_max_length() { - println!("{}{}: {}", indent, style(key).dim(), style("...").dim()); - } else { - println!("{}{}: {}", indent, style(key).dim(), style(s).green()); - } - } - Value::Number(n) => { - println!("{}{}: {}", indent, style(key).dim(), style(n).blue()); - } - Value::Bool(b) => { - println!("{}{}: {}", indent, style(key).dim(), style(b).blue()); - } - Value::Null => { - println!("{}{}: {}", indent, style(key).dim(), style("null").dim()); + _ => { + print!("{}{}: ", indent, style(key).dim()); + print_value(val, debug); } } } @@ -543,26 +548,7 @@ fn print_params(value: &Value, depth: usize, debug: bool) { print_params(item, depth + 1, debug); } } - Value::String(s) => { - if !debug && s.len() > get_tool_params_max_length() { - println!( - "{}{}", - indent, - style(format!("[REDACTED: {} chars]", s.len())).yellow() - ); - } else { - println!("{}{}", indent, style(s).green()); - } - } - Value::Number(n) => { - println!("{}{}", indent, style(n).yellow()); - } - Value::Bool(b) => { - println!("{}{}", indent, style(b).yellow()); - } - Value::Null => { - println!("{}{}", indent, style("null").dim()); - } + _ => print_value(value, debug), } }