VibeStats

Documentation

How it works

vibestats tracks your Claude Code and Codex usage across machines and surfaces it as a public heatmap on your GitHub profile and on vibestats.dev.

Architecture

Claude Code and Codex sessions
        │
        ▼  Stop/SessionStart hooks
  Rust binary (vibestats)
        │  reads ~/.claude/projects/**/*.jsonl and ~/.codex/sessions/**/*.jsonl
        │  pushes per-machine daily JSON
        ▼
  vibestats-data (private GitHub repo)
  └── machines/year=.../day=.../harness=.../machine_id=.../data.json
        │
        ▼  daily GitHub Actions cron
  stephenleo/vibestats@v2
  (aggregate.py + generate_svg.py + update_readme.py)
        │
        ├──► username/username/vibestats/heatmap.svg
        │     └──► profile README (<!-- vibestats-start/end -->)
        │
        └──► username/username/vibestats/data.json
              └──► vibestats.dev/[username] (client-side dashboard)

Data flow explained

  1. Local tools write usage state — Claude Code writes JSONL files under ~/.claude/projects/; Codex writes rollout JSONL files under ~/.codex/sessions/.
  2. Rust binary syncs on session end — The vibestats binary is invoked via Stop and SessionStart hooks. It reads all supported harnesses and pushes per-machine daily JSON to your private vibestats-data GitHub repository.
  3. GitHub Actions aggregates daily — A scheduled cron workflow in vibestats-data runs stephenleo/vibestats@v2 daily. It runs aggregate.py to combine data across machines, generate_svg.py to produce a heatmap SVG, and update_readme.py to embed the heatmap in your GitHub profile README.
  4. Profile README is updated — The update_readme.py script patches the <!-- vibestats-start --> / <!-- vibestats-end --> section in your username/username profile README with the freshly generated heatmap.svg.
  5. Dashboard served client-sidevibestats.dev/[username] fetches data.json from your username/username/vibestats/ folder and renders an interactive cal-heatmap in the browser — no server required.

Multi-machine support

Each machine writes to its own path in vibestats-data using a unique machine ID. The aggregation step merges data from all machines before generating the heatmap, so your activity from every machine is reflected in a single view.

Long-term retention

By default, Claude Code deletes local session transcripts in ~/.claude/projects/ after 30 days (controlled by the cleanupPeriodDays setting). vibestats persists aggregated daily stats to your private vibestats-data repo on every session, before that cleanup fires. The sync is non-destructive by design: a date that's been pruned locally never overwrites or deletes the remote archive (see the invariant comment in src/sync.rs). Once a day's stats are in vibestats-data, they stay there indefinitely.

That means:

GitHub contributions heatmap

vibestats also captures your GitHub contribution counts — including private contributions — so they survive even after you leave an organization. GitHub hides private contributions from logged-out visitors and retroactively removes them from your graph once you lose access to the org, which can make an active developer look inactive to hiring managers. vibestats snapshots the counts into your own vibestats-data repo before that can happen.

Two known limits, both fine for typical use: the per-type Commits filter can undercount only if you have more than 100 repositories active in a single year (the total is unaffected — it comes from GitHub's contribution calendar), and the typed streams bucket events by UTC date, so a contribution made near midnight may land on an adjacent day versus your GitHub profile's timezone. The "All" total always matches github.com exactly.