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 "ghost" => {
581 super::cmd_ghost(&rest);
582 return;
583 }
584 "filter" => {
585 super::cmd_filter(&rest);
586 return;
587 }
588 "heatmap" => {
589 heatmap::cmd_heatmap(&rest);
590 return;
591 }
592 "graph" => {
593 let mut action = "build";
594 let mut path_arg: Option<&str> = None;
595 for arg in &rest {
596 if arg == "build" {
597 action = "build";
598 } else {
599 path_arg = Some(arg.as_str());
600 }
601 }
602 let root = path_arg
603 .map(String::from)
604 .or_else(|| {
605 std::env::current_dir()
606 .ok()
607 .map(|p| p.to_string_lossy().to_string())
608 })
609 .unwrap_or_else(|| ".".to_string());
610 match action {
611 "build" => {
612 let index = core::graph_index::load_or_build(&root);
613 println!(
614 "Graph built: {} files, {} edges",
615 index.files.len(),
616 index.edges.len()
617 );
618 }
619 _ => {
620 eprintln!("Usage: lean-ctx graph [build] [path]");
621 }
622 }
623 return;
624 }
625 "session" => {
626 super::cmd_session();
627 return;
628 }
629 "wrapped" => {
630 super::cmd_wrapped(&rest);
631 return;
632 }
633 "sessions" => {
634 super::cmd_sessions(&rest);
635 return;
636 }
637 "benchmark" => {
638 super::cmd_benchmark(&rest);
639 return;
640 }
641 "config" => {
642 super::cmd_config(&rest);
643 return;
644 }
645 "stats" => {
646 super::cmd_stats(&rest);
647 return;
648 }
649 "cache" => {
650 super::cmd_cache(&rest);
651 return;
652 }
653 "theme" => {
654 super::cmd_theme(&rest);
655 return;
656 }
657 "tee" => {
658 super::cmd_tee(&rest);
659 return;
660 }
661 "terse" => {
662 super::cmd_terse(&rest);
663 return;
664 }
665 "slow-log" => {
666 super::cmd_slow_log(&rest);
667 return;
668 }
669 "update" | "--self-update" => {
670 core::updater::run(&rest);
671 return;
672 }
673 "doctor" => {
674 let code = doctor::run_cli(&rest);
675 if code != 0 {
676 std::process::exit(code);
677 }
678 return;
679 }
680 "gotchas" | "bugs" => {
681 super::cloud::cmd_gotchas(&rest);
682 return;
683 }
684 "buddy" | "pet" => {
685 super::cloud::cmd_buddy(&rest);
686 return;
687 }
688 "hook" => {
689 let action = rest.first().map(|s| s.as_str()).unwrap_or("help");
690 match action {
691 "rewrite" => hook_handlers::handle_rewrite(),
692 "redirect" => hook_handlers::handle_redirect(),
693 "copilot" => hook_handlers::handle_copilot(),
694 "codex-pretooluse" => hook_handlers::handle_codex_pretooluse(),
695 "codex-session-start" => hook_handlers::handle_codex_session_start(),
696 "rewrite-inline" => hook_handlers::handle_rewrite_inline(),
697 _ => {
698 eprintln!("Usage: lean-ctx hook <rewrite|redirect|copilot|codex-pretooluse|codex-session-start|rewrite-inline>");
699 eprintln!(" Internal commands used by agent hooks (Claude, Cursor, Copilot, etc.)");
700 std::process::exit(1);
701 }
702 }
703 return;
704 }
705 "report-issue" | "report" => {
706 report::run(&rest);
707 return;
708 }
709 "uninstall" => {
710 uninstall::run();
711 return;
712 }
713 "cheat" | "cheatsheet" | "cheat-sheet" => {
714 super::cmd_cheatsheet();
715 return;
716 }
717 "login" => {
718 super::cloud::cmd_login(&rest);
719 return;
720 }
721 "register" => {
722 super::cloud::cmd_register(&rest);
723 return;
724 }
725 "forgot-password" => {
726 super::cloud::cmd_forgot_password(&rest);
727 return;
728 }
729 "sync" => {
730 super::cloud::cmd_sync();
731 return;
732 }
733 "contribute" => {
734 super::cloud::cmd_contribute();
735 return;
736 }
737 "cloud" => {
738 super::cloud::cmd_cloud(&rest);
739 return;
740 }
741 "upgrade" => {
742 super::cloud::cmd_upgrade();
743 return;
744 }
745 "--version" | "-V" => {
746 println!("{}", core::integrity::origin_line());
747 return;
748 }
749 "--help" | "-h" => {
750 print_help();
751 return;
752 }
753 "mcp" => {}
754 _ => {
755 eprintln!("lean-ctx: unknown command '{}'\n", args[1]);
756 print_help();
757 std::process::exit(1);
758 }
759 }
760 }
761
762 if let Err(e) = run_mcp_server() {
763 eprintln!("lean-ctx: {e}");
764 std::process::exit(1);
765 }
766}
767
768fn passthrough(command: &str) -> ! {
769 let (shell, flag) = shell::shell_and_flag();
770 let status = std::process::Command::new(&shell)
771 .arg(&flag)
772 .arg(command)
773 .env("LEAN_CTX_ACTIVE", "1")
774 .status()
775 .map(|s| s.code().unwrap_or(1))
776 .unwrap_or(127);
777 std::process::exit(status);
778}
779
780fn run_async<F: std::future::Future>(future: F) -> F::Output {
781 tokio::runtime::Runtime::new()
782 .expect("failed to create async runtime")
783 .block_on(future)
784}
785
786fn run_mcp_server() -> Result<()> {
787 use rmcp::ServiceExt;
788 use tracing_subscriber::EnvFilter;
789
790 std::env::set_var("LEAN_CTX_MCP_SERVER", "1");
791
792 let rt = tokio::runtime::Runtime::new()?;
793 rt.block_on(async {
794 tracing_subscriber::fmt()
795 .with_env_filter(EnvFilter::from_default_env())
796 .with_writer(std::io::stderr)
797 .init();
798
799 tracing::info!(
800 "lean-ctx v{} MCP server starting",
801 env!("CARGO_PKG_VERSION")
802 );
803
804 let server = tools::create_server();
805 let transport =
806 mcp_stdio::HybridStdioTransport::new_server(tokio::io::stdin(), tokio::io::stdout());
807 let service = server.serve(transport).await?;
808 service.waiting().await?;
809
810 core::stats::flush();
811 core::mode_predictor::ModePredictor::flush();
812 core::feedback::FeedbackStore::flush();
813
814 Ok(())
815 })
816}
817
818fn print_help() {
819 println!(
820 "lean-ctx {version} — Context Runtime for AI Agents
821
82290+ compression patterns | 46 MCP tools | Context Continuity Protocol
823
824USAGE:
825 lean-ctx Start MCP server (stdio)
826 lean-ctx serve Start MCP server (Streamable HTTP)
827 lean-ctx -t \"command\" Track command (full output + stats, no compression)
828 lean-ctx -c \"command\" Execute with compressed output (used by AI hooks)
829 lean-ctx -c --raw \"command\" Execute without compression (full output)
830 lean-ctx exec \"command\" Same as -c
831 lean-ctx shell Interactive shell with compression
832
833COMMANDS:
834 gain Visual dashboard (colors, bars, sparklines, USD)
835 gain --live Live mode: auto-refreshes every 1s in-place
836 gain --graph 30-day savings chart
837 gain --daily Bordered day-by-day table with USD
838 gain --json Raw JSON export of all stats
839 token-report [--json] Token + memory report (project + session + CEP)
840 cep CEP impact report (score trends, cache, modes)
841 watch Live TUI dashboard (real-time event stream)
842 dashboard [--port=N] [--host=H] Open web dashboard (default: http://localhost:3333)
843 serve [--host H] [--port N] MCP over HTTP (Streamable HTTP, local-first)
844 proxy start [--port=4444] API proxy: compress tool_results before LLM API
845 proxy status Show proxy statistics
846 cache [list|clear|stats] Show/manage file read cache
847 wrapped [--week|--month|--all] Savings report card (shareable)
848 sessions [list|show|cleanup] Manage CCP sessions (~/.lean-ctx/sessions/)
849 benchmark run [path] [--json] Run real benchmark on project files
850 benchmark report [path] Generate shareable Markdown report
851 cheatsheet Command cheat sheet & workflow quick reference
852 setup One-command setup: shell + editor + verify
853 bootstrap Non-interactive setup + fix (zero-config)
854 status [--json] Show setup + MCP + rules status
855 init [--global] Install shell aliases (zsh/bash/fish/PowerShell)
856 init --agent <name> Configure MCP for specific editor/agent
857 read <file> [-m mode] Read file with compression
858 diff <file1> <file2> Compressed file diff
859 grep <pattern> [path] Search with compressed output
860 find <pattern> [path] Find files with compressed output
861 ls [path] Directory listing with compression
862 deps [path] Show project dependencies
863 discover Find uncompressed commands in shell history
864 ghost [--json] Ghost Token report: find hidden token waste
865 filter [list|validate|init] Manage custom compression filters (~/.lean-ctx/filters/)
866 session Show adoption statistics
867 config Show/edit configuration (~/.lean-ctx/config.toml)
868 theme [list|set|export|import] Customize terminal colors and themes
869 tee [list|clear|show <file>|last] Manage output tee files (~/.lean-ctx/tee/)
870 terse [off|lite|full|ultra] Set agent output verbosity (saves 25-65% output tokens)
871 slow-log [list|clear] Show/clear slow command log (~/.lean-ctx/slow-commands.log)
872 update [--check] Self-update lean-ctx binary from GitHub Releases
873 gotchas [list|clear|export|stats] Bug Memory: view/manage auto-detected error patterns
874 buddy [show|stats|ascii|json] Token Guardian: your data-driven coding companion
875 doctor [--fix] [--json] Run diagnostics (and optionally repair)
876 uninstall Remove shell hook, MCP configs, and data directory
877
878SHELL HOOK PATTERNS (90+):
879 git status, log, diff, add, commit, push, pull, fetch, clone,
880 branch, checkout, switch, merge, stash, tag, reset, remote
881 docker build, ps, images, logs, compose, exec, network
882 npm/pnpm install, test, run, list, outdated, audit
883 cargo build, test, check, clippy
884 gh pr list/view/create, issue list/view, run list/view
885 kubectl get pods/services/deployments, logs, describe, apply
886 python pip install/list/outdated, ruff check/format, poetry, uv
887 linters eslint, biome, prettier, golangci-lint
888 builds tsc, next build, vite build
889 ruby rubocop, bundle install/update, rake test, rails test
890 tests jest, vitest, pytest, go test, playwright, rspec, minitest
891 iac terraform, make, maven, gradle, dotnet, flutter, dart
892 utils curl, grep/rg, find, ls, wget, env
893 data JSON schema extraction, log deduplication
894
895READ MODES:
896 auto Auto-select optimal mode (default)
897 full Full content (cached re-reads = 13 tokens)
898 map Dependency graph + API signatures
899 signatures tree-sitter AST extraction (18 languages)
900 task Task-relevant filtering (requires ctx_session task)
901 reference One-line reference stub (cheap cache key)
902 aggressive Syntax-stripped content
903 entropy Shannon entropy filtered
904 diff Changed lines only
905 lines:N-M Specific line ranges (e.g. lines:10-50,80)
906
907ENVIRONMENT:
908 LEAN_CTX_DISABLED=1 Bypass ALL compression + prevent shell hook from loading
909 LEAN_CTX_ENABLED=0 Prevent shell hook auto-start (lean-ctx-on still works)
910 LEAN_CTX_RAW=1 Same as --raw for current command
911 LEAN_CTX_AUTONOMY=false Disable autonomous features
912 LEAN_CTX_COMPRESS=1 Force compression (even for excluded commands)
913
914OPTIONS:
915 --version, -V Show version
916 --help, -h Show this help
917
918EXAMPLES:
919 lean-ctx -c \"git status\" Compressed git output
920 lean-ctx -c \"kubectl get pods\" Compressed k8s output
921 lean-ctx -c \"gh pr list\" Compressed GitHub CLI output
922 lean-ctx gain Visual terminal dashboard
923 lean-ctx gain --live Live auto-updating terminal dashboard
924 lean-ctx gain --graph 30-day savings chart
925 lean-ctx gain --daily Day-by-day breakdown with USD
926 lean-ctx token-report --json Machine-readable token + memory report
927 lean-ctx dashboard Open web dashboard at localhost:3333
928 lean-ctx dashboard --host=0.0.0.0 Bind to all interfaces (remote access)
929 lean-ctx wrapped Weekly savings report card
930 lean-ctx wrapped --month Monthly savings report card
931 lean-ctx sessions list List all CCP sessions
932 lean-ctx sessions show Show latest session state
933 lean-ctx discover Find missed savings in shell history
934 lean-ctx setup One-command setup (shell + editors + verify)
935 lean-ctx bootstrap Non-interactive setup + fix (zero-config)
936 lean-ctx bootstrap --json Machine-readable bootstrap report
937 lean-ctx init --global Install shell aliases (includes lean-ctx-on/off/mode/status)
938 lean-ctx-on Enable shell aliases in track mode (full output + stats)
939 lean-ctx-off Disable all shell aliases
940 lean-ctx-mode track Track mode: full output, stats recorded (default)
941 lean-ctx-mode compress Compress mode: all output compressed (power users)
942 lean-ctx-mode off Same as lean-ctx-off
943 lean-ctx-status Show whether compression is active
944 lean-ctx init --agent pi Install Pi Coding Agent extension
945 lean-ctx doctor Check PATH, config, MCP, and dashboard port
946 lean-ctx doctor --fix --json Repair + machine-readable report
947 lean-ctx status --json Machine-readable current status
948 lean-ctx read src/main.rs -m map
949 lean-ctx grep \"pub fn\" src/
950 lean-ctx deps .
951
952CLOUD:
953 cloud status Show cloud connection status
954 login <email> Log into existing LeanCTX Cloud account
955 register <email> Create a new LeanCTX Cloud account
956 forgot-password <email> Send password reset email
957 sync Upload local stats to cloud dashboard
958 contribute Share anonymized compression data
959
960TROUBLESHOOTING:
961 Commands broken? lean-ctx-off (fixes current session)
962 Permanent fix? lean-ctx uninstall (removes all hooks)
963 Manual fix? Edit ~/.zshrc, remove the \"lean-ctx shell hook\" block
964 Binary missing? Aliases auto-fallback to original commands (safe)
965 Preview init? lean-ctx init --global --dry-run
966
967WEBSITE: https://leanctx.com
968GITHUB: https://github.com/yvgude/lean-ctx
969",
970 version = env!("CARGO_PKG_VERSION"),
971 );
972}