Your GitHub activity, visualized as generative SVG art.
Every visualization is a file you can edit. No two developers produce the same output.
Most GitHub stats tools give you a fixed set of cards and let you recolor them. ghpulse has no fixed visualizations at all. What it draws is described by a scene: a TOML document declaring a canvas, a palette and a list of layers that bind your data to geometry.
The seven scenes below ship with it. They are ordinary scene files using nothing you cannot use yourself, so the way to get something new is to copy one and edit it.
See the scene format reference to write your own.
Every scene has a dark and a light palette in one file. Pass --light for the
light variant, and pair the two with GitHub's #gh-dark-mode-only and
#gh-light-mode-only URL fragments to switch automatically.
Your repositories as a star field. Each language gets a sector, each repository sits in its language's sector at a radius from its commit rank, and repositories sharing a language are joined by constellation lines. Position comes from your data, so two developers with the same repository count still look different.
Your real daily contribution calendar for the trailing year, correctly dated, with per weekday totals, current and longest streaks.
Retro terminal. Monospace stats and a language breakdown, typed out as if you were reading them off a green screen.
A spider chart over your language mix, scaled by rank so a typical distribution has a distinct shape rather than collapsing into a circle.
A mirrored waveform with one bar per repository, height from commit rank and color from language. The pattern is a function of your repositories.
Retrowave sunset: striped neon sun, a perspective grid receding to the vanishing point, and language pillars rising from the horizon.
White light enters a glass prism and refracts into a fan of beams, one per language, each sized by its share.
# Default scene
ghpulse --token "$ACCESS_TOKEN"
# Pick a scene, and its light palette
ghpulse --token "$ACCESS_TOKEN" --scene terminal
ghpulse --token "$ACCESS_TOKEN" --scene terminal --light
# Your own scene
ghpulse --token "$ACCESS_TOKEN" --scene ./my-scene.toml
# Any size, any title
ghpulse --token "$ACCESS_TOKEN" --width 1200 --height 500 --title "2026"
# Save raw data, then re-render offline as often as you like
ghpulse --token "$ACCESS_TOKEN" --dump-json stats.json
ghpulse --from-json stats.json --scene radar --output ./out
# PNG export (requires --features png)
ghpulse --token "$ACCESS_TOKEN" --format bothghpulse [OPTIONS]
-t, --token <TOKEN> GitHub personal access token [env: ACCESS_TOKEN]
--scene <SCENE> Scene name or path to a scene file [default: nebula]
--light Render the light palette
--format <FORMAT> Output format: svg, png, both [default: svg]
-o, --output <DIR> Output directory [default: .]
--width <N> Canvas width, overriding the scene
--height <N> Canvas height, overriding the scene
--title <TEXT> Title text, overriding the scene
--rows <N> Cap how many rows any layer draws
--config <FILE> Config file [default: ghpulse.toml]
--exclude-repos <PATTERNS> Comma-separated glob patterns
--exclude-langs <LANGS> Comma-separated language names
--exclude-private Skip private repos
--exclude-archived Skip archived repos
--min-stars <N> Minimum stars to include a repo
--min-commits <N> Minimum commits to include a repo
--dump-json <FILE> Save raw stats to JSON
--from-json <FILE> Render from cached JSON (no API calls)
--list-scenes List available scenes
--list-langs List detected languages
--scene-dir <DIR> Load custom scenes from a directory
--max-retries <N> Max retries for flaky API endpoints [default: 10]
--verbose Verbose output
--debug Debug output
-V, --version Print version
Anything settable on the command line is settable in ghpulse.toml. A flag
always wins over the file.
scene = "heatmap"
width = 900
title = "My year in code"
exclude-langs = "HTML,CSS"Add this to your profile README repo:
# .github/workflows/ghpulse.yml
name: Generate Stats
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
permissions:
contents: write
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: QaidVoid/ghpulse@v1
with:
token: ${{ secrets.ACCESS_TOKEN }}
scene: nebula
exclude-repos: "test-*"
- name: Commit
run: |
git config user.name "ghpulse[bot]"
git config user.email "ghpulse[bot]@users.noreply.github.com"
git add .
git commit -m "chore: update ghpulse stats" || true
git pushThen in your README:

A scene binds a data source to a mark, with every visual property set from a constant or computed from your data:
[[layer]]
data = "repos"
mark = "circle"
layout = "radial"
region = { y = 0.15, height = 0.7, inset = 20 }
[layer.encode]
x = "{slot.centre_x + cos(slot.angle) * slot.radius * commits.rank}"
y = "{slot.centre_y + sin(slot.angle) * slot.radius * commits.rank}"
r = "{lerp(2, 12, commits.norm)}"
fill = "{contrast(language.color, palette.background)}"Point your editor at schema/scene.schema.json for
completion on mark, channel, scale and layout names while you type. Then:
ghpulse --from-json stats.json --scene ./my-scene.tomlThe full reference is in docs/scenes.md.
Repos are discovered through your commit contributions, so anything you have actually committed to (org repos, forks, OSS projects) surfaces regardless of token type. Repos you own but never committed to will not appear.
| Scope | What it unlocks |
|---|---|
read:user |
Profile, contribution data, language breakdown |
read:org |
Org repos and org-admin detection |
repo |
Private repos and traffic/view counts |
Minimum for public data: read:user. Add read:org for org-admin attribution
and repo for private repos and traffic.
Repository permissions
- Metadata: Read (required)
- Contents: Read
- Administration: Read (only for traffic/view counts)
For org repos, the org owner must:
- Enable fine-grained tokens in Org Settings, Personal access tokens, Allow access via fine-grained personal access tokens.
- Approve your token if approval is required.
When creating the token, set Resource owner to each org you want included (one token per resource owner). If your org does not allow fine-grained tokens, use a classic PAT instead.
git clone https://github.com/QaidVoid/ghpulse.git
cd ghpulse
cargo build --release
# with PNG support
cargo build --release --features pngMIT OR Apache-2.0