tokc — token consumption analyzer for Claude Code skills
Measures what a skill actually costs you, tier by tier, and tells you where to cut.
29 skill(s) tokenizer: o200k_base x1.180 (default)
SKILL T1/sess T2/use T3 ALWAYS-ON TAX $/mo
------------------------------------------------------------------------------
project-artifact 241 5,415 8.5k ████████████████████████ ! $2.99
receipts 159 4,865 20k ████████████████ ! $2.40
hook-development 140 4,548 16k ██████████████ $2.20
...
------------------------------------------------------------------------------
TOTAL 2,787 99,352 294k $45.65
Always-on tax
2,787 tokens ride in the system prompt of every request, in every session,
whether or not any skill fires.
-> 2.9M billed token-equivalents/month at 200 sessions x 40 requests
Listing budget (characters, not tokens)
████████████████████████████████████████ 10,577 / 8,000 chars (132%)
window 200,000 tokens x 4 chars/token -- model `sonnet`, ~/.claude/settings.json
! 2,577 chars over budget -- 6 skill(s) ship as `- name`, description omitted
demoted, least-used first: plugin-structure, project-artifact, receipts, ...
Why this exists
Existing tools estimate skill cost by asking Claude — which costs tokens and gives
different numbers each run — or by running tiktoken over SKILL.md, which
undercounts Claude by 15-20% and, worse, reports a single number for something
that is not a single cost.
A skill is loaded in three tiers, billed at wildly different frequencies:
| Tier | What | Paid |
|---|---|---|
| T1 | frontmatter name + description (+ whenToUse, which the listing appends) |
in the system prompt of every request, every session |
| T2 | the SKILL.md body |
once per invocation |
| T3 | references/, scripts/, … |
only when Claude actually reads the file |
The number that matters is almost never the one people optimize. A 250-token
description is re-sent on every request of every session — with prompt caching that
is a ×5.15 multiplier per session (1.25× cache write, then 0.10× per read).
A 6,000-token body that fires five times a month costs less than a 150-token
description that ships 200 times. tokc computes both and ranks accordingly.
Install
pip install tokc # or: uvx tokc scan
Zero hard dependencies. Three optional extras, none required:
pip install "tokc[fast]" # tiktoken — much better offline counts
pip install "tokc[exact]" # anthropic — enables `--exact` and `tokc calibrate`
pip install "tokc[yaml]" # pyyaml — frontmatter edge cases
Without them tokc falls back to a pure-python estimator and a minimal YAML
parser. Python 3.10+, Linux / macOS / Windows.
From source:
git clone https://github.com/Shult/token_consumer && cd token_consumer
pip install -e ".[dev]" # tiktoken + pyyaml + pytest
What leaves your machine
Nothing, unless you ask for it. Worth stating plainly, because tokc reads
files that are more personal than the skills themselves:
What it reads locally. Your skill directories; settings.json for
skillOverrides and listing settings; ~/.claude.json for the per-skill usage
counts that drive the listing order; your installed Claude Code build, to read
its own defaults out of the settings schema. tokc setup additionally scans
transcripts under ~/.claude. From those it keeps four things and nothing else:
the session count, distinct requestIds, the names of invoked skills (from
Skill / SlashCommand tool-use blocks) and the recorded skill_listing
attachment. Prompts, responses and tool output are never retained, and what is
collected never leaves the summary figures written to your config file.
What it sends. Only --exact and tokc calibrate open a network
connection. They post skill text — frontmatter and body — to Anthropic's
count_tokens endpoint, using your own credentials, and get a token count back.
Every other command, and every default, is fully offline. --no-config ignores
saved answers entirely.
What it writes. ./.tokc.json or ~/.claude/tokc.json (tokc setup), a
baseline at ./.tokc-baseline.json (tokc snapshot), and the HTML file you
name (tokc report -o). Nothing else on disk is modified — tokc never edits
your skills, it only tells you what to cut.
Both config files record local paths and usage figures, so keep them out of version control:
.tokc.json
.tokc-baseline.json
Commands
tokc setup # detect your harness, measure your usage, save both
tokc scan # every skill, ranked by always-on tax
tokc audit <name|path> # one skill: tiers, body heatmap, findings
tokc budget # simulate the system-prompt listing budget
tokc snapshot # record a baseline
tokc diff # what changed since the baseline
tokc watch <path> # live token counter while you edit
tokc report -o out.html # self-contained HTML report
tokc calibrate # measure the offline estimator against the real API
With no path, tokc scans the same roots Claude Code does: ~/.claude/skills,
<project>/.claude/skills, and installed plugin skills.
tokc setup — stop guessing
The numbers below only mean something if they match your harness and your
usage. tokc setup works both out and writes them to ./.tokc.json (or
~/.claude/tokc.json with --user), which every other command then reads.
tokc setup # detect, measure, confirm
tokc setup -y # accept everything detected
tokc setup --harness other # a harness tokc cannot inspect: ask, don't assume
Four things, in descending order of trust:
- A listing your harness actually sent. Transcripts record the
skill_listingattachment verbatim, so we compare our reconstruction line by line against the real thing — and recover the characters spent by skills bundled inside the harness, which never appear on disk but still eat the budget first. - The installed build's own settings schema.
skillListingMaxDescCharsandskillListingBudgetFractiondefaults are read out of the install (the VS Code extension's schema, or the CLI binary), not hard-coded — so an upgrade that changes them changestokctoo. - Your transcripts. Sessions, API requests per session (counted by distinct
requestId, not by message) and per-skill invocations over the last 30 days, replacing the 200 / 40 / 20 defaults. - You. For a harness that cannot be probed — Claude Desktop, claude.ai, a
third-party client — nothing is assumed.
tokcsays it does not know and asks whether the harness caps the listing at all.
Precedence runs weakest to strongest: probed defaults → ~/.claude/tokc.json →
./.tokc.json → the live settings.json chain → SLASH_COMMAND_TOOL_CHAR_BUDGET
→ command-line flags. --no-config ignores the saved answers entirely.
Tuning the cost model
Every projection is driven by explicit, overridable assumptions:
tokc scan --sessions 400 --requests 60 --invocations 10 --model claude-sonnet-5
tokc scan --no-cache # price without prompt caching (worst case)
tokc scan --worst-case # size the listing budget against the 200k window
tokc scan --context-window 1000000 # state the window outright
Accuracy
Three counting backends, picked automatically:
--exact— Anthropic'scount_tokensendpoint. Exact. Needs credentials (ANTHROPIC_API_KEY, or anant auth loginprofile).- tiktoken —
o200k_base× a calibration factor (default 1.18, the midpoint of Anthropic's stated 15-20% undercount). Offline. - heuristic — pure python, no dependencies. Roughly ±10%.
tokc calibrate measures backend 2 against backend 1 on your own skills and
stores the corrected factor, so subsequent offline runs are accurate for your corpus.
What it flags
Findings carry the tier they belong to and a savings estimate at the right rate — a T1 win is multiplied by sessions, a T2 win by invocations.
Tier 1 — the every-session tax
T1-DESC-MISSING/T1-DESC-BLOAT/T1-DESC-TRUNCATED— overskillListingMaxDescCharsthe harness cuts mid-sentence and appends an ellipsis, silently losing any trigger keywords at the end. The cap applies todescriptionandwhenToUsetogether, since the listing shows the pair.T1-HIDDEN/T1-NAME-ONLY—skillOverridesinsettings.jsontook this skill out of the listing, or left it there without its description. Reported so a zero is not mistaken for a saving.T1-NO-TRIGGER— nothing says when to use the skill, so you pay the tax and never get the benefit. (Recognises English and French trigger phrasing.)T1-FILLER— "this skill is used to", "in order to", …
Tier 2 — the per-invocation cost
T2-BODY-LARGE,T2-SECTION-HOG— one section dominating the body is the extraction candidate.T2-CODE-BLOCK,T2-BIG-TABLE— reference material paid on every invocation.T2-REPEAT— lines repeated 3+ times (code fences excluded).T2-HTML-COMMENT— invisible when rendered, fully billed to the model.T2-NO-STRUCTURE,T2-WHITESPACE.
Tier 3 — on-demand files
T3-UNREFERENCED— a bundled fileSKILL.mdnever points at is unreachable.T3-HUGE— reading it will force compaction mid-task.
CI
tokc budget --strict # exit 1 if the listing overflows and skills get dropped
tokc audit my-skill --strict # exit 1 on any HIGH finding
tokc diff --strict # exit 1 if the always-on tax grew
tokc scan --json # machine-readable
How the listing budget actually works
Worth stating precisely, because it is easy to get wrong in three ways at once:
- The budget is a character count, not a token count. It is derived from the
context window —
window(tokens) × charsPerToken × fraction, so200 000 × 4 × 0.01 = 8 000by default — but what it caps is the length of the assembled listing text. A model the harness does not recognise converts at 3 chars/token instead of 4, and the budget shrinks to 6 000. - The window is the running model's, resolved per session. Not the plan's,
and not a constant. On a 1M-context model the same formula gives
1 000 000 × 4 × 0.01 = 40 000— five times the budget, from the same account on the same machine. Assume 200k on a 1M session and a listing at 46% of budget reads as 229% over, complete with a list of skills it says you are about to lose.tokcdetects the window (below) instead of assuming it. - Nothing is dropped. An entry that does not fit ships as
- namewith no description. The skill stays listed and stays invocable by name; what it loses is the text the model selects on. - The order is neither arbitrary nor "largest first". Entries are ranked by
usageCount × max(0.5^(days_since_use / 7), 0.1)— recorded in~/.claude.json— and the best-ranked keep their descriptions, taken greedily, so a small entry can still fit after a large one was refused. Skills bundled inside the harness are exempt and consume the budget before any of yours.
tokc reproduces all four, reads the real usage ranking, and tokc setup
checks the result against a listing your harness genuinely sent.
Which context window
The budget swings by 5× on this, so it is detected and always shown, never assumed silently:
Listing budget (characters, not tokens)
████████████████████ 18,341 / 40,000 chars (46%)
window 1,000,000 tokens x 4 chars/token -- model `opus[1m]`, ~/.claude/settings.json
`--worst-case` prices the 8,000-char fallback you drop to if your 1M credits run out
Sources, weakest to strongest:
-
The 200 000 default, reported as assumed rather than as a finding.
-
The model your settings chain selects. A model id carrying the
[1m]marker is unconditionally a 1M window — that rule is static in the build, so it can be reproduced offline. -
CLAUDE_CODE_MAX_CONTEXT_TOKENS, which the build honours alongsideDISABLE_COMPACT. -
A listing your harness actually sent. The harness only shortens a listing that does not fit, so one that shipped with every description intact is proof the budget was at least that long — and a proof beats an inference. This is what catches the case rule 2 cannot see: a model flagged
native_1m, or the 1M beta header, both decided against a capability table fetched at runtime and therefore unreadable from the install. When a real listing contradicts the computed budget,tokcsays so and corrects itself:! assumed a 200,000-token window (8,000 chars), but the harness let a 8,389-char listing through untouched -- corrected to 1,000,000 tokens
--context-window states it outright. --worst-case forces the 200 000
fallback — which is not a hypothetical: if your 1M-context credits run out, the
build drops back to it mid-session, and your budget with it.
Caveats
- The T1 multiplier assumes 40 requests/session and a 5-minute cache TTL.
Override with
--requests, or lettokc setupmeasure it; use--no-cachefor the uncached worst case. - T3 is excluded from projections. Reference files load unpredictably, and counting them would penalise exactly the progressive-disclosure structure this tool tells you to adopt. They are reported, not projected.
- Skills bundled in the harness are invisible on disk. Without
tokc setupthe budget simulation ignores them and is optimistic by a few thousand characters. Setup recovers the figure from a real listing. - The context window is detected, not guaranteed. Two of the harness's three
routes to a 1M window are decided against a capability table fetched at
runtime, so they cannot be read off the install;
tokccatches those from a real listing instead, which needs one to exist. And the window is not stable: exhaust your 1M-context credits and the build falls back to 200k mid-session, taking your budget from 40 000 to 8 000.--worst-caseprices that. - Only Claude Code is modelled. The CLI and the VS Code extension share one
binary and one set of rules, read from the install. Claude Desktop, claude.ai
and third-party clients assemble the system prompt elsewhere;
tokcwill not pretend to know their policy, andtokc setup --harness otherasks instead. - Offline counts are estimates until you run
tokc calibrate.
Tests
python -m pytest -q # 37 tests
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tokc-0.2.1.tar.gz.
File metadata
- Download URL: tokc-0.2.1.tar.gz
- Upload date:
- Size: 64.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3624535dd9d312dbdb83ed8b0a0f1cfb9db0e1f5a6feb198823dc7e5b2c41a4c
|
|
| MD5 |
db65ccb29cf3e49edc8f078857c45ef5
|
|
| BLAKE2b-256 |
ced4fb7e7b4ad6c2556201435b303c93182b34a65d225e4435dac832a91403b3
|
Provenance
The following attestation bundles were made for tokc-0.2.1.tar.gz:
Publisher:
release.yml on Shult/token_consumer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tokc-0.2.1.tar.gz -
Subject digest:
3624535dd9d312dbdb83ed8b0a0f1cfb9db0e1f5a6feb198823dc7e5b2c41a4c - Sigstore transparency entry: 2568690203
- Sigstore integration time:
-
Permalink:
Shult/token_consumer@7b531272947026c9273b40783735cf8b243012ad -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/Shult
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7b531272947026c9273b40783735cf8b243012ad -
Trigger Event:
push
-
Statement type:
File details
Details for the file tokc-0.2.1-py3-none-any.whl.
File metadata
- Download URL: tokc-0.2.1-py3-none-any.whl
- Upload date:
- Size: 59.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98e904525e1fceeef34e6ec133788d6ea76c300178059d8645ff3019ad833694
|
|
| MD5 |
9a10c4b98ca4eff06fd73779a3e28a61
|
|
| BLAKE2b-256 |
566bc1ba8d0cc36f7248b1a14a9502cae3f24b50a4e8f3f1781ff6f4e30a6a17
|
Provenance
The following attestation bundles were made for tokc-0.2.1-py3-none-any.whl:
Publisher:
release.yml on Shult/token_consumer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tokc-0.2.1-py3-none-any.whl -
Subject digest:
98e904525e1fceeef34e6ec133788d6ea76c300178059d8645ff3019ad833694 - Sigstore transparency entry: 2568690208
- Sigstore integration time:
-
Permalink:
Shult/token_consumer@7b531272947026c9273b40783735cf8b243012ad -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/Shult
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7b531272947026c9273b40783735cf8b243012ad -
Trigger Event:
push
-
Statement type: