Skip to content

Instantly share code, notes, and snippets.

@grahamhelton
Created April 24, 2026 17:17
Show Gist options
  • Select an option

  • Save grahamhelton/a09c40c713db821d59393ad2facc97c5 to your computer and use it in GitHub Desktop.

Select an option

Save grahamhelton/a09c40c713db821d59393ad2facc97c5 to your computer and use it in GitHub Desktop.
check claude code price if you were using API pricing instead of a max subscription
#!/usr/bin/env python3
"""Claude Code status line: API-rate cost estimate (session / today)."""
import json
import sys
import glob
import os
import datetime as dt
# USD per 1M tokens: (input, output, cache_write_5m, cache_read)
# Source: platform.claude.com/docs/en/about-claude/pricing
PRICES = {
"opus-new": (5.0, 25.0, 6.25, 0.50), # Opus 4.5 / 4.6 / 4.7
"opus-legacy": (15.0, 75.0, 18.75, 1.50), # Opus 4 / 4.1 / 3
"sonnet": (3.0, 15.0, 3.75, 0.30), # Sonnet 4 / 4.5 / 4.6
"haiku-45": (1.0, 5.0, 1.25, 0.10), # Haiku 4.5
"haiku-35": (0.80, 4.0, 1.00, 0.08), # Haiku 3.5
"haiku-3": (0.25, 1.25, 0.30, 0.03), # Haiku 3
}
def family(model):
if not model:
return None
m = model.lower()
if "opus" in m:
# Opus 4.5+ uses new pricing; 4 / 4.1 / 3 use legacy
if any(v in m for v in ("4-5", "4.5", "4-6", "4.6", "4-7", "4.7")):
return "opus-new"
return "opus-legacy"
if "sonnet" in m:
return "sonnet"
if "haiku" in m:
if "4-5" in m or "4.5" in m: return "haiku-45"
if "3-5" in m or "3.5" in m: return "haiku-35"
return "haiku-3"
return None
def display_tag(fam):
if not fam: return "cc"
return fam.split("-")[0]
def cost_of(usage: dict, model: str) -> float:
fam = family(model)
if not fam:
return 0.0
pin, pout, pcw, pcr = PRICES[fam]
inp = usage.get("input_tokens", 0) or 0
out = usage.get("output_tokens", 0) or 0
cw = usage.get("cache_creation_input_tokens", 0) or 0
cr = usage.get("cache_read_input_tokens", 0) or 0
return (inp * pin + out * pout + cw * pcw + cr * pcr) / 1_000_000.0
def iter_usage(path: str, day: dt.date | None = None):
"""Yield (usage_dict, model_str) from a JSONL transcript.
If `day` is given, only yield entries whose timestamp is on that local date."""
try:
with open(path, "r") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
obj = json.loads(line)
except Exception:
continue
msg = obj.get("message") or {}
usage = msg.get("usage") or obj.get("usage")
model = msg.get("model") or obj.get("model")
if not usage or not model:
continue
if day is not None:
ts = obj.get("timestamp") or msg.get("timestamp")
if ts:
try:
t = dt.datetime.fromisoformat(ts.replace("Z", "+00:00"))
if t.astimezone().date() != day:
continue
except Exception:
pass
yield usage, model
except FileNotFoundError:
return
def session_cost(path: str) -> tuple[float, str | None]:
total = 0.0
last_model = None
for usage, model in iter_usage(path):
total += cost_of(usage, model)
if family(model):
last_model = model
return total, last_model
def today_cost() -> float:
today = dt.date.today()
midnight = dt.datetime.combine(today, dt.time.min).timestamp()
home = os.path.expanduser("~/.claude/projects")
total = 0.0
for path in glob.glob(os.path.join(home, "*", "*.jsonl")):
try:
if os.path.getmtime(path) < midnight:
continue
except OSError:
continue
for usage, model in iter_usage(path, day=today):
total += cost_of(usage, model)
return total
def main():
try:
raw = sys.stdin.read()
info = json.loads(raw) if raw.strip() else {}
transcript = info.get("transcript_path") or ""
sess, model = session_cost(transcript) if transcript else (0.0, None)
daily = today_cost()
tag = display_tag(family(model))
print(f"{tag} ${sess:.2f} / ${daily:.2f}")
except Exception:
print("$?")
if __name__ == "__main__":
main()
@grahamhelton
Copy link
Copy Markdown
Author

image

@grahamhelton
Copy link
Copy Markdown
Author

Add the following to ~/.claude/settings.json

    "statusLine": {
      "type": "command",
      "command": "python3 /replace/with/path/to/statusline.py"
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment