Claude Code Usage A developer created a Python script called claude-usage.py that captures the output of the /usage command from the Claude CLI. The script uses a pseudo-terminal to launch Claude, send the command, and render the screen output, then parses usage reset times into ISO 8601 timestamps. | /usr/bin/env -S uv run | | /// script | | dependencies = "pyte", "tzdata" | | /// | | | | """ | | claude-usage.py - Captures the output of the /usage command from the Claude CLI. | | | | Drives the interactive claude TUI through a pseudo-terminal PTY : launches it, | | sends /usage , snapshots the rendered screen, then shuts the process down cleanly. | | | | Usage: | | uv run claude-usage.py | | | | Steps performed: | | 1. Launch claude attached to a PTY so it behaves as an interactive session. | | 2. Wait for it to reach the input prompt. | | 3. Type /usage and press ENTER. | | 4. Wait, then capture the rendered screen. | | 5. Shut down: ESC - wait - /exit ENTER - wait - kill if still alive. | | 6. Print the captured screen from step 4. | | """ | | | | from future import annotations | | | | import argparse | | import fcntl | | import json | | import os | | import re | | import select | | import shutil | | import signal | | import struct | | import sys | | import termios | | import threading | | import time | | from datetime import datetime, timedelta | | from zoneinfo import ZoneInfo | | | | import pyte | | | | CLAUDE COMMAND = "claude" | | | | TERM ROWS = 150 | | TERM COLS = 160 | | | | WAIT AFTER START = 3.0 | | WAIT AFTER USAGE = 3.0 | | WAIT AFTER ESC = 2.0 | | WAIT AFTER EXIT = 3.0 | | TYPE DELAY = 0.4 | | | | ENTER = b"\r" | | ESC = b"\x1b" | | | | | | VERBOSE = True | | | | | | def log message: str - None: | | if VERBOSE: | | print f" claude-usage {message}", file=sys.stderr, flush=True | | | | | | def require command name: str - None: | | if shutil.which name is None: | | print f"{name} is not installed or not on PATH", file=sys.stderr | | sys.exit 1 | | | | | | def set window size fd: int, rows: int, cols: int - None: | | winsize = struct.pack "HHHH", rows, cols, 0, 0 | | fcntl.ioctl fd, termios.TIOCSWINSZ, winsize | | | | | | def render screen screen: pyte.Screen - str: | | lines = line.rstrip for line in screen.display | | while lines and not lines -1 : | | lines.pop | | return "\n".join lines | | | | | | MONTHS = { | | "jan": 1, "feb": 2, "mar": 3, "apr": 4, "may": 5, "jun": 6, | | "jul": 7, "aug": 8, "sep": 9, "oct": 10, "nov": 11, "dec": 12, | | } | | | | | | def parse clock text: str - tuple int, int : | | """Parses a clock string like '7:30pm', '4am' or '12pm' into hour, minute .""" | | match = re.match r"^ \d{1,2} ?:: \d{2} ?\s ap m $", text.strip .lower | | if not match: | | raise ValueError f"Unrecognized time format: {text r}" | | hour = int match.group 1 | | minute = int match.group 2 if match.group 2 else 0 | | meridiem = match.group 3 | | if meridiem == "pm" and hour = 12: | | hour += 12 | | elif meridiem == "am" and hour == 12: | | hour = 0 | | return hour, minute | | | | | | def parse reset text: str - str | None: | | """Resolves a reset string into an ISO 8601 timestamp. | | | | Accepts 'time Zone ' next occurrence of that time or | | 'Month Day at time Zone ' next occurrence of that date . | | """ | | if not text: | | return None | | | | tz match = re.search r"\ ^ + \ \s $", text | | try: | | tz = ZoneInfo tz match.group 1 if tz match else ZoneInfo "UTC" | | except Exception: | | tz = ZoneInfo "UTC" | | | | body = re.sub r"\s \ ^ +\ \s $", "", text .strip | | now = datetime.now tz | | | | dated = re.match r"^ A-Za-z + \s+ \d+ \s+at\s+ .+ $", body | | if dated: | | month = MONTHS.get dated.group 1 :3 .lower | | day = int dated.group 2 | | hour, minute = parse clock dated.group 3 | | if month is None: | | return None | | dt = datetime now.year, month, day, hour, minute, tzinfo=tz | | if dt < now: | | dt = dt.replace year=now.year + 1 | | return dt.isoformat | | | | try: | | hour, minute = parse clock body | | except ValueError: | | return None | | dt = now.replace hour=hour, minute=minute, second=0, microsecond=0 | | if dt <= now: | | dt += timedelta days=1 | | return dt.isoformat | | | | | | def parse usage block lines: list str , label: str - dict | None: | | """Finds a usage label and extracts its '