From 5e8e5fb7c20f31f510aa52a916c3e257920e05a1 Mon Sep 17 00:00:00 2001 From: Shodipo Ayomide Date: Fri, 4 Jul 2025 01:23:34 +0100 Subject: [PATCH] feat(goose-cli): theme persistence & selection (#1693) Co-authored-by: Bradley Axen --- crates/goose-cli/src/session/input.rs | 18 +++++++++++++++ crates/goose-cli/src/session/mod.rs | 22 +++++++++++++++++++ crates/goose-cli/src/session/output.rs | 11 ++++++++++ .../docs/guides/goose-cli-commands.md | 10 ++++++++- ui/desktop/src/hooks/useMessageStream.ts | 16 +++++++++----- 5 files changed, 70 insertions(+), 7 deletions(-) diff --git a/crates/goose-cli/src/session/input.rs b/crates/goose-cli/src/session/input.rs index 62c83465..2adea80a 100644 --- a/crates/goose-cli/src/session/input.rs +++ b/crates/goose-cli/src/session/input.rs @@ -11,6 +11,7 @@ pub enum InputResult { AddExtension(String), AddBuiltin(String), ToggleTheme, + SelectTheme(String), Retry, ListPrompts(Option), PromptCommand(PromptCommandOptions), @@ -103,6 +104,22 @@ fn handle_slash_command(input: &str) -> Option { Some(InputResult::Retry) } "/t" => Some(InputResult::ToggleTheme), + s if s.starts_with("/t ") => { + let t = s + .strip_prefix("/t ") + .unwrap_or_default() + .trim() + .to_lowercase(); + if ["light", "dark", "ansi"].contains(&t.as_str()) { + Some(InputResult::SelectTheme(t)) + } else { + println!( + "Theme Unavailable: {} Available themes are: light, dark, ansi", + t + ); + Some(InputResult::Retry) + } + } "/prompts" => Some(InputResult::ListPrompts(None)), s if s.starts_with(CMD_PROMPTS) => { // Parse arguments for /prompts command @@ -234,6 +251,7 @@ fn print_help() { "Available commands: /exit or /quit - Exit the session /t - Toggle Light/Dark/Ansi theme +/t - Set theme directly (light, dark, ansi) /extension - Add a stdio extension (format: ENV1=val1 command args...) /builtin - Add builtin extensions by name (comma-separated) /prompts [--extension ] - List all available prompts, optionally filtered by extension diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index 4b1485e0..e978fbe1 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -499,6 +499,28 @@ impl Session { output::set_theme(new_theme); continue; } + + input::InputResult::SelectTheme(theme_name) => { + save_history(&mut editor); + + let new_theme = match theme_name.as_str() { + "light" => { + println!("Switching to Light theme"); + output::Theme::Light + } + "dark" => { + println!("Switching to Dark theme"); + output::Theme::Dark + } + "ansi" => { + println!("Switching to Ansi theme"); + output::Theme::Ansi + } + _ => output::Theme::Dark, + }; + output::set_theme(new_theme); + continue; + } input::InputResult::Retry => continue, input::InputResult::ListPrompts(extension) => { save_history(&mut editor); diff --git a/crates/goose-cli/src/session/output.rs b/crates/goose-cli/src/session/output.rs index 60462cca..deeea706 100644 --- a/crates/goose-cli/src/session/output.rs +++ b/crates/goose-cli/src/session/output.rs @@ -67,6 +67,17 @@ pub fn set_theme(theme: Theme) { .set_param("GOOSE_CLI_THEME", Value::String(theme.as_config_string())) .expect("Failed to set theme"); CURRENT_THEME.with(|t| *t.borrow_mut() = theme); + + let config = Config::global(); + let theme_str = match theme { + Theme::Light => "light", + Theme::Dark => "dark", + Theme::Ansi => "ansi", + }; + + if let Err(e) = config.set_param("GOOSE_CLI_THEME", Value::String(theme_str.to_string())) { + eprintln!("Failed to save theme setting to config: {}", e); + } } pub fn get_theme() -> Theme { diff --git a/documentation/docs/guides/goose-cli-commands.md b/documentation/docs/guides/goose-cli-commands.md index 15fe0b78..323b69fb 100644 --- a/documentation/docs/guides/goose-cli-commands.md +++ b/documentation/docs/guides/goose-cli-commands.md @@ -556,6 +556,14 @@ All commands support tab completion. Press `` after a slash (/) to cycle th Goose CLI supports several shortcuts and built-in commands for easier navigation. +### Keyboard Navigation + - **`Ctrl+C`** - Interrupt the current request - **`Ctrl+J`** - Add a newline -- **`Cmd+Up/Down arrows`** - Navigate through command history \ No newline at end of file +- **`Cmd+Up/Down arrows`** - Navigate through command history + +### Slash Commands +- **`/exit` or `/quit`** - Exit the session +- **`/t`** - Toggle between Light/Dark/Ansi themes +- **`/t `** - Set theme directly (`/t light`, `/t dark`, or `/t ansi`) +- **`/?` or `/help`** - Display the help menu diff --git a/ui/desktop/src/hooks/useMessageStream.ts b/ui/desktop/src/hooks/useMessageStream.ts index 2195035b..3e382386 100644 --- a/ui/desktop/src/hooks/useMessageStream.ts +++ b/ui/desktop/src/hooks/useMessageStream.ts @@ -317,15 +317,16 @@ export function useMessageStream({ const lastMessage = currentMessages[currentMessages.length - 1]; onFinish(lastMessage, parsedEvent.reason); } - + // Fetch updated session metadata with token counts - const sessionId = (extraMetadataRef.current.body as Record)?.session_id as string; + const sessionId = (extraMetadataRef.current.body as Record) + ?.session_id as string; if (sessionId) { try { const sessionResponse = await getSessionHistory({ path: { session_id: sessionId }, }); - + if (sessionResponse.data?.metadata) { setSessionMetadata({ workingDir: sessionResponse.data.metadata.working_dir, @@ -335,9 +336,12 @@ export function useMessageStream({ totalTokens: sessionResponse.data.metadata.total_tokens || null, inputTokens: sessionResponse.data.metadata.input_tokens || null, outputTokens: sessionResponse.data.metadata.output_tokens || null, - accumulatedTotalTokens: sessionResponse.data.metadata.accumulated_total_tokens || null, - accumulatedInputTokens: sessionResponse.data.metadata.accumulated_input_tokens || null, - accumulatedOutputTokens: sessionResponse.data.metadata.accumulated_output_tokens || null, + accumulatedTotalTokens: + sessionResponse.data.metadata.accumulated_total_tokens || null, + accumulatedInputTokens: + sessionResponse.data.metadata.accumulated_input_tokens || null, + accumulatedOutputTokens: + sessionResponse.data.metadata.accumulated_output_tokens || null, }); } } catch (error) {