Monitor Claude / Codex 5-hour quota windows and notify on reset.
Project description
quota-monitor
Monitor Claude / Codex 5-hour quota windows and notify on reset. Zero-dependency Python, optional Cloudflare relay.
Setup with your AI Agent (recommended)
This tool assumes you have a local AI agent (Claude Code / Codex / Gemini CLI / OpenClaw / Hermes). The smoothest path is to let it prepare the outside world before you run the wizard.
Step 1 — Paste this prompt to your AI agent:
I want to install
franksde/quota-monitorfrom https://github.com/franksde/quota-monitor on macOS. Read the project README first. Before installing anything, explain the setup choices to me and ask which path I want:
- Notification mode
- Telegram direct: free and simple, but only works while my Mac can run the scheduled job.
- macOS native: local fallback only; it cannot notify me when the Mac is asleep or off.
- Cloudflare relay: optional cloud-delayed Telegram delivery. It keeps reset notifications working even if my Mac sleeps or shuts down, but it requires a Cloudflare account,
wrangler login, and a few extra setup minutes. Tell me whether it is free for normal personal use and roughly how long it should take.- Precise Claude tracking
- Explain statusLine tracking: better reset times when Claude Code provides rate-limit data, with local-only cache files.
- Tell me it can wrap an existing statusLine tool without changing its display.
- Keepalive
- Explain that keepalive is optional, off by default, needs
claudeCLI andtmux, does not work while macOS sleeps, and may conflict with provider usage policies.- Ask explicitly before enabling it.
- Prerequisites
- Python >= 3.11 or Homebrew Python.
- Telegram bot token + chat_id if I choose Telegram or Cloudflare relay.
- Cloudflare account +
wrangleronly if I choose Cloudflare relay. Ask whether I already have an account; if not, explain signup, expected time, and normal free-tier expectations before continuing.- Node.js/npm only if needed to install
wrangler.claudeCLI andtmuxonly if I choose keepalive.- LaunchAgent is the recommended scheduler on macOS.
For every dependency, tell me why it is needed and whether it can be skipped. Do not run
brew,npm, orwranglercommands until I confirm the mode. After I choose the mode, install only the dependencies required for that mode, explain what you changed, and then tell me to run: quota-monitor setup
Step 2 — Run the wizard
quota-monitor setup
Security note
If you give your AI agent a Cloudflare API token, use a scoped token (Workers + KV only), not your Global API Key. Revoke after.
StatusLine Precise Usage Tracking (Optional)
quota-monitor can read quota data from Claude Code via its statusLine mechanism. When the statusLine payload contains valid rate_limits, this gives you precise 5-hour and 7-day usage percentages and exact reset times — without making any extra API calls.
How it works
-
The setup wizard configures a lightweight Python wrapper as your Claude Code statusLine command
-
Each time Claude Code updates its status bar, the wrapper:
- Extracts
rate_limitsfrom the JSON payload - Writes it to a local cache (
~/.quota-monitor/rate_limits_cache.json) - Forwards everything to your original statusLine tool (if any)
- Returns the original output unchanged
- Extracts
-
When quota-monitor runs its periodic scan, it resolves precise data through a priority chain:
- Own wrapper cache (above) — fastest when Claude Code's statusline payload carries fresh
rate_limits claude-hudcache — if you run claude-hud, we read its.usage-cache.json(it polls Anthropic's OAuth usage API every 5 min, so it's accurate when our own cache goes stale due to cc switch / third-party routing)oh-my-claudecache — same idea, different schema; we read its.usage-cache-anthropic.json- Replay-window estimate — last-resort heuristic from local jsonl timestamps (hour-level error in third-party scenarios)
Adding a new HUD adapter is ~50 lines; see
docs/adding-hud-adapter.md. - Own wrapper cache (above) — fastest when Claude Code's statusline payload carries fresh
Compatibility
The wrapper is designed to work alongside existing statusLine tools:
- Open Island — detected and wrapped automatically
- Claude HUD — detected and wrapped automatically
- ccstatusline — detected and wrapped automatically
- Custom scripts — any existing
statusLineconfig is preserved
Manual install/uninstall
# Install (also available via `quota-monitor setup`)
quota-monitor statusline install
# Uninstall (also part of `quota-monitor uninstall`)
quota-monitor statusline uninstall
Precise vs Estimated notifications
- Precise (from statusLine): triggers when
five_hour.used_percentagereachesprobes.claude.precise_threshold_percent(default30), then uses the exact statusLine reset time: "Quota resets at 15:30" - Estimated (from local logs): "Quota resets at 15:30 (estimated from local conversation logs)"
When the estimated reset can be wrong
The estimated path infers the 5-hour window boundary from local ~/.claude/projects/**/*.jsonl timestamps. That works when Claude Code is the only thing burning your Anthropic quota. It can drift if either is true:
- You use a third-party model router (e.g.
cc switch). Those calls write local jsonl entries that look like API calls, but they never reach Anthropic and don't shift Anthropic's 5-hour window. Conversely, the call that actually started Anthropic's current window may never appear locally. - You also use claude.ai web chat. Web messages count toward the same 5-hour quota but are not written to any local file.
In either case, the probe is guessing. Measured drift on a real cc-switch install: roughly 60 minutes off when activity is continuous; up to ~4 hours off shortly after the actual server-side reset (replay can't see the reset event, so it keeps extending an already-stale window). For Anthropic-direct usage, keep statusLine installed: it caches the precise reset time from Claude Code's own rate-limit headers and the probe trusts that when available. For third-party routing or cc switch workflows, install a supported HUD tool that polls usage; otherwise quota-monitor may fall back to estimation.
Accuracy model
quota-monitor resolves reset times through a priority chain. Accuracy depends on which data source is available:
| Scenario | Source used | Accuracy |
|---|---|---|
Claude Code direct to Anthropic, fresh rate_limits in statusLine payload |
statusLine wrapper cache | precise |
Claude Code direct to Anthropic, no rate_limits yet |
HUD cache or replay fallback | depends |
| cc switch / third-party routing + HUD installed | HUD cache (e.g. claude-hud) | precise |
| cc switch / third-party routing, no HUD | replay_windows heuristic | estimated (hour-level error) |
Wrapper cache with expired resets_at |
ignored — falls back to next source | depends |
The statusLine wrapper is a fast path, not an authoritative source. It passively caches rate_limits from Claude Code's statusLine stdin; it does not call the Anthropic API. If the payload does not carry fresh data, the cache may be stale. quota-monitor does not currently implement direct Anthropic OAuth usage API polling.
Quickstart
Install via Homebrew:
brew install franksde/tap/quota-monitor
Then run the setup wizard to configure and install the LaunchAgent:
quota-monitor setup
What it does
quota-monitor scans local Claude activity logs and Codex usage metadata, derives the current quota window, and sends a notification when a reset should be actionable. It keeps only small state markers such as "already alerted for this reset", never conversation content.
Codex usage fetches are self-throttling: request frequency adapts to distance from the alert threshold, backing off to 10-20 minutes when usage is low and reusing cached data after the threshold is reached until the current window resets.
Optional features:
- Telegram direct notifications.
- macOS native notification fallback.
- Cloudflare relay for delayed delivery when your laptop may be asleep.
- Claude keepalive strategies, disabled by default.
How it works (in 30 seconds)
load_config()reads TOML +.env. Schema validation failure exits with a clear setup hint.load_state()reads~/.quota-monitor/state.json. Corruption resets to defaults and logs a warning.- Enabled probes collect timestamps or usage metadata. A single probe failure logs a warning and the rest continue.
core.window.replay_windows()derives the latest 5-hour window from timestamps alone. State never participates in window slicing.decide_alerts()emits alert decisions only for reset points that have not already been notified.dispatch_alert()sends to the primary notifier with retries, then fallback. State is updated only after a notifier succeeds.- Optional keepalive logic runs when explicitly enabled.
Safety & privacy
- Zero runtime dependencies: Python stdlib only.
- Local-first: config, secrets, and state live under
~/.quota-monitor/. - Secrets live in
.envor Cloudflare Worker secrets; they should never be committed. - Claude probe code reads metadata and timestamps from local files. It does not upload conversation content.
- Cloudflare relay is optional. If enabled, only scheduled alert payloads are sent to your Worker.
Cost
- Local mode costs $0.
- Telegram Bot API is free for normal personal usage.
- Cloudflare relay uses Workers + Queues + a tiny KV. CF Queues free tier allows 1M operations/month; each alert consumes 3 ops (send + deliver + ack), so heavy use (10 alerts/day) is ~900 ops/month. Workers Free allows 100k requests/day. Workers KV is used only as a schedule tombstone (≤10 ops/day, no
listoperations) so all KV free-tier limits are effectively non-binding. - The relay is event-driven via CF Queues
delaySeconds— no cron, no polling. Old quota-monitor versions ran a 3-minute cron that called KVlistand could trigger Cloudflare's "50% usage warning" email; that design has been replaced.
Choose your notification channel
| Channel | Best for | Tradeoff |
|---|---|---|
| Telegram direct | Most users | Requires bot token + chat id |
| macOS native | Local fallback while you are at the Mac | Cannot notify while the machine is asleep |
| Cloudflare relay | Delayed reset notification even if laptop is off | Requires wrangler, Cloudflare account, Worker + Queue + (optional) KV |
Recommended default: Telegram direct with macOS native fallback. Use Cloudflare relay if you care about delayed delivery while the laptop is not running.
Adding a channel we don't support
Give this prompt to your AI agent:
Add a new quota-monitor notifier named <name>. Read docs/adding-notifier.md.
Implement a Notifier class with name and send(Alert) -> None, wire it into
cli/run.py and cli/notify_test.py, add setup wizard options if appropriate,
and add pytest contract coverage following the Telegram notifier tests.
Advanced: Cloudflare relay
The wizard can deploy the relay automatically when you pick cloudflare_relay. Manual instructions live in docs/cloudflare.md and cloudflare-relay/README.md.
The relay exposes:
POST /api/schedule: enqueue a Telegram message into CF Queues withdelaySeconds = reset_time_epoch - now. Optionally accepts aschedule_idso a later schedule for the same id supersedes the earlier one (KV tombstone, seecloudflare-relay/README.mdfor details).- CF Queue consumer: at the scheduled time, checks the tombstone (if KV is bound) and delivers to Telegram, or silently ack-drops if superseded.
- Generic webhook handling for Claude status-style payloads.
⚠️ Risks
Keepalive (auto-renew Claude 5h window)
- Default: off.
- ToS: Anthropic's AUP discourages automated usage. Enable at your own risk; at scale this may invite attention or risk your account.
- Sleep limitation: keepalive stops working while macOS sleeps
(lid closed / idle sleep). Both
pollingandseamlessstrategies fail under sleep. The natural 5h reset will still happen — keepalive cannot save it. - Workarounds: desktop / always-on machine,
caffeinate -i, or accept the natural reset.
Keepalive content randomization — what it does and doesn't
We randomize keepalive content from a 10-phrase pool so no single message appears more than once per 10 keepalives. This neutralizes content-keyword detection.
It does NOT defeat pattern-based detection — request timing, token volume, session shape, and model choice are stronger signals than content. Use keepalive understanding any automated keep-window-alive behavior is detectable in principle.
Secret leak
- Never commit
.env. Rotate Telegram tokens via @BotFather if leaked.
Platform support
| Platform | Status |
|---|---|
| macOS | Supported target for v1 |
| Linux | Best-effort; use docs/linux-systemd.md |
| Windows | Not supported |
Troubleshooting
config file not found: runpython3.11 -m quota_monitor setup.- Telegram credentials missing: check
~/.quota-monitor/.env. - No notification on second run: expected when the same reset was already alerted.
- Corrupt state: quota-monitor self-heals to default state and logs a warning.
- LaunchAgent did not load: run
launchctl load -w ~/Library/LaunchAgents/io.github.frank.quotamonitor.plistmanually and inspect stderr. - Cloudflare deploy failed: run
wrangler whoami,wrangler tail, and seedocs/cloudflare.md.
Architecture
See docs/ARCHITECTURE.md.
Core rule: the quota window is derived by Full Replay from probe timestamps on every tick. state.json stores notification dedupe markers only; it does not store window start or reset values.
License
MIT. See LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file quota_monitor-0.2.6.tar.gz.
File metadata
- Download URL: quota_monitor-0.2.6.tar.gz
- Upload date:
- Size: 83.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7d050c7dbe47b9365ba9982e66c1029868b8294486a67c47517aa8a49861a998
|
|
| MD5 |
2666db668996053d1db1d8b3c4637cb0
|
|
| BLAKE2b-256 |
27d7af3116eda7c05ce72e5e4c8ec45ae4ecdb68f5a2a2645a2199679b31fca6
|
Provenance
The following attestation bundles were made for quota_monitor-0.2.6.tar.gz:
Publisher:
release.yml on franksde/quota-monitor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
quota_monitor-0.2.6.tar.gz -
Subject digest:
7d050c7dbe47b9365ba9982e66c1029868b8294486a67c47517aa8a49861a998 - Sigstore transparency entry: 1565893122
- Sigstore integration time:
-
Permalink:
franksde/quota-monitor@6f1c7e22fa8bdc5b95b302e3bd706f5283463594 -
Branch / Tag:
refs/tags/v0.2.6 - Owner: https://github.com/franksde
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6f1c7e22fa8bdc5b95b302e3bd706f5283463594 -
Trigger Event:
push
-
Statement type:
File details
Details for the file quota_monitor-0.2.6-py3-none-any.whl.
File metadata
- Download URL: quota_monitor-0.2.6-py3-none-any.whl
- Upload date:
- Size: 63.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e40c1e6af33579f65cb21b27b59af142c83388ad5cd3b56bea905a0e9cbd9e5
|
|
| MD5 |
38fd5cc3f075d4ecb27389cf1e0f27aa
|
|
| BLAKE2b-256 |
b12a8c86cf6639d4927de98b43a00ba18adbd07cfad2ad22d00acdfd4d97495a
|
Provenance
The following attestation bundles were made for quota_monitor-0.2.6-py3-none-any.whl:
Publisher:
release.yml on franksde/quota-monitor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
quota_monitor-0.2.6-py3-none-any.whl -
Subject digest:
6e40c1e6af33579f65cb21b27b59af142c83388ad5cd3b56bea905a0e9cbd9e5 - Sigstore transparency entry: 1565893288
- Sigstore integration time:
-
Permalink:
franksde/quota-monitor@6f1c7e22fa8bdc5b95b302e3bd706f5283463594 -
Branch / Tag:
refs/tags/v0.2.6 - Owner: https://github.com/franksde
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6f1c7e22fa8bdc5b95b302e3bd706f5283463594 -
Trigger Event:
push
-
Statement type: