Skip to content

devfetch

neofetch is for machines. devfetch is for developers.

Most GitHub profiles open with a wave: a README that says hello, a short bio, a row of pinned repositories. Mine boots a terminal. There's an ASCII portrait of my face on the left and a column of key: value rows on the right, and it repaints itself every night in whichever TokyoNight my reader is wearing.

This is the whole build, and it's small enough to steal in an afternoon. Every block of source below is pulled live from the repository, so nothing here can drift from what actually runs on my profile. Take it, change the parts marked TODO, and point it at yourself.

neofetch-style GitHub profile card for Illya Starikov: an ASCII portrait beside key-value rows for editor, shell, keyboards, languages, and live GitHub stats, in a TokyoNight terminal window.

That card is live, straight from my profile: two self-contained SVGs that swap by prefers-color-scheme (flip your system theme and watch). The idea isn't mine. I first saw it on Andrew6rant's profile and rebuilt it from scratch, since that project carries no license. The whole thing is about 600 lines across three Python scripts and a workflow.

One File, Two Faces

GitHub has no setting for "show a different image in dark mode," so the entire README is one <picture> element:

README.md · live from GitHub
Loading README.md…

The tempting approach, one SVG with a @media (prefers-color-scheme) block inside it, works in a browser and fails on GitHub, because README images render through an <img> tag and GitHub's camo proxy, and the mobile apps ignore the query outright. <picture> moves the decision up a layer, into HTML that GitHub controls: dark is the <source>, light is the fallback <img>.

That same camo proxy blocks every external fetch, which is why the two SVGs have to be fully self-contained: the font is a Fira Code subset embedded as a base64 data-URI, with no network calls and no working links anywhere in the card.

Drawing a Terminal

The card is drawn by hand, not templated. A single Python script places text on an exact monospace grid (every run pinned to its own x-coordinate so nothing shears if the embedded font falls back), with dotted-leader key: value rows, three macOS traffic-light dots, a title bar, and a TokyoNight palette. It emits both themed SVGs.

src/generate_svg.py · live from GitHub
Loading src/generate_svg.py…

The rows in build_info() are the only personal part: your name, editor, keyboards, contact. That's the first thing you'll change.

A Face You Draw by Hand

The portrait is the part people ask about, and it's the one step that can't be fully automated: you pick your own photo and tune it by eye. Start with a head-and-shoulders shot, cut the background out, and matte it onto black so the subject reads against the dark card:

uvx --python 3.11 --from "rembg[cpu,cli]" rembg i you.jpg you_nobg.png
magick you_nobg.png -background black -flatten you_black.png

Then luminance becomes glyphs (each pixel's brightness picks a character from a 70-glyph ramp), and a second pass walks the same photo cell by cell, converts each to HSV, and snaps it to a theme color. Two rules carry the whole look: pastel blues are forced to stay blue (the shirt), and warm skin-and-hair tones are pushed to silver so the shirt is the only real color in the frame.

src/ascii_portrait.py · live from GitHub
Loading src/ascii_portrait.py…

Re-render with different --contrast / --gamma / --sharpen until the face reads. This is hand-work; every photo is different, and there's no recipe that fits them all.

Counting Yourself

A card that brags should at least be honest, so the stats are real, pulled from GitHub's GraphQL API every night. Repos, stars, and followers are one query. The other two fight back: all-time commits need a year-by-year walk of contributionsCollection (it only spans a year at a time, and you have to fold in restrictedContributionsCount for private work), and honest lines-of-code means walking each repo's history yourself, since the REST stats endpoint is stale, 202s while it computes, and approximates on big repos. Each repo's result is cached against its branch-head SHA, so an unchanged repo costs zero API calls the next night.

src/fetch_stats.py · live from GitHub
Loading src/fetch_stats.py…

The Nightly Heartbeat

None of this is worth doing by hand twice, so a GitHub Action runs it on a cron, redraws the SVGs, and commits them back as a bot only when something changed. The token you give it decides what it can see: the default GITHUB_TOKEN counts public data; a personal access token with private scope (README_TOKEN) counts everything.

Loading the workflow…

The last step is the one I'd urge you to keep. The card that inspired mine had its update cron die quietly in 2025, and by the time anyone noticed it had been frozen for the better part of a year. A heartbeat you can't hear isn't a heartbeat, so mine files an issue against its own repo the moment a run fails. The card is allowed to go stale for a day; it is not allowed to go stale for a year without telling me.

Make It Yours

Everything you'd change is marked TODO in the source above. Clone the repo and grep for it:

grep -rn TODO src/
  • Your GitHub username: fetch_stats.py, the USER line.
  • Every card row: build_info() in generate_svg.py (labels and values both).
  • The user@host title and theme colors: also generate_svg.py.
  • The portrait: your own photo, per the section above.

Then run python3 src/fetch_stats.py and python3 src/generate_svg.py, commit the two SVGs, and drop the <picture> from the root README onto your profile. src/README.md has the full setup, dependencies, and run order.

Credit

The concept belongs to Andrew6rant, whose profile card I admired for a while before building my own. Every line here is an original re-implementation rather than a fork, but the idea, a profile that reads like a terminal and keeps itself current, is his. Mine just adds the thing his was missing: a pulse that screams when it stops.

The full source is at github.com/IllyaStarikov/IllyaStarikov. A profile README is the one page on GitHub that's entirely yours; I'd rather it say something than wave.

Comments

Related

Recent