1use crate::{
2 core, dashboard, doctor, heatmap, hook_handlers, mcp_stdio, report, setup, shell, status,
3 token_report, tools, tui, uninstall,
4};
5use anyhow::Result;
6
7pub fn run() {
8 let args: Vec<String> = std::env::args().collect();
9
10 if args.len() > 1 {
11 let rest = args[2..].to_vec();
12
13 match args[1].as_str() {
14 "-c" | "exec" => {
15 let raw = rest.first().map(|a| a == "--raw").unwrap_or(false);
16 let cmd_args = if raw { &args[3..] } else { &args[2..] };
17 let command = if cmd_args.len() == 1 {
18 cmd_args[0].clone()
19 } else {
20 shell::join_command(cmd_args)
21 };
22 if std::env::var("LEAN_CTX_ACTIVE").is_ok()
23 || std::env::var("LEAN_CTX_DISABLED").is_ok()
24 {
25 passthrough(&command);
26 }
27 if raw {
28 std::env::set_var("LEAN_CTX_RAW", "1");
29 } else {
30 std::env::set_var("LEAN_CTX_COMPRESS", "1");
31 }
32 let code = shell::exec(&command);
33 core::stats::flush();
34 std::process::exit(code);
35 }
36 "-t" | "--track" => {
37 let cmd_args = &args[2..];
38 let command = if cmd_args.len() == 1 {
39 cmd_args[0].clone()
40 } else {
41 shell::join_command(cmd_args)
42 };
43 if std::env::var("LEAN_CTX_ACTIVE").is_ok()
44 || std::env::var("LEAN_CTX_DISABLED").is_ok()
45 {
46 passthrough(&command);
47 }
48 let code = shell::exec(&command);
49 core::stats::flush();
50 std::process::exit(code);
51 }
52 "shell" | "--shell" => {
53 shell::interactive();
54 return;
55 }
56 "gain" => {
57 if rest.iter().any(|a| a == "--reset") {
58 core::stats::reset_all();
59 println!("Stats reset. All token savings data cleared.");
60 return;
61 }
62 if rest.iter().any(|a| a == "--live" || a == "--watch") {
63 core::stats::gain_live();
64 return;
65 }
66 let model = rest.iter().enumerate().find_map(|(i, a)| {
67 if let Some(v) = a.strip_prefix("--model=") {
68 return Some(v.to_string());
69 }
70 if a == "--model" {
71 return rest.get(i + 1).cloned();
72 }
73 None
74 });
75 let period = rest
76 .iter()
77 .enumerate()
78 .find_map(|(i, a)| {
79 if let Some(v) = a.strip_prefix("--period=") {
80 return Some(v.to_string());
81 }
82 if a == "--period" {
83 return rest.get(i + 1).cloned();
84 }
85 None
86 })
87 .unwrap_or_else(|| "all".to_string());
88 let limit = rest
89 .iter()
90 .enumerate()
91 .find_map(|(i, a)| {
92 if let Some(v) = a.strip_prefix("--limit=") {
93 return v.parse::<usize>().ok();
94 }
95 if a == "--limit" {
96 return rest.get(i + 1).and_then(|v| v.parse::<usize>().ok());
97 }
98 None
99 })
100 .unwrap_or(10);
101
102 if rest.iter().any(|a| a == "--graph") {
103 println!("{}", core::stats::format_gain_graph());
104 } else if rest.iter().any(|a| a == "--daily") {
105 println!("{}", core::stats::format_gain_daily());
106 } else if rest.iter().any(|a| a == "--json") {
107 println!(
108 "{}",
109 tools::ctx_gain::handle(
110 "json",
111 Some(&period),
112 model.as_deref(),
113 Some(limit)
114 )
115 );
116 } else if rest.iter().any(|a| a == "--score") {
117 println!(
118 "{}",
119 tools::ctx_gain::handle("score", None, model.as_deref(), Some(limit))
120 );
121 } else if rest.iter().any(|a| a == "--cost") {
122 println!(
123 "{}",
124 tools::ctx_gain::handle("cost", None, model.as_deref(), Some(limit))
125 );
126 } else if rest.iter().any(|a| a == "--tasks") {
127 println!(
128 "{}",
129 tools::ctx_gain::handle("tasks", None, None, Some(limit))
130 );
131 } else if rest.iter().any(|a| a == "--agents") {
132 println!(
133 "{}",
134 tools::ctx_gain::handle("agents", None, None, Some(limit))
135 );
136 } else if rest.iter().any(|a| a == "--heatmap") {
137 println!(
138 "{}",
139 tools::ctx_gain::handle("heatmap", None, None, Some(limit))
140 );
141 } else if rest.iter().any(|a| a == "--wrapped") {
142 println!(
143 "{}",
144 tools::ctx_gain::handle(
145 "wrapped",
146 Some(&period),
147 model.as_deref(),
148 Some(limit)
149 )
150 );
151 } else if rest.iter().any(|a| a == "--pipeline") {
152 let stats_path = dirs::home_dir()
153 .unwrap_or_default()
154 .join(".lean-ctx")
155 .join("pipeline_stats.json");
156 if let Ok(data) = std::fs::read_to_string(&stats_path) {
157 if let Ok(stats) =
158 serde_json::from_str::<core::pipeline::PipelineStats>(&data)
159 {
160 println!("{}", stats.format_summary());
161 } else {
162 println!("No pipeline stats available yet (corrupt data).");
163 }
164 } else {
165 println!(
166 "No pipeline stats available yet. Use MCP tools to generate data."
167 );
168 }
169 } else if rest.iter().any(|a| a == "--deep") {
170 println!(
171 "{}\n{}\n{}\n{}\n{}",
172 tools::ctx_gain::handle("report", None, model.as_deref(), Some(limit)),
173 tools::ctx_gain::handle("tasks", None, None, Some(limit)),
174 tools::ctx_gain::handle("cost", None, model.as_deref(), Some(limit)),
175 tools::ctx_gain::handle("agents", None, None, Some(limit)),
176 tools::ctx_gain::handle("heatmap", None, None, Some(limit))
177 );
178 } else {
179 println!("{}", core::stats::format_gain());
180 }
181 return;
182 }
183 "token-report" | "report-tokens" => {
184 let code = token_report::run_cli(&rest);
185 if code != 0 {
186 std::process::exit(code);
187 }
188 return;
189 }
190 "cep" => {
191 println!("{}", tools::ctx_gain::handle("score", None, None, Some(10)));
192 return;
193 }
194 "dashboard" => {
195 let port = rest
196 .iter()
197 .find_map(|p| p.strip_prefix("--port=").or_else(|| p.strip_prefix("-p=")))
198 .and_then(|p| p.parse().ok());
199 let host = rest
200 .iter()
201 .find_map(|p| p.strip_prefix("--host=").or_else(|| p.strip_prefix("-H=")))
202 .map(String::from);
203 let project = rest
204 .iter()
205 .find_map(|p| p.strip_prefix("--project="))
206 .map(String::from);
207 if let Some(ref p) = project {
208 std::env::set_var("LEAN_CTX_DASHBOARD_PROJECT", p);
209 }
210 run_async(dashboard::start(port, host));
211 return;
212 }
213 "serve" => {
214 #[cfg(feature = "http-server")]
215 {
216 let mut cfg = crate::http_server::HttpServerConfig::default();
217 let mut i = 0;
218 while i < rest.len() {
219 match rest[i].as_str() {
220 "--host" | "-H" => {
221 i += 1;
222 if i < rest.len() {
223 cfg.host = rest[i].clone();
224 }
225 }
226 arg if arg.starts_with("--host=") => {
227 cfg.host = arg["--host=".len()..].to_string();
228 }
229 "--port" | "-p" => {
230 i += 1;
231 if i < rest.len() {
232 if let Ok(p) = rest[i].parse::<u16>() {
233 cfg.port = p;
234 }
235 }
236 }
237 arg if arg.starts_with("--port=") => {
238 if let Ok(p) = arg["--port=".len()..].parse::<u16>() {
239 cfg.port = p;
240 }
241 }
242 "--project-root" => {
243 i += 1;
244 if i < rest.len() {
245 cfg.project_root = std::path::PathBuf::from(&rest[i]);
246 }
247 }
248 arg if arg.starts_with("--project-root=") => {
249 cfg.project_root =
250 std::path::PathBuf::from(&arg["--project-root=".len()..]);
251 }
252 "--auth-token" => {
253 i += 1;
254 if i < rest.len() {
255 cfg.auth_token = Some(rest[i].clone());
256 }
257 }
258 arg if arg.starts_with("--auth-token=") => {
259 cfg.auth_token = Some(arg["--auth-token=".len()..].to_string());
260 }
261 "--stateful" => cfg.stateful_mode = true,
262 "--stateless" => cfg.stateful_mode = false,
263 "--json" => cfg.json_response = true,
264 "--sse" => cfg.json_response = false,
265 "--disable-host-check" => cfg.disable_host_check = true,
266 "--allowed-host" => {
267 i += 1;
268 if i < rest.len() {
269 cfg.allowed_hosts.push(rest[i].clone());
270 }
271 }
272 arg if arg.starts_with("--allowed-host=") => {
273 cfg.allowed_hosts
274 .push(arg["--allowed-host=".len()..].to_string());
275 }
276 "--max-body-bytes" => {
277 i += 1;
278 if i < rest.len() {
279 if let Ok(n) = rest[i].parse::<usize>() {
280 cfg.max_body_bytes = n;
281 }
282 }
283 }
284 arg if arg.starts_with("--max-body-bytes=") => {
285 if let Ok(n) = arg["--max-body-bytes=".len()..].parse::<usize>() {
286 cfg.max_body_bytes = n;
287 }
288 }
289 "--max-concurrency" => {
290 i += 1;
291 if i < rest.len() {
292 if let Ok(n) = rest[i].parse::<usize>() {
293 cfg.max_concurrency = n;
294 }
295 }
296 }
297 arg if arg.starts_with("--max-concurrency=") => {
298 if let Ok(n) = arg["--max-concurrency=".len()..].parse::<usize>() {
299 cfg.max_concurrency = n;
300 }
301 }
302 "--max-rps" => {
303 i += 1;
304 if i < rest.len() {
305 if let Ok(n) = rest[i].parse::<u32>() {
306 cfg.max_rps = n;
307 }
308 }
309 }
310 arg if arg.starts_with("--max-rps=") => {
311 if let Ok(n) = arg["--max-rps=".len()..].parse::<u32>() {
312 cfg.max_rps = n;
313 }
314 }
315 "--rate-burst" => {
316 i += 1;
317 if i < rest.len() {
318 if let Ok(n) = rest[i].parse::<u32>() {
319 cfg.rate_burst = n;
320 }
321 }
322 }
323 arg if arg.starts_with("--rate-burst=") => {
324 if let Ok(n) = arg["--rate-burst=".len()..].parse::<u32>() {
325 cfg.rate_burst = n;
326 }
327 }
328 "--request-timeout-ms" => {
329 i += 1;
330 if i < rest.len() {
331 if let Ok(n) = rest[i].parse::<u64>() {
332 cfg.request_timeout_ms = n;
333 }
334 }
335 }
336 arg if arg.starts_with("--request-timeout-ms=") => {
337 if let Ok(n) = arg["--request-timeout-ms=".len()..].parse::<u64>() {
338 cfg.request_timeout_ms = n;
339 }
340 }
341 "--help" | "-h" => {
342 eprintln!(
343 "Usage: lean-ctx serve [--host H] [--port N] [--project-root DIR]\\n\\
344 \\n\\
345 Options:\\n\\
346 --host, -H Bind host (default: 127.0.0.1)\\n\\
347 --port, -p Bind port (default: 8080)\\n\\
348 --project-root Resolve relative paths against this root (default: cwd)\\n\\
349 --auth-token Require Authorization: Bearer <token> (required for non-loopback binds)\\n\\
350 --stateful/--stateless Streamable HTTP session mode (default: stateless)\\n\\
351 --json/--sse Response framing in stateless mode (default: json)\\n\\
352 --max-body-bytes Max request body size in bytes (default: 2097152)\\n\\
353 --max-concurrency Max concurrent requests (default: 32)\\n\\
354 --max-rps Max requests/sec (global, default: 50)\\n\\
355 --rate-burst Rate limiter burst (global, default: 100)\\n\\
356 --request-timeout-ms REST tool-call timeout (default: 30000)\\n\\
357 --allowed-host Add allowed Host header (repeatable)\\n\\
358 --disable-host-check Disable Host header validation (unsafe)"
359 );
360 return;
361 }
362 _ => {}
363 }
364 i += 1;
365 }
366
367 if cfg.auth_token.is_none() {
368 if let Ok(v) = std::env::var("LEAN_CTX_HTTP_TOKEN") {
369 if !v.trim().is_empty() {
370 cfg.auth_token = Some(v);
371 }
372 }
373 }
374
375 if let Err(e) = run_async(crate::http_server::serve(cfg)) {
376 eprintln!("HTTP server error: {e}");
377 std::process::exit(1);
378 }
379 return;
380 }
381 #[cfg(not(feature = "http-server"))]
382 {
383 eprintln!("lean-ctx serve is not available in this build");
384 std::process::exit(1);
385 }
386 }
387 "watch" => {
388 if let Err(e) = tui::run() {
389 eprintln!("TUI error: {e}");
390 std::process::exit(1);
391 }
392 return;
393 }
394 "proxy" => {
395 #[cfg(feature = "http-server")]
396 {
397 let sub = rest.first().map(|s| s.as_str()).unwrap_or("help");
398 match sub {
399 "start" => {
400 let port: u16 = rest
401 .iter()
402 .find_map(|p| {
403 p.strip_prefix("--port=").or_else(|| p.strip_prefix("-p="))
404 })
405 .and_then(|p| p.parse().ok())
406 .unwrap_or(4444);
407 let autostart = rest.iter().any(|a| a == "--autostart");
408 if autostart {
409 crate::proxy_autostart::install(port, false);
410 return;
411 }
412 if let Err(e) = run_async(crate::proxy::start_proxy(port)) {
413 eprintln!("Proxy error: {e}");
414 std::process::exit(1);
415 }
416 }
417 "stop" => {
418 match ureq::get(&format!(
419 "http://127.0.0.1:{}/health",
420 rest.iter()
421 .find_map(|p| p.strip_prefix("--port="))
422 .and_then(|p| p.parse::<u16>().ok())
423 .unwrap_or(4444)
424 ))
425 .call()
426 {
427 Ok(_) => {
428 println!("Proxy is running. Use Ctrl+C or kill the process.");
429 }
430 Err(_) => {
431 println!("No proxy running on that port.");
432 }
433 }
434 }
435 "status" => {
436 let port: u16 = rest
437 .iter()
438 .find_map(|p| p.strip_prefix("--port="))
439 .and_then(|p| p.parse().ok())
440 .unwrap_or(4444);
441 match ureq::get(&format!("http://127.0.0.1:{port}/status")).call() {
442 Ok(resp) => {
443 let body =
444 resp.into_body().read_to_string().unwrap_or_default();
445 if let Ok(v) = serde_json::from_str::<serde_json::Value>(&body)
446 {
447 println!("lean-ctx proxy status:");
448 println!(" Requests: {}", v["requests_total"]);
449 println!(" Compressed: {}", v["requests_compressed"]);
450 println!(" Tokens saved: {}", v["tokens_saved"]);
451 println!(
452 " Compression: {}%",
453 v["compression_ratio_pct"].as_str().unwrap_or("0.0")
454 );
455 } else {
456 println!("{body}");
457 }
458 }
459 Err(_) => {
460 println!("No proxy running on port {port}.");
461 println!("Start with: lean-ctx proxy start");
462 }
463 }
464 }
465 _ => {
466 println!("Usage: lean-ctx proxy <start|stop|status> [--port=4444]");
467 }
468 }
469 return;
470 }
471 #[cfg(not(feature = "http-server"))]
472 {
473 eprintln!("lean-ctx proxy is not available in this build");
474 std::process::exit(1);
475 }
476 }
477 "init" => {
478 super::cmd_init(&rest);
479 return;
480 }
481 "setup" => {
482 let non_interactive = rest.iter().any(|a| a == "--non-interactive");
483 let yes = rest.iter().any(|a| a == "--yes" || a == "-y");
484 let fix = rest.iter().any(|a| a == "--fix");
485 let json = rest.iter().any(|a| a == "--json");
486
487 if non_interactive || fix || json || yes {
488 let opts = setup::SetupOptions {
489 non_interactive,
490 yes,
491 fix,
492 json,
493 };
494 match setup::run_setup_with_options(opts) {
495 Ok(report) => {
496 if json {
497 println!(
498 "{}",
499 serde_json::to_string_pretty(&report)
500 .unwrap_or_else(|_| "{}".to_string())
501 );
502 }
503 if !report.success {
504 std::process::exit(1);
505 }
506 }
507 Err(e) => {
508 eprintln!("{e}");
509 std::process::exit(1);
510 }
511 }
512 } else {
513 setup::run_setup();
514 }
515 return;
516 }
517 "bootstrap" => {
518 let json = rest.iter().any(|a| a == "--json");
519 let opts = setup::SetupOptions {
520 non_interactive: true,
521 yes: true,
522 fix: true,
523 json,
524 };
525 match setup::run_setup_with_options(opts) {
526 Ok(report) => {
527 if json {
528 println!(
529 "{}",
530 serde_json::to_string_pretty(&report)
531 .unwrap_or_else(|_| "{}".to_string())
532 );
533 }
534 if !report.success {
535 std::process::exit(1);
536 }
537 }
538 Err(e) => {
539 eprintln!("{e}");
540 std::process::exit(1);
541 }
542 }
543 return;
544 }
545 "status" => {
546 let code = status::run_cli(&rest);
547 if code != 0 {
548 std::process::exit(code);
549 }
550 return;
551 }
552 "read" => {
553 super::cmd_read(&rest);
554 return;
555 }
556 "diff" => {
557 super::cmd_diff(&rest);
558 return;
559 }
560 "grep" => {
561 super::cmd_grep(&rest);
562 return;
563 }
564 "find" => {
565 super::cmd_find(&rest);
566 return;
567 }
568 "ls" => {
569 super::cmd_ls(&rest);
570 return;
571 }
572 "deps" => {
573 super::cmd_deps(&rest);
574 return;
575 }
576 "discover" => {
577 super::cmd_discover(&rest);
578 return;
579 }
580 "filter" => {
581 super::cmd_filter(&rest);
582 return;
583 }
584 "heatmap" => {
585 heatmap::cmd_heatmap(&rest);
586 return;
587 }
588 "graph" => {
589 let mut action = "build";
590 let mut path_arg: Option<&str> = None;
591 for arg in &rest {
592 if arg == "build" {
593 action = "build";
594 } else {
595 path_arg = Some(arg.as_str());
596 }
597 }
598 let root = path_arg
599 .map(String::from)
600 .or_else(|| {
601 std::env::current_dir()
602 .ok()
603 .map(|p| p.to_string_lossy().to_string())
604 })
605 .unwrap_or_else(|| ".".to_string());
606 match action {
607 "build" => {
608 let index = core::graph_index::load_or_build(&root);
609 println!(
610 "Graph built: {} files, {} edges",
611 index.files.len(),
612 index.edges.len()
613 );
614 }
615 _ => {
616 eprintln!("Usage: lean-ctx graph [build] [path]");
617 }
618 }
619 return;
620 }
621 "session" => {
622 super::cmd_session();
623 return;
624 }
625 "wrapped" => {
626 super::cmd_wrapped(&rest);
627 return;
628 }
629 "sessions" => {
630 super::cmd_sessions(&rest);
631 return;
632 }
633 "benchmark" => {
634 super::cmd_benchmark(&rest);
635 return;
636 }
637 "config" => {
638 super::cmd_config(&rest);
639 return;
640 }
641 "stats" => {
642 super::cmd_stats(&rest);
643 return;
644 }
645 "cache" => {
646 super::cmd_cache(&rest);
647 return;
648 }
649 "theme" => {
650 super::cmd_theme(&rest);
651 return;
652 }
653 "tee" => {
654 super::cmd_tee(&rest);
655 return;
656 }
657 "slow-log" => {
658 super::cmd_slow_log(&rest);
659 return;
660 }
661 "update" | "--self-update" => {
662 core::updater::run(&rest);
663 return;
664 }
665 "doctor" => {
666 let code = doctor::run_cli(&rest);
667 if code != 0 {
668 std::process::exit(code);
669 }
670 return;
671 }
672 "gotchas" | "bugs" => {
673 super::cloud::cmd_gotchas(&rest);
674 return;
675 }
676 "buddy" | "pet" => {
677 super::cloud::cmd_buddy(&rest);
678 return;
679 }
680 "hook" => {
681 let action = rest.first().map(|s| s.as_str()).unwrap_or("help");
682 match action {
683 "rewrite" => hook_handlers::handle_rewrite(),
684 "redirect" => hook_handlers::handle_redirect(),
685 "copilot" => hook_handlers::handle_copilot(),
686 "rewrite-inline" => hook_handlers::handle_rewrite_inline(),
687 _ => {
688 eprintln!("Usage: lean-ctx hook <rewrite|redirect|copilot|rewrite-inline>");
689 eprintln!(" Internal commands used by agent hooks (Claude, Cursor, Copilot, etc.)");
690 std::process::exit(1);
691 }
692 }
693 return;
694 }
695 "report-issue" | "report" => {
696 report::run(&rest);
697 return;
698 }
699 "uninstall" => {
700 uninstall::run();
701 return;
702 }
703 "cheat" | "cheatsheet" | "cheat-sheet" => {
704 super::cmd_cheatsheet();
705 return;
706 }
707 "login" => {
708 super::cloud::cmd_login(&rest);
709 return;
710 }
711 "sync" => {
712 super::cloud::cmd_sync();
713 return;
714 }
715 "contribute" => {
716 super::cloud::cmd_contribute();
717 return;
718 }
719 "cloud" => {
720 super::cloud::cmd_cloud(&rest);
721 return;
722 }
723 "upgrade" => {
724 super::cloud::cmd_upgrade();
725 return;
726 }
727 "--version" | "-V" => {
728 println!("{}", core::integrity::origin_line());
729 return;
730 }
731 "--help" | "-h" => {
732 print_help();
733 return;
734 }
735 "mcp" => {}
736 _ => {
737 eprintln!("lean-ctx: unknown command '{}'\n", args[1]);
738 print_help();
739 std::process::exit(1);
740 }
741 }
742 }
743
744 if let Err(e) = run_mcp_server() {
745 eprintln!("lean-ctx: {e}");
746 std::process::exit(1);
747 }
748}
749
750fn passthrough(command: &str) -> ! {
751 let (shell, flag) = shell::shell_and_flag();
752 let status = std::process::Command::new(&shell)
753 .arg(&flag)
754 .arg(command)
755 .env("LEAN_CTX_ACTIVE", "1")
756 .status()
757 .map(|s| s.code().unwrap_or(1))
758 .unwrap_or(127);
759 std::process::exit(status);
760}
761
762fn run_async<F: std::future::Future>(future: F) -> F::Output {
763 tokio::runtime::Runtime::new()
764 .expect("failed to create async runtime")
765 .block_on(future)
766}
767
768fn run_mcp_server() -> Result<()> {
769 use rmcp::ServiceExt;
770 use tracing_subscriber::EnvFilter;
771
772 std::env::set_var("LEAN_CTX_MCP_SERVER", "1");
773
774 let rt = tokio::runtime::Runtime::new()?;
775 rt.block_on(async {
776 tracing_subscriber::fmt()
777 .with_env_filter(EnvFilter::from_default_env())
778 .with_writer(std::io::stderr)
779 .init();
780
781 tracing::info!(
782 "lean-ctx v{} MCP server starting",
783 env!("CARGO_PKG_VERSION")
784 );
785
786 let server = tools::create_server();
787 let transport =
788 mcp_stdio::HybridStdioTransport::new_server(tokio::io::stdin(), tokio::io::stdout());
789 let service = server.serve(transport).await?;
790 service.waiting().await?;
791
792 core::stats::flush();
793 core::mode_predictor::ModePredictor::flush();
794 core::feedback::FeedbackStore::flush();
795
796 Ok(())
797 })
798}
799
800fn print_help() {
801 println!(
802 "lean-ctx {version} — Context Runtime for AI Agents
803
80490+ compression patterns | 46 MCP tools | Context Continuity Protocol
805
806USAGE:
807 lean-ctx Start MCP server (stdio)
808 lean-ctx serve Start MCP server (Streamable HTTP)
809 lean-ctx -t \"command\" Track command (full output + stats, no compression)
810 lean-ctx -c \"command\" Execute with compressed output (used by AI hooks)
811 lean-ctx -c --raw \"command\" Execute without compression (full output)
812 lean-ctx exec \"command\" Same as -c
813 lean-ctx shell Interactive shell with compression
814
815COMMANDS:
816 gain Visual dashboard (colors, bars, sparklines, USD)
817 gain --live Live mode: auto-refreshes every 1s in-place
818 gain --graph 30-day savings chart
819 gain --daily Bordered day-by-day table with USD
820 gain --json Raw JSON export of all stats
821 token-report [--json] Token + memory report (project + session + CEP)
822 cep CEP impact report (score trends, cache, modes)
823 watch Live TUI dashboard (real-time event stream)
824 dashboard [--port=N] [--host=H] Open web dashboard (default: http://localhost:3333)
825 serve [--host H] [--port N] MCP over HTTP (Streamable HTTP, local-first)
826 proxy start [--port=4444] API proxy: compress tool_results before LLM API
827 proxy status Show proxy statistics
828 cache [list|clear|stats] Show/manage file read cache
829 wrapped [--week|--month|--all] Savings report card (shareable)
830 sessions [list|show|cleanup] Manage CCP sessions (~/.lean-ctx/sessions/)
831 benchmark run [path] [--json] Run real benchmark on project files
832 benchmark report [path] Generate shareable Markdown report
833 cheatsheet Command cheat sheet & workflow quick reference
834 setup One-command setup: shell + editor + verify
835 bootstrap Non-interactive setup + fix (zero-config)
836 status [--json] Show setup + MCP + rules status
837 init [--global] Install shell aliases (zsh/bash/fish/PowerShell)
838 init --agent <name> Configure MCP for specific editor/agent
839 read <file> [-m mode] Read file with compression
840 diff <file1> <file2> Compressed file diff
841 grep <pattern> [path] Search with compressed output
842 find <pattern> [path] Find files with compressed output
843 ls [path] Directory listing with compression
844 deps [path] Show project dependencies
845 discover Find uncompressed commands in shell history
846 filter [list|validate|init] Manage custom compression filters (~/.lean-ctx/filters/)
847 session Show adoption statistics
848 config Show/edit configuration (~/.lean-ctx/config.toml)
849 theme [list|set|export|import] Customize terminal colors and themes
850 tee [list|clear|show <file>|last] Manage output tee files (~/.lean-ctx/tee/)
851 slow-log [list|clear] Show/clear slow command log (~/.lean-ctx/slow-commands.log)
852 update [--check] Self-update lean-ctx binary from GitHub Releases
853 gotchas [list|clear|export|stats] Bug Memory: view/manage auto-detected error patterns
854 buddy [show|stats|ascii|json] Token Guardian: your data-driven coding companion
855 doctor [--fix] [--json] Run diagnostics (and optionally repair)
856 uninstall Remove shell hook, MCP configs, and data directory
857
858SHELL HOOK PATTERNS (90+):
859 git status, log, diff, add, commit, push, pull, fetch, clone,
860 branch, checkout, switch, merge, stash, tag, reset, remote
861 docker build, ps, images, logs, compose, exec, network
862 npm/pnpm install, test, run, list, outdated, audit
863 cargo build, test, check, clippy
864 gh pr list/view/create, issue list/view, run list/view
865 kubectl get pods/services/deployments, logs, describe, apply
866 python pip install/list/outdated, ruff check/format, poetry, uv
867 linters eslint, biome, prettier, golangci-lint
868 builds tsc, next build, vite build
869 ruby rubocop, bundle install/update, rake test, rails test
870 tests jest, vitest, pytest, go test, playwright, rspec, minitest
871 iac terraform, make, maven, gradle, dotnet, flutter, dart
872 utils curl, grep/rg, find, ls, wget, env
873 data JSON schema extraction, log deduplication
874
875READ MODES:
876 auto Auto-select optimal mode (default)
877 full Full content (cached re-reads = 13 tokens)
878 map Dependency graph + API signatures
879 signatures tree-sitter AST extraction (18 languages)
880 task Task-relevant filtering (requires ctx_session task)
881 reference One-line reference stub (cheap cache key)
882 aggressive Syntax-stripped content
883 entropy Shannon entropy filtered
884 diff Changed lines only
885 lines:N-M Specific line ranges (e.g. lines:10-50,80)
886
887ENVIRONMENT:
888 LEAN_CTX_DISABLED=1 Bypass ALL compression + prevent shell hook from loading
889 LEAN_CTX_ENABLED=0 Prevent shell hook auto-start (lean-ctx-on still works)
890 LEAN_CTX_RAW=1 Same as --raw for current command
891 LEAN_CTX_AUTONOMY=false Disable autonomous features
892 LEAN_CTX_COMPRESS=1 Force compression (even for excluded commands)
893
894OPTIONS:
895 --version, -V Show version
896 --help, -h Show this help
897
898EXAMPLES:
899 lean-ctx -c \"git status\" Compressed git output
900 lean-ctx -c \"kubectl get pods\" Compressed k8s output
901 lean-ctx -c \"gh pr list\" Compressed GitHub CLI output
902 lean-ctx gain Visual terminal dashboard
903 lean-ctx gain --live Live auto-updating terminal dashboard
904 lean-ctx gain --graph 30-day savings chart
905 lean-ctx gain --daily Day-by-day breakdown with USD
906 lean-ctx token-report --json Machine-readable token + memory report
907 lean-ctx dashboard Open web dashboard at localhost:3333
908 lean-ctx dashboard --host=0.0.0.0 Bind to all interfaces (remote access)
909 lean-ctx wrapped Weekly savings report card
910 lean-ctx wrapped --month Monthly savings report card
911 lean-ctx sessions list List all CCP sessions
912 lean-ctx sessions show Show latest session state
913 lean-ctx discover Find missed savings in shell history
914 lean-ctx setup One-command setup (shell + editors + verify)
915 lean-ctx bootstrap Non-interactive setup + fix (zero-config)
916 lean-ctx bootstrap --json Machine-readable bootstrap report
917 lean-ctx init --global Install shell aliases (includes lean-ctx-on/off/mode/status)
918 lean-ctx-on Enable shell aliases in track mode (full output + stats)
919 lean-ctx-off Disable all shell aliases
920 lean-ctx-mode track Track mode: full output, stats recorded (default)
921 lean-ctx-mode compress Compress mode: all output compressed (power users)
922 lean-ctx-mode off Same as lean-ctx-off
923 lean-ctx-status Show whether compression is active
924 lean-ctx init --agent pi Install Pi Coding Agent extension
925 lean-ctx doctor Check PATH, config, MCP, and dashboard port
926 lean-ctx doctor --fix --json Repair + machine-readable report
927 lean-ctx status --json Machine-readable current status
928 lean-ctx read src/main.rs -m map
929 lean-ctx grep \"pub fn\" src/
930 lean-ctx deps .
931
932CLOUD:
933 cloud status Show cloud connection status
934 login <email> Register/login to LeanCTX Cloud
935 sync Upload local stats to cloud dashboard
936 contribute Share anonymized compression data
937
938TROUBLESHOOTING:
939 Commands broken? lean-ctx-off (fixes current session)
940 Permanent fix? lean-ctx uninstall (removes all hooks)
941 Manual fix? Edit ~/.zshrc, remove the \"lean-ctx shell hook\" block
942 Binary missing? Aliases auto-fallback to original commands (safe)
943 Preview init? lean-ctx init --global --dry-run
944
945WEBSITE: https://leanctx.com
946GITHUB: https://github.com/yvgude/lean-ctx
947",
948 version = env!("CARGO_PKG_VERSION"),
949 );
950}