Which Skill Is Quietly Burning Your Tokens? Find Out From transcript.jsonl

2026年8月26日3 次浏览来源:Dev.to阅读原文

Your monthly Claude Code bill went up 20%.

You know that much.

What you don't know is which Skill did it — and nothing in the tooling will tell you.

Run in Claude Code and you get — a per-model total and nothing else. "More expensive than last week" is visible. "Which Skill caused it" is not. closes that gap.

It's a 106-line shell script that parses transcript.jsonl with Python and tallies call counts per Skill, Agent, and MCP server using .

This article walks through how the script works and how to run it, with the actual code and actual numbers.

Why This Approach Works What Claude Code Is Actually Recording Claude Code streams every operation during a session into files under .

It's JSONL — one event per line, one file per session.

The files sit under a directory.

The skeleton of a single record looks like this: Inside sit blocks.

The field is the name of the tool that was invoked.

The Bash tool, the Edit tool, the Skill tool, the Agent tool, MCP calls — all of it is recorded in this same format.

Once I noticed that, the thought was: run this through a Counter and everything becomes visible.

For the Skill tool, the skill name lives in ; for the Agent tool it's ; and for MCP servers, the tool-name convention lets you extract the server name by splitting on .

The structure is consistent, so the parser comes out surprisingly simple.

What /usage Doesn't Tell You What Claude Code's command outputs is a per-model cost total for a period.

Useful as far as it goes, but the breakdown of that cost is invisible.

You can't see which session, which Skill, how many times it was called, or where the tokens went. doesn't tally token volume — it tallies call counts.

Accurate token totals would require picking up the object from API responses (per a comment in the script: token counts need usage-object aggregation, but call count is a stand-in for now), yet call counts alone are enough to outline what's heavy.

A Skill called 100 times and a Skill called once differ by orders of magnitude in token consumption.

Narrowing to "the Last N Days" With an mtime Window Tallying every session mixes in old logs and blurs comparisons.

The script cuts a time window using each file's .

The default is ; an argument changes it to or .

Passing emits only a one-line summary suited to a statusline.

Pipe that into a macOS status bar widget and the total call count accumulating week over week stays permanently visible.

The Four Axes the Counters Track The script maintains four counters. is the counter for all tools; the four above are its breakdown.

Among Skills, those in form get bundled per namespace — and that granularity earns its keep in practice.

There are moments when counting and separately tells you nothing you want; you only want to know that the plugin is heavy.

The decision logic is a plain branch.

The loop just reads one file line by line and calls .

Parse errors are swallowed by .

The whole aggregation core is under 30 lines.

The Overall Flow Here's the script's processing flow as an ASCII diagram.

The Script's Structure (106 Lines Total) splits into three parts.

Part 1: The shell layer (lines 1–16) Handles argument parsing, checking that the transcript directory exists, and handing off to the Python script.

The heredoc embeds the Python code inline.

The point of that structure is to keep everything in one file without dropping an external alongside it.

Operationally that means: nothing to install, no path resolution, works no matter where you call it from.

Part 2: Argument parsing and time-window computation (lines 18–28) After branching on the flag, is converted to the number .

The check accepts both the form and a bare integer.

Part 3: File scanning and the aggregation core (lines 37–73) gets the list of JSONL files, and only those passing the mtime filter are opened.

The pipeline is: per line → walk the list → extract blocks → increment the four Counters. is passed to keep an occasional invalid byte from halting the read of an entire file.

Part

分享