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
use clap::Parser;
use night_fury_daemon_core::cli::make_req;
use night_fury_daemon_core::{client, spawn};
use serde_json::json;
use tail_fin_daemon::cli::{expand_home, Cli, Commands, DaemonCmd, SessionCmd};
use tail_fin_daemon::driver::SiteEntry;
use tail_fin_daemon::handlers::{grok::GrokHandler, sa::SaHandler, twitter::TwitterHandler};

fn register_all_sites() {
    // Register all known sites so the launcher can look them up by id.
    // Adding a new site to the daemon = add one line here.
    tail_fin_daemon::driver::register_sites(vec![
        SiteEntry::new(tail_fin_sa::SeekingAlphaSite, |s| {
            Box::new(SaHandler::new(s))
        }),
        SiteEntry::new(tail_fin_twitter::TwitterSite, |s| {
            Box::new(TwitterHandler::new(s))
        }),
        SiteEntry::new(tail_fin_grok::GrokSite, |s| Box::new(GrokHandler::new(s))),
        SiteEntry::stateless(tail_fin_reddit::RedditSite),
        SiteEntry::stateless(tail_fin_instagram::InstagramSite),
        SiteEntry::stateless(tail_fin_xhs::XhsSite),
        SiteEntry::stateless(tail_fin_nansen::NansenSite),
        SiteEntry::stateless(tail_fin_coupang::CoupangSite),
        SiteEntry::stateless(tail_fin_spotify::SpotifySite),
        SiteEntry::stateless(tail_fin_youtube::YoutubeSite),
        SiteEntry::stateless(tail_fin_bloomberg::BloombergSite),
        SiteEntry::stateless(tail_fin_591::FiveNineOneSite),
        SiteEntry::stateless(tail_fin_pcc::PccSite),
        SiteEntry::stateless(tail_fin_tradingview::TradingViewSite),
    ]);
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_env("TFD_LOG")
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .with_writer(std::io::stderr)
        .init();

    let cli = Cli::parse();
    let socket = expand_home(&cli.socket);

    // Ensure the parent directory for the socket exists.
    if let Some(parent) = std::path::Path::new(&socket).parent() {
        let _ = std::fs::create_dir_all(parent);
    }

    match cli.command {
        Commands::Daemon { cmd } => match cmd {
            DaemonCmd::Start {
                idle_timeout,
                daemon_idle,
                max_sessions,
            } => {
                register_all_sites();
                tail_fin_daemon::server::serve(&socket, idle_timeout, daemon_idle, max_sessions)
                    .await?;
            }
            DaemonCmd::Stop => {
                let req = make_req("daemon.shutdown", None, json!({}));
                let resp = client::send(&socket, &req).await?;
                tail_fin_daemon::dispatch::print_and_check(&resp);
            }
            DaemonCmd::Status => {
                spawn::ensure_daemon(&socket).await?;
                let req = make_req("daemon.status", None, json!({}));
                let resp = client::send(&socket, &req).await?;
                tail_fin_daemon::dispatch::print_and_check(&resp);
            }
        },

        Commands::Session { cmd } => match cmd {
            SessionCmd::Acquire { site, host } => {
                spawn::ensure_daemon(&socket).await?;
                let req = make_req(
                    "session.acquire",
                    None,
                    json!({"site": site, "host": host, "mode": "acquired"}),
                );
                let resp = client::send(&socket, &req).await?;
                tail_fin_daemon::dispatch::print_and_check(&resp);
            }
            SessionCmd::Release { session_id } => {
                spawn::ensure_daemon(&socket).await?;
                let req = make_req("session.release", Some(&session_id), json!({}));
                let resp = client::send(&socket, &req).await?;
                tail_fin_daemon::dispatch::print_and_check(&resp);
            }
            SessionCmd::List => {
                spawn::ensure_daemon(&socket).await?;
                let req = make_req("session.list", None, json!({}));
                let resp = client::send(&socket, &req).await?;
                tail_fin_daemon::dispatch::print_and_check(&resp);
            }
            SessionCmd::Destroy { session_id } => {
                spawn::ensure_daemon(&socket).await?;
                let req = make_req("session.destroy", Some(&session_id), json!({}));
                let resp = client::send(&socket, &req).await?;
                tail_fin_daemon::dispatch::print_and_check(&resp);
            }
        },

        Commands::Start {
            idle_timeout,
            daemon_idle,
            max_sessions,
            detach: _,
        } => {
            register_all_sites();
            tail_fin_daemon::server::serve(&socket, idle_timeout, daemon_idle, max_sessions)
                .await?;
        }

        Commands::Sa { cmd } => {
            tail_fin_daemon::dispatch::sa::run(cmd, &socket, &cli.host, cli.session.clone())
                .await?;
        }
        Commands::Twitter { cmd } => {
            tail_fin_daemon::dispatch::twitter::run(cmd, &socket, &cli.host, cli.session.clone())
                .await?;
        }
        Commands::Grok { cmd } => {
            tail_fin_daemon::dispatch::grok::run(cmd, &socket, &cli.host, cli.session.clone())
                .await?;
        }
    }

    Ok(())
}