Skip to main content

Building a Terminal-Themed GitHub Profile README (With Live, Self-Updating Stats)

Introduction
#

GitHub has a small, under-used feature: create a public repository with the exact same name as your username, and the README.md inside it gets rendered at the top of your profile page — before your pinned repos, before anything else. It’s the closest thing GitHub gives you to a personal landing page, and most people leave it either empty or filled with a stock template.

This post walks through building one styled as a Linux terminal session — a whoami, a cat about.md, a skills listing — with genuinely dynamic content underneath: repo counts, per-project stars, contribution streaks, and a visitor counter that update themselves with zero maintenance, no personal access token, and no server of your own.

One important expectation to set upfront: a GitHub profile README is static markdown — GitHub strips out all <script> tags, so you cannot build a literally interactive terminal where typing whoami triggers a real command. What you can build is something that convincingly looks like a terminal session, with the “dynamic” parts being live-updating images, not live-executing code. That distinction matters and is worth understanding before you start.


Step 1: Create the Special Repository
#

  1. Go to github.com/new
  2. Name the repository exactly your username (case-sensitive match)
  3. Set it to Public — private doesn’t render on your profile
  4. Skip the README initialization — you’ll push your own

GitHub confirms it worked with a banner on the empty repo: "your-username/your-username is a special repository."


Step 2: The Trick Behind “Dynamic” Content in Static Markdown
#

Since a README can’t run code, every “live” element here is actually an image — an SVG generated on-the-fly by a small web service every time GitHub (or a browser) requests it. The two techniques used throughout:

shields.io dynamic JSON badges
#

shields.io can fetch a JSON document and render one field from it as a badge, generated fresh on each request (cached briefly):

https://img.shields.io/badge/dynamic/json?url=<json-endpoint>&query=$.<field>&label=<label>&color=<hex>

Pointed at GitHub’s own public API, this gives you a badge that always reflects reality with no code of your own running anywhere:

![Public Repos](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.github.com%2Fusers%2Fyour-username&query=%24.public_repos&label=Public%20Repos&color=39FF14&style=for-the-badge&logo=github)

api.github.com/users/your-username returns a JSON object with public_repos, followers, following, etc. — swap the query field to show any of them. The same trick works per-repository against api.github.com/repos/your-username/your-repo for star count and primary language:

![stars](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.github.com%2Frepos%2Fyour-username%2Fyour-repo&query=%24.stargazers_count&label=stars&color=39FF14&logo=github)

Create a new repo tomorrow, and the public-repo-count badge shows the new number the next time someone loads your profile — no workflow, no secret, no cron job.

Third-party stat-card generators
#

A handful of community projects run a small server that draws an SVG card from your public GitHub activity on each request:

What it showsService
Current/longest contribution streakstreak-stats.demolab.com/?user=your-username
Daily contribution graphgithub-readme-activity-graph.vercel.app/graph?username=your-username
Tech stack icon rowskillicons.dev/icons?i=py,js,docker,...
Typing/rotating headline textreadme-typing-svg.demolab.com/?lines=...
Profile visit counterkomarev.com/ghpvc/?username=your-username

None of these need authentication for public data — you just embed the URL as an <img> and the service does the work.


Step 3: A Gotcha Worth Knowing — Public Instances Go Down
#

While building this, two of the most commonly recommended widgets turned out to be broken: the main stats/top-languages card at github-readme-stats.vercel.app returned DEPLOYMENT_PAUSED, and the trophy case at github-profile-trophy.vercel.app returned Payment required / DEPLOYMENT_DISABLED.

This isn’t a one-off — both projects explicitly warn about it in their own docs:

“The public Vercel instance is best-effort and can be unreliable due to rate limits and traffic spikes.”

These are extremely popular projects (tens of thousands of profiles embed them), so the shared free-tier hosting periodically gets rate-limited or paused entirely. Always test the actual image URL in a browser or with curl before committing it to your README — a broken <img> tag on your professional profile page looks worse than not having the section at all.

curl -s -o /dev/null -w "%{http_code}\n" "https://github-readme-stats.vercel.app/api?username=your-username"
# 503 -> don't ship it

If you want those specific widgets and can’t rely on the shared instance, both projects support deploying your own copy to your personal free Vercel account in a few clicks, which isolates you from everyone else’s traffic:

  • Stats card: github.com/anuraghazra/github-readme-stats → “Deploy on your own”
  • Trophy case: github.com/ryo-ma/github-profile-trophy → “Deploy your own instance”

For this build, I skipped both and leaned on the widgets that were actually confirmed live (streak stats, activity graph, skill icons, shields.io, visitor counter) — a “stunning” profile that’s actually up beats a more elaborate one with broken images.


Step 4: Structuring It Like a Terminal Session
#

The “terminal” feel comes entirely from formatting, not functionality — fenced code blocks with a fake prompt line, followed by the “output” as plain text in the same block:

```bash
you@github:~$ whoami
Your Name
Your role — your focus areas
```

Repeating this pattern for whoami, cat about.md, ls skills/, and cat contact.txt gives a consistent, scannable rhythm, with the dynamic badges/cards dropped in after each “command” as if it were the command’s real output:

### `$ ./repo-stats.sh --live`

<img src="https://streak-stats.demolab.com/?user=your-username&theme=dark&hide_border=true" />
<img src="https://github-readme-activity-graph.vercel.app/graph?username=your-username&theme=tokyo-night&hide_border=true" width="100%" />

A monochrome-green accent color (#39FF14) on the badges and typing banner reinforces the terminal look without needing any custom CSS — GitHub-flavored markdown doesn’t allow <style> blocks either, so color has to be pushed through each service’s own color= parameter.


Step 5: Native Pinning, Not Just README Links#

GitHub separately lets you pin up to 6 repositories so they render as cards directly on your profile, above or below the README depending on theme — go to your profile → Customize your pins. This is worth doing in addition to linking projects in the README, since pinned repos show live star/fork counts and a language color bar without any markdown at all.


Step 6: Private Repo Counts (Optional, More Setup)
#

The shields.io trick above only works for public data — api.github.com/users/<you> never reveals private repo counts to an unauthenticated request, by design. If you want a “Public: 12 · Private: 8” style badge, that requires:

  1. A GitHub Personal Access Token with repo scope
  2. Stored as an encrypted Actions secret in your profile repo (never in the README or committed anywhere)
  3. A small scheduled GitHub Action that runs a script authenticated with that token, computes the counts, and rewrites a marked section of README.md, then commits

This is meaningfully more setup than the zero-token approach above, and it’s the only place a secret enters the picture at all — everything else in this build is 100% public data through public endpoints.


Conclusion
#

A GitHub profile README earns its “dynamic portfolio” reputation almost entirely from a handful of small, free, public image-generating services — not from anything running on your own infrastructure. The real engineering judgment is in:

  • Verifying every embedded URL actually resolves before shipping — popular free-tier widgets go down more often than their star counts would suggest
  • Choosing shields.io + GitHub’s own API wherever possible, since both are far more reliable than any third-party clone
  • Accepting that “dynamic” here means live-rendered image, not interactive terminal — and designing the terminal illusion around formatting rather than fighting markdown’s lack of JavaScript

The result is a profile page that updates its repo counts, star counts, and contribution streaks forever, without a single scheduled job to maintain.

Vivek US
Author
Vivek US
A Tech Enthusiast