# Agent Guidelines — QMK RGB Tool ## 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/via/ # VIA protocol implementation + LED subsystem internal/rgb/ # Effects, values, color definitions keyboards.json # Optional name/metadata lookup for known keyboards ``` ## CLI Interface ``` qmk-rgb-tool keyboard info qmk-rgb-tool effect breathing qmk-rgb-tool effect rainbow_moving_chevron qmk-rgb-tool effect rainbow_moving_chevron --zone backlight qmk-rgb-tool brightness # 0-255, verified by read-back qmk-rgb-tool speed # 0-255, verified by read-back qmk-rgb-tool color # Six hexadecimal digits qmk-rgb-tool mode # Raw zone-specific effect ID qmk-rgb-tool enable qmk-rgb-tool disable qmk-rgb-tool info qmk-rgb-tool save # Save current RGB state to a profile qmk-rgb-tool load # Load a profile and apply it to the keyboard qmk-rgb-tool delete # Delete a saved profile qmk-rgb-tool list # List saved profiles qmk-rgb-tool --device ... # Target keyboard number from `keyboard info` ``` ## 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. ### Impact 80 Effect Families Logo and Side use IDs 0–6: `none`, `wave`, `fixed_wave`, `spectrum`, `breathing`, `light`, and `shutdown`. Backlight uses the complete ID 0–45 family: ```text none, solid_color, alphas_mods, gradient_up_down, gradient_left_right, breathing, band_sat, band_val, band_pinwheel_sat, band_pinwheel_val, band_spiral_sat, band_spiral_val, cycle_all, cycle_left_right, cycle_up_down, cycle_out_in, cycle_out_in_dual, rainbow_moving_chevron, cycle_pinwheel, cycle_spiral, dual_beacon, rainbow_beacon, rainbow_pinwheels, flower_blooming, raindrops, jellybean_raindrops, hue_breathing, hue_pendulum, hue_wave, pixel_flow, digital_rain, solid_reactive, solid_reactive_wide, solid_reactive_multiwide, solid_reactive_cross, solid_reactive_multicross, solid_reactive_nexus, solid_reactive_multinexus, splash, multisplash, solid_splash, solid_multisplash, starlight, starlight_dual_hue, starlight_dual_sat, riverflow ``` ID 39 is `multisplash`; ID 41 is `solid_multisplash`. Supported aliases are `off` → `none`, `breathe` → `breathing`, `rainbow` (zone-dependent), `rainbow_wave` (Logo/Side), `solid` (zone-dependent), and legacy `static`. Do not assume a numeric effect ID is valid on every zone; use the zone-aware name resolver or deliberately use `mode` as a raw escape hatch. `info` emits a `zones` array containing each selected zone's channel, enabled state, effect name and ID, brightness, speed, and color. The top-level summary comes from the first selected zone. A failed zone includes an `error` field while other zone results remain available; the command prints JSON before returning non-zero. ## VIA Protocol Essentials - Transport: QMK Raw HID over a 32-byte hidraw report, report number `0` - Message types: - `0x07` — Custom set value - `0x08` — Custom get value - Impact 80 lighting channels: `0x02` logo, `0x03` backlight, `0x04` side - RGB value IDs: brightness `0x01`, effect `0x02`, speed `0x03`, color `0x04` - No separate enable handshake is used ## Keyboards (Known VID/PID) | Keyboard | VID | PID | |------------------|--------|--------| | Wobkey Rainy 75 | 0x6666 | 0x0001 | | Wobkey Impact 80 | 0x36B0 | 0x309F | `keyboards.json` maps VID+PID → display name. Only `name` is read by the code; unknown fields in the file are ignored, so entries may carry extra keys without affecting discovery. ## 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 collapse any speed above 0 to 4, while `backlight` scales brightness up to 255 and applies speed as given. `brightness` and `speed` therefore 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. ## 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, built into Go toolchain (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).