# 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 qmk-rgb-tool speed # 0-255 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` is persistent across commands and accepts `logo`, `backlight`, or `side`. 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 `rgb mode` as a raw escape hatch. `rgb 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). 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).