Neumorphic dashboard for tracking LLM coding plan quotas — browser-scrape first, API fallback.
Developed with OpenClaw 🐾
📊 Demo: GitHub Pages (static sample data)
# Clone
git clone https://github.com/joe2643/llm-quota-dashboard.git
cd llm-quota-dashboard
# Install deps
pip install flask websocket-client
# Option A: Start Chrome with CDP manually
# chrome --remote-debugging-port=18800
#
# Option B: If you have OpenClaw, the scraper auto-starts the browser:
# openclaw browser start
# Start dashboard (auto-refreshes every 30min)
python3 web/server.py # http://localhost:8502The server automatically:
- Scrapes all providers in parallel every 30 minutes
- Starts headless Chrome if no CDP browser is detected
- Detects login issues per provider
- Serves a live-updating neumorphic UI
The scraper reads data from provider dashboards using your existing Chrome sessions. You must be logged in to each provider in Chrome before the scraper can extract data. If a provider isn't logged in, the dashboard will show a 🔑 "Need login" card.
For first-time setup:
Option A: Use OpenClaw browser (recommended)
- Install OpenClaw
- Run
openclaw browser start— this launches a persistent Chrome profile at~/.openclaw/browser/ - Log in to each provider dashboard in that browser (sessions are saved permanently)
- The scraper auto-detects this browser on port 18800 — no extra flags needed
Option B: Use your own Chrome
- Open Chrome with
--remote-debugging-port=18800 - Manually log in to each provider's dashboard
- Run the scraper — it will reuse your sessions
💡 With OpenClaw: Tell your agent to open each provider's login page in the browser, then log in interactively. The agent can guide you through each provider step by step.
You may need to enable host browser control in your OpenClaw config:
{ "agents": { "defaults": { "sandbox": { "browser": { "allowHostControl": true } } } } }
The scrapers parse page text using regex patterns. If your provider dashboard is in a different language (e.g., Chinese vs English), the regex may not match. Current scrapers support:
| Provider | Languages Tested |
|---|---|
| Z.AI | English |
| DashScope | English ✅ + Chinese (中文) ✅ — both tested |
| Anthropic | English |
| Kimi | English ✅ + Chinese (中文) ✅ — both tested |
| MiniMax | English |
If your dashboard renders in a different language, you'll need to update the regex patterns in the scraper function. Ask your OpenClaw agent — it can inspect the page text and adapt the patterns.
- Regular Chrome (recommended): Uses your profile with saved logins. Sessions persist across restarts. All providers work.
- Headless Chrome (auto-fallback): Copies your Chrome profile cookies for session reuse. However, many providers don't work in headless mode due to stricter auth:
| Provider | Headless Support |
|---|---|
| Z.AI | ✅ Works (cookie auth) |
| Kimi | ✅ Works (cookie auth) |
| DashScope | ❌ Requires full browser session |
| Anthropic | ❌ CAPTCHA / token auth |
| MiniMax | ❌ Requires full browser session |
Recommendation: Run Chrome with --remote-debugging-port=18800 for full provider support. Headless is a fallback for when no visible Chrome is available.
-
OpenClaw browser fallback: If no Chrome is running on port 18800, the scraper will automatically try
openclaw browser startto launch a CDP-enabled browser. This works if you have OpenClaw installed. Login sessions are stored in OpenClaw's browser profile (~/.openclaw/browser/), so you only need to log in once. -
Multiple Chrome instances: Only one Chrome can bind to port 18800. Close other CDP-enabled instances before starting.
The scraper navigates provider dashboards like a normal user — no API abuse. However:
- Don't set refresh intervals too low (< 10 min) to avoid triggering anti-bot detection
- Some providers (e.g., Anthropic) may show CAPTCHAs after repeated automated visits
- The 30-minute default interval is conservative and recommended
Chrome (your profile, logged in) → CDP WebSocket
↓
Parallel scraper (one tab per provider, background)
↓
data/quota_data.json
↓
Flask server → Neumorphic UI (auto-polls every 30s)
index.html # GitHub Pages entry (static demo)
web/
index.html # Neumorphic frontend (vanilla HTML/CSS/JS)
server.py # Flask backend + background scraper
scripts/
scrape_dashboards_parallel.py # Parallel CDP scraper (primary)
scrape_dashboards.py # Sequential CDP scraper (fallback)
check_quota.py # Legacy Playwright scraper
data/
providers.json # Provider configs (editable)
quota_data.json # Latest quota data (auto-generated)
The easiest way to add and configure providers is through OpenClaw. Just tell your agent:
"Add DeepSeek to my LLM quota dashboard"
OpenClaw will:
- Add the provider config to
data/providers.json - Write the scraper function in
scrape_dashboards_parallel.py - Test it and fix any issues
- Commit the changes
This is how the dashboard was built — each provider scraper was developed conversationally through OpenClaw.
"Add DeepSeek to my LLM quota dashboard"
"Set up the dashboard on port 8502 with Cloudflare Tunnel"
"My Groq dashboard shows rate limits at platform.groq.com/usage —
add it to the scraper"
"The Kimi scraper is reading stale data, fix the wait logic"
"Add a new field 'daily_used_pct' to the Anthropic scraper —
it shows on the usage page as 'Daily usage: XX%'"
"Run a manual refresh and send me the results on WhatsApp"
"Schedule quota checks every 6 hours and alert me if any provider
drops below 20%"
Step 1: Add to data/providers.json:
{
"my_provider": {
"name": "My Provider",
"dashboard_url": "https://example.com/dashboard",
"color": "🔹",
"enabled": true,
"notes": "Any notes"
}
}Step 2: Add a scraper function in scripts/scrape_dashboards_parallel.py:
def scrape_my_provider(tab: CDPTab) -> dict:
tab.navigate("https://example.com/dashboard")
# Wait for page data to load
found, text = tab.wait_for_text(["some keyword"], timeout=15)
data = {"provider": "my_provider"}
# Parse with regex...
m = re.search(r'(\d+)%\s*used', text)
if m:
data["current_used_pct"] = int(m.group(1))
return {"status": "success", "method": "cdp_parallel",
"data": data, "last_checked": now_iso()}Step 3: Register in PROVIDERS dict:
PROVIDERS = {
...
"my_provider": {"name": "My Provider", "color": "🔹", "fn": scrape_my_provider},
}The dashboard frontend automatically renders any *_used_pct field as a progress bar.
| Field | Label |
|---|---|
session_used_pct |
Session |
5h_quota_used_pct / 5h_used_pct |
5h Quota |
weekly_used_pct / weekly_quota_used_pct |
Weekly |
weekly_all_used_pct |
Weekly (all) |
monthly_used_pct |
Monthly |
monthly_search_used_pct |
Search |
rate_limit_used_pct |
Rate Limit |
current_used_pct |
Current |
extra_used_pct |
Extra |
| Provider | Method | Data Extracted |
|---|---|---|
| Z.AI | CDP | 5h/Weekly/Monthly quota %, total tokens |
| DashScope | CDP + hover popover | 5h/Weekly/Monthly usage, plan info |
| Anthropic | CDP | Session/Weekly/Extra usage %, spend, balance |
| Kimi | CDP | Weekly/Rate limit %, tier, model |
| MiniMax | CDP | Prompts per window, plan, validity |
- Connects to Chrome via CDP WebSocket (
port 18800) - Opens one background tab per provider (parallel, won't steal focus)
- Navigates to dashboard, waits for data keywords
- Extracts
document.body.innerText, parses with regex - Detects login issues (redirect to login page →
need_loginstatus) - Closes tabs, saves to
quota_data.json
Login: The scraper uses your existing Chrome sessions. Just be logged in to your providers in the Chrome instance running with --remote-debugging-port.
# All providers (parallel)
python3 scripts/scrape_dashboards_parallel.py
# Single provider with debug output
python3 scripts/scrape_dashboards_parallel.py -p kimi --debug
# Sequential fallback
python3 scripts/scrape_dashboards.py- Design: Neumorphism (Soft UI) —
#E0E5ECbase, dual shadows - Stack: Flask + vanilla HTML/CSS/JS (no build step, no npm)
- Auto-refresh: Frontend polls every 30s, pauses when tab hidden
- Per-card timestamps: Shows when each provider was last checked
- Login detection: Shows 🔑 card when provider needs re-login
- With Flask: Live data from
/api/data, refresh button triggers scraper - Static (GitHub Pages): Falls back to
data/quota_data.json
python3 web/server.py # http://localhost:8502# ~/.cloudflared/config.yml
- service: http://localhost:8502
hostname: dashboard.yourdomain.comEnable GitHub Pages in repo settings (source: master, root /).
MIT