Saturate

stats

Query Claude Code usage stats from observability logs. Shows costs, session summaries, tool usage, token consumption, and more. Use when asked about costs, usage, stats, spending, session history, most-used tools, or token usage. Invoked with /stats or when asking questions like "how much have I spent today?"

Saturate 0 Updated 3mo ago
GitHub

Install

npx skillscat add saturate/claude/stats

Install via the SkillsCat registry.

SKILL.md

You are querying Claude Code observability logs stored as daily-rotated JSONL files in ~/.claude/logs/.

Log Files

File pattern Contents
tool-usage-YYYY-MM-DD.jsonl Pre/post tool use events + tool failures
sessions-YYYY-MM-DD.jsonl Session start/end with token totals and cost
turns-YYYY-MM-DD.jsonl Per-turn token usage and cost
prompts-YYYY-MM-DD.jsonl User prompts with text and length
subagents-YYYY-MM-DD.jsonl Subagent start/stop with duration and tokens
compactions-YYYY-MM-DD.jsonl Context compaction events
notifications-YYYY-MM-DD.jsonl Notification events
permissions-YYYY-MM-DD.jsonl Permission request events

Date Ranges

Parse what the user asks for and build the appropriate file list:

  • Today: *-$(date +%Y-%m-%d).jsonl
  • This week: iterate from Monday to today
  • This month: iterate from 1st to today
  • Specific date: *-YYYY-MM-DD.jsonl
  • All time: *.jsonl in the logs directory

Use this pattern to cat multiple days:

cat ~/.claude/logs/sessions-2026-02-{01..23}.jsonl 2>/dev/null

Or for cross-month ranges, generate the dates with a loop.

Common Queries

Cost summary

Query turns-*.jsonl for real-time cost data (available during active sessions, not just after session_end):

cat ~/.claude/logs/turns-$(date +%Y-%m-%d).jsonl 2>/dev/null \
  | jq -s '{
    turns: length,
    total_cost_usd: (map(.estimated_cost_usd // 0) | add),
    total_input_tokens: (map(.usage.input_tokens // 0) | add),
    total_output_tokens: (map(.usage.output_tokens // 0) | add),
    total_cache_read: (map(.usage.cache_read_input_tokens // 0) | add),
    total_cache_create: (map(.usage.cache_creation_input_tokens // 0) | add),
    total_tools: (map(.tool_count // 0) | add),
    total_failures: (map(.tool_failures // 0) | add),
    sessions: (map(.session_id) | unique | length)
  }'

Cost by project

Uses turns data so active sessions are included:

cat ~/.claude/logs/turns-$(date +%Y-%m-%d).jsonl 2>/dev/null \
  | jq -s 'group_by(.project) | map({
    project: .[0].project,
    sessions: (map(.session_id) | unique | length),
    turns: length,
    cost_usd: (map(.estimated_cost_usd // 0) | add)
  }) | sort_by(-.cost_usd)'

Most-used tools (today)

cat ~/.claude/logs/tool-usage-$(date +%Y-%m-%d).jsonl 2>/dev/null \
  | jq -s 'map(select(.event == "post")) | group_by(.tool_name) | map({
    tool: .[0].tool_name,
    count: length,
    avg_duration_ms: (map(.duration_ms // 0) | if length > 0 then (add / length | floor) else 0 end)
  }) | sort_by(-.count)'

Tool failures

cat ~/.claude/logs/tool-usage-$(date +%Y-%m-%d).jsonl 2>/dev/null \
  | jq -s 'map(select(.event == "tool_failure")) | {
    total_failures: length,
    by_tool: (group_by(.tool_name) | map({tool: .[0].tool_name, count: length}) | sort_by(-.count)),
    interrupts: (map(select(.is_interrupt == true)) | length)
  }'

Session list

cat ~/.claude/logs/sessions-$(date +%Y-%m-%d).jsonl 2>/dev/null \
  | jq -s 'map(select(.event == "session_end")) | map({
    session_id: .session_id[:8],
    project: .project,
    model: .model,
    duration_min: ((.duration_s // 0) / 60 | floor),
    turns: .turn_count,
    tools: .tool_count,
    cost_usd: .estimated_cost_usd,
    cache_hit: ((.cache_hit_rate // 0) * 100 | floor | tostring + "%")
  })'

Current session

If the user asks about "this session" or "current session", get the session_id from the environment or from the most recent session_start entry, then filter all log files by that session_id.

# Get current/latest session ID
SID=$(cat ~/.claude/logs/sessions-$(date +%Y-%m-%d).jsonl 2>/dev/null \
  | jq -r 'select(.event == "session_start") | .session_id' | tail -1)

# Get turns for this session
cat ~/.claude/logs/turns-$(date +%Y-%m-%d).jsonl 2>/dev/null \
  | jq -s --arg sid "$SID" '[.[] | select(.session_id == $sid)] | {
    turns: length,
    total_cost_usd: (map(.estimated_cost_usd // 0) | add),
    total_tools: (map(.tool_count // 0) | add),
    total_failures: (map(.tool_failures // 0) | add)
  }'

Subagent usage

cat ~/.claude/logs/subagents-$(date +%Y-%m-%d).jsonl 2>/dev/null \
  | jq -s 'map(select(.event == "subagent_stop")) | {
    total: length,
    by_type: (group_by(.agent_type) | map({type: .[0].agent_type, count: length, avg_duration_ms: (map(.duration_ms // 0) | add / length | floor)})),
    total_cost_usd: (map(.estimated_cost_usd // 0) | add)
  }'

Compaction pressure

cat ~/.claude/logs/compactions-$(date +%Y-%m-%d).jsonl 2>/dev/null \
  | jq -s '{
    total_compactions: length,
    by_session: (group_by(.session_id) | map({session: .[0].session_id[:8], count: length}) | sort_by(-.count))
  }'

Output Format

Present results as a clean markdown table or summary. Keep it concise. Round costs to 4 decimal places USD. Round percentages to whole numbers. Use human-readable durations (e.g., "12 min" not "720s").

If no data exists for the requested period, say so clearly rather than showing empty results.

If the user doesn't specify a time range, default to today.

Arguments

Users may invoke this as:

  • /stats — show today's cost summary
  • /stats week — this week's summary
  • /stats tools — most-used tools today
  • /stats sessions — list today's sessions
  • /stats cost march — cost for March
  • Or ask naturally: "how much have I spent this week?"

Parse the intent from their message and run the appropriate queries.