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