跪拜 Guibai
← Back to the summary

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”.

01-homepage.png


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.

02-profile-sample.png

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.

04-share-card.png


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:

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