From observability
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?"
How this skill is triggered — by the user, by Claude, or both
Slash command
/observability:statsThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are querying Claude Code observability logs stored as daily-rotated JSONL files in `~/.claude/logs/`.
You are querying Claude Code observability logs stored as daily-rotated JSONL files in ~/.claude/logs/.
| 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 |
Parse what the user asks for and build the appropriate file list:
*-$(date +%Y-%m-%d).jsonl*-YYYY-MM-DD.jsonl*.jsonl in the logs directoryUse 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.
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)
}'
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)'
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)'
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)
}'
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 + "%")
})'
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)
}'
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)
}'
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))
}'
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.
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 MarchParse the intent from their message and run the appropriate queries.
npx claudepluginhub saturate/agents --plugin observabilityDisplays real token usage and estimated savings for current Claude Code session from session logs—no AI estimation. Invoke via `/genshijin-stats` for session stats, `--all` lifetime totals, `--share` summaries, or `--since` periods.
Generates daily cost reports for Claude Code usage including total spend, running sessions, token stats via claude-view MCP tools. Useful for cost, spending, budget queries.
Analyzes Claude Code session logs to extract tool usage stats, thinking blocks, error patterns, debug trajectories, and generate actionable productivity recommendations. Provides cc-session CLI for overviews, timelines, searches.