The thing that finally annoyed me enough to go looking was context compaction. You’re twenty minutes into a session with a coding agent, it’s going well, and then the tool quietly announces it’s compacting the conversation. Fine. But why now? What was actually in that context? Which of my forty tool calls dumped a 30k-token file into the payload?

The CLI won’t tell you. It shows a percentage bar at best, and that bar is computed from numbers you never get to see. You’re flying on someone else’s instrument panel.

tokentap is a fairly small Python tool that fixes this in the most direct way possible: it puts an HTTP proxy between your CLI and the provider’s API, counts what goes past, and draws a live dashboard in a second terminal. Every request body also gets written to disk. That’s more or less the whole pitch.

Two terminals and you’re done

Install is a pip install tokentap (Python 3.10+). Then, in one terminal:

tokentap start

It asks where you want captured prompts saved, then paints a dashboard — a fuel gauge for cumulative context usage, a scrolling table of requests with timestamp, provider, model and token count, and the first line of the most recent prompt.

In another terminal, you launch your tool through tokentap instead of directly:

tokentap claude       # Claude Code
tokentap codex        # OpenAI Codex
tokentap gemini       # see the caveats section

And there’s an escape hatch for anything else:

tokentap run --provider minimax python my_app.py

The gauge is colour-coded: green under 50%, yellow to 80%, red above. Default limit is 200,000 tokens, adjustable with -l. On exit you get a one-line session total.

The part I actually find interesting

I assumed, from the words “intercept” and “proxy”, that this would be a mitmproxy-style setup — install a root CA, trust it, decrypt TLS on the fly. That’s the usual way to inspect HTTPS traffic and it’s a genuine pain to get working on a fresh machine, especially on macOS where half your tooling has its own opinion about the certificate store.

Tokentap sidesteps it entirely. Look at what tokentap claude does:

ANTHROPIC_BASE_URL=http://localhost:8080 claude

That’s it. It sets the base URL environment variable and execs the real CLI. The agent talks plain HTTP to localhost, the proxy reads the JSON body while it has it in the clear, then forwards the request over HTTPS to api.anthropic.com and pipes the response back.

No certificates because there’s no TLS to break. The SDK is choosing to talk to you.

This is a nice trade to be aware of, because it defines the tool’s limits precisely. Tokentap can see traffic from any client that (a) honours a base-URL environment variable and (b) is willing to speak HTTP to it. It can see nothing from a client that hardcodes its endpoint or pins certificates. You’re not intercepting anything in the adversarial sense — you’re politely asking, and every supported tool happens to say yes.

The OpenAI-compatible path has a small wrinkle worth reading, because a lot of providers now share that wire format but live at different hosts. Tokentap handles it with path prefixes:

tokentap run --provider minimax python my_app.py
  → OPENAI_BASE_URL=http://localhost:8080/minimax/v1
  → request arrives at /minimax/v1/chat/completions
  → proxy strips /minimax, forwards to https://api.minimax.io/v1/chat/completions

One proxy port, one dashboard, multiple upstreams, no config file. Adding a new OpenAI-compatible provider is presumably a routing-table entry. Currently --provider accepts anthropic, openai, gemini and minimax.

The prompt archive is the underrated feature

Token counts are what you come for. The archive is what you stay for.

Every intercepted request gets written twice: a Markdown file with metadata for reading, and the raw JSON body for grepping. That raw body is the real system prompt, the real tool definitions, the real conversation history after whatever the CLI decided to trim — not the marketing description of them.

Concretely, this is how you answer questions you otherwise can’t:

  • How large is the system prompt and tool schema before you’ve typed anything?
  • Did that MCP server just inject 8k tokens of tool descriptions into every single turn?
  • When the agent read a file, did it send the whole thing or a slice?
  • What exactly does the history look like after compaction?

I’ve found the answer is usually “the boring overhead is much bigger than I thought.” Tool definitions in particular are quietly expensive, and they ride along on every request in the session.

Caveats, honestly

Gemini CLI doesn’t work right now. It ignores custom base URLs under OAuth auth — an upstream bug, not tokentap’s. The command exists and will start working when Google fixes it. Until then, treat Gemini support as aspirational.

Your API key goes through the proxy. It has to; it’s in the header being forwarded. It’s a local process on your own machine, and the source is short enough to read, but be deliberate about it — I would not run this on a shared box, and I’d think twice on a work laptop with a company key.

The archive is plaintext on disk. Everything you send to the model, including any secrets or customer data that wandered into context, lands in a directory unencrypted. Pick that directory carefully and add it to .gitignore before you forget.

The context limit is a flag, not a lookup. -l 200000 is a number you supply. It doesn’t know your model’s actual window, so if you switch to something with a different limit the gauge lies to you until you say so.

localhost is HTTP. Plaintext on the loopback interface, so anything else on the machine could in principle read it. Not a concern on a personal laptop; potentially one elsewhere.

Worth it?

If you’re paying per token or living near a context ceiling, an afternoon with tokentap will tell you more about your usage than a month of guessing. It’s MIT-licensed, macOS and Linux, roughly 800 stars and 42 commits at time of writing — early, but the design is simple enough that “early” mostly means “few features” rather than “fragile.”

What I like most is that it doesn’t try to be clever. It’s an env var, a proxy, and a table. The insight is that you never needed TLS interception to see this data — you just needed to ask the client nicely to route through you.