DEV Community

Cover image for I Got Tired of Checking 6 AI Dashboards, So I Built a CLI That Checks Them All
Prakersh Maheshwari
Prakersh Maheshwari

Posted on

I Got Tired of Checking 6 AI Dashboards, So I Built a CLI That Checks Them All

Last month I realized I was opening 6 browser tabs every morning just to check my AI API quotas. Anthropic, Copilot, Codex, three more. Each with different billing cycles, different UI, different definitions of "usage."

So I built onWatch — a CLI that polls all of them from one terminal.

The Problem

I use Claude Code, Antigravity, and Codex CLI daily. Each has its own dashboard:

  • Anthropic tracks 5-hour limits, weekly all-model limits, and weekly Sonnet limits — three separate quotas with different reset windows
  • Codex has 5-hour limits plus review request quotas
  • Antigravity splits quotas across Claude+GPT, Gemini Pro, and Gemini Flash

That's 6 provider dashboards, each with multiple quota types, different billing cycles, and no cross-provider view.

The real problem isn't seeing current usage — every provider shows that. The problem is having no historical data. Which sessions burn quota fastest? Which days am I heaviest? When exactly does my cycle reset? No provider dashboard answers these questions.

The breaking point: I hit a rate limit mid-session with Claude Code. Lost my context, lost my momentum, and had no idea when the limit would reset.

What I Built

onWatch is a Go CLI that runs as a background daemon. It:

  1. Polls 6 providers in parallel — each provider runs as an independent agent with its own goroutine (internal/agent/). Anthropic, Codex, Copilot, Antigravity, Synthetic, and Z.ai.
  2. Stores history in SQLite — a single *sql.DB connection with WAL mode. Currently my database is 55MB after weeks of continuous polling across all 6 providers.
  3. Serves a Material Design 3 dashboard — HTML templates and static assets embedded via Go's embed.FS, served from the same binary. Dark and light mode.

The key insight that drives everything: per-cycle, historical usage patterns are more valuable than point-in-time snapshots.

The dashboard shows things like:

  • Your 5-hour Anthropic limit is at 72% utilization with 3h 9m until reset
  • Your burn rate is 38.2%/hr — at this pace, quota exhausts in 43 minutes
  • Your average 5-hour limit usage across 21 windows is 60%
  • Recent usage is trending -16% compared to earlier periods

These are real numbers from my running instance, not mock data.

Technical Decisions

Single binary (~13MB): Everything — HTML templates, JavaScript, CSS, service worker manifest — is embedded using Go's embed.FS. No npm build step, no external files. You download one file and run it.

//go:embed templates/*.html
var templatesFS embed.FS

//go:embed all:static/*
var staticFS embed.FS
Enter fullscreen mode Exit fullscreen mode

<50MB RAM with 6 providers: My running instance uses ~35MB RSS with all 6 providers polling every 60 seconds. Key decisions:

  • Single SQLite connection (not a pool) — *sql.DB with one connection
  • Bounded queries: cycles capped at 200, insights at 50
  • No ORM — parameterized SQL everywhere for both performance and injection prevention
  • Embedded assets mean zero runtime allocations for serving static files

The agent pattern: Each provider is an independent Agent struct that runs a polling loop in its own goroutine. Clean shutdown via context.Context cancellation. If one provider's API is slow or errors, others aren't affected.

type Agent struct {
    client   *api.Client
    store    *store.Store
    tracker  *tracker.Tracker
    interval time.Duration
    logger   *slog.Logger
    sm       *SessionManager
}

func (a *Agent) Run(ctx context.Context) error {
    // Polls immediately, then at configured interval
    // until context is cancelled
}
Enter fullscreen mode Exit fullscreen mode

Security:

  • subtle.ConstantTimeCompare for credential checks (timing attack prevention)
  • Parameterized SQL only — plus an validateOrderByColumn allowlist function for ORDER BY clauses
  • All data stays on your machine — zero telemetry, no cloud dependency

The Numbers

These are from the actual codebase and running instance:

Metric Value
Go source lines ~47,600
Test functions 486 across 33 test files
Binary size 13MB
Runtime memory ~35MB RSS (6 providers polling)
Database after weeks 55MB
Provider agents 6 (each with client, store, tracker, agent layers)
Static assets 6 files embedded (app.js, style.css, sw.js, manifest.json, favicon.svg, theme-init.js)

Try It

One-line install (macOS/Linux):

curl -fsSL https://raw.githubusercontent.com/onllm-dev/onwatch/main/install.sh | bash
Enter fullscreen mode Exit fullscreen mode

Windows (PowerShell):

irm https://raw.githubusercontent.com/onllm-dev/onwatch/main/install.ps1 | iex
Enter fullscreen mode Exit fullscreen mode

Docker:

cp .env.docker.example .env   # add your API keys
docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

It self-daemonizes on macOS, uses systemd on Linux, and runs in the foreground in Docker (auto-detected via IsDockerEnvironment()).

Open-source, GPL-3.0: github.com/onllm-dev/onwatch

What provider should we add next? MiniMax and Kimi are the most requested.

Top comments (0)