tail-fin-daemon 0.7.3

Long-running browser-session daemon for tail-fin (tfd binary). Keeps Chrome tabs warm across invocations via a Unix-socket protocol; registers Site implementations through a runtime Arc<dyn Site> registry.
Documentation
pub mod grok;
pub mod sa;
pub mod twitter;

use anyhow::{anyhow, Result};
use night_fury_daemon_core::cli::{make_req, print_response};
use night_fury_daemon_core::protocol::Response;
use night_fury_daemon_core::{client, spawn};
use serde_json::{json, Value};

pub struct ExecArgs<'a> {
    pub site: &'a str,
    pub host: &'a str,
    pub cmd: &'a str,
    pub params: Value,
}

/// Transparent mode: acquire (mode=in_use) → run business cmd → release.
/// Prints the business-cmd response; propagates errors from acquire.
pub async fn acquire_then_exec(socket: &str, args: ExecArgs<'_>) -> Result<()> {
    spawn::ensure_daemon(socket).await?;

    let acquire_req = make_req(
        "session.acquire",
        None,
        json!({"site": args.site, "host": args.host, "mode": "in_use"}),
    );
    let acquire_resp = client::send(socket, &acquire_req).await?;
    let sid = session_id_from(&acquire_resp)?;

    let exec_req = make_req(args.cmd, Some(&sid), args.params);
    let exec_resp = client::send(socket, &exec_req).await?;

    // Always release, even on exec error. Log but don't propagate release errors.
    let release_req = make_req("session.release", Some(&sid), json!({}));
    match client::send(socket, &release_req).await {
        Ok(resp) if !resp.ok => {
            tracing::warn!(
                session_id = %sid,
                error = %resp.error.clone().unwrap_or_default(),
                "session.release returned not-ok"
            );
        }
        Err(e) => {
            tracing::warn!(session_id = %sid, error = %e, "session.release failed");
        }
        _ => {}
    }

    print_and_check(&exec_resp);
    Ok(())
}

/// Print the response as JSON, then exit(1) if response.ok is false so
/// scripts and shell conditionals can detect failure. Returns normally on ok.
///
/// Exits directly instead of returning `Err` to avoid anyhow's double-print:
/// the full JSON is already on stdout, a terminal user doesn't need the same
/// error printed again on stderr.
pub fn print_and_check(resp: &Response) {
    print_response(resp);
    if !resp.ok {
        std::process::exit(1);
    }
}

fn session_id_from(resp: &Response) -> Result<String> {
    if !resp.ok {
        return Err(anyhow!(
            "acquire failed: {}",
            resp.error.clone().unwrap_or_default()
        ));
    }
    resp.data
        .as_ref()
        .and_then(|d| d.get("session_id"))
        .and_then(|v| v.as_str())
        .map(String::from)
        .ok_or_else(|| anyhow!("acquire response missing session_id"))
}