1use crate::constants::env::ai;
8use std::collections::HashMap;
9use std::fs;
10use std::path::Path;
11
12#[derive(Debug, Clone, Default)]
14pub struct EnvConfig {
15 pub base_url: Option<String>,
17 pub auth_token: Option<String>,
19 pub model: Option<String>,
21 pub extras: HashMap<String, String>,
23}
24
25impl EnvConfig {
26 pub fn load() -> Self {
30 let mut config = Self::load_from_dir(".");
32
33 if config.base_url.is_none() && config.auth_token.is_none() && config.model.is_none() {
35 if let Ok(exe_path) = std::env::current_exe() {
36 if let Some(exe_dir) = exe_path.parent() {
37 let exe_config = Self::load_from_dir(exe_dir.to_str().unwrap_or("."));
38 if exe_config.base_url.is_some()
40 || exe_config.auth_token.is_some()
41 || exe_config.model.is_some()
42 {
43 config = exe_config;
44 }
45 }
46 }
47 }
48
49 if let Some(home_dir) = dirs::home_dir() {
51 let settings_path = home_dir.join(".ai").join("settings.json");
52 if settings_path.exists() {
53 if let Ok(content) = fs::read_to_string(&settings_path) {
54 if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
55 if let Some(env) = json.get("env").and_then(|v| v.as_object()) {
56 for (key, value) in env {
57 if let Some(v) = value.as_str() {
58 config.set_value(key, v);
59 }
60 }
61 }
62 }
63 }
64 }
65 }
66
67 config.load_from_env();
69
70 config
71 }
72
73 pub fn load_from_dir(dir: &str) -> Self {
75 let mut config = Self::default();
76
77 let env_path = Path::new(dir).join(".env");
79 if env_path.exists() {
80 if let Ok(content) = fs::read_to_string(&env_path) {
81 config.parse_env_file(&content);
82 }
83 }
84
85 let mut current = Path::new(dir);
87 for _ in 0..3 {
88 if let Some(parent) = current.parent() {
89 let parent_env = parent.join(".env");
90 if parent_env.exists() && parent_env != env_path {
91 if let Ok(content) = fs::read_to_string(&parent_env) {
92 config.parse_env_file(&content);
93 }
94 }
95 current = parent;
96 } else {
97 break;
98 }
99 }
100
101 config.load_from_env();
103
104 config
105 }
106
107 fn parse_env_file(&mut self, content: &str) {
109 for line in content.lines() {
110 let line = line.trim();
111 if line.is_empty() || line.starts_with('#') {
113 continue;
114 }
115
116 if let Some((key, value)) = line.split_once('=') {
118 let key = key.trim();
119 let value = value.trim();
120
121 let value = value.trim_matches('"').trim_matches('\'');
123
124 self.set_value(key, value);
125 }
126 }
127 }
128
129 fn load_from_env(&mut self) {
131 if let Ok(val) = std::env::var(ai::BASE_URL) {
133 self.base_url = Some(val);
134 }
135
136 if let Ok(val) = std::env::var(ai::AUTH_TOKEN) {
138 self.auth_token = Some(val);
139 }
140
141 if self.auth_token.is_none() {
143 if let Ok(val) = std::env::var(ai::API_KEY) {
144 self.auth_token = Some(val);
145 }
146 }
147
148 if self.auth_token.is_none() {
150 if let Ok(val) = std::env::var(ai::ANTHROPIC_AUTH_TOKEN) {
151 self.auth_token = Some(val);
152 }
153 }
154
155 if let Ok(val) = std::env::var(ai::MODEL) {
157 self.model = Some(val);
158 }
159
160 if self.model.is_none() {
162 if let Ok(val) = std::env::var(ai::ANTHROPIC_MODEL) {
163 self.model = Some(val);
164 }
165 }
166
167 for (key, value) in std::env::vars() {
169 if key.starts_with("AI_") {
170 match key.as_str() {
171 "AI_BASE_URL" | "AI_AUTH_TOKEN" | "AI_MODEL" => {} _ => {
173 self.extras.insert(key, value);
174 }
175 }
176 }
177 }
178 }
179
180 fn set_value(&mut self, key: &str, value: &str) {
182 match key {
183 "AI_BASE_URL" => self.base_url = Some(value.to_string()),
184 ai::BASE_URL => {
185 if self.base_url.is_none() {
186 self.base_url = Some(value.to_string());
187 }
188 }
189 "AI_AUTH_TOKEN" => self.auth_token = Some(value.to_string()),
190 ai::API_KEY | ai::AUTH_TOKEN => {
191 if self.auth_token.is_none() {
193 self.auth_token = Some(value.to_string());
194 }
195 }
196 "AI_MODEL" => self.model = Some(value.to_string()),
197 ai::MODEL => {
198 if self.model.is_none() {
199 self.model = Some(value.to_string());
200 }
201 }
202 _ => {
203 if key.starts_with("AI_") {
204 self.extras.insert(key.to_string(), value.to_string());
205 }
206 }
207 }
208 }
209
210 pub fn get(&self, key: &str) -> Option<&str> {
212 match key {
213 "AI_BASE_URL" => self.base_url.as_deref(),
214 "AI_AUTH_TOKEN" => self.auth_token.as_deref(),
215 "AI_MODEL" => self.model.as_deref(),
216 _ => self.extras.get(key).map(|s| s.as_str()),
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_parse_env_file() {
227 let mut config = EnvConfig::default();
228 config.parse_env_file(
229 r#"
230# Comment
231AI_BASE_URL="http://localhost:8000"
232AI_AUTH_TOKEN='test-token'
233AI_MODEL=claude-sonnet-4-6
234"#,
235 );
236
237 assert_eq!(config.base_url, Some("http://localhost:8000".to_string()));
238 assert_eq!(config.auth_token, Some("test-token".to_string()));
239 assert_eq!(config.model, Some("claude-sonnet-4-6".to_string()));
240 }
241
242 #[test]
243 fn test_get_values() {
244 let config = EnvConfig {
245 base_url: Some("http://test".to_string()),
246 auth_token: Some("token".to_string()),
247 model: Some("model".to_string()),
248 extras: HashMap::new(),
249 };
250
251 assert_eq!(config.get("AI_BASE_URL"), Some("http://test"));
252 assert_eq!(config.get("AI_AUTH_TOKEN"), Some("token"));
253 assert_eq!(config.get("AI_MODEL"), Some("model"));
254 assert_eq!(config.get("UNKNOWN"), None);
255 }
256}
257
258fn read_assistant_mode_flag() -> bool {
265 if let Ok(val) = std::env::var(ai::CODE_ASSISTANT_MODE) {
266 return val == "1" || val == "true";
267 }
268
269 false
270}
271
272pub fn is_assistant_mode() -> bool {
275 read_assistant_mode_flag()
276}
277
278pub fn is_assistant_mode_enabled() -> bool {
280 read_assistant_mode_flag()
281}