Generate beautiful, customizable SVG statistics cards for your GitHub profile
Features • Themes • Quick Start • Configuration • Architecture
| Entry point | Description |
|---|---|
generate.py |
Generates SVG cards from live data |
generate_test.py |
Generates SVG cards with mock data |
| Entry point | Description |
|---|---|
api/ |
Serves live stats + SVG cards via HTTP |
api/generate_static_api.py |
Generates JSON files + saves snapshot |
| Entry point | Description |
|---|---|
Consumer workflow (for example: .github/workflows/update-static-api-data.yml) |
Scheduled job in the consuming repository that generates/saves snapshots |
| Mode | Use when | Data source | Config source |
|---|---|---|---|
generate.py |
You want SVGs directly from GitHub APIs | Live GitHub API | config.yml (if you cloned the repo) or Action config-overrides (if you use a workflow) |
generate.py + STATIC_API_DATA_DIR |
You want SVGs from pre-generated JSON (no live API calls during render). Requires a workflow in the consumer repository to generate/update api-data first. |
api-data/users/{username}/*.json |
config.yml (if you cloned the repo) or Action config-overrides (if you use a workflow) |
generate_test.py |
You want sample/mock SVG output for testing templates/themes | Mock data in code | config.yml |
api/ (FastAPI) |
You want HTTP endpoints/cards for external consumers | Live GitHub API | config.yml (generation settings) + server env/runtime scope rules |
api/generate_static_api.py |
You want to publish static JSON for GitHub Pages/CDN | Live GitHub API at generation time | config.yml (default or CONFIG_PATH) + optional CONFIG_OVERRIDES (script/workflow env) |
For card/stat generation settings, config.yml is the single base source.
Overrides are explicit:
- Action:
with: config-overrides - Static API script:
CONFIG_OVERRIDES
These are two override channels for two different entry points:
with: config-overridesis consumed by the reusable GitHub Action (uses: leonardokr/leo-git-statistics@v2).CONFIG_OVERRIDESis consumed by the static JSON generator script (api/generate_static_api.py).- In both cases,
config.ymlremains the base configuration and overrides are merged at runtime. - In mixed workflows (generate JSON first, then render SVGs), both channels can appear in the same workflow:
script step uses
CONFIG_OVERRIDES, action step useswith: config-overrides.
Workflow example for this mode: Option B: Static JSON Mode (requires 2 workflows)
A snapshot is a timestamped record of a user's GitHub statistics (stars, forks, followers, contributions, PRs, issues) saved to a SQLite database. Because GitHub does not provide historical data for most of these metrics, snapshots are the only way to track how they change over time. The stats history line chart is built entirely from these accumulated snapshots.
- Writers:
api/(POST /history/snapshot),api/generate_static_api.py, consumer repository workflow (cron) - Readers:
generate.py(stats_history chart),api/(history endpoint + stats-history card)
- Multiple Statistics Cards - Overview stats, language distribution, contribution streaks, streak battery, language puzzle, weekly commit calendar, and stats history line chart
- 25+ Built-in Themes - From Dracula to Nord, Catppuccin to Tokyo Night
- Extensible Theme System - Add your own themes via YAML files
- Animated SVGs - Smooth fade-in and slide animations
- Fully Configurable - Control which stats to show, filter repositories, and more
- Reusable GitHub Action - Run the generator from any repository (great for profile repos)
- Historical Statistics Tracking - Daily snapshots via GitHub Actions cron, with multi-series line chart visualization
- Accumulated Metrics - Track views and clones beyond GitHub's 14-day limit using SQLite with WAL mode
- REST API - FastAPI-based async API with JSON and SVG card endpoints, Swagger docs, caching (Redis or in-memory), rate limiting, API key auth, and Prometheus metrics
- Production-Ready - Docker/docker-compose deployment with gunicorn, Redis cache, structured logging, circuit breaker, and retry with exponential backoff
Displays comprehensive GitHub statistics including contributions, repositories, stars, forks, and more.
Shows your most used programming languages with an animated progress bar.
Tracks your current and longest contribution streaks.
Visual battery indicator showing your streak progress with recent contribution history.
Treemap visualization of your programming languages - area proportional to usage percentage.
Agenda-style weekly calendar where each commit is a time block, with repositories separated by color.
Multi-series line chart showing how your stats (stars, followers, following, contributions, forks, PRs, issues) evolve over time. Requires historical snapshots collected via the daily cron workflow or POST /history/snapshot.
Choose from 25+ built-in themes or create your own:
| Category | Themes |
|---|---|
| GitHub | default, light, dark, github_dimmed |
| Popular | dracula, nord, gruvbox, gruvbox_light, one_dark, monokai, tokyo_night, solarized_dark, solarized_light |
| Catppuccin | catppuccin_latte, catppuccin_frappe, catppuccin_macchiato, catppuccin_mocha |
| Material | palenight, material_darker, material_ocean, ayu, ayu_light, ayu_mirage |
| Creative | synthwave, cyberpunk, ocean, forest, sunset, midnight, aurora, neon, retro, lavender, rose_pine, rose_pine_dawn |
Pick the mode that matches your goal in When to Use Each Mode above.
- For profile card generation, use the reusable action (
uses: leonardokr/leo-git-statistics@v2). - For static JSON + SVG render (no live API calls during render), use a consumer workflow with two steps:
api/generate_static_api.pythen the reusable action withstatic-api-data-dir. - For source-level customization, clone/template this repository.
- Go to Settings -> Developer settings -> Personal access tokens -> Tokens (classic)
- Generate a new token with scopes:
repo,read:user,read:org - Copy the token
- In your profile repository (
<username>/<username>): Settings -> Secrets and variables -> Actions - Create secret named
PROFILE_STATS_TOKENwith your token
Then continue with the mode-specific sections below:
- Consumer Workflow Examples (Reusable Action)
- Repository Configuration (Clone/Template Mode)
- REST API (Hosted Service Mode)
Use this mode when you do not want to maintain this codebase and only need workflows in your profile/consumer repository to generate cards.
Expand Action Workflows (Live API and Static JSON Render)
Use this repository as an Action in your profile repository.
Required secret:
PROFILE_STATS_TOKEN: token used bygenerate.pyto query GitHub APIs.
This mode calls the GitHub API directly during card generation (not the leo-git-statistics REST API).
name: Update Profile SVG Stats
on:
schedule:
- cron: "0 */12 * * *"
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout profile repository
uses: actions/checkout@v4
- name: Generate SVGs via leo-git-statistics action
uses: leonardokr/leo-git-statistics@v2
with:
github-token: ${{ secrets.PROFILE_STATS_TOKEN }}
github-username: leonardokr
output-dir: profile
config-path: config.yml
config-overrides: |
timezone: America/Sao_Paulo
themes:
enabled: [dark, light]
stats_generation:
exclude_contrib_repos: "true"
mask_private_repos: "true"
- name: Commit and push
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add profile/*.svg
if ! git diff --quiet --staged; then
git commit -m "chore(stats): update profile SVG stats"
git push
fiUse this mode when you want cards rendered from pre-generated JSON instead of live GitHub API calls during render. You must have two steps in your automation:
Workflow B1: generate/update static JSON data.Workflow B2: render cards from that JSON data.
They do not need to run at the same schedule. You can run B1 and B2 as separate workflows with different intervals (for example, data every 30 minutes and cards every 6 hours), or combine both in a single workflow with two steps/jobs.
If you run only B1, you will have JSON files but no updated cards. If you run only B2 without existing JSON, card rendering from static data will fail/produce nothing.
name: Update static API data
on:
schedule:
- cron: "30 0 * * *"
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout leo-git-statistics source
uses: actions/checkout@v4
with:
repository: leonardokr/leo-git-statistics
ref: v2
path: leo-git-statistics
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: |
python -m pip install --upgrade pip
pip install -r leo-git-statistics/requirements.txt
- name: Generate static API JSON files
env:
GITHUB_TOKEN: ${{ secrets.PROFILE_STATS_TOKEN }}
GITHUB_ACTOR: ${{ github.repository_owner }}
SNAPSHOTS_DB_PATH: ${{ github.workspace }}/api-data/snapshots.db
CONFIG_OVERRIDES: |
timezone: America/Sao_Paulo
stats_generation:
exclude_contrib_repos: "true"
mask_private_repos: "true"
run: python leo-git-statistics/api/generate_static_api.py
- run: |
git add api-data
git commit -m "Update static API data" || exit 0
git pushThis workflow consumes the JSON generated by Workflow B1 (no live GitHub API calls during render).
name: Render cards from static API data
on:
schedule:
- cron: "0 */6 * * *"
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate SVGs from static JSON
uses: leonardokr/leo-git-statistics@v2
with:
github-token: ${{ secrets.PROFILE_STATS_TOKEN }}
github-username: leonardokr
output-dir: profile
config-path: config.yml
static-api-data-dir: api-data
config-overrides: |
timezone: America/Sao_Paulo
themes:
enabled: [dark, light]
stats_generation:
mask_private_repos: "true"
- run: |
git add profile/*.svg
git commit -m "Update profile SVG stats from static data" || exit 0
git pushUnder the hood, this maps to env STATIC_API_DATA_DIR in the action runtime.
Option A is independent and generates cards directly from the GitHub API.
| Input | Required | Default | Description |
|---|---|---|---|
github-token |
Yes | - | GitHub token consumed as ACCESS_TOKEN. |
github-username |
No | github.actor |
GitHub user to collect stats for. |
python-version |
No | 3.11 |
Python runtime version. |
output-dir |
No | generated_images |
Destination folder in caller repository. |
config-path |
No | config.yml |
Path to the config file in caller repository. |
config-overrides |
No | - | YAML fragment merged into config at runtime. |
static-api-data-dir |
No | - | Static JSON root path (example: api-data) to enable STATIC_API_DATA_DIR. |
This section applies to any workflow that calls the reusable action (uses: leonardokr/leo-git-statistics@v2), including:
- Option A (live GitHub API cards)
- Option B / Workflow B2 (render cards from static JSON)
It does not apply to Workflow B1 (api/generate_static_api.py), which uses CONFIG_OVERRIDES env instead.
In action-based workflows, you can configure behavior with:
with: config-path(read a config file from the caller repository, optional)with: config-overrides(inline YAML overrides)
If config-path does not exist in the caller repository, the action uses its bundled config.yml as base and then applies config-overrides.
timezone:
themes:
enabled:
stats_generation:
excluded_repos:
excluded_langs:
include_forked_repos:
exclude_contrib_repos:
exclude_archive_repos:
exclude_private_repos:
exclude_public_repos:
mask_private_repos:
store_repo_views:
store_repo_clones:
more_collabs:
manually_added_repos:
only_included_repos:
show_total_contributions:
show_repositories:
show_lines_changed:
show_avg_percent:
show_collaborators:
show_contributors:
show_views:
show_clones:
show_forks:
show_stars:
show_pull_requests:
show_issues:Use this mode when you cloned/template this repository and want to run/modify generation locally in this codebase (including templates and source code).
Expand config.yml Setup for Cloned/Template Repositories
Use this section only if you cloned this repository (or used it as a template) and will run scripts from this codebase directly.
name: Generate cards from cloned repo
on:
schedule:
- cron: "0 */6 * * *"
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- env:
ACCESS_TOKEN: ${{ secrets.PROFILE_STATS_TOKEN }}
GITHUB_ACTOR: ${{ github.repository_owner }}
run: python generate.py
- run: |
git add generated_images/*.svg
git commit -m "Update cards" || exit 0
git pushthemes:
enabled:
- default
- dark
- dracula
- nord
# Use 'all' to generate all themesstats_generation:
excluded_repos: "repo1,repo2"
excluded_langs: "HTML,CSS"
include_forked_repos: "false"
mask_private_repos: "true"
exclude_private_repos: "false"
exclude_archive_repos: "true"stats_generation:
show_total_contributions: "true"
show_repositories: "true"
show_lines_changed: "true"
show_stars: "true"
show_pull_requests: "true"
# ... more optionsThis project provides a FastAPI-based async REST API that exposes statistics as JSON and SVG cards. Built for production with caching, authentication, rate limiting, structured logging, and Prometheus metrics.
Note: The API requires backend hosting (Docker, Render, Railway, etc.) and cannot run on GitHub Pages. For static sites, use the workflow mode in Consumer Workflow Examples (Reusable Action), especially Option B: Static JSON Mode (requires 2 workflows).
Click to view API Documentation
# 1. Install dependencies
pip install -r requirements.txt
# 2. Configure environment (see api/.env.example)
export GITHUB_TOKEN=your_token_here
# 3. Run the API (development)
uvicorn api.main:app --reload --port 8000
# 4. Access interactive docs at http://localhost:8000/docs| Endpoint | Description |
|---|---|
GET /v1/users/{username}/overview |
Overview statistics (contributions, stars, forks, lines changed) |
GET /v1/users/{username}/languages |
Language distribution |
GET /v1/users/{username}/repositories |
Repository list (names only, paginated) |
GET /v1/users/{username}/repositories/detailed |
Detailed repository info (paginated) |
GET /v1/users/{username}/streak |
Contribution streak data |
GET /v1/users/{username}/commits/weekly |
Weekly commit schedule |
GET /v1/users/{username}/contributions/recent |
Recent contribution activity |
GET /v1/users/{username}/stats/full |
All data in one request |
| Endpoint | Description |
|---|---|
GET /v1/users/{username}/cards/themes |
List available card themes |
GET /v1/users/{username}/cards/overview?theme=dracula |
Overview SVG card |
GET /v1/users/{username}/cards/languages?theme=dark |
Language distribution SVG card |
GET /v1/users/{username}/cards/streak?theme=nord |
Contribution streak SVG card |
GET /v1/users/{username}/cards/streak-battery?theme=tokyo_night |
Streak battery SVG card |
GET /v1/users/{username}/cards/languages-puzzle?theme=catppuccin_mocha |
Language puzzle SVG card |
GET /v1/users/{username}/cards/commit-calendar?theme=dracula |
Commit calendar SVG card |
GET /v1/users/{username}/cards/stats-history?theme=dracula |
Historical stats SVG card |
SVG cards can be embedded directly in Markdown:
| Endpoint | Description |
|---|---|
GET /v1/users/{username}/compare/{other} |
Side-by-side user comparison |
GET /v1/users/{username}/history |
Historical stats snapshots |
POST /v1/users/{username}/webhooks |
Register a webhook for stat changes |
GET /v1/users/{username}/webhooks |
List registered webhooks |
DELETE /v1/users/{username}/webhooks/{id} |
Remove a webhook |
POST /v1/users/{username}/webhooks supports these condition keys:
| Key | Type | Trigger |
|---|---|---|
stars_threshold |
int |
Fires when total_stars crosses the threshold upward |
streak_broken |
bool |
Fires when current_streak drops from > 0 to 0 |
contributions_record |
bool |
Fires when total_contributions becomes greater than previous snapshot |
Example:
{
"url": "https://example.com/hooks/stats",
"conditions": {
"stars_threshold": 500,
"streak_broken": true,
"contributions_record": true
}
}| Endpoint | Description |
|---|---|
GET /health |
Health check |
GET /metrics |
Prometheus metrics |
GET /docs |
Interactive Swagger documentation |
Endpoints that return repository lists support filtering and pagination:
| Parameter | Default | Description |
|---|---|---|
page |
1 |
Page number |
per_page |
30 |
Items per page (max 100) |
visibility |
all |
public, private, or all |
sort |
stars |
stars, forks, updated, or name |
limit |
100 |
Max repositories to return (1-500) |
exclude_forks |
false |
Exclude forked repositories |
exclude_archived |
false |
Exclude archived repositories |
no_cache |
false |
Bypass cache and fetch fresh data |
curl http://localhost:8000/v1/users/leonardokr/overview{
"username": "leonardokr",
"name": "Leonardo Klein",
"total_contributions": 1250,
"repositories_count": 42,
"total_stars": 320,
"total_forks": 45,
"lines_added": 45230,
"lines_deleted": 12340,
"avg_contribution_percent": "68.50%"
}- API Key Authentication - Protect API routes with
Authorization: Bearer <key>. Enable withAPI_AUTH_ENABLED=trueandAPI_KEYS. Public endpoints remain available:/health,/docs, and/metrics. - Private Repo Isolation - Server-token requests always exclude private repositories, preventing accidental private data exposure.
- User Token Support - Clients may send
X-GitHub-Tokento use their own GitHub token. The API verifies token ownership against{username}before enabling private-repo access. - Rate Limiting - slowapi limits requests by API key (authenticated) or IP (anonymous). Configure with
RATE_LIMIT_DEFAULT,RATE_LIMIT_AUTH, andRATE_LIMIT_HEAVY. - Input Validation - Request payloads and query/path parameters are validated with Pydantic. Usernames are validated against GitHub username rules.
- Webhook Delivery Note - Outbound webhook callbacks are currently unsigned (no HMAC signature header yet). Protect webhook receivers with your own validation controls (tokenized URL, allowlist, or proxy checks).
Responses are cached with configurable TTL (CACHE_TTL, default 300 seconds / 5 minutes). Two backends are available:
- In-memory (
TTLCache) - Default, no configuration needed. Lost on restart. - Redis - Set
REDIS_URL=redis://localhost:6379/0. Shared across workers, survives restarts.
Cache status is returned via X-Cache: HIT/MISS response header.
- Retry with Exponential Backoff - Automatic retries (up to 3 attempts) via tenacity for rate-limit errors,
aiohttpclient/server timeout errors, and GitHub5xxresponses. - Circuit Breaker - Fails fast when GitHub API is down (opens after 5 consecutive failures, resets after 30s) via pybreaker.
- GitHub Rate Limit Monitoring - Tracks
X-RateLimit-*headers, logs warnings when low, and may pause briefly when remaining quota is critical. - Partial Responses - If a collector fails, the response still includes successful sections plus a
warningsfield describing degraded parts.
# 1. Configure environment
cp api/.env.example api/.env
# Edit api/.env with your GITHUB_TOKEN
# 2. Run with docker-compose (API + Redis)
docker-compose up -d
# API available at http://localhost:8000The docker-compose.yml includes the API service with gunicorn (4 workers) and a Redis cache.
# Production server
gunicorn api.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000Render:
- New -> Web Service -> Connect GitHub repo
- Build:
pip install -r requirements.txt - Start:
gunicorn api.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:$PORT - Environment variable:
GITHUB_TOKEN=your_token
Other options: Railway, Fly.io, any platform supporting Docker.
Static-site JSON generation (GitHub Pages/CDN) is documented in Consumer Workflow Examples (Reusable Action), under Option B: Static JSON Mode (requires 2 workflows).
Use the API from any platform that can make HTTP requests (frontend, backend, CI, mobile, etc.). Example in Python:
import requests
url = "https://your-api.example.com/v1/users/leonardokr/repositories/detailed"
response = requests.get(url, timeout=20)
response.raise_for_status()
payload = response.json()
for repo in payload.get("data", []):
print(repo["name"], repo.get("stars"))Create api/.env file (see api/.env.example).
What each variable is for and why it matters:
GITHUB_TOKEN/ACCESS_TOKENUsed by the backend to call GitHub APIs. Without one of these, the API cannot collect stats.GITHUB_TOKENis preferred;ACCESS_TOKENis fallback.PORTHTTP port exposed by the API server. Change it to match your hosting/runtime requirements.WORKERSNumber of API worker processes (used by gunicorn). Increase for more concurrency, at higher memory cost.API_AUTH_ENABLED+API_KEYSEnables API key protection for/v1/*endpoints. Use this when your API should not be publicly callable.CORS_ORIGINSBrowser allowlist for cross-origin requests. Use specific origins in production; keep*only for development/testing.RATE_LIMIT_DEFAULT,RATE_LIMIT_AUTH,RATE_LIMIT_HEAVYProtects the service from abuse and accidental overload. Limits use slowapi format like30/minute,100/hour,1000/day.REDIS_URLEnables shared cache across workers/instances. Recommended for production; if omitted, cache is in-memory per process.CACHE_TTLHow long cached responses live (seconds). Higher TTL reduces GitHub API calls but increases staleness.CACHE_MAXSIZEMax entries for in-memory cache backend. Tune based on memory budget and traffic.DATABASE_PATH,SNAPSHOTS_DB_PATH,WEBHOOKS_DB_PATHFile paths for SQLite databases (traffic, snapshots/history, webhooks). Override when you need custom storage layout.
Example:
# Required
GITHUB_TOKEN=your_github_personal_access_token
# or ACCESS_TOKEN=your_github_personal_access_token
# Server
PORT=8000
WORKERS=4
# Security
API_AUTH_ENABLED=false
API_KEYS=
CORS_ORIGINS=*
# Rate limiting
RATE_LIMIT_DEFAULT=30/minute
RATE_LIMIT_AUTH=100/minute
RATE_LIMIT_HEAVY=10/minute
# Cache
# Optional (if omitted, in-memory cache is used)
REDIS_URL=redis://localhost:6379/0
CACHE_TTL=300
CACHE_MAXSIZE=100
# Database
# Optional (defaults are used when omitted)
DATABASE_PATH=src/db/traffic.db
SNAPSHOTS_DB_PATH=src/db/snapshots.db
WEBHOOKS_DB_PATH=src/db/webhooks.dbRate Limit Configuration Guide
RATE_LIMIT_DEFAULT
- Applied to standard endpoints (most
/v1/users/*and/v1/users/{username}/cards/*routes). - Example:
RATE_LIMIT_DEFAULT=60/minuteallows up to 60 requests per minute per client key.
RATE_LIMIT_HEAVY
- Applied to expensive endpoints (for example compare, full stats, and force snapshot routes).
- Example:
RATE_LIMIT_HEAVY=5/minuteprotects GitHub quota on heavier operations.
RATE_LIMIT_AUTH
- Configured in middleware and available for authenticated-tier policies.
- Keep it defined for future/extended routing policies.
Result in practice:
- Lower values: better abuse protection, stricter throttling.
- Higher values: more throughput, higher load and GitHub API consumption risk.
API Authentication Configuration Guide
API_AUTH_ENABLED
false(default): API routes under/v1/*are publicly accessible.true:/v1/*routes requireAuthorization: Bearer <api_key>.
API_KEYS
- Comma-separated list of accepted API keys.
- Example:
API_KEYS=key_prod_1,key_internal_2
How to call:
- Header format:
Authorization: Bearer key_prod_1
What stays public even with auth enabled:
/health/docs/metrics
Result in practice:
API_AUTH_ENABLED=false: simpler public access, higher abuse risk.API_AUTH_ENABLED=true+ strong keys: controlled access and safer public deployments.
If API_AUTH_ENABLED=true and API_KEYS is empty, protected routes return server error (500) until keys are configured.
CORS Configuration Guide
CORS accepts all origins by default (CORS_ORIGINS=*).
For production, set an explicit comma-separated allowlist:
CORS_ORIGINS=https://leonardokr.github.io,https://myportfolio.comDo not include spaces between origins.
Result in practice:
*: easiest setup, least restrictive.- Explicit allowlist: safer browser access control for production.
This repository uses automated releases with release-please on main.
- Create changes in branches and merge with Conventional Commits.
release-pleaseopens/updates a release PR with changelog entries.- When the release PR is merged, it creates a GitHub Release and a semver tag (
v1.2.3). - The workflow also updates the floating major tag (
v1,v2, ...) and thelatesttag to the newest release.
For consumers:
- Use
@v2for stable major updates with automatic minor/patch refreshes. - Use
@latestto always track the newest release regardless of major version (may include breaking changes). - Pin exact versions (
@v2.0.3) only when strict reproducibility is required.
Add a new .yml file in src/themes/:
# src/themes/my_theme.yml
my_awesome_theme:
suffix: "MyAwesome"
colors:
bg_color: "#1a1b26"
title_color: "#7aa2f7"
text_color: "#a9b1d6"
icon_color: "#bb9af7"
percent_color: "#565f89"
border_color: "#292e42"
accent_color: "#9ece6a"
gradient_start: "#7aa2f7"
gradient_end: "#bb9af7"The example above is a minimal theme. Missing keys are auto-filled from defaults in src/themes/loader.py.
If you want full control over all cards, also define these optional groups:
my_awesome_theme:
suffix: "MyAwesome"
colors:
# Commit calendar card
calendar_title_color: "#7aa2f7"
calendar_subtitle_color: "#565f89"
calendar_day_label_color: "#a9b1d6"
calendar_hour_label_color: "#565f89"
calendar_grid_color: "#292e42"
calendar_grid_opacity: 0.55
calendar_legend_text_color: "#a9b1d6"
calendar_slot_opacity: 0.95
calendar_hue: 220
calendar_saturation_range: [60, 85]
calendar_lightness_range: [40, 60]
calendar_hue_spread: 90
# Languages puzzle card
puzzle_hue: 210
puzzle_saturation_range: [60, 85]
puzzle_lightness_range: [35, 65]
puzzle_hue_spread: 80
puzzle_text_color: "#FFFFFF"
puzzle_gap: 2
# Stats history card
line_chart_title_color: "#7aa2f7"
line_chart_subtitle_color: "#565f89"
line_chart_axis_color: "#565f89"
line_chart_grid_color: "#292e42"
line_chart_grid_opacity: 0.3
line_chart_legend_text_color: "#a9b1d6"
line_chart_hue: 220
line_chart_saturation_range: [60, 85]
line_chart_lightness_range: [40, 65]
line_chart_hue_spread: 120Use src/themes/github.yml as a full reference theme with all currently supported keys.
Then enable it in config.yml:
themes:
enabled:
- my_awesome_theme.
|- generate.py / generate_test.py # CLI entrypoints for SVG generation
|- api/
| |- main.py # FastAPI app bootstrap
| |- generate_static_api.py # Static JSON exporter (api-data)
| |- routes/ # HTTP endpoints (users, cards, compare, history, webhooks, health)
| |- services/ # API service layer (collector wiring, rendering, notifications)
| |- deps/ # auth/cache/token/session dependencies
| |- middleware/ # logging, metrics, rate limiting
| `- models/ # request/response schemas
|- src/
| |- core/ # GitHub collection and domain orchestration
| |- generators/ # SVG generators (overview, languages, streak, calendar, history)
| |- presentation/ # SVG template rendering and visual algorithms
| |- themes/ # YAML theme definitions + loader
| |- db/ # SQLite stores (traffic, snapshots, webhooks)
| `- utils/ # shared helpers (privacy, filesystem, decorators)
|- test/ # API/core/load tests
`- config.yml # base generation configuration
- Collection and delivery are separated:
src/corehandles GitHub data collection;api/handles HTTP concerns and exposes JSON/SVG. - Collector facade:
StatsCollectoraggregates specialized collectors (repos, streak, commits, traffic, engagement, code changes) behind one interface. - Generator registry: SVG generators register via
register_generator, so orchestration does not depend on hardcoded generator lists. - Async GitHub client with resilience:
GitHubClientcombines controlled concurrency, retry/backoff, and circuit breaker behavior. - Privacy controls are explicit: repository filtering and masking are built into collectors/responses; API token scope restricts private data exposure.
- Partial failure model: API endpoints can return successful sections plus
warningswhen some collectors fail. - Pluggable runtime dependencies: in-memory cache and local SQLite work by default; Redis and custom DB paths are optional via env.
- Python 3.11+
- pip
- Docker (optional, for containerized deployment)
# Production dependencies
pip install -r requirements.txt
# Development/test dependencies (includes httpx, aioresponses, locust)
pip install -r requirements-dev.txtUse generate_test.py when you want to validate templates/themes locally without GitHub API calls, token setup, or rate-limit impact.
python generate_test.pyexport GITHUB_TOKEN="your_github_token"
uvicorn api.main:app --reload --port 8000cp api/.env.example api/.env
# Edit api/.env with your GITHUB_TOKEN
docker-compose up -d# All tests
pytest test/
# API integration tests only
pytest test/api/
# Async collector tests only
pytest test/core/
# Load tests (requires running API)
locust -f test/load/locustfile.py --host http://localhost:8000Contributions are welcome! Feel free to:
- Add new themes
- Improve SVG templates
- Add new statistics
- Fix bugs
- Improve documentation
See the LICENSE file for details.
Built with Python, FastAPI, and GitHub Actions