opencode-tokenmeter — a live token-usage, cost, and delegation-tree sidebar for the OpenCode TUI.
Important
TokenMeter is a TUI plugin: you register it in tui.json, never in opencode.json's plugin array — opencode.json configures server/runtime behavior only.
TokenMeter registers a sidebar_content slot (order: 95) that renders a collapsible panel for the active session and its delegated descendants, updated in real time. The panel repaints when events arrive: each refresh event invalidates the affected session and schedules a debounced reconcile that rehydrates from the authoritative client messages (replace, never merge) — a stale in-memory mirror can never win over fresh data.
- Session — the active session and every delegated descendant: a primary token+cost line (each session's complete CUMULATIVE spend —
Σ input + Σ output + Σ reasoning + Σ cache.read + Σ cache.writeacross ALL assistant messages, exactly reconstructing the provider's billedtokens.total, with every component kept as a per-field high-water so compaction never lowers it) plus labeled secondary rows for input, output, reason, and cache, and a per-agent group list (↳ agent (N tasks)) ordered by spend weight. The panel starts with the master row expanded; theSubagentssection appears automatically with the first delegated group and toggles from its heading. - Project — all-time usage across directories/worktrees: the authoritative live
session.listtotal (fetched with an explicit 10_000-session limit — the SDK default of 100 would silently undercount) plus one deleted-session aggregate per project, persisted in a plugin-owned SQLite store (tokenmeter.sqliteunder the host state directory — neverapi.kv, whose whole-file read-modify-write would be clobbered by concurrent TUIs). Deleting a session records its final usage into that aggregate atomically and exactly once (tombstone admission), so duplicates, cascades and concurrent TUIs never inflate totals; a truncated list (at the cap) fails closed with the stable error line instead of showing a partial total; a ~2 s polling timer keeps the sidebar fresh when another OpenCode process works in the same project.
Before: you approximate spend from provider dashboards, and delegation spread is invisible.
After: cost, token spend, and the delegation tree of every session are one glance away, live in the terminal.
Each section answers a different question. Project already includes the active Session, so never add Project + Session together.
| Section | What it represents | How it is calculated |
|---|---|---|
| Project | All-time usage for the current OpenCode project across directories and worktrees, including deleted sessions | Sum of every live principal-session tree plus the persisted deleted-tree aggregate. Each session ID contributes exactly once; totals survive deletion and restart. |
| Session | The active principal session and its complete recursive delegation tree | Active root session spend + every child, grandchild, and deeper delegated session exactly once. Switching the active route switches this scope. |
| Subagents | Delegated descendants of the active Session; the principal/root session is excluded | agents counts distinct resolved agent types; task counts descendant sessions. Expanded rows group descendants by agent type and sum every run in that group. |
| Agent group | All delegated runs resolved to one agent type, such as general or sdd-apply |
Sum of the cumulative spend, reasoning, cost, input, output, and cache for that group's descendant sessions. Groups are ordered by token spend. |
For every scope, cumulative token spend uses the same formula: Σ input + Σ output + Σ reasoning + Σ cache.read + Σ cache.write.
The sidebar renders plain Unicode disclosure glyphs (▶/▼/↳) — no Nerd Font is required. Colors are semantic roles resolved from the host theme at runtime: section headings (Project, Session, Subagents) render in the semantic yellow theme().warning; agent names in theme().info (cyan); only the $amount on the primary line is light red (theme().error); secondary metric rows and task counts use a dimmer background-relative detail tone.
| Row | Rendered |
|---|---|
| Primary token+cost | <total> tokens · $<spend> — main text tone; only the $amount is light red (theme().error); the word spent is never rendered |
| Secondary rows | <input> input · <output> output and <reason> reason · <cache> cache — the reasoning label is exactly reason; real output = raw output + raw reasoning |
| Numbers modes | Compact = 3 rows (primary + paired input/output + paired reason/cache); Precise = 5 independent rows (tokens+cost, input, output, reason, cache) |
| Cache | Combined single value, or separated R<read>|W<write> with zero sides omitted and both-zero rendering 0 |
| Cost | USD cost calculated by OpenCode from the model's input/output/cache rates, always $-prefixed with exactly two decimals |
| Collapsed Subagents | Subagents (N agents · M tasks) — the aggregate counts render only while the section is collapsed |
Project and Session use the same metric contract; expanded agent groups repeat it per agent under ↳ name (N tasks) ▶ (closed) / ↳ name (N tasks) ▼ (open), with the per-agent chevron trailing the header.
See ARCHITECTURE.md for the full event → invalidation → reconcile flow.
Add the plugin to your TUI config (~/.config/opencode/tui.json user-level, .opencode/tui.json project-level, or tui.jsonc — all work):
{
"$schema": "https://opencode.ai/tui.json",
"plugin": ["opencode-tokenmeter-tui"]
}OpenCode resolves npm package names for TUI plugins and installs them automatically — there is no npm install step. The plugin registers the sidebar_content slot with order: 95 on load — no manual slot configuration is needed. Restart OpenCode after changing a TUI plugin or its tui.json entry.
OpenCode installs plugins cache-first: once opencode-tokenmeter-tui is in the package cache, it is reused forever and newer npm releases are never picked up automatically (there is no auto-update). To update, remove the plugin's cache directory and restart OpenCode — it reinstalls the latest published version automatically:
rm -rf ~/.cache/opencode/packages/opencode-tokenmeter-tui@latestThe cache directory is named after the config entry (@latest for a bare name), and OpenCode only skips installation when that directory already exists. Removing it forces a fresh install from npm on next start — no version to remember, nothing accumulates, the config stays untouched. (If you deliberately pin a version in the config, remove that version's directory instead.)
Local development instead of the npm package
Point at the built artifact (run bun run build first):
{
"$schema": "https://opencode.ai/tui.json",
"plugin": ["/abs/path/to/opencode-tokenmeter/dist/tui.js"]
}Open a session and check the right sidebar: a ▶ TokenMeter panel appears with Project, Session and — once delegations exist — Subagents headings (semantic yellow) above their compact summary rows. The Subagents section appears automatically with the first delegated group. Run TokenMeter: Settings from the command palette to adjust preferences, or press Ctrl+E (configurable in Settings) to expand/collapse all three sections together.
bun install # frozen lockfile preferred after first install| Command | What it does |
|---|---|
bun run typecheck |
tsc -p tsconfig.json && tsc -p tsconfig.test.json |
bun run test |
Unit tests (bun:test) — no build required |
bun run coverage |
Tests with coverage (lcov + text) — the gate keeps every source file at ≥80% statements/functions/lines; Bun has no branch metric; dist/** excluded as generated output |
bun run build |
Bundles src/tokenmeter.tsx into dist/tui.js (with dist/tui.d.ts) via scripts/build.ts |
bun run test:dist |
bun run build first, then the artifact regression test against dist/tui.js |
bun run audit |
bun audit |
bun run biome:check |
Read-only Biome gate: formatter + linter over src, test, scripts, TS configs |
bun run biome:format |
Apply the Biome formatter to the same files |
bun run hooks:install |
Install the repo-local Lefthook pre-commit hook |
bun run pack:dry-run |
Inspect the tarball contents before publishing |
The suite runs 224 tests / 0 failures across 7 files (7,820 expect calls) on Bun 1.3.11. test and test:dist are distinct on purpose: the unit suite never needs a build, and the dist test is never silently skipped — it fails hard if dist/tui.js is missing or non-reactive.
bun run build compiles the entry with @opentui/solid's createSolidTransformPlugin (via bun build, external runtime packages). Loading the source .tsx through Bun's ordinary eager JSX transform would emit jsxDEV calls with eagerly evaluated props — and the mounted sidebar would never repaint. The build script post-checks the artifact for real effect/insert/insertNode bindings and forbids jsxDEV/jsx-runtime, failing loudly instead of shipping a frozen panel.
Biome (2.5.x) is the formatter and linter — one fast tool that formats, lints, and organizes imports, configured by a single biome.json.
- Releases are tag-driven: push a stable
vX.Y.Ztag — the release workflow preflights, publishes to npm with provenance, and creates the GitHub Release. - Every release needs a curated release notes body in the single current release document
docs/releases/<tag>.md. The lifecycle keeps exactly one document: for a new release,git mv docs/releases/<old-tag>.md docs/releases/<new-tag>.md, replace its content with the narrative body (meaningful sections, PR/issue links — see the template in theci-cd-and-automationskill assets), bump the package version, commit, then tag. The release preflight fails the release whendocs/releases/has zero or multiple documents, the document name does not match the tag, or the body is empty, placeholder-filled, malformed, or mismatched to the tag/version — the GitHub Release is created from that file only, never from a raw commit list. - Publishing uses npm Trusted Publishing (OIDC) with provenance: no npm tokens exist, and publication runs in the protected
releaseenvironment. - The first-ever publish is a one-time manual authenticated bootstrap (dist-tag
bootstrap); the procedure, the npmjs trusted-publisher binding, and the full control list live in docs/release-security.md.
| Your task | Start here |
|---|---|
| Product intent and scope | PRD.md |
| Architecture: flows, module map, decisions | ARCHITECTURE.md |
| Panel layout, colors, glyphs, states | DESIGN.md |
| Understand the data-flow mental model | docs/codebase/mental-model.md |
| Navigate the code / dev commands | docs/CODEBASE-GUIDE.md |
| Architecture decision records | docs/adr/ |
| Release pipeline security (controls, bootstrap, drift checklist) | docs/release-security.md |
| Curated release notes (single current release document) | docs/releases/ |
| Authoring the bundled skill | docs/skill-style-guide.md |
| Branch policy, PR gates, labels | .github/CONTRIBUTING.md |
| Report a vulnerability | .github/SECURITY.md |
- User — install and register the plugin in Quick Start, then explore the delegation tree in the sidebar.
- Developer — start from docs/codebase/mental-model.md, then follow docs/CODEBASE-GUIDE.md.
- Maintainer — read docs/release-security.md before the first release.
