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 let monthly = result.potential_usd * 30.0;
93 let saved = crate::core::wrapped::format_tokens(result.potential_tokens as u64);
94
95 println!();
96 println!(" {bold}Here's what lean-ctx just started saving you{rst}");
97 println!(" {dim}estimated from your shell history — nothing leaves your machine{rst}");
98 println!();
99 println!(
100 " {green}~{saved} tokens{rst}{dim}/month{rst} across {total_missed} uncompressed commands {dim}(~${monthly:.0}/mo){rst}"
101 );
102 let top = result
103 .missed_commands
104 .iter()
105 .take(3)
106 .map(|m| format!("{} {}x", m.prefix, m.count))
107 .collect::<Vec<_>>()
108 .join(" ");
109 if !top.is_empty() {
110 println!(" {dim}top: {top}{rst}");
111 }
112 println!();
113 println!(
114 " {yellow}Run {bold}lean-ctx gain --wrapped{rst}{yellow} after a week for your real numbers — and a shareable card.{rst}"
115 );
116 println!();
117}
118
119pub fn cmd_ghost(args: &[String]) {
120 let json = args.iter().any(|a| a == "--json");
121
122 let history = load_shell_history();
123 let discover = crate::tools::ctx_discover::analyze_history(&history, 20);
124
125 let session = crate::core::session::SessionState::load_latest();
126 let store = crate::core::stats::load();
127
128 let unoptimized_tokens = discover.potential_tokens;
129 let _unoptimized_usd = discover.potential_usd;
130
131 let redundant_reads = store.cep.total_cache_hits as usize;
132 let redundant_tokens = redundant_reads * 200;
133
134 let wasted_original = store
135 .cep
136 .total_tokens_original
137 .saturating_sub(store.cep.total_tokens_compressed) as usize;
138 let truncated_tokens = wasted_original / 3;
139
140 let total_ghost = unoptimized_tokens + redundant_tokens + truncated_tokens;
141 let total_usd =
142 total_ghost as f64 * crate::core::stats::DEFAULT_INPUT_PRICE_PER_M / 1_000_000.0;
143 let monthly_usd = total_usd * 30.0;
144
145 if json {
146 let obj = serde_json::json!({
147 "ghost_tokens": total_ghost,
148 "breakdown": {
149 "unoptimized_shells": unoptimized_tokens,
150 "redundant_reads": redundant_tokens,
151 "truncated_contexts": truncated_tokens,
152 },
153 "estimated_usd": total_usd,
154 "monthly_usd": monthly_usd,
155 "session_active": session.is_some(),
156 "history_commands": discover.total_commands,
157 "already_optimized": discover.already_optimized,
158 });
159 println!("{}", serde_json::to_string_pretty(&obj).unwrap_or_default());
160 return;
161 }
162
163 let bold = "\x1b[1m";
164 let green = "\x1b[32m";
165 let yellow = "\x1b[33m";
166 let red = "\x1b[31m";
167 let dim = "\x1b[2m";
168 let rst = "\x1b[0m";
169 let white = "\x1b[97m";
170
171 println!();
172 println!(" {bold}{white}lean-ctx ghost report{rst}");
173 println!(" {dim}{}{rst}", "=".repeat(40));
174 println!();
175
176 if total_ghost == 0 {
177 println!(" {green}No ghost tokens detected!{rst}");
178 println!(
179 " {dim}All {} commands optimized.{rst}",
180 discover.total_commands
181 );
182 println!();
183 return;
184 }
185
186 let severity = if total_ghost > 10000 {
187 red
188 } else if total_ghost > 3000 {
189 yellow
190 } else {
191 green
192 };
193
194 println!(
195 " {bold}Ghost Tokens found:{rst} {severity}{total_ghost:>8}{rst} tokens {dim}(~${total_usd:.2}){rst}"
196 );
197 println!();
198
199 if unoptimized_tokens > 0 {
200 let missed_count: u32 = discover.missed_commands.iter().map(|m| m.count).sum();
201 println!(
202 " {dim} Unoptimized shells:{rst} {white}{unoptimized_tokens:>8}{rst} {dim}({missed_count} cmds without lean-ctx){rst}"
203 );
204 }
205 if redundant_tokens > 0 {
206 println!(
207 " {dim} Redundant reads:{rst} {white}{redundant_tokens:>8}{rst} {dim}({redundant_reads} cache hits = wasted re-reads){rst}"
208 );
209 }
210 if truncated_tokens > 0 {
211 println!(
212 " {dim} Oversized contexts:{rst} {white}{truncated_tokens:>8}{rst} {dim}(uncompressed portion of tool results){rst}"
213 );
214 }
215
216 println!();
217 println!(" {bold}Monthly savings potential:{rst} {green}${monthly_usd:.2}{rst}");
218
219 if !discover.missed_commands.is_empty() {
220 println!();
221 println!(" {bold}Top unoptimized commands:{rst}");
222 for m in discover.missed_commands.iter().take(5) {
223 println!(
224 " {dim}{:>4}x{rst} {white}{:<12}{rst} {dim}{}{rst}",
225 m.count, m.prefix, m.description
226 );
227 }
228 }
229
230 println!();
231 if discover.already_optimized == 0 {
232 println!(
233 " {yellow}Run '{bold}lean-ctx setup{rst}{yellow}' to eliminate ghost tokens.{rst}"
234 );
235 } else {
236 println!(
237 " {dim}Already optimized: {}/{} commands{rst}",
238 discover.already_optimized, discover.total_commands
239 );
240 }
241 println!();
242}