# Agent Guidelines — QMK RGB Tool ## Read README.md First `README.md` is the single source of truth for this project's behaviour: the command surface, the effect catalog, zone semantics, the protocol, the keyboard list and platform setup. **Read it in full before you work on this repository.** This file deliberately does not repeat any of it. A second copy is a second thing that can go stale, and that has happened: a command prefix that had not existed for many commits survived in eleven lines of prose, and a `keyboards.json` field was documented here as load-bearing while the code never read it. So the split is: - `README.md` — what the software does. Reference. - `AGENTS.md` — how to work on it. Discipline. - `.claude/skills/qmk-rgb/SKILL.md` — the live-query workflow for the tool. For the effect catalog specifically, read it from no document at all. Run `qmk-rgb-tool effect --list` and use the output. Logo and Side accept fewer effects than Backlight, and only the tool knows what the current firmware supports. ## Objective Cross-platform Go CLI library for programmatic/agent-friendly control of QMK keyboard RGB lighting. ## Architecture ``` cmd/ # Cobra-based CLI entrypoints internal/device/ # QMK Raw HID discovery and keyboard numbering internal/hid/ # Cross-platform HID access internal/via/ # VIA protocol implementation + LED subsystem internal/rgb/ # Effects, values, color definitions keyboards.json # Optional name/metadata lookup for known keyboards ``` ## Device Selection Discovery matches every connected keyboard exposing the QMK Raw HID signature (Usage Page `0xFF60`, Usage `0x61`) — the same check `qmk/qmk_udev` performs. `keyboards.json` is only consulted to attach a name to a recognized model; an absent or malformed file costs names, not the ability to drive the keyboard. `keyboard info` numbers the connected keyboards from 1 in a stable order (vendor ID, product ID, path). `--device` accepts that number, never a HID path. Without `--device`, commands proceed only when exactly one keyboard is connected; otherwise they fail and list the selectable numbers, so a command never targets an unintended keyboard. Device numbers are stable for the current session only — HID paths are reassigned on reboot and most keyboards report no serial number. `--zone` accepts `logo`, `backlight`, or `side` and may be given on any subcommand. It is a persistent flag in cobra's sense only — accepted on the root and inherited by subcommands — and nothing is remembered between runs, so it must be repeated on every invocation. Without `--zone`, commands target Logo, Backlight, and Side in that order. With `--zone`, commands target exactly one zone. Unsupported default targets are skipped with a stderr warning; unsupported explicit-zone effects and unknown names fail before the device is opened. ## Stack - Go 1.26+ (cross-platform: Linux, macOS, Windows) - `github.com/sstallion/go-hid` for HID access - `github.com/spf13/cobra` for CLI framework ## Constraints - CLI-first, no GUI — designed for automation and agent consumption - Output should be machine-parseable (JSON when possible) - Cross-platform from day one ## Consistency Documentation describes real, verifiable behavior. Names are only the start — behavior counts too. Before any edit: 1. **Binary name** — the `go build -o` target (usually `$GOBIN/$module_name`) must match every example in README, AGENTS.md, comments, and docs. 2. **CLI command** — the root command name (e.g. `qmk-rgb-tool`) is the binary name; every usage example, CLI reference table, and doc must use the same string. 3. **Module path** — the `go.mod` module path is the source of truth for `go install` targets. 4. **Zone names** — `logo`, `backlight`, `side` must be identical in code, docs, and examples. Documented behavior must also match the code: 5. **Flags and accepted values** — what a flag accepts and rejects, its type, its default, and whether it persists across commands. A doc promising `--device 1` must not accept a path. 6. **Error messages and exit codes** — the exact strings a user is told to look for, which error takes precedence when several apply, and when JSON is still printed before a non-zero exit. 7. **JSON output shape** — field names, types, and whether a field is always present, omitted, or `null`. Agents and scripts parse this. 8. **Ranges, defaults, and stability** — accepted ranges, default values, boundary behavior, and any ordering or stability guarantee together with the scope it holds over (a device number is stable for one session, not across reboots). ## Firmware Transforms Values The Impact 80 firmware rescales or clamps brightness and speed per channel, so an accepted 0–255 request is not the value the keyboard holds. `logo` and `side` cap brightness at 160 and have only three reachable speeds — 0 freezes the animation, 1 is the slowest movement, anything above 1 becomes 4 — while `backlight` scales brightness up to 255 and applies speed as given. README.md tabulates the per-channel behaviour; the rule that follows from it is the part that matters when you write code here: `brightness` and `speed` read every selected zone back and print what was actually applied; where all zones match they print `Brightness set to N` or `Speed set to N`, and where any zone differs they print one summary line naming each zone's real value and the request. Never let a command report success for a value the keyboard did not accept. Both read back through the shared `setValueVerified`, so a future parameter needs no new read-back path. When you see a name or a behavior in one file, grep for it across the whole repo before deciding if a change is consistent. ## Read the Firmware When Something Is Unclear Unclear firmware behaviour is answered by reading source, not by inferring it from names, from a general QMK assumption, or from a doc line that nobody has checked. Two sources settle nearly everything, and both are cheap to reach. Upstream QMK is public: `quantum/rgb_matrix/rgb_matrix.h` for the value API, `quantum/rgb_matrix/animations/` for what an effect actually does to hue, saturation and value. The vendor's VIA definition JSON for the board settles what the interface exposes at all — which custom value IDs exist, which ranges they take, and for which effects a color control is offered. It is the authority on the wire format, more than upstream is, because the ID mapping is the vendor's. The board's own firmware is a vendor binary with no public source, so both sources describe the family, not the unit on the desk. Confirm against the hardware where that is cheap, then put the observation in README.md. One trap this session paid for: the vendor channels are not all the same QMK subsystem. One is `rgb_matrix`, one is the lightweight `rgblight`, and one is the audio effect subsystem. An upstream file that governs one channel does not govern another, so "the QMK source says" has to name the channel it was read for. A formula from the wrong subsystem is how this repo ended up claiming that speed 0 and 1 behaved identically. ## Code vs Documentation All documentation (README.md, comments, AGENTS.md) must stay in sync with the code. When code and documentation conflict, **ask the user** before deciding which one to change. Do not silently pick a winner — explicitly state the conflict and get direction. ## Go Development ### Tooling - `gopls` — Go Language Server, installed separately with `go install golang.org/x/tools/gopls@latest` (LSP, diagnostics, go-to-def, rename, references) - `gofmt` / `goimports` — formatting. `goimports` adds/removes imports automatically. Always run before committing. - `go test` — built-in test framework. Tests live in `*_test.go` files alongside source. - `go vet` — static analysis. Run before committing. - `go build` — compiles without installing. Fast, cached. - `go mod tidy` — adds missing deps, removes unused ones. Run after every import change. ### Style (Effective Go + Code Review Comments) - **Formatting**: always `gofmt` or `goimports`. Tabs for indentation, no line-length limit but avoid uncomfortably long lines. - **Names**: `MixedCaps` / `mixedCaps`, no underscores. Short local variables (`i`, `c`, `r`). Descriptive for globals. - **Initialisms**: consistent case — `URL`, `ID`, `HTTP` → `appID`, `urlPony`, `ServeHTTP`. - **Comments**: doc comments start with the name, end with period. `// Package rgb provides RGB effect handling.` - **Imports**: standard library first, blank line, then third-party. Grouped. ### Error Handling - Return errors, never `panic` for normal control flow. - Indent error flow early — happy path at minimal indentation: ```go if err != nil { return err } // normal code ``` - Error strings: lowercase, no period — `fmt.Errorf("device not found")`. - Do not discard errors with `_`. ### Interfaces & Types - Define interfaces in the **consumer** package, not the producer. - Use pointer receivers when in doubt (mutation, large structs, sync fields). - Return concrete types from constructors — let consumers mock if needed. - Empty slices: `var t []string` (nil), not `t := []string{}` (non-nil), unless JSON encoding requires `[]`. ### Concurrency - Pass `context.Context` as **first parameter**. Never store in structs. - Clear goroutine lifetimes — document when/why they exit. Avoid leaks via channels. - Prefer synchronous functions — callers can add concurrency, not remove it. ### Testing - Table-driven tests preferred for multiple cases. - Failure messages: `t.Errorf("Effect(%q) = %d, want %d", input, got, want)`. - Use `*_test.go` files. Test package should match source package (internal tests). - Example functions (`func Example...`) double as docs and tests. ### Modules & Packages - Module path: `github.com//` (when published). - Package names: short, single-word, lowercase. No `util`, `common`, `api`, `types`. - Zero values should be useful (e.g. `bytes.Buffer`, `sync.Mutex`). ## graphify This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. When the user types `/graphify`, use the installed graphify skill or instructions before doing anything else. Rules: - For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. - Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it. - If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. - Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. - After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).