Skip to content

Latest commit

 

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

devcard — a live, embeddable dev stats card powered by real AI-coding activity

scpe scpe-seal license: MIT TypeScript Cloudflare Workers + D1 Python 3

A live, embeddable dev stats card — powered by your real AI-assisted coding activity, not your keystrokes.

Claude Code Codex Cursor aider, Windsurf, local models MIT

devcard — live example

↑ This is a real, live card. It updates within seconds of its owner writing code.

Tools like WakaTime measure how long your editor is focused. devcard measures something new: the actual output of your AI coding sessions. A tiny hook watches every edit Claude Code makes on your machine, and your card updates in near real time — languages, volume, commits, activity — wherever it's embedded.

Why it's different

  • Live, not batch. The card reflects your latest coding session within seconds, not "synced last night."
  • Measures agent output. Built for the vibe-coding era: it captures what you ship with an AI agent, which keystroke timers can't see.
  • Private by architecture, not by promise. Project names, file paths, and code content never leave your machine. The public backend only ever receives: language, line counts, event type, timestamp, and a repo count (your GitHub repository total — see what it counts). There is no column in the public database where a filename could even be stored.
  • Embeds anywhere. It's just an SVG URL — paste it in your GitHub profile README, portfolio, blog, anywhere <img> works.
  • Speaks the viewer's language. The card auto-localizes (en/pt/es) based on the visitor's browser. Force one with ?lang=pt.
  • Auto dark/light. Follows the viewer's system theme via prefers-color-scheme.
  • Sponsor button, automatically. If your GitHub Sponsors page is active, a ♥ Sponsor pill appears on its own.

How it works

flowchart LR
    subgraph M["your machine"]
        CC["Claude Code<br/>PostToolUse hook"] -->|"every Edit / Write / Bash"| DB[("local SQLite<br/>full detail, offline-first")]
        GT["git post-commit hook<br/>(Codex, Cursor, aider, hand-typed…)"] -->|"git diff --numstat"| DB
        DB -->|"throttled, detached"| SY["devcard_sync.py"]
    end
    SY -->|"POST /ingest<br/>anonymized batches, token-gated"| W["Cloudflare Worker + D1"]
    E["your embed<br/>README · site · anywhere"] -->|"GET /svg"| W
    W -->|"rendered SVG<br/>cached 60s"| E
Loading
  1. A global Claude Code PostToolUse hook fires on every Edit/Write/Bash call — pure Python stdlib, zero token cost, runs in milliseconds, and never blocks your session (all errors are swallowed and logged locally).
  2. Events land in a local SQLite database first (source of truth — works offline, syncs later).
  3. Anonymized batches sync to a Cloudflare Worker + D1 (free tier is plenty).
  4. The Worker renders your SVG card on demand, cached 60s.

Deploy your own — one command

You need: Python 3, Node 18+, git, and a free Cloudflare account.

git clone https://github.com/augbastos/devcard && cd devcard && python setup.py

The wizard does everything: creates your D1 database, applies the schema, generates and stores your ingest token, deploys your Worker, installs the capture hook, runs an end-to-end smoke test, and prints your ready-to-paste embed snippet. Two questions, ~2 minutes.

Works with your agent

Pick one capture mode per machine (both together would double-count the same lines):

Your tool Mode Granularity How
Claude Code claude Live, per-edit — card moves while you code Native PostToolUse hook (installed by setup.py)
OpenAI Codex git Per-commit, real diff stats git post-commit hook
Cursor git Per-commit, real diff stats git post-commit hook
aider / Windsurf / Cline git Per-commit, real diff stats git post-commit hook
Local models (Ollama, LM Studio, llama.cpp + anything) git Per-commit, real diff stats git post-commit hook
Hand-typed code git Per-commit, real diff stats git post-commit hook

The git mode hooks git itself, not the agent — that's why the compatibility list is "anything that commits", with zero per-tool integration code to maintain. It reads each commit's real git diff --numstat (lines and bytes per language), so the numbers are actual diff stats, not estimates.

Install it into any repos you want tracked (appends safely to existing hooks like husky — nothing gets overwritten):

python hook/install_git_hook.py C:/path/to/your/projects

New agent hits the market tomorrow? If it commits to git, your card already supports it.

Manual setup (if you prefer to see every step)

1. Clone and create your backend

git clone https://github.com/augbastos/devcard
cd devcard/worker
npm install
npx wrangler login
npx wrangler d1 create devcard        # copy the database_id it prints

Edit worker/wrangler.toml: paste your database_id and set GITHUB_USERNAME to your GitHub login.

npx wrangler d1 execute devcard --remote --file=schema.sql
npx wrangler deploy                    # note your URL: card.<your-subdomain>.workers.dev

2. Create your ingest token

python -c "import secrets; print(secrets.token_hex(24))"
npx wrangler secret put INGEST_TOKEN   # paste the token when prompted

Set the same token as an environment variable so the hook can use it:

# Windows
setx DEVCARD_INGEST_TOKEN "<your-token>"
# macOS/Linux — add to your shell profile
export DEVCARD_INGEST_TOKEN="<your-token>"

3. Point the hook at your Worker

In hook/devcard_lib.py, set WORKER_INGEST_URL to https://<your-worker-url>/ingest.

4. Register the hook in Claude Code

Add to ~/.claude/settings.json (adjust both paths to your machine):

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python /path/to/devcard/hook/devcard_capture.py",
            "timeout": 3000
          }
        ]
      }
    ]
  }
}

Restart Claude Code, write some code, and open https://<your-worker-url>/svg?user=<you>. That's your card.

Embed it

<img src="https://<your-worker-url>/svg?user=<you>" alt="devcard" />

For your GitHub profile: create a repo named exactly like your username, and paste that line into its README.

Layouts & themes

Mix and match with query params — every combination is a valid embed:

<img src="https://<your-worker-url>/svg?user=<you>&layout=banner&theme=terminal" alt="devcard" />

Layouts (?layout=):

Value Size What you get
full (default) 480×tall Everything: avatar, streak flame, language bar + legend, 16-week contribution heatmap, pinned repos, stats, badges
banner 480×72 One-line strip: avatar, @user, lines, streak, mini language bar — for forum sigs and tight READMEs
half 480×152 Header + lines + language bar + stats row
vertical 280×~290 Narrow column for site/blog sidebars

Themes (?theme=): default (follows the viewer's light/dark system theme) · dark · light · gentle (soft rosé/lilac) · cyberpunk (neon noir) · terminal (green-phosphor CRT).

The heatmap paints real per-day output (intensity relative to your own p90, so one huge day doesn't flatten the rest), the flame lights up at a 7+ day streak, and the footer shows "updated Xmin ago" — a live card that can prove it's live. Day boundaries use the TIMEZONE var in wrangler.toml.

Badges, certifications, awards

Manual, self-declared entries rendered as pills with icons (star = badge, seal = certification, trophy = award):

cd worker
npx wrangler d1 execute devcard --remote --command \
  "INSERT INTO profile_entries (kind, label, detail, created_at) VALUES ('certification', 'AWS Cloud Practitioner', NULL, strftime('%s','now'))"

kind is one of badge | certification | award.

Pinned repos

Highlight up to 3 repos on your card — each renders as a linked box with live star count:

npx wrangler d1 execute devcard --remote --command \
  "INSERT INTO pinned_repos (repo, note, position, created_at) VALUES ('wavr', 'privacy-first home presence', 1, strftime('%s','now'))"

repo must be a public repo under your GitHub account. note is an optional one-liner. Lowest position renders first.

Customize it (please do)

MIT licensed — fork it and make it yours. No framework, no build step for the card itself — just plain SVG template strings:

  • Colors/themes: token sets (light + optional dark) in worker/src/themes.ts — a new theme is a handful of hex values
  • Layout: worker/src/render.ts (full) and worker/src/render-layouts.ts (banner/half/vertical)
  • Languages: detection via the EXT_LANGUAGE map in hook/devcard_lib.py, colors via LANGUAGE_COLORS in worker/src/svg-utils.ts
  • Strings/locales: add a language to STRINGS in worker/src/index.ts in ~1 line

Privacy model

Data Local SQLite Public D1/card
Language, lines added/removed
Event type, timestamp
Repo count (a number)
Project names / paths ✅ (never leaves) ❌ no column exists
File names, code content ❌ never stored

The sync payload is built from a SQL projection that physically excludes project identifiers, and the public schema has nowhere to put them. Ingest is token-gated and idempotent.

What the repo count actually counts

It is the number of repositories you own on GitHub — public plus private — read locally through the gh CLI (already authenticated on your machine) and shipped as a single integer. No GitHub token ever goes near the Worker, and the number matches what the card's N repos → link resolves to.

If gh isn't installed or the call fails, the card falls back to counting the distinct git repository roots you've worked in, resolved from the working directories the hook recorded. Those directories stay local; only the total is published.

The raw count of working directories is deliberately never published. A cwd is not a repo: agent scratchpads, node_modules, and eleven subfolders of one project would each register as "a repo" and inflate the number several-fold.

Security model

The card is designed so it can't be turned against its owner:

  • No inbound surface on your machine. The hook opens no ports and listens to nothing — it only makes outbound HTTPS calls to your Worker. There is nothing on your computer for an attacker to connect to.
  • Ingest is locked down. POST /ingest requires a secret token, enforces strict schema validation (types, ranges, event-type whitelist), caps batch size (100 events) and body size (256 KB), and skips anything malformed instead of erroring.
  • The public endpoint is read-only aggregate data. GET /svg runs fixed, parameterized SQL over anonymous aggregates. The user parameter is only ever compared against your configured username — never used in a query or a fetch.
  • Rendering is injection-safe. Every dynamic string (badge labels, repo names, notes) is XML-escaped before entering the SVG; the SVG contains no scripts.
  • Secrets never touch git. The token lives in Wrangler's secret store + your env; .dev.vars is gitignored.

Counting rules

Two capture modes, counting different things on purpose.

git mode reads git diff --numstat per commit, so a line is a real net change. Commits count invocations that produce a commit; --amend is excluded, because it rewrites one already counted.

claude mode counts written output — an edit counts the region it replaced, a write counts the file. That is why the card says lines written rather than lines of code: it measures what a session produced, not the size of a codebase.

Ingest enforces plausibility server-side: events with impossible line counts, timestamps outside a sane window, or unknown types are rejected. The card's "tracking since" line shows how long the account has actually been measured.

Roadmap

  • Signed batches for tamper-evident sync
  • More locales and community themes — a theme is about 20 lines of tokens in worker/src/themes.ts, PRs welcome
  • Multi-user hosted mode

Built by Augusto Bastos · MIT

About

Live, embeddable dev stats card for the AI coding era — works with Claude Code, Codex, Cursor, aider, local models, anything that commits

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages