GitHub Archaeology: Input a Username, Get a 71KB Single-File 'Programming Personality' Excavation
theme: cyanosis
I Used WorkBuddy to Build a “GitHub Archaeology Lab”: Enter a Username and Unearth Your Programming Personality Profile
Online demo: https://github-archaeology.app.workbuddy.host/?sharecode=HVE7YZLNGQ
No login required, reads only public GitHub data, and all computation happens locally in the browser.
After writing code for so many years, have you ever wondered that your GitHub account is actually a ruin?
Which repositories are the earliest strata, which one is your heaviest treasure, what time do you habitually commit, have you ever given variables sloppy names? These traces are all left there; it's just that no one has ever dug through them for you.
This 1024 (Programmers' Day), I used WorkBuddy to build a “GitHub Archaeology Lab”. Enter a GitHub username, and it treats your repositories as an archaeological site, then unearths a “Programming Personality Profile”.
What it digs up
The profile has four parts.
1. Language growth rings
Group repositories that have had pushes by language and draw them as a donut chart, with percentages that add up to exactly 100%.
The sample account has 18 repositories: JavaScript 33%, TypeScript 17%, Vue 11%, HTML 11%, Python 6%, and other 22%. Whether the account is specialized or a generalist shows at a glance.
2. Four-dimensional excavation square
Four dimensions, each with the calculation basis written next to it, not scored at random:
| Dimension | Algorithm | Sample value |
|---|---|---|
| Night Owl Index | Distribution of commit hours; more late-night commits means a higher score | 92 |
| Naming neatness | Repos that follow naming conventions / total repos | 14/18 -> 78 |
| Breadth of exploration | Number of languages + repository scale | 9 languages / 18 repos -> 100 |
| Open-source social | Magnitude of followers and stars | 128 followers / 268 stars -> 85 |
3. Unearthed artifacts
It automatically digs out four “artifacts”: the oldest layer, the heaviest treasure, the brightest exhibit, and a naming fossil.
The sample account unearthed: untitled-game from 7 years ago, dm-supply-next at 24.2MB, juejin-csdn-extension with 86 stars, and a test that looked like it was casually typed at 3 AM.
Honestly, seeing that my first repository was called untitled-game left me a bit dazed.
4. Programming personality + verdict
The four dimension scores combine into one of 16 types, with a one-line verdict. The sample account is “Night Excavator, Stratigraphic Chronicler”:
“You fold every late night into a repository, using commits to carbon-14 date time.”
Finally, you can export a 1080x1440 profile card in one click, complete with a stamp and topic tags.
A few implementation decisions worth mentioning
1. Decide the metaphor first, then the features
The initial feature list was boring: count languages, count repositories, calculate activity. After finishing it, I didn't even want to look at it.
Then I changed the approach: treat the data as an already buried archaeological site, where my job is excavation and dating, not making reports.
That made the structure clear immediately: repositories are strata, languages are growth rings, repository size is artifacts, and commit times are activity cross-sections. Even the kraft-paper texture, the stamp, and the “Archive No. CA-ARCHAE-1150” all grew out of this one metaphor.
The essence of information architecture is not feature classification; it is giving users a memorable explanatory framework. Settle the metaphor first, and the rest is just filling in details.
2. Read only three public endpoints, compute everything locally
No OAuth application, no authorizations, just three public endpoints fetched concurrently with Promise.all:
async function fetchGithub(username){
const base = "https://api.github.com";
const headers = { "Accept": "application/vnd.github+json" };
const [pr, rr, er] = await Promise.all([
fetch(`${base}/users/${encodeURIComponent(username)}`, { headers }),
fetch(`${base}/users/${encodeURIComponent(username)}/repos?per_page=100&sort=pushed`, { headers }),
// events is an optional data source; failure should not drag down the whole flow
fetch(`${base}/users/${encodeURIComponent(username)}/events/public?per_page=100`, { headers })
.catch(() => null)
]);
if (pr.status === 404) throw new Error("NOT_FOUND");
if (pr.status === 403 || pr.status === 429) throw new Error("RATE_LIMIT");
if (!pr.ok) throw new Error("API_FAIL");
const profile = await pr.json();
const repos = rr.ok ? await rr.json() : [];
const events = (er && er.ok) ? await er.json().catch(() => []) : [];
return { profile, repos, events };
}
Two details:
eventsgets its owncatch./events/publiccan return an empty array or fail for some accounts, but it only affects the Night Owl Index, so it should not crash the whole profile; it degrades to an empty array instead.- Errors need to be graded.
404means the user does not exist,403/429means rate-limited, and the messages are completely different. The former should make the user check the spelling; the latter should tell them “come back later”.
3. Share card: Canvas export and avoiding canvas tainting
Drawing an avatar into Canvas and calling toBlob to export is the standard approach for this kind of “image generation” requirement, and it is also the standard pitfall:
const img = new Image();
img.crossOrigin = "anonymous"; // Key: without this, the canvas will be marked as tainted
img.src = avatarUrl; // GitHub avatars go through a CDN and need CORS permission
img.onload = () => {
// Unify the aged look: grayscale + lower contrast + slight sepia, matching the kraft-paper texture
ctx.filter = "grayscale(.35) contrast(1.05) sepia(.12)";
ctx.drawImage(img, x, y, w, h);
};
// Export
canvas.toBlob(done, "image/png");
For the sample account, I simply converted the avatar to a base64 string and embedded it in the HTML, killing two birds: it also solves the problem of the sample profile missing an avatar when offline or rate-limited.
4. Single-file HTML, zero dependencies, zero build
The whole application is a single 71KB index.html, vanilla JS, no CDN imports, no bundler, and system fonts only.
The benefit is direct: publish and it is live; edit one line and republish, and the link stays the same. It also avoids problems that are especially deadly in sharing scenarios, like “the CDN dies at peak hours, fonts fail to load, and the page styling collapses”.
code-archaeologist/
├── index.html # single-file app, double-click to open
├── assets/
│ └── demo-avatar.png # demo avatar (already embedded in HTML)
└── screenshots/
To run it locally, download index.html and double-click it; no server needed.
Two pitfalls hit along the way
The first is anonymous API rate limiting. GitHub allows only 60 unauthenticated requests per hour, and exceeding that returns 403 directly. So in addition to graded error messages, there is an offline fallback: when the API is unavailable, the app can switch to the built-in sample profile, guaranteeing that whoever opens it always has something to see and never faces a blank screen.
The second is canvas tainting. That is the crossOrigin bit above. The sneaky part is worth noting: if the image loads successfully but crossOrigin was not set, drawImage does not throw; only toBlob throws a SecurityError. The error location and the real cause are several lines apart, and the first investigation can easily go in the wrong direction.
Neither of these is hard, but both belong to the category of “you will not find them until you actually run it”. The advantage of using WorkBuddy is that it can run the code and report the error directly, which is much faster than blind trial and error.
About privacy
This tool reads only three public endpoints and involves no authorized login. The data it obtains is calculated and rendered entirely in the browser, with no upload, no database storage, and no retention. Close the page, and nothing is left behind.
If you are also building a tool that reads third-party user data, this advice is worth copying: do not request authorization when you can avoid it. A solution that reads only public data can state its privacy policy in one sentence, and the user's psychological barrier is much lower.
Finally
Want to see what personality you are? Click here to test directly, results in three seconds:
https://github-archaeology.app.workbuddy.host/?sharecode=HVE7YZLNGQ
You can also append a username directly to open that user's own profile:
https://github-archaeology.app.workbuddy.host/?sharecode=HVE7YZLNGQ&u=your-github-username
After testing, feel free to report your personality type in the comments. I am curious how many people share the “Night Excavator, Stratigraphic Chronicler” type.
This work participates in WorkBuddy's “1024 Maker Challenge” programmer's day application creation contest.
Tech stack: single-file HTML + vanilla JS | GitHub REST API | Canvas 2D