Spaces:
Running
Running
File size: 2,106 Bytes
125f9c0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | """Spend tracker — summarize this billing cycle's Modal spend for `make status`.
Reads `modal billing report --json` (usage line items) and sums their cost, then
frames it against the configured spend limit and credit budget so the $280 (expiry
Jun 30) draws down visibly instead of by guesswork.
The Modal billing report exposes *spend*, not the credit balance, so the credit
total and spend limit are passed in via env (set in .env / Makefile):
CREDIT_BUDGET total credits in the workspace (default 280)
SPEND_LIMIT the hard cap you set in the dashboard (default 50)
"""
from __future__ import annotations
import os
import json
import subprocess
def _spend_this_month() -> float | None:
try:
out = subprocess.run(
["modal", "billing", "report", "--for", "this month", "--json"],
capture_output=True, text=True, timeout=30,
)
if out.returncode != 0:
return None
items = json.loads(out.stdout or "[]")
except Exception:
return None
total = 0.0
for it in items if isinstance(items, list) else []:
# Be tolerant of the line-item schema: sum any plausible cost field.
for k in ("cost", "amount", "total", "cost_usd"):
v = it.get(k) if isinstance(it, dict) else None
if isinstance(v, (int, float)):
total += float(v)
break
return total
def main() -> None:
budget = float(os.environ.get("CREDIT_BUDGET", "280"))
limit = float(os.environ.get("SPEND_LIMIT", "50"))
spent = _spend_this_month()
if spent is None:
print("credits: (could not read billing report)")
return
bar_n = 20
frac = min(1.0, spent / limit) if limit > 0 else 0.0
bar = "█" * int(frac * bar_n) + "░" * (bar_n - int(frac * bar_n))
warn = " ⚠️ NEAR LIMIT" if frac >= 0.8 else ""
print(f"spent this cycle: ${spent:,.2f} / ${limit:,.0f} limit [{bar}]{warn}")
print(f"credits: ${budget:,.0f} total (expire Jun 30) · ${max(0, budget - spent):,.2f} left")
if __name__ == "__main__":
main()
|