quotch 0.5.5

Fast cross-platform CLI for AI coding-agent usage limits
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

// Account = a credential source, not a person. Not serialized.
#[derive(Debug, Clone)]
pub struct Account {
    pub provider: &'static str,
    pub id: String, // offline-derivable: "default" | label | hash(path)
    pub source: CredentialSource,
    pub label: Option<String>,
    #[allow(dead_code)] // email etc — fetched later, NEVER a cache key
    pub display: Option<String>,
}

#[derive(Debug, Clone)]
pub enum CredentialSource {
    FilePath(PathBuf),
    #[allow(dead_code)] // macOS `security` item name (Phase: macOS support)
    Keychain(String),
    #[allow(dead_code)] // env var name (later providers)
    Env(String),
    #[allow(dead_code)] // local RPC port (Phase 4: Antigravity)
    Loopback(u16),
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WindowKind {
    Rolling,
    Calendar,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Unit {
    Percent,
    Requests,
    Credits,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Window {
    pub key: String, // "5h" | "7d" | "7d:fable" | "monthly"
    pub kind: WindowKind,
    pub unit: Unit,
    pub used_pct: f64, // always present; uniform rendering
    pub used: Option<f64>,
    pub limit: Option<f64>,
    pub unlimited: bool,
    pub resets_at: Option<DateTime<Utc>>,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Status {
    Ok,
    Stale,
    AuthMissing,
    NotDetected,
    Error,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Snapshot {
    pub provider: String,
    pub account: String, // Account.id
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub label: Option<String>,
    pub plan: Option<String>,
    pub windows: Vec<Window>,
    pub fetched_at: DateTime<Utc>,
    pub status: Status,
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub error: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub raw: Option<serde_json::Value>, // full provider response; kept in cache, stripped from --json unless --raw
}

// frozen --json envelope, v1
#[derive(Debug, Serialize)]
pub struct Output {
    pub v: u32,
    pub generated_at: DateTime<Utc>,
    pub snapshots: Vec<Snapshot>,
}