flarer 0.1.0

Rust client and CLI for Cloudflare's Browser Rendering REST API (content, screenshot, PDF, snapshot, markdown, scrape, JSON extraction, links, crawl).
Documentation
//! `flarer` CLI binary.

use clap::Parser;
use flarer::cli::{Args, InteractiveConfig, interactive_init, interactive_prompt};
use flarer::{Account, Flarer, JsonOptions, Output, ResponseFormat, Tool};
use tracing_subscriber::{EnvFilter, fmt};

#[tokio::main]
async fn main() {
    init_tracing();
    if let Err(err) = run().await {
        eprintln!("error: {err}");
        std::process::exit(1);
    }
}

fn init_tracing() {
    let filter = EnvFilter::try_from_env("FLARER_LOG").unwrap_or_else(|_| EnvFilter::new("warn"));
    let _ = fmt().with_env_filter(filter).with_target(false).try_init();
}

async fn run() -> flarer::Result<()> {
    // We want to support `flarer` (no args) running interactively too, so
    // detect that before clap, which would otherwise error on missing `--url`.
    let raw: Vec<String> = std::env::args().collect();
    let interactive_flag = raw.len() == 1 || raw.iter().any(|a| a == "-i" || a == "--interactive");

    if interactive_flag {
        let _ = dotenv::dotenv();
        let flarer = interactive_init().await?;
        let cfg = interactive_prompt().await?;
        let out = run_config(&flarer, cfg).await?;
        println!("{}", out.display_string());
        return Ok(());
    }

    let args = Args::parse();
    let _ = dotenv::dotenv();
    let account = Account::from_env()?;

    let mut builder = Flarer::builder().account(account);
    if let Some(dir) = args.output_dir.clone() {
        builder = builder.output_dir(dir);
    }
    let flarer = builder.build()?;
    flarer.verify().await?;

    let url = args
        .url
        .as_deref()
        .ok_or_else(|| flarer::FlarerError::Config("--url is required".into()))?;

    let out = match args.tool {
        Tool::Json => {
            let mut opts = JsonOptions::new();
            if let Some(p) = args.prompt.clone() {
                opts = opts.with_prompt(p);
            }
            if let Some(path) = args.schema.as_deref() {
                let raw = std::fs::read_to_string(path)?;
                let rf: ResponseFormat = serde_json::from_str(&raw)?;
                opts = opts.with_response_format(rf);
            }
            Output::Json(flarer.json(url, opts).await?)
        }
        other => flarer.run(other, url).await?,
    };

    println!("{}", out.display_string());
    Ok(())
}

async fn run_config(flarer: &Flarer, cfg: InteractiveConfig) -> flarer::Result<Output> {
    match cfg.tool {
        Tool::Json => Ok(Output::Json(flarer.json(&cfg.url, cfg.json_options).await?)),
        other => flarer.run(other, &cfg.url).await,
    }
}