harn devtools
harn devtools [--tool <name>] [--usage] [--window-days <n>] [--format table|json]Reports the on-disk state of the AI coding agents Harnery is built for — Claude
Code (~/.claude), Codex (~/.codex), and Cursor (~/.cursor) — in one place:
logged-in status, plan / seat tier, auth expiry, session counts, and, where the
tool records them locally, rate-limit / quota windows.
The default report comes from files on disk — no network call. Auth tokens are inspected only for their non-secret claims (email, plan, expiry); the token strings themselves are never printed. A signal a tool keeps server-side surfaces as blank with a note rather than a guess.
Opt-in enrichments reach the network, each authenticating with the credential
the tool already stores on disk — the user’s own token reading the user’s own
data. For Claude Code, devtools reads the OAuth token and calls the same
/api/oauth/usage endpoint the /usage command uses, for the 5h + weekly
windows and extra-usage spend. For Cursor, it reads the IDE session token and
calls cursor.com’s dashboard API (the Spending-page request) for the billing
cycle + percent-used. --no-api turns all of it off.
What each tool exposes
Section titled “What each tool exposes”| Signal | Claude Code | Codex | Cursor |
|---|---|---|---|
| Logged-in | ✅ credential + expiry | ✅ auth.json |
✅ state.vscdb |
| Account | ✅ email (JWT claim) | ✅ email (state.vscdb) |
|
| Plan / tier | ✅ seat tier | ✅ live plan (rate-limit snapshot) | ✅ Stripe membership + status |
| Auth expiry | ✅ | ✅ | ❌ |
| Session count | ✅ transcripts | ✅ threads (state_5.sqlite) |
✅ chats (composerHeaders) |
| Quota / reset windows | ✅ 5h + weekly, via oauth/usage | ✅ 5h + weekly, from token_count |
✅ billing cycle, via session token |
| Percent used | ✅ session + weekly | ✅ 5h + weekly | ✅ total / API / first-party |
| Overage spend | ✅ extra-usage credits | ❌ | ✅ on-demand |
| Token totals | ✅ windowed, always on (cached) | ✅ windowed, always on | ❌ server-side |
Login, plan, tier, auth expiry, and session counts all come from files on disk.
The quota windows, percent-used, and overage spend for Claude Code and Cursor
are the enrichment’s doing — they live server-side and are fetched with the
local token. Cursor’s state.vscdb read needs a SQLite engine (Bun’s
bun:sqlite); on a plain-Node host it degrades to login-presence only, and on
WSL the reader opens the Windows-side DB immutable rather than copying it.
Codex is read entirely from local files (auth.json + the JSONL session
rollouts), no endpoint — and unlike Claude Code and Cursor, there is nothing to
gain from a network call. OpenAI delivers Codex’s rate-limit state as a
token_count event inside the stream of an actual model turn, not from a cheap
standalone usage GET, so the rollout the running client writes each turn already
carries the authoritative server snapshot for free. “Refreshing” it from the
server would mean spending a model turn to learn how much quota is left; the
local file is strictly better. The snapshot’s freshness therefore equals last
active — an idle Codex shows an older reading, and the last-active timestamp
says exactly how old.
A machine can carry more than one Codex install (e.g. a WSL CLI and the Windows
desktop app, each its own account); the reader locks onto the active one —
whichever owns the most-recently-written rollout — and reads auth and rate limits
from that same install so the two never mix. Auth expiry is reported from the
access token (which outlives the shorter id token and is what refreshes), so a
healthy login isn’t shown as “expired”. When the snapshot’s
rate_limit_reached_type is set (a limit was actually hit), that surfaces as a
rate limit reached (<window>) note.
Codex is also the one tool whose token total is always on the card, over the
--window-days window (default 7). Each session’s cumulative
total_token_usage (cache reads included, matching Codex’s own /status) is the
last token_count event in its rollout, so the total is one bounded tail-read
per in-window rollout rather than a full parse — cheap enough to run on every
render.
Claude Code’s token total is also always on the card, but it’s harder to keep
cheap: a Claude transcript has no per-session cumulative field, so the total is
the sum of every message — a full read of each in-window transcript, hundreds of
MB in a busy week. So it’s memoized per transcript at
~/.cache/harnery/devtools/claude-tokens.json, keyed by each file’s path +
mtime + size. The first read scans everything; after that only the transcripts
that actually changed (usually just the live session) are re-read, so the card
stays responsive. --usage forces a fresh, cache-bypassing recount. The figure
sums input + output + cache tokens, so cache reads dominate — expect a number
far larger than the billed-token count.
Rate-limit discipline
Section titled “Rate-limit discipline”These endpoints are shared with the tools’ own clients — Anthropic’s
oauth/usage is the exact endpoint Claude Code’s Account & Usage panel calls,
and it is sharply rate-limited. The enrichment is built so the dashboard can
never starve that client:
- Cached per account under
~/.cache/harnery/devtools/for five minutes, keyed by a fingerprint of the token. However often the dashboard re-renders, the network is touched at most once per tool per five minutes; switching accounts uses a fresh key, so the new account’s numbers show immediately rather than the previous account’s cache. - Backs off on a 429. A rate-limited response arms a cooldown from the
server’s
Retry-Afterand suppresses further calls until it passes, serving the last-known-good value meanwhile. A rate limit can’t cascade into a hammer loop. - Requests carry the tool’s own client identity, with a live version. Claude
sends
claude-cli/<version> (external, cli)+x-app: cli; Cursor sends its Electron client UA (… Cursor/<version> Chrome/… Electron/… Safari/537.36) — so both read as first-party traffic, not a scraper. Each version is read live from the local install (Claude from theversionfield the running client stamps into its newest session transcript — which tracks auto-updates and can be ahead of the bootstrappackage.json; Cursor fromstate.vscdb).
You can spot-check that these integrations still work with harn devtools doctor — see Checking for drift.
The remaining gap: exact token totals aren’t exposed by either usage endpoint (only percentages and dollar spend).
Checking for drift
Section titled “Checking for drift”These integrations lean on undocumented endpoints, so a client update could
change the required headers or the response schema. harn devtools doctor is
the occasional health check: it makes one live call per usage endpoint
(bypassing the cache), using the exact request builders production uses, and
classifies each result.
harn devtools doctor✓ claude-code ok 200, expected shape present https://api.anthropic.com/api/oauth/usage (client 2.1.207)✓ cursor ok 200, expected shape present https://cursor.com/api/dashboard/get-current-period-usage (client 3.11.13)An auth_rejected (401/403) means our headers or token stopped being accepted;
shape_changed means a 200 came back without the fields we parse. Either is the
signal to review the request builder or the parser. rate_limited is reported
as-is (not a defect), and the printed client versions let you eyeball version
drift. It is intentionally not scheduled — run it by hand so it can’t itself
contribute to a rate limit. Codex is local-file-only, so it has no endpoint to
probe and is omitted.
Options
Section titled “Options”| Flag | Effect |
|---|---|
--tool <name> |
Restrict to a tool (repeatable): claude-code, codex, cursor. Default: all three. |
--usage |
Force a fresh, cache-bypassing recount of Claude Code’s token total (the card already shows it, memoized). |
--window-days <n> |
Window for token totals: only count transcripts / rollouts modified within N days (default 7). |
--no-api |
Skip every network enrichment (Claude + Cursor); stay fully local. |
--format <type> |
table (default) or json. |
Network enrichment
Section titled “Network enrichment”All of the following are skipped by --no-api, cached per account for five
minutes, and degrade to a note on any failure (never throw). See
Rate-limit discipline for how the cache, the 429
backoff, and the client-identity headers keep these calls from starving the
tools’ own clients.
Claude Code usage (no key needed). Reads the OAuth token from
~/.claude/.credentials.json and calls api.anthropic.com/api/oauth/usage —
the endpoint the /usage command uses — with Authorization: Bearer plus the
anthropic-beta: oauth-2025-04-20 header. Fills quota[] (the 5h session +
weekly + model-scoped weekly windows) and spend (extra-usage credits). An
expired token is skipped. That endpoint is rate-limited and shared with Claude
Code’s own usage panel, which is exactly why the result is cached.
Cursor usage + billing (no key needed). Reads Cursor’s session token from
state.vscdb and calls cursor.com/api/dashboard/get-current-period-usage —
the Spending-page request — authenticating with the WorkOS session cookie the
IDE already holds. Fills usage: { cycleStart, cycleEnd, totalPercentUsed, apiPercentUsed, firstPartyPercentUsed, includedLimitCents } and spend
(on-demand). An expired token is skipped.
Cursor Cloud Agents (needs an API key). If you store a Cursor API key, the
entry also gains api: { ok, keyName, cloudAgents } from the public /v0 API:
harn devtools cursor-key set # paste the key on stdinharn devtools cursor-key status # is a key configured, and from whereharn devtools cursor-key clear # remove itThe key is read from CURSOR_API_KEY first, then ~/.config/harnery/cursor-api-key
(stored 0600). Cursor’s public usage/spend API is Team-only (/teams/*), so
an individual Pro/Pro+ key returns no token cost there — the key path only adds
Cloud Agent runs (/v0/me, /v0/agents). The richer billing numbers come from
the session-token path above, not the key.
Examples
Section titled “Examples”Default report:
harn devtools── claude-code ───────────────────────────── logged in yes account dev@example.com plan team_tier_1 rate tier default_claude_max_5x auth expires 2026-07-13T13:37:20Z sessions 436 quota (5h) 92% used · resets 2026-07-13T10:29:59Z quota (weekly) 27% used · resets 2026-07-18T06:59:59Z quota (weekly · Fable) 37% used · resets 2026-07-18T06:59:59Z extra usage $0.97 / $5.00Token totals for the last day, JSON, one tool:
harn devtools --tool claude-code --usage --window-days 1 --format jsonThe JSON envelope is { ok, generatedAt, windowDays, tools: ToolStatus[] },
where each ToolStatus carries installed, loggedIn, account, plan,
rateLimitTier, authExpiresAt, sessions, lastActivity, quota[],
tokensUsed, api, usage (Cursor billing/percent-used), spend (overage),
and notes[].