claude-color
Live docs & demo → edocksil.github.io/claude-color
Drive a wired VIA-compatible keyboard's RGB from Claude Code status — see at a glance whether Claude is working, waiting for you, idle, or hit an error, without looking at the terminal.
- working → blue-purple
- waiting for you (permission / input) → amber, breathing
- idle (turn done, session alive) → deep green
- error (a turn ended on an API/session failure) → red, breathing
- Claude Code exits / no session running → your normal lighting is restored
Why claude-color
Built to be correct under real conditions, not just a demo:
- Multi-session aware — one keyboard, many Claude Code sessions, resolved by
priority (
error>waiting>working>idle). - Survives crashes & sleep — sessions are tracked by their Claude process; dead ones are pruned, and a reconcile on logon, unlock and every five minutes restores your lighting when no session is running.
- Accurate status — distinguishes a real permission prompt (amber) from a
finished-but-idle session (green) via Claude Code's
notification_type. - Restores your lighting — captures your real keyboard color and puts it back, with a guard against capturing its own status colors.
- Windows-first, keyboard-generic (any wired VIA RGB keyboard), 127 tests.
Requirements
- Windows 11, Python 3.11+. Install from PyPI
with
pip(or uv). - Any wired keyboard exposing a VIA raw-HID interface with an RGB matrix, connected by USB cable. VIA support is most commonly provided by QMK firmware with VIA enabled, but only the VIA protocol is required — not QMK itself. Developed and validated on the Keychron Q6 Max. Wireless is not supported (wired only). See docs/COMPATIBILITY.md.
- Optional but recommended: the
qmk_hidbinary on your PATH (primary backend). Without it, the built-in Pythonhidapibackend is used.
Install
From PyPI:
pip install claude-color # or: uv tool install claude-color
Then set it up (run from any terminal):
claude-color doctor # confirm the keyboard is detected
claude-color test # cycle working/waiting/idle/error, then restore
claude-color install # wire the Claude Code hooks into %USERPROFILE%\.claude\settings.json
Make sure your Python (or uv) scripts bin dir is on PATH so Claude Code — and the
scheduled reconcile task — can invoke claude-color.
Install from source (contributors)
git clone https://github.com/Edocksil/claude-color && cd claude-color
uv tool install --editable . # exposes `claude-color` on PATH, tracking your clone
claude-color install
Demo
claude-color test cycles working → waiting → idle → error (showing breathing
where configured), then restores your lighting. To eyeball a single state, use
claude-color set <state> --timeout <seconds> (applies it briefly, then restores).
claude-color test # ~2s per color (quick check)
claude-color test --dwell 30 # hold each color 30s (slow demo)
claude-color set error --timeout 5 # briefly show error, then restore
Configure
Everything is tunable in %LOCALAPPDATA%\claude-color\config.toml (created on
first run). Every option and its default:
| Option | Default | What it does |
|---|---|---|
backend |
"auto" |
How to talk to the keyboard: auto, qmk_hid, or hidapi. |
qmk_hid_path |
"qmk_hid" |
Name on PATH (or a full path) of the qmk_hid binary. |
vid / pid |
"" |
Hex device filter — only needed if several HID devices match. Find yours with claude-color doctor or qmk_hid -l. |
solid_effect_index |
1 |
Your keymap's VIA "solid color" effect index. If a solid status shows an animation, find the right index with claude-color probe. |
breathing_effect_index |
2 |
Your keymap's VIA breathing effect index. If a breathing status shows a rainbow, find the right index with claude-color probe. |
inherit_brightness |
true |
true: status colors use the keyboard's own brightness; false: each state's V in [colors] (custom brightness per status). Inheriting needs a backend that can read current lighting — hidapi can; the qmk_hid binary can't, so it falls back to V. |
staleness_minutes |
120 |
A stuck session with no known process is forgotten after this many minutes. Sessions with a live Claude process are kept regardless of age, so a long turn is never pruned out from under you. |
waiting_max_minutes |
10 |
Amber gives up after this many minutes with no new event, and the session drops back to idle. Interrupting a prompt fires no hook, so without this the amber can outlive the question by hours. Raise it if you often leave a real prompt unanswered for longer; 0 disables it. |
[colors].working |
[180, 255, 200] |
working color as H, S, V (0–255). |
[colors].waiting |
[30, 255, 220] |
waiting color (amber). |
[colors].idle |
[90, 255, 120] |
idle color (green). |
[colors].error |
[0, 255, 220] |
error color (red). |
[breathing].waiting |
128 |
waiting breathes at this speed (0–255). |
[breathing].error |
160 |
error breathes at this speed (0–255). |
[breathing] is per-state opt-in: a state listed there breathes at the given
speed; remove the line to keep it solid, and an empty [breathing] means every
state is solid. Only waiting and error breathe by default.
Not sure which effect indices your keymap uses? Run claude-color probe — it
cycles the RGB-matrix effects so you can spot which is solid and which breathes,
then set solid_effect_index / breathing_effect_index. Known values per board
live in docs/COMPATIBILITY.md.
How it works
flowchart LR
CC["Claude Code hooks"] -->|"SessionStart · UserPromptSubmit · Notification · Stop · StopFailure · SessionEnd"| K["claude-color hook"]
K --> S["state.json<br/>(per session, by PID)"]
S -->|"winner: error > waiting > working > idle"| KB["keyboard RGB<br/>(VIA raw-HID)"]
R["claude-color reconcile<br/>(logon / unlock / every 5 min)"] --> S
Claude Code fires hooks (SessionStart, UserPromptSubmit, Notification,
Stop, StopFailure, SessionEnd). Each runs claude-color hook, which updates
a per-session state file (%LOCALAPPDATA%\claude-color\state.json) and pushes
the highest-priority color (error > waiting > working > idle) across all
live sessions — StopFailure fires when a turn ends on an API/session error,
setting that session to error. Sessions are tracked by their Claude process id;
dead ones are pruned, and a scheduled claude-color reconcile (logon, unlock, and
every five minutes) restores your lighting when no Claude is running. When the last
session ends, the lighting captured at first SessionStart is restored.
The five-minute tick is also what bounds a stale status. Hooks are fire-and-exit,
so a session that goes silent — you interrupt a turn, or dismiss a prompt, neither
of which fires any Claude Code hook — has nothing to re-evaluate it. The tick gives
waiting_max_minutes something to run on, so a wrong amber clears within about
fifteen minutes instead of lasting until the next real event. The task is
Windows-only and needs one elevated claude-color install to register; without it
the hooks still work, but nothing ticks.
Troubleshooting
- No color changes: run
claude-color doctor. Ifavailable: False, check the USB cable (wireless is not supported) and trybackend = "hidapi"or setvid/pid. - Animation instead of solid color: fix
solid_effect_index. - Colors never restore: baseline capture may be unsupported on your firmware
via the active backend; try
backend = "hidapi". Logs are in%LOCALAPPDATA%\claude-color\claude-color.log. - Uninstall:
claude-color uninstall.
License
MIT — © 2026 Edocksil.
Metadata
Release files for claude-color 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| claude_color-0.2.1.tar.gz | 43.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| claude_color-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 71.5 kB
Release files / claude_color-0.2.1.tar.gz
| Download URL | claude_color-0.2.1.tar.gz |
|---|---|
| Size | 43.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
20fc0e1b4742be3db91b25ed455e2c9fa309d557af9d9d6b3dc03c93690dff91
|
|
BLAKE2b-256 checksum How to use checksums |
f781fb32a5a82d5609aed4e14754b4dec262ac6a46768553664ea590d7e240c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 15, 2026.
Transparency logRelease files / claude_color-0.2.1-py3-none-any.whl
| Download URL | claude_color-0.2.1-py3-none-any.whl |
|---|---|
| Size | 28.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b47fb339c6469c689fd3dcdf84e66d14b1ab25aae9fd5053358abad39a091838
|
|
BLAKE2b-256 checksum How to use checksums |
6daaf87f314a2526d48ccc468dc9e5bc4546e73a121efda4d97cec4a8577572b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 15, 2026.
Transparency log