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