Turn your sparse contribution graph into balanced, pixel‑perfect art — ethically, transparently, and within GitHub’s rules.
Many GitHub users aren’t professional developers. They still have an account to share small tools, notes, or experiments once in a while. The result? Their contribution graph looks empty most of the year. GitHub Printer exists to help you curate that graph: generate a balanced pixel‑art banner and “print” it across a chosen year using lightweight marker commits.
Please read GitHub’s Terms of Service and Community Guidelines. This tool is for legitimate self‑publishing and curation. Do not abuse it (spam, deception, misleading activity, or automated flooding across repos). You are responsible for your account and its compliance.
My own graph during a short period looked like this (for inspiration):
GitHub shades each day’s square by activity level (commit contributions, PRs, issues, etc.). Visually, you see four intensities (we’ll call them quartiles or levels L1..L4; the background “none” is separate). If you want a balanced banner (e.g., a 7×52 header strip), it helps to keep the same number of pixels of each of the 4 levels.
This repo ships a helper that converts text -> balanced pixel image so that each color level appears equally often. That image becomes a target layout for your year.
What this project does
pixelart.py — builds a balanced 7×52 banner from text with:
two subtle shadow layers,
a uniform checkerboard background assembled from all deficit colors,
a final correction pass to make counts match exactly (same number of pixels for levels 1–4).
printer.py — fills your contribution graph for a selected year by creating commits in one target repository you own. It:
reads your daily contribution caps,
computes how many commits to add per day to match the image,
uses the Git Database API + a small marker file (.github/.contrib-marker.txt) to create the commits,
respects rate limits and handles empty repos safely.
⚠️ Be considerate: The tool operates in your repo, on the default branch, and only creates tiny marker commits. Do not run it across many repos or organizations. Keep the rate within limits.
Installation
Create your OAuth App (credentials)
printer.py uses OAuth (user‑to‑server). You’ll create a GitHub OAuth App in your account to obtain a Client ID and Client Secret (this is not a “GitHub App” installation; we specifically use OAuth endpoints).
Go to GitHub -> Settings -> Developer settings -> OAuth Apps -> New OAuth App.
Fill in:
Application name:github-printer (or anything)
Homepage URL:https://example.com (any URL you own; not used by the script)
Authorization callback URL:http://localhost:8765/callback(matches the script default; you may change it)
After creation, note your Client ID and Client Secret.
The script will request scopes: repo, read:user, user:email.
Alternative: you can use a classic PAT via GITHUB_PAT with repo scope. OAuth is recommended for clarity and easy revocation.
Environment variables
Create a small .env or export these in your shell (PowerShell examples included):
# Required for OAuth flow
export GITHUB_APP_CLIENT_ID="Iv1..."
export GITHUB_APP_CLIENT_SECRET="your-secret"
export OAUTH_REDIRECT_URL="http://localhost:8765/callback"
# Optional: use a classic PAT instead of OAuth
# export GITHUB_PAT="ghp_..."
# Optional: where to store tokens exchanged by OAuth
export GITHUB_TOKEN_STORE="./github_tokens.json"
# Optional: default repo name (owner is inferred from your account)
export TARGET_REPO="github-printer"
PowerShell:
# OAuth
[Environment]::SetEnvironmentVariable("GITHUB_APP_CLIENT_ID","Iv1...","User")
[Environment]::SetEnvironmentVariable("GITHUB_APP_CLIENT_SECRET","your-secret","User")
[Environment]::SetEnvironmentVariable("OAUTH_REDIRECT_URL","http://localhost:8765/callback","User")
# PAT (optional)
# [Environment]::SetEnvironmentVariable("GITHUB_PAT","ghp_...","User")
Install Python deps:
pip install pillow requests
Quick start
Generate a balanced banner (7×52) from text:
python pixelart.py --text "HELLO" --shadowdx 0 --out ./out_banner7
# outputs: banner_7x52.png and a x50 preview
Authorize (OAuth) — get the URL and finish the code exchange:
python printer.py auth-url
# open the printed URL, authorize, copy the "code" here:
python printer.py exchange-code --code YOUR_CODE
(or set GITHUB_PAT instead of OAuth)
Dry run a paint plan for a year (see how many commits would be made):
The tool creates the repo if missing, initializes the default branch if empty, and commits to .github/.contrib-marker.txt on the default branch.
CLI reference
printer.py subcommands:
auth-url Print the OAuth authorization URL (repo read:user user:email).
exchange-code --code <CODE> Exchange the OAuth code for a user token and store it locally.
contribute [--repo owner/name|name] [--message MSG] [--count N] [--date YYYY-MM-DD] Make N marker commits for the given date (UTC). Creates the repo/branch if needed.
max-daily --from YYYY-MM-DD --to YYYY-MM-DD [--json] [--include-private] Show your per‑day commit‑contributions in the range (aggregated across repos).
paint --year YYYY --image path.png [--repo ...] [--message ...] [--dry-run] [--include-private] Compute the plan from the image and print it across the year.
pixelart.py arguments:
--text "YOUR TEXT" — banner text (emojis supported)
--shadowdx N — horizontal shadow offset (default 0)
Target per color: TGT = H*W/4 (i.e., exactly the same count for 1..4).
Pipeline:
Text (adaptive height 6->5->4) with two shadows (level 1).
Uniform checkerboard fill built from all deficit colors.
Final correction pass (closest to center, excluding text & shadow) to hit the exact totals.
Printing plan (printer.py)
Reads your daily contribution totals and computes additions needed to match the pixel’s level for that day.
Uses the Git Database API to write a tiny marker line into .github/.contrib-marker.txt per commit.
Handles empty repos by creating the first commit via the Contents API.
Respects API rate limits (shows status; backs off when needed).
Troubleshooting
403 Resource not accessible by integration (GET /user/emails) If your OAuth scopes or PAT don’t permit reading emails, the script will fall back to a noreply address ({id}+{login}@users.noreply.github.com).
409 Git Repository is empty The script auto‑initializes the default branch by creating the marker file through the Contents API, then continues with Git DB commits.
Rate limits / Abuse detection The script detects both hard and secondary limits, waits for reset, and prints a budget estimate before painting.
Missing permissions to create a repo Ensure your token has repo scope and that you own the target user repo (or have org permissions).
Image shape Any RGBA image works, but 7×52 is recommended to mirror the year strip. The mapper walks the year in a repeatable pattern and assigns pixels consistently.
FAQ
Q: Is this allowed by GitHub? A: GitHub doesn’t ban personal tooling that creates commits in your own repo. However, deceptive or spammy behavior is not OK. Use responsibly and follow the ToS/Guidelines.
Q: OAuth App vs GitHub App? A: This script uses OAuth App (endpoints: /login/oauth/...). In GitHub settings, create an OAuth App, not an “App installation.” A classic PAT also works via GITHUB_PAT.
Q: What scopes are required? A: repo, read:user, user:email.
Q: Will this affect my real projects? A: Painting happens in a single target repo (created if needed) by editing a marker file. It won’t touch other repos.
Q: Can I limit how many commits it adds per day? A: The plan respects your existing per‑day contributions and computes the delta to reach the pixel’s level, with internal caps to avoid unnatural spikes.
Q: Can I pause and resume? A: Yes. You can run paint multiple times; it recalculates based on the current state of your contributions.
GitHub Printer
Turn your sparse contribution graph into balanced, pixel‑perfect art — ethically, transparently, and within GitHub’s rules.
Many GitHub users aren’t professional developers. They still have an account to share small tools, notes, or experiments once in a while. The result? Their contribution graph looks empty most of the year.
GitHub Printer exists to help you curate that graph: generate a balanced pixel‑art banner and “print” it across a chosen year using lightweight marker commits.
My own graph during a short period looked like this (for inspiration):
Table of Contents
How the contribution graph works
GitHub shades each day’s square by activity level (commit contributions, PRs, issues, etc.). Visually, you see four intensities (we’ll call them quartiles or levels L1..L4; the background “none” is separate).
If you want a balanced banner (e.g., a 7×52 header strip), it helps to keep the same number of pixels of each of the 4 levels.
This repo ships a helper that converts text -> balanced pixel image so that each color level appears equally often. That image becomes a target layout for your year.
What this project does
pixelart.py— builds a balanced 7×52 banner from text with:printer.py— fills your contribution graph for a selected year by creating commits in one target repository you own. It:.github/.contrib-marker.txt) to create the commits,Installation
Create your OAuth App (credentials)
printer.pyuses OAuth (user‑to‑server). You’ll create a GitHub OAuth App in your account to obtain a Client ID and Client Secret (this is not a “GitHub App” installation; we specifically use OAuth endpoints).github-printer(or anything)https://example.com(any URL you own; not used by the script)http://localhost:8765/callback(matches the script default; you may change it)repo,read:user,user:email.Environment variables
Create a small
.envor export these in your shell (PowerShell examples included):PowerShell:
Install Python deps:
Quick start
Generate a balanced banner (7×52) from text:
Authorize (OAuth) — get the URL and finish the code exchange:
(or set
GITHUB_PATinstead of OAuth)Dry run a paint plan for a year (see how many commits would be made):
Execute the paint (creates/uses a repo you own; default is
TARGET_REPOorgithub-printer):CLI reference
printer.pysubcommands:auth-urlPrint the OAuth authorization URL (
repo read:user user:email).exchange-code --code <CODE>Exchange the OAuth
codefor a user token and store it locally.contribute [--repo owner/name|name] [--message MSG] [--count N] [--date YYYY-MM-DD]Make N marker commits for the given date (UTC). Creates the repo/branch if needed.
max-daily --from YYYY-MM-DD --to YYYY-MM-DD [--json] [--include-private]Show your per‑day commit‑contributions in the range (aggregated across repos).
paint --year YYYY --image path.png [--repo ...] [--message ...] [--dry-run] [--include-private]Compute the plan from the image and print it across the year.
pixelart.pyarguments:--text "YOUR TEXT"— banner text (emojis supported)--shadowdx N— horizontal shadow offset (default0)--font PATH— optional custom TTF--phase N— shifts the global checkerboard phase--out DIR— output directoryExamples
Simple daily contributions (one day):
Inspect max daily contributions in a month:
Paint with a custom repo and message:
Generate text banner with emoji and slight phase shift:
Design notes (how it paints)
Pixel art balancing (
pixelart.py)1=#033A16,2=#196C2E,3=#2EA043,4=#56D364, NEUTRAL#151B23TGT = H*W/4(i.e., exactly the same count for 1..4).6->5->4) with two shadows (level 1).Printing plan (
printer.py).github/.contrib-marker.txtper commit.Troubleshooting
403
Resource not accessible by integration(GET/user/emails)If your OAuth scopes or PAT don’t permit reading emails, the script will fall back to a noreply address (
{id}+{login}@users.noreply.github.com).409
Git Repository is emptyThe script auto‑initializes the default branch by creating the marker file through the Contents API, then continues with Git DB commits.
Rate limits / Abuse detection
The script detects both hard and secondary limits, waits for reset, and prints a budget estimate before painting.
Missing permissions to create a repo
Ensure your token has
reposcope and that you own the target user repo (or have org permissions).Image shape
Any RGBA image works, but 7×52 is recommended to mirror the year strip. The mapper walks the year in a repeatable pattern and assigns pixels consistently.
FAQ
Q: Is this allowed by GitHub?
A: GitHub doesn’t ban personal tooling that creates commits in your own repo. However, deceptive or spammy behavior is not OK. Use responsibly and follow the ToS/Guidelines.
Q: OAuth App vs GitHub App?
A: This script uses OAuth App (endpoints:
/login/oauth/...). In GitHub settings, create an OAuth App, not an “App installation.” A classic PAT also works viaGITHUB_PAT.Q: What scopes are required?
A:
repo,read:user,user:email.Q: Will this affect my real projects?
A: Painting happens in a single target repo (created if needed) by editing a marker file. It won’t touch other repos.
Q: Can I limit how many commits it adds per day?
A: The plan respects your existing per‑day contributions and computes the delta to reach the pixel’s level, with internal caps to avoid unnatural spikes.
Q: Can I pause and resume?
A: Yes. You can run
paintmultiple times; it recalculates based on the current state of your contributions.License
MIT (see
LICENSE).