rok-cli 0.6.1

Developer CLI for rok-based Axum applications
//! `rok metrics` — live metrics dashboard TUI (M7.3).

pub fn run(url: Option<&str>, interval: Option<u64>) -> anyhow::Result<()> {
    let interval_secs = interval.unwrap_or(2);
    let metrics_url = url
        .map(|u| u.to_string())
        .or_else(|| std::env::var("METRICS_URL").ok())
        .unwrap_or_else(|| "http://localhost:3000/metrics".to_string());

    println!("rok metrics — live dashboard");
    println!("{}", "".repeat(60));
    println!("  Source:   {metrics_url}");
    println!("  Refresh:  every {interval_secs}s");
    println!("  Press Ctrl+C to exit.");
    println!("{}", "".repeat(60));

    let mut iteration = 0u64;
    loop {
        iteration += 1;
        println!();
        println!("  [tick #{iteration}]  {}", chrono::Utc::now().format("%H:%M:%S"));

        match fetch_metrics(&metrics_url) {
            Ok(body) => {
                let lines: Vec<&str> = body.lines()
                    .filter(|l| !l.starts_with('#') && !l.trim().is_empty())
                    .take(20)
                    .collect();
                for line in lines {
                    println!("    {line}");
                }
                if body.lines().filter(|l| !l.starts_with('#') && !l.trim().is_empty()).count() > 20 {
                    println!("    … (see {metrics_url} for full output)");
                }
            }
            Err(e) => {
                println!("  ! could not reach {metrics_url}: {e}");
                println!("    Ensure your app is running with a /metrics endpoint.");
            }
        }

        std::thread::sleep(std::time::Duration::from_secs(interval_secs));
    }
}

fn fetch_metrics(url: &str) -> anyhow::Result<String> {
    let mut resp = ureq::get(url)
        .header("Accept", "text/plain; version=0.0.4")
        .call()?;
    Ok(resp.body_mut().read_to_string()?)
}