Skip to main content

lean_ctx/cli/
dispatch.rs

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