main.rsannotatedmain.rssource62 lines · 1.8 KB · raw

jevhooks serve is the daemon, one for the machine. jevhooks mcp is the MCP server a Claude Code session starts, which makes sure the daemon runs.

4mod client;
5mod headroom;
6mod judge;
7mod log;
8mod mcp;
9mod paths;
10mod serve;
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 {

Run the daemon: one for the machine, on a Unix socket.

25    Serve,

Run the MCP server a Claude Code session starts (stdio).

27    Mcp,

Print the running daemon's status.

29    Status,

Print the most recent decisions, oldest first.

31    Recent {
32        #[arg(default_value_t = 20)]
33        count: u32,
34    },

Stop the running daemon.

36    Stop,
37}

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}
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}