Drive any wired VIA-compatible RGB keyboard from Claude Code status
Project description
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 boot/logon/unlock reconcile 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 is forgotten after this many minutes. |
[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)"] --> 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)
restores your lighting when no Claude is running. When the last session ends,
the lighting captured at first SessionStart is restored.
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file claude_color-0.2.0.tar.gz.
File metadata
- Download URL: claude_color-0.2.0.tar.gz
- Upload date:
- Size: 35.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
abcccb96b75d84e3d4b8697fef3aa837e00025c2484447b74101c9de935da3c4
|
|
| MD5 |
59cecf604e981a9c9d84d2cff3a57a43
|
|
| BLAKE2b-256 |
07d51190a5cc57aeca334f214ce743b2ff32786c95ec5beb33166085461a0fbc
|
Provenance
The following attestation bundles were made for claude_color-0.2.0.tar.gz:
Publisher:
release.yml on Edocksil/claude-color
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_color-0.2.0.tar.gz -
Subject digest:
abcccb96b75d84e3d4b8697fef3aa837e00025c2484447b74101c9de935da3c4 - Sigstore transparency entry: 2264579239
- Sigstore integration time:
-
Permalink:
Edocksil/claude-color@09e9a6b4403d5a7cd5d06cb6a0a4b3c306a4827d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Edocksil
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@09e9a6b4403d5a7cd5d06cb6a0a4b3c306a4827d -
Trigger Event:
push
-
Statement type:
File details
Details for the file claude_color-0.2.0-py3-none-any.whl.
File metadata
- Download URL: claude_color-0.2.0-py3-none-any.whl
- Upload date:
- Size: 24.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
82844d1776a95694e9b0094cf2f4e33ee8797d3be8d3c3838b8fc64c49a42490
|
|
| MD5 |
5abe1349ebc75323b8b059b528f9d898
|
|
| BLAKE2b-256 |
2741f4d53b6b9e18cd0fe89b116fa762a1c1842a820cd107cca3d781e21fea4a
|
Provenance
The following attestation bundles were made for claude_color-0.2.0-py3-none-any.whl:
Publisher:
release.yml on Edocksil/claude-color
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_color-0.2.0-py3-none-any.whl -
Subject digest:
82844d1776a95694e9b0094cf2f4e33ee8797d3be8d3c3838b8fc64c49a42490 - Sigstore transparency entry: 2264579366
- Sigstore integration time:
-
Permalink:
Edocksil/claude-color@09e9a6b4403d5a7cd5d06cb6a0a4b3c306a4827d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Edocksil
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@09e9a6b4403d5a7cd5d06cb6a0a4b3c306a4827d -
Trigger Event:
push
-
Statement type: