tokr 0.1.0

Persistent token-usage ledger for AI coding agents. Captures on write, queries forever.
use anyhow::{Context, Result};
use serde::Deserialize;
use std::time::Duration;

use crate::pricing::{self, ModelPrice};

pub const DEFAULT_URL: &str =
    "https://raw.githubusercontent.com/Codycody31/tokr/master/data/pricing.toml";

#[derive(Debug, Deserialize)]
struct PricingFile {
    #[serde(rename = "model")]
    models: Vec<ModelPrice>,
}

pub fn run(url: Option<String>) -> Result<()> {
    let url = url.unwrap_or_else(|| DEFAULT_URL.to_string());
    println!("Fetching pricing from {url} ...");

    let agent = ureq::AgentBuilder::new()
        .timeout(Duration::from_secs(20))
        .user_agent(concat!("tokr/", env!("CARGO_PKG_VERSION")))
        .build();

    let resp = agent
        .get(&url)
        .call()
        .with_context(|| format!("GET {url}"))?;
    if resp.status() >= 400 {
        anyhow::bail!("fetch failed: HTTP {}", resp.status());
    }
    let body = resp.into_string().context("reading response body")?;

    let parsed: PricingFile = toml::from_str(&body).context("parsing fetched TOML")?;
    if parsed.models.is_empty() {
        anyhow::bail!("fetched file has no [[model]] entries");
    }

    let n_total = parsed.models.len();
    let added = pricing::append_overrides(&parsed.models).context("writing overrides file")?;

    println!(
        "Fetched {n_total} entries; appended {added} new entries to {}",
        pricing::overrides_path()?.display()
    );
    if added > 0 {
        println!(
            "Existing rows are still costed against their stamped pricing_version. \
             Run `tokr recost` to re-stamp historical events using the new prices."
        );
    } else {
        println!("No new entries — your local pricing is already up to date.");
    }
    Ok(())
}