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