Repository navigation
Commands
Every command prints plain text and exits 0, except where noted. Token counts are estimates: characters divided by four.
Answers "did this file change since I last read it?" so the agent can skip a re-read, or read only the diff.
python tw.py changed notes.mdThe first time, there is no snapshot yet:
new notes.md (3 lines, ~5 tokens): no snapshot yet; read it once
Run it again with no change:
unchanged notes.md
all unchanged: skip the re-read
After the second line was changed to TWO:
changed notes.md (2 changed lines; diff ~15 tokens vs whole file ~5)
@@ -1,3 +1,3 @@
one
-two
+TWO
three
A path that does not exist prints missing nope.md and the command still exits 0.
Options:
-
--snapshots DIR: where snapshots live. Default.tokenwright/snapshotsin the current folder, or$TOKENWRIGHT_SNAPSHOTS. -
--context N: diff context lines. -
--quiet: status lines only, no diff. -
--dry-run: compare without updating the snapshot.
Details the help does not say:
- A file counts as unchanged when its size and modification time match the snapshot. If only the time moved, the content hash decides, and it prints
unchanged ... (touched, same content). - Files over 2,000,000 bytes get a hash but no stored copy, so a later change says
changed ... (binary or no stored copy; read it). Binary files say the same. - Every run updates the snapshot, unless
--dry-run. - For a tiny file the diff can cost more than the file, as above. Before 0.2.1 the line count was one too high for a file ending in a newline.
Ranks the token sinks in a Claude Code session transcript (JSONL). With no path it reads the newest file under ~/.claude/projects.
Example on a synthetic 5-turn transcript that read a 500-line file whole on turns 1 and 3:
python tw.py score session.jsonltranscript session.jsonl
turns 5, tool calls 2; input 50, cache read 5000, cache write 500, output 100 tokens
estimated avoidable ~2196 tokens entered context; carried on later turns as ~6588 cache-read tokens (billed at about a tenth of input)
whole-read ~1098
repeat read ~1098
top sinks:
~ 1098 #1 whole-read Read src/app.py whole (500 lines)
fix: search first (Grep -n with -C), then Read offset/limit; or readwright outline
~ 1098 #2 repeat read Read src/app.py again (2 reads)
fix: keep the facts from the first read, or `tw.py changed` to skip unchanged files
The second read counts once, as a repeat read. Before 0.2.1 it was also listed as a whole read and counted twice.
The sinks it looks for:
| Kind | When |
|---|---|
| whole-read | the first Read of a file with no offset or limit returned 400 lines or more |
| repeat read | the same file read again |
| re-read after own edit | read again after the agent's own edit |
| shell-cat |
cat, type or Get-Content printed a whole file |
| big-output | any other tool returned 20,000 characters or more |
| retry | a call failed again with the same input |
| poll | the same shell command ran a third time or more |
| debug-loop | three failed calls in a row to one tool (once per streak) |
"Carried" is each sink multiplied by the turns left after it. An image Read counts as about 1,600 tokens.
Options: --top N (how many sinks to list), --json (includes waste_per_100_calls), --compare N.
--compare N (0.2.7) scores the newest N other transcripts in the same project folder, skips those with fewer than 10 tool calls, and adds one line: this session's estimated waste per 100 tool calls next to the best and the median of those sessions.
compare: ~6171 waste tokens per 100 calls; best of 16 past sessions here ~0 (2cd2f88d-....jsonl), median ~8449
Finds shell command sequences that repeat across sessions: candidates for a script.
python tw.py repeats s0.jsonl s1.jsonl s2.jsonl --top 13 transcripts scanned; 1 sequences repeat in >= 3 sessions
3 sessions, 3 steps -> script candidate (see tokenwright-script):
git status
python -m unittest discover -s tests
git push
Before comparing, it drops a leading cd DIR &&, and replaces quoted strings with <s>, numbers with <n> and paths with <path>. Only Bash and PowerShell calls count. With no paths, --last N scans the newest N transcripts. --min-len, --max-len and --min-sessions set the sequence lengths and how many sessions must share one.
Prices a skill, or every skill under a folder, in three parts.
python tw.py skill demo-skillskill always on-trigger on-demand
demo 10 29 1003
always = name + description, paid by every session where the skill is listed
savings, largest first:
~ 0 demo: references/orphan.md (~1000 tokens) is never named in SKILL.md, so the agent cannot find it on demand: link it with when to read it, or delete it
It flags a description over 600 characters, a body over 500 lines, code blocks of 25 lines or more, 15 or more numbered steps with no scripts/ folder, and files over 2,000 bytes that nothing links. --json prints the report as JSON.
python tw.py estimate --total-only big.log notes.md 10505 total (~tokens)
A folder is walked recursively.
Forwards everything after catalog to skills/tokenwright-catalog/scripts/tidy.py. Added in 0.2.0. Its commands: scan, lint, conflicts, triggers, check, apply, sections, split, park, unpark, parked, budget, usage, offload, startup, harvest, version. Each takes --agent claude|codex|copilot|cursor|all, --project PATH and --json.
With two user skills whose descriptions overlap:
python tw.py catalog conflicts1 pairs at TF-IDF cosine >= 0.45 among 2 listed skills (0.65+ near-duplicate)
0.63 feed-scan ~ radar-sweep
shared words: feed, track, news, articl, databas, note, summarise, trend
boundary for feed-scan: To sweep news feeds every morning for articles about databases, use radar-sweep.
boundary for radar-sweep: To collect articles from news feeds about databases, use feed-scan.
python tw.py catalog lintWARN ST019 feed-scan: description is 0.63 similar to radar-sweep and has no boundary clause; end with one short 'Not for <their job> (that is radar-sweep)'
WARN ST019 radar-sweep: description is 0.63 similar to feed-scan and has no boundary clause; end with one short 'Not for <their job> (that is feed-scan)'
2 skills checked; 2 findings at warn or above
apply writes a checked description and keeps a backup under ~/.skill-tidy/backups/; when that folder cannot be written, the backup goes beside the file as SKILL.md.bak-<time>. The state folder keeps its skill-tidy name so parking lots made with skill-tidy keep working.
Added in 0.2.7. Prints the tool output the post hook has counted for a session, short enough for a status line. With no argument it reads the most recent session. It stays at tw ~0k unless TOKENWRIGHT_HOOKS is set, because only the hook counts.
python tw.py metertw ~30k
The counts live in tokenwright-meter/ under the system temp folder, one small JSON file per session; set TOKENWRIGHT_METER_DIR to move them.
The entry point for the plugin's hooks. It reads the hook event as JSON on stdin. See Hooks.
This wiki describes tokenwright 0.2.12 and was last updated on 2026-11-01. tokenwright is MIT licensed. Report problems in the issues.
Using it
The releases
Contributing
Elsewhere