Spaces:
Paused
Paused
| #!/usr/bin/env python3 | |
| """ | |
| Summarize CodexBar local cost usage by model. | |
| Defaults to current model (most recent daily entry), or list all models. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import os | |
| import subprocess | |
| import sys | |
| from dataclasses import dataclass | |
| from datetime import date, datetime, timedelta | |
| from typing import Any, Dict, Iterable, List, Optional, Tuple | |
| def eprint(msg: str) -> None: | |
| print(msg, file=sys.stderr) | |
| def run_codexbar_cost(provider: str) -> List[Dict[str, Any]]: | |
| cmd = ["codexbar", "cost", "--format", "json", "--provider", provider] | |
| try: | |
| output = subprocess.check_output(cmd, text=True) | |
| except FileNotFoundError: | |
| raise RuntimeError("codexbar not found on PATH. Install CodexBar CLI first.") | |
| except subprocess.CalledProcessError as exc: | |
| raise RuntimeError(f"codexbar cost failed (exit {exc.returncode}).") | |
| try: | |
| payload = json.loads(output) | |
| except json.JSONDecodeError as exc: | |
| raise RuntimeError(f"Failed to parse codexbar JSON output: {exc}") | |
| if not isinstance(payload, list): | |
| raise RuntimeError("Expected codexbar cost JSON array.") | |
| return payload | |
| def load_payload(input_path: Optional[str], provider: str) -> Dict[str, Any]: | |
| if input_path: | |
| if input_path == "-": | |
| raw = sys.stdin.read() | |
| else: | |
| with open(input_path, "r", encoding="utf-8") as handle: | |
| raw = handle.read() | |
| data = json.loads(raw) | |
| else: | |
| data = run_codexbar_cost(provider) | |
| if isinstance(data, dict): | |
| return data | |
| if isinstance(data, list): | |
| for entry in data: | |
| if isinstance(entry, dict) and entry.get("provider") == provider: | |
| return entry | |
| raise RuntimeError(f"Provider '{provider}' not found in codexbar payload.") | |
| raise RuntimeError("Unsupported JSON input format.") | |
| class ModelCost: | |
| model: str | |
| cost: float | |
| def parse_daily_entries(payload: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| daily = payload.get("daily") | |
| if not daily: | |
| return [] | |
| if not isinstance(daily, list): | |
| return [] | |
| return [entry for entry in daily if isinstance(entry, dict)] | |
| def parse_date(value: str) -> Optional[date]: | |
| try: | |
| return datetime.strptime(value, "%Y-%m-%d").date() | |
| except Exception: | |
| return None | |
| def filter_by_days(entries: List[Dict[str, Any]], days: Optional[int]) -> List[Dict[str, Any]]: | |
| if not days: | |
| return entries | |
| cutoff = date.today() - timedelta(days=days - 1) | |
| filtered: List[Dict[str, Any]] = [] | |
| for entry in entries: | |
| day = entry.get("date") | |
| if not isinstance(day, str): | |
| continue | |
| parsed = parse_date(day) | |
| if parsed and parsed >= cutoff: | |
| filtered.append(entry) | |
| return filtered | |
| def aggregate_costs(entries: Iterable[Dict[str, Any]]) -> Dict[str, float]: | |
| totals: Dict[str, float] = {} | |
| for entry in entries: | |
| breakdowns = entry.get("modelBreakdowns") | |
| if not breakdowns: | |
| continue | |
| if not isinstance(breakdowns, list): | |
| continue | |
| for item in breakdowns: | |
| if not isinstance(item, dict): | |
| continue | |
| model = item.get("modelName") | |
| cost = item.get("cost") | |
| if not isinstance(model, str): | |
| continue | |
| if not isinstance(cost, (int, float)): | |
| continue | |
| totals[model] = totals.get(model, 0.0) + float(cost) | |
| return totals | |
| def pick_current_model(entries: List[Dict[str, Any]]) -> Tuple[Optional[str], Optional[str]]: | |
| if not entries: | |
| return None, None | |
| sorted_entries = sorted( | |
| entries, | |
| key=lambda entry: entry.get("date") or "", | |
| ) | |
| for entry in reversed(sorted_entries): | |
| breakdowns = entry.get("modelBreakdowns") | |
| if isinstance(breakdowns, list) and breakdowns: | |
| scored: List[ModelCost] = [] | |
| for item in breakdowns: | |
| if not isinstance(item, dict): | |
| continue | |
| model = item.get("modelName") | |
| cost = item.get("cost") | |
| if isinstance(model, str) and isinstance(cost, (int, float)): | |
| scored.append(ModelCost(model=model, cost=float(cost))) | |
| if scored: | |
| scored.sort(key=lambda item: item.cost, reverse=True) | |
| return scored[0].model, entry.get("date") if isinstance(entry.get("date"), str) else None | |
| models_used = entry.get("modelsUsed") | |
| if isinstance(models_used, list) and models_used: | |
| last = models_used[-1] | |
| if isinstance(last, str): | |
| return last, entry.get("date") if isinstance(entry.get("date"), str) else None | |
| return None, None | |
| def usd(value: Optional[float]) -> str: | |
| if value is None: | |
| return "—" | |
| return f"${value:,.2f}" | |
| def latest_day_cost(entries: List[Dict[str, Any]], model: str) -> Tuple[Optional[str], Optional[float]]: | |
| if not entries: | |
| return None, None | |
| sorted_entries = sorted( | |
| entries, | |
| key=lambda entry: entry.get("date") or "", | |
| ) | |
| for entry in reversed(sorted_entries): | |
| breakdowns = entry.get("modelBreakdowns") | |
| if not isinstance(breakdowns, list): | |
| continue | |
| for item in breakdowns: | |
| if not isinstance(item, dict): | |
| continue | |
| if item.get("modelName") == model: | |
| cost = item.get("cost") if isinstance(item.get("cost"), (int, float)) else None | |
| day = entry.get("date") if isinstance(entry.get("date"), str) else None | |
| return day, float(cost) if cost is not None else None | |
| return None, None | |
| def render_text_current( | |
| provider: str, | |
| model: str, | |
| latest_date: Optional[str], | |
| total_cost: Optional[float], | |
| latest_cost: Optional[float], | |
| latest_cost_date: Optional[str], | |
| entry_count: int, | |
| ) -> str: | |
| lines = [f"Provider: {provider}", f"Current model: {model}"] | |
| if latest_date: | |
| lines.append(f"Latest model date: {latest_date}") | |
| lines.append(f"Total cost (rows): {usd(total_cost)}") | |
| if latest_cost_date: | |
| lines.append(f"Latest day cost: {usd(latest_cost)} ({latest_cost_date})") | |
| lines.append(f"Daily rows: {entry_count}") | |
| return "\n".join(lines) | |
| def render_text_all(provider: str, totals: Dict[str, float]) -> str: | |
| lines = [f"Provider: {provider}", "Models:"] | |
| for model, cost in sorted(totals.items(), key=lambda item: item[1], reverse=True): | |
| lines.append(f"- {model}: {usd(cost)}") | |
| return "\n".join(lines) | |
| def build_json_current( | |
| provider: str, | |
| model: str, | |
| latest_date: Optional[str], | |
| total_cost: Optional[float], | |
| latest_cost: Optional[float], | |
| latest_cost_date: Optional[str], | |
| entry_count: int, | |
| ) -> Dict[str, Any]: | |
| return { | |
| "provider": provider, | |
| "mode": "current", | |
| "model": model, | |
| "latestModelDate": latest_date, | |
| "totalCostUSD": total_cost, | |
| "latestDayCostUSD": latest_cost, | |
| "latestDayCostDate": latest_cost_date, | |
| "dailyRowCount": entry_count, | |
| } | |
| def build_json_all(provider: str, totals: Dict[str, float]) -> Dict[str, Any]: | |
| return { | |
| "provider": provider, | |
| "mode": "all", | |
| "models": [ | |
| {"model": model, "totalCostUSD": cost} | |
| for model, cost in sorted(totals.items(), key=lambda item: item[1], reverse=True) | |
| ], | |
| } | |
| def main() -> int: | |
| parser = argparse.ArgumentParser(description="Summarize CodexBar model usage from local cost logs.") | |
| parser.add_argument("--provider", choices=["codex", "claude"], default="codex") | |
| parser.add_argument("--mode", choices=["current", "all"], default="current") | |
| parser.add_argument("--model", help="Explicit model name to report instead of auto-current.") | |
| parser.add_argument("--input", help="Path to codexbar cost JSON (or '-' for stdin).") | |
| parser.add_argument("--days", type=int, help="Limit to last N days (based on daily rows).") | |
| parser.add_argument("--format", choices=["text", "json"], default="text") | |
| parser.add_argument("--pretty", action="store_true", help="Pretty-print JSON output.") | |
| args = parser.parse_args() | |
| try: | |
| payload = load_payload(args.input, args.provider) | |
| except Exception as exc: | |
| eprint(str(exc)) | |
| return 1 | |
| entries = parse_daily_entries(payload) | |
| entries = filter_by_days(entries, args.days) | |
| if args.mode == "current": | |
| model = args.model | |
| latest_date = None | |
| if not model: | |
| model, latest_date = pick_current_model(entries) | |
| if not model: | |
| eprint("No model data found in codexbar cost payload.") | |
| return 2 | |
| totals = aggregate_costs(entries) | |
| total_cost = totals.get(model) | |
| latest_cost_date, latest_cost = latest_day_cost(entries, model) | |
| if args.format == "json": | |
| payload_out = build_json_current( | |
| provider=args.provider, | |
| model=model, | |
| latest_date=latest_date, | |
| total_cost=total_cost, | |
| latest_cost=latest_cost, | |
| latest_cost_date=latest_cost_date, | |
| entry_count=len(entries), | |
| ) | |
| indent = 2 if args.pretty else None | |
| print(json.dumps(payload_out, indent=indent, sort_keys=args.pretty)) | |
| else: | |
| print( | |
| render_text_current( | |
| provider=args.provider, | |
| model=model, | |
| latest_date=latest_date, | |
| total_cost=total_cost, | |
| latest_cost=latest_cost, | |
| latest_cost_date=latest_cost_date, | |
| entry_count=len(entries), | |
| ) | |
| ) | |
| return 0 | |
| totals = aggregate_costs(entries) | |
| if not totals: | |
| eprint("No model breakdowns found in codexbar cost payload.") | |
| return 2 | |
| if args.format == "json": | |
| payload_out = build_json_all(provider=args.provider, totals=totals) | |
| indent = 2 if args.pretty else None | |
| print(json.dumps(payload_out, indent=indent, sort_keys=args.pretty)) | |
| else: | |
| print(render_text_all(provider=args.provider, totals=totals)) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |