lean_ctx/cli/
config_cmd.rs1use 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}