cekanje 0.1.0

tmux notifier daemon for Claude Code sessions: track every active session, surface a native popup when one needs attention, jump to its pane via fzf
use crate::client;
use crate::state::{Session, Status};
use crate::tmux;
use anyhow::{Context, Result, bail};
use std::io::Write;
use std::process::{Command, Stdio};

pub async fn run(port: u16) -> Result<()> {
    let body = client::http_get(port, "/list")
        .await
        .context("fetch /list from cekanje daemon")?;
    let sessions: Vec<Session> = serde_json::from_str(&body).unwrap_or_default();

    if sessions.is_empty() {
        eprintln!("(no claude sessions)");
        return Ok(());
    }

    let mut lines = String::new();
    for s in &sessions {
        let icon = match s.status {
            Status::Waiting => "",
            Status::Working => "  ",
        };
        let pane = s.tmux.as_ref().map(|t| t.pane.as_str()).unwrap_or("?");
        let cwd = s
            .cwd
            .as_ref()
            .map(|p| p.display().to_string())
            .unwrap_or_default();
        let msg = s.last_message.as_deref().unwrap_or("");
        let age = s
            .waiting_since_secs
            .map(|n| format!(" {n}s"))
            .unwrap_or_default();
        lines.push_str(&format!(
            "{icon} {pane:<6} {cwd}{age}  {msg}\t{sid}\n",
            sid = s.session_id
        ));
    }

    let mut child = Command::new("fzf")
        .args([
            "--with-nth=1",
            "--delimiter=\t",
            "--height=100%",
            "--no-info",
            "--prompt=claude> ",
        ])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .context("spawn fzf — is it installed?")?;

    child
        .stdin
        .as_mut()
        .expect("fzf stdin")
        .write_all(lines.as_bytes())?;
    let out = child.wait_with_output()?;
    if !out.status.success() {
        return Ok(());
    }
    let line = String::from_utf8_lossy(&out.stdout);
    let sid = line.split('\t').nth(1).unwrap_or("").trim();
    if sid.is_empty() {
        return Ok(());
    }
    let Some(target) = sessions.iter().find(|s| s.session_id == sid) else {
        return Ok(());
    };
    let Some(loc) = &target.tmux else {
        bail!("no tmux location recorded for {sid}");
    };
    tmux::switch_to_pane(loc.socket.as_deref(), &loc.pane)?;
    Ok(())
}