aicade
v0.7.0
Published
An arcade for agent downtime. Wraps your AI coding CLI and pops up chess puzzles while it works.
Maintainers
Readme
aicade
An arcade for agent downtime.
aicade wraps an AI coding CLI — Claude Code, Codex, aider, anything with a
terminal — and watches how long it has been working. When a run passes a few
seconds, a small window appears beside your terminal with a chess puzzle in it.
When the run finishes, the window pulses once, plays a soft chime, and gets out
of the way.
It never takes keyboard focus, and it leaves on its own when the run is over.
npm install -g aicade
aicade setup
aicade claudeaicade claude behaves exactly like claude. Colours, cursor addressing, raw
keystrokes, window resizing and the exit code all pass through untouched — if
you could tell the difference, it would be broken.
Install
npm install -g aicade # ~2 MB, no Electron yet
aicade setup # fetch the window runtime and the puzzle databasenpm install deliberately pulls only the CLI. The Electron runtime (~100 MB) is
downloaded on first use into ~/.aicade/runtime, straight from the official
GitHub release and verified against its published checksum. If you never open
the window, you never pay for it. aicade setup just does that eagerly, along
with the puzzles, so your first real run is instant.
Requires macOS or Linux and Node 20.11+.
Use
aicade claude
aicade codex --model o3
aicade -- npm run build # -- when the command name collides with a subcommand| Command | |
|---|---|
| aicade stats | wait time captured, puzzles solved, rating |
| aicade setup | fetch the runtime and puzzles |
| aicade puzzles sync | re-download the puzzle subset |
| aicade puzzles status | what is on disk, by rating band |
| aicade casino <url> | point the casino pane at a site |
| aicade config --set k=v | change one setting, e.g. timing.idle_threshold_ms=5000 |
| aicade tune | replay a recorded session against different thresholds |
| aicade join <handle> | take a handle on the shared board (opt-in) |
| aicade board | the shared board — --all for all time |
| aicade leave | forget your handle and stop syncing |
| aicade doctor | check everything is wired up |
| aicade quit | close the resident window process |
In the window
| | |
|---|---|
| Cmd/Ctrl+Enter | hide, and raise the terminal you launched from |
| Esc | hide, leave the run going |
| Cmd/Ctrl+Tab | next pane · Cmd/Ctrl+1…9 jump to one |
| type a move | Nf3 or g1f3, then Enter |
| Alt+H / Alt+N | hint (counts as a miss) / skip |
How it decides you are waiting
This is the part that has to be right, and the numbers below come from measuring real Claude Code sessions rather than from guessing.
While an agent is actually working it streams densely — median gap between writes is about 100ms, 95th percentile under half a second. Between tool calls it goes quiet for a second or three. When it is done and waiting for you, it goes quiet for as long as you leave it.
So:
ACTIVE— a byte arrived withinidle_threshold_ms.WORKING— the run has lastedworking_threshold_ms(4s). The window opens.IDLE— nothing foridle_threshold_ms(4s). The run is over: pulse, chime, and the window hides itself.
Two things that are easy to get wrong, and were wrong in 0.1.0:
A run's age is wall-clock from its first byte, not the length of the current unbroken stretch of output. Restarting the clock every time the agent paused to think meant the threshold was reached tens of seconds late, or never. You are waiting during the pause too, so the pause counts.
Output that arrives while you are typing is the TUI redrawing around your
keystrokes, not the agent working. Left alone it starts phantom runs and pops
the window open mid-sentence, so the run clock is held until your typing settles
(typing_suppress_ms). When that happens you get a run-held line in the log
explaining why nothing appeared.
Everything is logged to ~/.aicade/state.log:
2026-08-31T16:17:15.169Z run[48743] state from=IDLE to=ACTIVE runId=-
2026-08-31T16:17:16.774Z run[48743] state from=ACTIVE to=WORKING runId=48743-1
2026-08-31T16:17:16.774Z run[48743] show runId=48743-1 reason=working
2026-08-31T16:17:21.530Z run[48743] state from=WORKING to=IDLE runId=48743-1
2026-08-31T16:17:21.530Z run[48743] finished runId=48743-1 ms=4827Tuning it against your own sessions
Don't guess — record and replay:
AICADE_TRACE=1 aicade claude # records byte timings to ~/.aicade/trace.log
aicade tune # where the window would open and close
aicade tune --working 3000 --idle 5000
aicade config --set timing.idle_threshold_ms=5000aicade tune replays the recording through the real state machine, so you can
see the effect of a threshold before committing to it. The trace records only
timestamps and byte counts — never what was typed or printed.
AICADE_NO_UI=1 aicade claude runs the wrapper and the state machine with no
window at all.
Panes
Chess — the default, and the reason the tool exists. Puzzles come from the Lichess puzzle database (CC0). A rating-stratified subset of about 17,000 puzzles is downloaded once (~3 MB on disk) and served locally, so puzzles appear instantly and work with no network at all.
Your rating is Glicko-2, stored locally, and puzzles are served within ±150 points of it. A single puzzle moves you at most 100 points while the rating is still provisional and 40 after that — unclamped Glicko-2 will happily move a fresh rating 200 points on one puzzle, which is correct and reads as broken. Drag or click to move, or type the move. A wrong move shakes and lets you retry, but the puzzle counts as missed — as does a hint. If the window hides mid-puzzle you get that exact puzzle back, not a new one.
Profile — who you are, what you play, where you rank, and what you have earned. Your rank badge and points up top, then your games, then achievements. Open a game for its own analytics: rating over time, accuracy broken down by puzzle difficulty, activity by day, fastest and typical solve times, and which motifs you actually see — forks, pins, back-rank mates.
Achievements — 28 of them across rank, skill, streaks, volume, time and novelty, in four tiers. They key off rank rather than score wherever they can: "top 10 this week" means something to everyone, "10,000 points" only means something to whoever knows what a point is worth. A few are novelty — being one of the first players, solving at 3am, coming back from three misses in a row, solving one cleanly in under five seconds. Momentary ones are recorded when they happen rather than recomputed, because solving at 3am is not a state you can work out again tomorrow.
Casino — a browser view pointed at whatever you put in casino.url:
aicade casino https://example.com # sets it and restarts the window
aicade casino # show the current value and what it does
aicade casino --clearThen log in by hand, once. The pane uses a persistent session partition, so the login survives restarts, and a normal desktop user agent, because some sites misbehave under Electron's default. There are no injected scripts, no request interception and no automation: it is a window onto a site and nothing more. Links that open a new window go to your system browser instead.
Left unset, the pane shows an empty state rather than a broken page.
Wallet buttons will not work. Electron has no browser extensions, so Phantom and Solflare cannot inject and a "Connect wallet" button does nothing. Sign in with email instead; the persistent partition keeps you signed in.
Affiliate disclosure. aicade ships with the author's affiliate code and
attaches it to the casino URL, so wagering on a site they are an affiliate of
earns them a commission. It costs you nothing and changes nothing about the
site. aicade casino prints this too. To clear or replace it:
aicade casino --ref "" # no referral at all
aicade casino --ref <yours> # or use your own
aicade casino --ref <yours> --param ref # if the site expects ?ref= insteadAdding a third is one file in src/ui/panes/:
// src/ui/panes/reader.js
import path from 'node:path';
export default {
id: 'reader',
title: 'Reader',
order: 30,
create({ config, dist }) {
return { url: config.reader?.url || 'https://example.com', external: true, preload: false };
},
};Every .js in that directory is discovered at startup. Return { file } for a
local page or { url, external: true } for a remote one; add partition for a
persistent session and preload: false to keep aicade's API away from a
third-party page.
Points, streaks and the board
Two numbers, deliberately.
Rating is Glicko-2 and answers "how strong are you". Nothing inflates it — not streaks, not volume — because the moment it rewards anything but solving, it stops being an estimate of your strength.
Points are the other thing: a record of effort, and what the board ranks on.
points = 10 × (puzzle rating / 1500)^1.5 × streak multiplier- Skill pays. Difficulty is absolute, not relative to you, so a 2500 puzzle is worth about four times a 1000 puzzle for everybody. Since puzzles are served near your rating, getting stronger earns more on its own.
- Streaks pay. +8% per consecutive solve, capped at double at thirteen in a row. A miss resets it, and so does skipping — a skip is not a solve.
- Volume wins the long game. Because the multiplier is bounded, points stay linear in puzzles solved. No streak, however hot, out-earns simply doing more over a month. The cap is the only dial that decides how fast a hot run climbs in the short term.
The board is ranked over two windows, this week and all time, because an all-time-only board is just "who installed aicade first" and nobody new can ever win it.
The shared board is opt-in
aicade touches the network exactly twice: once at setup, to download puzzles, and then never again — unless you join.
aicade join <handle> # takes a handle, turns on syncing
aicade board # this week
aicade board --all
aicade leave # forget the handle, stop syncingWhat gets sent: your handle, and for each puzzle its Lichess id, whether you solved it, and when.
What never gets sent: what you ran, how long you waited, your working
directory, which terminal you use, anything from the casino pane, and anything
from state.log or trace.log. There is a test that fails if any of it starts
leaking.
The server does not trust your numbers. A client says only which puzzles it attempted; the server looks each one up on Lichess, then recomputes the rating and points itself. Each puzzle counts once per player. Your local rating and the board's may drift apart slightly — the board's is the one that counts on the board.
There is no account recovery. No email, no password. The token in
~/.aicade/identity.json is the only proof the handle is yours, so back it up if
you care. Nothing to leak, nothing to reset.
Run your own board instead if you'd rather — the Worker is in worker/.
Config
~/.aicade/config.toml, written with commented defaults on first run.
[timing]
working_threshold_ms = 4000 # how long a run must last before the window opens
idle_threshold_ms = 4000 # silence this long means the run is over
typing_suppress_ms = 2500 # hold the window while you are still typing
[window]
position = "bottom-right" # or bottom-left / top-right / top-left / center
default_pane = "chess"
width = 560
height = 680
hide_on_finish = true # leave on its own once the run is done
hide_delay_ms = 1400 # long enough to see the pulse
[chess]
rating_band = 150
auto_advance_ms = 1200
[casino]
url = ""
[sound]
enabled = true
[leaderboard]
enabled = false # nothing leaves this machine while false
url = "https://aicade-leaderboard.noah-haufer.workers.dev"Position and size are only used the first time; after that the window remembers where you left it.
Files
~/.aicade/
config.toml yours to edit
state.log every state transition, for tuning
trace.log byte timings, only when AICADE_TRACE=1
identity.json your board token, if you joined — chmod 600, back it up
store.json rating, stats, the puzzle you were part-way through
puzzles/ one NDJSON file per 100-point rating band
runtime/ Electron, fetched on demand — shared, so it ignores AICADE_HOME
ui.log the window process
sock the wrapper talks to the window over thisNotes
Focus. The window is shown with showInactive() and, on macOS, the process
is a dock-less accessory app, so it cannot take focus even if it wanted to. There
is no modal, no system alert, nothing that demands dismissal. It floats above
other windows but is not on every space.
Transparency. The wrapper is a pure byte pipe. One subtlety worth recording:
a pty applies its own newline translation, and so does your terminal, so a naive
wrapper adds carriage returns the bare command never emitted — invisible for
ordinary output, but visible corruption for any full-screen TUI, which is every
agent CLI. aicade turns off output post-processing on its own terminal for the
duration and restores it on exit, which makes the byte stream identical. There
are tests for exactly this.
Multiple terminals. Run aicade in several at once; they share one window
process, and any of them can open it.
AICADE_HOME relocates config, stats, puzzles and the socket, which is
useful for a second profile or for testing. It deliberately does not relocate
the Electron runtime — that is a downloaded binary cache, and one copy is
enough.
Troubleshooting
aicade doctor first — it checks the runtime, the puzzles, node-pty, and
whether the parent terminal was identified.
The window never appears, or appears late. Check ~/.aicade/state.log. A
run-held reason=typing line means the output was keystroke echo and the clock
was held. Otherwise record a session with AICADE_TRACE=1 and run aicade tune
to see exactly where the thresholds fire.
The window does not go away. aicade config --set window.hide_on_finish=true,
then aicade quit so the window picks it up. If it hides too fast to see the
pulse, raise window.hide_delay_ms.
Cmd+Enter does not raise the terminal. On macOS this needs the parent
terminal to be identified as an .app bundle; aicade doctor shows what it
found. On Linux it needs wmctrl or xdotool, and under Wayland one app cannot
raise another at all.
posix_spawnp failed. node-pty ships a helper binary that needs the
executable bit, and npm 11 skips install scripts by default. aicade repairs this
itself at startup; if you still hit it, aicade doctor will report it.
A rendering bug you want to report. AICADE_SHOT=/tmp/shots aicade ui
writes a PNG of the chrome and of each pane — easier than screenshotting a
window that never takes focus.
Licence
GPL-3.0-or-later. aicade bundles chessground,
which is GPL-3.0-or-later, so the combined work is too. See NOTICE for the full
attribution list, including the CC0 puzzle data and the CC-BY-SA piece artwork.
