Skip to main content

cove_cli/
events.rs

1// ── Shared event helpers ──
2//
3// Functions for reading and writing Cove session event files.
4// Used by hook handler (writing), sidebar (reading), and kill (writing end events).
5
6use std::fs::{self, OpenOptions};
7use std::io::Write;
8use std::path::{Path, PathBuf};
9
10/// Path to the Cove events directory (~/.cove/events/).
11pub fn events_dir() -> PathBuf {
12    let home = std::env::var("HOME").unwrap_or_default();
13    PathBuf::from(home).join(".cove").join("events")
14}
15
16/// Append a state event to the session's event file.
17pub fn write_event(session_id: &str, cwd: &str, pane_id: &str, state: &str) -> Result<(), String> {
18    write_event_to(&events_dir(), session_id, cwd, pane_id, state)
19}
20
21/// Append a state event to a session file in the given directory.
22pub fn write_event_to(
23    dir: &Path,
24    session_id: &str,
25    cwd: &str,
26    pane_id: &str,
27    state: &str,
28) -> Result<(), String> {
29    fs::create_dir_all(dir).map_err(|e| format!("create events dir: {e}"))?;
30
31    let path = dir.join(format!("{session_id}.jsonl"));
32    let mut file = OpenOptions::new()
33        .create(true)
34        .append(true)
35        .open(&path)
36        .map_err(|e| format!("open event file: {e}"))?;
37
38    let ts = std::time::SystemTime::now()
39        .duration_since(std::time::UNIX_EPOCH)
40        .unwrap_or_default()
41        .as_secs();
42
43    let line = format!(r#"{{"state":"{state}","cwd":"{cwd}","pane_id":"{pane_id}","ts":{ts}}}"#);
44    writeln!(file, "{line}").map_err(|e| format!("write event: {e}"))?;
45
46    Ok(())
47}
48
49/// Find the Claude session_id for a given tmux pane_id by scanning cove event files.
50/// Returns the session_id from the file whose last event matches the pane_id
51/// with the highest timestamp (handles pane_id recycling).
52pub fn find_session_id(pane_id: &str) -> Option<String> {
53    find_session_id_in(&events_dir(), pane_id)
54}
55
56/// Find the Claude session_id for a given pane_id by scanning event files in the given directory.
57pub fn find_session_id_in(dir: &Path, pane_id: &str) -> Option<String> {
58    let entries = fs::read_dir(dir).ok()?;
59
60    let mut best: Option<(String, u64)> = None;
61    for entry in entries.flatten() {
62        let path = entry.path();
63        if path.extension().and_then(|e| e.to_str()) != Some("jsonl") {
64            continue;
65        }
66        let content = fs::read_to_string(&path).unwrap_or_default();
67        let last_line = content.lines().rev().find(|l| !l.trim().is_empty());
68        if let Some(line) = last_line {
69            if let Ok(event) = serde_json::from_str::<serde_json::Value>(line) {
70                if event.get("pane_id").and_then(|v| v.as_str()) == Some(pane_id) {
71                    let ts = event.get("ts").and_then(|v| v.as_u64()).unwrap_or(0);
72                    if best.as_ref().is_none_or(|(_, prev_ts)| ts > *prev_ts) {
73                        if let Some(sid) = path.file_stem().and_then(|s| s.to_str()) {
74                            best = Some((sid.to_string(), ts));
75                        }
76                    }
77                }
78            }
79        }
80    }
81
82    best.map(|(sid, _)| sid)
83}