1use rtk_mcp_cc::{delegate, Config};
18use std::io::{Read, Write};
19use std::path::PathBuf;
20use std::process::{Command, Stdio};
21
22fn main() {
23 let args: Vec<String> = std::env::args().collect();
24 if args.iter().any(|a| a == "--version" || a == "-V") {
25 println!("rtk-cc-hook {}", env!("CARGO_PKG_VERSION"));
26 return;
27 }
28 if args.iter().any(|a| a == "--help" || a == "-h") {
29 print_help();
30 return;
31 }
32
33 tracing_subscriber::fmt()
35 .with_writer(std::io::stderr)
36 .with_env_filter(
37 tracing_subscriber::EnvFilter::try_from_default_env()
38 .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn")),
39 )
40 .init();
41
42 let mut input = String::new();
43 if std::io::stdin().read_to_string(&mut input).is_err() {
44 tracing::warn!("could not read hook input from stdin; passing through");
45 return;
46 }
47
48 let project_dir = std::env::var("CLAUDE_PROJECT_DIR")
49 .map(PathBuf::from)
50 .or_else(|_| std::env::current_dir())
51 .unwrap_or_else(|_| PathBuf::from("."));
52
53 let config = Config::load(&project_dir);
54 let verdict = delegate(&input, &config, |args, payload| {
55 run_rtk(&config.rtk_bin, args, payload)
56 });
57
58 print!("{verdict}");
59}
60
61fn run_rtk(rtk_bin: &str, args: &[String], payload: &str) -> Option<String> {
69 let mut child = match Command::new(rtk_bin)
70 .args(args)
71 .stdin(Stdio::piped())
72 .stdout(Stdio::piped())
73 .stderr(Stdio::inherit())
74 .spawn()
75 {
76 Ok(child) => child,
77 Err(e) => {
78 tracing::warn!(rtk_bin, "could not spawn rtk ({e}); passing through");
79 return None;
80 }
81 };
82
83 if let Some(mut stdin) = child.stdin.take() {
86 if let Err(e) = stdin.write_all(payload.as_bytes()) {
87 tracing::warn!("could not write to rtk stdin ({e})");
88 }
89 }
90
91 let output = match child.wait_with_output() {
92 Ok(output) => output,
93 Err(e) => {
94 tracing::warn!("rtk did not complete ({e}); passing through");
95 return None;
96 }
97 };
98
99 if !output.status.success() {
100 tracing::warn!(status = ?output.status, "rtk exited non-zero; passing through");
101 return None;
102 }
103
104 match String::from_utf8(output.stdout) {
105 Ok(stdout) => Some(stdout),
106 Err(_) => {
107 tracing::warn!("rtk produced non-UTF-8 stdout; passing through");
108 None
109 }
110 }
111}
112
113fn print_help() {
114 println!(
115 "rtk-cc-hook {} — PreToolUse hook that routes shell commands through rtk",
116 env!("CARGO_PKG_VERSION")
117 );
118 println!();
119 println!("Usage: rtk-cc-hook");
120 println!(" Reads a Claude Code PreToolUse event as JSON on stdin, delegates to");
121 println!(" `rtk hook claude`, and writes the resulting verdict to stdout. Writes");
122 println!(" nothing — and always exits 0 — when rtk is unavailable or declines to");
123 println!(" rewrite, so the original command runs unchanged.");
124 println!();
125 println!("Environment:");
126 println!(" RTK_BIN Path to the rtk executable (default: `rtk` on PATH)");
127 println!(" RTK_CC_DISABLE=1 Pass every command through untouched");
128 println!(" RTK_CC_ULTRA_COMPACT Pass --ultra-compact to rtk");
129 println!(" RTK_CC_SKIP_ENV Pass --skip-env to rtk");
130 println!();
131 println!("Settings file: .claude/rtk-mcp-cc.local.md (environment overrides it)");
132}