
Model Usage
- 3.2k installs
- 385k repo stars
- Updated August 3, 2026
- steipete/clawdis
model-usage is a skill that summarizes CodexBar local cost logs by model for Codex or Claude in current or all-models modes.
About
Model Usage is a macOS-oriented skill for reading per-model AI usage costs from CodexBar local cost logs. It supports current model mode using the most recent daily row with modelBreakdowns and picking the highest-cost model, or all models mode for full summaries. Providers include codex and claude. Default input runs codexbar cost --format json --provider, while file or stdin input via --input works on any platform with exported JSON. The bundled scripts/model_usage.py handles summarization with optional --model override, text or JSON --format output, and --pretty printing. Current model logic falls back to the last entry in modelsUsed when breakdowns are missing. CodexBar CLI live reads are documented for macOS with brew cask install steipete/tap/codexbar; Linux and other platforms should pass exported JSON until a supported local install path exists. Output values are cost-only per model because CodexBar does not split tokens by model. References/codexbar-cli.md documents CLI flags and cost JSON fields for deeper inspection.
- Summarizes CodexBar per-model costs for codex or claude providers.
- Modes: current highest-cost model or all-models full breakdown.
- Bundled model_usage.py supports live CLI, file, or stdin JSON input.
- macOS live reads via codexbar cost; other platforms use --input exports.
- Output is cost-only; CodexBar JSON does not split tokens per model.
Model Usage by the numbers
- 3,177 all-time installs (skills.sh)
- +156 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #174 of 3,280 Productivity & Planning skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 3, 2026 (Skillselion catalog sync)
model-usage capabilities & compatibility
- Capabilities
- current model and all models cost summarization · codex and claude provider selection · live codexbar cli or input file and stdin json · text and json formatted output via model_usage.p
- Use cases
- devops · planning
- Platforms
- macOS
What model-usage says it does
Get per-model usage cost from CodexBar's local cost logs
Values are cost-only per model; tokens are not split by model in CodexBar output
npx skills add https://github.com/steipete/clawdis --skill model-usageAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3.2k |
|---|---|
| repo stars | ★ 385k |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 3, 2026 |
| Repository | steipete/clawdis ↗ |
How do I see per-model AI usage costs from my local CodexBar logs for Codex or Claude?
Summarize CodexBar local cost logs by model for Codex or Claude with current or full breakdowns via bundled Python script.
Who is it for?
macOS developers with CodexBar installed who want quick per-model cost breakdowns from local logs.
Skip if: Skip when you need token-level splits by model; CodexBar output is cost-only without per-model token counts.
When should I use this skill?
User asks for CodexBar cost summary, per-model usage, or current versus all-model spend breakdown.
What you get
Text or JSON summary of per-model costs from the latest daily row or full history via model_usage.py.
- per-model cost summary text or JSON
By the numbers
- Requires codexbar binary installed via Homebrew cask steipete/tap/codexbar
- macOS-only per openclaw metadata os darwin constraint
Files
Model usage
Overview
Get per-model usage cost from CodexBar's local cost logs. Supports "current model" (most recent daily entry) or "all models" summaries for Codex or Claude.
Live CodexBar CLI invocation is currently documented for macOS only. The bundled Python summarizer is portable: if you already have exported CodexBar JSON, --input mode works anywhere Python is available.
Quick start
1. Fetch cost JSON via CodexBar CLI or pass a JSON file. 2. Use the bundled script to summarize by model.
python {baseDir}/scripts/model_usage.py --provider codex --mode current
python {baseDir}/scripts/model_usage.py --provider codex --mode all
python {baseDir}/scripts/model_usage.py --provider claude --mode all --format json --prettyCurrent model logic
- Uses the most recent daily row with
modelBreakdowns. - Picks the model with the highest cost in that row.
- Falls back to the last entry in
modelsUsedwhen breakdowns are missing. - Override with
--model <name>when you need a specific model.
Inputs
- Default: runs
codexbar cost --format json --provider <codex|claude>. - macOS: use the bundled CodexBar CLI install path above for live local usage reads.
- Linux/other platforms: use
--inputwith exported CodexBar JSON until this skill documents a supported local CodexBar install path for that platform. - File or stdin:
codexbar cost --provider codex --format json > /tmp/cost.json
python {baseDir}/scripts/model_usage.py --input /tmp/cost.json --mode all
cat /tmp/cost.json | python {baseDir}/scripts/model_usage.py --input - --mode currentOutput
- Text (default) or JSON (
--format json --pretty). - Values are cost-only per model; tokens are not split by model in CodexBar output.
References
- Read
references/codexbar-cli.mdfor CLI flags and cost JSON fields.
CodexBar CLI quick ref (usage + cost)
Install
- App: Preferences -> Advanced -> Install CLI
- Repo: ./bin/install-codexbar-cli.sh
Commands
- Usage snapshot (web/cli sources):
- codexbar usage --format json --pretty
- codexbar --provider all --format json
- Local cost usage (Codex + Claude only):
- codexbar cost --format json --pretty
- codexbar cost --provider codex|claude --format json
Cost JSON fields
The payload is an array (one per provider).
- provider, source, updatedAt
- sessionTokens, sessionCostUSD
- last30DaysTokens, last30DaysCostUSD
- daily[]: date, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, totalTokens, totalCost, modelsUsed, modelBreakdowns[]
- modelBreakdowns[]: modelName, cost
- totals: totalInputTokens, totalOutputTokens, cacheReadTokens, cacheCreationTokens, totalTokens, totalCost
Notes
- Cost usage is local-only. It reads JSONL logs under:
- Codex: ~/.codex/sessions/\*_/_.jsonl
- Claude: ~/.config/claude/projects/*/\.jsonl or ~/.claude/projects/*/\.jsonl
- If web usage is required (non-local), use codexbar usage (not cost).
#!/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 subprocess
import sys
from dataclasses import dataclass
from datetime import date, datetime, timedelta
from typing import Any, Dict, Iterable, List, Optional, Tuple
def positive_int(value: str) -> int:
try:
parsed = int(value)
except ValueError as exc:
raise argparse.ArgumentTypeError("must be an integer") from exc
if parsed < 1:
raise argparse.ArgumentTypeError("must be >= 1")
return parsed
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.")
@dataclass
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=positive_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())
#!/usr/bin/env python3
"""
Tests for model_usage helpers.
"""
import argparse
from datetime import date, timedelta
from unittest import TestCase, main
from model_usage import filter_by_days, positive_int
class TestModelUsage(TestCase):
def test_positive_int_accepts_valid_numbers(self):
self.assertEqual(positive_int("1"), 1)
self.assertEqual(positive_int("7"), 7)
def test_positive_int_rejects_zero_and_negative(self):
with self.assertRaises(argparse.ArgumentTypeError):
positive_int("0")
with self.assertRaises(argparse.ArgumentTypeError):
positive_int("-3")
def test_filter_by_days_keeps_recent_entries(self):
today = date.today()
entries = [
{"date": (today - timedelta(days=5)).strftime("%Y-%m-%d"), "modelBreakdowns": []},
{"date": (today - timedelta(days=1)).strftime("%Y-%m-%d"), "modelBreakdowns": []},
{"date": today.strftime("%Y-%m-%d"), "modelBreakdowns": []},
]
filtered = filter_by_days(entries, 2)
self.assertEqual(len(filtered), 2)
self.assertEqual(filtered[0]["date"], (today - timedelta(days=1)).strftime("%Y-%m-%d"))
self.assertEqual(filtered[1]["date"], today.strftime("%Y-%m-%d"))
if __name__ == "__main__":
main()
Related skills
How it compares
Pick model-usage over cloud billing dashboards when you already run CodexBar locally and want instant per-model totals from session logs without API credentials.
FAQ
Does this work off macOS?
Live codexbar CLI reads are macOS-only; use --input with exported JSON on other platforms.
How is current model chosen?
It uses the most recent daily row with modelBreakdowns and picks the highest-cost model, with --model override available.
Are token counts included per model?
No. Output values are cost-only; tokens are not split by model in CodexBar output.
Is Model Usage safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.