1//! `jevhooks serve` is the daemon, one for the machine. `jevhooks mcp` is the 2//! MCP server a Claude Code session starts, which makes sure the daemon runs. 3 4mod client; 5mod headroom; 6mod judge; 7mod log; 8mod mcp; 9mod paths; 10mod serve; 11 12use clap::{Parser, Subcommand}; 13use hyper::Method; 14 15#[derive(Parser)] 16#[command(version, about = "Claude Code hook events answered by Jev")] 17struct Cli { 18 #[command(subcommand)] 19 command: Command, 20} 21 22#[derive(Subcommand)] 23enum Command { 24 /// Run the daemon: one for the machine, on a Unix socket. 25 Serve, 26 /// Run the MCP server a Claude Code session starts (stdio). 27 Mcp, 28 /// Print the running daemon's status. 29 Status, 30 /// Print the most recent decisions, oldest first. 31 Recent { 32 #[arg(default_value_t = 20)] 33 count: u32, 34 }, 35 /// Stop the running daemon. 36 Stop, 37} 38 39/// One request to the daemon, printed. 40async fn print(method: Method, path: &str) -> Result<(), Box<dyn std::error::Error>> { 41 // A reader that closes early (`| head`) ends this quietly. Only for the 42 // commands that print: a daemon must survive a peer closing its socket. 43 unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL) }; 44 let (_, value) = client::call(method, path, None).await?; 45 println!("{}", serde_json::to_string_pretty(&value)?); 46 Ok(()) 47} 48 49#[tokio::main(flavor = "current_thread")] 50async fn main() { 51 let done = match Cli::parse().command { 52 Command::Serve => serve::serve().await, 53 Command::Mcp => mcp::serve().await, 54 Command::Status => print(Method::GET, "/status").await, 55 Command::Recent { count } => print(Method::GET, &format!("/recent?count={count}")).await, 56 Command::Stop => print(Method::POST, "/shutdown").await, 57 }; 58 if let Err(why) = done { 59 eprintln!("jevhooks: {why}"); 60 std::process::exit(1); 61 } 62}