π Browse your AI coding spend in the terminal β OpenCode, Claude Code, Codex & friends
OpenTab
Your AI coding tools keep a tab. OpenTab opens it.
Anonymized demo data β click the reel for the full-quality video.
One reel, every view β trends, a calendar spend heatmap, drill from a month down to a single session, and live theming
βΆ The full tour on YouTube β every view, walked through
Also a web browser β opentab --web renders the same data as one self-contained, shareable page
A local, standard-library terminal UI for your AI coding spend. It reads the records your coding tools already keep on disk and shows where your tokens and money went: by month, day, project, session, and model, down to the subagent tree. Browse one tool at a time, or merge them all.
Your tools already keep this ledger; OpenTab is just the reader. No backend, no telemetry, no accounts β it opens those files read-only. Standard-library-only at runtime (curses + sqlite3): pipx install opentab-ai and there's nothing else to pull in.
Features
- One tab for every tool β OpenCode,
- Every machine, one tab β code on more than one box?
opentab pullgathers each
M). How it works.
- Drill, don't scroll β month β day β project β session β model, down the recursive
- Trends β daily / weekly / monthly charts, a calendar spend heatmap, and model /
- Turns and Tools β per-turn cost over time inside a session, each prompt carrying
- Context β a session's context window over time: a heat-shaded growth curve with
- Honest
$what-if β subscription usage shows its true$0, and$reprices it at
P shows the exact per-model table behind the estimate.
- A web twin β the same browser as one self-contained HTML file (
--html), or served
--serve, --web).
- Lazygit-style driving β keyboard and mouse: scroll, click to select, double-click to
- Themes β 30 bundled: Tokyo Night, Catppuccin, Kanagawa, Everforest, Gruvbox, Nord,
C) and
the web page.
- Quality of life β git worktrees fold into their repo, CSV export of any view, and
$ view are remembered between runs.
- Private by construction β local-only, read-only, no telemetry, no accounts; a demo
Install
Python 3.9+ and a terminal β nothing else. Already true on macOS, Linux, and WSL; native Windows works too (see Windows).
Try it first, nothing installed:
uvx --from opentab-ai opentab --demo # or: pipx run --spec opentab-ai opentab --demo
--demo runs the full TUI on your real usage, anonymized in memory β titles, paths, and absolute numbers replaced with synthetic ones β so trying it out (and sharing the screen) is safe. It reads your tools' own records, so it needs at least one AI coding tool's history on disk. Drop --demo to see the real numbers. Scramble only some of it with a comma list β --demo titles,spend shows real prompt bodies but fake names and hidden costs (categories: titles, turns, spend) β or press D in the TUI for a multi-check picker of the same.
Then install for real:
pipx install opentab-ai
The PyPI distribution is opentab-ai; the command it installs is opentab. Upgrade later with pipx upgrade opentab-ai.
Other ways to install β Homebrew, install script, pip, from source
Homebrew (macOS / Linux):
brew install hamidi-dev/tap/opentab # upgrade later with brew upgrade opentab
Install script (installs via pipx; re-run to update):
curl -fsSL https://raw.githubusercontent.com/hamidi-dev/opentab/main/install.sh | bash
pip: plain pip install --user opentab-ai works too.
From source:
git clone https://github.com/hamidi-dev/opentab && cd opentab
pipx install . # or pip install -e . for a live-editable checkout
Usage
opentab # open the browser, all time
opentab --days 30 # start within a window (rescope live with R)
opentab --since 2026-05-01 --until 2026-05-31
opentab --harness claude # one tool only (switch live with H)
opentab --demo # safe for live demos / screenshots
opentab --web # the same browser, in your web browser
opentab doctor # what's found, what isn't, and why β paste this into a bug report
Everything is discoverable in-app β ? shows the full keymap, every panel and overlay documented. The full reference lives in docs/: data harnesses, keys, pricing, the web browser, Windows/WSL, and privacy.
Data harnesses
OpenTab reads the local records each AI coding tool keeps. Pick one with --harness (--source still works as a deprecated alias), point its flag at a non-default location, or just pass a file path (opentab requests.csv, opentab path/to/opencode.db) and the harness is inferred. --harness auto (the default) restores your last-used harness, else merges every present harness when more than one exists; switch live with H.
Every harness feeds the same browser β months, days, projects, sessions, models, trends. What each tool's records support on top:
| Harness | Cost | Subagent tree | Turns | Tools | Context | |--------|------|:---:|:---:|:---:|:---:| | OpenCode | real recorded | β | β | β | β | | Claude Code | tokens only β $ estimates | β | β | β | β | | Codex CLI | tokens only β $ estimates | β | β | β | β Β³ | | Hermes Agent | mixed β metered real, rest estimated | β | β β΅ | β | β | | GitHub Copilot CLI | tokens only β $ estimates | β | β ΒΉ | β | β | | Copilot Chat in VS Code | tokens only β $ estimates | β | β | β | β | | pi-agent | mixed β metered real, rest estimated | β | β | β | β | | omp | mixed β metered real, rest estimated | β | β | β | β | | OpenClaw | mixed β metered real, rest estimated | β | β | β | β | | zaly | mixed β metered real, rest estimated | β | β | β | β | | CSV / JSONL request logs | mixed β per-row cost column | β | β | β Β² | β β΄ |
Subagent tree β recursive per-subagent cost under the session that delegated Β· Turns β the per-turn cost timeline inside a session Β· Tools β token attribution per tool call and MCP server Β· Context β the context-window growth curve, measured from recorded usage (it rides on Turns); Claude Code and zaly log full message content, so they add the estimated breakdown of what filled it Β· ΒΉ headerless: the OTEL export captures no prompt text Β· Β² with the optional tool column Β· Β³ Codex records per-turn deltas of a cumulative total, not per-request prompt sizes, so an honest curve isn't derivable Β· β΄ only with a real session_id column β a synthetic per-day session interleaves unrelated conversations Β· β΅ Hermes stores no per-message usage, so its turns are read from the agent log (~/.hermes/logs/agent.log*) and joined to the session by id; because that log rotates, only sessions inside the retained window offer the tab, and a resumed session's turns can exceed the total Hermes itself accumulated.
docs/sources.md has the full detail per harness β where each tool's records live, its flags and env vars, how cost is derived, quirks (Copilot's opt-in OTEL export, Codex's cumulative counters, the pi/omp/OpenClaw/zaly metered-vs-subscription split, β¦), the CSV/JSONL schema, and the merged --harness all view.
Fleet
Code on more than one machine? **opentab pull gathers each box's spend over SSH β all in parallel β and opens them merged into one browser** you filter and drill by machine.
opentab pull laptop workstation gpu-box # fetch all three over SSH, open the fleet
opentab pull # later: refresh every machine you've saved
Each host is remembered (the list in ~/.config/opentab/remotes.json, the summaries it pulls in ~/.cache/opentab/remotes/), so a bare opentab pull refreshes the lot. A host is any ssh target β box, user@host, name=user@host to label it, or http://host:port for a box already running opentab web.
No agent, nothing to install, nothing listening. The remote only needs opentab on its PATH: pull runs opentab export - there, which prints that box's spend summary β totals, a per-model breakdown, and Turns / Tools / Context, but no transcripts β and streams it back over the SSH pipe. (If opentab isn't on the machine's non-interactive PATH, set that box's cmd in remotes.json.)
Once pulled, the fleet behaves like any other harness:
Mfilters every view to one machine β the harness picker (H)'s twin; Trends and
Lon a pulled session reopens it on the box it ran on only when that machine has
remotes.json: the launch targets wrap the resume command in ssh -t
<that machine> 'cd <its project> && β¦' (y yanks the same line). URL-based pulls have no
SSH target and therefore offer only the copied command.
opentab remotereopens the last pull offline, with no SSH round-trip.opentab export box.jsonwrites one box's summary by hand (so
ssh box opentab export > box.json works), and opentab forget <machine> drops one
you no longer pull.
Pair any of it with --demo for a shareable fleet snapshot β names and numbers scrambled.
Keys
OpenTab opens on a stacked Months / Days (or Projects) sidebar, lazygit-style: drill from a month or day into its detail tabs, from the Sessions tab into a single session β cost split, model mix, subagent tree β and step back out with Esc. The short version:
| Key | Action | |-----|--------| | j/k Β· h/l Β· Enter Β· Esc | Move Β· switch tabs Β· drill in Β· step back out (Tab flips the sidebar panels) | | 1/2/3 Β· 0 | Jump to a panel β each wears its number in its title ([1] Years β¦ [0] the detail pane) | | + | Maximize / restore the drilled-in detail pane (the sidebar stays clickable beside it) | | Mouse | Wheel scrolls, click selects, double-click drills, a column-header click sorts | | T | Trends β cost charts, the calendar heatmap, model/provider/harness rankings; every tab drills down to a session | | $ / P | What-if pricing at API list rates, and the price table behind it | | w | What-if model β arm one model as a comparison target ("what if the expensive model had done the subagents' work too?"); the selected session's Subagents tab then adds a What-if column pricing each node's tokens at that model's rates, and its Overview the whole session: your models vs all at the target, both at list rates β the only apples-to-apples basis, so a session that delegated nothing still answers. Session-scoped: every other view keeps its actual cost, and $ keeps working. w again clears it | | R / a | Scope to a date range (30d, 2026-05, start..end, β¦) / back to all time | | f | Live fuzzy filter, fzf-style | | H / C / D | Switch data harness Β· colour theme Β· demo mode β from anywhere, overlays included | | M | Filter every view to one machine (fleet only) β the harness picker's twin, for --pull/--remote boxes | | L | Relaunch the session in its own tool β tmux window/split/popup, Herdr tab/split, or your own launcher; a session pulled from another machine reopens on that machine over SSH only when its remotes.json entry has an SSH target; URL-based pulls offer only the copied command | | n | Note β the selected session β why it cost what it did. Searchable, exported, kept in its own file | | e / o | Export the current view to CSV / open the project's directory | | K | Remap anything β opens ~/.config/opentab/keymap.conf in $EDITOR and reloads it the moment you return; every key in every view, picker and prompt is configurable, and the footer/help re-label themselves live (docs) | | ? / q | Help / quit |
The active **harness, range, sort, focused sidebar panel, ignored projects, and $ what-if view are remembered between runs** (stored in ~/.local/state/opentab/state.json; pass --no-state to disable, and --demo does not persist). A w target model is not β it's a transient analysis mode, and a remembered one would quietly re-frame the next run's Subagents tab. Session notes (n) live apart, in ~/.local/share/opentab/notes.json β they're the one thing here you wrote rather than opentab derived, so they're saved the moment you write them and never pruned. The complete keymap β bookmarks, notes, ignore lists, the sort picker, overlay keys, custom launcher hooks β is in docs/keys.md.
Web browser (--html / --serve / --web)
opentab --html writes the whole browser as one self-contained HTML file β no server, no dependencies, works from disk or any static host. It's the TUI in the browser: the same sidebar, detail tabs, Trends and price-table overlays, live range scoping and colour themes, the same $ and w what-if pricing, driven by the same keys or the mouse, with every view a shareable deep link. opentab --serve serves it live on http://localhost:8321 and adds the per-session Turns/Tools drill-in; opentab --web also opens it in your default browser. Details, deep links, and security notes: docs/web.md.
Demo mode
opentab --demo is for showing the tool to other people without leaking your real work: session titles and project paths become deterministic, plausible fakes, and sessions recorded with no cost get a synthetic price derived from their real token counts β all transformed in memory on load, nothing written back. The shape of your data stays real (the proportions between sessions and months, the model mix), the absolute numbers do not, and a DEMO β synthetic header tag keeps synthetic figures from ever being mistaken for real ones.
Why a browser, not just a usage CLI
Plenty of tools will print your token totals. OpenTab is built to explore them:
- Interactive, not a one-shot report. Drill month β day β project β session β
- Subagent cost trees. When a session delegated work, OpenTab attributes the cost
- Standard-library runtime. Just
curses+sqlite3from the standard library:
npx, no service to run. pipx install opentab-ai and it runs anywhere
Python 3.9+ exists, including a locked-down box (the sole dependency, windows-curses,
is pulled in only on native Windows).
- Honest cost for subscription usage. Subscription/credit sessions show a truthful
$0 recorded, and the $ view reprices their tokens at API list rates β a clear
"what this would have cost metered" estimate you can toggle on and off.
If you just want a single number in your terminal, a usage CLI does the job. OpenTab is for when you want to poke at the spend. (See also A note on cost accuracy.)
A note on cost accuracy
The numbers come straight from each tool's own data (cost/tokens per message, rolled up per session) β local attribution of what your tools recorded. Some sessions show tokens with a $0.00 local cost: the usage was recorded but no per-token price, normal whenever billing isn't per token (subscription plans, credit/token plans). That money isn't missing, it's billed elsewhere β by your subscription or account credits β so OpenTab surfaces it as "unpriced tokens" rather than guessing.
Press $ (non-demo) for the what-if view: real recorded spend plus what $0.00 subscription/credit usage would have cost at published API list prices, from a models.dev snapshot bundled with each release β nothing is fetched at runtime, so the TUI stays offline. P shows the exact per-model rates behind it, including the whole models.dev catalog blended to one eff $/M figure at your token mix. How the estimate is priced, the P views, pinning, and refreshing rates (--refresh-models): docs/pricing.md.
What it touches
Local-only, no network, no telemetry, no accounts β it opens every harness file read-only, so it doesn't modify any of them. It writes only its own files β config, prefs, notes and caches under the standard XDG dirs (~/.config, ~/.local/state, ~/.local/share, ~/.cache), plus the CSV/HTML exports you explicitly ask for β and runs external programs only on the key you press. The full list of everything it reads, writes, and runs: docs/privacy.md.
Something looks wrong: opentab doctor
opentab doctor
One block covering this OpenTab β version, how it was installed, and whether the opentab on your PATH is even the copy that just answered β every harness backend (found, or not found and why, with the fix), the terminal's colour and glyph capabilities including any multiplexer in the way, the price catalog, and OpenTab's own files. It answers the questions that otherwise take a round-trip: why a tool you use isn't showing up (a Copilot export that's opt-in, a --zaly-dir pointed one level too deep, VS Code sessions that recorded no tokens), why only one harness is in view (a remembered H), and why the colours or the box frames look off. Anything it tells you to set is written in your own shell's syntax.
It reports and never repairs β nothing is created, warmed or fetched β and it reads no transcript, so it can't print a prompt or a session title. Paths are folded to ~ and pulled machines are counted rather than named, which makes the output safe to paste into a public issue as-is; --full opts out for your own eyes. Exit code is 1 only if something is genuinely broken.
If the colours look wrong
OpenTab hits its themes' exact colours by redefining palette slots (init_color). Most terminals honour that; a few accept the call and quietly ignore it, and then every theme looks the same β C cycles through them with nothing on screen changing. That's the tell (other apps re-colouring fine is not evidence β they use truecolor escapes, which take a different path).
Known hosts β currently herdr, which re-emits each pane's cells and forwards a palette index rather than the colour behind it β are detected and switched over automatically, so there's nothing to set. Anywhere else, export OPENTABNOINIT_COLOR=1 and OpenTab picks the nearest standard 256-colour instead (matched in CIE Lab, so hues survive); =0 forces the exact colours back on if your terminal has since been fixed. It's an environment variable rather than a flag because it describes the terminal, not the run β set it once in that terminal's profile.
Windows
OpenTab uses Python's curses, which native Windows Python doesn't bundle β so opentab-ai declares windows-curses as a Windows-only dependency and pipx pulls it in for you: pipx install opentab-ai and run. Under WSL, curses is already there, so a plain opentab works β and it can read the Windows-side OpenCode database and VS Code store through /mnt/c. Details: docs/windows.md.
Development
CI runs Ruff, unit tests, and ShellCheck. See CONTRIBUTING.md for local setup, the test/lint commands, the pre-push hooks, and commit conventions, and docs/architecture.md for how the code is put together.
License
MIT β see LICENSE.