doum_cli/system/
config.rs1use crate::llm::Provider;
2use crate::system::paths::get_config_path;
3use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5use std::fs;
6use std::path::PathBuf;
7
8#[cfg(unix)]
9use std::os::unix::fs::PermissionsExt;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct Config {
14 pub llm: LLMConfig,
15 pub context: ContextConfig,
16 pub logging: LoggingConfig,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct LLMConfig {
22 pub provider: String,
23 pub model: String,
24 pub timeout: u64,
25 pub max_retries: u32,
26 pub use_thinking: bool,
27 pub use_web_search: bool,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ContextConfig {
33 pub max_lines: usize,
34 pub max_size_kb: usize,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct LoggingConfig {
40 pub enabled: bool,
41 pub level: String,
42}
43
44fn ensure_config() -> Result<PathBuf> {
46 let config_path = get_config_path()?;
47
48 if let Some(parent) = config_path.parent()
49 && !parent.exists()
50 {
51 fs::create_dir_all(parent).context("Failed to create config directory")?;
52
53 #[cfg(unix)]
55 {
56 let metadata = fs::metadata(parent).context("Failed to read directory metadata")?;
57 let mut permissions = metadata.permissions();
58 permissions.set_mode(0o700);
59 fs::set_permissions(parent, permissions)
60 .context("Failed to set directory permissions")?;
61 }
62 }
63
64 Ok(config_path)
65}
66
67pub fn load_config() -> Result<Config> {
69 let config_path = ensure_config()?;
70
71 if config_path.exists() {
72 let content = fs::read_to_string(&config_path).context("Failed to read config file")?;
74 let config: Config = toml::from_str(&content).context("Failed to parse config file")?;
75 Ok(config)
76 } else {
77 let config = load_default_config()?;
79 save_config(&config)?;
80 Ok(config)
81 }
82}
83
84pub fn load_default_config() -> Result<Config> {
86 Ok(Config {
87 llm: LLMConfig {
88 provider: Provider::OpenAI.as_str().to_string(),
89 model: "gpt-5".to_string(),
90 timeout: 30,
91 max_retries: 3,
92 use_thinking: false,
93 use_web_search: true,
94 },
95 context: ContextConfig {
96 max_lines: 100,
97 max_size_kb: 50,
98 },
99 logging: LoggingConfig {
100 enabled: false,
101 level: "info".to_string(),
102 },
103 })
104}
105
106pub fn save_config(config: &Config) -> Result<()> {
108 let config_path = ensure_config()?;
110 let content = toml::to_string_pretty(config).context("Failed to serialize config")?;
111 fs::write(&config_path, content).context("Failed to write config file")?;
112
113 #[cfg(windows)]
115 {
116 }
119
120 #[cfg(unix)]
122 {
123 let metadata = fs::metadata(&config_path).context("File metadata read failed")?;
124 let mut permissions = metadata.permissions();
125 permissions.set_mode(0o600);
126 fs::set_permissions(&config_path, permissions).context("Failed to set file permissions")?;
127 }
128
129 Ok(())
130}