One prompt, and a GitHub profile that reads like a professional wrote it.
Most profile READMEs fail the same three ways: a wave emoji and a sentence that would be true of anybody, a row of stats widgets served by someone else's machine, and a list of technologies nobody can verify. byline is a prompt that refuses all three, six templates that survive what GitHub actually renders, and a small script that hands the model your real repositories so it stops inventing you.
git clone https://github.com/openwarehq/byline
cd byline
node facts.mjs <your-username>Paste PROMPT.md into Claude, paste the facts block underneath,
send. Put the output in a repository named exactly your username.
Want the animated one:
node banner.mjs <your-username> --out profile.svgThat is a real generated file, animating right now — scanning portrait, typed dossier, counters. Every figure in it came from the API.
Working under an org? Name it — see the org trap below, because this one silently ruins the result.
A repository whose name is exactly your username — octocat/octocat — with
a README.md at its root, public. GitHub renders that file at the top of your
profile page. There is no setting to switch on; the repository name is the whole
mechanism, and if the name does not match your username character for character
nothing appears.
GitHub offers to create it for you with a "special repository" note when you start a new repo with your own name.
If your organisation membership is private — which is the default — then
GET /users/you/orgs returns nothing, and any tool that trusts it misses every
repository you have shipped under that org.
That is usually your best work. On the account this repo was tested against, including the org moved the top project from 3★ to 49★ and the total from 22 stars to 88.
node facts.mjs <your-username> --also <your-org>--also is repeatable, and takes another user account just as happily as an org.
The rules are in PROMPT.md in full. The four that change the
output most:
No third-party widget services. No github-readme-stats, no streak counter,
no trophy case, no visitor badge. Those render on someone else's server: they
rate-limit into a broken image at the worst possible moment, they can vanish
entirely, and every person who visits your profile makes a request to a host
neither of you chose. Your profile should not be an analytics event for a
stranger.
No claim without a fact behind it. If the facts block does not say it, the model does not know it — not what you are learning, not what you are passionate about, not how to reach you. Given nothing to work with, a model fills the space, and what it fills it with is the reason every profile reads the same.
Lead with work. "Full-stack developer passionate about scalable solutions" cannot be false, so it says nothing. The projects are the evidence and the sentence is the caption, not the other way round.
Only what GitHub renders. READMEs are sanitised: no <script>, no <style>,
no class, no CSS at all. Every template here is built from the subset that
actually survives, which is why none of them depend on a stylesheet that will
never load.
01-plain |
No HTML at all. Renders identically everywhere. Pick this if unsure. |
02-terminal |
A fenced block as the masthead — designed-looking, zero external requests. |
03-index |
A table. Fastest to scan past about four projects. |
04-dossier |
Collapsed <details> sections. Depth without spending the first fifteen seconds. |
05-centered |
The one with a banner, and a <picture> that swaps for dark mode. |
06-dossier-scan |
The animated one. Uses banner.mjs. |
Name one in your message, or let the model choose from the facts.
There is a worked example in examples/ — a real
account, with a note on what was deliberately left out.
banner.mjs writes a self-contained SVG, 1200×1180 — tall enough to scroll:
- a terminal panel with your avatar as a duotone dot-matrix, scanline sweeping it
- your dossier, typed out line by line
- five counters that count up to their real values
- the contribution graph — the real green squares, twelve months, with month ticks, total, active days, longest streak and current streak
- language distribution bars, and a concentration ring
- selected repositories with star bars, and recent activity
~240 animations. It plays once and stops on the finished state — a looping
reveal blanks the panel and retypes it every few seconds, which means most
visitors arrive mid-build and read a half-empty page. The build is a nice thing
to catch, not the resting state. --loop restores looping; --still also stops
the ambient scanline and cursor for a completely static image.
84–118KB depending on your avatar.
node banner.mjs <your-username> --also <your-org> --out profile.svgThe point is that it is a file you own. GitHub renders SVG through its image proxy and animation inside the SVG plays — that is how every animated profile works. But nearly all of them point at a hosted service, which means the centrepiece of the profile is a request to a machine the author does not control: when it rate-limits, the profile shows a broken image, and when it shuts down, the profile shows a broken image forever. This one has no external references at all. The avatar is inlined as a data URI, so it renders with the network off.
What it cannot do, because GitHub strips both: JavaScript, and anything needing a stylesheet. The animation is SMIL, which survives the proxy.
The contribution graph is real. It is not in the REST API — only in GraphQL, which needs a token — so byline reads the same public HTML fragment the profile page itself uses. No token, and it is the same data GitHub shows anyone who visits you. If GitHub changes that markup the panel is omitted and the rest of the banner still renders; a missing panel should not cost you the whole file.
The counters genuinely count. SMIL cannot animate the text inside a node, so each one is fifteen pre-rendered values cross-faded in sequence — the only way to get the effect inside GitHub's rules. The last step holds to the end of the loop, so it always settles on the true figure and never on a rounded one.
It is a snapshot, not a live feed. Regenerate it when the numbers move — a stale star count is worse than no star count.
If your avatar is a flat colour, the portrait panel renders as an empty scan frame. That is not a bug; there is nothing in the image to resolve.
Organisations have no contribution calendar — GitHub 404s that endpoint for them. Rather than leaving a labelled empty panel on a public page, an org banner puts every repository in that space instead, which is what an organisation profile is for. See openwarehq, which runs this.
- It doesn't write your bio for you. It will not invent a personality, and if your repositories have no descriptions it will tell you so rather than paper over it. Descriptions are almost always the highest-value thing you can fix.
- It can't see private work, or contributions to repos you don't own. The public API shows public repositories owned by the names you give it. Pull requests you have landed elsewhere are invisible to it — add those yourself.
- Followers and stars are not a measure of you. They are in the facts block because leaving a number out and having the model guess is worse. Small numbers are fine; the templates are built so a three-repository profile reads as confident rather than empty.
- Join date is not experience. It is when you made an account. The prompt is told not to convert it into a seniority claim.
The public API allows 60 requests an hour per IP and one run costs two, plus one
per --also. If you hit the limit, GITHUB_TOKEN raises it to 5,000:
GITHUB_TOKEN=ghp_... node facts.mjs <your-username> --also <your-org>A classic token with no scopes ticked is enough — everything read here is already public.
MIT. Node 18+ for fetch; no dependencies, no install step, no network calls
except to api.github.com and your avatar.