There's a widget on my homepage now that shows my Claude Code usage from the last 30 days. A heatmap, token counts, session counts, streaks. I thought it would be fun to make this visible.
View your own stats
If you use Claude Code, you can see your own usage stats by typing /stats in any session. It'll show you a summary right in the terminal.
Where the data comes from
Claude Code keeps usage stats locally in ~/.claude/stats-cache.json. That file has daily message counts, session counts, tool calls, token usage broken down by model, and some session metadata like when I first started using it.
The skill
I wrote a Claude Code skill (/claude-stats) that reads the stats cache, computes last-30-day numbers, and dumps a static JSON file into app/data/. It figures out tokens, sessions, active days, streaks, most active day, favorite model, and builds the daily array that powers the heatmap.
Here's the full skill file — save it as ~/.claude/skills/claude-stats-widget.md and you'll be able to run /claude-stats in any session:
# Claude Stats Widget Generator
## Description
Generates a static JSON data file with Claude Code usage stats for the personal website widget.
## User-invocable
- Name: claude-stats
- Description: Generate Claude Code stats JSON for the personal website widget
## Instructions
1. Read `~/.claude/stats-cache.json`
2. Determine today's date and compute the last-30-day window
3. Filter `dailyActivity` entries within that window
4. For each day in the 30-day range, include an entry (use `messageCount: 0` for days with no activity)
5. Compute metrics:
- **totalTokens**: Sum all `tokensByModel` values from `dailyModelTokens` entries in the window
- **totalSessions**: Sum all `sessionCount` values from `dailyActivity` entries in the window
- **activeDays**: Count of days with `messageCount > 0`
- **totalDays**: 30
- **favoriteModel**: The model with the most total tokens in the window (use friendly names: "Opus 4.6", "Sonnet 4.6", "Opus 4.5", "Sonnet 4.5")
- **longestStreak**: Longest run of consecutive days with activity in the window
- **currentStreak**: Current run of consecutive days with activity ending today (or most recent active day)
- **mostActiveDay**: The day with the highest `messageCount`, formatted as "Mon DD" (e.g., "Jan 30")
6. Write output to `app/data/claude-stats.json` in your website repo
### Model name mapping
- `claude-opus-4-6` → "Opus 4.6"
- `claude-sonnet-4-6` → "Sonnet 4.6"
- `claude-opus-4-5-20251101` → "Opus 4.5"
- `claude-sonnet-4-5-20250929` → "Sonnet 4.5"
### Output schema
```json
{
"generatedAt": "YYYY-MM-DD",
"period": "last30days",
"totalTokens": 1234567,
"totalSessions": 123,
"activeDays": 25,
"totalDays": 30,
"favoriteModel": "Opus 4.6",
"longestStreak": 16,
"currentStreak": 2,
"mostActiveDay": "Jan 29",
"dailyActivity": [
{ "date": "YYYY-MM-DD", "messageCount": 500 }
]
}
```
The component
A React component (app/components/claude-stats.tsx) imports that JSON at build time. It draws a heatmap in orange tones (columns for weeks, rows for days of the week) and a grid of the computed stats next to it. Message counts map to brightness levels, so you can spot busy days at a glance.
Refreshing it
When I want to update the stats:
- Run
/claude-statsin Claude Code - Commit the regenerated JSON
- Deploy
Originally I kept it manual on purpose. But I've since set up a cron job on my OpenClaw instance that pulls the latest stats every night, regenerates the JSON, and republishes the site automatically. So the widget stays up to date without me thinking about it.