Cross-platform Go CLI library for programmatic/agent-friendly control of VIA-compatible keyboard RGB lighting (starting with Impact 80).
cmd/ # Cobra-based CLI entrypoints
internal/device/ # HID device discovery (VID/PID matching)
internal/via/ # VIA protocol implementation + LED subsystem
internal/rgb/ # Effects, values, color definitions
keyboards.json # VID/PID database + keyboard metadata
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 <val> # 0-255
qmk-rgb-tool speed <val> # 0-255
qmk-rgb-tool color <hex> # Six hexadecimal digits
qmk-rgb-tool mode <index> # Raw zone-specific effect ID
qmk-rgb-tool enable
qmk-rgb-tool disable
qmk-rgb-tool info
qmk-rgb-tool save <name> # Save current RGB state to a profile
qmk-rgb-tool load <name> # Load a profile and apply it to the keyboard
qmk-rgb-tool delete <name> # Delete a saved profile
qmk-rgb-tool list # List saved profiles
--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.
Logo and Side use IDs 0–6: none, wave, fixed_wave, spectrum,
breathing, light, and shutdown.
Backlight uses the complete ID 0–45 family:
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.
00x07 — Custom set value0x08 — Custom get value0x02 logo, 0x03 backlight, 0x04 side0x01, effect 0x02, speed 0x03, color 0x04| Keyboard | VID | PID |
|---|---|---|
| Wobkey Rainy 75 | 0x6666 | 0x0001 |
| Wobkey Impact 80 | 0x36B0 | 0x309F |
keyboards.json maps VID+PID → VIA protocol config + RGB layout metadata.
github.com/sstallion/go-hid for HID accessgithub.com/spf13/cobra for CLI frameworkNames used in code must appear identically everywhere. Before any edit:
go build -o target (usually $GOBIN/$module_name) must match every example in README, AGENTS.md, comments, and docs.qmk-rgb-tool) is the binary name; every usage example, CLI reference table, and doc must use the same string.go.mod module path is the source of truth for go install targets.logo, backlight, side must be identical in code, docs, and examples.When you see a name in one file, grep for it across the whole repo before deciding if a change is consistent.
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.
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.gofmt or goimports. Tabs for indentation, no line-length limit but avoid uncomfortably long lines.MixedCaps / mixedCaps, no underscores. Short local variables (i, c, r). Descriptive for globals.URL, ID, HTTP → appID, urlPony, ServeHTTP.// Package rgb provides RGB effect handling.panic for normal control flow.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/<user>/<project> (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 "<question>" when graphify-out/graph.json exists. Use graphify path "<A>" "<B>" for relationships and graphify explain "<concept>" 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).