use std::path::PathBuf;
use crate::brand::{BRAND, env_name};
pub const ENV_PREFIX: &str = "INDUSAGI";
pub const SDK_CREDENTIALS_MARKER: &str = "<sdk-managed-credentials>";
pub fn read_env(suffix: &str) -> Option<String> {
read_raw(&env_name(suffix))
}
pub fn read_raw(name: &str) -> Option<String> {
match std::env::var(name) {
Ok(raw) => {
let trimmed = raw.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
Err(_) => None,
}
}
pub fn indusagi_home() -> PathBuf {
if let Some(home) = read_env("HOME") {
return PathBuf::from(home);
}
home_dir().unwrap_or_else(|| PathBuf::from("."))
}
pub(crate) fn home_dir() -> Option<PathBuf> {
read_raw("HOME")
.or_else(|| read_raw("USERPROFILE"))
.map(PathBuf::from)
}
pub const PROVIDER_ENV_MAP: &[(&str, &str)] = &[
("openai", "OPENAI_API_KEY"),
("azure-openai-responses", "AZURE_OPENAI_API_KEY"),
("google", "GEMINI_API_KEY"),
("groq", "GROQ_API_KEY"),
("cerebras", "CEREBRAS_API_KEY"),
("xai", "XAI_API_KEY"),
("openrouter", "OPENROUTER_API_KEY"),
("vercel-ai-gateway", "AI_GATEWAY_API_KEY"),
("zai", "ZAI_API_KEY"),
("mistral", "MISTRAL_API_KEY"),
("minimax", "MINIMAX_API_KEY"),
("minimax-cn", "MINIMAX_CN_API_KEY"),
("opencode", "OPENCODE_API_KEY"),
("sarvam", "SARVAM_API_KEY"),
("krutrim", "KRUTRIM_API_KEY"),
("nvidia", "NVIDIA_API_KEY"),
];
pub fn provider_env_var(provider: &str) -> Option<&'static str> {
PROVIDER_ENV_MAP
.iter()
.find(|(name, _)| *name == provider)
.map(|(_, var)| *var)
}
pub fn brand_env_prefix() -> &'static str {
BRAND.env_prefix
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn env_name_is_re_exported_grammar() {
assert_eq!(env_name("home"), "INDUSAGI_HOME");
assert_eq!(ENV_PREFIX, "INDUSAGI");
assert_eq!(brand_env_prefix(), "INDUSAGI_");
}
#[test]
fn read_raw_trims_and_treats_empty_as_absent() {
let name = "INDUSAGI_TEST_READ_RAW_X1";
unsafe {
std::env::set_var(name, " spaced value ");
}
assert_eq!(read_raw(name), Some("spaced value".to_string()));
unsafe {
std::env::set_var(name, " ");
}
assert_eq!(read_raw(name), None, "whitespace-only is absent");
unsafe {
std::env::remove_var(name);
}
assert_eq!(read_raw(name), None, "unset is absent");
}
#[test]
fn indusagi_home_honors_the_override_env() {
let name = env_name("HOME");
unsafe {
std::env::set_var(&name, "/tmp/sandbox-home");
}
assert_eq!(indusagi_home(), PathBuf::from("/tmp/sandbox-home"));
unsafe {
std::env::set_var(&name, " ");
}
let fallback = indusagi_home();
assert_ne!(fallback, PathBuf::from(" "));
unsafe {
std::env::remove_var(&name);
}
}
#[test]
fn provider_table_has_the_15_locked_rows() {
assert_eq!(PROVIDER_ENV_MAP.len(), 16); assert_eq!(provider_env_var("openai"), Some("OPENAI_API_KEY"));
assert_eq!(provider_env_var("google"), Some("GEMINI_API_KEY"));
assert_eq!(
provider_env_var("azure-openai-responses"),
Some("AZURE_OPENAI_API_KEY")
);
assert_eq!(provider_env_var("anthropic"), None);
assert_eq!(provider_env_var("amazon-bedrock"), None);
}
#[test]
fn sdk_marker_is_the_locked_sentinel() {
assert_eq!(SDK_CREDENTIALS_MARKER, "<sdk-managed-credentials>");
}
}