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 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}