Skip to content

Instantly share code, notes, and snippets.

@ani03sha
Created March 31, 2026 05:33
Show Gist options
  • Select an option

  • Save ani03sha/f880463e3abb72dcacc82f2bf05cb4d0 to your computer and use it in GitHub Desktop.

Select an option

Save ani03sha/f880463e3abb72dcacc82f2bf05cb4d0 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
Analyze per-skill token usage from a Claude Code session JSONL file.
Usage:
python3 skill_token_usage.py <path_to_session.jsonl>
If no path given, uses the most recent session file found.
"""
import json
import sys
import os
import glob
from collections import defaultdict
def find_latest_session():
pattern = os.path.expanduser("~/.claude/projects/**/*.jsonl")
files = [f for f in glob.glob(pattern, recursive=True) if "/subagents/" not in f]
if not files:
return None
return max(files, key=os.path.getmtime)
def parse_session(path):
records = []
with open(path) as f:
for line in f:
line = line.strip()
if not line:
continue
try:
records.append(json.loads(line))
except json.JSONDecodeError:
continue
return records
def get_usage(record):
msg = record.get("message", {})
usage = msg.get("usage", {})
return {
"input": usage.get("input_tokens", 0),
"output": usage.get("output_tokens", 0),
"cache_read": usage.get("cache_read_input_tokens", 0),
"cache_create": usage.get("cache_creation_input_tokens", 0),
}
def total_tokens(u):
return u["input"] + u["output"] + u["cache_read"] + u["cache_create"]
def add_usage(a, b):
return {k: a[k] + b[k] for k in a}
def main():
path = sys.argv[1] if len(sys.argv) > 1 else find_latest_session()
if not path:
print("No session file found.")
sys.exit(1)
print(f"Analyzing: {path}\n")
records = parse_session(path)
# Build UUID -> record map for assistant messages
uuid_to_record = {r["uuid"]: r for r in records if "uuid" in r}
# Track current skill context as we scan user messages in order
skill_usage = defaultdict(lambda: {"input": 0, "output": 0, "cache_read": 0, "cache_create": 0})
session_total = {"input": 0, "output": 0, "cache_read": 0, "cache_create": 0}
current_skill = None
# Collect (uuid, skill_or_None) for all records in order
# Strategy: find user messages that invoke a skill, then attribute
# subsequent assistant messages to that skill until next user turn
# Pass 1: build ordered list of (uuid, type, skill_name or None)
ordered = []
for r in records:
rtype = r.get("type")
if rtype == "user":
content = r.get("message", {}).get("content", "")
skill = None
if isinstance(content, str) and "<command-name>" in content:
# Extract skill name from <command-name>/skill-name</command-name>
start = content.find("<command-name>") + len("<command-name>")
end = content.find("</command-name>")
if end > start:
skill = content[start:end].lstrip("/")
ordered.append(("user", r, skill))
elif rtype == "assistant":
ordered.append(("assistant", r, None))
# Pass 2: accumulate tokens, attributing to active skill
for kind, r, skill in ordered:
if kind == "user":
if skill:
current_skill = skill
elif kind == "assistant":
u = get_usage(r)
if any(u.values()):
session_total = add_usage(session_total, u)
key = current_skill if current_skill else "(no skill / manual)"
skill_usage[key] = add_usage(skill_usage[key], u)
# Print results
print(f"{'Skill':<25} {'Input':>10} {'Output':>10} {'Cache Read':>15} {'Cache Create':>14} {'Total':>15}")
print("-" * 95)
for skill, u in sorted(skill_usage.items(), key=lambda x: -total_tokens(x[1])):
print(f"{skill:<25} {u['input']:>10,} {u['output']:>10,} {u['cache_read']:>15,} {u['cache_create']:>14,} {total_tokens(u):>15,}")
print("-" * 95)
print(f"{'SESSION TOTAL':<25} {session_total['input']:>10,} {session_total['output']:>10,} {session_total['cache_read']:>15,} {session_total['cache_create']:>14,} {total_tokens(session_total):>15,}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment