Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,5 @@ do_not_version/
/ui/desktop/src/bin/goose-scheduler-executor
/ui/desktop/src/bin/goose
/.env

/working_dir
12 changes: 7 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/goose-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ http = "1.0"
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"] }
Expand Down
9 changes: 5 additions & 4 deletions crates/goose-cli/src/recipes/print_recipe.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::HashMap;

use anstream::println;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we also need to import the eprintln wrapper here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a good question - this PR is fixing automations based on recipe. stderr is not useful at this point for much when it comes to recipes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the surrounding context but my understanding of the bug we're aiming to fix is "don't write ANSI to non-ttys" which is definitely a good idea globally.

This PR is fixing that for println! but is also changing some things to use eprintln! (why?) but crucially also is not using the anstyle wrapper so we'll still output ANSI styles to stderr which seems wrong to me.

IOW we should consistently use anstream::{println, eprintln}; at least right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mostly agree, just not sure I want this to be held up because of that. eprint vs print in the code is kind of a mess right now

use console::style;
use goose::recipe::{Recipe, BUILT_IN_RECIPE_DIR_PARAM};

Expand Down Expand Up @@ -81,16 +82,16 @@ pub fn missing_parameters_command_line(missing_params: Vec<String>) -> 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!();
}
118 changes: 60 additions & 58 deletions crates/goose-cli/src/session/output.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use anstream::println;
use bat::WrappingMode;
use console::{style, Color};
use goose::config::Config;
Expand All @@ -11,11 +12,10 @@ 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;

// Re-export theme for use in main
#[derive(Clone, Copy)]
pub enum Theme {
Expand Down Expand Up @@ -131,20 +131,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) {
Expand All @@ -159,7 +165,9 @@ pub fn render_message(message: &Message, debug: bool) {
println!("Image: [data: {}, type: {}]", image.data, image.mime_type);
}
MessageContent::Thinking(thinking) => {
if std::env::var("GOOSE_CLI_SHOW_THINKING").is_ok() {
if std::env::var("GOOSE_CLI_SHOW_THINKING").is_ok()
&& std::io::stdout().is_terminal()
{
println!("\n{}", style("Thinking:").dim().italic());
print_markdown(&thinking.thinking, theme);
}
Expand All @@ -183,6 +191,10 @@ pub fn render_text(text: &str, color: Option<Color>, dim: bool) {
}

pub fn render_text_no_newlines(text: &str, color: Option<Color>, dim: bool) {
if !std::io::stdout().is_terminal() {
println!("{}", text);
return;
}
let mut styled_text = style(text);
if dim {
styled_text = styled_text.dim();
Expand Down Expand Up @@ -287,17 +299,18 @@ pub fn render_prompts(prompts: &HashMap<String, Vec<String>>) {

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 {
Expand All @@ -316,7 +329,6 @@ pub fn render_prompt_info(info: &PromptInfo) {
);
}
}
println!();
}

pub fn render_extension_success(name: &str) {
Expand Down Expand Up @@ -465,14 +477,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()
Comment on lines +480 to +481
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am less familiar with bat and this cliclack crate but IME with https://docs.rs/indicatif/latest/indicatif/ it's pretty easy to use e.g. https://docs.rs/indicatif/latest/indicatif/struct.ProgressBar.html#method.println to automate these kinds of conditional prints.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks cool. here we are just skipping the formatting of markdown though

.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 {
print!("{}", content);
}
}

const INDENT: &str = " ";
Expand All @@ -484,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);

Expand All @@ -502,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);
}
}
}
Expand All @@ -527,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),
}
}

Expand Down Expand Up @@ -775,7 +777,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,
Expand Down
Loading