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
756#[cfg(feature = "http-server")]
757fn run_async<F: std::future::Future>(future: F) -> F::Output {
758 tokio::runtime::Runtime::new()
759 .expect("failed to create async runtime")
760 .block_on(future)
761}
762
763fn run_mcp_server() -> Result<()> {
764 use rmcp::ServiceExt;
765 use tracing_subscriber::EnvFilter;
766
767 std::env::set_var("NEBU_CTX_MCP_SERVER", "1");
768
769 let rt = tokio::runtime::Runtime::new()?;
770 rt.block_on(async {
771 tracing_subscriber::fmt()
772 .with_env_filter(EnvFilter::from_default_env())
773 .with_writer(std::io::stderr)
774 .init();
775
776 tracing::info!(
777 "nebu-ctx v{} MCP server starting",
778 env!("CARGO_PKG_VERSION")
779 );
780
781 let server = tools::create_server();
782 core::telemetry_queue::start_drain_task();
783 let transport =
784 mcp_stdio::HybridStdioTransport::new_server(tokio::io::stdin(), tokio::io::stdout());
785 let service = server.serve(transport).await?;
786 service.waiting().await?;
787
788 core::stats::flush();
789 core::mode_predictor::ModePredictor::flush();
790 core::feedback::FeedbackStore::flush();
791
792 Ok(())
793 })
794}
795
796fn print_help() {
797 println!(
798 "nebu-ctx {version} — Context Runtime for AI Agents
799
80090+ compression patterns | 46 MCP tools | Context Continuity Protocol
801
802USAGE:
803 nebu-ctx Start MCP server (stdio)
804 nebu-ctx serve Start MCP server (Streamable HTTP)
805 nebu-ctx -t \"command\" Track command (full output + stats, no compression)
806 nebu-ctx -c \"command\" Execute with compressed output (used by AI hooks)
807 nebu-ctx -c --raw \"command\" Execute without compression (full output)
808 nebu-ctx exec \"command\" Same as -c
809 nebu-ctx bypass \"command\" Run command with zero compression (raw passthrough)
810 nebu-ctx shell Interactive shell with compression
811
812COMMANDS:
813 token-report [--json] Token + memory report (project + session + CEP)
814 gain [action] Fetch analytics from server (report, score, cost, tasks, etc.)
815 serve [--host H] [--port N] MCP over HTTP (Streamable HTTP, local-first)
816 proxy start [--port=4444] API proxy: compress tool_results before LLM API
817 proxy status Show proxy statistics
818 cache [list|clear|stats] Show/manage file read cache
819 wrapped [--week|--month|--all] Savings report card (shareable)
820 sessions [list|show|cleanup] Manage CCP sessions (~/.nebu-ctx/sessions/)
821 benchmark run [path] [--json] Run real benchmark on project files
822 benchmark report [path] Generate shareable Markdown report
823 cheatsheet Command cheat sheet & workflow quick reference
824 setup One-command setup: shell + editor + verify
825 bootstrap Non-interactive setup + fix (zero-config)
826 status [--json] Show setup + MCP + rules status
827 init <shell> Print shell hook to stdout (eval pattern, like starship)
828 Supported: bash, zsh, fish, powershell
829 init --global Install shell aliases to rc file (file-based)
830 init --agent <name> Configure MCP for specific editor/agent
831 read <file> [-m mode] Read file with compression
832 diff <file1> <file2> Compressed file diff
833 grep <pattern> [path] Search with compressed output
834 find <pattern> [path] Find files with compressed output
835 ls [path] Directory listing with compression
836 deps [path] Show project dependencies
837 discover Find uncompressed commands in shell history
838 filter [list|validate|init] Manage custom compression filters (~/.nebu-ctx/filters/)
839 session Show adoption statistics
840 config Show/edit configuration (~/.nebu-ctx/config.toml)
841 theme [list|set|export|import] Customize terminal colors and themes
842 tee [list|clear|show <file>|last] Manage output tee files (~/.nebu-ctx/tee/)
843 terse [off|lite|full|ultra] Set agent output verbosity (saves 25-65% output tokens)
844 slow-log [list|clear] Show/clear slow command log (~/.nebu-ctx/slow-commands.log)
845 gotchas [list|clear|export|stats] Bug Memory: view/manage auto-detected error patterns
846 buddy [show|stats|ascii|json] Token Guardian: your data-driven coding companion
847 doctor [--fix] [--json] Run diagnostics (and optionally repair)
848 safety-levels Show compression safety levels per command
849 bypass \"command\" Run command with zero compression (raw passthrough)
850 uninstall Remove shell hook, MCP configs, and data directory
851
852SHELL HOOK PATTERNS (90+):
853 git status, log, diff, add, commit, push, pull, fetch, clone,
854 branch, checkout, switch, merge, stash, tag, reset, remote
855 docker build, ps, images, logs, compose, exec, network
856 npm/pnpm install, test, run, list, outdated, audit
857 cargo build, test, check, clippy
858 gh pr list/view/create, issue list/view, run list/view
859 kubectl get pods/services/deployments, logs, describe, apply
860 python pip install/list/outdated, ruff check/format, poetry, uv
861 linters eslint, biome, prettier, golangci-lint
862 builds tsc, next build, vite build
863 ruby rubocop, bundle install/update, rake test, rails test
864 tests jest, vitest, pytest, go test, playwright, rspec, minitest
865 iac terraform, make, maven, gradle, dotnet, flutter, dart
866 utils curl, grep/rg, find, ls, wget, env
867 data JSON schema extraction, log deduplication
868
869READ MODES:
870 auto Auto-select optimal mode (default)
871 full Full content (cached re-reads = 13 tokens)
872 map Dependency graph + API signatures
873 signatures tree-sitter AST extraction (18 languages)
874 task Task-relevant filtering (requires ctx_session task)
875 reference One-line reference stub (cheap cache key)
876 aggressive Syntax-stripped content
877 entropy Shannon entropy filtered
878 diff Changed lines only
879 lines:N-M Specific line ranges (e.g. lines:10-50,80)
880
881ENVIRONMENT:
882 NEBU_CTX_DISABLED=1 Bypass ALL compression + prevent shell hook from loading
883 NEBU_CTX_ENABLED=0 Prevent shell hook auto-start (nebu-ctx-on still works)
884 NEBU_CTX_RAW=1 Same as --raw for current command
885 NEBU_CTX_AUTONOMY=false Disable autonomous features
886 NEBU_CTX_COMPRESS=1 Force compression (even for excluded commands)
887
888OPTIONS:
889 --version, -V Show version
890 --help, -h Show this help
891
892EXAMPLES:
893 nebu-ctx -c \"git status\" Compressed git output
894 nebu-ctx -c \"kubectl get pods\" Compressed k8s output
895 nebu-ctx -c \"gh pr list\" Compressed GitHub CLI output
896 nebu-ctx token-report --json Machine-readable token + memory report
897 nebu-ctx wrapped Weekly savings report card
898 nebu-ctx wrapped --month Monthly savings report card
899 nebu-ctx sessions list List all CCP sessions
900 nebu-ctx sessions show Show latest session state
901 nebu-ctx discover Find missed savings in shell history
902 nebu-ctx setup One-command setup (shell + editors + verify)
903 nebu-ctx bootstrap Non-interactive setup + fix (zero-config)
904 nebu-ctx bootstrap --json Machine-readable bootstrap report
905 nebu-ctx init --global Install shell aliases (file-based, includes nebu-ctx-on/off)
906
907EVAL INIT (starship/zoxide style — always in sync with binary version):
908 # bash: add to ~/.bashrc
909 eval \"$(nebu-ctx init bash)\"
910 # zsh: add to ~/.zshrc
911 eval \"$(nebu-ctx init zsh)\"
912 # fish: add to ~/.config/fish/config.fish
913 nebu-ctx init fish | source
914 # powershell: add to $PROFILE
915 nebu-ctx init powershell | Invoke-Expression
916 nebu-ctx-on Enable shell aliases in track mode (full output + stats)
917 nebu-ctx-off Disable all shell aliases
918 nebu-ctx-mode track Track mode: full output, stats recorded (default)
919 nebu-ctx-mode compress Compress mode: all output compressed (power users)
920 nebu-ctx-mode off Same as nebu-ctx-off
921 nebu-ctx-status Show whether compression is active
922 nebu-ctx init --agent pi Install Pi Coding Agent extension
923 nebu-ctx doctor Check PATH, config, MCP, and local edge health
924 nebu-ctx doctor --fix --json Repair + machine-readable report
925 nebu-ctx status --json Machine-readable current status
926 nebu-ctx read src/main.rs -m map
927 nebu-ctx grep \"pub fn\" src/
928 nebu-ctx deps .
929
930HOST CONNECTION:
931 connect [--endpoint <url>] [--token <token>] Save and validate a server connection
932 status Includes host connection status
933 disconnect Remove the saved server connection
934
935TROUBLESHOOTING:
936 Commands broken? nebu-ctx-off (fixes current session)
937 Permanent fix? nebu-ctx uninstall (removes all hooks)
938 Manual fix? Edit ~/.zshrc, remove the \"nebu-ctx shell hook\" block
939 Binary missing? Aliases auto-fallback to original commands (safe)
940 Preview init? nebu-ctx init --global --dry-run
941
942WEBSITE: https://nebu-ctx.com
943GITHUB: https://github.com/MarkBovee/nebu-ctx
944",
945 version = env!("CARGO_PKG_VERSION"),
946 );
947}