htop, but it's a Knicks game. Your CPU called from the Garden floor — user time versus the system, top processes as the starting five.
knicks-scoreboard is a live terminal system monitor skinned as a Madison Square Garden scoreboard: the headline score splits CPU into KNICKS (user time) versus the OPPONENT (system + iowait), the box score is your busiest processes as players, and the game clock is the host's uptime.
Tip
No kernel modules, no BPF, and no network. knicks-scoreboard reads /proc straight through yeet's graph API, so the same one-liner runs on any Linux box. The numbers are pure system telemetry — there is no live NBA data here, and nothing to fetch.
curl -fsSL https://yeet.cx | sh
yeet run github:yeet-src/knicks-scoreboardManual install guide · Linux only
Sort the box score by rebounds (memory) instead of points, or grab a single pipe-safe snapshot:
yeet run github:yeet-src/knicks-scoreboard -- --sort reb
yeet run github:yeet-src/knicks-scoreboard -- --once | catLive scoreboard (main.js)
--sort <pts|reb|ast>(defaultpts) —pts: CPU% first;reb: resident memory first;ast: thread count first.--interval <ms>(default1000, floored at100) — live refresh period.--secs <n>— exit after n seconds (default: run until Ctrl-C).--once— render a single snapshot and exit (automatic when output is piped).
JSON stream (dump.js)
yeet run github:yeet-src/knicks-scoreboard/dump.js--interval <ms>— stream one box-score object every interval (default: emit one snapshot, then exit).
Every number on the scoreboard is real system telemetry wearing a basketball stat line.
The score is CPU, split by who's spending it. The kernel charges every tick of CPU time to either user mode (your programs doing work) or system mode (the kernel working on their behalf), plus iowait (a core parked waiting on disk). KNICKS is the user share; the OPPONENT is system + iowait. Both are summed across every core as percent-of-one-core, so a single pegged core scores 100 and a fully-loaded 16-core box can run the score up past a thousand. The leading team gets the ◄ flag.
CPU is a rate, not a reading. A process's /proc entry counts cumulative ticks since it started, so a single sample tells you nothing about right now. knicks-scoreboard takes two snapshots and scores the difference — the work done between refreshes. That's why the score settles in over the first second and why --once pauses briefly to take its two samples.
The players are processes. The starting five are the five busiest processes this refresh, the bench is the next five. Each player's stat line: # is the PID (jersey number), PTS is its CPU%, REB is resident memory in MB, AST is its thread count, and MIN is how long the process has been alive. Because the team line is summed from the same per-process deltas, KNICKS + OPPONENT always reconciles with the roster, within rounding.
The game clock is uptime. The host's uptime is folded into a 48-minute game — four 12-minute quarters that count down 12:00 → 00:00 and roll Q1 → Q4 → Q1. It is monotonic and never runs negative.
Mostly the moments when you'd reach for top, except you wanted it to be fun.
- Box feels busy. Who's actually eating the CPU right now — and is it user work or kernel/IO?
- Something is thrashing the disk. Is the OPPONENT (system + iowait) running up the score?
- Quick glance at the busiest five processes with memory and thread counts, without parsing
top. - A live, glanceable monitor for a spare terminal or a wall display that isn't another grafana tab.
Banner: a plain-text NEW YORK KNICKS / MADISON SQUARE GARDEN marquee in Knicks orange and blue. Text only — no logos.
Scoreboard: the two team totals drawn big in a seven-segment digit font, KNICKS on the left in orange, the OPPONENT on the right in silver, with the leader flagged. Below them sits the quarter and game clock. On a narrow terminal the big digits collapse to a single KNICKS N vs N OPPONENT line.
STARTING FIVE: the five busiest processes, brightest rows. Columns, left to right:
#: the PID, the player's jersey number.PLAYER: the process name. Long names truncate with an ellipsis.PTS: CPU% this refresh (user + system time for that process).REB: resident set size in MB (in GB once it crosses ~10 GB).AST: thread count.MIN: process age, asMM:SSfor young processes andHh/Ddonce it's been up for hours or days.
When the terminal is too narrow to hold every column, the lower-priority ones drop first (AST, then REB, then MIN); #, PLAYER, and PTS always stay, and PLAYER absorbs the slack.
BENCH: the next five processes, dimmed.
CROWD ticker: the 1/5/15-minute load average styled as a crowd-tempo line, with a bar that fills as load approaches your core count.
JS side (the whole thing). knicks-scoreboard is pure JavaScript with no compiled components.
main.js(entrypoint): terminal management, the render loop, and screen layout. Detects whether stdout is a PTY; if not, it falls back to a single snapshot automatically. Draws the seven-segment score, drops table columns when the terminal is too narrow, and clips colored lines to the visible width without breaking ANSI escape sequences. Flags:--sort,--interval,--secs,--once.data.js: oneyeet.graph.query()call per sample, coveringprocs.stat(user/system ticks, RSS, threads, start time),kernel_stats(iowait),cpu,load_average, andhost.uptime. Diffs two samples into the box score — per-process CPU%, the team totals, and the game clock — matching a process across samples by PID and start time so a recycled PID can't inherit a predecessor's counters. Shared by bothmain.jsanddump.js.dump.js: alternate entrypoint. Emits the same box score as newline-delimited JSON, one object per refresh: the score line, the quarter and clock, and the full roster (starters+bench). Pipe tojqto filter or stream into other tools. Accepts--intervalto stream continuously.demo.sh: spins up CPU busy loops to put points on the KNICKS side and a sync/IO loop to score for the OPPONENT, then launches the scoreboard. Override the home burn withLOAD=N.
Data flow. Each refresh: data.js queries yeet's graph layer, which reads /proc and /sys. It holds the previous sample, diffs the two, and returns a plain JS box-score object (score, clock, roster, load). main.js sorts and renders it to the terminal in one synced write per frame, using the alternate screen buffer and cursor positioning to avoid flicker.
Important
yeet itself is the only hard requirement. Everything here comes from /proc and /sys, which are present on all mainstream Linux distributions by default; no special kernel config, no elevated privileges beyond what yeet already uses to read the process table.
- The yeet daemon, which handles process sandboxing and the graph API.
curl -fsSL https://yeet.cx | shinstalls it.
Note
What knicks-scoreboard doesn't do, and where the metaphor bends.
- There is no live NBA data, and there can't be. The yeet runtime has no network or HTTP access. Every number is local system telemetry; the basketball framing is presentation only. This is not affiliated with, endorsed by, or connected to the NBA or the New York Knicks.
- The score needs two samples to mean anything. The very first frame, and an idle box, honestly read
0–0; the score settles in over the first refresh. - The roster shows the top ten processes by the active sort. Short-lived processes that start and exit between two samples never appear, and kernel worker threads with no CPU this tick sink to the bench at
0PTS. - Team totals are summed from the per-process deltas plus unattributable iowait, so KNICKS + OPPONENT reconciles with the full process table, not just the ten players on screen.
- The game clock is derived from uptime, not wall-clock or any real game; it's a monotonic novelty, not a timer you should trust for anything.
- Process names are the kernel's
commfield (/proc/<pid>/stat), truncated by the kernel to 15 characters before knicks-scoreboard ever sees them. ThePLAYERcolumn shows that raw string.
1. Why is the score 0–0 when I start it?
CPU usage is a rate. knicks-scoreboard needs two snapshots a moment apart to score the work done between them, so the first frame reads 0–0 and fills in on the next refresh. On a genuinely idle box, 0–0 is the correct final score.
2. Why can the score go over 100? Over 1000? The score is percent-of-one-core summed across every core. One fully-pegged core scores 100, so a busy 16-core machine can run KNICKS or the OPPONENT well past a thousand. It's a basketball score on purpose.
3. Will this affect my system's performance?
One /proc round-trip per second at default settings; the read load is negligible. demo.sh does spin up CPU and IO workers intentionally to put points on the board for the demo — that's opt-in and stops when you Ctrl-C.
4. Is this safe to run on shared or production infrastructure?
It reads the process table only; it writes nothing, attaches to no other process, traces no network traffic, and modifies no kernel state. Running it is equivalent to running top in a loop.
5. How is this different from top or htop?
Under the skin it's the same data — busiest processes, CPU split by user/system, load average, uptime. knicks-scoreboard reframes the user/system split as a live score, ranks processes as a box score with PTS/REB/AST/MIN, and ships a pipe-friendly JSON feed via dump.js, all in a single yeet run with no installation. It's top you can leave on a wall display.
knicks-scoreboard is pure JavaScript and currently ships without a dedicated license file in this repository.
Built with yeet, a JS runtime for writing eBPF programs on Linux machines. Join us on discord.
