fn list_available_ccs_profiles_with_deps(
env: &dyn CcsEnvironment,
fs: &dyn CcsFilesystem,
) -> Vec<String> {
let Some(ccs_dir) = ccs_dir_with_env(env) else {
return Vec::new();
};
let yaml_keys: Vec<String> = load_ccs_profiles_from_config_yaml_with_deps(env, fs)
.map(|m| m.into_keys().collect::<Vec<_>>())
.unwrap_or_default();
let json_keys: Vec<String> = load_ccs_profiles_from_config_json_with_deps(env, fs)
.map(|m| m.into_keys().collect::<Vec<_>>())
.unwrap_or_default();
let file_keys: Vec<String> = list_ccs_json_files_with_fs(fs, &ccs_dir)
.map(|files| {
files
.iter()
.filter_map(|path| {
path.file_name()
.and_then(|n| n.to_str())
.filter(|name| is_ccs_settings_filename(name))
.and_then(derive_ccs_profile_name_from_filename)
})
.collect()
})
.unwrap_or_default();
yaml_keys
.into_iter()
.chain(json_keys)
.chain(file_keys)
.collect::<std::collections::HashSet<_>>()
.into_iter()
.collect::<std::collections::BTreeSet<_>>()
.into_iter()
.collect()
}
pub fn find_ccs_profile_suggestions(input: &str) -> Vec<String> {
find_ccs_profile_suggestions_with_deps(&RealCcsEnvironment, &RealCcsFilesystem, input)
}
fn find_ccs_profile_suggestions_with_deps(
env: &dyn CcsEnvironment,
fs: &dyn CcsFilesystem,
input: &str,
) -> Vec<String> {
let available = list_available_ccs_profiles_with_deps(env, fs);
let input_lower = input.to_lowercase();
available
.into_iter()
.filter(|profile| {
let profile_lower = profile.to_lowercase();
profile_lower == input_lower
|| profile_lower.contains(&input_lower)
|| input_lower.contains(&profile_lower)
})
.collect()
}
pub fn load_ccs_env_vars(
profile: &str,
) -> Result<std::collections::HashMap<String, String>, CcsEnvVarsError> {
load_ccs_env_vars_with_deps(&RealCcsEnvironment, &RealCcsFilesystem, profile)
}
pub fn load_ccs_env_vars_with_deps(
env: &dyn CcsEnvironment,
fs: &dyn CcsFilesystem,
profile: &str,
) -> Result<std::collections::HashMap<String, String>, CcsEnvVarsError> {
if profile.is_empty() {
return Err(CcsEnvVarsError::InvalidProfile {
profile: profile.to_string(),
});
}
let settings_path = resolve_ccs_settings_path_with_deps(env, fs, profile)?;
let content =
fs.read_to_string(&settings_path)
.map_err(|source| CcsEnvVarsError::ReadFile {
path: settings_path.clone(),
source,
})?;
let json: serde_json::Value =
serde_json::from_str(&content).map_err(|source| CcsEnvVarsError::ParseJson {
path: settings_path.clone(),
source,
})?;
let env_obj = find_env_object(&json).ok_or_else(|| CcsEnvVarsError::MissingEnv {
path: settings_path.clone(),
})?;
let env_vars: std::collections::HashMap<String, String> = env_obj
.iter()
.map(|(key, value)| {
if !is_valid_env_var_name_portable(key) {
return Err(CcsEnvVarsError::InvalidEnvVarName {
path: settings_path.clone(),
key: key.clone(),
});
}
if is_dangerous_env_var_name(key) {
return Err(CcsEnvVarsError::DangerousEnvVar {
path: settings_path.clone(),
key: key.clone(),
});
}
let str_value =
value
.as_str()
.ok_or_else(|| CcsEnvVarsError::NonStringEnvVarValue {
path: settings_path.clone(),
key: key.clone(),
})?;
if !is_safe_env_var_value(str_value) {
return Err(CcsEnvVarsError::UnsafeEnvVarValue {
path: settings_path.clone(),
key: key.clone(),
});
}
Ok((key.clone(), str_value.to_string()))
})
.collect::<Result<_, _>>()?;
Ok(env_vars)
}
pub fn find_claude_binary() -> Option<std::path::PathBuf> {
which::which("claude").ok()
}