v1.1.0MIT

ai-usage-tui

Terminal dashboard that measures what each AI coding model actually costs per passing test — routing, escalation and budget analytics across Claude Code, Codex CLI, GitHub Copilot, Gemini CLI, OpenCode and Local models, where an unknown cost stays unknown instead of rendering as $0.00. Live TUI or JSON/CSV.

Invented demo data, rendered off-screen by scripts/render-readme-screenshots.sh — the GIF replays a key script through the dashboard’s own dispatch, one frame per key. No real account, project, or spend appears in any image here.
  • Live TUI or JSON/CSV
  • Everything is read locally, in place.
  • No account, no telemetry
  • Linux and macOS (x86_64 and aarch64) and Windows x86_64

Install

curl -fsSL https://raw.githubusercontent.com/SophanaSok/ai-usage-tui/main/scripts/install.sh | sh
brew install sophanasok/tap/ai-usage-tui
scoop bucket add sophanasok https://github.com/SophanaSok/scoop-bucket
scoop install ai-usage-tui
cargo install ai-usage-tui --locked
cargo binstall ai-usage-tui

Also on the release page as prebuilt binaries, .deb and .rpm packages, a PKGBUILD and a Chocolatey package.

Privacy and network behavior

  • OpenCode data is read locally from SQLite in read-only mode.
  • The local-model journal (--record-ollama, --record-usage, --record-event) stores usage metadata, not prompt or response content. --record-event also stores the project path and session id, when the adapter sends them.
  • Routing events contain only the JSON fields supplied by the caller.
  • An LLM agent you point at the tool (--summary-json, the Claude Code skill) reads token counts, model names, costs, project paths and session ids. Those travel to the model provider that agent runs on, as anything in its context does — that is your agent’s data flow, not this tool’s, which still transmits nothing. Nothing from your prompts or transcripts is in any export.
  • Prompts, completions, API keys, credentials, and interaction content are not collected.
  • Claude Code session transcripts contain source code and secrets; only the usage block of each line is parsed. A test plants a fake credential in a transcript and fails if it reaches a usage record.
  • ~/.claude.json is read to decide billing — only whether oauthAccount is present and its userRateLimitTier / organizationRateLimitTier strings — and for the cached rate-limit windows under cachedUsageUtilization, as the Subscription limits section itemises. The file also holds the account’s email, name, organisation, and per-project prompt history; none of that is retained or logged, and the parsed document is dropped at once. .credentials.json and settings.json are never read.
  • --statusline reads Claude Code’s statusline payload from stdin, and from it only the three rate_limits windows’ used_percentage and resets_at. The session id, transcript path, working directory, model and session cost in the same payload are never deserialised. It writes one file in the data directory, statusline-limits.json, holding those figures and the time they arrived, and nothing else. The environment is checked only for the presence of ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, CLAUDE_CODE_USE_BEDROCK, and CLAUDE_CODE_USE_VERTEX; their values are not read.
  • Copilot’s CLI store and session logs contain prompts, completions and tool arguments in their turns table and user.message / assistant.message records; only the assistant_usage_events columns and session.shutdown aggregates are parsed, under the same planted-credential test as Claude Code.
  • Cursor’s state.vscdb is never opened. See Why there is no Cursor collector.
  • Codex rollouts contain prompts, tool-call arguments and outputs, and reasoning summaries; only session_meta, turn_context, and the token_count block (token counts, and the rate_limits windows beside them) are parsed, under the same planted-credential test as Claude Code. ~/.codex/auth.json is a credential file and is never opened; the environment is checked only for the presence of OPENAI_API_KEY and CODEX_API_KEY.
  • Omarchy’s agents-panel records are read-only display data: six fields per record (id, name, updatedAt, ready, tierLabel, usageStatusText, limits). The agents’ credentials, Omarchy’s probe cache, the record’s authHelpText and token tallies are never read, no network request is made, and the reader writes nothing into the directory.
  • --omarchy-record is the one write into Omarchy’s directory, and only that explicit action performs it: <id>.json (opencode by default) holding token counts, model ids, request and session counts, and budget figures — never content, never a path. Ids that would overwrite Omarchy’s own files (claude, codex, fireworks) are refused; the file is written atomically with mode 0600.
  • Per-project attribution records the working directory path of each session, so ~/a/build and ~/b/build stay separate projects. The dashboard shows only the shortest name that distinguishes them, but --json and --csv export the full path — worth knowing before pasting an export into a ticket.
  • Normal dashboard and export operation does not require a network request, and nothing is written outside the tool’s own data directory unless --omarchy-record is run.
  • --refresh-pricing, --refresh-zen, and an enabled zen_pricing background collector make outbound requests to OpenCode/Zen endpoints.
  • Two commands ask GitHub for the latest release tag, and nothing else does: --check-update, whose only job is to ask and cache, and --doctor when [update] check = true. Off by default, never automatic, and never on the dashboard’s refresh path. It is a plain GET of a public endpoint: no usage data, no identifiers, no query parameters. The User-Agent names the tool and its version because GitHub’s API requires one.
  • That answer is cached in the tool’s own data directory, and the dashboard reads it once at startup to show a newer release in its header. The cache is what keeps the header offline: it redraws several times a second and never makes a request or reads a clock. Only those two commands write it, so with neither run the header stays silent — except for an answer an earlier run already left, which --doctor discloses.
  • A recurring check is yours to schedule, not the dashboard’s to make: contrib/systemd/user/ai-usage-update.timer runs --check-update daily, and any other scheduler can run the same command. The dashboard process itself never makes the request, whatever the config says.
  • Reporting how this copy was installed and how to upgrade it needs no network at all. --doctor reads it off the running binary’s own path, so it is always on and works offline.

What it shows

  • Routing aggregates: cost per passing test, with retries, escalations, and review defects per model
  • Derived escalations: which sessions reached for a pricier model, and what it cost afterwards
  • A compact summary an LLM can read whole — cache-hit rate, tokens and cost per request, and the model mix by project, session and day — with a shipped Claude Code skill, so you can simply ask how to cut your token usage or whether your routing is earning its cost
  • Usage grouped by provider and model, across OpenCode, Claude Code, Codex CLI, GitHub Copilot, Gemini CLI, and Local models
  • Input, output, reasoning, cache-read, and cache-write tokens
  • Today (local calendar day), trailing 7-day, trailing 30-day, all-time, or custom-day ranges
  • LOCAL, CLOUD, FREE, PAID, and UNKNOWN classifications, each with its share of the tokens, and one strip dividing the whole between them
  • Provider-reported, calculated, estimated, free, local, quota-billed, or unavailable cost
  • Daily and monthly budget status
  • Subscription rate-limit windows and their resets, from Claude Code’s own cache, from its status line (--statusline, which also gives Claude Code a one-line readout), and from Omarchy’s agents panel
Category Meaning
LOCAL Usage identified as running on a local endpoint
CLOUD Hosted or cloud-routed usage without authoritative cost
FREE Usage from a model explicitly identified as free
PAID Usage from a provider that bills per token — including aggregators and clouds (OpenRouter, Bedrock, Azure, Vertex). Whether a rate is known is reported separately, below
UNKNOWN Usage whose provider is not recognised as billing per token

PAID is about who bills, not about whether we have a figure. A paid row with no published rate keeps cost as unknown and counts against the pricing-coverage figure, so the gap is visible rather than hidden in UNKNOWN.

Cost status is reported separately from category: reported comes from the provider, calculated or estimated comes from pricing data, free and local are non-billable, and unavailable remains unknown.

quota is its own case: the usage is billed, but against an account quota or GPU time rather than per token, so no per-request price exists to report. Ollama Cloud is one example; Claude Code on a Pro or Max subscription is the other. For subscription rows the API-list-rate figure is kept as api_equivalent_cost and shown as API-RATE EQUIV. in the breakdown, but it is never summed into cost or budgets. quota is deliberately not counted as a pricing gap — doing so reported a correct refusal to invent a number as a failure to produce one — and deliberately not rendered as $0.00. The header shows the volume alongside the coverage figure so it cannot silently disappear.

Unknown cost is kept unknown rather than displayed as paid usage with a zero cost. Local and explicitly free usage is excluded from budget spend.

Model-routing analytics

was the expensive model worth it

This answers a question a usage total cannot: is the expensive model actually earning its cost on your work? A model that costs 5x more but lands the change on the first try can be the cheaper one. The panel ranks agent/model pairs by cost per delivered result — dollars spent per passing test — alongside the retry, escalation, and review-defect rates behind that figure.

A pair that never reported a test result — or a retry, escalation or defect count — shows , not 0%. Never having been measured is not the same as failing everything, or as never needing a second attempt.

How the routing panel is fed →

t
The routing panel
cost per delivered result per agent, above escalations derived from the sessions themselves.

Watch it

ai-usage-tui
The dashboard walked panel by panel: model totals, routing analytics, a sort, a project drill-down, subscription limits, and the key reference
The dashboard walked panel by panel: model totals, routing analytics, a sort, a project drill-down, subscription limits, and the key reference

Invented demo data, rendered off-screen by scripts/render-readme-screenshots.sh — the GIF replays a key script through the dashboard’s own dispatch, one frame per key. No real account, project, or spend appears in any image here.

What a Max subscription bought

That is a ratio a reader can form an opinion about — $4.38 of list-rate compute per commit, $8.08 per merged pull request.

The whole measurement →

Every panel

Invented demo data, rendered off-screen by scripts/render-readme-screenshots.sh — the GIF replays a key script through the dashboard’s own dispatch, one frame per key. No real account, project, or spend appears in any image here.

Why there is no Cursor collector

Cursor is the agent most often asked about here, and it is deliberately absent.

The whole argument →