cmd-usage 0.7.19

Live Command Code (commandcode.ai) usage dashboard: credits, 5-hour and weekly windows, plan limits. Watch mode or one-shot.
Documentation
// The docs catalogue: what a model costs and what each *plan* allows.
//
// Generated by `scripts/extract-zen.ts` (run `bun scripts/extract-zen.ts`) from
// OpenCode's own docs sources, so the numbers move with upstream instead of
// living in code. Go ships two plans (Go $10/mo, Go Plus $40/mo) that share one
// token price list but grant each model a different monthly dollar limit; Zen is
// pay-as-you-go, so it has rates but no limits.
//
// Which plan you are on is not discoverable: the usage endpoint reports the
// meters' percentages, not a plan id, and the local store records no
// subscription, so `ocuse` takes a `--plan` flag and defaults to
// [`DEFAULT_PLAN`].
use serde::Deserialize;
use std::collections::BTreeMap;

// Re-exported from `cmduse-core` so the path stays inside its own crate (a
// `include_str!` reaching outside is fine locally and fatal at `cargo publish`).
const RAW: &str = cmduse_core::ZEN_JSON;

/// The plan `ocuse` measures against when `--plan` is not given.
pub const DEFAULT_PLAN: &str = "go";

#[derive(Debug, Clone, Deserialize)]
pub struct RateVariant {
    pub peak: Option<String>,
    #[serde(rename = "tierMaxTokens")]
    pub tier_max_tokens: Option<u64>,
    #[serde(rename = "tierMinTokens")]
    pub tier_min_tokens: Option<u64>,
    pub input: f64,
    pub output: f64,
    #[serde(rename = "cacheRead")]
    pub cache_read: f64,
    #[serde(rename = "cacheWrite")]
    pub cache_write: f64,
}

#[derive(Debug, Clone, Deserialize)]
pub struct Model {
    pub id: String,
    pub name: String,
    pub variants: Vec<RateVariant>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct WindowShare {
    #[serde(rename = "fiveHour")]
    pub five_hour: f64,
    pub weekly: f64,
    pub monthly: f64,
}

/// One Go plan: a price plus a monthly dollar limit per model id.
#[derive(Debug, Clone, Deserialize)]
pub struct GoPlan {
    pub label: String,
    #[serde(rename = "priceUsd")]
    pub price_usd: f64,
    /// model id -> monthly dollar limit; a missing id is free/unlimited.
    #[serde(rename = "allowanceByModel")]
    pub allowance_by_model: BTreeMap<String, f64>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct Go {
    #[serde(rename = "windowShare")]
    pub window_share: WindowShare,
    /// Token rates, shared across plans (pricing is the same for both).
    pub models: Vec<Model>,
    /// Keyed by plan slug ("go", "go-plus"); only the allowances differ.
    pub plans: BTreeMap<String, GoPlan>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct Zen {
    pub models: Vec<Model>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct Catalog {
    #[serde(rename = "extractedAt")]
    pub extracted_at: String,
    pub go: Go,
    pub zen: Zen,
}

/// Parsed once; the file is a compile-time constant.
pub fn catalog() -> Catalog {
    serde_json::from_str(RAW)
        .expect("core/zen.json is generated; regenerate with scripts/extract-zen.ts")
}

impl Catalog {
    /// The plan ids the docs define, in the map's (alphabetical) order — "go"
    /// sorts before "go-plus", which is the order they read in the docs.
    pub fn plan_ids(&self) -> Vec<&str> {
        self.go.plans.keys().map(String::as_str).collect()
    }

    pub fn plan(&self, plan_id: &str) -> Option<&GoPlan> {
        self.go.plans.get(plan_id)
    }

    /// Monthly dollar limit for a model under one plan, if the docs list one.
    /// None for a free/unlimited model, an unknown model, or an unknown plan.
    pub fn go_limit(&self, plan_id: &str, model_id: &str) -> Option<f64> {
        self.plan(plan_id)
            .and_then(|plan| plan.allowance_by_model.get(model_id).copied())
    }
}

/// Suggested cap for a rolling window, from the documented share of the limit.
pub fn window_cap(limit: f64, share: f64) -> f64 {
    limit * share
}

/// Is this instant inside a DeepSeek peak window?
///
/// The docs give it exactly: 01:00–04:00 and 06:00–10:00 UTC, Monday–Friday;
/// every other hour, weekends included, is off-peak. The console applies it
/// (its per-request cost for a peak-hour turn is the peak sheet), while opencode
/// records the off-peak sheet for **every** turn — which is why this store reads
/// ~40% under the console on peak-heavy days and why the total is priced here
/// instead of trusting `cost`.
pub fn is_peak(at_ms: i64) -> bool {
    let secs = at_ms.div_euclid(1000);
    let weekday = (secs.div_euclid(86_400) + 4).rem_euclid(7); // 1970-01-01 was a Thursday
    if weekday >= 5 {
        return false;
    }
    let hour = secs.rem_euclid(86_400) / 3600;
    (1..4).contains(&hour) || (6..10).contains(&hour)
}

impl Catalog {
    /// What one turn costs on the documented sheets, or `None` when the model is
    /// unknown or free (no rate rows), so the caller can fall back to `cost`.
    ///
    /// `reasoning` is billed at the output rate — opencode does the same in its
    /// own `cost`, verified to the cent on real rows.
    #[allow(clippy::too_many_arguments)]
    pub fn price(
        &self,
        model_id: &str,
        at_ms: i64,
        input: u64,
        output: u64,
        reasoning: u64,
        cache_read: u64,
        cache_write: u64,
    ) -> Option<f64> {
        let model = self.go.models.iter().find(|m| m.id == model_id)?;
        let peak = model
            .variants
            .iter()
            .find(|v| v.peak.as_deref() == Some("peak"));
        let off_peak = model
            .variants
            .iter()
            .find(|v| v.peak.as_deref() == Some("off-peak"));
        let rates = match (peak, off_peak) {
            (Some(peak), Some(_)) if is_peak(at_ms) => peak,
            (Some(_), Some(off)) => off,
            // No time-of-day split (or a single sheet): the only variant wins.
            _ => model.variants.first()?,
        };
        let cost = input as f64 * rates.input
            + (output + reasoning) as f64 * rates.output
            + cache_read as f64 * rates.cache_read
            + cache_write as f64 * rates.cache_write;
        (cost > 0.0).then_some(cost / 1e6)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn catalogue_parses_and_carries_both_plans() {
        let c = catalog();
        assert!(c.go.models.len() > 20, "go models: {}", c.go.models.len());
        assert!(!c.zen.models.is_empty());
        assert_eq!(c.plan_ids(), vec!["go", "go-plus"]);
        assert_eq!(c.plan("go").map(|p| p.price_usd), Some(10.0));
        assert_eq!(c.plan("go-plus").map(|p| p.price_usd), Some(40.0));
        assert_eq!(c.go_limit("go", "glm-5.3-flash"), Some(60.0));
        assert_eq!(c.go_limit("go-plus", "glm-5.3-flash"), Some(180.0));
        assert_eq!(c.go_limit("go", "nope-not-a-model"), None);
        assert_eq!(c.go_limit("nope-not-a-plan", "glm-5.3-flash"), None);
    }

    #[test]
    fn plans_share_the_model_set_and_differ_on_allowance() {
        let c = catalog();
        let go = c.plan("go").expect("go");
        let plus = c.plan("go-plus").expect("go-plus");
        assert_eq!(go.label, "Go");
        assert_eq!(plus.label, "Go Plus");
        assert_eq!(go.allowance_by_model.len(), plus.allowance_by_model.len());
        // Rates are shared, so every allowance model is in the other plan too,
        // and Go Plus grants more on at least one of them.
        assert!(plus
            .allowance_by_model
            .iter()
            .all(|(id, _)| go.allowance_by_model.contains_key(id)));
        assert!(plus
            .allowance_by_model
            .iter()
            .any(|(id, limit)| go.allowance_by_model.get(id).is_some_and(|g| limit > g)));
    }

    #[test]
    fn window_split_matches_the_docs() {
        let c = catalog();
        assert_eq!(c.go.window_share.five_hour, 0.2);
        assert_eq!(c.go.window_share.weekly, 0.5);
        assert_eq!(c.go.window_share.monthly, 1.0);
        assert_eq!(window_cap(60.0, c.go.window_share.five_hour), 12.0);
    }

    #[test]
    fn tiered_and_peak_variants_are_kept() {
        let c = catalog();
        let qwen =
            c.go.models
                .iter()
                .find(|m| m.id == "qwen3.7-plus")
                .expect("qwen3.7-plus");
        assert_eq!(qwen.variants.len(), 2);
        assert!(qwen
            .variants
            .iter()
            .any(|v| v.tier_max_tokens == Some(256_000)));
        let deepseek =
            c.go.models
                .iter()
                .find(|m| m.id == "deepseek-v4-flash")
                .expect("deepseek");
        assert!(deepseek
            .variants
            .iter()
            .any(|v| v.peak.as_deref() == Some("peak")));
    }
}