Skip to content

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Live agent status on a Work Louder Creator Micro 2 — on Linux

Turn the macropad's keys into live status lights for your Claude Code sessions: blue while an agent works, amber when it wants a decision, green when it's done. Same idea as the Codex integration, except it runs on Linux, needs no ChatGPT app, and watches whatever sessions you choose.

Built by reverse-engineering the firmware's RPC service. The device is driven directly over raw HID — the vendor app doesn't even need to be running.

Why this exists

Work Louder's 0.6 firmware brought "agent supervision" to the Creator Micro, but it's wired to the ChatGPT desktop app — and it's for its agent, not yours. The vendor app greys the layer out on Linux and tells you the feature isn't available here.

It turns out the LEDs aren't gated on any of that. A key becomes a status slot purely because its keycode is KV_OAI_AG00AG05, and v.oai.thstatus drives those slots over plain JSON-RPC. Any layer can have them, any program can drive them. Everything else here follows from that.

The ChatGPT app has since shipped a Linux build, and it drives the very same six slots. That's fine — this daemon only ever writes the slots it owns, and fills them from the opposite end, so the two run side by side. See docs/PROTOCOL.md for why per-layer status is impossible.

What you get

  • Live status for up to 6 sessions (one slot per project directory), driven by Claude Code hooks — no polling of anything, no wrapper around the CLI
  • A background daemon that only talks to the device when something changes, survives unplugging, and reconnects on its own
  • The ring lit with whatever is most urgent, so a session waiting on you is visible from across the room without reading four keys (--no-ring to skip)
  • A safe config writer that reads every write back and compares it byte for byte, because this firmware truncates oversized writes without saying so
  • A protocol write-up (docs/PROTOCOL.md) covering the RPC methods, the config format, and the traps that cost me a frozen device

It is not limited to Claude Code. Anything that can write a small JSON file can drive the lights.

Requirements

  • A Creator Micro 2, firmware v0.6.1 or later (VID 303A, PID 8298)
  • Linux with hidraw, Python 3.9+, no extra packages
  • Read/write access to the device node — see udev

Only tested on the hardware named above, on Ubuntu 26.04 under GNOME/Wayland, over both USB and Bluetooth LE.

Install

git clone https://github.com/coeckmath-maker/creator-micro-agent-status
cd creator-micro-agent-status
./install.sh

That copies the scripts to ~/.local/share/wl-agent-status/, installs a --user systemd unit, and prints the hook configuration to add to ~/.claude/settings.json.

Then put KV_OAI_AG00AG03 on four keys of a layer (see Configuring the layer) and you're done.

udev permissions

Bluetooth HID sits on bus 0005, so match on KERNELS rather than the USB attributes:

# /etc/udev/rules.d/99-worklouder.rules
KERNEL=="hidraw*", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="8298", MODE="0660", TAG+="uaccess"
KERNEL=="hidraw*", KERNELS=="0005:303A:*", MODE="0660", TAG+="uaccess"
sudo udevadm control --reload-rules && sudo udevadm trigger

How it works

Claude Code hooks ──► record_state.py ──► state.json
                                              │
                                              ▼
                                    agent_status_daemon.py
                                              │  JSON-RPC over hidraw
                                              ▼
                                    Creator Micro 2 LEDs

Four hooks (SessionStart, UserPromptSubmit, Stop, Notification) write a one-line state per session. The daemon groups sessions by working directory, keeps the worst state per project, assigns each project a stable LED slot, and pushes a frame only when something actually changed.

Hooks reload without restarting your session, so you can wire this up mid-flight.

State Colour Trigger
working blue you sent a prompt
awaiting-approval amber a permission prompt is up
unread green the turn finished
idle white session started, nothing pending
error red set it yourself

The ring

The band around the case takes the most urgent state on the board, dimmed to 0.35 — a wide diffuse surface at full brightness dominates a desk instead of informing it. idle deliberately leaves it dark: a light that is always on tells you nothing, and this one sits on top of whatever ambient lighting you chose yourself.

That last part is the catch. The ring is a property of the device, not of a layer — the firmware has nowhere to store a per-layer version — so driving it overrides your own setting on every layer, and it is still doing so while you are on some other one. If you would rather keep your lighting, run the daemon with --no-ring:

systemctl --user edit --full wl-agent-status.service   # add --no-ring to ExecStart

The daemon hands the ring back when it stops. It does not hand the keys back: another program may own the slots it never touched.

Configuring the layer

The device's own flash is the source of truth — the vendor app reads it on connect and overwrites its local cache. Editing the host-side files does nothing on its own.

# read the current config off the device
python3 src/write_config.py --read keymap.json > keymap.json

# edit it, then write it back (verified by read-back)
python3 src/write_config.py keymap.json keymap.json

Put KV_OAI_AG00AG03 on the keys you want as status lights — ideally ones with transparent keycaps.

Keep keymap.json under about 5.5 KB. Past roughly 6.1 KB the firmware truncates writes silently, and a write past that point froze my device hard enough to need the internal reset button. write_config.py always reads back and will tell you if it doesn't match. Details in docs/PROTOCOL.md.

Two things you give up

Using KV_OAI_ keycodes makes the layer read-only in the vendor app, and disables its per-layer lighting. Both are structural, both are documented in the protocol notes. Configure that layer from scripts instead.

Debugging

python3 src/listen.py 60      # dump everything the device emits for 60s
python3 src/wl_device.py working awaiting-approval unread idle   # force colours
python3 src/wl_device.py --ring awaiting-approval                # force the ring
python3 -m unittest discover -s test                             # no hardware needed
journalctl --user -u wl-agent-status -f

listen.py is how the notification format above was found. If you're exploring this firmware yourself, start there.

Notes for other Linux users of the Input app

Things that cost me hours, in case they cost you fewer:

  • The app force-enables your screen reader. It sets IsEnabled and ScreenReaderEnabled on org.a11y.Status whenever it thinks it's on Wayland — which starts Orca, which reads every key you press aloud. Launch it with XDG_SESSION_TYPE=x11 and it stops. --ozone-platform=x11 alone is not enough; it doesn't change the variable the app checks.
  • CMD_STEP smart actions are refused until you tick the consent box in the app's settings.
  • TEXT_STEP smart actions need xdotool, which cannot type into native Wayland windows anyway. Use keyboard macros — they're real HID keystrokes and work everywhere.
  • Keycodes are physical positions, not letters. On AZERTY, KC_Q types a. See the table in the protocol notes.
  • After killing the app, delete ~/.config/input/Singleton{Lock,Cookie,Socket} or it silently refuses to start again.

Credits

Protocol work cross-checked against two MIT-licensed projects that emulate or drive this hardware:

Two more appeared in September 2026, from macOS: 00cyre/creator-micro-kit (a Node library for this RPC service) and 00cyre/claude-code-keypad (the same idea as this project, reading Claude Code's transcripts instead of its hooks). They go through a Swift IOKit bridge, so neither runs here — but they found the same central fact independently, and v.oai.rgbcfg is documented there first. What that method wants on the wire, though, is not what they say: see docs/PROTOCOL.md.

Not affiliated with Work Louder or OpenAI. Hardware is theirs; the mistakes here are mine.

License

MIT — see LICENSE.

About

Live Claude Code session status on a Work Louder Creator Micro 2 — from Linux, no vendor app, with the reverse-engineered protocol notes

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages