tokenfishing 0.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. tokenfishing-0.9.0/LICENSE +21 -0
  2. tokenfishing-0.9.0/PKG-INFO +523 -0
  3. tokenfishing-0.9.0/README.md +502 -0
  4. tokenfishing-0.9.0/pyproject.toml +41 -0
  5. tokenfishing-0.9.0/setup.cfg +4 -0
  6. tokenfishing-0.9.0/tests/test_aggregate.py +523 -0
  7. tokenfishing-0.9.0/tests/test_i18n.py +96 -0
  8. tokenfishing-0.9.0/tests/test_parser.py +157 -0
  9. tokenfishing-0.9.0/tests/test_plan_usage.py +212 -0
  10. tokenfishing-0.9.0/tests/test_state.py +234 -0
  11. tokenfishing-0.9.0/tests/test_statusline.py +127 -0
  12. tokenfishing-0.9.0/tests/test_themes.py +240 -0
  13. tokenfishing-0.9.0/tokenfishing/__init__.py +18 -0
  14. tokenfishing-0.9.0/tokenfishing/__main__.py +8 -0
  15. tokenfishing-0.9.0/tokenfishing/aggregate.py +568 -0
  16. tokenfishing-0.9.0/tokenfishing/config.py +97 -0
  17. tokenfishing-0.9.0/tokenfishing/i18n.py +148 -0
  18. tokenfishing-0.9.0/tokenfishing/parser.py +208 -0
  19. tokenfishing-0.9.0/tokenfishing/paths.py +90 -0
  20. tokenfishing-0.9.0/tokenfishing/plan_usage.py +298 -0
  21. tokenfishing-0.9.0/tokenfishing/popup.py +566 -0
  22. tokenfishing-0.9.0/tokenfishing/render.py +242 -0
  23. tokenfishing-0.9.0/tokenfishing/state.py +225 -0
  24. tokenfishing-0.9.0/tokenfishing/statusline.py +331 -0
  25. tokenfishing-0.9.0/tokenfishing/themes.py +1336 -0
  26. tokenfishing-0.9.0/tokenfishing.egg-info/PKG-INFO +523 -0
  27. tokenfishing-0.9.0/tokenfishing.egg-info/SOURCES.txt +28 -0
  28. tokenfishing-0.9.0/tokenfishing.egg-info/dependency_links.txt +1 -0
  29. tokenfishing-0.9.0/tokenfishing.egg-info/entry_points.txt +3 -0
  30. tokenfishing-0.9.0/tokenfishing.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sozerong
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,523 @@
1
+ Metadata-Version: 2.4
2
+ Name: tokenfishing
3
+ Version: 0.9.0
4
+ Summary: An always-on-top pixel-art window showing your Claude Code usage
5
+ Author: sozerong
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/sozerong/token_fishing
8
+ Project-URL: Source, https://github.com/sozerong/token_fishing
9
+ Keywords: claude-code,token,usage,monitor,pixel-art
10
+ Classifier: Environment :: X11 Applications
11
+ Classifier: Environment :: Win32 (MS Windows)
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Utilities
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Dynamic: license-file
21
+
22
+ # token fishing
23
+
24
+ *An always-on-top pixel-art window that shows how much Claude Code you have used.*
25
+
26
+ <img src="https://raw.githubusercontent.com/sozerong/token_fishing/main/docs/demo-fishing.gif" width="350" alt="Fishing theme: fish accumulate as tokens are spent">
27
+
28
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
29
+ [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
30
+ [![Dependencies](https://img.shields.io/badge/runtime%20dependencies-none-brightgreen)](pyproject.toml)
31
+
32
+ How much have you spent in this 5-hour window, how fast are you burning it, and when
33
+ does it reset? `tokenfishing` answers all three in a small window that stays on top of
34
+ your editor. No dashboard to open, no browser tab to keep around.
35
+
36
+ | What you see | What it means |
37
+ |---|---|
38
+ | Number of things moving around | Tokens spent in the current 5-hour window |
39
+ | How fast they move | Burn rate (tokens per minute) |
40
+ | Height of the sun | Time left until the window resets — high at noon, low at dusk |
41
+ | Things gathered near the base | In *depletion* mode, how much of the window is already gone |
42
+
43
+ The tier climbs through five steps as the window fills. In the fishing theme that is
44
+ **empty basket → small fry → half basket → full basket → full boat**.
45
+
46
+ - **No runtime dependencies** — standard library only
47
+ - **Nothing leaves your machine** — every byte is read and rendered locally
48
+ - **Official numbers when available** — usage percentage and reset time come straight
49
+ from what Claude reports, not from a guess
50
+
51
+ ---
52
+
53
+ ## Table of contents
54
+
55
+ - [Themes](#themes)
56
+ - [Fishing spots](#fishing-spots)
57
+ - [Display modes](#display-modes)
58
+ - [Language](#language)
59
+ - [Requirements](#requirements)
60
+ - [Installation](#installation)
61
+ - [Updating](#updating)
62
+ - [Uninstalling](#uninstalling)
63
+ - [Usage](#usage)
64
+ - [Accuracy: where the numbers come from](#accuracy-where-the-numbers-come-from)
65
+ - [Configuration](#configuration)
66
+ - [Troubleshooting](#troubleshooting)
67
+ - [What it shows, and what it refuses to show](#what-it-shows-and-what-it-refuses-to-show)
68
+ - [How it works](#how-it-works)
69
+ - [Development](#development)
70
+ - [License](#license)
71
+
72
+ ---
73
+
74
+ ## Themes
75
+
76
+ The left button at the bottom of the window cycles through eight themes. Your choice is
77
+ remembered.
78
+
79
+ ![Eight themes](https://raw.githubusercontent.com/sozerong/token_fishing/main/docs/themes.png)
80
+
81
+ <img src="https://raw.githubusercontent.com/sozerong/token_fishing/main/docs/demo-themes.gif" width="350" alt="Cycling through the eight themes">
82
+
83
+ | Theme | Sky | Ground | Landmark | What is counted | When the window drains |
84
+ |---|---|---|---|---|---|
85
+ | Fishing | Sun | Rolling waves | Boat / pier / breakwater … | Fish | Catch fills a landing net |
86
+ | Village | Sun and clouds | Meadow | House | Villagers | New houses go up beside it |
87
+ | Ranch | Sun and clouds | Meadow | Barn | Animals | A round corral fills up |
88
+ | Space | Starfield and moon | *(none — open galaxy)* | Rocket | Stars | The rocket's flame grows |
89
+ | Garden | Sun and clouds | Meadow | Greenhouse | Flowers | A round flower bed fills up |
90
+ | Mine | Rock ceiling and lamp | Sleepers and rails | Mine shaft | Ore | An ore cart fills up |
91
+ | City | Scrolling skyline | Two-lane road | Tower | Cars | More buildings light up |
92
+ | Apiary | Sun and clouds | Flower meadow | Two hives and a smoker | Bees | *(fill only — see below)* |
93
+
94
+ **A theme only changes pictures and wording.** Every theme shares the same tier
95
+ thresholds, so the same usage always maps to the same tier no matter which one you pick.
96
+ This is enforced by `tests/test_themes.py`.
97
+
98
+ ## Fishing spots
99
+
100
+ The fishing theme has its own background selector — a third button appears while the
101
+ fishing theme is active. Each spot changes the water colour, the sky, the props, and
102
+ which fish you catch.
103
+
104
+ | Spot | Setting | Props |
105
+ |---|---|---|
106
+ | Open sea | Deep blue water | Mast and flag, life ring, passing ship, gulls |
107
+ | Pier | Green harbour water | Lamp post, crates, coiled rope, gulls |
108
+ | Rocky shore | Cold, deep water | Seaweed, breaking spray, gulls |
109
+ | Breakwater | Steel-blue water | Red-striped lighthouse, tetrapods, gulls |
110
+ | Island | Tropical turquoise | Palm tree with coconuts, beach ball, shells, gulls |
111
+ | Car camping | Lakeside — land on the left | Open tailgate, camping chair, hanging lantern |
112
+ | Tent camping | Lakeside — land on the left | Tent, campfire with a pot, hanging lantern |
113
+
114
+ ## Display modes
115
+
116
+ The right button switches between the two.
117
+
118
+ | Mode | Behaviour |
119
+ |---|---|
120
+ | Accumulate | The screen fills up as you spend. *"How much have I used?"* |
121
+ | Depletion | Starts full and empties as you spend. *"How much is left?"* |
122
+
123
+ The apiary has no depletion mode — a hive is something you fill, not something you empty
124
+ — so the button hides while that theme is on screen. Your choice is kept, and comes back
125
+ as soon as you move to another theme.
126
+
127
+ ## Language
128
+
129
+ The window speaks English or Korean. It follows your system language on first run, and
130
+ you can pin it:
131
+
132
+ ```bash
133
+ tokenfishing --lang en
134
+ tokenfishing --lang ko
135
+ ```
136
+
137
+ The choice is remembered, so you only pass it once. It covers everything on screen — theme
138
+ names, tier labels, the buttons, the window title — and the statusline the hook
139
+ draws inside Claude Code.
140
+
141
+ ---
142
+
143
+ ## Requirements
144
+
145
+ - Python 3.11 or newer
146
+ - tkinter (the standard-library GUI module)
147
+ - Claude Code history in `~/.claude/projects/`
148
+
149
+ Check what you have first:
150
+
151
+ ```bash
152
+ python3 -c "import sys, tkinter; print(sys.version)"
153
+ ```
154
+
155
+ If that prints `ModuleNotFoundError: No module named 'tkinter'`, install it:
156
+
157
+ | Platform | Command |
158
+ |---|---|
159
+ | Debian / Ubuntu | `sudo apt install python3-tk` |
160
+ | Fedora / RHEL | `sudo dnf install python3-tkinter` |
161
+ | Arch | `sudo pacman -S tk` |
162
+ | macOS (Homebrew Python) | `brew install python-tk` |
163
+ | macOS (python.org build) | already included |
164
+ | Windows | already included |
165
+
166
+ ---
167
+
168
+ ## Installation
169
+
170
+ ### With pip
171
+
172
+ ```bash
173
+ pip3 install tokenfishing
174
+ tokenfishing --install-statusline
175
+ ```
176
+
177
+ Two commands land on your `PATH`: `tokenfishing` (the window) and `tokenfishing-console`
178
+ (plain text). The second line registers the Claude Code statusline hook — without it the
179
+ window falls back to an estimate, so do not skip it.
180
+
181
+ Prefer an isolated install, or want to run it without installing at all:
182
+
183
+ ```bash
184
+ pipx install tokenfishing # isolated from the rest of your Python
185
+ uvx tokenfishing # run it once, install nothing
186
+ ```
187
+
188
+ To track `main` instead of the last release, install
189
+ `git+https://github.com/sozerong/token_fishing.git` under any of the commands above.
190
+
191
+ > If your shell answers `command not found: tokenfishing` after a `pip3 install --user`,
192
+ > the user script directory is not on your `PATH`. Either add
193
+ > `python3 -m site --user-base`'s `bin` (Windows: `Scripts`) to it, use `pipx`, or run the
194
+ > installer script below, which does that part for you.
195
+
196
+ ### With the installer script
197
+
198
+ Use this if you would rather not touch `PATH` yourself. It picks a Python 3.11+
199
+ interpreter, warns you if tkinter is missing (with the exact command for your platform),
200
+ installs through `pipx` if you have it and `pip --user` otherwise, **adds the install
201
+ directory to your `PATH`** (in your shell rc file on macOS/Linux, in your user environment
202
+ on Windows, and in the current session so it works immediately), and registers the
203
+ statusline hook.
204
+
205
+ ```bash
206
+ git clone https://github.com/sozerong/token_fishing.git
207
+ cd token_fishing
208
+ bash install.sh
209
+ ```
210
+
211
+ ```powershell
212
+ git clone https://github.com/sozerong/token_fishing.git
213
+ cd token_fishing
214
+ powershell -ExecutionPolicy Bypass -File install.ps1
215
+ ```
216
+
217
+ > **Do not run the installer with `sudo`** (or from an elevated PowerShell). Everything
218
+ > this tool touches lives in your own home directory — `~/.claude`. Installing as root
219
+ > would register the statusline hook in *root's* home, and the tool would never find your
220
+ > usage data. The script refuses to run as root for exactly this reason.
221
+
222
+ For hacking on it, install the checkout in place with `pip3 install -e .`, or just run
223
+ `python3 -m tokenfishing` from the repository root.
224
+
225
+ ## Updating
226
+
227
+ ```bash
228
+ pip3 install --upgrade tokenfishing
229
+ tokenfishing --install-statusline
230
+ ```
231
+
232
+ With pipx it is `pipx upgrade tokenfishing`. If you installed from the git URL, add
233
+ `--force-reinstall` — a git URL carries no version for pip to compare, so `--upgrade`
234
+ alone would decide you are already up to date.
235
+
236
+ Re-register the hook after every update: it stores an **absolute path** to the installed
237
+ file, and an update that moves the install location would otherwise leave Claude Code
238
+ running a file that no longer exists. If you installed with the script, `bash install.sh
239
+ update` (Windows: `powershell -File install.ps1 update`) pulls, reinstalls and re-registers
240
+ in one step.
241
+
242
+ ## Uninstalling
243
+
244
+ ```bash
245
+ tokenfishing --uninstall-statusline
246
+ pip3 uninstall tokenfishing # or: pipx uninstall tokenfishing
247
+ ```
248
+
249
+ The first line unregisters the statusline hook from `~/.claude/settings.json`, leaving the
250
+ rest of your settings alone. Run it **before** removing the package — afterwards the
251
+ command is gone. Two files are left behind for you to delete if you want them gone:
252
+
253
+ ```bash
254
+ rm ~/.claude/tokenfishing-config.json ~/.claude/tokenfishing-limits.json
255
+ ```
256
+
257
+ `bash install.sh uninstall` (Windows: `powershell -File install.ps1 uninstall`) does all of
258
+ that, plus taking the `PATH` entry back out.
259
+
260
+ Your Claude Code transcripts under `~/.claude/projects` are never touched.
261
+
262
+ ### Using it on more than one machine
263
+
264
+ Install on each machine — the transcripts it reads are local, so there is nothing to
265
+ sync. See [Accuracy](#accuracy-where-the-numbers-come-from) for what does and
266
+ does not match across machines.
267
+
268
+ ---
269
+
270
+ ## Usage
271
+
272
+ ```bash
273
+ tokenfishing
274
+ ```
275
+
276
+ An always-on-top window opens and refreshes every 10 seconds. Close the window to quit.
277
+
278
+ ### Command-line options
279
+
280
+ ```
281
+ tokenfishing [options]
282
+
283
+ -d, --detach run in the background and return the shell immediately
284
+ --lang ko|en language for the window (remembered; defaults to your system)
285
+ --debug print diagnostics to stderr
286
+ --doctor diagnose the usage data sources and exit
287
+ --install-statusline
288
+ register the Claude Code statusline hook
289
+ -V, --version print the version
290
+ -h, --help print this help
291
+ ```
292
+
293
+ Use `-d` to keep your terminal free. On Windows it re-launches through `pythonw` so no
294
+ console window tags along; on macOS and Linux it detaches into a new session and survives
295
+ closing the terminal.
296
+
297
+ ```bash
298
+ tokenfishing -d
299
+ # running in background (PID 18556)
300
+ ```
301
+
302
+ To stop it, close the window or kill the process:
303
+
304
+ ```bash
305
+ pkill -f "tokenfishing" # macOS / Linux
306
+ taskkill /F /IM pythonw.exe # Windows
307
+ ```
308
+
309
+ ### Console output
310
+
311
+ For a terminal-only summary:
312
+
313
+ ```bash
314
+ tokenfishing-console
315
+ ```
316
+
317
+ ---
318
+
319
+ ## Accuracy: where the numbers come from
320
+
321
+ Everything is derived from the JSONL transcripts Claude Code writes under
322
+ `~/.claude/projects/`. Parsing them correctly is the whole point of this project, and the
323
+ parser is covered item-by-item (input / output / cache-write / cache-read) by the tests in
324
+ `tests/`, on hand-written fixtures that pin down each of the ways a naive reader gets the
325
+ number wrong.
326
+
327
+ The **usage percentage and reset time** can come from three places, and the window title
328
+ always tells you which one is in play:
329
+
330
+ | Source | Title shows | How exact |
331
+ |---|---|---|
332
+ | Statusline hook | `official·hook` | **Exact**, as of the last time Claude Code drew its statusline |
333
+ | Desktop app history | `official·app` | Exact as of the app's last write (roughly every 15 minutes) |
334
+ | Neither | `estimate (no official numbers)` | Estimated from a learned limit — may drift |
335
+
336
+ Whichever of the two was captured **more recently** wins, and the 5-hour and weekly figures
337
+ always come from the same capture so they never describe two different moments. If the
338
+ number is more than 15 minutes old, the title says how old — `official·hook 47분 전` — so a
339
+ figure that looks out of date is visibly out of date rather than quietly wrong.
340
+
341
+ Run `tokenfishing --install-statusline` once to register the hook. This is the
342
+ recommended setup: **numbers taken through the Claude Code CLI hook are exact.**
343
+
344
+ Claude Code has only one statusline slot. If something else already owns it — a plugin,
345
+ your own script — the installer does not take it: it **chains**, so your existing
346
+ statusline keeps drawing exactly what it drew before while this tool reads the official
347
+ numbers off the same input. Uninstalling puts the original command back. If the original
348
+ ever disappears (plugin paths are per-session), the chain falls back to printing this
349
+ tool's own line rather than an empty statusline.
350
+
351
+ ### Known sources of drift
352
+
353
+ - **Usage from the Claude desktop app, claude.ai on the web, or mobile counts against the
354
+ same 5-hour limit but leaves no trace in the local JSONL.** If a session was opened by
355
+ usage this tool cannot see, the estimated window start — and therefore the reset time —
356
+ can be off. Pin it with `TOKENFISHING_RESET_AT` when that matters.
357
+ - Estimated values are never presented as certain. They are prefixed with `~` and drawn
358
+ in a different colour.
359
+
360
+ ### Across several machines
361
+
362
+ Transcripts are written by the Claude Code CLI on the machine that ran it, and nothing
363
+ syncs them. So if you use the same account on a laptop and a desktop, each window shows a
364
+ different slice — but not of everything:
365
+
366
+ | Value | Same on every machine? |
367
+ |---|---|
368
+ | Usage percentage, time until reset, weekly percentage | **Yes** — reported per account |
369
+ | Token count, request count, burn rate, weekly tokens | **No** — only what that machine did |
370
+ | Theme and mode selection | **No** — the config file is local too |
371
+
372
+ There is no combined view. Install the statusline hook on **every** machine: without it a
373
+ machine falls back to estimating the limit from its own partial token count, which comes
374
+ out too low.
375
+
376
+ The percentages agree only as far as each machine's last capture goes. The hook updates
377
+ when Claude Code draws its statusline, so a machine you have not touched for hours is
378
+ still holding the figure from back then — correct for the account, but stale. That is why
379
+ the title prints the age once it passes 15 minutes: when two machines disagree, the one
380
+ with the fresher capture is the one to believe.
381
+
382
+ ### Does it work outside Korea?
383
+
384
+ Yes. Concretely:
385
+
386
+ - **Time zones.** The 5-hour window is computed entirely in UTC from UTC timestamps, so it
387
+ is correct in any time zone. The weekly figure uses your *local* midnight.
388
+ - **Locales and encodings.** Transcripts are read as UTF-8 with replacement, and console
389
+ output is forced to UTF-8, so a non-UTF-8 terminal code page will not crash it.
390
+ - **Paths.** `~/.claude` on macOS, Linux and Windows, plus `CLAUDE_CONFIG_DIR`, plus the
391
+ MSIX-redirected location the Windows Store build of the Claude desktop app writes to.
392
+ - **Weekly reset day.** Defaults to Tuesday, which is what one observed account used. This
393
+ varies per account rather than per country — set `TOKENFISHING_WEEKLY_RESET_DAY` if your
394
+ official screen says a different day.
395
+
396
+ ---
397
+
398
+ ## Configuration
399
+
400
+ Three settings are toggled in the window itself (theme, fishing spot, display mode) and
401
+ stored in `~/.claude/tokenfishing-config.json`. Everything else is an environment
402
+ variable:
403
+
404
+ | Variable | Effect |
405
+ |---|---|
406
+ | `TOKENFISHING_RESET_AT` | Pin the 5-hour reset time (e.g. `20:17`, or an ISO timestamp) when the estimate is off |
407
+ | `TOKENFISHING_WEEKLY_RESET_DAY` | Weekly reset weekday, `0` = Monday … `6` = Sunday. Default `1` (Tuesday) |
408
+ | `CLAUDE_CONFIG_DIR` | Look for Claude Code data somewhere other than `~/.claude` |
409
+
410
+ ---
411
+
412
+ ## Troubleshooting
413
+
414
+ **The title says `estimate (no official numbers)`.**
415
+ Neither source was found. Register the hook with `tokenfishing --install-statusline`, or
416
+ open the Claude desktop app once so it writes its usage history. Then run
417
+ `tokenfishing --doctor` to see exactly which files were found.
418
+
419
+ **The title says `official·app` and the reset time is marked `~`.**
420
+ Working as intended: the desktop app records percentages but not window boundaries, so the
421
+ reset time has to be estimated from that machine's own transcripts. The percentages are
422
+ account-wide and will agree with your other machines; the reset time will not. Install the
423
+ statusline hook on that machine to make it exact.
424
+
425
+ **Nothing appears / the window opens empty.**
426
+ There is no active 5-hour window — nothing has been sent recently. The window will say so.
427
+
428
+ **The reset time looks wrong.**
429
+ Most likely you used Claude somewhere this tool cannot see (web, mobile, desktop app).
430
+ Read the reset time off the official usage screen and pin it:
431
+
432
+ ```bash
433
+ TOKENFISHING_RESET_AT=20:17 tokenfishing
434
+ ```
435
+
436
+ **The weekly total looks wrong.**
437
+ Your account resets on a different weekday. Set `TOKENFISHING_WEEKLY_RESET_DAY`.
438
+
439
+ **Diagnostics.**
440
+
441
+ ```bash
442
+ tokenfishing --debug
443
+ # [tokenfishing] start fill=official official=app pct=64.0 left=270 mode=depletion
444
+ ```
445
+
446
+ ---
447
+
448
+ ## What it shows, and what it refuses to show
449
+
450
+ Included, because it is plain aggregation:
451
+
452
+ - Tokens used in the current 5-hour window, request count, burn rate
453
+ - Per-model breakdown, weekly total, all-time total
454
+ - The official usage percentage and reset time, when they are available
455
+
456
+ Deliberately **not** included, because it would require guessing:
457
+
458
+ - **Percent-of-limit derived from an estimated limit.** A P90-estimated limit was measured
459
+ at 82.6% against an official screen showing 35% — off by more than double.
460
+ - **Pace and forecast**, which are built on that estimate.
461
+ - **Dollar cost.** It would mean hard-coding a price table that silently goes stale, and
462
+ subscription users do not pay per token in the first place.
463
+
464
+ ---
465
+
466
+ ## How it works
467
+
468
+ ```
469
+ ~/.claude/projects/**/*.jsonl
470
+ │ streamed line by line, never read whole
471
+
472
+ [ parser ] normalises one usage record per request
473
+ │ · de-duplicates by requestId (one turn spans several lines)
474
+ │ · takes max(output_tokens) per request (sub-agent files grow)
475
+ │ · includes subagents/agent-*.jsonl (separate files, no double count)
476
+
477
+ UsageEntry
478
+
479
+
480
+ [ aggregate ] 5-hour rolling window, burn rate, weekly totals
481
+
482
+
483
+ GameState ──► [ themes ] ──► pixels
484
+ ```
485
+
486
+ The parser is deliberately a thin layer: nothing above it knows what a JSONL line looks
487
+ like, and the drawing layer never invents a number — it only translates what `aggregate`
488
+ produced into a metaphor.
489
+
490
+ The transcript format is an undocumented internal detail with no version field, so the
491
+ parser ignores unknown fields rather than rejecting records, and keeps a counter of
492
+ never-before-seen keys as an early warning that the format changed.
493
+
494
+ ---
495
+
496
+ ## Development
497
+
498
+ ```bash
499
+ git clone https://github.com/sozerong/token_fishing.git
500
+ cd token_fishing
501
+ python3 -m pytest tests -q
502
+ ```
503
+
504
+ Self-checks for the drawing layers run standalone:
505
+
506
+ ```bash
507
+ python3 -m tokenfishing.themes # 8 themes, 7 fishing spots
508
+ ```
509
+
510
+ The token tests must keep passing **item by item** — input, output, cache-write and
511
+ cache-read each on their own. The two ways of misreading the transcripts push the total in
512
+ opposite directions, so a test that only compares the sum can pass while both halves are
513
+ wrong. A screen that looks nice but reports the wrong number is a regression, not a
514
+ feature.
515
+
516
+ All test fixtures are hand-written synthetic data. Real transcripts contain real
517
+ conversations and are never committed.
518
+
519
+ ---
520
+
521
+ ## License
522
+
523
+ MIT — see [LICENSE](LICENSE).