Skip to main content

lean_ctx/cli/
config_cmd.rs

1use crate::core::config;
2use crate::core::theme;
3
4pub fn cmd_config(args: &[String]) {
5    let cfg = config::Config::load();
6
7    if args.is_empty() {
8        println!("{}", cfg.show());
9        return;
10    }
11
12    match args[0].as_str() {
13        "init" | "create" => {
14            let default = config::Config::default();
15            match default.save() {
16                Ok(()) => {
17                    let path = config::Config::path().map_or_else(
18                        || "~/.lean-ctx/config.toml".to_string(),
19                        |p| p.to_string_lossy().to_string(),
20                    );
21                    println!("Created default config at {path}");
22                }
23                Err(e) => eprintln!("Error: {e}"),
24            }
25        }
26        "set" => {
27            if args.len() < 3 {
28                eprintln!("Usage: lean-ctx config set <key> <value>");
29                std::process::exit(1);
30            }
31            let mut cfg = cfg;
32            let key = &args[1];
33            let val = &args[2];
34            match key.as_str() {
35                "ultra_compact" => cfg.ultra_compact = val == "true",
36                "tee_on_error" | "tee_mode" => {
37                    cfg.tee_mode = match val.as_str() {
38                        "true" | "failures" => config::TeeMode::Failures,
39                        "always" => config::TeeMode::Always,
40                        "false" | "never" => config::TeeMode::Never,
41                        _ => {
42                            eprintln!("Valid tee_mode values: always, failures, never");
43                            std::process::exit(1);
44                        }
45                    };
46                }
47                "checkpoint_interval" => {
48                    cfg.checkpoint_interval = val.parse().unwrap_or(15);
49                }
50                "theme" => {
51                    if theme::from_preset(val).is_some() || val == "custom" {
52                        cfg.theme.clone_from(val);
53                    } else {
54                        eprintln!(
55                            "Unknown theme '{val}'. Available: {}",
56                            theme::PRESET_NAMES.join(", ")
57                        );
58                        std::process::exit(1);
59                    }
60                }
61                "slow_command_threshold_ms" => {
62                    cfg.slow_command_threshold_ms = val.parse().unwrap_or(5000);
63                }
64                "passthrough_urls" => {
65                    cfg.passthrough_urls = val.split(',').map(|s| s.trim().to_string()).collect();
66                }
67                "excluded_commands" => {
68                    cfg.excluded_commands = val
69                        .split(',')
70                        .map(|s| s.trim().to_string())
71                        .filter(|s| !s.is_empty())
72                        .collect();
73                }
74                "rules_scope" => match val.as_str() {
75                    "global" | "project" | "both" => {
76                        cfg.rules_scope = Some(val.clone());
77                    }
78                    _ => {
79                        eprintln!("Valid rules_scope values: global, project, both");
80                        std::process::exit(1);
81                    }
82                },
83                _ => {
84                    eprintln!("Unknown config key: {key}");
85                    std::process::exit(1);
86                }
87            }
88            match cfg.save() {
89                Ok(()) => println!("Updated {key} = {val}"),
90                Err(e) => eprintln!("Error saving config: {e}"),
91            }
92        }
93        _ => {
94            eprintln!("Usage: lean-ctx config [init|set <key> <value>]");
95            std::process::exit(1);
96        }
97    }
98}
99
100pub fn cmd_benchmark(args: &[String]) {
101    use crate::core::benchmark;
102
103    let action = args.first().map_or("run", std::string::String::as_str);
104
105    match action {
106        "--help" | "-h" => {
107            println!("Usage: lean-ctx benchmark run [path] [--json]");
108            println!("       lean-ctx benchmark report [path]");
109        }
110        "run" => {
111            let path = args.get(1).map_or(".", std::string::String::as_str);
112            let is_json = args.iter().any(|a| a == "--json");
113
114            let result = benchmark::run_project_benchmark(path);
115            if is_json {
116                println!("{}", benchmark::format_json(&result));
117            } else {
118                println!("{}", benchmark::format_terminal(&result));
119            }
120        }
121        "report" => {
122            let path = args.get(1).map_or(".", std::string::String::as_str);
123            let result = benchmark::run_project_benchmark(path);
124            println!("{}", benchmark::format_markdown(&result));
125        }
126        _ => {
127            if std::path::Path::new(action).exists() {
128                let result = benchmark::run_project_benchmark(action);
129                println!("{}", benchmark::format_terminal(&result));
130            } else {
131                eprintln!("Usage: lean-ctx benchmark run [path] [--json]");
132                eprintln!("       lean-ctx benchmark report [path]");
133                std::process::exit(1);
134            }
135        }
136    }
137}
138
139pub fn cmd_stats(args: &[String]) {
140    match args.first().map(std::string::String::as_str) {
141        Some("reset-cep") => {
142            crate::core::stats::reset_cep();
143            println!("CEP stats reset. Shell hook data preserved.");
144        }
145        Some("json") => {
146            let store = crate::core::stats::load();
147            println!(
148                "{}",
149                serde_json::to_string_pretty(&store).unwrap_or_else(|_| "{}".to_string())
150            );
151        }
152        _ => {
153            let store = crate::core::stats::load();
154            let input_saved = store
155                .total_input_tokens
156                .saturating_sub(store.total_output_tokens);
157            let pct = if store.total_input_tokens > 0 {
158                input_saved as f64 / store.total_input_tokens as f64 * 100.0
159            } else {
160                0.0
161            };
162            println!("Commands:    {}", store.total_commands);
163            println!("Input:       {} tokens", store.total_input_tokens);
164            println!("Output:      {} tokens", store.total_output_tokens);
165            println!("Saved:       {input_saved} tokens ({pct:.1}%)");
166            println!();
167            println!("CEP sessions:  {}", store.cep.sessions);
168            println!(
169                "CEP tokens:    {} → {}",
170                store.cep.total_tokens_original, store.cep.total_tokens_compressed
171            );
172            println!();
173            println!("Subcommands: stats reset-cep | stats json");
174        }
175    }
176}
177
178pub fn cmd_cache(args: &[String]) {
179    use crate::core::cli_cache;
180    match args.first().map(std::string::String::as_str) {
181        Some("clear") => {
182            let count = cli_cache::clear();
183            println!("Cleared {count} cached entries.");
184        }
185        Some("reset") => {
186            let project_flag = args.get(1).map(std::string::String::as_str) == Some("--project");
187            if project_flag {
188                let root =
189                    crate::core::session::SessionState::load_latest().and_then(|s| s.project_root);
190                if let Some(root) = root {
191                    let count = cli_cache::clear_project(&root);
192                    println!("Reset {count} cache entries for project: {root}");
193                } else {
194                    eprintln!("No active project root found. Start a session first.");
195                    std::process::exit(1);
196                }
197            } else {
198                let count = cli_cache::clear();
199                println!("Reset all {count} cache entries.");
200            }
201        }
202        Some("stats") => {
203            let (hits, reads, entries) = cli_cache::stats();
204            let rate = if reads > 0 {
205                (hits as f64 / reads as f64 * 100.0).round() as u32
206            } else {
207                0
208            };
209            println!("CLI Cache Stats:");
210            println!("  Entries:   {entries}");
211            println!("  Reads:     {reads}");
212            println!("  Hits:      {hits}");
213            println!("  Hit Rate:  {rate}%");
214        }
215        Some("invalidate") => {
216            if args.len() < 2 {
217                eprintln!("Usage: lean-ctx cache invalidate <path>");
218                std::process::exit(1);
219            }
220            cli_cache::invalidate(&args[1]);
221            println!("Invalidated cache for {}", args[1]);
222        }
223        _ => {
224            let (hits, reads, entries) = cli_cache::stats();
225            let rate = if reads > 0 {
226                (hits as f64 / reads as f64 * 100.0).round() as u32
227            } else {
228                0
229            };
230            println!("CLI File Cache: {entries} entries, {hits}/{reads} hits ({rate}%)");
231            println!();
232            println!("Subcommands:");
233            println!("  cache stats       Show detailed stats");
234            println!("  cache clear       Clear all cached entries");
235            println!("  cache reset       Reset all cache (or --project for current project only)");
236            println!("  cache invalidate  Remove specific file from cache");
237        }
238    }
239}