1use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5use uira_core::{atomic_write_secure, UIRA_DIR};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct CliConfig {
10 #[serde(default)]
12 pub default_provider: Option<String>,
13
14 #[serde(default)]
16 pub default_model: Option<String>,
17
18 #[serde(default)]
20 pub api_keys: std::collections::HashMap<String, String>,
21
22 #[serde(default)]
24 pub working_directory: Option<PathBuf>,
25
26 #[serde(default = "default_true")]
28 pub colors: bool,
29
30 #[serde(default)]
32 pub verbose: bool,
33}
34
35fn default_true() -> bool {
36 true
37}
38
39impl Default for CliConfig {
40 fn default() -> Self {
41 Self {
42 default_provider: None,
43 default_model: None,
44 api_keys: std::collections::HashMap::new(),
45 working_directory: None,
46 colors: true,
47 verbose: false,
48 }
49 }
50}
51
52#[allow(dead_code)] impl CliConfig {
54 pub fn load() -> Self {
56 if let Some(config_dir) = dirs::config_dir() {
62 let config_path = config_dir.join("uira").join("config.toml");
63 if config_path.exists() {
64 if let Ok(content) = std::fs::read_to_string(&config_path) {
65 if let Ok(config) = toml::from_str(&content) {
66 return config;
67 }
68 }
69 }
70 }
71
72 if let Some(home) = dirs::home_dir() {
73 let config_path = home.join(UIRA_DIR).join("config.toml");
74 if config_path.exists() {
75 if let Ok(content) = std::fs::read_to_string(&config_path) {
76 if let Ok(config) = toml::from_str(&content) {
77 return config;
78 }
79 }
80 }
81 }
82
83 Self::default()
84 }
85
86 pub fn save(&self) -> std::io::Result<()> {
88 let config_dir = dirs::config_dir()
89 .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "No config dir"))?
90 .join("uira");
91
92 std::fs::create_dir_all(&config_dir)?;
93
94 let config_path = config_dir.join("config.toml");
95 let content = toml::to_string_pretty(self)
96 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
97
98 atomic_write_secure(&config_path, content.as_bytes())
99 }
100
101 pub fn get_api_key(&self, provider: &str) -> Option<&String> {
103 self.api_keys.get(provider)
104 }
105
106 pub fn set_api_key(&mut self, provider: impl Into<String>, key: impl Into<String>) {
108 self.api_keys.insert(provider.into(), key.into());
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_default_config() {
118 let config = CliConfig::default();
119 assert!(config.colors);
120 assert!(!config.verbose);
121 }
122
123 #[test]
124 fn test_api_key_management() {
125 let mut config = CliConfig::default();
126 config.set_api_key("anthropic", "sk-test-key");
127 assert_eq!(
128 config.get_api_key("anthropic"),
129 Some(&"sk-test-key".to_string())
130 );
131 }
132}