Configure the AngryMiao CyberBoard R4 (keymap + LED) over USB without AM Master
Project description
cyberboard-cli
A CLI — and the protocol knowledge base behind it — for writing AngryMiao CyberBoard R4 configuration without the official AM Master app.
The goal: manage your keymap and LED display as separate, version-controllable sources, and write them straight to the board from the command line — robustly, without AM Master's flaky connection.
Status: the write protocol is fully reverse-engineered, and the
cyberboardCLI is implemented and installable (see Install below). Writing and keymap read-back are verified on real R4 hardware; keymap authoring (TOML) and LED display authoring (GIF / declarative recipes) work today. Still WIP: per-key LED authoring, the MCP server, and the Claude plugin. The self-contained protocol spec lives under.claude/rules/(Japanese).
Install
Requires Python ≥ 3.11. Dependencies are split into a small core plus optional extras, so a keymap-only or device-only setup stays lean:
- core —
pyserial, for device I/O (devices/read/write/doctor). [led]—pillow, for LED authoring (led/anim).[verify]—jsonschema, for strict schema validation inverify(it falls back to basic checks without it).[all]— everything (pillow+jsonschema).
Not on PyPI yet, so install straight from git (the default branch is main):
# Run once, no install (ephemeral) — with LED authoring:
uvx --from 'cyberboard-cli[led] @ git+https://github.com/GeneralD/cyberboard-cli' cyberboard --help
# Install as a persistent tool (uv):
uv tool install 'git+https://github.com/GeneralD/cyberboard-cli' # core only
uv tool install 'cyberboard-cli[led] @ git+https://github.com/GeneralD/cyberboard-cli' # + LED
# Or pipx / pip into a venv:
pipx install 'cyberboard-cli[led] @ git+https://github.com/GeneralD/cyberboard-cli'
pip install 'cyberboard-cli[led] @ git+https://github.com/GeneralD/cyberboard-cli'
From a clone, run it without installing via uv:
uv run --extra led cyberboard --help # LED commands need --extra led; device commands don't
Usage
cyberboard <command> — run cyberboard <command> --help for each command's options.
| Command | What |
|---|---|
devices / device |
List connected boards / show one device's detail |
doctor |
Non-destructive connectivity health check |
build |
keymap.toml → IR config (--dump for the reverse) |
verify |
Validate an IR config against the schema |
led |
GIF ⇄ IR display codec + terminal player (gif2ir / ir2gif / play / recipe) |
anim |
Render declarative LED animations (render / preview / montage) |
compose |
Compose a led.toml manifest (multi-source slots) → IR |
read |
Read config back from the device (keymap) |
write |
Write an IR config to the device |
set-time |
Set the device RTC clock |
completion |
Print a shell completion script (bash / zsh / fish) |
cyberboard devices # find your board
cyberboard anim preview -r examples/led/text-scroll.json -o preview.gif # author an LED animation
cyberboard led play -i preview.gif # play it right in the terminal (Ctrl-C to stop)
cyberboard compose -m examples/led/compose.toml -b base.json -o config.json # combine many sources per slot
cyberboard build -k keymap.toml -b base.json -o config.json # build a config from a TOML keymap
cyberboard write config.json --execute # write it (omit --execute for a dry run)
Shell completion
cyberboard completion <shell> prints a completion script. Homebrew wires this
up automatically; for a pip/uv install, install it manually:
cyberboard completion zsh > "${fpath[1]}/_cyberboard" # zsh (then restart)
cyberboard completion bash > /usr/local/etc/bash_completion.d/cyberboard # bash
cyberboard completion fish > ~/.config/fish/completions/cyberboard.fish # fish
MCP server
The same operations are available to MCP clients (Claude, editors, agents) via a stdio server that wraps the CLI — so the MCP surface never drifts from the CLI.
pip install 'cyberboard-cli[mcp]' # or: uv tool install 'cyberboard-cli[mcp] @ git+https://github.com/GeneralD/cyberboard-cli'
cyberboard-mcp # serves over stdio
Point a client at the cyberboard-mcp command (stdio). Example client config:
{ "mcpServers": { "cyberboard": { "command": "cyberboard-mcp" } } }
Tools: list_devices · device_info · doctor · verify · build_keymap ·
render_animation · preview_animation · gif_to_ir · ir_to_gif ·
read_keymap · write_config. write_config is destructive and defaults to a
dry run (pass execute=true to actually write). LED tools need the [led]
extra in the same environment.
Claude Code plugin
If you use Claude Code, install the plugin to get the cyberboard MCP server
auto-configured — no hand-editing of mcpServers.
First, in your terminal, install the package so the cyberboard-mcp
command is on PATH:
pip install 'cyberboard-cli[mcp]' # or: uv tool install 'cyberboard-cli[mcp] @ git+https://github.com/GeneralD/cyberboard-cli'
Then, inside Claude Code (these are Claude Code slash commands, not shell), add this repo as a plugin marketplace and install the plugin:
/plugin marketplace add GeneralD/cyberboard-cli
/plugin install cyberboard@cyberboard-cli
The plugin's MCP server points at the cyberboard-mcp console script, so the
package install is the prerequisite: if you enable the plugin before installing
the package, the server fails to start (cyberboard-mcp is not on PATH).
Once the package is installed, enabling the plugin starts the server
automatically. The plugin manifest lives at plugins/cyberboard/, and the
marketplace manifest at .claude-plugin/marketplace.json (both in this repo).
Why
The official setup has three problems this project fixes:
- Keymap and LED live in one JSON file. Applying a community LED animation overwrites your keymap. This project keeps them apart and recombines them only at build time, so "swap just the LED" is safe.
- AM Master's connection is flaky — writes succeed or fail at random. The root causes are now identified (see the protocol doc) and are fixable on the CLI side.
- It's inflexible — no partial updates, no scripting, no diffing.
What we know (headline findings)
All reverse-engineered from AM Master 1.3.7 (decompiled locally; the decompiled sources are not redistributed here — see Legal):
- Transport is USB CDC serial (pyserial) @ 9600 baud. HID is detection-only.
- No encryption on the config path — the AES in the app is just PyInstaller bytecode obfuscation.
- Frames are a fixed 64 bytes:
[0]category,[1]subcommand,[2..62]payload,[63]CRC-8 (poly0x07). - LED model:
frames= the 40×5 = 200-px top display;keyframes= the 90 per-key backlights. Slots 1/2/3 = pages 5/6/7. (Empirically verified: active pages always pack to exactly 200 px / 90 px — see_re/verify_encoding.py.) - Send sequence and full command table are documented in
.claude/rules/30-write-protocol.md.
Repository layout
| Path | What |
|---|---|
.claude/rules/ |
The protocol & schema knowledge base (Japanese) — start at 00-overview.md |
.claude/rules/30-write-protocol.md |
The definitive write-protocol spec (transport, frames, CRC, command table, send order) |
_re/verify_encoding.py |
Standalone encoder that re-derives the byte packing and checks it against real config JSON (no device needed) |
_re/zscan.py |
Pure-Python zlib brute-scanner used in the first static-analysis pass |
Confidence is marked throughout: 🟢 source-confirmed · 🟡 strong inference · 🔴 needs live-hardware capture.
Roadmap
Done:
- M0 — Protocol analysis ✅ decompiled; encoding verified; wire bytes confirmed by live serial handshake on a real R4.
- M1 — Full write ✅ a known-good config writes over the reverse-engineered sequence (LED visually confirmed on hardware).
- M2 — Read-back + diff ✅ for the keymap (write → read → 1400/1400 match). LED has no read-back path, so it's authored from source.
- M3 — Keymap build ✅
keymap.toml→ IR with lossless round-trip. - M5 — LED display authoring ✅ GIF ⇄ IR codec + declarative animation recipes
- an in-terminal player (
led play, half-block truecolor) and a frame montage (anim montage) for judging motion/loop in a still viewer.
- an in-terminal player (
Productization (in progress): a unified cyberboard CLI core (done), standalone
packaging (this), then an MCP server and a Claude plugin that all call the same
core — plus per-key LED authoring and a sprite/vision LED design loop.
Note: partial writes are not supported by firmware (
JSON_STARTerases the whole config), so "swap just the LED" is done by read → merge → full write, not by a partial write.
Legal
This is an independent interoperability project. The reverse-engineering was done
on a locally-owned copy of AM Master for the sole purpose of interoperating with
hardware the author owns. The vendor's app, its installer, its extracted
bytecode, and the decompiled sources are deliberately excluded from this repo
(see .gitignore); only original analysis and first-party tooling are published.
All trademarks belong to their respective owners. "AngryMiao" and "CyberBoard"
are trademarks of AngryMiao.
Acknowledgements
angrymiao-cyberboard-config-mergerandmiaomerge— sibling tools that merge/composite LED animations into a writable JSON (writing still relies on AM Master; that's the gap this project closes).
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 cyberboard_cli-0.1.0.tar.gz.
File metadata
- Download URL: cyberboard_cli-0.1.0.tar.gz
- Upload date:
- Size: 395.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
24f49518cc2ce9f3599afabb230cbe0cb6583a1e915fcf706efbf776d48d4fc3
|
|
| MD5 |
598f865fbfe5da825e94e3cdba5ec3f0
|
|
| BLAKE2b-256 |
a4d76fef0647a90e3f0c31a9cf3dc6abde5a5647cd919369d7015f82dfe9aab0
|
Provenance
The following attestation bundles were made for cyberboard_cli-0.1.0.tar.gz:
Publisher:
publish.yml on GeneralD/cyberboard-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cyberboard_cli-0.1.0.tar.gz -
Subject digest:
24f49518cc2ce9f3599afabb230cbe0cb6583a1e915fcf706efbf776d48d4fc3 - Sigstore transparency entry: 1925636723
- Sigstore integration time:
-
Permalink:
GeneralD/cyberboard-cli@2ac4d301225dd3e10e2916b3aec08fc11fc3d187 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/GeneralD
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2ac4d301225dd3e10e2916b3aec08fc11fc3d187 -
Trigger Event:
push
-
Statement type:
File details
Details for the file cyberboard_cli-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cyberboard_cli-0.1.0-py3-none-any.whl
- Upload date:
- Size: 62.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b2810e8a3ca96106d31e0e9e80c8043122467fc6ac697e61baf79c57157eb50
|
|
| MD5 |
8e82c38d196c6c98ce9a75484a22ff0c
|
|
| BLAKE2b-256 |
12928df8d3d9ad5052666462577556a3b48f546e24c643f82dec377e7ac4f4be
|
Provenance
The following attestation bundles were made for cyberboard_cli-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on GeneralD/cyberboard-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cyberboard_cli-0.1.0-py3-none-any.whl -
Subject digest:
7b2810e8a3ca96106d31e0e9e80c8043122467fc6ac697e61baf79c57157eb50 - Sigstore transparency entry: 1925636891
- Sigstore integration time:
-
Permalink:
GeneralD/cyberboard-cli@2ac4d301225dd3e10e2916b3aec08fc11fc3d187 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/GeneralD
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2ac4d301225dd3e10e2916b3aec08fc11fc3d187 -
Trigger Event:
push
-
Statement type: