English | 简体中文
A context-budget profiler for Claude Code sessions.
See which skills, MCP tool descriptors, project files, and reasoning ate your 200k-token window — before the next session walks into the same wall.
You opened Claude Code, loaded three or four skills you needed last week, wired a couple of MCP servers, started a real task, and at 82% window utilization the assistant told you to start a new chat. You have no idea which of the seven things you added is responsible. The vendor meter shows one aggregate number; JuliusBrussee/caveman (68k★, 1,138 stars/day) trims the model's output style, and rtk-ai/rtk (58k★) proxies commands — both clever workarounds, neither tells you what you were paying for. Meanwhile Uber just capped per-seat AI spend at $1,500/month (Simon Willison's TIL), so the question of what is in your context is now an accounting one as much as a craft one. ctxprof is the missing observer: a single static binary that turns Claude Code's flat token total into a structured allocation across six buckets — system / skill / MCP / file / reasoning / output — so you can decide what to unload instead of guessing.
A finished Claude Code JSONL session streams through four pure packages — parser → estimate → attribute → render — with no network calls and no model in the loop. The source data carries real per-turn message.usage totals but no per-block token field, so each block gets a local chars/4 estimate, then attribute scales those weights to match the turn's real total: the session sum stays exact while per-bucket splits become calibrated estimates. Every reconciled block lands in one of six buckets — system / skill / mcp / file / reasoning / output — which render prints as a flame tree or emits as allocation_v1.json.
- Architecture
- What you see
- Install
- Quickstart (30 seconds)
- How it works
vs caveman— adjacent, not a competitor- Configuration
- Schema:
allocation_v1.json - Roadmap
- Kill criteria (honesty section)
- Contributing
- License
- Share this
session 2026-06-04 14:22 — 184,512 / 200,000 tokens (92%)
├── skills ████████████░░ 47,210 (25.6%)
│ ├── caveman 19,840
│ ├── code-review 14,520
│ └── frontend-design 12,850
├── mcp █████░░░░░░░░░ 18,403 (10.0%)
│ ├── pencil 11,201
│ └── shadcn-ui 7,202
├── system prompt █░░░░░░░░░░░░░ 4,128 ( 2.2%)
├── files ████████░░░░░░ 31,990 (17.3%)
├── reasoning ██████████░░░░ 62,540 (33.9%)
└── output ███░░░░░░░░░░░ 20,241 (11.0%)
Rendered in CI by
docs/demo.tape(vhs) — seeassets/README.mdfor how to re-record it.
go install github.com/SuperMarioYL/ctxprof/cmd/ctxprof@latestOr grab a static binary from the releases page.
# 1. Profile your most recent Claude Code session (auto-discovers ~/.claude/projects/)
ctxprof
# 2. Or point at a specific session file
ctxprof --session ~/.claude/projects/myproj/abc123.jsonl
# 3. Pipe structured allocation to another tool
ctxprof --json | jq '.buckets'Sample --json output
{
"schema": "allocation_v1",
"session_id": "abc123",
"window_max": 200000,
"total_tokens": 184512,
"estimated": true,
"buckets": {
"skill": { "tokens": 47210, "items": [{"name":"caveman","tokens":19840}] },
"mcp": { "tokens": 18403, "items": [{"name":"pencil","tokens":11201}] },
"system": { "tokens": 4128 },
"file": { "tokens": 31990 },
"reasoning": { "tokens": 62540 },
"output": { "tokens": 20241 }
}
}The Claude Code JSONL session log gives you real per-turn totals (message.usage.input_tokens, cache_read_input_tokens, cache_creation_input_tokens, output_tokens) but no per-content-block token field. So bucket numbers cannot be read; they have to be estimated and then reconciled. ctxprof does three things per session:
- Estimate — tokenize each content block locally (
chars/4heuristic in v0.1, with a slot for a real BPE tokenizer in v0.2). - Reconcile — for each assistant turn, scale that turn's estimated block weights so they sum to the turn's real
message.usagetotal. The session-level sum is therefore exact; per-bucket splits are calibrated estimates, not field reads. - Attribute — fold each reconciled block weight into one of six buckets by a deterministic, no-model-in-the-loop classifier:
| Block | → Bucket | Why |
|---|---|---|
type:thinking |
reasoning |
The model's internal trace |
type:text (assistant) |
output |
Visible output to the user |
tool_use name = Read |
file |
Project content pulled in |
tool_use name = Skill |
skill |
input.command names the skill |
tool_use name starts mcp__ |
mcp |
mcp__<server>__<tool> |
tool_use anything else (Bash, Edit, …) |
output |
The model's action surface |
tool_result |
file |
Retrieved content brought back in |
| (no per-block signal) | system |
Approximated from first-turn cache_creation_input_tokens |
Four packages, one static binary, zero network calls:
cmd/ctxprof → internal/parser → internal/estimate → internal/attribute → internal/render
(cobra) (JSONL stream) (chars/4 weights) (classify+reconcile) (tree | json)
caveman and rtk are token savers. ctxprof is a token observer. They're complementary, not competing:
| ctxprof | caveman | claude --stats |
|
|---|---|---|---|
| Shows total window usage | ✓ | partial | ✓ |
| Attributes tokens to skills | ✓ | — | — |
| Attributes tokens to MCP descriptors | ✓ | — | — |
| Attributes tokens to reasoning trace | ✓ | — | — |
| Actively reduces token usage | — | ✓ | — |
| Rewrites the model's output style | — | ✓ | — |
| Single static binary, no install of an agent | ✓ | — | ✓ |
| Open schema other tools can emit | ✓ | — | — |
If you already use caveman, ctxprof tells you what caveman is saving you from. If a session has been profiled before and after a skill is unloaded, you get a falsifiable answer to "was it worth installing?"
No config file in v0.1 — every knob is a flag:
| Flag | Type | Default | Meaning |
|---|---|---|---|
| (positional) | path | — | JSONL session file to profile |
--session |
path | — | Same as positional, kept for scripting clarity |
--json |
bool | false |
Emit allocation_v1.json to stdout instead of the tree |
--no-color |
bool | false |
Disable ANSI colors (use this when piping to less) |
--window-max |
int | 200000 |
Context window size used for the percentage math |
With no args, ctxprof scans ~/.claude/projects/ and picks the most recent .jsonl.
The JSON shape is the moat. If Codex / Aider / Cursor ever want to emit "what's in the context," they can publish the same schema — and ctxprof becomes a renderer over a shared spec instead of a Claude Code-only tool. The schema lives at internal/schema/allocation_v1.json; it's small enough to read end-to-end in five minutes.
PRs that add a second harness's emitter are explicitly welcome — that's how this stops being a one-vendor tool.
- m1 — parse_session. Stream the JSONL and emit per-turn records carrying the real
message.usagetotals plus typed content blocks with locally-estimated weights. - m2 — attribute_buckets. Six-bucket classifier + per-turn reconciliation; session-level sum is exact, per-bucket splits are calibrated estimates.
- m3 — render_treemap. Flame-graph-style tree to a true-color terminal, plus
--jsonemittingallocation_v1.json. - v0.2 — BPE tokenizer. Replace
chars/4with a real Claude-family tokenizer for tighter pre-reconciliation estimates. - v0.2 — second-harness parser. Codex or Aider, depending on which OSS maintainer says yes first.
- v0.3 — multi-session diff. "What changed between this run and last week's?"
- v0.3+ — CI mode. Fail a build if the context allocation exceeds a budget. Driven by inbound team-plan demand, not built speculatively.
Explicitly not on the roadmap for v0.1: web UI, real-time tail mode, cost-in-dollars conversion, auto-suggestions for token cuts, hosted SaaS. See §6 of the MVP plan if you want the full out-of-scope list.
This project will be shut down if, 30 days after the v0.1 tag:
- the repo has < 250 stars, AND
- fewer than 5 organic issues opened by non-launch-channel users, AND
- no community contributor has added a second harness parser.
Or earlier if Anthropic ships a native "what's in your context" status-line panel — at that point ctxprof pivots to a schema-only spec repo, and the binary is archived. Better to admit it than to keep a zombie around.
Issues and PRs welcome — especially:
- Classifier rules for tool names that should attribute differently (open an issue with a fixture).
- A second harness's parser (Codex / Aider / Cursor). Match the
allocation_v1.jsonschema and ctxprof renders the tree for free. - Real session fixtures (redacted) so the test suite catches edge cases other people hit first.
The classifier is in internal/attribute/classifier.go; tests are table-driven, so a new rule is one PR with one row.
MIT — do whatever you want, attribution appreciated, no warranty.
ctxprof — the context-budget profiler for Claude Code.
See which skills, MCP tools, files & reasoning ate your 200k window.
Single static Go binary. MIT. https://github.com/SuperMarioYL/ctxprof
After pushing the repo, set GitHub topics to help discovery:
gh repo edit --add-topic claude-code --add-topic mcp --add-topic profiler \
--add-topic context-window --add-topic developer-tools