Skip to content

sembsa/claude-code-race

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🏁 Claude Code Race

A live racing leaderboard powered by Claude Code hooks. Each developer gets their own lane; the more prompts they send today, the further their car drives. When Claude is "thinking" (between UserPromptSubmit and Stop), a yellow 😊 badge pops above the vehicle and the car bobs faster. Top 5 leaderboards are tracked per day and per week.

Designed for a full-screen TV display in the office β€” autostarts after boot.

Translations: English Β· Polski Β· Deutsch Β· EspaΓ±ol Β· FranΓ§ais Β· ζ—₯本θͺž

preview


Why?

Claude Code emits hooks at well-defined moments of a coding session. Wiring them into a single shared dashboard turns day-to-day usage into a friendly, ambient game: nothing serious, nothing surveillance-y, just a fun way to make the office feel alive.


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    POST /api/event    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Developer's laptop      β”‚ ─────────────────────►│  Server (Node + WebSocket)    β”‚
β”‚  Claude Code             β”‚                       β”‚  Express + ws + JSON storage  β”‚
β”‚   β”œβ”€β”€ hook (sh|cmd|ps1)  β”‚                       β”‚                               β”‚
β”‚   └── ~/.claude/settings β”‚                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                    β”‚ WebSocket push
                                                                β–Ό
                                                      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                      β”‚ Browser dashboard   β”‚
                                                      β”‚ (TV in the office)  β”‚
                                                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  • Hooks fire on UserPromptSubmit, PreToolUse, Stop, SessionStart.
  • A tiny curl / Invoke-WebRequest posts to the server with { user, type, hostname }.
  • The server aggregates daily + weekly counters, persists to data/stats.json, and broadcasts snapshots over WebSocket. The browser re-renders cars positions in real time.
  • No frontend framework, no database β€” just Node + vanilla CSS/JS.

Quick start (server)

Requires Node.js 18+ (tested on Node 22 LTS).

git clone https://github.com/sembsa/claude-code-race.git
cd claude-code-race
npm install
npm start                            # http://0.0.0.0:3000

Open http://<host>:3000 in any browser (full-screen mode recommended for the TV). Press F to toggle browser fullscreen.

Persistent data lives in data/stats.json (auto-created, debounced writes).

Production install (systemd)

sudo tee /etc/systemd/system/claude-race.service <<'UNIT' >/dev/null
[Unit]
Description=Claude Code Race server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=YOUR_USER
WorkingDirectory=/path/to/claude-code-race
ExecStart=/usr/bin/node /path/to/claude-code-race/server.js
Restart=always
RestartSec=3
Environment=PORT=3000

[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now claude-race

Kiosk display (GNOME / Ubuntu)

Create ~/.config/autostart/claude-race-kiosk.desktop:

[Desktop Entry]
Type=Application
Name=Claude Code Race (kiosk)
Exec=firefox --kiosk http://localhost:3000
X-GNOME-Autostart-enabled=true

Quick start (developers β€” install hooks)

macOS / Linux

curl -sSL http://<host>:3000/install.sh | bash
# or with a custom nickname:
curl -sSL http://<host>:3000/install.sh | NAME=alice bash

Windows (PowerShell 5.1+ / 7+)

iwr -useb http://<host>:3000/install.ps1 | iex
# or with a custom nickname:
$env:NAME="bob"; iwr -useb http://<host>:3000/install.ps1 | iex

The installer:

  1. Creates ~/.claude-code-race/ with a hook.sh (Unix) or hook.ps1 + hook.cmd (Windows).
  2. Idempotently appends hook entries to ~/.claude/settings.json. Re-running replaces our entries in place (matched by the claude-code-race tag), so it never duplicates.
  3. Sends a start event so the avatar appears immediately on the dashboard.
  4. (Windows only) runs a smoke test by calling the hook directly and verifying the server saw the event. Each hook call is logged to ~/.claude-code-race/hook.log with OK/ERR lines β€” useful for debugging.

After installation, run /clear in Claude Code or restart it so the updated settings.json is loaded.


Scoring

Event Action
UserPromptSubmit prompt β†’ +10 pts, 😊 thinking on
PreToolUse tool β†’ +1 pt, 😊 thinking on
Stop stop β†’ 😊 off
SessionStart start β†’ presence only, no points

The thinking 😊 also auto-expires after 90 s if Stop never arrives.

Position scale (asymptotic, absolute)

Each player is positioned independently, never reaching the finish line:

position = (1 - exp(-score / 100)) * 0.82
Score (β‰ˆ prompts) Lane position
20 (~2 prompts) 15 %
50 (~5 prompts) 32 %
100 (~10 prompts) 52 %
200 (~20 prompts) 71 %
300 (~30 prompts) 78 %
500 (~50 prompts) 81 %
1000+ ~82 % (cap)

That way early prompts produce a visible jump, but reaching the front of the pack requires real work β€” and nobody "wins" by touching the finish line.

Auto-resets

  • Daily counters reset at UTC midnight.
  • Weekly counters reset on Monday (ISO week).
  • A player disappears from the dashboard after 30 minutes of inactivity (re-appears automatically on next event).
  • "Online" badge: active within the last 5 minutes.

Manual reset

curl -X POST http://<host>:3000/api/reset \
  -H 'Content-Type: application/json' \
  -d '{"scope":"daily"}'
# scope: daily | weekly | users | all

API

Method Path Description
GET / Dashboard (HTML/CSS/JS)
GET /install.sh Bash installer (server URL substituted at request time)
GET /install.ps1 PowerShell installer (server URL substituted)
GET /api/stats Current snapshot (JSON)
GET /api/debug Recent 50 events + raw users map (handy for diagnostics)
POST /api/event Record a hook event
POST /api/reset Reset a scope (daily / weekly / users / all)
WS / Push snapshots on every event + heartbeat every 5 s

POST /api/event

{ "user": "alice", "type": "prompt", "hostname": "mbp-alice" }

type ∈ { "prompt", "tool", "stop", "start", "ping" }. The username is sanitized to [A-Za-z0-9._-] and truncated to 32 characters.

Snapshot

{
  "type": "snapshot",
  "date": "2026-05-28",
  "week": "2026-W22",
  "users": [
    {
      "name": "alice",
      "car": "🏎️",
      "color": "#ff4757",
      "thinking": true,
      "lastSeen": 1779961638582,
      "daily":  { "prompts": 12, "tools": 47 },
      "weekly": { "prompts": 88, "tools": 350 }
    }
  ]
}

Uninstall hooks (developer)

rm -rf ~/.claude-code-race
python3 -c '
import json, pathlib
p = pathlib.Path("~/.claude/settings.json").expanduser()
d = json.loads(p.read_text())
for k, arr in list(d.get("hooks", {}).items()):
    arr = [e for e in arr if not any("claude-code-race" in (h.get("command","") or "")
                                     for h in (e.get("hooks") or []))]
    if arr: d["hooks"][k] = arr
    else: d["hooks"].pop(k, None)
p.write_text(json.dumps(d, indent=2)+"\n")
'

On Windows do the same in PowerShell β€” open ~/.claude/settings.json, remove any hook entries whose command contains claude-code-race, then delete the ~/.claude-code-race folder.


Repository layout

.
β”œβ”€β”€ package.json
β”œβ”€β”€ server.js                # Node server: HTTP + WebSocket + installer templating
β”œβ”€β”€ public/
β”‚   β”œβ”€β”€ index.html           # Dashboard
β”‚   β”œβ”€β”€ style.css            # Neon dark theme, responsive vw-based units
β”‚   β”œβ”€β”€ game.js              # Render, WebSocket, animations, leaderboards
β”‚   β”œβ”€β”€ install.sh           # Installer template β€” server substitutes URL
β”‚   └── install.ps1          # Windows installer template
β”œβ”€β”€ docs/
β”‚   └── preview.png
β”œβ”€β”€ LICENSE
└── README*.md

Configuration

Environment variables (server):

Var Default Description
PORT 3000 HTTP/WebSocket port

Environment variables (installer, client side):

Var Default Description
NAME $USER / $env:USERNAME Nickname displayed in the race
SERVER (host you fetched from) Override the server URL

Contributing

Issues and pull requests welcome. This is intentionally tiny β€” keep it that way. Things that would be nice but aren't here yet:

  • Sound effects on overtaking (kept off by default β€” TV in the office).
  • Per-project leaderboards (right now it's a single global pool).
  • Persistent history (just date-stamped JSON dumps).
  • Username normalization for non-ASCII characters (e.g. Polish Ε‚ β†’ l).

License

MIT β€” see LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors