1//! Where the daemon keeps its socket, its log and its decisions. The socket's 2//! path is `jevhooks_events::socket_path`, which the mod calls too. 3 4use std::path::PathBuf; 5 6/// `$XDG_STATE_HOME/jevhooks`, or `~/.local/state/jevhooks`. 7pub fn state_dir() -> PathBuf { 8 let base = std::env::var_os("XDG_STATE_HOME") 9 .filter(|v| !v.is_empty()) 10 .map(PathBuf::from) 11 .or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".local/state"))) 12 .unwrap_or_else(|| PathBuf::from(".")); 13 base.join("jevhooks") 14} 15 16/// The one daemon's socket; every session's mod and MCP server talk to it. 17/// An error when no path is short enough to be a socket's. 18pub fn socket() -> Result<PathBuf, String> { 19 let var = |name: &str| std::env::var(name).ok(); 20 jevhooks_events::socket_path(var("XDG_STATE_HOME").as_deref(), var("HOME").as_deref(), var("XDG_RUNTIME_DIR").as_deref()) 21 .map(PathBuf::from) 22} 23 24/// Held for as long as a daemon runs, so two cannot serve at once. 25pub fn lock() -> PathBuf { 26 state_dir().join("daemon.lock") 27} 28 29/// The daemon's stderr, since nothing else is attached to it. 30pub fn daemon_log() -> PathBuf { 31 state_dir().join("daemon.log") 32} 33 34/// Every decision and what became of it, one JSON object per line. 35pub fn decisions() -> PathBuf { 36 state_dir().join("decisions.jsonl") 37} 38 39/// Which build this process is: the executable's device, inode, size and 40/// modification time. A rebuilt binary is a different identity, which is how 41/// a new session's MCP server knows the running daemon is stale. 42pub fn identity() -> String { 43 use std::os::unix::fs::MetadataExt; 44 match std::env::current_exe().and_then(std::fs::metadata) { 45 Ok(meta) => format!("{}:{}:{}:{}", meta.dev(), meta.ino(), meta.len(), meta.mtime_nsec() as i128 + meta.mtime() as i128 * 1_000_000_000), 46 Err(_) => "unknown".to_owned(), 47 } 48}