|
2 | 2 | """ |
3 | 3 | quota_status.py — run manually to check today's token usage |
4 | 4 | Usage: python3 quota_status.py |
5 | | - TOKEN_QUOTA_DAILY=1000000 python3 quota_status.py |
| 5 | + TOKEN_QUOTA_DAILY=1000000 TOKEN_QUOTA_WEEKLY=5000000 python3 quota_status.py |
6 | 6 | """ |
7 | 7 |
|
8 | 8 | import json |
9 | 9 | import os |
10 | | -from datetime import date |
| 10 | +from datetime import date, timedelta |
11 | 11 | from pathlib import Path |
12 | 12 |
|
13 | 13 | LEDGER_DIR = Path(os.environ.get("TOKEN_QUOTA_DIR", Path.home() / ".claude-token-quota")) |
14 | 14 | DAILY_LIMIT = int(os.environ.get("TOKEN_QUOTA_DAILY", 1_000_000)) |
15 | 15 |
|
16 | | -def main(): |
17 | | - ledger_file = LEDGER_DIR / f"{date.today().isoformat()}.json" |
| 16 | +_weekly_raw = os.environ.get("TOKEN_QUOTA_WEEKLY") |
| 17 | +WEEKLY_LIMIT = int(_weekly_raw) if _weekly_raw else None |
18 | 18 |
|
19 | | - if not ledger_file.exists(): |
20 | | - print(f"No usage recorded today ({date.today().isoformat()}).") |
21 | | - print(f"Daily limit: {DAILY_LIMIT:,} tokens") |
22 | | - return |
| 19 | +_monthly_raw = os.environ.get("TOKEN_QUOTA_MONTHLY") |
| 20 | +MONTHLY_LIMIT = int(_monthly_raw) if _monthly_raw else None |
23 | 21 |
|
24 | | - data = json.loads(ledger_file.read_text()) |
25 | | - used = data.get("total_tokens", 0) |
26 | | - remaining = max(0, DAILY_LIMIT - used) |
27 | | - pct = (used / DAILY_LIMIT) * 100 if DAILY_LIMIT > 0 else 0 |
28 | | - sessions = data.get("sessions", []) |
29 | 22 |
|
30 | | - bar_width = 30 |
31 | | - filled = int(bar_width * pct / 100) |
32 | | - bar = "█" * filled + "░" * (bar_width - filled) |
| 23 | +def get_period_total(start: date, end: date) -> int: |
| 24 | + total = 0 |
| 25 | + current = start |
| 26 | + while current <= end: |
| 27 | + p = LEDGER_DIR / f"{current.isoformat()}.json" |
| 28 | + if p.exists(): |
| 29 | + try: |
| 30 | + data = json.loads(p.read_text()) |
| 31 | + total += data.get("total_tokens", 0) |
| 32 | + except Exception: |
| 33 | + pass |
| 34 | + current += timedelta(days=1) |
| 35 | + return total |
33 | 36 |
|
| 37 | + |
| 38 | +def _render_bar(used: int, limit: int) -> tuple[str, float, str]: |
| 39 | + pct = (used / limit) * 100 if limit > 0 else 0 |
| 40 | + filled = min(30, int(30 * pct / 100)) |
| 41 | + bar = "█" * filled + "░" * (30 - filled) |
34 | 42 | status = "✅ OK" if pct < 80 else ("⚠️ WARNING" if pct < 100 else "🚫 EXCEEDED") |
| 43 | + return bar, pct, status |
| 44 | + |
| 45 | + |
| 46 | +def main(): |
| 47 | + today = date.today() |
35 | 48 |
|
36 | 49 | print(f"\n{'─'*50}") |
37 | | - print(f" Claude Code Token Quota — {date.today().isoformat()}") |
| 50 | + print(f" Claude Code Token Quota — {today.isoformat()}") |
38 | 51 | print(f"{'─'*50}") |
39 | | - print(f" [{bar}] {pct:.1f}%") |
40 | | - print(f" Used: {used:>12,} tokens") |
41 | | - print(f" Remaining: {remaining:>12,} tokens") |
42 | | - print(f" Limit: {DAILY_LIMIT:>12,} tokens") |
43 | | - print(f" Status: {status}") |
44 | | - print(f" Turns: {len(sessions)}") |
45 | | - if sessions: |
46 | | - print(f" Last turn: {sessions[-1]['timestamp']}") |
47 | | - print(f"{'─'*50}\n") |
| 52 | + |
| 53 | + ledger_file = LEDGER_DIR / f"{today.isoformat()}.json" |
| 54 | + if not ledger_file.exists(): |
| 55 | + print(f" No usage recorded today ({today.isoformat()}).") |
| 56 | + print(f" Daily limit: {DAILY_LIMIT:,} tokens") |
| 57 | + sessions = [] |
| 58 | + else: |
| 59 | + data = json.loads(ledger_file.read_text()) |
| 60 | + used = data.get("total_tokens", 0) |
| 61 | + remaining = max(0, DAILY_LIMIT - used) |
| 62 | + sessions = data.get("sessions", []) |
| 63 | + bar, pct, status = _render_bar(used, DAILY_LIMIT) |
| 64 | + |
| 65 | + print(f" [{bar}] {pct:.1f}%") |
| 66 | + print(f" Used: {used:>12,} tokens") |
| 67 | + print(f" Remaining: {remaining:>12,} tokens") |
| 68 | + print(f" Limit: {DAILY_LIMIT:>12,} tokens") |
| 69 | + print(f" Status: {status}") |
| 70 | + print(f" Turns: {len(sessions)}") |
| 71 | + if sessions: |
| 72 | + print(f" Last turn: {sessions[-1]['timestamp']}") |
| 73 | + |
| 74 | + if WEEKLY_LIMIT is not None: |
| 75 | + week_start = today - timedelta(days=6) |
| 76 | + used_weekly = get_period_total(week_start, today) |
| 77 | + remaining_weekly = max(0, WEEKLY_LIMIT - used_weekly) |
| 78 | + bar, pct, status = _render_bar(used_weekly, WEEKLY_LIMIT) |
| 79 | + |
| 80 | + print(f"\n Weekly (rolling 7-day: {week_start.isoformat()} – {today.isoformat()})") |
| 81 | + print(f" [{bar}] {pct:.1f}%") |
| 82 | + print(f" Used: {used_weekly:>12,} tokens") |
| 83 | + print(f" Remaining: {remaining_weekly:>12,} tokens") |
| 84 | + print(f" Limit: {WEEKLY_LIMIT:>12,} tokens") |
| 85 | + print(f" Status: {status}") |
| 86 | + |
| 87 | + if MONTHLY_LIMIT is not None: |
| 88 | + month_start = today.replace(day=1) |
| 89 | + used_monthly = get_period_total(month_start, today) |
| 90 | + remaining_monthly = max(0, MONTHLY_LIMIT - used_monthly) |
| 91 | + bar, pct, status = _render_bar(used_monthly, MONTHLY_LIMIT) |
| 92 | + |
| 93 | + print(f"\n Monthly ({today.strftime('%B %Y')})") |
| 94 | + print(f" [{bar}] {pct:.1f}%") |
| 95 | + print(f" Used: {used_monthly:>12,} tokens") |
| 96 | + print(f" Remaining: {remaining_monthly:>12,} tokens") |
| 97 | + print(f" Limit: {MONTHLY_LIMIT:>12,} tokens") |
| 98 | + print(f" Status: {status}") |
| 99 | + |
| 100 | + print(f"\n{'─'*50}\n") |
| 101 | + |
48 | 102 |
|
49 | 103 | if __name__ == "__main__": |
50 | 104 | main() |
0 commit comments