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