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