Skip to main content

lean_ctx/cli/
dispatch.rs

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