Skip to main content

lean_ctx/core/
config.rs

1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3use std::sync::Mutex;
4use std::time::SystemTime;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(default)]
8pub struct Config {
9    pub ultra_compact: bool,
10    pub tee_on_error: bool,
11    pub checkpoint_interval: u32,
12    pub excluded_commands: Vec<String>,
13    pub custom_aliases: Vec<AliasEntry>,
14    /// Commands taking longer than this threshold (ms) are recorded in the slow log.
15    /// Set to 0 to disable slow logging.
16    pub slow_command_threshold_ms: u64,
17    #[serde(default = "default_theme")]
18    pub theme: String,
19    #[serde(default)]
20    pub cloud: CloudConfig,
21}
22
23fn default_theme() -> String {
24    "default".to_string()
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, Default)]
28#[serde(default)]
29pub struct CloudConfig {
30    pub contribute_enabled: bool,
31    pub last_contribute: Option<String>,
32    pub last_sync: Option<String>,
33    pub last_model_pull: Option<String>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct AliasEntry {
38    pub command: String,
39    pub alias: String,
40}
41
42impl Default for Config {
43    fn default() -> Self {
44        Self {
45            ultra_compact: false,
46            tee_on_error: false,
47            checkpoint_interval: 15,
48            excluded_commands: Vec::new(),
49            custom_aliases: Vec::new(),
50            slow_command_threshold_ms: 5000,
51            theme: default_theme(),
52            cloud: CloudConfig::default(),
53        }
54    }
55}
56
57impl Config {
58    pub fn path() -> Option<PathBuf> {
59        dirs::home_dir().map(|h| h.join(".lean-ctx").join("config.toml"))
60    }
61
62    pub fn load() -> Self {
63        static CACHE: Mutex<Option<(Config, SystemTime)>> = Mutex::new(None);
64
65        let path = match Self::path() {
66            Some(p) => p,
67            None => return Self::default(),
68        };
69
70        let mtime = std::fs::metadata(&path)
71            .and_then(|m| m.modified())
72            .unwrap_or(SystemTime::UNIX_EPOCH);
73
74        if let Ok(guard) = CACHE.lock() {
75            if let Some((ref cfg, ref cached_mtime)) = *guard {
76                if *cached_mtime == mtime {
77                    return cfg.clone();
78                }
79            }
80        }
81
82        let cfg = match std::fs::read_to_string(&path) {
83            Ok(content) => toml::from_str(&content).unwrap_or_default(),
84            Err(_) => Self::default(),
85        };
86
87        if let Ok(mut guard) = CACHE.lock() {
88            *guard = Some((cfg.clone(), mtime));
89        }
90
91        cfg
92    }
93
94    pub fn save(&self) -> Result<(), String> {
95        let path = Self::path().ok_or("cannot determine home directory")?;
96        if let Some(parent) = path.parent() {
97            std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
98        }
99        let content = toml::to_string_pretty(self).map_err(|e| e.to_string())?;
100        std::fs::write(&path, content).map_err(|e| e.to_string())
101    }
102
103    pub fn show(&self) -> String {
104        let path = Self::path()
105            .map(|p| p.to_string_lossy().to_string())
106            .unwrap_or_else(|| "~/.lean-ctx/config.toml".to_string());
107        let content = toml::to_string_pretty(self).unwrap_or_default();
108        format!("Config: {path}\n\n{content}")
109    }
110}