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}