Changelog
Format: Keep a Changelog, SemVer
[Unreleased]
Added
- mcp: a built-in MCP endpoint at
/mcp— streamable HTTP, stateless, no auth, no SDK dependency — with thirteen tools covering every public feature:list_card_types,generate_card_url,preview_card,get_user_stats,register_card,inspect_card,revoke_card,purge_user_cache,opt_out,opt_in,read_docs, and the ChatGPT-connectorsearch/fetchpair. REST and MCP share one actions layer (src/actions.ts), so neither transport can drift from or bypass the other. - api:
GET /api/stats(the cards' numbers as JSON, with rank and freshness),POST /api/inspect(manage key → frozen settings, never secrets),POST /api/purge(delete every cached byte for a username; 2/h/IP + 1/day/username), andPOST /api/optout/POST /api/optin(owner-verified render block and reversal — verification is one identity probe with any zero-scope token of the account, never stored). Opted-out users render an "Opted out" card, return 410 from/api/stats, and cannot be re-registered. - api: machine-readable
codeon every JSON error (plusretryAfterSwhere retryable), CORSaccess-control-allow-origin: *on all public responses with OPTIONS preflight on POST routes, and a rate limit on the previously unlimited/api/revoke(newmanagescope, 30/h/IP). - discoverability: a
/docspage (REST + MCP reference with connect guides for claude.ai, Claude Code, Cursor, and ChatGPT),llms.txt+llms-full.txt(the llmstxt.org convention;llms-full.txtis the canonical machine reference theread_docstool serves),openapi.json(OpenAPI 3.1 for ChatGPT Actions and agent frameworks),/.well-known/mcp.json, and an in-reposerver.jsonready for MCP-registry submission. All URL-bearing files are stamped bynpm run site:meta; a docs-parity test fails CI if any card type, theme, route, tool, or error code goes missing from the docs. - storage:
RedisLike.scanin all three adapters (Upstash REST, TCP, memory), used only by the purge path — the render hot path never scans.
Added
- site: privacy (
/privacy), security (/security), and changelog (/changelog) pages — truthful trust content generated and URL-managed bynpm run site:meta, including the changelog rendered straight fromCHANGELOG.mdwith no new dependency. Plus a real 404 page, one identical footer nav on every page,.well-known/security.txt, clean extensionless URLs, and a sitemap covering all four pages. - repo: MIT
LICENSEandSECURITY.mdvulnerability-disclosure policy.
Changed
- seo: dynamic surfaces (
/api/*,/c/*) now sendX-Robots-Tag: noindex, nofollow, noarchiveso embedded cards never get indexed even when bots fetch them; the landing H1 is now "GitHub stats cards for your README" (the old line stays as supporting copy) and meta descriptions describe all ten card types.
Added
- cards: six new card types, bringing the total to ten.
repos(top 5 public non-fork repos by stars, with language dots and fork counts),activity(31-day contribution area chart with peak marker),trophies(six S+/S/A/B/C tier tiles in gold/silver/bronze),heatmap(GitHub-style 36-week contribution grid in the theme accent),monthly(12-month contribution bars), andweekdays(contributions by day of week with a busiest-day callout). All work on/api/card?card=…and registered/c/<token>/<card>routes, appear in the landing-page picker (now two rows of chips) and post-generate gallery, and are returned as register-response snippets. Calendar-based cards share one generalized fetch; monthly and weekdays reuse a single cached year window, and repos/trophies cost zero extra GitHub calls.
Fixed
- stats: the Issues row's "resolved" count now means issues resolved as a maintainer — closed issues across repos the user owns, whoever authored them (via GitHub search) — instead of "issues the user authored that happen to be closed", which wildly undercounted for maintainers.
- stats: all baseline counts (stars, repos, PRs, issues, languages) are now public-only by construction (
privacy: PUBLICrepo listing,is:publicsearches), so anonymous and PAT-backed renders of the same profile always agree regardless of how privileged the fetching token is. Previously a privileged fallback token (e.g. dev borrowinggh auth token) leaked private-repo stars/PRs/issues into the anonymous card.count_privatestill augments the commits number only. - register: fine-grained PATs restricted to "Only select repositories" are now rejected at registration with an actionable message — GitHub silently filters every GraphQL count to token-visible repos, which made such cards undercount stars/PRs/issues versus the public view.
- landing page: restored the live preview's cache-buster (dropped in the static-hero change), so the preview can no longer serve a stale browser-cached SVG; the dev server also serves static assets with
no-cacheso JS/CSS edits always reach the browser after a plain reload.
Added
- seo: full discoverability pass on the landing page — canonical/robots meta, Open Graph + Twitter cards with a 1200×630 social image, JSON-LD (
WebSite+ maintainerPerson), favicon set (SVG + PNG sizes + maskable), web manifest,robots.txt(API and/c/routes disallowed), andsitemap.xml. All absolute URLs are regenerated fromPUBLIC_BASE_URLvianpm run site:meta, so the committedlocalhostvalues are placeholders until the deploy domain is set. - landing page: the hero preview now starts on a bundled static example SVG instead of a live API call, so first paint never depends on GitHub/Redis. The LIVE tag and live-request footnote stay hidden until the first real interaction triggers an actual render.
- dev: standalone
npm run devserver — runs againstREDIS_URLdirectly over TCP, no Docker required. Falls back to Upstash REST env vars, then an in-memory store, in that order. - dev: per-scope rate limits are now configurable (
AppDeps.rateLimits); both dev modes get generous limits (card 1000/60s, registered cards 5000/60s, register 100/hour) so normal iteration never trips "Cooling down". Production is unaffected — it keeps the same defaults as before (30/60s, 120/60s, 5/hour) since it doesn't pass an override. - landing page: after generating a card, "All four cards, live" shows a real live preview of all four card types (stats, languages, streak, badge) instead of text-only copy links. Languages gets a Bars/Donut toggle that swaps the live preview and the copy-ready markdown together; Badge lists every metric variant (stars, followers, commits, PRs, issues, repos), each with its own copy button. Registered card routes (
/c/<token>/langs) now accept an optional?layout=bars|donutquery override so a bar-layout registration can still link a donut variant without re-registering. Gallery images load one at a time instead of all at once, since firing many concurrent requests for a brand-new username was racing the server's per-user render lock and surfacing "warming up" placeholders.
Changed
- rank: stats-card rank score is now an absolute-magnitude formula (
log10(1+n)weighted sum: stars 0.40, commits 0.28, PRs 0.10, followers 0.08, issues 0.05, reviews 0.05, issuesClosed 0.02, contributedTo 0.02) instead of a population-percentile comparison. The percentile model capped how much any one metric could contribute (its own weight share, however maxed out), which made huge-but-inactive profiles (e.g. 20k+ stars, 0 commits this year) read as mediocre. Also adds areviewssignal (fromtotalPullRequestReviewContributions, no extra API cost).
Added
- streak card: the 🔥 now flickers continuously (subtle scale + rotation), separate from its one-time entrance fade. Adds
prefers-reduced-motionsupport to all SVG cards, since this is the first continuous animation among them.
Fixed
- dev:
npm run dev:flush-cachenow connects to Redis directly (REDIS_URLif set, else the docker stack's local redis) instead of shelling into a docker container — it works in standalone dev mode, not just the docker stack. - badge card: fixed a rendering bug where the label/value segments and the border were each rounded independently, producing a mismatched notch at the seam and a doubled border curve. Both segments are now clipped by one shared outline, and the border (when shown) is a single stroke on that same path. Also removed a CSS double-rounding on the landing page's badge preview, where a wrapper
overflow:hiddenclipped the already-rounded badge a second time at an unrelated radius.