diff --git a/crates/chat-cli/src/api_client/mod.rs b/crates/chat-cli/src/api_client/mod.rs index f21b448b77..8363d377a2 100644 --- a/crates/chat-cli/src/api_client/mod.rs +++ b/crates/chat-cli/src/api_client/mod.rs @@ -138,7 +138,7 @@ impl ApiClient { model_cache: Arc::new(RwLock::new(None)), }; - if let Ok(json) = env.get("Q_MOCK_CHAT_RESPONSE") { + if let Some(json) = crate::util::env_var::get_mock_chat_response(env) { this.set_mock_output(serde_json::from_str(fs.read_to_string(json).await.unwrap().as_str()).unwrap()); } @@ -148,7 +148,7 @@ impl ApiClient { // If SIGV4_AUTH_ENABLED is true, use Q developer client let mut streaming_client = None; let mut sigv4_streaming_client = None; - match env.get("AMAZON_Q_SIGV4").is_ok() { + match crate::util::env_var::is_sigv4_enabled(env) { true => { let credentials_chain = CredentialsChain::new().await; if let Err(err) = credentials_chain.provide_credentials().await { diff --git a/crates/chat-cli/src/auth/builder_id.rs b/crates/chat-cli/src/auth/builder_id.rs index 72c59115fd..ec8df09713 100644 --- a/crates/chat-cli/src/auth/builder_id.rs +++ b/crates/chat-cli/src/auth/builder_id.rs @@ -63,6 +63,8 @@ use crate::database::{ Database, Secret, }; +use crate::os::Env; +use crate::util::env_var::is_sigv4_enabled; #[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum OAuthFlow { @@ -589,7 +591,7 @@ pub async fn poll_create_token( pub async fn is_logged_in(database: &mut Database) -> bool { // Check for BuilderId if not using Sigv4 - if std::env::var("AMAZON_Q_SIGV4").is_ok_and(|v| !v.is_empty()) { + if is_sigv4_enabled(&Env::new()) { debug!("logged in using sigv4 credentials"); return true; } diff --git a/crates/chat-cli/src/cli/chat/cli/editor.rs b/crates/chat-cli/src/cli/chat/cli/editor.rs index b1934ab695..93f7edbfa2 100644 --- a/crates/chat-cli/src/cli/chat/cli/editor.rs +++ b/crates/chat-cli/src/cli/chat/cli/editor.rs @@ -11,6 +11,7 @@ use crate::cli::chat::{ ChatState, }; use crate::theme::StyledText; +use crate::util::env_var::get_editor; #[deny(missing_docs)] #[derive(Debug, PartialEq, Args)] @@ -86,7 +87,7 @@ impl EditorArgs { /// Launch the user's preferred editor with the given file path fn launch_editor(file_path: &std::path::Path) -> Result<(), ChatError> { // Get the editor from environment variable or use a default - let editor_cmd = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string()); + let editor_cmd = get_editor(); // Parse the editor command to handle arguments let mut parts = diff --git a/crates/chat-cli/src/cli/chat/cli/usage/mod.rs b/crates/chat-cli/src/cli/chat/cli/usage/mod.rs index bb01cfdcfb..c279020592 100644 --- a/crates/chat-cli/src/cli/chat/cli/usage/mod.rs +++ b/crates/chat-cli/src/cli/chat/cli/usage/mod.rs @@ -1,7 +1,11 @@ use clap::Args; use crate::cli::chat::token_counter::TokenCount; -use crate::cli::chat::{ChatError, ChatSession, ChatState}; +use crate::cli::chat::{ + ChatError, + ChatSession, + ChatState, +}; use crate::os::Os; pub mod usage_data_provider; @@ -32,6 +36,8 @@ impl UsageArgs { pub async fn execute(self, os: &Os, session: &mut ChatSession) -> Result { let usage_data = usage_data_provider::get_detailed_usage_data(session, os).await?; usage_renderer::render_context_window(&usage_data, session).await?; - Ok(ChatState::PromptUser { skip_printing_tools: true }) + Ok(ChatState::PromptUser { + skip_printing_tools: true, + }) } -} \ No newline at end of file +} diff --git a/crates/chat-cli/src/cli/chat/cli/usage/usage_data_provider.rs b/crates/chat-cli/src/cli/chat/cli/usage/usage_data_provider.rs index 15e2c03ed9..aec77e6d8f 100644 --- a/crates/chat-cli/src/cli/chat/cli/usage/usage_data_provider.rs +++ b/crates/chat-cli/src/cli/chat/cli/usage/usage_data_provider.rs @@ -1,10 +1,19 @@ use crate::cli::chat::cli::model::context_window_tokens; -use crate::cli::chat::token_counter::{CharCount, TokenCount}; -use crate::cli::chat::{ChatError, ChatSession}; +use crate::cli::chat::token_counter::{ + CharCount, + TokenCount, +}; +use crate::cli::chat::{ + ChatError, + ChatSession, +}; use crate::os::Os; /// Get detailed usage data for context window analysis -pub(super) async fn get_detailed_usage_data(session: &mut ChatSession, os: &Os) -> Result { +pub(super) async fn get_detailed_usage_data( + session: &mut ChatSession, + os: &Os, +) -> Result { let context_window_size = context_window_tokens(session.conversation.model_info.as_ref()); let state = session diff --git a/crates/chat-cli/src/cli/chat/cli/usage/usage_renderer.rs b/crates/chat-cli/src/cli/chat/cli/usage/usage_renderer.rs index dcc576a168..7125ba817b 100644 --- a/crates/chat-cli/src/cli/chat/cli/usage/usage_renderer.rs +++ b/crates/chat-cli/src/cli/chat/cli/usage/usage_renderer.rs @@ -1,8 +1,15 @@ use crossterm::style::Attribute; -use crossterm::{execute, queue, style}; +use crossterm::{ + execute, + queue, + style, +}; use crate::cli::chat::token_counter::TokenCount; -use crate::cli::chat::{ChatError, ChatSession}; +use crate::cli::chat::{ + ChatError, + ChatSession, +}; use crate::theme::StyledText; /// Calculate usage percentage from token counts (private utility) @@ -11,7 +18,10 @@ fn calculate_usage_percentage(tokens: TokenCount, context_window_size: usize) -> } /// Render context window information section -pub async fn render_context_window(usage_data: &super::DetailedUsageData, session: &mut ChatSession) -> Result<(), ChatError> { +pub async fn render_context_window( + usage_data: &super::DetailedUsageData, + session: &mut ChatSession, +) -> Result<(), ChatError> { if !usage_data.dropped_context_files.is_empty() { execute!( session.stderr, diff --git a/crates/chat-cli/src/cli/chat/tools/delegate.rs b/crates/chat-cli/src/cli/chat/tools/delegate.rs index 55eb8d5870..fde2288cb2 100644 --- a/crates/chat-cli/src/cli/chat/tools/delegate.rs +++ b/crates/chat-cli/src/cli/chat/tools/delegate.rs @@ -41,6 +41,7 @@ use crate::cli::{ }; use crate::os::Os; use crate::theme::StyledText; +use crate::util::env_var::get_all_env_vars; use crate::util::paths::PathResolver; /// Launch and manage async agent processes. Delegate tasks to agents that run independently in @@ -334,7 +335,7 @@ pub async fn spawn_agent_process(os: &Os, agent: &str, task: &str) -> Result( max_result_size: usize, mut updates: Option, ) -> Result { - let shell = std::env::var("AMAZON_Q_CHAT_SHELL").unwrap_or("bash".to_string()); + let shell = get_chat_shell(); // Set up environment variables with user agent metadata for CloudTrail tracking let env_vars = env_vars_with_user_agent(os); 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 b5ba02d10c..09a64058a1 100644 --- a/crates/chat-cli/src/cli/chat/tools/fs_write.rs +++ b/crates/chat-cli/src/cli/chat/tools/fs_write.rs @@ -311,11 +311,11 @@ impl FsWrite { let relative_path = format_path(cwd, &path); let prev = if os.fs.exists(&path) { let file = os.fs.read_to_string_sync(&path)?; - stylize_output_if_able(os, &path, &file) + stylize_output_if_able(&path, &file) } else { Default::default() }; - let new = stylize_output_if_able(os, &relative_path, &file_text); + let new = stylize_output_if_able(&relative_path, &file_text); print_diff(output, &prev, &new, 1)?; // Display summary as purpose if available after the diff @@ -343,8 +343,8 @@ impl FsWrite { let old = [prefix, insert_line_content, suffix].join(""); let new = [prefix, insert_line_content, new_str, suffix].join(""); - let old = stylize_output_if_able(os, &relative_path, &old); - let new = stylize_output_if_able(os, &relative_path, &new); + let old = stylize_output_if_able(&relative_path, &old); + let new = stylize_output_if_able(&relative_path, &new); print_diff(output, &old, &new, start_line)?; // Display summary as purpose if available after the diff @@ -362,8 +362,8 @@ impl FsWrite { Some((start_line, end_line)) => (start_line, end_line), _ => (0, 0), }; - let old_str = stylize_output_if_able(os, &relative_path, old_str); - let new_str = stylize_output_if_able(os, &relative_path, new_str); + let old_str = stylize_output_if_able(&relative_path, old_str); + let new_str = stylize_output_if_able(&relative_path, new_str); print_diff(output, &old_str, &new_str, start_line)?; // Display summary as purpose if available after the diff @@ -375,7 +375,7 @@ impl FsWrite { let path = sanitize_path_tool_arg(os, path); let relative_path = format_path(cwd, &path); let start_line = os.fs.read_to_string_sync(&path)?.lines().count() + 1; - let file = stylize_output_if_able(os, &relative_path, new_str); + let file = stylize_output_if_able(&relative_path, new_str); print_diff(output, &Default::default(), &file, start_line)?; // Display summary as purpose if available after the diff @@ -763,8 +763,8 @@ fn terminal_width_required_for_line_count(line_count: usize) -> usize { line_count.to_string().chars().count() } -fn stylize_output_if_able(os: &Os, path: impl AsRef, file_text: &str) -> StylizedFile { - if supports_truecolor(os) { +fn stylize_output_if_able(path: impl AsRef, file_text: &str) -> StylizedFile { + if supports_truecolor() { match stylized_file(path, file_text) { Ok(s) => return s, Err(err) => { diff --git a/crates/chat-cli/src/cli/chat/tools/mod.rs b/crates/chat-cli/src/cli/chat/tools/mod.rs index 36fa167d5b..4859fbf210 100644 --- a/crates/chat-cli/src/cli/chat/tools/mod.rs +++ b/crates/chat-cli/src/cli/chat/tools/mod.rs @@ -444,9 +444,9 @@ fn format_path(cwd: impl AsRef, path: impl AsRef) -> String { .unwrap_or(path.as_ref().to_string_lossy().to_string()) } -fn supports_truecolor(os: &Os) -> bool { +fn supports_truecolor() -> bool { // Simple override to disable truecolor since shell_color doesn't use Context. - !os.env.get("Q_DISABLE_TRUECOLOR").is_ok_and(|s| !s.is_empty()) + !crate::util::env_var::is_truecolor_disabled() && shell_color::get_color_support().contains(shell_color::ColorSupport::TERM24BIT) } @@ -514,7 +514,7 @@ pub fn queue_function_result(result: &str, updates: &mut impl Write, is_error: b /// Helper function to set up environment variables with user agent metadata for CloudTrail tracking pub fn env_vars_with_user_agent(os: &Os) -> std::collections::HashMap { - let mut env_vars: std::collections::HashMap = std::env::vars().collect(); + let mut env_vars: std::collections::HashMap = crate::util::env_var::get_all_env_vars().collect(); // Set up additional metadata for the AWS CLI user agent let user_agent_metadata_value = format!( diff --git a/crates/chat-cli/src/cli/chat/util/mod.rs b/crates/chat-cli/src/cli/chat/util/mod.rs index 6b9aa05dfd..27b02c54ff 100644 --- a/crates/chat-cli/src/cli/chat/util/mod.rs +++ b/crates/chat-cli/src/cli/chat/util/mod.rs @@ -16,6 +16,7 @@ use eyre::Result; use super::ChatError; use super::token_counter::TokenCounter; +use crate::util::env_var::get_term; pub fn truncate_safe(s: &str, max_bytes: usize) -> &str { if s.len() <= max_bytes { @@ -119,7 +120,7 @@ pub fn play_notification_bell(requires_confirmation: bool) { /// Determine if we should play the bell based on terminal type fn should_play_bell() -> bool { // Get the TERM environment variable - if let Ok(term) = std::env::var("TERM") { + if let Some(term) = get_term() { // List of terminals known to handle bell character well let bell_compatible_terms = [ "xterm", diff --git a/crates/chat-cli/src/cli/mod.rs b/crates/chat-cli/src/cli/mod.rs index 0877c0e57b..4fb89ef475 100644 --- a/crates/chat-cli/src/cli/mod.rs +++ b/crates/chat-cli/src/cli/mod.rs @@ -1,4 +1,8 @@ use crate::theme::StyledText; +use crate::util::env_var::{ + get_aws_region, + is_log_stdout_enabled, +}; mod agent; pub mod chat; mod debug; @@ -231,7 +235,7 @@ impl Cli { ), false => None, }, - log_to_stdout: std::env::var_os("Q_LOG_STDOUT").is_some() || self.verbose > 0, + log_to_stdout: is_log_stdout_enabled() || self.verbose > 0, log_file_path: match subcommand { RootSubcommand::Chat { .. } => Some(logs_dir().expect("home dir must be set").join("qchat.log")), _ => None, @@ -240,7 +244,7 @@ impl Cli { }); // Check for region support. - if let Ok(region) = std::env::var("AWS_REGION") { + if let Ok(region) = get_aws_region() { if GOV_REGIONS.contains(®ion.as_str()) { bail!("AWS GovCloud ({region}) is not supported.") } diff --git a/crates/chat-cli/src/cli/settings.rs b/crates/chat-cli/src/cli/settings.rs index 60419ea777..2fc23d8fe4 100644 --- a/crates/chat-cli/src/cli/settings.rs +++ b/crates/chat-cli/src/cli/settings.rs @@ -10,7 +10,6 @@ use crossterm::style::Stylize; use eyre::{ Result, WrapErr, - bail, }; use globset::Glob; use serde_json::json; @@ -190,12 +189,10 @@ impl SettingsArgs { match self.cmd { Some(SettingsSubcommands::Open) => { let file = GlobalPaths::settings_path().context("Could not get settings path")?; - if let Ok(editor) = os.env.get("EDITOR") { - tokio::process::Command::new(editor).arg(file).spawn()?.wait().await?; - Ok(ExitCode::SUCCESS) - } else { - bail!("The EDITOR environment variable is not set") - } + let editor = + crate::util::env_var::try_get_editor().context("The EDITOR environment variable is not set")?; + tokio::process::Command::new(editor).arg(file).spawn()?.wait().await?; + Ok(ExitCode::SUCCESS) }, Some(SettingsSubcommands::List { all, format, state }) => { if state { diff --git a/crates/chat-cli/src/logging.rs b/crates/chat-cli/src/logging.rs index 60edfd9e96..dd567dd1b8 100644 --- a/crates/chat-cli/src/logging.rs +++ b/crates/chat-cli/src/logging.rs @@ -14,7 +14,7 @@ use tracing_subscriber::{ fmt, }; -use crate::util::env_var::Q_LOG_LEVEL; +use crate::util::env_var::get_log_level as get_env_log_level; const MAX_FILE_SIZE: u64 = 10 * 1024 * 1024; const DEFAULT_FILTER: LevelFilter = LevelFilter::ERROR; @@ -196,7 +196,7 @@ pub fn get_log_level() -> String { .lock() .unwrap() .clone() - .unwrap_or_else(|| std::env::var(Q_LOG_LEVEL).unwrap_or_else(|_| DEFAULT_FILTER.to_string())) + .unwrap_or_else(|| get_env_log_level(&crate::os::Env::new()).unwrap_or_else(|_| DEFAULT_FILTER.to_string())) } /// Set the log level to the given level. @@ -247,7 +247,7 @@ fn create_filter_layer() -> EnvFilter { .lock() .unwrap() .clone() - .or_else(|| std::env::var(Q_LOG_LEVEL).ok()); + .or_else(|| get_env_log_level(&crate::os::Env::new()).ok()); match log_level { Some(level) => EnvFilter::builder() diff --git a/crates/chat-cli/src/mcp_client/client.rs b/crates/chat-cli/src/mcp_client/client.rs index e18c36bfdf..c2d52197d3 100644 --- a/crates/chat-cli/src/mcp_client/client.rs +++ b/crates/chat-cli/src/mcp_client/client.rs @@ -59,7 +59,7 @@ use crate::cli::chat::tools::custom_tool::{ TransportType, }; use crate::os::Os; -use crate::util::paths::DirectoryError; +use crate::util::env_var::get_all_env_vars; /// Fetches all pages of specified resources from a server macro_rules! paginated_fetch { @@ -140,8 +140,6 @@ pub enum McpClientError { #[error("Client has not finished initializing")] NotReady, #[error(transparent)] - Directory(#[from] DirectoryError), - #[error(transparent)] OauthUtil(#[from] OauthUtilError), #[error(transparent)] Parse(#[from] url::ParseError), @@ -431,7 +429,7 @@ impl McpClientService { process_env_vars(envs, &os.env); cmd.envs(envs); } - cmd.envs(std::env::vars()).args(args); + cmd.envs(get_all_env_vars()).args(args); #[cfg(not(windows))] cmd.process_group(0); diff --git a/crates/chat-cli/src/os/diagnostics.rs b/crates/chat-cli/src/os/diagnostics.rs index 0152b56c3b..7b4cd9cc7a 100644 --- a/crates/chat-cli/src/os/diagnostics.rs +++ b/crates/chat-cli/src/os/diagnostics.rs @@ -112,7 +112,7 @@ impl EnvVarDiagnostic { fn new() -> EnvVarDiagnostic { let env_vars = std::env::vars() .filter(|(key, _)| { - let fig_var = crate::util::env_var::ALL.contains(&key.as_str()); + let fig_var = crate::util::consts::env_var::ALL.contains(&key.as_str()); let other_var = [ // General env vars "SHELL", diff --git a/crates/chat-cli/src/telemetry/mod.rs b/crates/chat-cli/src/telemetry/mod.rs index 3c6ef9af43..1ee5da2374 100644 --- a/crates/chat-cli/src/telemetry/mod.rs +++ b/crates/chat-cli/src/telemetry/mod.rs @@ -74,7 +74,7 @@ pub use crate::telemetry::core::{ QProfileSwitchIntent, TelemetryResult, }; -use crate::util::env_var::Q_CLI_CLIENT_APPLICATION; +use crate::util::env_var::get_cli_client_application; use crate::util::system_info::os_version; #[derive(thiserror::Error, Debug)] @@ -113,7 +113,6 @@ impl From for TelemetryError { const PRODUCT: &str = "CodeWhisperer"; const PRODUCT_VERSION: &str = env!("CARGO_PKG_VERSION"); -const CLIENT_ID_ENV_VAR: &str = "Q_TELEMETRY_CLIENT_ID"; /// A IDE toolkit telemetry stage #[derive(Debug, Clone)] @@ -499,7 +498,7 @@ async fn set_event_metadata(database: &Database, event: &mut Event) { } // Set the client application from environment variable - if let Ok(client_app) = std::env::var(Q_CLI_CLIENT_APPLICATION) { + if let Some(client_app) = get_cli_client_application() { event.set_client_application(client_app); } } @@ -515,7 +514,7 @@ struct TelemetryClient { impl TelemetryClient { async fn new(env: &Env, fs: &Fs, database: &mut Database) -> Result { let telemetry_enabled = !cfg!(test) - && env.get_os("Q_DISABLE_TELEMETRY").is_none() + && !crate::util::env_var::is_telemetry_disabled() && database.settings.get_bool(Setting::TelemetryEnabled).unwrap_or(true); // If telemetry is disabled we do not emit using toolkit_telemetry @@ -541,7 +540,7 @@ impl TelemetryClient { return Ok(uuid!("ffffffff-ffff-ffff-ffff-ffffffffffff")); } - if let Ok(client_id) = env.get(CLIENT_ID_ENV_VAR) { + if let Ok(client_id) = crate::util::env_var::get_telemetry_client_id(env) { if let Ok(uuid) = Uuid::from_str(&client_id) { return Ok(uuid); } @@ -566,7 +565,7 @@ impl TelemetryClient { } // cw telemetry is only available with bearer token auth. - let codewhisperer_client = if env.get("AMAZON_Q_SIGV4").is_ok() { + let codewhisperer_client = if crate::util::env_var::is_sigv4_enabled(&Env::new()) { None } else { Some(ApiClient::new(env, fs, database, None).await?) diff --git a/crates/chat-cli/src/util/consts.rs b/crates/chat-cli/src/util/consts.rs index 41fbf5bef3..14a0cea686 100644 --- a/crates/chat-cli/src/util/consts.rs +++ b/crates/chat-cli/src/util/consts.rs @@ -67,7 +67,52 @@ pub mod env_var { Q_BUNDLE_METADATA_PATH = "Q_BUNDLE_METADATA_PATH", /// Identifier for the client application or service using the chat-cli - Q_CLI_CLIENT_APPLICATION = "Q_CLI_CLIENT_APPLICATION" + Q_CLI_CLIENT_APPLICATION = "Q_CLI_CLIENT_APPLICATION", + + /// Enable logging to stdout + Q_LOG_STDOUT = "Q_LOG_STDOUT", + + /// Disable telemetry collection + Q_DISABLE_TELEMETRY = "Q_DISABLE_TELEMETRY", + + /// Mock chat response for testing + Q_MOCK_CHAT_RESPONSE = "Q_MOCK_CHAT_RESPONSE", + + /// Disable truecolor terminal support + Q_DISABLE_TRUECOLOR = "Q_DISABLE_TRUECOLOR", + + /// Fake remote environment for testing + Q_FAKE_IS_REMOTE = "Q_FAKE_IS_REMOTE", + + /// Codespaces environment indicator + Q_CODESPACES = "Q_CODESPACES", + + /// CI environment indicator + Q_CI = "Q_CI", + + /// Telemetry client ID + Q_TELEMETRY_CLIENT_ID = "Q_TELEMETRY_CLIENT_ID", + + /// Amazon Q SigV4 authentication + AMAZON_Q_SIGV4 = "AMAZON_Q_SIGV4", + + /// Amazon Q chat shell + AMAZON_Q_CHAT_SHELL = "AMAZON_Q_CHAT_SHELL", + + /// Editor environment variable + EDITOR = "EDITOR", + + /// Terminal type + TERM = "TERM", + + /// AWS region + AWS_REGION = "AWS_REGION", + + /// GitHub Codespaces environment + CODESPACES = "CODESPACES", + + /// CI environment + CI = "CI" } } diff --git a/crates/chat-cli/src/util/editor.rs b/crates/chat-cli/src/util/editor.rs index a7aa9baa6e..f4061ae41d 100644 --- a/crates/chat-cli/src/util/editor.rs +++ b/crates/chat-cli/src/util/editor.rs @@ -1,6 +1,8 @@ use std::path::Path; use std::process::Command; +use crate::util::env_var::get_editor; + /// Launch the user's preferred editor with the given file path. /// /// This function properly parses the EDITOR environment variable to handle @@ -13,7 +15,7 @@ use std::process::Command; /// * `Ok(())` if the editor was launched successfully and exited with success /// * `Err` if the editor failed to launch or exited with an error pub fn launch_editor(file_path: &Path) -> eyre::Result<()> { - let editor_cmd = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string()); + let editor_cmd = get_editor(); // Parse the editor command to handle arguments let mut parts = shlex::split(&editor_cmd).ok_or_else(|| eyre::eyre!("Failed to parse EDITOR command"))?; diff --git a/crates/chat-cli/src/util/env_var.rs b/crates/chat-cli/src/util/env_var.rs new file mode 100644 index 0000000000..850bd5831b --- /dev/null +++ b/crates/chat-cli/src/util/env_var.rs @@ -0,0 +1,92 @@ +use crate::os::Env; +use crate::util::consts::env_var::*; + +/// Get log level from environment +pub fn get_log_level(env: &Env) -> Result { + env.get(Q_LOG_LEVEL) +} + +/// Get chat shell with default fallback +#[cfg(unix)] +pub fn get_chat_shell() -> String { + Env::new() + .get(AMAZON_Q_CHAT_SHELL) + .unwrap_or_else(|_| "bash".to_string()) +} + +/// Check if stdout logging is enabled +pub fn is_log_stdout_enabled() -> bool { + Env::new().get_os(Q_LOG_STDOUT).is_some() +} + +/// Check if telemetry is disabled +pub fn is_telemetry_disabled() -> bool { + Env::new().get_os(Q_DISABLE_TELEMETRY).is_some() +} + +/// Get mock chat response for testing +pub fn get_mock_chat_response(env: &Env) -> Option { + env.get(Q_MOCK_CHAT_RESPONSE).ok() +} + +/// Check if truecolor is disabled +pub fn is_truecolor_disabled() -> bool { + Env::new().get_os(Q_DISABLE_TRUECOLOR).is_some_and(|s| !s.is_empty()) +} + +/// Check if remote environment is faked +pub fn is_remote_fake() -> bool { + Env::new().get_os(Q_FAKE_IS_REMOTE).is_some() +} + +/// Check if running in Codespaces +pub fn in_codespaces() -> bool { + let env = Env::new(); + env.get_os(CODESPACES).is_some() || env.get_os(Q_CODESPACES).is_some() +} + +/// Check if running in CI +pub fn in_ci() -> bool { + let env = Env::new(); + env.get_os(CI).is_some() || env.get_os(Q_CI).is_some() +} + +/// Get CLI client application +pub fn get_cli_client_application() -> Option { + Env::new().get(Q_CLI_CLIENT_APPLICATION).ok() +} + +/// Get editor with default fallback +pub fn get_editor() -> String { + Env::new().get(EDITOR).unwrap_or_else(|_| "vi".to_string()) +} + +/// Try to get editor without fallback +pub fn try_get_editor() -> Result { + Env::new().get(EDITOR) +} + +/// Get terminal type +pub fn get_term() -> Option { + Env::new().get(TERM).ok() +} + +/// Get AWS region +pub fn get_aws_region() -> Result { + Env::new().get(AWS_REGION) +} + +/// Check if SigV4 authentication is enabled +pub fn is_sigv4_enabled(env: &Env) -> bool { + env.get(AMAZON_Q_SIGV4).is_ok_and(|v| !v.is_empty()) +} + +/// Get all environment variables +pub fn get_all_env_vars() -> std::env::Vars { + std::env::vars() +} + +/// Get telemetry client ID +pub fn get_telemetry_client_id(env: &Env) -> Result { + env.get(Q_TELEMETRY_CLIENT_ID) +} diff --git a/crates/chat-cli/src/util/file_uri.rs b/crates/chat-cli/src/util/file_uri.rs index ecb987af0d..b80fd6e96d 100644 --- a/crates/chat-cli/src/util/file_uri.rs +++ b/crates/chat-cli/src/util/file_uri.rs @@ -153,16 +153,24 @@ mod tests { // the expansion behavior using error messages let uri = "file://~/test.txt"; let base = Path::new("/some/other/path"); - + // This will fail to find the file (expected), but the error should show // an expanded absolute path, not a path with literal ~ let result = resolve_file_uri(uri, base); - + match result { Err(FileUriError::FileNotFound { path }) => { // Verify the path was expanded (should start with / not ~) - assert!(path.starts_with("/"), "Path should be absolute after tilde expansion, got: {:?}", path); - assert!(!path.to_string_lossy().contains("~"), "Path should not contain literal tilde, got: {:?}", path); + assert!( + path.is_absolute(), + "Path should be absolute after tilde expansion, got: {:?}", + path + ); + assert!( + !path.to_string_lossy().contains("~"), + "Path should not contain literal tilde, got: {:?}", + path + ); }, _ => panic!("Expected FileNotFound error"), } diff --git a/crates/chat-cli/src/util/mod.rs b/crates/chat-cli/src/util/mod.rs index 4825890b88..a0097d5a4d 100644 --- a/crates/chat-cli/src/util/mod.rs +++ b/crates/chat-cli/src/util/mod.rs @@ -1,5 +1,6 @@ pub mod consts; pub mod editor; +pub mod env_var; pub mod file_uri; pub mod knowledge_store; pub mod open; diff --git a/crates/chat-cli/src/util/system_info/mod.rs b/crates/chat-cli/src/util/system_info/mod.rs index 6f4aa75f3a..62058a86ac 100644 --- a/crates/chat-cli/src/util/system_info/mod.rs +++ b/crates/chat-cli/src/util/system_info/mod.rs @@ -12,6 +12,7 @@ use serde::{ }; use crate::os::Env; +use crate::util::env_var::is_remote_fake; /// Fields for OS release information /// Fields from @@ -175,16 +176,15 @@ pub fn in_wsl() -> bool { /// Is the calling binary running on a remote instance pub fn is_remote() -> bool { // TODO(chay): Add detection for inside docker container - in_ssh() || in_wsl() || std::env::var_os("Q_FAKE_IS_REMOTE").is_some() + in_ssh() || in_wsl() || is_remote_fake() } pub fn in_codespaces() -> bool { static IN_CODESPACES: OnceLock = OnceLock::new(); - *IN_CODESPACES - .get_or_init(|| std::env::var_os("CODESPACES").is_some() || std::env::var_os("Q_CODESPACES").is_some()) + *IN_CODESPACES.get_or_init(crate::util::env_var::in_codespaces) } pub fn in_ci() -> bool { static IN_CI: OnceLock = OnceLock::new(); - *IN_CI.get_or_init(|| std::env::var_os("CI").is_some() || std::env::var_os("Q_CI").is_some()) + *IN_CI.get_or_init(crate::util::env_var::in_ci) }