lean_ctx/cli/
discover_cmd.rs1use super::common::load_shell_history;
2
3pub fn cmd_discover(args: &[String]) {
4 let history = load_shell_history();
5 if history.is_empty() {
6 println!("No shell history found.");
7 return;
8 }
9
10 let result = crate::tools::ctx_discover::analyze_history(&history, 20);
11
12 if let Some(path) = card_target(args) {
13 match std::fs::write(
14 &path,
15 crate::tools::ctx_discover::render_before_card(&result),
16 ) {
17 Ok(()) => println!(
18 "Before-card written to {path}\n\
19 Share it, or run `lean-ctx gain --wrapped` after a week for your real numbers."
20 ),
21 Err(e) => {
22 eprintln!("Failed to write {path}: {e}");
23 std::process::exit(1);
24 }
25 }
26 return;
27 }
28
29 println!("{}", crate::tools::ctx_discover::format_cli_output(&result));
30}
31
32fn card_target(args: &[String]) -> Option<String> {
35 let mut requested = false;
36 let mut path: Option<String> = None;
37 for (i, a) in args.iter().enumerate() {
38 if let Some(v) = a.strip_prefix("--card=") {
39 requested = true;
40 path = Some(v.to_string());
41 } else if a == "--card" {
42 requested = true;
43 if let Some(next) = args.get(i + 1)
44 && !next.starts_with('-')
45 {
46 path = Some(next.clone());
47 }
48 }
49 }
50 requested.then(|| path.unwrap_or_else(|| "lean-ctx-before.svg".to_string()))
51}
52
53fn first_run_marker() -> Option<std::path::PathBuf> {
55 crate::core::paths::cache_dir()
56 .ok()
57 .map(|d| d.join(".first_run_wow_done"))
58}
59
60pub fn show_first_run_wow() {
65 let Some(marker) = first_run_marker() else {
66 return;
67 };
68 if marker.exists() {
69 return;
70 }
71 if let Some(parent) = marker.parent() {
73 let _ = std::fs::create_dir_all(parent);
74 }
75 let _ = std::fs::write(&marker, "shown\n");
76
77 let history = load_shell_history();
78 if history.is_empty() {
79 return;
80 }
81 let result = crate::tools::ctx_discover::analyze_history(&history, 20);
82 let total_missed: u32 = result.missed_commands.iter().map(|m| m.count).sum();
83 if total_missed == 0 {
84 return;
85 }
86
87 let bold = "\x1b[1m";
88 let green = "\x1b[32m";
89 let yellow = "\x1b[33m";
90 let dim = "\x1b[2m";
91 let rst = "\x1b[0m";
92
93 let top = result
94 .missed_commands
95 .iter()
96 .take(3)
97 .map(|m| format!("{} {}x", m.prefix, m.count))
98 .collect::<Vec<_>>()
99 .join(" ");
100
101 println!();
102 println!(" {bold}Here's what lean-ctx is about to start saving you{rst}");
103 println!(" {dim}estimated from your shell history — nothing leaves your machine{rst}");
104 println!();
105 if result.has_measured_data {
106 let monthly = result.potential_usd * 30.0;
108 let saved = crate::core::wrapped::format_tokens(result.potential_tokens as u64);
109 println!(
110 " {green}~{saved} tokens{rst}{dim}/month{rst} across {total_missed} uncompressed commands {dim}(~${monthly:.0}/mo, from your measured rate){rst}"
111 );
112 } else {
113 println!(
115 " {green}{total_missed} commands{rst} {dim}that lean-ctx will now compress automatically{rst}"
116 );
117 }
118 if !top.is_empty() {
119 println!(" {dim}top: {top}{rst}");
120 }
121 println!();
122 println!(
123 " {yellow}Run {bold}lean-ctx gain --wrapped{rst}{yellow} after a week for your real numbers — and a shareable card.{rst}"
124 );
125 println!();
126}
127
128pub fn cmd_ghost(args: &[String]) {
129 let json = args.iter().any(|a| a == "--json");
130
131 let history = load_shell_history();
132 let discover = crate::tools::ctx_discover::analyze_history(&history, 20);
133
134 let session = crate::core::session::SessionState::load_latest();
135 let store = crate::core::stats::load();
136
137 let unoptimized_tokens = discover.potential_tokens;
138 let _unoptimized_usd = discover.potential_usd;
139
140 let redundant_reads = store.cep.total_cache_hits as usize;
145 let avg_read_tokens = crate::core::heatmap::HeatMap::load()
146 .avg_original_tokens_per_access()
147 .unwrap_or(0) as usize;
148 let redundant_tokens = redundant_reads.saturating_mul(avg_read_tokens);
149
150 let total_ghost = unoptimized_tokens + redundant_tokens;
155 let total_usd =
156 total_ghost as f64 * crate::core::stats::DEFAULT_INPUT_PRICE_PER_M / 1_000_000.0;
157 let monthly_usd = total_usd * 30.0;
158
159 if json {
160 let obj = serde_json::json!({
161 "ghost_tokens": total_ghost,
162 "breakdown": {
163 "unoptimized_shells": unoptimized_tokens,
164 "redundant_reads": redundant_tokens,
165 "truncated_contexts": 0,
166 },
167 "estimated_usd": total_usd,
168 "monthly_usd": monthly_usd,
169 "session_active": session.is_some(),
170 "history_commands": discover.total_commands,
171 "already_optimized": discover.already_optimized,
172 });
173 println!("{}", serde_json::to_string_pretty(&obj).unwrap_or_default());
174 return;
175 }
176
177 let bold = "\x1b[1m";
178 let green = "\x1b[32m";
179 let yellow = "\x1b[33m";
180 let red = "\x1b[31m";
181 let dim = "\x1b[2m";
182 let rst = "\x1b[0m";
183 let white = "\x1b[97m";
184
185 println!();
186 println!(" {bold}{white}lean-ctx ghost report{rst}");
187 println!(" {dim}{}{rst}", "=".repeat(40));
188 println!();
189
190 if total_ghost == 0 {
191 println!(" {green}No ghost tokens detected!{rst}");
192 println!(
193 " {dim}All {} commands optimized.{rst}",
194 discover.total_commands
195 );
196 println!();
197 return;
198 }
199
200 let severity = if total_ghost > 10000 {
201 red
202 } else if total_ghost > 3000 {
203 yellow
204 } else {
205 green
206 };
207
208 println!(
209 " {bold}Ghost Tokens found:{rst} {severity}{total_ghost:>8}{rst} tokens {dim}(~${total_usd:.2}){rst}"
210 );
211 println!();
212
213 if unoptimized_tokens > 0 {
214 let missed_count: u32 = discover.missed_commands.iter().map(|m| m.count).sum();
215 println!(
216 " {dim} Unoptimized shells:{rst} {white}{unoptimized_tokens:>8}{rst} {dim}({missed_count} cmds without lean-ctx){rst}"
217 );
218 }
219 if redundant_tokens > 0 {
220 println!(
221 " {dim} Redundant reads:{rst} {white}{redundant_tokens:>8}{rst} {dim}({redundant_reads} cache hits = wasted re-reads){rst}"
222 );
223 }
224 println!();
225 println!(" {bold}Monthly savings potential:{rst} {green}${monthly_usd:.2}{rst}");
226
227 if !discover.missed_commands.is_empty() {
228 println!();
229 println!(" {bold}Top unoptimized commands:{rst}");
230 for m in discover.missed_commands.iter().take(5) {
231 println!(
232 " {dim}{:>4}x{rst} {white}{:<12}{rst} {dim}{}{rst}",
233 m.count, m.prefix, m.description
234 );
235 }
236 }
237
238 println!();
239 if discover.already_optimized == 0 {
240 println!(
241 " {yellow}Run '{bold}lean-ctx onboard{rst}{yellow}' to eliminate ghost tokens.{rst}"
242 );
243 } else {
244 println!(
245 " {dim}Already optimized: {}/{} commands{rst}",
246 discover.already_optimized, discover.total_commands
247 );
248 }
249 println!();
250}