Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VibeGauge

An Android home-screen widget + web dashboard that shows your AI subscription usage limits at a glance — Claude (Anthropic), Codex (ChatGPT), Droid (Factory), and DeepSeek: the 5-hour rolling window, the weekly window, reset countdowns, and remaining API balance.

Gauges are color-coded by how much of the window you've used: 🟢 green < 70% · 🟡 amber 70–90% · 🔴 red ≥ 90%.

Android widget Web dashboard
Android widget screenshot Web dashboard screenshot

Features

  • Multi-provider: Claude, Codex (ChatGPT plan), Droid (Factory), DeepSeek — any provider whose credentials are missing simply shows ; the rest still work.
  • Android widget (Kotlin / Jetpack Glance):
    • Wide → all providers side by side; small → one provider, tap ◀ ▶ to cycle.
    • Responsive size buckets incl. foldable cover-screen / inner-screen cells.
    • Dark & light themes (follows the system setting).
    • Refresh via WorkManager (~15 min), on tap, and on widget add.
  • Web dashboard (single static HTML file, zero build step) — same data, same design language, works as a browser tab or a "Mac widget" style small window.
  • Per-model weekly caps: when Anthropic exposes a model-scoped weekly limit (e.g. a per-model sub-row in their usage panel), the Claude card splits into ALL / per-model micro-bars automatically — the label comes from the API.
  • Droid Core fallback: once Factory's Standard pool is exhausted the tile flips to the Core pool (with a CORE badge) and back automatically.
  • Resilient bridge: per-provider isolation, retries, 60s cache, last-good fallback with staleness cutoff, OAuth auto-refresh for Claude and Codex.

How it works

Usage limits are per-account, not per-machine — any process logged into your Claude / ChatGPT account sees the same numbers. So a single always-on bridge running wherever you're already logged in can serve them, and the clients stay dumb: fetch one normalized JSON, render it.

[Android widget]                       ┌──> api.anthropic.com  (Claude OAuth usage)
[Web dashboard ] --HTTPS + bearer --> [bridge /usage] ──> chatgpt.com  (Codex rate-limit headers)
                                       ├──> api.factory.ai    (Droid billing limits)
                                       └──> api.deepseek.com  (balance)
Provider Source Data
Claude GET api.anthropic.com/api/oauth/usage 5h + weekly utilization %, reset times, per-model weekly cap
Codex streamed POST chatgpt.com/backend-api/codex/responses, read the x-codex-* rate-limit response headers (body aborted → ~zero quota) 5h + weekly used %, reset times
Droid GET api.factory.ai/api/billing/limits Standard + Core pools, 5h + weekly
DeepSeek GET api.deepseek.com/user/balance remaining balance

Part 1 — Run the bridge

The bridge is a single zero-dependency Python file (bridge/bridge.py, stdlib only, Python 3.8+). Run it on a machine that is always on and already logged into the CLIs you want to monitor (Claude Code, Codex). A cheap always-on VPS or home server is ideal.

1. Credentials the bridge reads

  • Claude~/.claude/.credentials.json (created by claude login). Override with CLAUDE_CREDS.
  • Codex~/.codex/auth.json (created by codex login, ChatGPT plan). Override with CODEX_AUTH.
  • DeepSeek (optional) — an API key, via env DEEPSEEK_API_KEY or a file (DEEPSEEK_KEY_FILE, default ~/.ai-usage-gauge/.deepseek_key).
  • Droid / Factory (optional) — an API key, via env FACTORY_API_KEY or a file (FACTORY_KEY_FILE, default ~/.ai-usage-gauge/.factory_key).

2. Set a shared bearer token

The widget authenticates to the bridge with a static bearer token you choose:

mkdir -p ~/.ai-usage-gauge
python3 -c "import secrets; print(secrets.token_urlsafe(32))" > ~/.ai-usage-gauge/.bridge_token
chmod 600 ~/.ai-usage-gauge/.bridge_token

Or supply it inline with the USAGE_BRIDGE_TOKEN env var.

3. Start it

python3 bridge/bridge.py          # listens on 127.0.0.1:8787

Endpoints:

  • GET /usage — bearer-authed, returns normalized JSON for all providers.
  • GET /health — no auth.

Test locally:

curl -H "Authorization: Bearer $(cat ~/.ai-usage-gauge/.bridge_token)" localhost:8787/usage

4. Keep it running (systemd)

See deploy/ for ready-to-edit systemd units and notes. In short:

# edit the unit paths first, then:
sudo cp deploy/ai-usage-bridge.service /etc/systemd/system/
sudo systemctl enable --now ai-usage-bridge

Configuration reference (env vars)

Var Default Purpose
LIMITS_BRIDGE_PORT 8787 Listen port
USAGE_STATE_DIR ~/.ai-usage-gauge Base dir for the token/key files
USAGE_BRIDGE_TOKEN Bearer token (overrides the file)
BRIDGE_TOKEN_FILE $USAGE_STATE_DIR/.bridge_token Bearer token file
CLAUDE_CREDS ~/.claude/.credentials.json Claude OAuth creds
CODEX_AUTH ~/.codex/auth.json Codex OAuth creds
DEEPSEEK_API_KEY DeepSeek key (overrides the file)
DEEPSEEK_KEY_FILE $USAGE_STATE_DIR/.deepseek_key DeepSeek key file
FACTORY_API_KEY Factory (Droid) key (overrides the file)
FACTORY_KEY_FILE $USAGE_STATE_DIR/.factory_key Factory key file
FACTORY_TIER standard Default Droid pool to surface (standard / core)
BRIDGE_ERROR_LOG $USAGE_STATE_DIR/bridge-errors.log Per-provider fetch failure log
BRIDGE_FETCH_ATTEMPTS 3 Retries per provider fetch
USAGE_LAST_GOOD_MAX_AGE 600 Seconds a stale last-good value may be served

Part 2 — Expose the bridge to your phone

The widget needs to reach the bridge over the internet. Pick one:

  • Cloudflare Tunnel (recommended, stable): requires a domain on Cloudflare. cloudflared tunnel login, create a named tunnel, route a subdomain to http://localhost:8787. Stable public HTTPS URL, no open ports.
  • Cloudflare quick tunnel (free, no domain): cloudflared tunnel --url http://localhost:8787 → prints a random *.trycloudflare.com URL. Works instantly, but the URL changes on restart. See deploy/ for a systemd unit + helpers that detect the current URL and can notify you (pluggable NOTIFY_CMD) when it rotates.
  • Tailscale / WireGuard: keep it fully private. Note Android allows only one VPN at a time, so this conflicts with other VPN/proxy apps you run.

The bridge listens on 127.0.0.1 only — it is never directly exposed; the tunnel terminates TLS and forwards to it.

Part 3 — The web dashboard

web/vibegauge.html is a single static file — open it in any browser (or host it anywhere static). On first load it asks for your bridge URL and bearer token, stored only in that browser's localStorage. No build step, no dependencies.

Part 4 — Build & install the Android app

A prebuilt debug APK is attached to each GitHub Release. To build it yourself:

Requirements: JDK 17, Android SDK (platform 35 + build-tools 35), no Android Studio needed.

cd android
echo "sdk.dir=$ANDROID_HOME" > local.properties
./gradlew :app:assembleDebug
# → app/build/outputs/apk/debug/app-debug.apk

Sideload the APK (enable "install from unknown sources"). Open VibeGauge, enter your bridge URL and bearer token, tap Save & refresh, then add the widget from your launcher's widget picker.

Headless widget screenshot tests

The repo includes a Robolectric-based render harness that rasterizes the real widget to PNGs — no device or emulator needed:

cd android
./gradlew :app:testDebugUnitTest --tests '*WidgetRenderTest'
# → writes /tmp/widget-*.png for visual inspection

Refresh cadence

  • The bridge caches upstream results for 60s (so redraws don't hammer providers).
  • The widget refreshes via WorkManager every ~15 minutes (Android's minimum periodic interval; may stretch longer under Doze/battery optimization), plus on add and on tap (tapping forces an immediate fetch).
  • Worst-case staleness ≈ up to ~15 min + up to 60s. A tap gets you data no more than ~60s old.

Security notes

  • The bridge binds to 127.0.0.1 only; expose it exclusively through your tunnel.
  • GET /usage requires the bearer token (constant-time comparison); /health is auth-free and returns no data.
  • Provider credentials never leave the bridge host and are never sent to clients — the JSON contains only percentages, timestamps, and balances.
  • On Android, the bridge URL + token are stored in EncryptedSharedPreferences.
  • On web, they live in that browser's localStorage only — never embedded in the HTML file itself.
  • No secrets belong in this repo: state files live outside the tree (see .gitignore).

Tech

Kotlin · Jetpack Glance · WorkManager · OkHttp · EncryptedSharedPreferences · Robolectric render tests · Python stdlib bridge · single-file static web UI. minSdk 26, target 35.

Contributing

Issues and PRs welcome. Useful starting points:

  • PLAN.md — original design/architecture notes and provider API findings.
  • DESIGN_SPEC.md — the widget's visual spec (size buckets, ring geometry, palette).
  • bridge/bridge.py --diagnose-claude — dumps the raw Anthropic usage response for debugging provider changes.

License

MIT — see LICENSE.