#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CcsCommandResult {
pub command: String,
}
impl PartialEq<&str> for CcsCommandResult {
fn eq(&self, other: &&str) -> bool {
self.command == *other
}
}
impl PartialEq<String> for CcsCommandResult {
fn eq(&self, other: &String) -> bool {
self.command == *other
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CcsEnvVarDebugSummary {
pub whitelisted_keys_present: Vec<String>,
pub redacted_sensitive_keys: usize,
pub hidden_non_whitelisted_keys: usize,
}
#[must_use]
pub fn ccs_env_var_debug_summary(env_vars: &HashMap<String, String>) -> CcsEnvVarDebugSummary {
let whitelisted_prefixes = ["ANTHROPIC_BASE_URL", "ANTHROPIC_MODEL"];
let sensitive_markers = ["token", "key", "secret", "password", "auth"];
let whitelisted_keys_present = env_vars
.keys()
.filter(|key| {
whitelisted_prefixes
.iter()
.any(|prefix| key.eq_ignore_ascii_case(prefix))
})
.cloned()
.collect();
let redacted_sensitive_keys = env_vars
.keys()
.filter(|key| {
let lower = key.to_ascii_lowercase();
sensitive_markers.iter().any(|m| lower.contains(m))
})
.count();
let hidden_non_whitelisted_keys = env_vars
.keys()
.filter(|key| {
!whitelisted_prefixes
.iter()
.any(|prefix| key.eq_ignore_ascii_case(prefix))
&& !sensitive_markers
.iter()
.any(|m| key.to_ascii_lowercase().contains(m))
})
.count();
CcsEnvVarDebugSummary {
whitelisted_keys_present,
redacted_sensitive_keys,
hidden_non_whitelisted_keys,
}
}
#[must_use]
pub fn resolve_ccs_agent<S: std::hash::BuildHasher>(
alias: &str,
aliases: &HashMap<String, CcsAliasConfig, S>,
defaults: &CcsConfig,
) -> Option<AgentConfig> {
let (cmd, display_name) = if alias.is_empty() {
(
CcsAliasConfig {
cmd: "ccs".to_string(),
..CcsAliasConfig::default()
},
"ccs".to_string(),
)
} else if let Some(cfg) = aliases.get(alias) {
(cfg.clone(), format!("ccs-{alias}"))
} else {
return None;
};
Some(build_ccs_agent_config(&cmd, defaults, display_name, alias))
}
const fn is_glm_alias(alias_name: &str) -> bool {
alias_name.eq_ignore_ascii_case("glm")
}
#[cfg(any(test, feature = "test-utils"))]
#[must_use]
pub fn resolve_ccs_command(
alias_config: &CcsAliasConfig,
alias_name: &str,
env_vars_loaded: bool,
profile_used_for_env: Option<&String>,
debug_mode: bool,
) -> CcsCommandResult {
resolve_ccs_command_impl(
alias_config,
alias_name,
env_vars_loaded,
profile_used_for_env,
debug_mode,
)
}
#[cfg(not(any(test, feature = "test-utils")))]
#[must_use]
pub fn resolve_ccs_command(
alias_config: &CcsAliasConfig,
alias_name: &str,
env_vars_loaded: bool,
profile_used_for_env: Option<&String>,
debug_mode: bool,
) -> CcsCommandResult {
resolve_ccs_command_impl(
alias_config,
alias_name,
env_vars_loaded,
profile_used_for_env,
debug_mode,
)
}
fn resolve_ccs_command_impl(
alias_config: &CcsAliasConfig,
alias_name: &str,
env_vars_loaded: bool,
profile_used_for_env: Option<&String>,
_debug_mode: bool,
) -> CcsCommandResult {
let original_cmd = alias_config.cmd.as_str();
find_claude_binary().map_or_else(
|| CcsCommandResult {
command: original_cmd.to_string(),
},
|claude_path| {
let can_bypass_wrapper = is_glm_alias(alias_name) && env_vars_loaded;
if !can_bypass_wrapper {
return CcsCommandResult {
command: original_cmd.to_string(),
};
}
let Ok(parts) = split_command(original_cmd) else {
return CcsCommandResult {
command: original_cmd.to_string(),
};
};
let profile = ccs_profile_from_command(original_cmd)
.or_else(|| profile_used_for_env.cloned())
.unwrap_or_else(|| alias_name.to_string());
let is_ccs_cmd = parts.first().is_some_and(|p| looks_like_ccs_executable(p));
let skip = if parts.get(1).is_some_and(|p| p == &profile) {
Some(2)
} else if parts.get(1).is_some_and(|p| p == "api")
&& parts.get(2).is_some_and(|p| p == &profile)
{
Some(3)
} else {
None
};
let is_profile_ccs_cmd = is_ccs_cmd && skip.is_some();
if !is_profile_ccs_cmd {
return CcsCommandResult {
command: original_cmd.to_string(),
};
}
let skip = skip.unwrap_or(2);
let new_parts: Vec<String> = std::iter::once(claude_path.to_string_lossy().to_string())
.chain(parts.into_iter().skip(skip))
.collect();
let new_cmd = shell_words::join(&new_parts);
CcsCommandResult { command: new_cmd }
},
)
}
include!("agent_config.rs");