diff --git a/crates/chat-cli/src/api_client/delay_interceptor.rs b/crates/chat-cli/src/api_client/delay_interceptor.rs index e9ec39bafa..be2b060a51 100644 --- a/crates/chat-cli/src/api_client/delay_interceptor.rs +++ b/crates/chat-cli/src/api_client/delay_interceptor.rs @@ -13,13 +13,13 @@ use aws_smithy_types::config_bag::{ Storable, StoreReplace, }; -use crossterm::style::Color; use crossterm::{ execute, style, }; use crate::api_client::MAX_RETRY_DELAY_DURATION; +use crate::theme::StyledText; #[derive(Debug, Clone)] pub struct DelayTrackingInterceptor { @@ -39,9 +39,9 @@ impl DelayTrackingInterceptor { let mut stderr = std::io::stderr(); let _ = execute!( stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("\nWARNING: "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(message), style::Print("\n") ); diff --git a/crates/chat-cli/src/cli/agent/legacy/mod.rs b/crates/chat-cli/src/cli/agent/legacy/mod.rs index 972811a25a..8657cc3c0e 100644 --- a/crates/chat-cli/src/cli/agent/legacy/mod.rs +++ b/crates/chat-cli/src/cli/agent/legacy/mod.rs @@ -18,6 +18,7 @@ use super::{ use crate::cli::agent::hook::Hook; use crate::cli::agent::legacy::context::LegacyContextConfig; use crate::os::Os; +use crate::theme::StyledText; use crate::util::directories; /// Performs the migration from legacy profile configuration to agent configuration if it hasn't @@ -110,10 +111,7 @@ pub async fn migrate(os: &mut Os, force: bool) -> eyre::Result .interact_on_opt(&dialoguer::console::Term::stdout()) { Ok(sel) => { - let _ = crossterm::execute!( - std::io::stdout(), - crossterm::style::SetForegroundColor(crossterm::style::Color::Magenta) - ); + let _ = crossterm::execute!(std::io::stdout(), StyledText::emphasis_fg()); sel }, // Ctrl‑C -> Err(Interrupted) diff --git a/crates/chat-cli/src/cli/agent/mod.rs b/crates/chat-cli/src/cli/agent/mod.rs index 369932091a..3b42a7191b 100644 --- a/crates/chat-cli/src/cli/agent/mod.rs +++ b/crates/chat-cli/src/cli/agent/mod.rs @@ -19,10 +19,7 @@ use std::path::{ PathBuf, }; -use crossterm::style::{ - Color, - Stylize as _, -}; +use crossterm::style::Stylize as _; use crossterm::{ execute, queue, @@ -65,6 +62,7 @@ use crate::cli::agent::hook::{ }; use crate::database::settings::Setting; use crate::os::Os; +use crate::theme::StyledText; use crate::util::{ self, MCP_SERVER_TOOL_DELIMITER, @@ -232,13 +230,13 @@ impl Agent { if mcp_servers.mcp_servers.contains_key(name) { let _ = queue!( output, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("WARNING: "), - style::ResetColor, + StyledText::reset(), style::Print("MCP server '"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(name), - style::ResetColor, + StyledText::reset(), style::Print( "' is already configured in agent config. Skipping duplicate from legacy mcp.json.\n" ) @@ -455,10 +453,10 @@ impl Agents { if !mcp_enabled { let _ = execute!( output, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("\n"), style::Print("⚠️ WARNING: "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("MCP functionality has been disabled by your administrator.\n\n"), ); } @@ -517,9 +515,9 @@ impl Agents { load_metadata.load_failed_count += 1; let _ = queue!( output, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error: "), - style::ResetColor, + StyledText::reset(), style::Print(e), style::Print("\n"), ); @@ -555,9 +553,9 @@ impl Agents { load_metadata.load_failed_count += 1; let _ = queue!( output, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error: "), - style::ResetColor, + StyledText::reset(), style::Print(e), style::Print("\n"), ); @@ -625,13 +623,13 @@ impl Agents { if local_names.contains(name) { let _ = queue!( output, - style::SetForegroundColor(style::Color::Yellow), + StyledText::warning_fg(), style::Print("WARNING: "), - style::ResetColor, + StyledText::reset(), style::Print("Agent conflict for "), - style::SetForegroundColor(style::Color::Green), + StyledText::success_fg(), style::Print(name), - style::ResetColor, + StyledText::reset(), style::Print(". Using workspace version.\n") ); false @@ -655,15 +653,15 @@ impl Agents { } let _ = queue!( output, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(format!( ": no agent with name {} found. Falling back to user specified default", name )), style::Print("\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), ); } @@ -673,15 +671,15 @@ impl Agents { } let _ = queue!( output, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(format!( ": user defined default {} not found. Falling back to in-memory default", user_set_default )), style::Print("\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), ); } @@ -739,17 +737,17 @@ impl Agents { let name = &agent.name; let _ = execute!( output, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("WARNING "), - style::ResetColor, + StyledText::reset(), style::Print("Agent config "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(name), - style::ResetColor, + StyledText::reset(), style::Print(" is malformed at "), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(&e.instance_path), - style::ResetColor, + StyledText::reset(), style::Print(format!(": {e}\n")), ); } @@ -892,17 +890,17 @@ pub fn queue_permission_override_warning( ) -> Result<(), std::io::Error> { Ok(queue!( output, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("WARNING: "), - style::ResetColor, + StyledText::reset(), style::Print("You have trusted "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(tool_name), - style::ResetColor, + StyledText::reset(), style::Print(" tool, which overrides the toolsSettings: "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(overridden_settings), - style::ResetColor, + StyledText::reset(), style::Print("\n"), )?) } diff --git a/crates/chat-cli/src/cli/agent/root_command_args.rs b/crates/chat-cli/src/cli/agent/root_command_args.rs index 20d4e62803..fcd654a63e 100644 --- a/crates/chat-cli/src/cli/agent/root_command_args.rs +++ b/crates/chat-cli/src/cli/agent/root_command_args.rs @@ -6,7 +6,6 @@ use clap::{ Args, Subcommand, }; -use crossterm::style::Color; use crossterm::{ queue, style, @@ -25,6 +24,7 @@ use super::{ }; use crate::database::settings::Setting; use crate::os::Os; +use crate::theme::StyledText; use crate::util::directories; #[derive(Clone, Debug, Subcommand, PartialEq, Eq)] @@ -175,9 +175,9 @@ impl AgentArgs { let Ok(instance) = serde_json::to_value(&agent) else { queue!( stderr, - style::SetForegroundColor(style::Color::Red), + StyledText::error_fg(), style::Print("Error: "), - style::ResetColor, + StyledText::reset(), style::Print("failed to obtain value from agent provided. Aborting validation"), )?; break 'validate; @@ -188,9 +188,9 @@ impl AgentArgs { Err(e) => { queue!( stderr, - style::SetForegroundColor(style::Color::Red), + StyledText::error_fg(), style::Print("Error: "), - style::ResetColor, + StyledText::reset(), style::Print(format!("failed to obtain schema: {e}. Aborting validation")) )?; break 'validate; @@ -201,17 +201,17 @@ impl AgentArgs { let name = &agent.name; queue!( stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("WARNING "), - style::ResetColor, + StyledText::reset(), style::Print("Agent config "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(name), - style::ResetColor, + StyledText::reset(), style::Print(" is malformed at "), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(&e.instance_path), - style::ResetColor, + StyledText::reset(), style::Print(format!(": {e}\n")), )?; } @@ -219,9 +219,9 @@ impl AgentArgs { Err(e) => { let _ = queue!( stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error: "), - style::ResetColor, + StyledText::reset(), style::Print(e), style::Print("\n"), ); @@ -235,15 +235,15 @@ impl AgentArgs { if !force { let _ = queue!( stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("WARNING: "), - style::ResetColor, + StyledText::reset(), style::Print( "manual migrate is potentially destructive to existing agent configs with name collision. Use" ), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(" --force "), - style::ResetColor, + StyledText::reset(), style::Print("to run"), style::Print("\n"), ); @@ -255,9 +255,9 @@ impl AgentArgs { let migrated_count = new_agents.len(); let _ = queue!( stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("✓ Success: "), - style::ResetColor, + StyledText::reset(), style::Print(format!( "Profile migration successful. Migrated {} agent(s)\n", migrated_count @@ -267,18 +267,18 @@ impl AgentArgs { Ok(None) => { let _ = queue!( stderr, - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::Print("Info: "), - style::ResetColor, + StyledText::reset(), style::Print("Migration was not performed. Nothing to migrate\n"), ); }, Err(e) => { let _ = queue!( stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error: "), - style::ResetColor, + StyledText::reset(), style::Print(format!("Migration did not happen for the following reason: {e}\n")), ); }, @@ -295,19 +295,19 @@ impl AgentArgs { let _ = queue!( stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("✓ Default agent set to '"), style::Print(&agent.name), style::Print("'. This will take effect the next time q chat is launched.\n"), - style::ResetColor, + StyledText::reset(), ); }, Err(e) => { let _ = queue!( stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error: "), - style::ResetColor, + StyledText::reset(), style::Print(format!("Failed to set default agent: {e}\n")), ); }, diff --git a/crates/chat-cli/src/cli/chat/cli/checkpoint.rs b/crates/chat-cli/src/cli/chat/cli/checkpoint.rs index fcd6f29fe3..57c67cfa40 100644 --- a/crates/chat-cli/src/cli/chat/cli/checkpoint.rs +++ b/crates/chat-cli/src/cli/chat/cli/checkpoint.rs @@ -3,7 +3,6 @@ use std::io::Write; use clap::Subcommand; use crossterm::style::{ Attribute, - Color, StyledContent, Stylize, }; @@ -28,6 +27,7 @@ use crate::cli::experiment::experiment_manager::{ ExperimentName, }; use crate::os::Os; +use crate::theme::StyledText; use crate::util::directories::get_shadow_repo_dir; #[derive(Debug, PartialEq, Subcommand)] @@ -90,9 +90,9 @@ impl CheckpointSubcommand { if !ExperimentManager::is_enabled(os, ExperimentName::Checkpoint) { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("\nCheckpoint is disabled. Enable it with: q settings chat.enableCheckpoint true\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -103,11 +103,11 @@ impl CheckpointSubcommand { if session.conversation.is_in_tangent_mode() { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print( "⚠️ Checkpoint is disabled while in tangent mode. Please exit tangent mode if you want to use checkpoint.\n\n" ), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -127,11 +127,11 @@ impl CheckpointSubcommand { if session.conversation.checkpoint_manager.is_some() { execute!( session.stderr, - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::Print( "✓ Checkpoints are already enabled for this session! Use /checkpoint list to see current checkpoints.\n" ), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } else { let path = get_shadow_repo_dir(os, session.conversation.conversation_id().to_string()) @@ -146,14 +146,14 @@ impl CheckpointSubcommand { execute!( session.stderr, - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::SetAttribute(Attribute::Bold), style::Print(format!( "📷 Checkpoints are enabled! (took {:.2}s)\n", start.elapsed().as_secs_f32() )), - style::SetForegroundColor(Color::Reset), - style::SetAttribute(Attribute::Reset), + StyledText::reset(), + StyledText::reset_attributes(), )?; } @@ -172,9 +172,9 @@ impl CheckpointSubcommand { let Some(manager) = session.conversation.checkpoint_manager.take() else { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("⚠️ Checkpoints not enabled. Use '/checkpoint init' to enable.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -214,11 +214,11 @@ impl CheckpointSubcommand { Ok(_) => { execute!( session.stderr, - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::SetAttribute(Attribute::Bold), style::Print(format!("✓ Restored to checkpoint {}\n", tag)), - style::SetForegroundColor(Color::Reset), - style::SetAttribute(Attribute::Reset), + StyledText::reset(), + StyledText::reset_attributes(), )?; session.conversation.checkpoint_manager = Some(manager); }, @@ -237,9 +237,9 @@ impl CheckpointSubcommand { let Some(manager) = session.conversation.checkpoint_manager.as_ref() else { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("⚠️ Checkpoints not enabled. Use '/checkpoint init' to enable.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -258,9 +258,9 @@ impl CheckpointSubcommand { let Some(manager) = session.conversation.checkpoint_manager.take() else { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("⚠️ ️Checkpoints not enabled.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -279,7 +279,7 @@ impl CheckpointSubcommand { session.stderr, style::SetAttribute(Attribute::Bold), style::Print("✓ Deleted shadow repository for this session.\n"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), )?; }, Err(e) => { @@ -297,9 +297,9 @@ impl CheckpointSubcommand { let Some(manager) = session.conversation.checkpoint_manager.as_ref() else { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("⚠️ ️Checkpoints not enabled. Use '/checkpoint init' to enable.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -318,9 +318,9 @@ impl CheckpointSubcommand { let Some(manager) = session.conversation.checkpoint_manager.as_ref() else { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("⚠️ Checkpoints not enabled. Use '/checkpoint init' to enable.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -333,12 +333,12 @@ impl CheckpointSubcommand { if tag1 != "HEAD" && !manager.tag_index.contains_key(&tag1) { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(format!( "⚠️ Checkpoint '{}' not found! Use /checkpoint list to see available checkpoints\n", tag1 )), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -348,12 +348,12 @@ impl CheckpointSubcommand { if tag2 != "HEAD" && !manager.tag_index.contains_key(&tag2) { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(format!( "⚠️ Checkpoint '{}' not found! Use /checkpoint list to see available checkpoints\n", tag2 )), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -368,9 +368,9 @@ impl CheckpointSubcommand { execute!( session.stderr, - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::Print(header), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; match manager.diff(&tag1, &tag2) { @@ -378,9 +378,9 @@ impl CheckpointSubcommand { if diff.trim().is_empty() { execute!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("No changes.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } else { execute!(session.stderr, style::Print(diff))?; @@ -497,9 +497,9 @@ fn expand_checkpoint(manager: &CheckpointManager, output: &mut impl Write, tag: let Some(&idx) = manager.tag_index.get(tag) else { execute!( output, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(format!("⚠️ checkpoint '{}' not found\n", tag)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(()); }; @@ -540,18 +540,18 @@ fn expand_checkpoint(manager: &CheckpointManager, output: &mut impl Write, tag: execute!( output, - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::Print(" └─ "), style::Print(display), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; if !stats_str.is_empty() { execute!( output, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(format!(" ({})", stats_str)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } diff --git a/crates/chat-cli/src/cli/chat/cli/clear.rs b/crates/chat-cli/src/cli/chat/cli/clear.rs index 994ed35e03..b706bbbdb8 100644 --- a/crates/chat-cli/src/cli/chat/cli/clear.rs +++ b/crates/chat-cli/src/cli/chat/cli/clear.rs @@ -1,7 +1,6 @@ use clap::Args; use crossterm::style::{ self, - Color, Stylize, }; use crossterm::{ @@ -14,6 +13,7 @@ use crate::cli::chat::{ ChatSession, ChatState, }; +use crate::theme::StyledText; #[deny(missing_docs)] #[derive(Debug, PartialEq, Args)] @@ -24,20 +24,20 @@ impl ClearArgs { pub async fn execute(self, session: &mut ChatSession) -> Result { execute!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print( "\nAre you sure? This will erase the conversation history and context from hooks for the current session. " ), style::Print("["), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("y"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("/"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("n"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("]:\n\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), cursor::Show, )?; @@ -60,9 +60,9 @@ impl ClearArgs { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("\nConversation history cleared.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } diff --git a/crates/chat-cli/src/cli/chat/cli/context.rs b/crates/chat-cli/src/cli/chat/cli/context.rs index 9264c18561..7bff051bfc 100644 --- a/crates/chat-cli/src/cli/chat/cli/context.rs +++ b/crates/chat-cli/src/cli/chat/cli/context.rs @@ -1,10 +1,7 @@ use std::collections::HashSet; use clap::Subcommand; -use crossterm::style::{ - Attribute, - Color, -}; +use crossterm::style::Attribute; use crossterm::{ execute, style, @@ -27,6 +24,7 @@ use crate::constants::help_text::{ context_long_help, }; use crate::os::Os; +use crate::theme::StyledText; #[deny(missing_docs)] #[derive(Debug, PartialEq, Subcommand)] @@ -71,9 +69,9 @@ impl ContextSubcommand { let Some(context_manager) = &mut session.conversation.context_manager else { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("\nContext management is not available.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; return Ok(ChatState::PromptUser { @@ -95,17 +93,17 @@ impl ContextSubcommand { execute!( session.stderr, style::SetAttribute(Attribute::Bold), - style::SetForegroundColor(Color::Magenta), + StyledText::emphasis_fg(), style::Print(format!("👤 Agent ({}):\n", context_manager.current_profile)), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), )?; if agent_owned_list.is_empty() { execute!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" \n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } else { for path in &agent_owned_list { @@ -116,13 +114,13 @@ impl ContextSubcommand { { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format!( "({} match{})", context_files.len(), if context_files.len() == 1 { "" } else { "es" } )), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; profile_context_files .extend(context_files.into_iter().map(|(path, content)| (path, content, false))); @@ -135,17 +133,17 @@ impl ContextSubcommand { execute!( session.stderr, style::SetAttribute(Attribute::Bold), - style::SetForegroundColor(Color::Magenta), + StyledText::emphasis_fg(), style::Print("💬 Session (temporary):\n"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), )?; if session_owned_list.is_empty() { execute!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" \n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } else { for path in &session_owned_list { @@ -156,13 +154,13 @@ impl ContextSubcommand { { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format!( "({} match{})", context_files.len(), if context_files.len() == 1 { "" } else { "es" } )), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; profile_context_files .extend(context_files.into_iter().map(|(path, content)| (path, content, true))); @@ -175,9 +173,9 @@ impl ContextSubcommand { if profile_context_files.is_empty() { execute!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("No files in the current directory matched the rules above.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } else { let total = profile_context_files.len(); @@ -187,15 +185,15 @@ impl ContextSubcommand { .sum::(); execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::SetAttribute(Attribute::Bold), style::Print(format!( "{} matched file{} in use:\n", total, if total == 1 { "" } else { "s" } )), - style::SetForegroundColor(Color::Reset), - style::SetAttribute(Attribute::Reset) + StyledText::reset(), + StyledText::reset_attributes() )?; for (filename, content, is_temporary) in &profile_context_files { @@ -204,16 +202,16 @@ impl ContextSubcommand { execute!( session.stderr, style::Print(format!("{} {} ", icon, filename)), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(format!("(~{} tkns)\n", est_tokens)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; if expand { execute!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(format!("{}\n\n", content)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } } @@ -238,12 +236,12 @@ impl ContextSubcommand { if !dropped_files.is_empty() { execute!( session.stderr, - style::SetForegroundColor(Color::DarkYellow), + StyledText::warning_fg(), style::Print(format!( "Total token count exceeds limit: {}. The following files will be automatically dropped when interacting with Q. Consider removing them. \n\n", context_files_max_size )), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; let total_files = dropped_files.len(); @@ -254,9 +252,9 @@ impl ContextSubcommand { execute!( session.stderr, style::Print(format!("{} ", filename)), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(format!("(~{} tkns)\n", est_tokens)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } @@ -279,14 +277,14 @@ impl ContextSubcommand { execute!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&border), style::Print("\n"), style::SetAttribute(Attribute::Bold), style::Print(" CONVERSATION SUMMARY"), style::Print("\n"), style::Print(&border), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n\n"), style::Print(&summary), style::Print("\n\n\n") @@ -298,18 +296,18 @@ impl ContextSubcommand { Ok(_) => { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format!("\nAdded {} path(s) to context.\n", paths.len())), style::Print("Note: Context modifications via slash command is temporary.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; }, Err(e) => { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("\nError: {}\n\n", e)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; }, }, @@ -317,18 +315,18 @@ impl ContextSubcommand { Ok(_) => { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format!("\nRemoved {} path(s) from context.\n\n", paths.len(),)), style::Print("Note: Context modifications via slash command is temporary.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; }, Err(e) => { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("\nError: {}\n\n", e)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; }, }, @@ -336,22 +334,22 @@ impl ContextSubcommand { context_manager.clear(); execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("\nCleared context\n"), style::Print("Note: Context modifications via slash command is temporary.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; }, Self::Hooks => { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print( "The /context hooks command is deprecated.\n\nConfigure hooks directly with your agent instead: " ), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(AGENT_FORMAT_HOOKS_DOC_URL), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; }, diff --git a/crates/chat-cli/src/cli/chat/cli/editor.rs b/crates/chat-cli/src/cli/chat/cli/editor.rs index ff0433e9e4..b1934ab695 100644 --- a/crates/chat-cli/src/cli/chat/cli/editor.rs +++ b/crates/chat-cli/src/cli/chat/cli/editor.rs @@ -2,8 +2,6 @@ use clap::Args; use crossterm::execute; use crossterm::style::{ self, - Attribute, - Color, }; use uuid::Uuid; @@ -12,6 +10,7 @@ use crate::cli::chat::{ ChatSession, ChatState, }; +use crate::theme::StyledText; #[deny(missing_docs)] #[derive(Debug, PartialEq, Args)] @@ -34,9 +33,9 @@ impl EditorArgs { Err(err) => { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("\nError opening editor: {}\n\n", err)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; return Ok(ChatState::PromptUser { @@ -49,9 +48,9 @@ impl EditorArgs { true => { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("\nEmpty content from editor, not submitting.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; ChatState::PromptUser { @@ -61,18 +60,18 @@ impl EditorArgs { false => { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("\nContent loaded from editor. Submitting prompt...\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; // Display the content as if the user typed it execute!( session.stderr, - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::Magenta), + StyledText::reset_attributes(), + StyledText::emphasis_fg(), style::Print("> "), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print(&content), style::Print("\n") )?; diff --git a/crates/chat-cli/src/cli/chat/cli/experiment.rs b/crates/chat-cli/src/cli/chat/cli/experiment.rs index fe43164424..7996b1bfe0 100644 --- a/crates/chat-cli/src/cli/chat/cli/experiment.rs +++ b/crates/chat-cli/src/cli/chat/cli/experiment.rs @@ -1,14 +1,10 @@ // ABOUTME: Implements the /experiment slash command for toggling experimental features // ABOUTME: Provides interactive selection interface similar to /model command - use clap::Args; -use crossterm::style::{ - self, - Color, -}; use crossterm::{ execute, queue, + style, }; use dialoguer::Select; @@ -19,6 +15,7 @@ use crate::cli::chat::{ }; use crate::cli::experiment::experiment_manager::ExperimentManager; use crate::os::Os; +use crate::theme::StyledText; #[derive(Debug, PartialEq, Args)] pub struct ExperimentArgs; @@ -62,9 +59,9 @@ async fn select_experiment(os: &mut Os, session: &mut ChatSession) -> Result = match Select::with_theme(&crate::util::dialoguer_theme()) @@ -74,10 +71,7 @@ async fn select_experiment(os: &mut Os, session: &mut ChatSession) -> Result { - let _ = crossterm::execute!( - std::io::stdout(), - crossterm::style::SetForegroundColor(crossterm::style::Color::Magenta) - ); + let _ = crossterm::execute!(std::io::stdout(), StyledText::emphasis_fg()); sel }, // Ctrl‑C -> Err(Interrupted) @@ -94,7 +88,7 @@ async fn select_experiment(os: &mut Os, session: &mut ChatSession) -> Result return Err(ChatError::Custom(format!("Failed to choose experiment: {e}").into())), }; - queue!(session.stderr, style::ResetColor)?; + queue!(session.stderr, StyledText::reset())?; if let Some(index) = selection { // Clear the dialoguer selection line and disclaimer @@ -123,15 +117,15 @@ async fn select_experiment(os: &mut Os, session: &mut ChatSession) -> Result Result Result<(), std::io::Error> { queue!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("\nKnowledge tool is disabled. Enable it with: q settings chat.enableKnowledge true\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("💡 Your knowledge base data is preserved and will be available when re-enabled.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), ) } @@ -139,9 +139,9 @@ impl KnowledgeSubcommand { queue!( session.stderr, style::SetAttribute(crossterm::style::Attribute::Bold), - style::SetForegroundColor(Color::Magenta), + StyledText::emphasis_fg(), style::Print(format!("👤 Agent ({}):\n", agent)), - style::SetAttribute(crossterm::style::Attribute::Reset), + StyledText::reset_attributes(), )?; match KnowledgeStore::get_async_instance(os, Self::get_agent(session)).await { @@ -171,18 +171,18 @@ impl KnowledgeSubcommand { { queue!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" \n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } }, Err(_) => { queue!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" \n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; }, } @@ -202,12 +202,12 @@ impl KnowledgeSubcommand { session.stderr, style::Print(format!("{}📂 ", indent)), style::SetAttribute(style::Attribute::Bold), - style::SetForegroundColor(Color::Grey), + StyledText::secondary_fg(), style::Print(&ctx.name), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format!(" ({})", &ctx.id[..8])), - style::SetAttribute(style::Attribute::Reset), - style::SetForegroundColor(Color::Reset), + StyledText::reset_attributes(), + StyledText::reset(), style::Print("\n") )?; @@ -216,9 +216,9 @@ impl KnowledgeSubcommand { queue!( session.stderr, style::Print(format!("{} ", indent)), - style::SetForegroundColor(Color::Grey), + StyledText::secondary_fg(), style::Print(format!("{}\n", source_path)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } @@ -226,17 +226,17 @@ impl KnowledgeSubcommand { queue!( session.stderr, style::Print(format!("{} ", indent)), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format!("{} items", ctx.item_count)), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" • "), - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::Print(ctx.embedding_type.description()), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" • "), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(format!("{}", ctx.updated_at.format("%m/%d %H:%M"))), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n\n") )?; } @@ -498,9 +498,9 @@ impl KnowledgeSubcommand { OperationResult::Success(msg) => { queue!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format!("\n{}\n\n", msg)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), ) }, OperationResult::Info(msg) => { @@ -508,7 +508,7 @@ impl KnowledgeSubcommand { queue!( session.stderr, style::Print(format!("\n{}\n\n", msg)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } Ok(()) @@ -516,17 +516,17 @@ impl KnowledgeSubcommand { OperationResult::Warning(msg) => { queue!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(format!("\n{}\n\n", msg)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), ) }, OperationResult::Error(msg) => { queue!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("\nError: {}\n\n", msg)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), ) }, } diff --git a/crates/chat-cli/src/cli/chat/cli/logdump.rs b/crates/chat-cli/src/cli/chat/cli/logdump.rs index 7abd16c3ae..3918b42520 100644 --- a/crates/chat-cli/src/cli/chat/cli/logdump.rs +++ b/crates/chat-cli/src/cli/chat/cli/logdump.rs @@ -9,7 +9,6 @@ use clap::Args; use crossterm::execute; use crossterm::style::{ self, - Color, }; use zip::ZipWriter; use zip::write::SimpleFileOptions; @@ -19,6 +18,7 @@ use crate::cli::chat::{ ChatSession, ChatState, }; +use crate::theme::StyledText; use crate::util::directories::logs_dir; /// Arguments for the logdump command that collects logs for support investigation @@ -29,9 +29,9 @@ impl LogdumpArgs { pub async fn execute(self, session: &mut ChatSession) -> Result { execute!( session.stderr, - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print("Collecting logs...\n"), - style::ResetColor, + StyledText::reset(), )?; let timestamp = Utc::now().format("%Y-%m-%dT%H-%M-%SZ").to_string(); @@ -44,20 +44,20 @@ impl LogdumpArgs { Ok(log_count) => { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format!( "✓ Successfully created {} with {} log files\n", zip_filename, log_count )), - style::ResetColor, + StyledText::reset(), )?; }, Err(e) => { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("✗ Failed to create log dump: {}\n\n", e)), - style::ResetColor, + StyledText::reset(), )?; return Err(ChatError::Custom(format!("Log dump failed: {}", e).into())); }, diff --git a/crates/chat-cli/src/cli/chat/cli/mcp.rs b/crates/chat-cli/src/cli/chat/cli/mcp.rs index 1cabd5344b..a9a8d2d53a 100644 --- a/crates/chat-cli/src/cli/chat/cli/mcp.rs +++ b/crates/chat-cli/src/cli/chat/cli/mcp.rs @@ -1,10 +1,9 @@ use std::io::Write; use clap::Args; -use crossterm::queue; -use crossterm::style::{ - self, - Color, +use crossterm::{ + queue, + style, }; use crate::cli::chat::tool_manager::LoadingRecord; @@ -13,6 +12,7 @@ use crate::cli::chat::{ ChatSession, ChatState, }; +use crate::theme::StyledText; /// Arguments for the MCP (Model Context Protocol) command. /// @@ -27,10 +27,10 @@ impl McpArgs { if !session.conversation.mcp_enabled { queue!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("\n"), style::Print("⚠️ WARNING: "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("MCP functionality has been disabled by your administrator.\n\n"), )?; session.stderr.flush()?; diff --git a/crates/chat-cli/src/cli/chat/cli/mod.rs b/crates/chat-cli/src/cli/chat/cli/mod.rs index 02f9cf08b8..09864fa039 100644 --- a/crates/chat-cli/src/cli/chat/cli/mod.rs +++ b/crates/chat-cli/src/cli/chat/cli/mod.rs @@ -1,3 +1,4 @@ +use crate::theme::StyledText; pub mod changelog; pub mod checkpoint; pub mod clear; @@ -50,12 +51,12 @@ use crate::cli::chat::{ ChatState, }; use crate::cli::issue; -use crate::constants::ui_text::EXTRA_HELP; +use crate::constants::ui_text; use crate::os::Os; /// q (Amazon Q Chat) #[derive(Debug, PartialEq, Parser)] -#[command(color = clap::ColorChoice::Always, term_width = 0, after_long_help = EXTRA_HELP)] +#[command(color = clap::ColorChoice::Always, term_width = 0, after_long_help = &ui_text::extra_help())] pub enum SlashCommand { /// Quit the application #[command(aliases = ["q", "exit"])] @@ -137,16 +138,16 @@ impl SlashCommand { }; execute!( session.stderr, - style::SetForegroundColor(style::Color::Yellow), + StyledText::warning_fg(), style::Print("This command has been deprecated. Use"), - style::SetForegroundColor(style::Color::Cyan), + StyledText::brand_fg(), style::Print(" /agent "), - style::SetForegroundColor(style::Color::Yellow), + StyledText::warning_fg(), style::Print("instead.\nSee "), style::Print(AGENT_MIGRATION_DOC_URL), style::Print(" for more detail"), style::Print("\n"), - style::ResetColor, + StyledText::reset(), )?; Ok(ChatState::PromptUser { diff --git a/crates/chat-cli/src/cli/chat/cli/model.rs b/crates/chat-cli/src/cli/chat/cli/model.rs index bc6fba24e0..8ffcb8b70c 100644 --- a/crates/chat-cli/src/cli/chat/cli/model.rs +++ b/crates/chat-cli/src/cli/chat/cli/model.rs @@ -2,7 +2,6 @@ use amzn_codewhisperer_client::types::Model; use clap::Args; use crossterm::style::{ self, - Color, }; use crossterm::{ execute, @@ -21,6 +20,7 @@ use crate::cli::chat::{ ChatState, }; use crate::os::Os; +use crate::theme::StyledText; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ModelInfo { @@ -93,9 +93,9 @@ pub async fn select_model(os: &Os, session: &mut ChatSession) -> Result Result { - let _ = crossterm::execute!( - std::io::stdout(), - crossterm::style::SetForegroundColor(crossterm::style::Color::Magenta) - ); + let _ = crossterm::execute!(std::io::stdout(), StyledText::emphasis_fg()); sel }, // Ctrl‑C -> Err(Interrupted) @@ -139,7 +136,7 @@ pub async fn select_model(os: &Os, session: &mut ChatSession) -> Result return Err(ChatError::Custom(format!("Failed to choose model: {e}").into())), }; - queue!(session.stderr, style::ResetColor)?; + queue!(session.stderr, StyledText::reset())?; if let Some(index) = selection { let selected = models[index].clone(); @@ -150,13 +147,13 @@ pub async fn select_model(os: &Os, session: &mut ChatSession) -> Result { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("\nFailed to {} {}: {}\n\n", $name, $path, &err)), - style::SetAttribute(Attribute::Reset) + StyledText::reset_attributes() )?; return Ok(ChatState::PromptUser { @@ -61,12 +60,12 @@ impl PersistSubcommand { if os.fs.exists(&path) && !force { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!( "\nFile at {} already exists. To overwrite, use -f or --force\n\n", &path )), - style::SetAttribute(Attribute::Reset) + StyledText::reset_attributes() )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -76,9 +75,9 @@ impl PersistSubcommand { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format!("\n✔ Exported conversation state to {}\n\n", &path)), - style::SetAttribute(Attribute::Reset) + StyledText::reset_attributes() )?; }, Self::Load { path } => { @@ -112,9 +111,9 @@ impl PersistSubcommand { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format!("\n✔ Imported conversation state from {}\n\n", &path)), - style::SetAttribute(Attribute::Reset) + StyledText::reset_attributes() )?; }, } diff --git a/crates/chat-cli/src/cli/chat/cli/profile.rs b/crates/chat-cli/src/cli/chat/cli/profile.rs index 90c5679713..5d05568afa 100644 --- a/crates/chat-cli/src/cli/chat/cli/profile.rs +++ b/crates/chat-cli/src/cli/chat/cli/profile.rs @@ -5,8 +5,6 @@ use std::io::Write; use clap::Subcommand; use crossterm::style::{ self, - Attribute, - Color, }; use crossterm::{ execute, @@ -42,6 +40,7 @@ use crate::cli::chat::{ }; use crate::database::settings::Setting; use crate::os::Os; +use crate::theme::StyledText; use crate::util::directories::chat_global_agent_path; use crate::util::{ NullWriter, @@ -148,9 +147,9 @@ impl AgentSubcommand { ($err:expr) => { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("\nError: {}\n\n", $err)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )? }; } @@ -164,10 +163,10 @@ impl AgentSubcommand { if active_profile.is_some_and(|p| p == *profile) { queue!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("* "), style::Print(&profile.name), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } else { queue!(session.stderr, style::Print(" "), style::Print(&profile.name),)?; @@ -214,9 +213,9 @@ impl AgentSubcommand { Err(e) => { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error: "), - style::ResetColor, + StyledText::reset(), style::Print(&e), style::Print("\n"), )?; @@ -230,17 +229,17 @@ impl AgentSubcommand { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("Agent "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(name), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(" has been created successfully"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Changes take effect on next launch"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; }, @@ -267,9 +266,9 @@ impl AgentSubcommand { Err(e) => { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error: "), - style::ResetColor, + StyledText::reset(), style::Print(&e), style::Print("\n"), )?; @@ -283,17 +282,17 @@ impl AgentSubcommand { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("Agent "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(name), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(" has been edited successfully"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Changes take effect on next launch"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; }, @@ -324,10 +323,7 @@ impl AgentSubcommand { .interact_on_opt(&dialoguer::console::Term::stdout()) { Ok(sel) => { - let _ = crossterm::execute!( - std::io::stdout(), - crossterm::style::SetForegroundColor(crossterm::style::Color::Magenta) - ); + let _ = crossterm::execute!(std::io::stdout(), StyledText::emphasis_fg()); sel }, // Ctrl‑C -> Err(Interrupted) @@ -406,12 +402,12 @@ impl AgentSubcommand { }; execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(format!( "To make changes or create agents, please do so via create the corresponding config in {}, where you would also find an example config for your reference.\nTo switch agent, launch another instance of q chat with --agent.\n\n", global_path )), - style::SetAttribute(Attribute::Reset) + StyledText::reset_attributes() )?; }, Self::SetDefault { name } => match session.conversation.agents.agents.get(&name) { @@ -424,19 +420,19 @@ impl AgentSubcommand { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("✓ Default agent set to '"), style::Print(&agent.name), style::Print("'. This will take effect the next time q chat is launched.\n"), - style::ResetColor, + StyledText::reset(), )?; }, None => { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error: "), - style::ResetColor, + StyledText::reset(), style::Print(format!("No agent with name {name} found\n")), )?; }, @@ -461,10 +457,7 @@ impl AgentSubcommand { .interact_on_opt(&dialoguer::console::Term::stdout()) { Ok(sel) => { - let _ = crossterm::execute!( - std::io::stdout(), - crossterm::style::SetForegroundColor(crossterm::style::Color::Magenta) - ); + let _ = crossterm::execute!(std::io::stdout(), StyledText::emphasis_fg()); sel }, // Ctrl‑C -> Err(Interrupted) @@ -521,7 +514,7 @@ fn highlight_json(output: &mut impl Write, json_str: &str) -> eyre::Result<()> { queue!(output, style::Print(escaped))?; } - Ok(execute!(output, style::ResetColor)?) + Ok(execute!(output, StyledText::reset())?) } /// Searches all configuration sources for MCP servers and returns a deduplicated list. diff --git a/crates/chat-cli/src/cli/chat/cli/prompts.rs b/crates/chat-cli/src/cli/chat/cli/prompts.rs index 6c35108566..e9fcd348c4 100644 --- a/crates/chat-cli/src/cli/chat/cli/prompts.rs +++ b/crates/chat-cli/src/cli/chat/cli/prompts.rs @@ -16,7 +16,6 @@ use clap::{ use crossterm::style::{ self, Attribute, - Color, }; use crossterm::{ execute, @@ -41,6 +40,7 @@ use crate::cli::chat::{ }; use crate::mcp_client::McpClientError; use crate::os::Os; +use crate::theme::StyledText; use crate::util::directories::{ chat_global_prompts_dir, chat_local_prompts_dir, @@ -336,13 +336,13 @@ fn handle_mcp_invalid_params_error( queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Error: Invalid arguments for prompt '"), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("':\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; for error in &all_errors { @@ -351,11 +351,11 @@ fn handle_mcp_invalid_params_error( queue!( session.stderr, style::Print(" - "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(¶m_name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(": "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(&error.message), style::Print("\n"), )?; @@ -363,7 +363,7 @@ fn handle_mcp_invalid_params_error( queue!( session.stderr, style::Print(" - "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(&error.message), style::Print("\n"), )?; @@ -373,11 +373,11 @@ fn handle_mcp_invalid_params_error( queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("Use '/prompts details "), style::Print(name), style::Print("' for usage information."), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; @@ -387,15 +387,15 @@ fn handle_mcp_invalid_params_error( queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Error: Invalid arguments for prompt '"), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("'. Use '/prompts details "), style::Print(name), style::Print("' for usage information."), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; execute!(session.stderr)?; @@ -422,10 +422,10 @@ fn handle_mcp_internal_error(name: &str, error_str: &str, session: &mut ChatSess queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error: "), style::Print(message), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; @@ -450,13 +450,13 @@ fn handle_mcp_internal_error(name: &str, error_str: &str, session: &mut ChatSess queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error: MCP server internal error while processing prompt '"), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(name), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("'."), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; execute!(session.stderr)?; @@ -475,11 +475,11 @@ fn display_missing_args_error( queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Error: Missing required arguments for prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(prompt_name), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n\n"), )?; @@ -500,7 +500,7 @@ fn display_missing_args_error( queue!( session.stderr, style::Print("Usage: "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print("@"), style::Print(prompt_name), )?; @@ -522,11 +522,7 @@ fn display_missing_args_error( )?; } - queue!( - session.stderr, - style::SetForegroundColor(Color::Reset), - style::Print("\n"), - )?; + queue!(session.stderr, StyledText::reset(), style::Print("\n"),)?; if !args.is_empty() { queue!(session.stderr, style::Print("\nArguments:\n"),)?; @@ -536,11 +532,11 @@ fn display_missing_args_error( queue!( session.stderr, style::Print(" "), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("(required) "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&arg.name), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; if let Some(desc) = &arg.description { if !desc.trim().is_empty() { @@ -555,11 +551,11 @@ fn display_missing_args_error( queue!( session.stderr, style::Print(" "), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("(optional) "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&arg.name), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; if let Some(desc) = &arg.description { if !desc.trim().is_empty() { @@ -662,13 +658,13 @@ impl PromptsArgs { style::Print("\n"), style::SetAttribute(Attribute::Bold), style::Print("Usage: "), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("You can use a prompt by typing "), style::SetAttribute(Attribute::Bold), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("'@ [...args]'"), - style::SetForegroundColor(Color::Reset), - style::SetAttribute(Attribute::Reset), + StyledText::reset(), + StyledText::reset_attributes(), style::Print("\n\n"), )?; @@ -678,21 +674,21 @@ impl PromptsArgs { style::Print("\n"), style::SetAttribute(Attribute::Bold), style::Print("Prompt"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print({ let padding = description_pos.saturating_sub(UnicodeWidthStr::width("Prompt")); " ".repeat(padding) }), style::SetAttribute(Attribute::Bold), style::Print("Description"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print({ let padding = arguments_pos.saturating_sub(description_pos + UnicodeWidthStr::width("Description")); " ".repeat(padding) }), style::SetAttribute(Attribute::Bold), style::Print("Arguments (* = required)"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n"), style::Print(format!("{}\n", "▔".repeat(terminal_width))), )?; @@ -736,7 +732,7 @@ impl PromptsArgs { session.stderr, style::SetAttribute(Attribute::Bold), style::Print("Global (.aws/amazonq/prompts):"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n"), )?; for name in &global_prompts { @@ -753,7 +749,7 @@ impl PromptsArgs { session.stderr, style::SetAttribute(Attribute::Bold), style::Print("Local (.amazonq/prompts):"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n"), )?; for name in &local_prompts { @@ -762,9 +758,9 @@ impl PromptsArgs { if has_global_version { queue!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(" (overrides global)"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } @@ -784,7 +780,7 @@ impl PromptsArgs { style::SetAttribute(Attribute::Bold), style::Print(server_name), style::Print(" (MCP):"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n"), )?; @@ -802,9 +798,9 @@ impl PromptsArgs { queue!( session.stderr, style::Print(" ".repeat(description_padding)), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(&truncated_desc), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; // Print arguments if they exist @@ -817,12 +813,12 @@ impl PromptsArgs { for (i, arg) in args.iter().enumerate() { queue!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(match arg.required { Some(true) => format!("{}*", arg.name), _ => arg.name.clone(), }), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(if i < args.len() - 1 { ", " } else { "" }), )?; } @@ -930,17 +926,17 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("⚠ Warning: Both file-based and MCP prompts named '"), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("' exist. Showing file-based prompt.\n"), style::Print("To see MCP prompt, specify server: "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print("/prompts details /"), style::Print(&name), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; execute!(session.stderr)?; @@ -981,17 +977,17 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" not found. Use "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print("/prompts list"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" to see available prompts.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; }, 1 => { @@ -1008,15 +1004,15 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" is ambiguous. Use one of the following:"), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(alt_msg), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; }, } @@ -1037,7 +1033,7 @@ impl PromptsSubcommand { style::Print("\n"), style::SetAttribute(Attribute::Bold), style::Print("Prompt Details"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n"), style::Print("▔".repeat(terminal_width)), style::Print("\n\n"), @@ -1048,12 +1044,12 @@ impl PromptsSubcommand { session.stderr, style::SetAttribute(Attribute::Bold), style::Print("Name: "), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print(&prompt.name), style::Print("\n"), style::SetAttribute(Attribute::Bold), style::Print("Server: "), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print(&bundle.server_name), style::Print("\n\n"), )?; @@ -1063,7 +1059,7 @@ impl PromptsSubcommand { session.stderr, style::SetAttribute(Attribute::Bold), style::Print("Description:"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n"), )?; @@ -1081,9 +1077,9 @@ impl PromptsSubcommand { _ => { queue!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" (no description available)"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; }, @@ -1095,8 +1091,8 @@ impl PromptsSubcommand { style::Print("\n"), style::SetAttribute(Attribute::Bold), style::Print("Usage: "), - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::Cyan), + StyledText::reset_attributes(), + StyledText::brand_fg(), style::Print("@"), style::Print(&prompt.name), )?; @@ -1124,11 +1120,7 @@ impl PromptsSubcommand { } } - queue!( - session.stderr, - style::SetForegroundColor(Color::Reset), - style::Print("\n"), - )?; + queue!(session.stderr, StyledText::reset(), style::Print("\n"),)?; // Display arguments queue!( @@ -1136,7 +1128,7 @@ impl PromptsSubcommand { style::Print("\n"), style::SetAttribute(Attribute::Bold), style::Print("Arguments:"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n"), )?; @@ -1144,9 +1136,9 @@ impl PromptsSubcommand { if args.is_empty() { queue!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" (no arguments)"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; } else { @@ -1158,11 +1150,11 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print(" "), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("(required) "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&arg.name), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; // Show argument description if available @@ -1180,11 +1172,11 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print(" "), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("(optional) "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&arg.name), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; // Show argument description if available @@ -1200,9 +1192,9 @@ impl PromptsSubcommand { } else { queue!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" (no arguments)"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; } @@ -1224,7 +1216,7 @@ impl PromptsSubcommand { style::Print("\n"), style::SetAttribute(Attribute::Bold), style::Print("Prompt Details"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n"), style::Print("▔".repeat(terminal_width)), style::Print("\n\n"), @@ -1235,15 +1227,15 @@ impl PromptsSubcommand { session.stderr, style::SetAttribute(Attribute::Bold), style::Print("Name: "), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print(name), style::Print("\n"), style::SetAttribute(Attribute::Bold), style::Print("Source: "), - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::DarkGrey), + StyledText::reset_attributes(), + StyledText::secondary_fg(), style::Print(source.display().to_string()), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n\n"), )?; @@ -1252,11 +1244,11 @@ impl PromptsSubcommand { session.stderr, style::SetAttribute(Attribute::Bold), style::Print("Usage: "), - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::Green), + StyledText::reset_attributes(), + StyledText::success_fg(), style::Print("@"), style::Print(name), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n\n"), )?; @@ -1265,7 +1257,7 @@ impl PromptsSubcommand { session.stderr, style::SetAttribute(Attribute::Bold), style::Print("Content Preview:"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n"), )?; @@ -1274,10 +1266,10 @@ impl PromptsSubcommand { for line in preview_lines { queue!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" "), style::Print(line), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; } @@ -1285,11 +1277,11 @@ impl PromptsSubcommand { if lines.len() > 5 { queue!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" ... ("), style::Print((lines.len() - 5).to_string()), style::Print(" more lines)"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; } @@ -1317,17 +1309,17 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("⚠ Warning: Both file-based and MCP prompts named '"), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("' exist. Using file-based prompt.\n"), style::Print("To use MCP prompt, specify server: "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print("@/"), style::Print(&name), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; execute!(session.stderr)?; @@ -1369,32 +1361,32 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(prompt_name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" is ambiguous. Use one of the following "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(alt_msg), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; }, GetPromptError::PromptNotFound(prompt_name) => { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(prompt_name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" not found. Use "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print("/prompts list"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" to see available prompts.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; }, GetPromptError::McpClient(_) | GetPromptError::Service(_) => { @@ -1418,14 +1410,14 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Error: Failed to execute prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(". "), style::Print(&error_str), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; execute!(session.stderr)?; @@ -1462,13 +1454,13 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("❌ Invalid prompt name: "), style::Print(validation_error), style::Print("\n"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("Valid names contain only letters, numbers, hyphens, and underscores (1-50 characters)\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1484,15 +1476,15 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" already exists in "), style::Print(location), style::Print(" directory. Use "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print("/prompts edit "), style::Print(&name), if global { @@ -1500,9 +1492,9 @@ impl PromptsSubcommand { } else { style::Print("") }, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" to modify it.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1530,17 +1522,17 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("⚠ Warning: A "), style::Print(existing_scope), style::Print(" prompt named '"), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("' already exists.\n"), style::Print(override_message), style::Print("\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; // Flush stderr to ensure the warning is displayed before asking for input @@ -1553,9 +1545,9 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("✓ Prompt creation cancelled.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1567,9 +1559,9 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("✓ Prompt creation cancelled.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1594,17 +1586,17 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("✓ Created "), style::Print(location), style::Print(" prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(" at "), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(target_prompt.path.display().to_string()), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n\n"), )?; }, @@ -1624,9 +1616,9 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("Opening editor to create prompt content...\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; // Try to open the editor @@ -1635,32 +1627,32 @@ impl PromptsSubcommand { let location = if global { "global" } else { "local" }; queue!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("✓ Created "), style::Print(location), style::Print(" prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(" at "), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(target_prompt.path.display().to_string()), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n\n"), )?; }, Err(err) => { queue!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error opening editor: "), style::Print(err.to_string()), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("Tip: You can edit this file directly: "), style::Print(target_prompt.path.display().to_string()), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n\n"), )?; }, @@ -1684,11 +1676,11 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("❌ Invalid prompt name: "), style::Print(validation_error), style::Print("\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1704,13 +1696,13 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Global prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" not found.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1724,26 +1716,26 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Local prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" not found, but global version exists.\n"), style::Print("Use "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print("/prompts edit "), style::Print(&name), style::Print(" --global"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" to edit the global version, or\n"), style::Print("use "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print("/prompts create "), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" to create a local override.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1752,13 +1744,13 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" not found.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1769,18 +1761,18 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("Opening editor for "), style::Print(location), style::Print(" prompt: "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("File: "), style::Print(target_prompt.path.display().to_string()), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n\n"), )?; @@ -1789,23 +1781,23 @@ impl PromptsSubcommand { Ok(()) => { queue!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("✓ Prompt edited successfully.\n\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; }, Err(err) => { queue!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error opening editor: "), style::Print(err.to_string()), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("Tip: You can edit this file directly: "), style::Print(target_prompt.path.display().to_string()), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n\n"), )?; }, @@ -1831,13 +1823,13 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Global prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" not found.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1850,20 +1842,20 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Local prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" not found, but global version exists.\n"), style::Print("Use "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print("/prompts remove "), style::Print(&name), style::Print(" --global"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" to remove the global version.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1872,13 +1864,13 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(" not found.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1891,18 +1883,18 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("⚠ Warning: This will permanently remove the "), style::Print(location), style::Print(" prompt '"), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("'.\n"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("File: "), style::Print(target_prompt.path.display().to_string()), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; @@ -1916,9 +1908,9 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("✓ Removal cancelled.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1930,9 +1922,9 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("✓ Removal cancelled.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1945,25 +1937,25 @@ impl PromptsSubcommand { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("✓ Removed "), style::Print(location), style::Print(" prompt "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&name), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(" successfully.\n\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; }, Err(err) => { queue!( session.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error deleting prompt: "), style::Print(err.to_string()), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n\n"), )?; }, @@ -2021,9 +2013,9 @@ fn display_prompt_content( if !content.trim().is_empty() { queue!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(content), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; } @@ -2041,9 +2033,9 @@ fn display_file_prompt_content(_prompt_name: &str, content: &str, session: &mut if !content.trim().is_empty() { queue!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(content), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), )?; } diff --git a/crates/chat-cli/src/cli/chat/cli/reply.rs b/crates/chat-cli/src/cli/chat/cli/reply.rs index 0bddf2083e..642f90fe19 100644 --- a/crates/chat-cli/src/cli/chat/cli/reply.rs +++ b/crates/chat-cli/src/cli/chat/cli/reply.rs @@ -2,7 +2,6 @@ use clap::Args; use crossterm::execute; use crossterm::style::{ self, - Color, }; use super::editor::open_editor; @@ -11,6 +10,7 @@ use crate::cli::chat::{ ChatSession, ChatState, }; +use crate::theme::StyledText; /// Arguments to the `/reply` command. #[deny(missing_docs)] @@ -39,9 +39,9 @@ impl ReplyArgs { None => { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("\nNo assistant message found to reply to.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; return Ok(ChatState::PromptUser { @@ -55,9 +55,9 @@ impl ReplyArgs { Err(err) => { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("\nError opening editor: {}\n\n", err)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; return Ok(ChatState::PromptUser { @@ -71,9 +71,9 @@ impl ReplyArgs { true => { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("\nNo changes made in editor, not submitting.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; ChatState::PromptUser { @@ -83,18 +83,18 @@ impl ReplyArgs { false => { execute!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("\nContent loaded from editor. Submitting prompt...\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; // Display the content as if the user typed it execute!( session.stderr, - style::SetAttribute(style::Attribute::Reset), - style::SetForegroundColor(Color::Magenta), + StyledText::reset_attributes(), + StyledText::emphasis_fg(), style::Print("> "), - style::SetAttribute(style::Attribute::Reset), + StyledText::reset_attributes(), style::Print(&content), style::Print("\n") )?; diff --git a/crates/chat-cli/src/cli/chat/cli/subscribe.rs b/crates/chat-cli/src/cli/chat/cli/subscribe.rs index 36dd670f04..e72280e02b 100644 --- a/crates/chat-cli/src/cli/chat/cli/subscribe.rs +++ b/crates/chat-cli/src/cli/chat/cli/subscribe.rs @@ -1,8 +1,5 @@ use clap::Args; -use crossterm::style::{ - Color, - Stylize, -}; +use crossterm::style::Stylize; use crossterm::{ cursor, execute, @@ -20,6 +17,7 @@ use crate::cli::chat::{ with_spinner, }; use crate::os::Os; +use crate::theme::StyledText; use crate::util::system_info::is_remote; const SUBSCRIBE_TITLE_TEXT: &str = color_print::cstr! { "Subscribe to Q Developer Pro" }; @@ -45,9 +43,9 @@ impl SubscribeArgs { { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("\nYour Q Developer Pro subscription is managed through IAM Identity Center.\n\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } else if self.manage { queue!(session.stderr, style::Print("\n"),)?; @@ -56,24 +54,24 @@ impl SubscribeArgs { if status != ActualSubscriptionStatus::Active { queue!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("You don't seem to have a Q Developer Pro subscription. "), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("Use "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("/subscribe"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" to upgrade your subscription.\n\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } }, Err(err) => { queue!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("Failed to get subscription status: {}\n\n", err)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; }, } @@ -90,8 +88,8 @@ impl SubscribeArgs { execute!( session.stderr, style::Print(format!("Open this URL to manage your subscription: {}\n\n", url.blue())), - style::ResetColor, - style::SetForegroundColor(Color::Reset), + StyledText::reset(), + StyledText::reset(), )?; } } else { @@ -113,9 +111,9 @@ async fn upgrade_to_pro(os: &mut Os, session: &mut ChatSession) -> Result<(), Ch if status == ActualSubscriptionStatus::Active { queue!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Your Builder ID already has a Q Developer Pro subscription.\n\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(()); } @@ -123,9 +121,9 @@ async fn upgrade_to_pro(os: &mut Os, session: &mut ChatSession) -> Result<(), Ch Err(e) => { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("{}\n\n", e)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; // Don't exit early here, the check isn't required to subscribe. }, @@ -135,9 +133,9 @@ async fn upgrade_to_pro(os: &mut Os, session: &mut ChatSession) -> Result<(), Ch queue!( session.stderr, style::Print(SUBSCRIBE_TITLE_TEXT), - style::SetForegroundColor(Color::Grey), + StyledText::secondary_fg(), style::Print(format!("\n\n{}\n\n", SUBSCRIBE_TEXT)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), cursor::Show )?; @@ -151,18 +149,14 @@ async fn upgrade_to_pro(os: &mut Os, session: &mut ChatSession) -> Result<(), Ch ); let user_input = session.read_user_input(&prompt, true); - queue!( - session.stderr, - style::SetForegroundColor(Color::Reset), - style::Print("\n"), - )?; + queue!(session.stderr, StyledText::reset(), style::Print("\n"),)?; if !user_input.is_some_and(|i| ["y", "Y"].contains(&i.as_str())) { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Upgrade cancelled.\n\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(()); } @@ -180,13 +174,13 @@ async fn upgrade_to_pro(os: &mut Os, session: &mut ChatSession) -> Result<(), Ch if is_remote() || crate::util::open::open_url_async(&url).await.is_err() { queue!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(format!( "{} Having issues opening the AWS console? Try copy and pasting the URL > {}\n\n", "?".magenta(), url.blue() )), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } diff --git a/crates/chat-cli/src/cli/chat/cli/tangent.rs b/crates/chat-cli/src/cli/chat/cli/tangent.rs index fd54238c4f..e9e0444013 100644 --- a/crates/chat-cli/src/cli/chat/cli/tangent.rs +++ b/crates/chat-cli/src/cli/chat/cli/tangent.rs @@ -5,7 +5,6 @@ use clap::{ use crossterm::execute; use crossterm::style::{ self, - Color, }; use crate::cli::chat::{ @@ -18,6 +17,7 @@ use crate::cli::experiment::experiment_manager::{ ExperimentName, }; use crate::os::Os; +use crate::theme::StyledText; #[derive(Debug, PartialEq, Args)] pub struct TangentArgs { @@ -52,9 +52,9 @@ impl TangentArgs { if !ExperimentManager::is_enabled(os, ExperimentName::TangentMode) { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("\nTangent mode is disabled. Enable it with: q settings chat.enableTangentMode true\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -67,11 +67,11 @@ impl TangentArgs { if ExperimentManager::is_enabled(os, ExperimentName::Checkpoint) { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print( "⚠️ Checkpoint is disabled while in tangent mode. Please exit tangent mode if you want to use checkpoint.\n" ), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } if session.conversation.is_in_tangent_mode() { @@ -81,20 +81,20 @@ impl TangentArgs { execute!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("Restored conversation from checkpoint ("), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("↯"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(") with last conversation entry preserved.\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } else { execute!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("You need to be in tangent mode to use tail.\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } }, @@ -106,24 +106,24 @@ impl TangentArgs { execute!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("Restored conversation from checkpoint ("), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("↯"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("). - Returned to main conversation.\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } else { // Check if checkpoint is enabled if ExperimentManager::is_enabled(os, ExperimentName::Checkpoint) { execute!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print( "⚠️ Checkpoint is disabled while in tangent mode. Please exit tangent mode if you want to use checkpoint.\n" ), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } @@ -142,24 +142,24 @@ impl TangentArgs { execute!( session.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("Created a conversation checkpoint ("), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("↯"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("). Use "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&tangent_key_display), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" or "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("/tangent"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" to restore the conversation later.\n"), style::Print( "Note: this functionality is experimental and may change or be removed in the future.\n" ), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } }, diff --git a/crates/chat-cli/src/cli/chat/cli/todos.rs b/crates/chat-cli/src/cli/chat/cli/todos.rs index a7ac856a7e..a3232b0e97 100644 --- a/crates/chat-cli/src/cli/chat/cli/todos.rs +++ b/crates/chat-cli/src/cli/chat/cli/todos.rs @@ -19,6 +19,7 @@ use crate::cli::chat::{ ChatState, }; use crate::os::Os; +use crate::theme::StyledText; /// Defines subcommands that allow users to view and manage todo lists #[derive(Debug, PartialEq, Subcommand)] @@ -70,9 +71,9 @@ impl TodoSubcommand { if !TodoList::is_enabled(os) { execute!( session.stderr, - style::SetForegroundColor(style::Color::Red), + StyledText::error_fg(), style::Print("Todo lists are disabled. Enable them with: q settings chat.enableTodoList true\n"), - style::SetForegroundColor(style::Color::Reset) + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, diff --git a/crates/chat-cli/src/cli/chat/cli/tools.rs b/crates/chat-cli/src/cli/chat/cli/tools.rs index ec9d075c25..9aee173e71 100644 --- a/crates/chat-cli/src/cli/chat/cli/tools.rs +++ b/crates/chat-cli/src/cli/chat/cli/tools.rs @@ -8,10 +8,7 @@ use clap::{ Args, Subcommand, }; -use crossterm::style::{ - Attribute, - Color, -}; +use crossterm::style::Attribute; use crossterm::{ queue, style, @@ -34,6 +31,7 @@ use crate::cli::chat::{ trust_all_text, }; use crate::constants::help_text::tools_long_help; +use crate::theme::StyledText; use crate::util::consts::MCP_SERVER_TOOL_DELIMITER; /// Command-line arguments for managing tools in the chat session @@ -87,7 +85,7 @@ impl ToolsArgs { let width = (longest + 2).saturating_sub("Tool".len()) + 4; format!("Tool{:>width$}Permission", "", width = width) }), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n"), style::Print("▔".repeat(terminal_width)), )?; @@ -137,7 +135,7 @@ impl ToolsArgs { session.stderr, style::SetAttribute(Attribute::Bold), style::Print(format!("{}:\n", origin)), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print(to_display), style::Print("\n") ); @@ -149,11 +147,11 @@ impl ToolsArgs { session.stderr, style::SetAttribute(Attribute::Bold), style::Print("Servers loading (Some of these might need auth. See "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("/mcp"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(" for details)"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n"), style::Print("▔".repeat(terminal_width)), )?; @@ -168,21 +166,21 @@ impl ToolsArgs { style::Print( "\nNo tools are currently enabled.\n\nRefer to the documentation for how to add tools to your agent: " ), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(AGENT_FORMAT_TOOLS_DOC_URL), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } if !session.conversation.mcp_enabled { queue!( session.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("\n"), style::Print("⚠️ WARNING: "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("MCP functionality has been disabled by your administrator.\n\n"), )?; } @@ -265,14 +263,14 @@ impl ToolsSubcommand { if !invalid_tools.is_empty() { queue!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("\nCannot trust '{}', ", invalid_tools.join("', '"))), if invalid_tools.len() > 1 { style::Print("they do not exist.") } else { style::Print("it does not exist.") }, - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } if !valid_tools.is_empty() { @@ -291,7 +289,7 @@ impl ToolsSubcommand { queue!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), if tools_to_trust.len() > 1 { style::Print(format!("\nTools '{}' are ", tools_to_trust.join("', '"))) } else { @@ -300,8 +298,8 @@ impl ToolsSubcommand { style::Print("now trusted. I will "), style::SetAttribute(Attribute::Bold), style::Print("not"), - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::Green), + StyledText::reset_attributes(), + StyledText::success_fg(), style::Print(format!( " ask for confirmation before running {}.", if tools_to_trust.len() > 1 { @@ -311,7 +309,7 @@ impl ToolsSubcommand { } )), style::Print("\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; session.conversation.agents.trust_tools(tools_to_trust); @@ -326,14 +324,14 @@ impl ToolsSubcommand { if !invalid_tools.is_empty() { queue!( session.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("\nCannot untrust '{}', ", invalid_tools.join("', '"))), if invalid_tools.len() > 1 { style::Print("they do not exist.") } else { style::Print("it does not exist.") }, - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } if !valid_tools.is_empty() { @@ -354,14 +352,14 @@ impl ToolsSubcommand { queue!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), if tools_to_untrust.len() > 1 { style::Print(format!("\nTools '{}' are ", tools_to_untrust.join("', '"))) } else { style::Print(format!("\nTool '{}' is ", tools_to_untrust[0])) }, style::Print("set to per-request confirmation.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } }, @@ -401,9 +399,9 @@ impl ToolsSubcommand { } queue!( session.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("\nReset all tools to the permission levels as defined in agent."), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; }, }; diff --git a/crates/chat-cli/src/cli/chat/cli/usage.rs b/crates/chat-cli/src/cli/chat/cli/usage.rs index 4240bf4c2f..765465025f 100644 --- a/crates/chat-cli/src/cli/chat/cli/usage.rs +++ b/crates/chat-cli/src/cli/chat/cli/usage.rs @@ -1,8 +1,5 @@ use clap::Args; -use crossterm::style::{ - Attribute, - Color, -}; +use crossterm::style::Attribute; use crossterm::{ execute, queue, @@ -20,6 +17,7 @@ use crate::cli::chat::{ ChatState, }; use crate::os::Os; +use crate::theme::StyledText; /// Detailed usage data for context window analysis #[derive(Debug)] @@ -91,13 +89,13 @@ impl UsageArgs { if !usage_data.dropped_context_files.is_empty() { execute!( session.stderr, - style::SetForegroundColor(Color::DarkYellow), + StyledText::warning_fg(), style::Print("\nSome context files are dropped due to size limit, please run "), - style::SetForegroundColor(Color::DarkGreen), + StyledText::success_fg(), style::Print("/context show "), - style::SetForegroundColor(Color::DarkYellow), + StyledText::warning_fg(), style::Print("to learn more.\n"), - style::SetForegroundColor(style::Color::Reset) + StyledText::reset(), )?; } @@ -132,9 +130,9 @@ impl UsageArgs { usage_data.total_tokens, usage_data.context_window_size / 1000 )), - style::SetForegroundColor(Color::DarkRed), + StyledText::error_fg(), style::Print("█".repeat(progress_bar_width)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(" "), style::Print(format!("{:.2}%", total_percentage)), )?; @@ -147,7 +145,7 @@ impl UsageArgs { usage_data.context_window_size / 1000 )), // Context files - style::SetForegroundColor(Color::DarkCyan), + StyledText::brand_fg(), // add a nice visual to mimic "tiny" progress, so the overrall progress bar doesn't look too // empty style::Print( @@ -159,7 +157,7 @@ impl UsageArgs { ), style::Print("█".repeat(context_width)), // Tools - style::SetForegroundColor(Color::DarkRed), + StyledText::error_fg(), style::Print("|".repeat(if tools_width == 0 && usage_data.tools_tokens.value() > 0 { 1 } else { @@ -167,7 +165,7 @@ impl UsageArgs { })), style::Print("█".repeat(tools_width)), // Assistant responses - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::Print( "|".repeat(if assistant_width == 0 && usage_data.assistant_tokens.value() > 0 { 1 @@ -177,17 +175,17 @@ impl UsageArgs { ), style::Print("█".repeat(assistant_width)), // User prompts - style::SetForegroundColor(Color::Magenta), + StyledText::emphasis_fg(), style::Print("|".repeat(if user_width == 0 && usage_data.user_tokens.value() > 0 { 1 } else { 0 })), style::Print("█".repeat(user_width)), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("█".repeat(left_over_width)), style::Print(" "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(format!("{:.2}%", total_percentage)), )?; } @@ -196,33 +194,33 @@ impl UsageArgs { queue!( session.stderr, - style::SetForegroundColor(Color::DarkCyan), + StyledText::brand_fg(), style::Print("█ Context files: "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(format!( "~{} tokens ({:.2}%)\n", usage_data.context_tokens, calculate_usage_percentage(usage_data.context_tokens, usage_data.context_window_size) )), - style::SetForegroundColor(Color::DarkRed), + StyledText::error_fg(), style::Print("█ Tools: "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(format!( " ~{} tokens ({:.2}%)\n", usage_data.tools_tokens, calculate_usage_percentage(usage_data.tools_tokens, usage_data.context_window_size) )), - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::Print("█ Q responses: "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(format!( " ~{} tokens ({:.2}%)\n", usage_data.assistant_tokens, calculate_usage_percentage(usage_data.assistant_tokens, usage_data.context_window_size) )), - style::SetForegroundColor(Color::Magenta), + StyledText::emphasis_fg(), style::Print("█ Your prompts: "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(format!( " ~{} tokens ({:.2}%)\n\n", usage_data.user_tokens, @@ -234,24 +232,24 @@ impl UsageArgs { session.stderr, style::SetAttribute(Attribute::Bold), style::Print("\n💡 Pro Tips:\n"), - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::DarkGrey), + StyledText::reset_attributes(), + StyledText::secondary_fg(), style::Print("Run "), - style::SetForegroundColor(Color::DarkGreen), + StyledText::success_fg(), style::Print("/compact"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" to replace the conversation history with its summary\n"), style::Print("Run "), - style::SetForegroundColor(Color::DarkGreen), + StyledText::success_fg(), style::Print("/clear"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" to erase the entire chat history\n"), style::Print("Run "), - style::SetForegroundColor(Color::DarkGreen), + StyledText::success_fg(), style::Print("/context show"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" to see tokens per context file\n\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; Ok(ChatState::PromptUser { diff --git a/crates/chat-cli/src/cli/chat/conversation.rs b/crates/chat-cli/src/cli/chat/conversation.rs index 022e42e74d..b60b3de86b 100644 --- a/crates/chat-cli/src/cli/chat/conversation.rs +++ b/crates/chat-cli/src/cli/chat/conversation.rs @@ -7,7 +7,6 @@ use std::io::Write; use std::sync::atomic::Ordering; use chrono::Local; -use crossterm::style::Color; use crossterm::{ execute, style, @@ -84,6 +83,7 @@ use crate::cli::chat::cli::model::{ }; use crate::cli::chat::tools::custom_tool::CustomToolConfig; use crate::os::Os; +use crate::theme::StyledText; pub const CONTEXT_ENTRY_START_HEADER: &str = "--- CONTEXT ENTRY BEGIN ---\n"; pub const CONTEXT_ENTRY_END_HEADER: &str = "--- CONTEXT ENTRY END ---\n\n"; @@ -515,13 +515,13 @@ impl ConversationState { if !context.dropped_context_files.is_empty() { execute!( stderr, - style::SetForegroundColor(Color::DarkYellow), + StyledText::warning_fg(), style::Print("\nSome context files are dropped due to size limit, please run "), - style::SetForegroundColor(Color::DarkGreen), + StyledText::success_fg(), style::Print("/context show "), - style::SetForegroundColor(Color::DarkYellow), + StyledText::warning_fg(), style::Print("to learn more.\n"), - style::SetForegroundColor(style::Color::Reset) + StyledText::reset(), ) .ok(); } diff --git a/crates/chat-cli/src/cli/chat/mod.rs b/crates/chat-cli/src/cli/chat/mod.rs index aa5540d645..f82939bdf8 100644 --- a/crates/chat-cli/src/cli/chat/mod.rs +++ b/crates/chat-cli/src/cli/chat/mod.rs @@ -1,3 +1,4 @@ +use crate::theme::StyledText; pub mod cli; mod consts; pub mod context; @@ -13,14 +14,7 @@ mod prompt; mod prompt_parser; pub mod server_messenger; use crate::cli::chat::checkpoint::CHECKPOINT_MESSAGE_MAX_LENGTH; -use crate::constants::ui_text::{ - LIMIT_REACHED_TEXT, - POPULAR_SHORTCUTS, - RESUME_TEXT, - SMALL_SCREEN_POPULAR_SHORTCUTS, - SMALL_SCREEN_WELCOME, - WELCOME_TEXT, -}; +use crate::constants::ui_text; #[cfg(unix)] mod skim_integration; mod token_counter; @@ -62,7 +56,6 @@ pub use conversation::ConversationState; use conversation::TokenWarningLevel; use crossterm::style::{ Attribute, - Color, Stylize, }; use crossterm::{ @@ -172,7 +165,6 @@ use crate::cli::experiment::experiment_manager::{ use crate::constants::{ error_messages, tips, - ui_text, }; use crate::database::settings::Setting; use crate::os::Os; @@ -268,13 +260,13 @@ impl ChatArgs { { execute!( stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("WARNING: "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("--profile is deprecated, use "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("--agent"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(" instead\n") )?; } @@ -331,17 +323,17 @@ impl ChatArgs { if !tool.starts_with("@") && !NATIVE_TOOLS.contains(&tool.as_str()) { let _ = queue!( stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("WARNING: "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("--trust-tools arg for custom tool "), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(tool), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(" needs to be prepended with "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("@{MCPSERVERNAME}/"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n"), ); } @@ -390,13 +382,13 @@ impl ChatArgs { } else { let _ = execute!( stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("WARNING: "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("Agent specifies model '"), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(agent_model), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("' which is not available. Falling back to configured defaults.\n"), ); fallback_model_id() @@ -446,9 +438,6 @@ impl ChatArgs { // Maximum number of times to show the changelog announcement per version const CHANGELOG_MAX_SHOW_COUNT: i64 = 2; -// Only show the model-related tip for now to make users aware of this feature. -const ROTATING_TIPS: [&str; 20] = tips::ROTATING_TIPS; - const GREETING_BREAK_POINT: usize = 80; const RESPONSE_TIMEOUT_CONTENT: &str = "Response timed out - message took too long to generate"; @@ -641,9 +630,9 @@ impl ChatSession { if agents.switch(profile).is_err() { execute!( stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Error"), - style::ResetColor, + StyledText::reset(), style::Print(format!( ": cannot resume conversation with {profile} because it no longer exists. Using default.\n" )) @@ -842,9 +831,9 @@ impl ChatSession { // their context. execute!( self.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Your conversation is too large to continue.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(format!( "• Run {} to compact your conversation. See {} for compaction options\n", "/compact".green(), @@ -852,7 +841,7 @@ impl ChatSession { )), style::Print(format!("• Run {} to analyze your context usage\n", "/usage".green())), style::Print(format!("• Run {} to reset your conversation state\n", "/clear".green())), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n\n"), )?; ("Unable to compact the conversation history", eyre!(err), true) @@ -869,11 +858,11 @@ impl ChatSession { { execute!( self.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("The conversation history has overflowed.\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(format!("• Run {} to compact your conversation\n", "/compact".green())), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n\n"), )?; ("The conversation history has overflowed", eyre!(err), false) @@ -894,9 +883,9 @@ impl ChatSession { execute!( self.stdout, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("The context window has overflowed, summarizing the history..."), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n\n"), )?; @@ -912,12 +901,12 @@ impl ChatSession { execute!( self.stderr, style::SetAttribute(Attribute::Bold), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(error_messages::RATE_LIMIT_PREFIX), style::Print("\n"), style::Print(format!(" {}\n\n", err.clone())), - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::Reset), + StyledText::reset_attributes(), + StyledText::reset(), )?; (error_messages::TROUBLE_RESPONDING, eyre!(err), false) }, @@ -926,12 +915,12 @@ impl ChatSession { execute!( self.stderr, style::SetAttribute(Attribute::Bold), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print( "\nThe model you've selected is temporarily unavailable. Please select a different model.\n" ), - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::Reset), + StyledText::reset_attributes(), + StyledText::reset(), )?; if let Some(id) = request_id { @@ -958,11 +947,11 @@ impl ChatSession { execute!( self.stderr, style::SetAttribute(Attribute::Bold), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("{}:\n", error_messages::TROUBLE_RESPONDING)), style::Print(format!(" {}\n", err.clone())), - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::Reset), + StyledText::reset_attributes(), + StyledText::reset(), )?; (error_messages::TROUBLE_RESPONDING, eyre!(err), false) }, @@ -971,20 +960,20 @@ impl ChatSession { if subscription_status.is_err() { execute!( self.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!( "Unable to verify subscription status: {}\n\n", subscription_status.as_ref().err().unwrap() )), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } execute!( self.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Monthly request limit reached"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; let limits_text = format!( @@ -997,21 +986,21 @@ impl ChatSession { { execute!( self.stderr, - style::Print(format!("\n\n{LIMIT_REACHED_TEXT} {limits_text}")), - style::SetForegroundColor(Color::DarkGrey), + style::Print(format!("\n\n{} {limits_text}", ui_text::limit_reached_text())), + StyledText::secondary_fg(), style::Print("\n\nUse "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("/subscribe"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(" to upgrade your subscription.\n\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } else { execute!( self.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(format!(" - {limits_text}\n\n")), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } @@ -1033,7 +1022,7 @@ impl ChatSession { queue!( self.stderr, style::SetAttribute(Attribute::Bold), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), )?; let text = re.replace_all(&format!("{}: {:?}\n", context, report), "").into_owned(); @@ -1041,11 +1030,7 @@ impl ChatSession { queue!(self.stderr, style::Print(&text),)?; self.conversation.append_transcript(text); - execute!( - self.stderr, - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::Reset), - )?; + execute!(self.stderr, StyledText::reset_attributes(), StyledText::reset(),)?; } self.conversation.enforce_conversation_invariants(); @@ -1107,8 +1092,8 @@ impl Drop for ChatSession { execute!( self.stderr, cursor::MoveToColumn(0), - style::SetAttribute(Attribute::Reset), - style::ResetColor, + StyledText::reset_attributes(), + StyledText::reset(), cursor::Show ) .ok(); @@ -1197,16 +1182,17 @@ impl ChatSession { .unwrap_or(true) { let welcome_text = match self.existing_conversation { - true => RESUME_TEXT, + true => ui_text::resume_text(), false => match is_small_screen { - true => SMALL_SCREEN_WELCOME, - false => WELCOME_TEXT, + true => ui_text::small_screen_welcome(), + false => ui_text::welcome_text(), }, }; - execute!(self.stderr, style::Print(welcome_text), style::Print("\n\n"),)?; + execute!(self.stderr, style::Print(&welcome_text), style::Print("\n\n"),)?; - let tip = ROTATING_TIPS[usize::try_from(rand::random::()).unwrap_or(0) % ROTATING_TIPS.len()]; + let rotating_tips = tips::get_rotating_tips(); + let tip = &rotating_tips[usize::try_from(rand::random::()).unwrap_or(0) % rotating_tips.len()]; if is_small_screen { // If the screen is small, print the tip in a single line execute!( @@ -1221,16 +1207,16 @@ impl ChatSession { "Did you know?", tip, GREETING_BREAK_POINT, - Color::DarkGrey, + crate::theme::theme().ui.secondary_text, )?; } execute!( self.stderr, style::Print("\n"), - style::Print(match is_small_screen { - true => SMALL_SCREEN_POPULAR_SHORTCUTS, - false => POPULAR_SHORTCUTS, + style::Print(&match is_small_screen { + true => ui_text::small_screen_popular_shortcuts(), + false => ui_text::popular_shortcuts(), }), style::Print("\n"), style::Print( @@ -1239,7 +1225,7 @@ impl ChatSession { .dark_grey() ) )?; - execute!(self.stderr, style::Print("\n"), style::SetForegroundColor(Color::Reset))?; + execute!(self.stderr, style::Print("\n"), StyledText::reset())?; } // Check if we should show the whats-new announcement @@ -1268,9 +1254,9 @@ impl ChatSession { let display_name = model_option.model_name.as_deref().unwrap_or(&model_option.model_id); execute!( self.stderr, - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(format!("🤖 You are chatting with {}\n", display_name)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print("\n") )?; } @@ -1371,9 +1357,9 @@ impl ChatSession { if self.conversation.history().is_empty() { execute!( self.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("\nConversation too short to compact.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; return Ok(ChatState::PromptUser { @@ -1387,9 +1373,9 @@ impl ChatSession { self.stderr, terminal::Clear(terminal::ClearType::CurrentLine), cursor::MoveToColumn(0), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("Truncating large messages..."), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n\n"), )?; } @@ -1421,7 +1407,7 @@ impl ChatSession { self.stderr, terminal::Clear(terminal::ClearType::CurrentLine), cursor::MoveToColumn(0), - style::SetAttribute(Attribute::Reset) + StyledText::reset_attributes() )?; } @@ -1542,9 +1528,9 @@ impl ChatSession { { execute!( self.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("✔ Conversation history has been compacted successfully!\n\n"), - style::SetForegroundColor(Color::DarkGrey) + StyledText::secondary_fg() )?; let mut output = Vec::new(); @@ -1564,14 +1550,14 @@ impl ChatSession { execute!( self.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print(&border), style::Print("\n"), style::SetAttribute(Attribute::Bold), style::Print(" CONVERSATION SUMMARY"), style::Print("\n"), style::Print(&border), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n\n"), )?; @@ -1579,7 +1565,7 @@ impl ChatSession { output, style::Print(&summary), style::Print("\n\n"), - style::SetForegroundColor(Color::Cyan), + StyledText::brand_fg(), style::Print("The conversation history has been replaced with this summary.\n"), style::Print("It contains all important details from previous interactions.\n"), )?; @@ -1589,7 +1575,7 @@ impl ChatSession { self.stderr, style::Print(&border), style::Print("\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } } @@ -1663,9 +1649,9 @@ impl ChatSession { if agent_name.trim().is_empty() || agent_description.trim().is_empty() { execute!( self.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("\nAgent name and description cannot be empty.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; return Ok(ChatState::PromptUser { @@ -1719,7 +1705,7 @@ impl ChatSession { self.stderr, terminal::Clear(terminal::ClearType::CurrentLine), cursor::MoveToColumn(0), - style::SetAttribute(Attribute::Reset) + StyledText::reset_attributes() )?; } return Err(err); @@ -1780,9 +1766,9 @@ impl ChatSession { Err(_) => { execute!( self.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("✗ The LLM did not generate a valid agent configuration. Please try again.\n\n"), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1801,9 +1787,9 @@ impl ChatSession { Err(err) => { execute!( self.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("✗ Invalid edited configuration: {}\n\n", err)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; return Ok(ChatState::PromptUser { skip_printing_tools: true, @@ -1815,21 +1801,21 @@ impl ChatSession { if let Err(err) = save_agent_config(os, &final_agent_config, agent_name, is_global).await { execute!( self.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("✗ Failed to save agent config: {}\n\n", err)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; return Err(err); } execute!( self.stderr, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format!( "✓ Agent '{}' has been created and saved successfully!\n", agent_name )), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; Ok(ChatState::PromptUser { @@ -1853,25 +1839,25 @@ impl ChatSession { if show_tool_use_confirmation_dialog { execute!( self.stderr, - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("\nAllow this action? Use '"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("t"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("' to trust (always allow) this tool for the session. ["), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("y"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("/"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("n"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("/"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("t"), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print("]:\n\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } @@ -1896,11 +1882,7 @@ impl ChatSession { .put_skim_command_selector(os, Arc::new(context_manager.clone()), tool_names); } - execute!( - self.stderr, - style::SetForegroundColor(Color::Reset), - style::SetAttribute(Attribute::Reset) - )?; + execute!(self.stderr, StyledText::reset(), StyledText::reset_attributes())?; let prompt = self.generate_tool_trust_prompt(os).await; let user_input = match self.read_user_input(&prompt, false) { Some(input) => input, @@ -1959,9 +1941,9 @@ impl ChatSession { Err(err) => { queue!( self.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("\nFailed to execute command: {}\n", err)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; let _ = self .send_slash_command_telemetry( @@ -2044,18 +2026,18 @@ impl ChatSession { if !status.success() { queue!( self.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(format!("Self exited with status: {}\n", status)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } }, Err(e) => { queue!( self.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("\nFailed to execute command: {}\n", e)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; }, } @@ -2142,8 +2124,8 @@ impl ChatSession { .await?; self.send_tool_use_telemetry(os).await; - queue!(self.stderr, style::SetForegroundColor(Color::Magenta))?; - queue!(self.stderr, style::SetForegroundColor(Color::Reset))?; + queue!(self.stderr, StyledText::emphasis_fg())?; + queue!(self.stderr, StyledText::reset())?; queue!(self.stderr, cursor::Hide)?; if self.interactive { @@ -2203,15 +2185,15 @@ impl ChatSession { execute!( self.stderr, - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print("Command "), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(&tool.name), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(" is rejected because it matches one or more rules on the denied list:"), style::Print(formatted_set), style::Print("\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; return Ok(ChatState::HandleInput { @@ -2311,10 +2293,10 @@ impl ChatSession { Err(e) => { execute!( self.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(format!("Could not check if uncommitted changes exist: {e}\n")), style::Print("Saving anyways...\n"), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; true }, @@ -2414,19 +2396,19 @@ impl ChatSession { self.stdout, style::Print(CONTINUATION_LINE), style::Print("\n"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::SetAttribute(Attribute::Bold), style::Print(format!(" ● Completed in {}s", tool_time)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; if let Some(tag) = checkpoint_tag { execute!( self.stdout, - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::SetAttribute(Attribute::Bold), style::Print(format!(" [{tag}]")), - style::SetForegroundColor(Color::Reset), - style::SetAttribute(Attribute::Reset), + StyledText::reset(), + StyledText::reset_attributes(), )?; } execute!(self.stdout, style::Print("\n\n"))?; @@ -2476,12 +2458,12 @@ impl ChatSession { style::Print(CONTINUATION_LINE), style::Print("\n"), style::SetAttribute(Attribute::Bold), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!(" ● Execution failed after {}s:\n", tool_time)), - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::Red), + StyledText::reset_attributes(), + StyledText::error_fg(), style::Print(&err), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print("\n\n"), )?; @@ -2557,8 +2539,8 @@ impl ChatSession { self.conversation.add_tool_results_with_images(tool_results, images); execute!( self.stderr, - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::Reset), + StyledText::reset_attributes(), + StyledText::reset(), style::Print("\n") )?; } else { @@ -2566,7 +2548,7 @@ impl ChatSession { } execute!(self.stderr, cursor::Hide)?; - execute!(self.stderr, style::Print("\n"), style::SetAttribute(Attribute::Reset))?; + execute!(self.stderr, style::Print("\n"), StyledText::reset_attributes())?; if self.interactive { self.spinner = Some(Spinner::new(Spinners::Dots, "Thinking...".to_string())); } @@ -2626,7 +2608,7 @@ impl ChatSession { drop(self.spinner.take()); queue!( self.stderr, - style::SetForegroundColor(Color::Reset), + StyledText::reset(), cursor::MoveToColumn(0), cursor::Show, terminal::Clear(terminal::ClearType::CurrentLine), @@ -2649,9 +2631,9 @@ impl ChatSession { if !response_prefix_printed && !text.trim().is_empty() { queue!( self.stdout, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print("> "), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; response_prefix_printed = true; } @@ -2808,12 +2790,12 @@ impl ChatSession { let _ = queue!( self.stdout, style::Print("\n\n"), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(format!( "Tool validation failed: {}\n Retrying the request...", error_message )), - style::ResetColor, + StyledText::reset(), style::Print("\n"), ); self.conversation.add_tool_results(tool_results); @@ -2902,18 +2884,18 @@ impl ChatSession { play_notification_bell(tool_uses.is_empty()); } - queue!(self.stderr, style::ResetColor, style::SetAttribute(Attribute::Reset))?; + queue!(self.stderr, StyledText::reset(), StyledText::reset_attributes())?; execute!(self.stdout, style::Print("\n"))?; for (i, citation) in &state.citations { queue!( self.stdout, style::Print("\n"), - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::Print(format!("[^{i}]: ")), - style::SetForegroundColor(Color::DarkGrey), + StyledText::secondary_fg(), style::Print(format!("{citation}\n")), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; } @@ -2953,18 +2935,18 @@ impl ChatSession { ) { execute!( self.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print(format!("⚠️ Could not create automatic checkpoint: {}\n\n", e)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } else { execute!( self.stderr, - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::SetAttribute(Attribute::Bold), style::Print(format!("✓ Created checkpoint {}\n\n", tag)), - style::SetForegroundColor(Color::Reset), - style::SetAttribute(Attribute::Reset), + StyledText::reset(), + StyledText::reset_attributes(), )?; } @@ -3066,7 +3048,7 @@ impl ChatSession { self.stderr, style::SetAttribute(Attribute::Bold), style::Print("Tool validation failed: "), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), )?; for tool_result in &tool_results { for block in &tool_result.content { @@ -3081,9 +3063,9 @@ impl ChatSession { queue!( self.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("{}\n", content)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } } @@ -3163,9 +3145,9 @@ impl ChatSession { queue!( self.stderr, style::Print("\n"), - style::SetForegroundColor(Color::Red), + StyledText::error_fg(), style::Print(format!("{}\n", content)), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } } @@ -3243,22 +3225,22 @@ impl ChatSession { queue!( self.stdout, - style::SetForegroundColor(Color::Magenta), + StyledText::emphasis_fg(), style::Print(format!( "🛠️ Using tool: {}{}", tool_use.tool.display_name(), if trusted { " (trusted)".dark_green() } else { "".reset() } )), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; if let Tool::Custom(ref tool) = tool_use.tool { queue!( self.stdout, - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(" from mcp server "), - style::SetForegroundColor(Color::Magenta), + StyledText::emphasis_fg(), style::Print(&tool.server_name), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; } @@ -3363,14 +3345,14 @@ impl ChatSession { // Memory constraint warning with gentler wording execute!( self.stderr, - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::SetAttribute(Attribute::Bold), style::Print("\n⚠️ This conversation is getting lengthy.\n"), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), style::Print( "To ensure continued smooth operation, please use /compact to summarize the conversation.\n\n" ), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), )?; }, TokenWarningLevel::None => { diff --git a/crates/chat-cli/src/cli/chat/parse.rs b/crates/chat-cli/src/cli/chat/parse.rs index ed30f3944b..cf457b56f3 100644 --- a/crates/chat-cli/src/cli/chat/parse.rs +++ b/crates/chat-cli/src/cli/chat/parse.rs @@ -2,7 +2,6 @@ use std::io::Write; use crossterm::style::{ Attribute, - Color, Stylize, }; use crossterm::{ @@ -45,11 +44,7 @@ use winnow::token::{ take_while, }; -const CODE_COLOR: Color = Color::Green; -const HEADING_COLOR: Color = Color::Magenta; -const BLOCKQUOTE_COLOR: Color = Color::DarkGrey; -const URL_TEXT_COLOR: Color = Color::Blue; -const URL_LINK_COLOR: Color = Color::DarkGrey; +use crate::theme::StyledText; const DEFAULT_RULE_WIDTH: usize = 40; @@ -228,7 +223,7 @@ fn heading<'a, 'b>( let print = format!("{level} "); queue_newline_or_advance(&mut o, state, print.width())?; - queue(&mut o, style::SetForegroundColor(HEADING_COLOR))?; + queue(&mut o, StyledText::emphasis_fg())?; queue(&mut o, style::SetAttribute(Attribute::Bold))?; queue(&mut o, style::Print(print)) } @@ -301,9 +296,9 @@ fn code<'a, 'b>( let out = code.replace("&", "&").replace(">", ">").replace("<", "<"); queue_newline_or_advance(&mut o, state, out.width())?; - queue(&mut o, style::SetForegroundColor(Color::Green))?; + queue(&mut o, StyledText::success_fg())?; queue(&mut o, style::Print(out))?; - queue(&mut o, style::ResetColor) + queue(&mut o, StyledText::reset()) } } @@ -321,7 +316,7 @@ fn blockquote<'a, 'b>( .len(); let print = "│ ".repeat(level); - queue(&mut o, style::SetForegroundColor(BLOCKQUOTE_COLOR))?; + queue(&mut o, StyledText::secondary_fg())?; queue_newline_or_advance(&mut o, state, print.width())?; queue(&mut o, style::Print(print)) } @@ -410,9 +405,9 @@ fn citation<'a, 'b>( state.citations.push((num.to_owned(), link.to_owned())); queue_newline_or_advance(&mut o, state, num.width() + 1)?; - queue(&mut o, style::SetForegroundColor(URL_TEXT_COLOR))?; + queue(&mut o, StyledText::info_fg())?; queue(&mut o, style::Print(format!("[^{num}]")))?; - queue(&mut o, style::ResetColor) + queue(&mut o, StyledText::reset()) } } @@ -446,12 +441,12 @@ fn url<'a, 'b>( // Only generate output if the complete URL pattern matches queue_newline_or_advance(&mut o, state, display.width() + 1)?; - queue(&mut o, style::SetForegroundColor(URL_TEXT_COLOR))?; + queue(&mut o, StyledText::info_fg())?; queue(&mut o, style::Print(format!("{display} ")))?; - queue(&mut o, style::SetForegroundColor(URL_LINK_COLOR))?; + queue(&mut o, StyledText::secondary_fg())?; state.column += link.width(); queue(&mut o, style::Print(link))?; - queue(&mut o, style::ResetColor) + queue(&mut o, StyledText::reset()) } } @@ -509,8 +504,8 @@ fn line_ending<'a, 'b>( state.column = 0; state.set_newline = true; - queue(&mut o, style::ResetColor)?; - queue(&mut o, style::SetAttribute(style::Attribute::Reset))?; + queue(&mut o, StyledText::reset())?; + queue(&mut o, StyledText::reset_attributes())?; queue(&mut o, style::Print("\n")) } } @@ -577,7 +572,7 @@ fn codeblock_begin<'a, 'b>( queue(&mut o, style::Print(format!("{}\n", language).bold()))?; } - queue(&mut o, style::SetForegroundColor(CODE_COLOR))?; + queue(&mut o, StyledText::success_fg())?; Ok(()) } @@ -590,7 +585,7 @@ fn codeblock_end<'a, 'b>( move |i| { "```".parse_next(i)?; state.in_codeblock = false; - queue(&mut o, style::ResetColor) + queue(&mut o, StyledText::reset()) } } @@ -713,27 +708,27 @@ mod tests { validate!(linted_codeblock_1, "```java\nhello world!```", [ style::SetAttribute(Attribute::Bold), style::Print("java\n"), - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(CODE_COLOR), + StyledText::reset_attributes(), + StyledText::success_fg(), style::Print("hello world!"), - style::ResetColor, + StyledText::reset(), ]); validate!(code_1, "`print`", [ - style::SetForegroundColor(CODE_COLOR), + StyledText::success_fg(), style::Print("print"), - style::ResetColor, + StyledText::reset(), ]); validate!(url_1, "[google](google.com)", [ - style::SetForegroundColor(URL_TEXT_COLOR), + StyledText::info_fg(), style::Print("google "), - style::SetForegroundColor(URL_LINK_COLOR), + StyledText::secondary_fg(), style::Print("google.com"), - style::ResetColor, + StyledText::reset(), ]); validate!(citation_1, "[[1]](google.com)", [ - style::SetForegroundColor(URL_TEXT_COLOR), + StyledText::info_fg(), style::Print("[^1]"), - style::ResetColor, + StyledText::reset(), ]); validate!(bold_1, "**hello**", [ style::SetAttribute(Attribute::Bold), @@ -757,7 +752,7 @@ mod tests { validate!(fallback_1, "+ % @ . ? ", [style::Print("+ % @ . ?")]); validate!(horizontal_rule_1, "---", [style::Print("━".repeat(80))]); validate!(heading_1, "# Hello World", [ - style::SetForegroundColor(HEADING_COLOR), + StyledText::emphasis_fg(), style::SetAttribute(Attribute::Bold), style::Print("# Hello World"), ]); @@ -765,7 +760,7 @@ mod tests { validate!(bulleted_item_2, "* bullet", [style::Print("• bullet")]); validate!(numbered_item_1, "1. number", [style::Print("1. number")]); validate!(blockquote_1, "> hello", [ - style::SetForegroundColor(BLOCKQUOTE_COLOR), + StyledText::secondary_fg(), style::Print("│ hello"), ]); validate!(square_bracket_1, "[test]", [style::Print("[test]")]); @@ -816,8 +811,8 @@ mod tests { "line one\nline two", [ style::Print("line one"), - style::ResetColor, - style::SetAttribute(style::Attribute::Reset), + StyledText::reset(), + StyledText::reset_attributes(), style::Print("\n"), style::Print("line two") ], diff --git a/crates/chat-cli/src/cli/chat/prompt.rs b/crates/chat-cli/src/cli/chat/prompt.rs index b741657ab8..1c936bf044 100644 --- a/crates/chat-cli/src/cli/chat/prompt.rs +++ b/crates/chat-cli/src/cli/chat/prompt.rs @@ -413,47 +413,47 @@ impl Highlighter for ChatHelper { } fn highlight_prompt<'b, 's: 'b, 'p: 'b>(&'s self, prompt: &'p str, _default: bool) -> Cow<'b, str> { - use crossterm::style::Stylize; + use crate::theme::StyledText; // Parse the plain text prompt to extract profile and warning information // and apply colors using crossterm's ANSI escape codes if let Some(components) = parse_prompt_components(prompt) { let mut result = String::new(); - // Add notifier part if present (blue) + // Add notifier part if present (info blue) if let Some(notifier) = components.delegate_notifier { - result.push_str(&format!("[{}]\n", notifier).blue().to_string()); + result.push_str(&StyledText::info(&format!("[{}]\n", notifier))); } - // Add profile part if present (cyan) + // Add profile part if present (profile indicator cyan) if let Some(profile) = components.profile { - result.push_str(&format!("[{}] ", profile).cyan().to_string()); + result.push_str(&StyledText::profile(&format!("[{}] ", profile))); } // Add percentage part if present (colored by usage level) if let Some(percentage) = components.usage_percentage { let colored_percentage = if percentage < 50.0 { - format!("{}% ", percentage as u32).green() + StyledText::usage_low(&format!("{}% ", percentage as u32)) } else if percentage < 90.0 { - format!("{}% ", percentage as u32).yellow() + StyledText::usage_medium(&format!("{}% ", percentage as u32)) } else { - format!("{}% ", percentage as u32).red() + StyledText::usage_high(&format!("{}% ", percentage as u32)) }; - result.push_str(&colored_percentage.to_string()); + result.push_str(&colored_percentage); } - // Add tangent indicator if present (yellow) + // Add tangent indicator if present (tangent yellow) if components.tangent_mode { - result.push_str(&"↯ ".yellow().to_string()); + result.push_str(&StyledText::tangent("↯ ")); } - // Add warning symbol if present (red) + // Add warning symbol if present (error red) if components.warning { - result.push_str(&"!".red().to_string()); + result.push_str(&StyledText::error("!")); } - // Add the prompt symbol (magenta) - result.push_str(&"> ".magenta().to_string()); + // Add the prompt symbol (prompt magenta) + result.push_str(&StyledText::prompt("> ")); Cow::Owned(result) } else { @@ -554,12 +554,12 @@ pub fn rl( #[cfg(test)] mod tests { - use crossterm::style::Stylize; use rustyline::highlight::Highlighter; use rustyline::history::DefaultHistory; use super::*; use crate::cli::experiment::experiment_manager::ExperimentName; + use crate::theme::StyledText; #[tokio::test] async fn test_chat_completer_command_completion() { @@ -631,7 +631,7 @@ mod tests { // Test basic prompt highlighting let highlighted = helper.highlight_prompt("> ", true); - assert_eq!(highlighted, "> ".magenta().to_string()); + assert_eq!(highlighted, StyledText::prompt("> ")); } #[tokio::test] @@ -655,7 +655,10 @@ mod tests { // Test warning prompt highlighting let highlighted = helper.highlight_prompt("!> ", true); - assert_eq!(highlighted, format!("{}{}", "!".red(), "> ".magenta())); + assert_eq!( + highlighted, + format!("{}{}", StyledText::error("!"), StyledText::prompt("> ")) + ); } #[tokio::test] @@ -679,7 +682,10 @@ mod tests { // Test profile prompt highlighting let highlighted = helper.highlight_prompt("[test-profile] > ", true); - assert_eq!(highlighted, format!("{}{}", "[test-profile] ".cyan(), "> ".magenta())); + assert_eq!( + highlighted, + format!("{}{}", StyledText::profile("[test-profile] "), StyledText::prompt("> ")) + ); } #[tokio::test] @@ -705,7 +711,12 @@ mod tests { // Should have cyan profile + red warning + cyan bold prompt assert_eq!( highlighted, - format!("{}{}{}", "[dev] ".cyan(), "!".red(), "> ".magenta()) + format!( + "{}{}{}", + StyledText::profile("[dev] "), + StyledText::error("!"), + StyledText::prompt("> ") + ) ); } @@ -753,7 +764,10 @@ mod tests { // Test tangent mode prompt highlighting - ↯ yellow, > magenta let highlighted = helper.highlight_prompt("↯ > ", true); - assert_eq!(highlighted, format!("{}{}", "↯ ".yellow(), "> ".magenta())); + assert_eq!( + highlighted, + format!("{}{}", StyledText::tangent("↯ "), StyledText::prompt("> ")) + ); } #[tokio::test] @@ -776,7 +790,15 @@ mod tests { // Test tangent mode with warning - ↯ yellow, ! red, > magenta let highlighted = helper.highlight_prompt("↯ !> ", true); - assert_eq!(highlighted, format!("{}{}{}", "↯ ".yellow(), "!".red(), "> ".magenta())); + assert_eq!( + highlighted, + format!( + "{}{}{}", + StyledText::tangent("↯ "), + StyledText::error("!"), + StyledText::prompt("> ") + ) + ); } #[tokio::test] @@ -801,7 +823,12 @@ mod tests { let highlighted = helper.highlight_prompt("[dev] ↯ > ", true); assert_eq!( highlighted, - format!("{}{}{}", "[dev] ".cyan(), "↯ ".yellow(), "> ".magenta()) + format!( + "{}{}{}", + StyledText::profile("[dev] "), + StyledText::tangent("↯ "), + StyledText::prompt("> ") + ) ); } diff --git a/crates/chat-cli/src/cli/chat/tool_manager.rs b/crates/chat-cli/src/cli/chat/tool_manager.rs index 8834318540..07c1c8a7df 100644 --- a/crates/chat-cli/src/cli/chat/tool_manager.rs +++ b/crates/chat-cli/src/cli/chat/tool_manager.rs @@ -96,6 +96,7 @@ use crate::mcp_client::{ }; use crate::os::Os; use crate::telemetry::TelemetryThread; +use crate::theme::StyledText; use crate::util::MCP_SERVER_TOOL_DELIMITER; use crate::util::directories::home_dir; @@ -301,15 +302,15 @@ impl ToolManagerBuilder { if server_name == "builtin" { let _ = queue!( output, - style::SetForegroundColor(style::Color::Red), + StyledText::error_fg(), style::Print("✗ Invalid server name "), - style::SetForegroundColor(style::Color::Blue), + StyledText::info_fg(), style::Print(&server_name), - style::ResetColor, + StyledText::reset(), style::Print(". Server name cannot contain reserved word "), - style::SetForegroundColor(style::Color::Yellow), + StyledText::warning_fg(), style::Print("builtin"), - style::ResetColor, + StyledText::reset(), style::Print(" (it is used to denote native tools)\n") ); false @@ -1900,15 +1901,15 @@ fn sanitize_name(orig: String, regex: ®ex::Regex, hasher: &mut impl Hasher) - fn queue_success_message(name: &str, time_taken: &str, output: &mut impl Write) -> eyre::Result<()> { Ok(queue!( output, - style::SetForegroundColor(style::Color::Green), + StyledText::success_fg(), style::Print("✓ "), - style::SetForegroundColor(style::Color::Blue), + StyledText::info_fg(), style::Print(name), - style::ResetColor, + StyledText::reset(), style::Print(" loaded in "), - style::SetForegroundColor(style::Color::Yellow), + StyledText::warning_fg(), style::Print(format!("{time_taken} s\n")), - style::ResetColor, + StyledText::reset(), )?) } @@ -1920,39 +1921,29 @@ fn queue_init_message( output: &mut impl Write, ) -> eyre::Result<()> { if total == complete { - queue!( - output, - style::SetForegroundColor(style::Color::Green), - style::Print("✓"), - style::ResetColor, - )?; + queue!(output, StyledText::success_fg(), style::Print("✓"), StyledText::reset(),)?; } else if total == complete + failed { - queue!( - output, - style::SetForegroundColor(style::Color::Red), - style::Print("✗"), - style::ResetColor, - )?; + queue!(output, StyledText::error_fg(), style::Print("✗"), StyledText::reset(),)?; } else { queue!(output, style::Print(SPINNER_CHARS[spinner_logo_idx]))?; } queue!( output, - style::SetForegroundColor(style::Color::Blue), + StyledText::info_fg(), style::Print(format!(" {}", complete)), - style::ResetColor, + StyledText::reset(), style::Print(" of "), - style::SetForegroundColor(style::Color::Blue), + StyledText::info_fg(), style::Print(format!("{} ", total)), - style::ResetColor, + StyledText::reset(), style::Print("mcp servers initialized."), )?; if total > complete + failed { queue!( output, - style::SetForegroundColor(style::Color::Blue), + StyledText::info_fg(), style::Print(" ctrl-c "), - style::ResetColor, + StyledText::reset(), style::Print("to start chatting now") )?; } @@ -1968,33 +1959,33 @@ fn queue_failure_message( use crate::util::CHAT_BINARY_NAME; Ok(queue!( output, - style::SetForegroundColor(style::Color::Red), + StyledText::error_fg(), style::Print("✗ "), - style::SetForegroundColor(style::Color::Blue), + StyledText::info_fg(), style::Print(name), - style::ResetColor, + StyledText::reset(), style::Print(" has failed to load after"), - style::SetForegroundColor(style::Color::Yellow), + StyledText::warning_fg(), style::Print(format!(" {time} s")), - style::ResetColor, + StyledText::reset(), style::Print("\n - "), style::Print(fail_load_msg), style::Print("\n"), style::Print(format!( " - run with Q_LOG_LEVEL=trace and see $TMPDIR/qlog/{CHAT_BINARY_NAME}.log for detail\n" )), - style::ResetColor, + StyledText::reset(), )?) } fn queue_oauth_message(name: &str, output: &mut impl Write) -> eyre::Result<()> { Ok(queue!( output, - style::SetForegroundColor(style::Color::Yellow), + StyledText::warning_fg(), style::Print("⚠ "), - style::SetForegroundColor(style::Color::Blue), + StyledText::info_fg(), style::Print(name), - style::ResetColor, + StyledText::reset(), style::Print(" requires OAuth authentication. Use /mcp to see the auth link\n"), )?) } @@ -2002,15 +1993,15 @@ fn queue_oauth_message(name: &str, output: &mut impl Write) -> eyre::Result<()> fn queue_oauth_message_with_link(name: &str, msg: &eyre::Report, output: &mut impl Write) -> eyre::Result<()> { Ok(queue!( output, - style::SetForegroundColor(style::Color::Yellow), + StyledText::warning_fg(), style::Print("⚠ "), - style::SetForegroundColor(style::Color::Blue), + StyledText::info_fg(), style::Print(name), - style::ResetColor, + StyledText::reset(), style::Print(" requires OAuth authentication. Follow this link to proceed: \n"), - style::SetForegroundColor(style::Color::Yellow), + StyledText::warning_fg(), style::Print(msg), - style::ResetColor, + StyledText::reset(), style::Print("\n") )?) } @@ -2018,31 +2009,31 @@ fn queue_oauth_message_with_link(name: &str, msg: &eyre::Report, output: &mut im fn queue_warn_message(name: &str, msg: &eyre::Report, time: &str, output: &mut impl Write) -> eyre::Result<()> { Ok(queue!( output, - style::SetForegroundColor(style::Color::Yellow), + StyledText::warning_fg(), style::Print("⚠ "), - style::SetForegroundColor(style::Color::Blue), + StyledText::info_fg(), style::Print(name), - style::ResetColor, + StyledText::reset(), style::Print(" has loaded in"), - style::SetForegroundColor(style::Color::Yellow), + StyledText::warning_fg(), style::Print(format!(" {time} s")), - style::ResetColor, + StyledText::reset(), style::Print(" with the following warning:\n"), style::Print(msg), - style::ResetColor, + StyledText::reset(), )?) } fn queue_disabled_message(name: &str, output: &mut impl Write) -> eyre::Result<()> { Ok(queue!( output, - style::SetForegroundColor(style::Color::DarkGrey), + StyledText::secondary_fg(), style::Print("○ "), - style::SetForegroundColor(style::Color::Blue), + StyledText::info_fg(), style::Print(name), - style::ResetColor, + StyledText::reset(), style::Print(" is disabled\n"), - style::ResetColor, + StyledText::reset(), )?) } @@ -2054,21 +2045,21 @@ fn queue_incomplete_load_message( ) -> eyre::Result<()> { Ok(queue!( output, - style::SetForegroundColor(style::Color::Yellow), + StyledText::warning_fg(), style::Print("⚠"), - style::SetForegroundColor(style::Color::Blue), + StyledText::info_fg(), style::Print(format!(" {}", complete)), - style::ResetColor, + StyledText::reset(), style::Print(" of "), - style::SetForegroundColor(style::Color::Blue), + StyledText::info_fg(), style::Print(format!("{} ", total)), - style::ResetColor, + StyledText::reset(), style::Print("mcp servers initialized."), - style::ResetColor, + StyledText::reset(), // We expect the message start with a newline style::Print(" Servers still loading:"), style::Print(msg), - style::ResetColor, + StyledText::reset(), )?) } diff --git a/crates/chat-cli/src/cli/chat/tools/custom_tool.rs b/crates/chat-cli/src/cli/chat/tools/custom_tool.rs index 0e45205678..b564321c16 100644 --- a/crates/chat-cli/src/cli/chat/tools/custom_tool.rs +++ b/crates/chat-cli/src/cli/chat/tools/custom_tool.rs @@ -27,6 +27,7 @@ use crate::mcp_client::{ oauth_util, }; use crate::os::Os; +use crate::theme::StyledText; use crate::util::MCP_SERVER_TOOL_DELIMITER; #[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq, JsonSchema)] @@ -137,9 +138,9 @@ impl CustomTool { queue!( output, style::Print("Running "), - style::SetForegroundColor(style::Color::Green), + StyledText::success_fg(), style::Print(&self.name), - style::ResetColor, + StyledText::reset(), )?; if let Some(params) = &self.params { let params = match serde_json::to_string_pretty(params) { @@ -155,7 +156,7 @@ impl CustomTool { style::Print(" with the param:\n"), style::Print(params), style::Print("\n"), - style::ResetColor, + StyledText::reset(), )?; } else { queue!(output, style::Print("\n"))?; diff --git a/crates/chat-cli/src/cli/chat/tools/delegate.rs b/crates/chat-cli/src/cli/chat/tools/delegate.rs index f0f8005ad4..6f70ffdd5a 100644 --- a/crates/chat-cli/src/cli/chat/tools/delegate.rs +++ b/crates/chat-cli/src/cli/chat/tools/delegate.rs @@ -6,11 +6,7 @@ use std::io::{ use std::path::PathBuf; use chrono::Utc; -use crossterm::style::{ - Color, - Print, - SetForegroundColor, -}; +use crossterm::style::Print; use crossterm::{ execute, queue, @@ -44,6 +40,7 @@ use crate::cli::{ DEFAULT_AGENT_NAME, }; use crate::os::Os; +use crate::theme::StyledText; /// Launch and manage async agent processes. Delegate tasks to agents that run independently in /// background. @@ -191,9 +188,9 @@ pub fn display_agent_info(agent: &str, task: &str, config: &AgentConfig) -> Resu execute!( stdout(), Print("\n"), - SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), Print("! This task will run with the agent's specific tool permissions.\n\n"), - SetForegroundColor(Color::Reset), + StyledText::reset(), )?; Ok(()) @@ -213,11 +210,11 @@ pub fn display_default_agent_warning() -> Result<()> { execute!( stdout(), Print("\n"), - SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), Print( "! This task will run with trust-all permissions and can execute commands or consume system/cloud resources.\n\n" ), - SetForegroundColor(Color::Reset), + StyledText::reset(), )?; Ok(()) } @@ -225,9 +222,9 @@ pub fn display_default_agent_warning() -> Result<()> { pub fn get_user_confirmation() -> Result { execute!( stdout(), - SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), Print("Continue? [y/N]: "), - SetForegroundColor(Color::Reset), + StyledText::reset(), )?; let mut input = String::new(); diff --git a/crates/chat-cli/src/cli/chat/tools/execute/mod.rs b/crates/chat-cli/src/cli/chat/tools/execute/mod.rs index 200cac641a..3edbd4e239 100644 --- a/crates/chat-cli/src/cli/chat/tools/execute/mod.rs +++ b/crates/chat-cli/src/cli/chat/tools/execute/mod.rs @@ -3,7 +3,6 @@ use std::io::Write; use crossterm::queue; use crossterm::style::{ self, - Color, }; use eyre::Result; use regex::Regex; @@ -23,6 +22,7 @@ use crate::cli::chat::tools::{ }; use crate::cli::chat::util::truncate_safe; use crate::os::Os; +use crate::theme::StyledText; use crate::util::tool_permission_checker::is_tool_in_allowlist; // Platform-specific modules @@ -166,10 +166,10 @@ impl ExecuteCommand { queue!( output, - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&self.command), style::Print("\n"), - style::ResetColor + StyledText::reset(), )?; // Add the summary if available diff --git a/crates/chat-cli/src/cli/chat/tools/fs_read.rs b/crates/chat-cli/src/cli/chat/tools/fs_read.rs index e6dc7e31ba..bbad25eb69 100644 --- a/crates/chat-cli/src/cli/chat/tools/fs_read.rs +++ b/crates/chat-cli/src/cli/chat/tools/fs_read.rs @@ -5,7 +5,6 @@ use std::io::Write; use crossterm::queue; use crossterm::style::{ self, - Color, }; use eyre::{ Result, @@ -45,6 +44,7 @@ use crate::cli::chat::{ sanitize_unicode_tags, }; use crate::os::Os; +use crate::theme::StyledText; use crate::util::directories; use crate::util::tool_permission_checker::is_tool_in_allowlist; @@ -84,9 +84,9 @@ impl FsRead { queue!( updates, style::Print("Batch fs_read operation with "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(self.operations.len()), - style::ResetColor, + StyledText::reset(), style::Print(" operations:\n") )?; @@ -425,10 +425,10 @@ impl FsImage { queue!( updates, style::Print("Reading images: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&self.image_paths.join("\n")), style::Print("\n"), - style::ResetColor, + StyledText::reset(), )?; Ok(()) } @@ -466,9 +466,9 @@ impl FsLine { queue!( updates, style::Print("Reading file: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&self.path), - style::ResetColor, + StyledText::reset(), style::Print(", "), )?; @@ -479,21 +479,21 @@ impl FsLine { _ if end == line_count => Ok(queue!( updates, style::Print("from line "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(start), - style::ResetColor, + StyledText::reset(), style::Print(" to end of file"), )?), _ => Ok(queue!( updates, style::Print("from line "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(start), - style::ResetColor, + StyledText::reset(), style::Print(" to "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(end), - style::ResetColor, + StyledText::reset(), )?), } } @@ -595,13 +595,13 @@ impl FsSearch { queue!( updates, style::Print("Searching: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&self.path), - style::ResetColor, + StyledText::reset(), style::Print(" for pattern: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&self.pattern.to_lowercase()), - style::ResetColor, + StyledText::reset(), )?; Ok(()) } @@ -691,9 +691,9 @@ impl FsDirectory { queue!( updates, style::Print("Reading directory: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&self.path), - style::ResetColor, + StyledText::reset(), style::Print(" "), )?; let depth = self.depth.unwrap_or_default(); diff --git a/crates/chat-cli/src/cli/chat/tools/fs_write.rs b/crates/chat-cli/src/cli/chat/tools/fs_write.rs index d72ccb2be6..46de4daac3 100644 --- a/crates/chat-cli/src/cli/chat/tools/fs_write.rs +++ b/crates/chat-cli/src/cli/chat/tools/fs_write.rs @@ -9,7 +9,6 @@ use std::sync::LazyLock; use crossterm::queue; use crossterm::style::{ self, - Color, }; use eyre::{ ContextCompat as _, @@ -44,6 +43,10 @@ use crate::cli::agent::{ }; use crate::cli::chat::line_tracker::FileLineTracker; use crate::os::Os; +use crate::theme::{ + StyledText, + theme, +}; use crate::util::directories; use crate::util::tool_permission_checker::is_tool_in_allowlist; @@ -120,9 +123,9 @@ impl FsWrite { queue!( output, style::Print(invoke_description), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format_path(cwd, &path)), - style::ResetColor, + StyledText::reset(), style::Print("\n"), )?; @@ -134,9 +137,9 @@ impl FsWrite { queue!( output, style::Print("Updating: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format_path(cwd, &path)), - style::ResetColor, + StyledText::reset(), style::Print("\n"), )?; match matches.len() { @@ -155,9 +158,9 @@ impl FsWrite { queue!( output, style::Print("Updating: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format_path(cwd, &path)), - style::ResetColor, + StyledText::reset(), style::Print("\n"), )?; @@ -176,9 +179,9 @@ impl FsWrite { queue!( output, style::Print("Appending to: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format_path(cwd, &path)), - style::ResetColor, + StyledText::reset(), style::Print("\n"), )?; @@ -423,9 +426,9 @@ impl FsWrite { queue!( output, style::Print("Path: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&relative_path), - style::ResetColor, + StyledText::reset(), style::Print("\n\n"), )?; Ok(()) @@ -663,20 +666,20 @@ fn print_diff( for change in diff.iter_all_changes() { // Define the colors per line. let (text_color, gutter_bg_color, line_bg_color) = match (change.tag(), new_str.truecolor) { - (similar::ChangeTag::Equal, true) => (style::Color::Reset, new_str.gutter_bg, new_str.line_bg), + (similar::ChangeTag::Equal, true) => (theme().ui.secondary_text, new_str.gutter_bg, new_str.line_bg), (similar::ChangeTag::Delete, true) => ( - style::Color::Reset, + theme().ui.secondary_text, style::Color::Rgb { r: 79, g: 40, b: 40 }, style::Color::Rgb { r: 36, g: 25, b: 28 }, ), (similar::ChangeTag::Insert, true) => ( - style::Color::Reset, + theme().ui.secondary_text, style::Color::Rgb { r: 40, g: 67, b: 43 }, style::Color::Rgb { r: 24, g: 38, b: 30 }, ), - (similar::ChangeTag::Equal, false) => (style::Color::Reset, new_str.gutter_bg, new_str.line_bg), - (similar::ChangeTag::Delete, false) => (style::Color::Red, new_str.gutter_bg, new_str.line_bg), - (similar::ChangeTag::Insert, false) => (style::Color::Green, new_str.gutter_bg, new_str.line_bg), + (similar::ChangeTag::Equal, false) => (theme().ui.secondary_text, new_str.gutter_bg, new_str.line_bg), + (similar::ChangeTag::Delete, false) => (theme().status.error, new_str.gutter_bg, new_str.line_bg), + (similar::ChangeTag::Insert, false) => (theme().status.success, new_str.gutter_bg, new_str.line_bg), }; // Define the change tag character to print, if any. let sign = match change.tag() { @@ -720,13 +723,13 @@ fn print_diff( // Print the line. queue!( output, - style::SetForegroundColor(style::Color::Reset), + StyledText::reset(), style::Print(":"), style::SetForegroundColor(text_color), style::SetBackgroundColor(line_bg_color), style::Print(" "), style::Print(change), - style::ResetColor, + StyledText::reset(), )?; } queue!( @@ -772,8 +775,8 @@ fn stylize_output_if_able(os: &Os, path: impl AsRef, file_text: &str) -> S StylizedFile { truecolor: false, content: file_text.to_string(), - gutter_bg: style::Color::Reset, - line_bg: style::Color::Reset, + gutter_bg: theme().ui.secondary_text, + line_bg: theme().ui.secondary_text, } } @@ -796,8 +799,8 @@ impl Default for StylizedFile { Self { truecolor: false, content: Default::default(), - gutter_bg: style::Color::Reset, - line_bg: style::Color::Reset, + gutter_bg: theme().ui.secondary_text, + line_bg: theme().ui.secondary_text, } } } diff --git a/crates/chat-cli/src/cli/chat/tools/gh_issue.rs b/crates/chat-cli/src/cli/chat/tools/gh_issue.rs index a426e9e4e9..1aea366a3e 100644 --- a/crates/chat-cli/src/cli/chat/tools/gh_issue.rs +++ b/crates/chat-cli/src/cli/chat/tools/gh_issue.rs @@ -1,7 +1,6 @@ use std::collections::VecDeque; use std::io::Write; -use crossterm::style::Color; use crossterm::{ queue, style, @@ -18,6 +17,7 @@ use super::super::util::issue::IssueCreator; use super::InvokeOutput; use crate::cli::chat::token_counter::TokenCounter; use crate::os::Os; +use crate::theme::StyledText; #[derive(Debug, Clone, Deserialize)] pub struct GhIssue { @@ -190,9 +190,9 @@ impl GhIssue { Ok(queue!( output, style::Print("I will prepare a github issue with our conversation history.\n\n"), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(format!("Title: {}\n", &self.title)), - style::ResetColor + StyledText::reset(), )?) } diff --git a/crates/chat-cli/src/cli/chat/tools/knowledge.rs b/crates/chat-cli/src/cli/chat/tools/knowledge.rs index a8f1c9999c..7055bf3c5e 100644 --- a/crates/chat-cli/src/cli/chat/tools/knowledge.rs +++ b/crates/chat-cli/src/cli/chat/tools/knowledge.rs @@ -3,7 +3,6 @@ use std::io::Write; use crossterm::queue; use crossterm::style::{ self, - Color, }; use eyre::Result; use serde::Deserialize; @@ -22,6 +21,7 @@ use crate::cli::experiment::experiment_manager::{ ExperimentName, }; use crate::os::Os; +use crate::theme::StyledText; use crate::util::knowledge_store::KnowledgeStore; use crate::util::tool_permission_checker::is_tool_in_allowlist; @@ -156,9 +156,9 @@ impl Knowledge { queue!( updates, style::Print("Adding to knowledge base: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&add.name), - style::ResetColor, + StyledText::reset(), )?; // Check if value is a path or text content @@ -168,9 +168,9 @@ impl Knowledge { queue!( updates, style::Print(format!(" ({}: ", path_type)), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&add.value), - style::ResetColor, + StyledText::reset(), style::Print(")\n") )?; } else { @@ -179,18 +179,18 @@ impl Knowledge { queue!( updates, style::Print(" (text: "), - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::Print(format!("{}...", preview)), - style::ResetColor, + StyledText::reset(), style::Print(")\n") )?; } else { queue!( updates, style::Print(" (text: "), - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::Print(&add.value), - style::ResetColor, + StyledText::reset(), style::Print(")\n") )?; } @@ -201,33 +201,33 @@ impl Knowledge { queue!( updates, style::Print("Removing from knowledge base by name: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&remove.name), - style::ResetColor, + StyledText::reset(), )?; } else if !remove.context_id.is_empty() { queue!( updates, style::Print("Removing from knowledge base by ID: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&remove.context_id), - style::ResetColor, + StyledText::reset(), )?; } else if !remove.path.is_empty() { queue!( updates, style::Print("Removing from knowledge base by path: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&remove.path), - style::ResetColor, + StyledText::reset(), )?; } else { queue!( updates, style::Print("Removing from knowledge base: "), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("No identifier provided"), - style::ResetColor, + StyledText::reset(), )?; } }, @@ -238,17 +238,17 @@ impl Knowledge { queue!( updates, style::Print(" with ID: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&update.context_id), - style::ResetColor, + StyledText::reset(), )?; } else if !update.name.is_empty() { queue!( updates, style::Print(" with name: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&update.name), - style::ResetColor, + StyledText::reset(), )?; } @@ -257,18 +257,18 @@ impl Knowledge { queue!( updates, style::Print(format!(" using new {}: ", path_type)), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&update.path), - style::ResetColor, + StyledText::reset(), )?; }, Knowledge::Clear(_) => { queue!( updates, style::Print("Clearing "), - style::SetForegroundColor(Color::Yellow), + StyledText::warning_fg(), style::Print("all"), - style::ResetColor, + StyledText::reset(), style::Print(" knowledge base entries"), )?; }, @@ -276,18 +276,18 @@ impl Knowledge { queue!( updates, style::Print("Searching knowledge base for: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(&search.query), - style::ResetColor, + StyledText::reset(), )?; if let Some(context_id) = &search.context_id { queue!( updates, style::Print(" in context: "), - style::SetForegroundColor(Color::Green), + StyledText::success_fg(), style::Print(context_id), - style::ResetColor, + StyledText::reset(), )?; } else { queue!(updates, style::Print(" across all contexts"),)?; diff --git a/crates/chat-cli/src/cli/chat/tools/mod.rs b/crates/chat-cli/src/cli/chat/tools/mod.rs index 39c611c4d2..a176bd39cd 100644 --- a/crates/chat-cli/src/cli/chat/tools/mod.rs +++ b/crates/chat-cli/src/cli/chat/tools/mod.rs @@ -24,7 +24,6 @@ use std::path::{ use crossterm::queue; use crossterm::style::{ self, - Color, }; use custom_tool::CustomTool; use delegate::Delegate; @@ -58,6 +57,10 @@ use crate::cli::agent::{ }; use crate::cli::chat::line_tracker::FileLineTracker; use crate::os::Os; +use crate::theme::{ + StyledText, + theme, +}; pub const DEFAULT_APPROVE: [&str; 0] = []; pub const NATIVE_TOOLS: [&str; 9] = [ @@ -416,9 +419,9 @@ pub fn display_purpose(purpose: Option<&String>, updates: &mut impl Write) -> Re style::Print(super::CONTINUATION_LINE), style::Print("\n"), style::Print(super::PURPOSE_ARROW), - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::Print("Purpose: "), - style::ResetColor, + StyledText::reset(), style::Print(purpose), style::Print("\n"), )?; @@ -438,9 +441,9 @@ pub fn queue_function_result(result: &str, updates: &mut impl Write, is_error: b // Determine symbol and color let (symbol, color) = match (is_error, use_bullet) { - (true, _) => (super::ERROR_EXCLAMATION, Color::Red), - (false, true) => (super::TOOL_BULLET, Color::Reset), - (false, false) => (super::SUCCESS_TICK, Color::Green), + (true, _) => (super::ERROR_EXCLAMATION, theme().status.error), + (false, true) => (super::TOOL_BULLET, theme().ui.secondary_text), + (false, false) => (super::SUCCESS_TICK, theme().status.success), }; queue!(updates, style::Print("\n"))?; @@ -451,7 +454,7 @@ pub fn queue_function_result(result: &str, updates: &mut impl Write, is_error: b updates, style::SetForegroundColor(color), style::Print(symbol), - style::ResetColor, + StyledText::reset(), style::Print(first_line), style::Print("\n"), )?; diff --git a/crates/chat-cli/src/cli/chat/tools/thinking.rs b/crates/chat-cli/src/cli/chat/tools/thinking.rs index 5b0fbc5308..b35388f440 100644 --- a/crates/chat-cli/src/cli/chat/tools/thinking.rs +++ b/crates/chat-cli/src/cli/chat/tools/thinking.rs @@ -3,7 +3,6 @@ use std::io::Write; use crossterm::queue; use crossterm::style::{ self, - Color, }; use eyre::Result; use serde::Deserialize; @@ -17,6 +16,7 @@ use crate::cli::experiment::experiment_manager::{ ExperimentName, }; use crate::os::Os; +use crate::theme::StyledText; /// The Think tool allows the model to reason through complex problems during response generation. /// It provides a dedicated space for the model to process information from tool call results, @@ -43,9 +43,9 @@ impl Thinking { // Show a preview of the thought that will be displayed queue!( output, - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::Print("I'll share my reasoning process: "), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), style::Print(&self.thought), style::Print("\n") )?; diff --git a/crates/chat-cli/src/cli/chat/tools/todo.rs b/crates/chat-cli/src/cli/chat/tools/todo.rs index 11dbfa2a9e..d56820454f 100644 --- a/crates/chat-cli/src/cli/chat/tools/todo.rs +++ b/crates/chat-cli/src/cli/chat/tools/todo.rs @@ -29,6 +29,7 @@ use crate::cli::experiment::experiment_manager::{ ExperimentName, }; use crate::os::Os; +use crate::theme::StyledText; #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct Task { @@ -96,19 +97,15 @@ fn queue_next_without_newline(output: &mut impl Write, task: String, completed: if completed { queue!( output, - style::SetForegroundColor(style::Color::Green), + StyledText::success_fg(), style::Print("[x] "), style::SetAttribute(style::Attribute::Italic), - style::SetForegroundColor(style::Color::DarkGrey), + StyledText::secondary_fg(), style::Print(task), style::SetAttribute(style::Attribute::NoItalic), )?; } else { - queue!( - output, - style::SetForegroundColor(style::Color::Reset), - style::Print(format!("[ ] {task}")), - )?; + queue!(output, StyledText::reset(), style::Print(format!("[ ] {task}")),)?; } Ok(()) } @@ -226,9 +223,9 @@ impl TodoList { if !Self::is_enabled(os) { queue!( output, - style::SetForegroundColor(style::Color::Red), + StyledText::error_fg(), style::Print("Todo lists are disabled. Enable them with: q settings chat.enableTodoList true"), - style::SetForegroundColor(style::Color::Reset) + StyledText::reset(), )?; return Ok(InvokeOutput { output: super::OutputKind::Text("Todo lists are disabled.".to_string()), diff --git a/crates/chat-cli/src/cli/chat/util/images.rs b/crates/chat-cli/src/cli/chat/util/images.rs index 27f3623926..3dffeeb8c9 100644 --- a/crates/chat-cli/src/cli/chat/util/images.rs +++ b/crates/chat-cli/src/cli/chat/util/images.rs @@ -6,7 +6,6 @@ use std::str::FromStr; use crossterm::execute; use crossterm::style::{ self, - Color, }; use serde::{ Deserialize, @@ -22,6 +21,7 @@ use crate::cli::chat::consts::{ MAX_IMAGE_SIZE, MAX_NUMBER_OF_IMAGES_PER_REQUEST, }; +use crate::theme::StyledText; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ImageMetadata { @@ -91,12 +91,12 @@ pub fn handle_images_from_paths(output: &mut impl Write, paths: &[String]) -> Ri if valid_images.len() > MAX_NUMBER_OF_IMAGES_PER_REQUEST { execute!( &mut *output, - style::SetForegroundColor(Color::DarkYellow), + StyledText::warning_fg(), style::Print(format!( "\nMore than {} images detected. Extra ones will be dropped.\n", MAX_NUMBER_OF_IMAGES_PER_REQUEST )), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), ) .ok(); valid_images.truncate(MAX_NUMBER_OF_IMAGES_PER_REQUEST); @@ -105,12 +105,12 @@ pub fn handle_images_from_paths(output: &mut impl Write, paths: &[String]) -> Ri if !images_exceeding_size_limit.is_empty() { execute!( &mut *output, - style::SetForegroundColor(Color::DarkYellow), + StyledText::warning_fg(), style::Print(format!( "\nThe following images are dropped due to exceeding size limit ({}MB):\n", MAX_IMAGE_SIZE / (1024 * 1024) )), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), ) .ok(); for (_, metadata) in &images_exceeding_size_limit { @@ -123,9 +123,9 @@ pub fn handle_images_from_paths(output: &mut impl Write, paths: &[String]) -> Ri }; execute!( &mut *output, - style::SetForegroundColor(Color::DarkYellow), + StyledText::warning_fg(), style::Print(format!(" - {} ({})\n", metadata.filename, image_size_str)), - style::SetForegroundColor(Color::Reset) + StyledText::reset(), ) .ok(); } diff --git a/crates/chat-cli/src/cli/chat/util/ui.rs b/crates/chat-cli/src/cli/chat/util/ui.rs index 43efc8a685..b99308e6cd 100644 --- a/crates/chat-cli/src/cli/chat/util/ui.rs +++ b/crates/chat-cli/src/cli/chat/util/ui.rs @@ -117,10 +117,10 @@ pub fn draw_box( #[cfg(test)] mod tests { use bstr::ByteSlice; - use crossterm::style::Color; use super::*; use crate::cli::chat::GREETING_BREAK_POINT; + use crate::theme::theme; #[tokio::test] async fn test_draw_tip_box() { @@ -133,7 +133,7 @@ mod tests { "Did you know?", short_tip, GREETING_BREAK_POINT, - Color::DarkGrey, + theme().ui.secondary_text, ) .expect("Failed to draw tip box"); @@ -144,7 +144,7 @@ mod tests { "Did you know?", long_tip, GREETING_BREAK_POINT, - Color::DarkGrey, + theme().ui.secondary_text, ) .expect("Failed to draw tip box"); @@ -160,7 +160,7 @@ mod tests { "Did you know?", long_tip_with_one_long_word.as_str(), GREETING_BREAK_POINT, - Color::DarkGrey, + theme().ui.secondary_text, ) .expect("Failed to draw tip box"); // Test with a long tip with two long words that should wrap @@ -170,7 +170,7 @@ mod tests { "Did you know?", long_tip_with_two_long_words.as_str(), GREETING_BREAK_POINT, - Color::DarkGrey, + theme().ui.secondary_text, ) .expect("Failed to draw tip box"); diff --git a/crates/chat-cli/src/cli/mcp.rs b/crates/chat-cli/src/cli/mcp.rs index f0e8b97886..93f1da1dc0 100644 --- a/crates/chat-cli/src/cli/mcp.rs +++ b/crates/chat-cli/src/cli/mcp.rs @@ -11,7 +11,6 @@ use clap::{ Args, ValueEnum, }; -use crossterm::style::Stylize; use crossterm::{ execute, style, @@ -36,6 +35,7 @@ use crate::cli::chat::tools::custom_tool::{ default_timeout, }; use crate::os::Os; +use crate::theme::StyledText; use crate::util::directories; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Hash)] @@ -307,7 +307,7 @@ impl ListArgs { writeln!(output)?; writeln!(output, "{}:\n", scope_display(&scope))?; for (agent_name, cfg_opt, _) in agents { - writeln!(output, " {}", agent_name.bold())?; + writeln!(output, " {}", StyledText::emphasis(&agent_name))?; match cfg_opt { Some(cfg) if !cfg.mcp_servers.is_empty() => { // Sorting servers by name since HashMap is unordered, and having a bunch diff --git a/crates/chat-cli/src/cli/mod.rs b/crates/chat-cli/src/cli/mod.rs index 324af77121..523900d639 100644 --- a/crates/chat-cli/src/cli/mod.rs +++ b/crates/chat-cli/src/cli/mod.rs @@ -1,3 +1,4 @@ +use crate::theme::StyledText; mod agent; pub mod chat; mod debug; @@ -31,7 +32,6 @@ use clap::{ Subcommand, ValueEnum, }; -use crossterm::style::Stylize; use eyre::{ Result, bail, @@ -141,7 +141,7 @@ impl RootSubcommand { if self.requires_auth() && !crate::auth::is_logged_in(&mut os.database).await { bail!( "You are not logged in, please log in with {}", - format!("{CLI_BINARY_NAME} login").bold() + StyledText::command(&format!("{CLI_BINARY_NAME} login")) ); } diff --git a/crates/chat-cli/src/cli/user.rs b/crates/chat-cli/src/cli/user.rs index 3014d931c4..240da92caf 100644 --- a/crates/chat-cli/src/cli/user.rs +++ b/crates/chat-cli/src/cli/user.rs @@ -14,7 +14,6 @@ use clap::{ Args, Subcommand, }; -use crossterm::style::Stylize; use dialoguer::Select; use eyre::{ Result, @@ -43,6 +42,7 @@ use crate::telemetry::{ QProfileSwitchIntent, TelemetryResult, }; +use crate::theme::StyledText; use crate::util::spinner::{ Spinner, SpinnerComponent, @@ -79,7 +79,7 @@ impl LoginArgs { if crate::auth::is_logged_in(&mut os.database).await { eyre::bail!( "Already logged in, please logout with {} first", - format!("{CLI_BINARY_NAME} logout").magenta() + StyledText::command(&format!("{CLI_BINARY_NAME} logout")) ); } @@ -179,7 +179,7 @@ pub async fn logout(os: &mut Os) -> Result { eprintln!("You are now logged out"); eprintln!( "Run {} to log back in to {PRODUCT_NAME}", - format!("{CLI_BINARY_NAME} login").magenta() + StyledText::command(&format!("{CLI_BINARY_NAME} login")) ); Ok(ExitCode::SUCCESS) @@ -283,7 +283,7 @@ async fn try_device_authorization(os: &mut Os, start_url: Option, region println!(); println!("Confirm the following code in the browser"); - println!("Code: {}", device_auth.user_code.bold()); + println!("Code: {}", StyledText::emphasis(&device_auth.user_code)); println!(); let print_open_url = || println!("Open this URL: {}", device_auth.verification_uri_complete); diff --git a/crates/chat-cli/src/constants.rs b/crates/chat-cli/src/constants.rs index 2c6172252a..fa38ec0104 100644 --- a/crates/chat-cli/src/constants.rs +++ b/crates/chat-cli/src/constants.rs @@ -1,5 +1,7 @@ //! Centralized constants for user-facing messages +use crate::theme::StyledText; + /// Base product name without any qualifiers pub const PRODUCT_NAME: &str = "Amazon Q"; @@ -17,67 +19,164 @@ pub mod error_messages { /// UI text constants pub mod ui_text { + use super::StyledText; + /// Welcome text for small screens - pub const SMALL_SCREEN_WELCOME: &str = color_print::cstr! {"Welcome to Amazon Q!"}; + pub fn small_screen_welcome() -> String { + format!("Welcome to {}!", StyledText::brand("Amazon Q")) + } /// Changelog header text pub fn changelog_header() -> String { - color_print::cstr! {"What's New in Amazon Q CLI\n\n"}.to_string() + format!("{}\n\n", StyledText::emphasis("What's New in Amazon Q CLI")) } /// Trust all tools warning text pub fn trust_all_warning() -> String { - color_print::cstr! {"All tools are now trusted (!). Amazon Q will execute tools without asking for confirmation.\ -\nAgents can sometimes do unexpected things so understand the risks. -\nLearn more at https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-chat-security.html#command-line-chat-trustall-safety"}.to_string() + let mut warning = String::new(); + + warning.push_str(&StyledText::success("All tools are now trusted (")); + warning.push_str(&StyledText::error("!")); + warning.push_str(&StyledText::success( + "). Amazon Q will execute tools without asking for confirmation.", + )); + warning.push_str("\nAgents can sometimes do unexpected things so understand the risks."); + warning.push_str("\n\nLearn more at https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-chat-security.html#command-line-chat-trustall-safety"); + + warning } /// Rate limit reached message - pub const LIMIT_REACHED_TEXT: &str = color_print::cstr! { "You've used all your free requests for this month. You have two options: + pub fn limit_reached_text() -> String { + format!( + "You've used all your free requests for this month. You have two options: -1. Upgrade to a paid subscription for increased limits. See our Pricing page for what's included> https://aws.amazon.com/q/developer/pricing/ -2. Wait until next month when your limit automatically resets." }; +1. Upgrade to a paid subscription for increased limits. See our Pricing page for what's included> {} +2. Wait until next month when your limit automatically resets.", + StyledText::info("https://aws.amazon.com/q/developer/pricing/") + ) + } /// Extra help text shown in chat interface - pub const EXTRA_HELP: &str = color_print::cstr! {" -MCP: -You can now configure the Amazon Q CLI to use MCP servers. -Learn how: https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/qdev-mcp.html - -Tips: -!{command} Quickly execute a command in your current session -Ctrl(^) + j Insert new-line to provide multi-line prompt - Alternatively, [Alt(⌥) + Enter(⏎)] -Ctrl(^) + s Fuzzy search commands and context files - Use Tab to select multiple items - Change the keybind using: q settings chat.skimCommandKey x -Ctrl(^) + t Toggle tangent mode for isolated conversations - Change the keybind using: q settings chat.tangentModeKey x -chat.editMode The prompt editing mode (vim or emacs) - Change using: q settings chat.skimCommandKey x -"}; + pub fn extra_help() -> String { + let mut help = String::new(); + + // MCP section + help.push('\n'); + help.push_str(&StyledText::brand("MCP:")); + help.push('\n'); + help.push_str(&StyledText::secondary( + "You can now configure the Amazon Q CLI to use MCP servers.", + )); + help.push_str(&StyledText::secondary( + "\nLearn how: https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/qdev-mcp.html", + )); + + // Tips section + help.push_str("\n\n"); + help.push_str(&StyledText::brand("Tips:")); + help.push('\n'); + + // Command execution tip + help.push_str(&format!( + "{} {}", + StyledText::primary("!{command}"), + StyledText::secondary("Quickly execute a command in your current session") + )); + help.push('\n'); + + // Multi-line prompt tip + help.push_str(&format!( + "{} {}", + StyledText::primary("Ctrl(^) + j"), + StyledText::secondary("Insert new-line to provide multi-line prompt") + )); + help.push_str(&format!( + "\n {}", + StyledText::secondary("Alternatively, [Alt(⌥) + Enter(⏎)]") + )); + help.push('\n'); + + // Fuzzy search tip + help.push_str(&format!( + "{} {}", + StyledText::primary("Ctrl(^) + s"), + StyledText::secondary("Fuzzy search commands and context files") + )); + help.push_str(&format!( + "\n {}", + StyledText::secondary("Use Tab to select multiple items") + )); + help.push_str(&format!( + "\n {}", + StyledText::secondary("Change the keybind using: q settings chat.skimCommandKey x") + )); + help.push('\n'); + + // Tangent mode tip + help.push_str(&format!( + "{} {}", + StyledText::primary("Ctrl(^) + t"), + StyledText::secondary("Toggle tangent mode for isolated conversations") + )); + help.push_str(&format!( + "\n {}", + StyledText::secondary("Change the keybind using: q settings chat.tangentModeKey x") + )); + help.push('\n'); + + // Edit mode tip + help.push_str(&format!( + "{} {}", + StyledText::primary("chat.editMode"), + StyledText::secondary("The prompt editing mode (vim or emacs)") + )); + help.push_str(&format!( + "\n {}", + StyledText::secondary("Change using: q settings chat.skimCommandKey x") + )); + + help + } /// Welcome text with ASCII art logo for large screens - pub const WELCOME_TEXT: &str = color_print::cstr! {" + pub fn welcome_text() -> String { + StyledText::brand( + " ⢠⣶⣶⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣿⣿⣿⣶⣦⡀⠀ ⠀⠀⠀⣾⡿⢻⣿⡆⠀⠀⠀⢀⣄⡄⢀⣠⣤⣤⡀⢀⣠⣤⣤⡀⠀⠀⢀⣠⣤⣤⣤⣄⠀⠀⢀⣤⣤⣤⣤⣤⣤⡀⠀⠀⣀⣤⣤⣤⣀⠀⠀⠀⢠⣤⡀⣀⣤⣤⣄⡀⠀⠀⠀⠀⠀⠀⢠⣿⣿⠋⠀⠀⠀⠙⣿⣿⡆ ⠀⠀⣼⣿⠇⠀⣿⣿⡄⠀⠀⢸⣿⣿⠛⠉⠻⣿⣿⠛⠉⠛⣿⣿⠀⠀⠘⠛⠉⠉⠻⣿⣧⠀⠈⠛⠛⠛⣻⣿⡿⠀⢀⣾⣿⠛⠉⠻⣿⣷⡀⠀⢸⣿⡟⠛⠉⢻⣿⣷⠀⠀⠀⠀⠀⠀⣼⣿⡏⠀⠀⠀⠀⠀⢸⣿⣿ ⠀⢰⣿⣿⣤⣤⣼⣿⣷⠀⠀⢸⣿⣿⠀⠀⠀⣿⣿⠀⠀⠀⣿⣿⠀⠀⢀⣴⣶⣶⣶⣿⣿⠀⠀⠀⣠⣾⡿⠋⠀⠀⢸⣿⣿⠀⠀⠀⣿⣿⡇⠀⢸⣿⡇⠀⠀⢸⣿⣿⠀⠀⠀⠀⠀⠀⢹⣿⣇⠀⠀⠀⠀⠀⢸⣿⡿ ⢀⣿⣿⠋⠉⠉⠉⢻⣿⣇⠀⢸⣿⣿⠀⠀⠀⣿⣿⠀⠀⠀⣿⣿⠀⠀⣿⣿⡀⠀⣠⣿⣿⠀⢀⣴⣿⣋⣀⣀⣀⡀⠘⣿⣿⣄⣀⣠⣿⣿⠃⠀⢸⣿⡇⠀⠀⢸⣿⣿⠀⠀⠀⠀⠀⠀⠈⢿⣿⣦⣀⣀⣀⣴⣿⡿⠃ ⠚⠛⠋⠀⠀⠀⠀⠘⠛⠛⠀⠘⠛⠛⠀⠀⠀⠛⠛⠀⠀⠀⠛⠛⠀⠀⠙⠻⠿⠟⠋⠛⠛⠀⠘⠛⠛⠛⠛⠛⠛⠃⠀⠈⠛⠿⠿⠿⠛⠁⠀⠀⠘⠛⠃⠀⠀⠘⠛⠛⠀⠀⠀⠀⠀⠀⠀⠀⠙⠛⠿⢿⣿⣿⣋⠀⠀ - ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠿⢿⡧"}; + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠿⢿⡧", + ) + } /// Resume conversation text - pub const RESUME_TEXT: &str = color_print::cstr! {"Picking up where we left off..."}; + pub fn resume_text() -> String { + StyledText::emphasis("Picking up where we left off...") + } /// Popular shortcuts text for large screens - pub const POPULAR_SHORTCUTS: &str = color_print::cstr! {"/help all commands ctrl + j new lines ctrl + s fuzzy search"}; + pub fn popular_shortcuts() -> String { + format!( + "{} all commands • {} new lines • {} fuzzy search", + StyledText::command("/help"), + StyledText::command("ctrl + j"), + StyledText::command("ctrl + s") + ) + } /// Popular shortcuts text for small screens - pub const SMALL_SCREEN_POPULAR_SHORTCUTS: &str = color_print::cstr! {"/help all commands -ctrl + j new lines -ctrl + s fuzzy search -"}; + pub fn small_screen_popular_shortcuts() -> String { + format!( + "{} all commands\n{} new lines\n{} fuzzy search", + StyledText::command("/help"), + StyledText::command("ctrl + j"), + StyledText::command("ctrl + s") + ) + } } /// Help text constants for CLI commands @@ -123,42 +222,95 @@ Notes: /// Tips and rotating messages pub mod tips { - /// Array of rotating tips shown to users - pub const ROTATING_TIPS: [&str; 20] = [ - color_print::cstr! {"You can resume the last conversation from your current directory by launching with - q chat --resume"}, - color_print::cstr! {"Get notified whenever Amazon Q CLI finishes responding. - Just run q settings chat.enableNotifications true"}, - color_print::cstr! {"You can use - /editor to edit your prompt with a vim-like experience"}, - color_print::cstr! {"/usage shows you a visual breakdown of your current context window usage"}, - color_print::cstr! {"Get notified whenever Amazon Q CLI finishes responding. Just run q settings - chat.enableNotifications true"}, - color_print::cstr! {"You can execute bash commands by typing - ! followed by the command"}, - color_print::cstr! {"Q can use tools without asking for - confirmation every time. Give /tools trust a try"}, - color_print::cstr! {"You can - programmatically inject context to your prompts by using hooks. Check out /context hooks - help"}, - color_print::cstr! {"You can use /compact to replace the conversation - history with its summary to free up the context space"}, - color_print::cstr! {"If you want to file an issue - to the Amazon Q CLI team, just tell me, or run q issue"}, - color_print::cstr! {"You can enable - custom tools with MCP servers. Learn more with /help"}, - color_print::cstr! {"You can - specify wait time (in ms) for mcp server loading with q settings mcp.initTimeout {timeout in - int}. Servers that takes longer than the specified time will continue to load in the background. Use - /tools to see pending servers."}, - color_print::cstr! {"You can see the server load status as well as any - warnings or errors associated with /mcp"}, - color_print::cstr! {"Use /model to select the model to use for this conversation"}, - color_print::cstr! {"Set a default model by running q settings chat.defaultModel MODEL. Run /model to learn more."}, - color_print::cstr! {"Run /prompts to learn how to build & run repeatable workflows"}, - color_print::cstr! {"Use /tangent or ctrl + t (customizable) to start isolated conversations ( ↯ ) that don't affect your main chat history"}, - color_print::cstr! {"Ask me directly about my capabilities! Try questions like \"What can you do?\" or \"Can you save conversations?\""}, - color_print::cstr! {"Stay up to date with the latest features and improvements! Use /changelog to see what's new in Amazon Q CLI"}, - color_print::cstr! {"Enable workspace checkpoints to snapshot & restore changes. Just run q settings chat.enableCheckpoint true"}, - ]; + use super::StyledText; + + /// Get rotating tips shown to users + pub fn get_rotating_tips() -> Vec { + vec![ + format!( + "You can resume the last conversation from your current directory by launching with {}", + StyledText::command("q chat --resume") + ), + format!( + "Get notified whenever Amazon Q CLI finishes responding. Just run {}", + StyledText::command("q settings chat.enableNotifications true") + ), + format!( + "You can use {} to edit your prompt with a vim-like experience", + StyledText::command("/editor") + ), + format!( + "{} shows you a visual breakdown of your current context window usage", + StyledText::command("/usage") + ), + format!( + "Get notified whenever Amazon Q CLI finishes responding. Just run {}", + StyledText::command("q settings chat.enableNotifications true") + ), + format!( + "You can execute bash commands by typing {} followed by the command", + StyledText::command("!") + ), + format!( + "Q can use tools without asking for confirmation every time. Give {} a try", + StyledText::command("/tools trust") + ), + format!( + "You can programmatically inject context to your prompts by using hooks. Check out {}", + StyledText::command("/context hooks help") + ), + format!( + "You can use {} to replace the conversation history with its summary to free up the context space", + StyledText::command("/compact") + ), + format!( + "If you want to file an issue to the Amazon Q CLI team, just tell me, or run {}", + StyledText::command("q issue") + ), + format!( + "You can enable custom tools with {}. Learn more with /help", + StyledText::command("MCP servers") + ), + format!( + "You can specify wait time (in ms) for mcp server loading with {}. Servers that takes longer than the specified time will continue to load in the background. Use /tools to see pending servers.", + StyledText::command("q settings mcp.initTimeout {timeout in int}") + ), + format!( + "You can see the server load status as well as any warnings or errors associated with {}", + StyledText::command("/mcp") + ), + format!( + "Use {} to select the model to use for this conversation", + StyledText::command("/model") + ), + format!( + "Set a default model by running {}. Run {} to learn more.", + StyledText::command("q settings chat.defaultModel MODEL"), + StyledText::command("/model") + ), + format!( + "Run {} to learn how to build & run repeatable workflows", + StyledText::command("/prompts") + ), + format!( + "Use {} or {} (customizable) to start isolated conversations ( ↯ ) that don't affect your main chat history", + StyledText::command("/tangent"), + StyledText::command("ctrl + t") + ), + format!( + "Ask me directly about my capabilities! Try questions like {} or {}", + StyledText::command("\"What can you do?\""), + StyledText::command("\"Can you save conversations?\"") + ), + format!( + "Stay up to date with the latest features and improvements! Use {} to see what's new in Amazon Q CLI", + StyledText::command("/changelog") + ), + format!( + "Enable workspace checkpoints to snapshot & restore changes. Just run {} {}", + StyledText::command("q"), + StyledText::command("settings chat.enableCheckpoint true") + ), + ] + } } diff --git a/crates/chat-cli/src/lib.rs b/crates/chat-cli/src/lib.rs index d2c155212d..2e9bc4baf9 100644 --- a/crates/chat-cli/src/lib.rs +++ b/crates/chat-cli/src/lib.rs @@ -13,6 +13,7 @@ pub mod mcp_client; pub mod os; pub mod request; pub mod telemetry; +pub mod theme; pub mod util; pub use mcp_client::*; diff --git a/crates/chat-cli/src/main.rs b/crates/chat-cli/src/main.rs index b1ddb80549..c4f0317551 100644 --- a/crates/chat-cli/src/main.rs +++ b/crates/chat-cli/src/main.rs @@ -9,15 +9,16 @@ mod mcp_client; mod os; mod request; mod telemetry; +mod theme; mod util; use std::process::ExitCode; use anstream::eprintln; use clap::Parser; -use crossterm::style::Stylize; use eyre::Result; use logging::get_log_level_max; +use theme::StyledText; use tracing::metadata::LevelFilter; #[global_allocator] @@ -42,9 +43,9 @@ fn main() -> Result { Ok(exit_code) => Ok(exit_code), Err(err) => { if verbose || get_log_level_max() > LevelFilter::INFO { - eprintln!("{} {err:?}", "error:".bold().red()); + eprintln!("{} {err:?}", StyledText::error("error:")); } else { - eprintln!("{} {err}", "error:".bold().red()); + eprintln!("{} {err}", StyledText::error("error:")); } Ok(ExitCode::FAILURE) diff --git a/crates/chat-cli/src/theme/colors.rs b/crates/chat-cli/src/theme/colors.rs new file mode 100644 index 0000000000..864ba8d7dd --- /dev/null +++ b/crates/chat-cli/src/theme/colors.rs @@ -0,0 +1,84 @@ +//! Color definitions and semantic color categories for the theme system + +use crossterm::style::Color; + +/// Colors for status messages and feedback +#[derive(Debug, Clone)] +pub struct StatusColors { + /// Error messages and critical warnings + pub error: Color, + /// Warning messages and cautions + pub warning: Color, + /// Success messages and confirmations + pub success: Color, + /// Informational messages and tips + pub info: Color, +} + +/// Colors for general UI elements and text +#[derive(Debug, Clone)] +pub struct UiColors { + /// Primary brand color + pub primary_brand: Color, + /// Primary text color + pub primary_text: Color, + /// Secondary/muted text for descriptions and helper text + pub secondary_text: Color, + /// Emphasis color for important text and headers (typically magenta) + pub emphasis: Color, + /// Color for highlighting commands and code examples (typically green) + pub command_highlight: Color, +} + +/// Colors for interactive elements and user interface indicators +#[derive(Debug, Clone)] +pub struct InteractiveColors { + /// The prompt symbol ("> ") + pub prompt_symbol: Color, + /// Profile indicator text ("[profile] ") + pub profile_indicator: Color, + /// Tangent mode indicator ("↯ ") + pub tangent_indicator: Color, + /// Low usage indicator + pub usage_low: Color, + /// Medium usage indicator + pub usage_medium: Color, + /// High usage indicator + pub usage_high: Color, +} + +impl Default for StatusColors { + fn default() -> Self { + Self { + error: Color::Red, + warning: Color::Yellow, + success: Color::Green, + info: Color::Blue, + } + } +} + +impl Default for UiColors { + fn default() -> Self { + Self { + primary_brand: Color::Cyan, + primary_text: Color::White, + secondary_text: Color::DarkGrey, + emphasis: Color::Magenta, + command_highlight: Color::Green, + } + } +} + +impl Default for InteractiveColors { + fn default() -> Self { + Self { + prompt_symbol: Color::Magenta, + profile_indicator: Color::Cyan, + tangent_indicator: Color::Yellow, + usage_low: Color::Green, + usage_medium: Color::Yellow, + usage_high: Color::Red, + } + } +} diff --git a/crates/chat-cli/src/theme/crossterm_ext.rs b/crates/chat-cli/src/theme/crossterm_ext.rs new file mode 100644 index 0000000000..c21b6d317c --- /dev/null +++ b/crates/chat-cli/src/theme/crossterm_ext.rs @@ -0,0 +1,281 @@ +//! Crossterm extensions for the theme system +use crossterm::style::{ + Attribute, + Color, + ResetColor, + SetAttribute, + SetForegroundColor, +}; + +use crate::theme::theme; + +/// Unified text styling API that provides both string methods and crossterm commands +pub struct StyledText; + +impl StyledText { + // ===== High-level string methods ===== + // These return formatted strings ready for printing + + /// Create error-styled text + pub fn error(text: &str) -> String { + format!("\x1b[{}m{}\x1b[0m", color_to_ansi_code(theme().status.error), text) + } + + /// Create info-styled text + pub fn info(text: &str) -> String { + format!("\x1b[{}m{}\x1b[0m", color_to_ansi_code(theme().status.info), text) + } + + /// Create emphasis-styled text + pub fn emphasis(text: &str) -> String { + format!("\x1b[{}m{}\x1b[0m", color_to_ansi_code(theme().ui.emphasis), text) + } + + /// Create command-styled text + pub fn command(text: &str) -> String { + format!( + "\x1b[{}m{}\x1b[0m", + color_to_ansi_code(theme().ui.command_highlight), + text + ) + } + + // ===== Interactive element string methods ===== + // These are for UI elements that indicate interaction or state + + /// Create prompt-styled text + pub fn prompt(text: &str) -> String { + format!( + "\x1b[{}m{}\x1b[0m", + color_to_ansi_code(theme().interactive.prompt_symbol), + text + ) + } + + /// Create profile-styled text + pub fn profile(text: &str) -> String { + format!( + "\x1b[{}m{}\x1b[0m", + color_to_ansi_code(theme().interactive.profile_indicator), + text + ) + } + + /// Create tangent-styled text + pub fn tangent(text: &str) -> String { + format!( + "\x1b[{}m{}\x1b[0m", + color_to_ansi_code(theme().interactive.tangent_indicator), + text + ) + } + + /// Create usage-low-styled text + pub fn usage_low(text: &str) -> String { + format!( + "\x1b[{}m{}\x1b[0m", + color_to_ansi_code(theme().interactive.usage_low), + text + ) + } + + /// Create usage-medium-styled text + pub fn usage_medium(text: &str) -> String { + format!( + "\x1b[{}m{}\x1b[0m", + color_to_ansi_code(theme().interactive.usage_medium), + text + ) + } + + /// Create usage-high-styled text + pub fn usage_high(text: &str) -> String { + format!( + "\x1b[{}m{}\x1b[0m", + color_to_ansi_code(theme().interactive.usage_high), + text + ) + } + + /// Create brand-styled text (primary brand color) + pub fn brand(text: &str) -> String { + format!("\x1b[{}m{}\x1b[0m", color_to_ansi_code(theme().ui.primary_brand), text) + } + + /// Create primary-styled text (primary text color) + pub fn primary(text: &str) -> String { + format!("\x1b[{}m{}\x1b[0m", color_to_ansi_code(theme().ui.primary_text), text) + } + + /// Create secondary-styled text (muted/helper text) + pub fn secondary(text: &str) -> String { + format!("\x1b[{}m{}\x1b[0m", color_to_ansi_code(theme().ui.secondary_text), text) + } + + /// Create success-styled text + pub fn success(text: &str) -> String { + format!("\x1b[{}m{}\x1b[0m", color_to_ansi_code(theme().status.success), text) + } + + // ===== Low-level crossterm command methods ===== + // These return crossterm commands for complex terminal operations + + /// Set foreground to error color + pub fn error_fg() -> SetForegroundColor { + SetForegroundColor(theme().status.error) + } + + /// Set foreground to warning color + pub fn warning_fg() -> SetForegroundColor { + SetForegroundColor(theme().status.warning) + } + + /// Set foreground to success color + pub fn success_fg() -> SetForegroundColor { + SetForegroundColor(theme().status.success) + } + + /// Set foreground to info color + pub fn info_fg() -> SetForegroundColor { + SetForegroundColor(theme().status.info) + } + + /// Set foreground to primary brand color + pub fn brand_fg() -> SetForegroundColor { + SetForegroundColor(theme().ui.primary_brand) + } + + /// Set foreground to secondary text color + pub fn secondary_fg() -> SetForegroundColor { + SetForegroundColor(theme().ui.secondary_text) + } + + /// Set foreground to emphasis color + pub fn emphasis_fg() -> SetForegroundColor { + SetForegroundColor(theme().ui.emphasis) + } + + /// Reset all styling to default + pub fn reset() -> ResetColor { + ResetColor + } + + /// Reset attributes + pub fn reset_attributes() -> SetAttribute { + SetAttribute(Attribute::Reset) + } +} + +/// Convert a crossterm Color to ANSI color code +fn color_to_ansi_code(color: Color) -> u8 { + match color { + Color::Black => 30, + Color::DarkGrey => 90, + Color::Red => 31, + Color::DarkRed => 31, + Color::Green => 32, + Color::DarkGreen => 32, + Color::Yellow => 33, + Color::DarkYellow => 33, + Color::Blue => 34, + Color::DarkBlue => 34, + Color::Magenta => 35, + Color::DarkMagenta => 35, + Color::Cyan => 36, + Color::DarkCyan => 36, + Color::White => 37, + Color::Grey => 37, + Color::Rgb { r, g, b } => { + // For RGB colors, we'll use a simplified mapping to the closest basic color + // This is a fallback - in practice, most terminals support RGB + if r > 200 && g < 100 && b < 100 { + 31 + } + // Red-ish + else if r < 100 && g > 200 && b < 100 { + 32 + } + // Green-ish + else if r > 200 && g > 200 && b < 100 { + 33 + } + // Yellow-ish + else if r < 100 && g < 100 && b > 200 { + 34 + } + // Blue-ish + else if r > 200 && g < 100 && b > 200 { + 35 + } + // Magenta-ish + else if r < 100 && g > 200 && b > 200 { + 36 + } + // Cyan-ish + else if r > 150 && g > 150 && b > 150 { + 37 + } + // White-ish + else { + 30 + } // Black-ish + }, + Color::AnsiValue(val) => { + // Map ANSI 256 colors to basic 8 colors + match val { + 0..=7 => 30 + val, + 8..=15 => 90 + (val - 8), + _ => 37, // Default to white for other values + } + }, + Color::Reset => 37, // Default to white + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_styled_text_string_methods() { + // Test that string methods return non-empty strings + assert!(!StyledText::error("test").is_empty()); + assert!(!StyledText::info("test").is_empty()); + assert!(!StyledText::emphasis("test").is_empty()); + assert!(!StyledText::command("test").is_empty()); + assert!(!StyledText::prompt("test").is_empty()); + assert!(!StyledText::profile("test").is_empty()); + assert!(!StyledText::tangent("test").is_empty()); + assert!(!StyledText::usage_low("test").is_empty()); + assert!(!StyledText::usage_medium("test").is_empty()); + assert!(!StyledText::usage_high("test").is_empty()); + } + + #[test] + fn test_styled_text_crossterm_methods() { + // Test that crossterm methods return the expected command types + let _error_fg = StyledText::error_fg(); + let _warning_fg = StyledText::warning_fg(); + let _success_fg = StyledText::success_fg(); + let _info_fg = StyledText::info_fg(); + let _brand_fg = StyledText::brand_fg(); + let _secondary_fg = StyledText::secondary_fg(); + let _emphasis_fg = StyledText::emphasis_fg(); + let _reset = StyledText::reset(); + let _reset_attr = StyledText::reset_attributes(); + + assert!(true); + } + + #[test] + fn test_color_to_ansi_code() { + assert_eq!(color_to_ansi_code(Color::Red), 31); + assert_eq!(color_to_ansi_code(Color::Green), 32); + assert_eq!(color_to_ansi_code(Color::Blue), 34); + assert_eq!(color_to_ansi_code(Color::Yellow), 33); + assert_eq!(color_to_ansi_code(Color::Cyan), 36); + assert_eq!(color_to_ansi_code(Color::Magenta), 35); + assert_eq!(color_to_ansi_code(Color::White), 37); + assert_eq!(color_to_ansi_code(Color::Black), 30); + } +} diff --git a/crates/chat-cli/src/theme/mod.rs b/crates/chat-cli/src/theme/mod.rs new file mode 100644 index 0000000000..2ae81baf18 --- /dev/null +++ b/crates/chat-cli/src/theme/mod.rs @@ -0,0 +1,44 @@ +//! Centralized theme system for Amazon Q CLI colors +//! +//! This module provides a unified color management system that replaces inline color +//! definitions throughout the codebase with semantic theme references. The theme system +//! maintains backward compatibility with existing color systems (color_print, crossterm) +//! while providing a consistent API for color usage. + +pub mod colors; +pub mod crossterm_ext; + +use std::sync::LazyLock; + +pub use colors::*; +pub use crossterm_ext::*; + +/// Main theme configuration containing all color categories +#[derive(Debug, Clone)] +pub struct Theme { + /// Colors for status messages (error, warning, success, info) + pub status: StatusColors, + /// Colors for UI elements (branding, text, links, etc.) + pub ui: UiColors, + /// Colors for interactive elements (prompts, indicators, etc.) + pub interactive: InteractiveColors, +} + +/// Global theme instance available throughout the application +pub static DEFAULT_THEME: LazyLock = LazyLock::new(Theme::default); + +/// Get a reference to the global theme instance +pub fn theme() -> &'static Theme { + &DEFAULT_THEME +} + +impl Default for Theme { + /// Creates the default theme with colors matching the current CLI appearance + fn default() -> Self { + Self { + status: StatusColors::default(), + ui: UiColors::default(), + interactive: InteractiveColors::default(), + } + } +} diff --git a/crates/chat-cli/src/util/ui.rs b/crates/chat-cli/src/util/ui.rs index 9b8a140aca..7a551120e3 100644 --- a/crates/chat-cli/src/util/ui.rs +++ b/crates/chat-cli/src/util/ui.rs @@ -4,12 +4,12 @@ use crossterm::execute; use crossterm::style::{ self, Attribute, - Color, }; use eyre::Result; use crate::cli::feed::Feed; use crate::constants::ui_text; +use crate::theme::StyledText; /// Render changelog content from feed.json with manual formatting pub fn render_changelog_content(output: &mut impl Write) -> Result<()> { @@ -29,11 +29,11 @@ pub fn render_changelog_content(output: &mut impl Write) -> Result<()> { // Show version header execute!( output, - style::SetForegroundColor(Color::Blue), + StyledText::info_fg(), style::SetAttribute(Attribute::Bold), style::Print(format!("## {} ({})\n", entry.version, entry.date)), - style::SetAttribute(Attribute::Reset), - style::SetForegroundColor(Color::Reset), + StyledText::reset_attributes(), + StyledText::reset(), )?; let mut sorted_changes = entry.changes.clone(); @@ -46,9 +46,9 @@ pub fn render_changelog_content(output: &mut impl Write) -> Result<()> { execute!(output, style::Print("• ["))?; execute!( output, - style::SetForegroundColor(Color::Magenta), + StyledText::emphasis_fg(), style::Print(&capitalized_type), - style::SetForegroundColor(Color::Reset), + StyledText::reset(), )?; execute!(output, style::Print("] "))?; print_with_bold(output, &processed_description)?; @@ -143,7 +143,7 @@ fn print_with_bold(output: &mut impl Write, segments: &[(String, bool)]) -> Resu output, style::SetAttribute(Attribute::Bold), style::Print(text), - style::SetAttribute(Attribute::Reset), + StyledText::reset_attributes(), )?; } else { execute!(output, style::Print(text))?;