ソースを参照

move the definition commands under the keyboard command

`definition` had two children and `keyboard` had one, and the one was `info`
because a bare `info` was already the per-zone RGB state. So `keyboard` existed
to disambiguate that collision rather than to hold anything, which is why the
root help showed an empty namespace beside a `definition` that appeared to have
no relation to it.

What sits under it now is coherent on both sides. `fetch` writes a file that
belongs to a board and looks it up by the board's vendor and product ID, so
there is nothing to fetch without one. `definitions` lists what is in use, one
entry per board, and never opens a board — the same profile as `keyboard info`,
which was already in the group.

The listing is named `definitions` and not `list` on purpose. Under `keyboard`,
`list` reads as listing the keyboards, which is what `info` does, and the root
already has a `list` that means the profile store; one verb must not name two
stores. `definitions` collides with nothing and says what it lists.

The five messages that told a user to run `definition fetch` name `keyboard
fetch` now, and README quotes four of them verbatim, so the two move together.
There is no alias for the old name: a silent one would keep it alive, which is
the drift this removes, in the other direction.
Paul-Dieter Klumpp 1 週間 前
親
コミット
dbc872c7ca

+ 3 - 3
.claude/skills/qmk-rgb/SKILL.md

@@ -27,7 +27,7 @@ qmk-rgb-tool effect --list                    # Effects per zone, one per line
 qmk-rgb-tool --json effect --list             # The same, as JSON for parsing
 qmk-rgb-tool --json effect --list             # The same, as JSON for parsing
 qmk-rgb-tool keyboard info                    # Connected keyboards
 qmk-rgb-tool keyboard info                    # Connected keyboards
 qmk-rgb-tool info                             # Current RGB state
 qmk-rgb-tool info                             # Current RGB state
-qmk-rgb-tool definition list                  # Which definition files are in use, and where each is from
+qmk-rgb-tool keyboard definitions            # Which definition files are in use, and where each is from
 ```
 ```
 
 
 Present the raw output to the user. Never duplicate documentation — the tool output is authoritative.
 Present the raw output to the user. Never duplicate documentation — the tool output is authoritative.
@@ -74,8 +74,8 @@ keyboard has no names, say so and offer the two ways to supply them, in this
 order:
 order:
 
 
 ```bash
 ```bash
-qmk-rgb-tool definition fetch   # download the definition for the connected board
-qmk-rgb-tool definition list    # every definition in use, from the per-user directory and built in
+qmk-rgb-tool keyboard fetch                  # download the definition for the connected board
+qmk-rgb-tool keyboard definitions            # every definition in use, from the per-user directory and built in
 ```
 ```
 
 
 `fetch` needs the board to be in VIA's collection. Many are not, including the
 `fetch` needs the board to be in VIA's collection. Many are not, including the

+ 5 - 5
AGENTS.md

@@ -48,7 +48,7 @@ Each of the two is one directory, and it is both what is written and what is
 read: `definitionsPath()` and `profilesPath()`, and nothing else. Both were
 read: `definitionsPath()` and `profilesPath()`, and nothing else. Both were
 searches until they were not — next to the executable, then the working
 searches until they were not — next to the executable, then the working
 directory, then the user's — and the search was a defect in both cases, for the
 directory, then the user's — and the search was a defect in both cases, for the
-same reason in reverse. A definition is written by `definition fetch` and read by
+same reason in reverse. A definition is written by `keyboard fetch` and read by
 every command, so a directory beside the binary hides the file the user just
 every command, so a directory beside the binary hides the file the user just
 fetched, for as long as the binary is run from a checkout, which is where it is
 fetched, for as long as the binary is run from a checkout, which is where it is
 run from. A profile is written to the user's directory and read from a
 run from. A profile is written to the user's directory and read from a
@@ -241,7 +241,7 @@ measured on VIA's own collection of 2029 definitions rather than assumed:
   what a document should tell a user to type.
   what a document should tell a user to type.
 - A fetched file has to be parsed and matched before it is believed. VIA answers
 - A fetched file has to be parsed and matched before it is believed. VIA answers
   an unknown board with its own web page and HTTP 200, so status alone proves
   an unknown board with its own web page and HTTP 200, so status alone proves
-  nothing. `definition fetch` tries v3 then v2, and only stores a document that
+  nothing. `keyboard fetch` tries v3 then v2, and only stores a document that
   parses and names the board that was asked about.
   parses and names the board that was asked about.
 
 
 The files are VIA's format, but their addressing is QMK's, and that is not a
 The files are VIA's format, but their addressing is QMK's, and that is not a
@@ -289,18 +289,18 @@ transcribed per board and cannot be generated from a common source.
 
 
 ## Output Shape
 ## Output Shape
 
 
-`keyboard info`, `info`, `list`, `effect --list` and `definition list` print text,
+`keyboard info`, `info`, `list`, `effect --list` and `keyboard definitions` print text,
 because that is what a person reads, and JSON only behind the persistent `--json`.
 because that is what a person reads, and JSON only behind the persistent `--json`.
 The field names are the ones the JSON always had, so a consumer that passes the
 The field names are the ones the JSON always had, so a consumer that passes the
 flag is unaffected — where a field had to be added, it is added rather than
 flag is unaffected — where a field had to be added, it is added rather than
-renamed or reshaped. `definition list` grew a `source` field per entry for exactly
+renamed or reshaped. `keyboard definitions` grew a `source` field per entry for exactly
 this reason: it now lists the definitions built into the binary as well as the
 this reason: it now lists the definitions built into the binary as well as the
 ones in the user directory, and a path alone does not say which of the two a line
 ones in the user directory, and a path alone does not say which of the two a line
 is, because a built-in one has a path relative to the build. A command that emits
 is, because a built-in one has a path relative to the build. A command that emits
 structured data and neither honours `--json` nor says why is the bug this rule
 structured data and neither honours `--json` nor says why is the bug this rule
 exists for. Two shapes are deliberately
 exists for. Two shapes are deliberately
 not in that list: `effect` with no argument routes to `effect --list`, and
 not in that list: `effect` with no argument routes to `effect --list`, and
-`definition fetch` writes a file and prints a line about it.
+`keyboard fetch` writes a file and prints a line about it.
 
 
 `keyboard info` does not open the board, so it must not report the board's
 `keyboard info` does not open the board, so it must not report the board's
 channels or an effect list from one: it reports the name and whether this tool has
 channels or an effect list from one: it reports the name and whether this tool has

+ 13 - 13
README.md

@@ -40,8 +40,8 @@ when you open a board in its web app — so the tool needs that board's file, th
 VIA does. Identify the keyboard, then get its definition:
 VIA does. Identify the keyboard, then get its definition:
 
 
 ```bash
 ```bash
-qmk-rgb-tool definition fetch   # download the definition for the connected keyboard
-qmk-rgb-tool definition list    # every definition in use, and where each is from
+qmk-rgb-tool keyboard fetch         # download the definition for the connected keyboard
+qmk-rgb-tool keyboard definitions   # every definition in use, and where each is from
 ```
 ```
 
 
 **Where those files live.** Both directories are under the platform's per-user
 **Where those files live.** Both directories are under the platform's per-user
@@ -54,7 +54,7 @@ configuration directory, and each is read from there and written to there:
 | Windows | `%AppData%\qmk-rgb-tool\` |
 | Windows | `%AppData%\qmk-rgb-tool\` |
 
 
 One directory per kind, and not a search. A definition is written by
 One directory per kind, and not a search. A definition is written by
-`definition fetch` and read by every command, so a search path would mean the
+`keyboard fetch` and read by every command, so a search path would mean the
 file a fetch produced is not the file the next command reads. A profile is
 file a fetch produced is not the file the next command reads. A profile is
 written there and read there, so `save <name>` followed by `load <name>` finds
 written there and read there, so `save <name>` followed by `load <name>` finds
 what was just written and a same-named file in a checkout cannot be loaded in its
 what was just written and a same-named file in a checkout cannot be loaded in its
@@ -324,20 +324,20 @@ commands need the catalog and say so rather than guessing:
 
 
 ```
 ```
 $ qmk-rgb-tool effect wave
 $ qmk-rgb-tool effect wave
-Error: no effect names for this keyboard: run `definition fetch` for its VIA definition, or set an
+Error: no effect names for this keyboard: run `keyboard fetch` for its VIA definition, or set an
 effect by number with `effect <index>`
 effect by number with `effect <index>`
 
 
 $ qmk-rgb-tool enable
 $ qmk-rgb-tool enable
-Error: this keyboard has no effect names, so `enable` cannot choose an effect: run `definition fetch` for
+Error: this keyboard has no effect names, so `enable` cannot choose an effect: run `keyboard fetch` for
 its VIA definition, or set one with `effect <index>`
 its VIA definition, or set one with `effect <index>`
 
 
 $ qmk-rgb-tool load paul
 $ qmk-rgb-tool load paul
-Warning: profile "paul" has no effect names for this keyboard, so nothing applied; run `definition fetch` for
+Warning: profile "paul" has no effect names for this keyboard, so nothing applied; run `keyboard fetch` for
 its VIA definition
 its VIA definition
 
 
 $ qmk-rgb-tool save paul
 $ qmk-rgb-tool save paul
 Warning: this keyboard has no effect names, so the profile records effect "unknown" and cannot restore it; run
 Warning: this keyboard has no effect names, so the profile records effect "unknown" and cannot restore it; run
-`definition fetch` for its VIA definition, or set an effect with `effect <index>`
+`keyboard fetch` for its VIA definition, or set an effect with `effect <index>`
 ```
 ```
 
 
 `enable` needs it because it has to choose an effect to turn a channel on.
 `enable` needs it because it has to choose an effect to turn a channel on.
@@ -466,7 +466,7 @@ failure, but the summary line always states the value that was actually applied.
 - **Effect names from VIA definition files** — the vendor's own names, read at runtime; no name is hand-written, so there is one source per board, and a file the user places overrides the one built in
 - **Effect names from VIA definition files** — the vendor's own names, read at runtime; no name is hand-written, so there is one source per board, and a file the user places overrides the one built in
 - **Compatibility aliases** — `off`, `breathe`, `rainbow`, `rainbow_wave`, `solid`, `static` resolve to correct effect IDs per channel
 - **Compatibility aliases** — `off`, `breathe`, `rainbow`, `rainbow_wave`, `solid`, `static` resolve to correct effect IDs per channel
 - **Reactive & splash effects** — honor `color` and `speed` for key-press illumination
 - **Reactive & splash effects** — honor `color` and `speed` for key-press illumination
-- **Machine-parseable output** — text by default, JSON behind `--json` for `keyboard info`, `info`, `list`, `effect --list` and `definition list`
+- **Machine-parseable output** — text by default, JSON behind `--json` for `keyboard info`, `info`, `list`, `effect --list` and `keyboard definitions`
 - **Agent-friendly** — designed for automation, scripting, and CLI-first workflows
 - **Agent-friendly** — designed for automation, scripting, and CLI-first workflows
 - **Multiple devices** — `keyboard info` numbers each keyboard; `--device <n>` targets one
 - **Multiple devices** — `keyboard info` numbers each keyboard; `--device <n>` targets one
 
 
@@ -673,7 +673,7 @@ a channel the same thing, and a name is matched ignoring case. On a board whose
 definition writes `Backlight`, all of `--zone backlight`, `--zone Backlight` and
 definition writes `Backlight`, all of `--zone backlight`, `--zone Backlight` and
 `--zone rgb_matrix` reach the same channel: the label is the name, the subsystem
 `--zone rgb_matrix` reach the same channel: the label is the name, the subsystem
 stays accepted because it follows from the channel number. `qmk-rgb-tool
 stays accepted because it follows from the channel number. `qmk-rgb-tool
-definition list` prints the label beside the subsystem it stands for, and says
+keyboard definitions` prints the label beside the subsystem it stands for, and says
 where each definition came from — `user` for a file in the per-user directory,
 where each definition came from — `user` for a file in the per-user directory,
 `built-in` for one compiled into the binary:
 `built-in` for one compiled into the binary:
 
 
@@ -732,8 +732,8 @@ board", and that file is gone, so the field would have been unanswerable:
 | `qmk-rgb-tool effect <name\|index>`   | Set an effect by name, or a raw effect index 0–255, verified by read-back; a board that does not implement the index clamps it to the highest it does |
 | `qmk-rgb-tool effect <name\|index>`   | Set an effect by name, or a raw effect index 0–255, verified by read-back; a board that does not implement the index clamps it to the highest it does |
 | `qmk-rgb-tool effect`                 | With no argument, list every effect per channel |
 | `qmk-rgb-tool effect`                 | With no argument, list every effect per channel |
 | `qmk-rgb-tool effect --list`          | The same list, as a flag                   |
 | `qmk-rgb-tool effect --list`          | The same list, as a flag                   |
-| `qmk-rgb-tool definition fetch`       | Download the VIA definition for the connected keyboard |
-| `qmk-rgb-tool definition list`        | List every definition in use, from the per-user directory and built into the binary, each marked with which it is |
+| `qmk-rgb-tool keyboard fetch`         | Download the VIA definition for the connected keyboard |
+| `qmk-rgb-tool keyboard definitions`   | List every definition in use, from the per-user directory and built into the binary, each marked with which it is |
 | `qmk-rgb-tool brightness <val>`       | Set brightness (0–255) on selected zones, verified by read-back |
 | `qmk-rgb-tool brightness <val>`       | Set brightness (0–255) on selected zones, verified by read-back |
 | `qmk-rgb-tool speed <val>`            | Set effect speed (0–255) on selected zones, verified by read-back; on `logo` and `side` only 0, 1 and 4 are reachable |
 | `qmk-rgb-tool speed <val>`            | Set effect speed (0–255) on selected zones, verified by read-back; on `logo` and `side` only 0, 1 and 4 are reachable |
 | `qmk-rgb-tool color <hex>`            | Set color (e.g. `ff0000`) on selected zones |
 | `qmk-rgb-tool color <hex>`            | Set color (e.g. `ff0000`) on selected zones |
@@ -742,7 +742,7 @@ board", and that file is gone, so the field would have been unanswerable:
 | `qmk-rgb-tool --zone <channel> ...`   | Target one channel: `backlight`, `rgblight`, `rgb_matrix`, `audio` or `led_matrix`, or the name the board's definition gives it |
 | `qmk-rgb-tool --zone <channel> ...`   | Target one channel: `backlight`, `rgblight`, `rgb_matrix`, `audio` or `led_matrix`, or the name the board's definition gives it |
 | `qmk-rgb-tool --device <n> ...`      | Target keyboard by number (see `keyboard info`) |
 | `qmk-rgb-tool --device <n> ...`      | Target keyboard by number (see `keyboard info`) |
 | `qmk-rgb-tool --definition <path>`  | Read effect names from this VIA definition file instead of the one in the data directory; applies to every command that resolves names, and a file for another board is refused |
 | `qmk-rgb-tool --definition <path>`  | Read effect names from this VIA definition file instead of the one in the data directory; applies to every command that resolves names, and a file for another board is refused |
-| `qmk-rgb-tool --json`               | Print JSON instead of text, for `keyboard info`, `info`, `list`, `effect --list` and `definition list` |
+| `qmk-rgb-tool --json`               | Print JSON instead of text, for `keyboard info`, `info`, `list`, `effect --list` and `keyboard definitions` |
 | `qmk-rgb-tool -v`, `--version`       | Print the version                          |
 | `qmk-rgb-tool -v`, `--version`       | Print the version                          |
 | `qmk-rgb-tool save [name]`            | Save current RGB state as `<name>.json` in the per-user `profiles/` (name lowercased, non-`[a-z0-9-_]` mapped to `-`, a leading `-` prefixed with `unnamed-`); without a name it writes `default` |
 | `qmk-rgb-tool save [name]`            | Save current RGB state as `<name>.json` in the per-user `profiles/` (name lowercased, non-`[a-z0-9-_]` mapped to `-`, a leading `-` prefixed with `unnamed-`); without a name it writes `default` |
 | `qmk-rgb-tool load [name]`            | Load and apply a profile by name from the per-user `profiles/`; without a name it loads `default` |
 | `qmk-rgb-tool load [name]`            | Load and apply a profile by name from the per-user `profiles/`; without a name it loads `default` |
@@ -755,7 +755,7 @@ token. `effect`, `load`, `save` and `delete` accept an optional name.
 `brightness`, `speed` and `color` require exactly one argument. `effect` takes one
 `brightness`, `speed` and `color` require exactly one argument. `effect` takes one
 argument too, and accepts either a name or a raw effect ID.
 argument too, and accepts either a name or a raw effect ID.
 
 
-`keyboard info`, `info`, `list`, `definition list` and `effect --list` print
+`keyboard info`, `info`, `list`, `keyboard definitions` and `effect --list` print
 text, because a person reads them. Pass `--json` for the machine shape, which is
 text, because a person reads them. Pass `--json` for the machine shape, which is
 the same data with the same field names as before:
 the same data with the same field names as before:
 
 

+ 1 - 1
cmd/qmk-rgb-tool/channels_test.go

@@ -447,7 +447,7 @@ func TestRootHelpGroupsTheCommands(t *testing.T) {
 		"Lighting Commands:",
 		"Lighting Commands:",
 		"Profile Commands:",
 		"Profile Commands:",
 		"Keyboard Commands:",
 		"Keyboard Commands:",
-		"save", "load", "list", "delete", "effect", "definition", "keyboard",
+		"save", "load", "list", "delete", "effect", "keyboard",
 	} {
 	} {
 		if !strings.Contains(help, want) {
 		if !strings.Contains(help, want) {
 			t.Errorf("--help does not mention %q", want)
 			t.Errorf("--help does not mention %q", want)

+ 2 - 2
cmd/qmk-rgb-tool/datadir.go

@@ -15,7 +15,7 @@ import (
 // There is one such directory and both kinds of file are read from it and
 // There is one such directory and both kinds of file are read from it and
 // written to it. There used to be a search: definitions and profiles were looked
 // written to it. There used to be a search: definitions and profiles were looked
 // for next to the executable and in the working directory before the user's, and
 // for next to the executable and in the working directory before the user's, and
-// that was wrong for both. A definition is written by `definition fetch` and read
+// that was wrong for both. A definition is written by `keyboard fetch` and read
 // by every command, so a search path can hide the file the user just fetched. A
 // by every command, so a search path can hide the file the user just fetched. A
 // profile is written to the user's directory, so a search path for reading can
 // profile is written to the user's directory, so a search path for reading can
 // find a same-named file in a checkout instead and load that one — the same name,
 // find a same-named file in a checkout instead and load that one — the same name,
@@ -46,7 +46,7 @@ func userDataDir() string {
 }
 }
 
 
 // definitionsPath is where the definition files live: the per-user directory,
 // definitionsPath is where the definition files live: the per-user directory,
-// and nowhere else. A `definition fetch` writes here, so anything else would mean
+// and nowhere else. A `keyboard fetch` writes here, so anything else would mean
 // the file a fetch produced is not the file the next command reads. The
 // the file a fetch produced is not the file the next command reads. The
 // definitions built into the binary are consulted after this one, by
 // definitions built into the binary are consulted after this one, by
 // candidateDefinitions in catalog.go.
 // candidateDefinitions in catalog.go.

+ 1 - 1
cmd/qmk-rgb-tool/datadir_test.go

@@ -36,7 +36,7 @@ func TestDataDirectoriesAreTheUserDirectory(t *testing.T) {
 // directory, then the user's. Both reasons for that are gone, and a test has to
 // directory, then the user's. Both reasons for that are gone, and a test has to
 // say so, because the search is the kind of thing that comes back.
 // say so, because the search is the kind of thing that comes back.
 //
 //
-// A search path for a definition hides the file `definition fetch` just wrote,
+// A search path for a definition hides the file `keyboard fetch` just wrote,
 // because a definitions directory beside the binary exists for as long as the
 // because a definitions directory beside the binary exists for as long as the
 // binary is run from a checkout, and it wins. A search path for a profile finds a
 // binary is run from a checkout, and it wins. A search path for a profile finds a
 // same-named file in a checkout and loads that one instead of the one the user
 // same-named file in a checkout and loads that one instead of the one the user

+ 15 - 21
cmd/qmk-rgb-tool/definition.go

@@ -31,20 +31,10 @@ var httpGet = func(url string) ([]byte, int, error) {
 	return body, resp.StatusCode, err
 	return body, resp.StatusCode, err
 }
 }
 
 
-func NewDefinitionCmd() *cobra.Command {
-	cmd := &cobra.Command{
-		Use:   "definition",
-		Short: "Manage keyboard definition files",
-		Long: "A keyboard's effect names come from a VIA definition file, the same one VIA\n" +
-			"itself uses. `fetch` downloads the file for the connected keyboard into the\n" +
-			"data directory; to use a file you already have, put it in that directory or\n" +
-			"pass it with --definition.",
-	}
-	cmd.AddCommand(newDefinitionFetchCmd(), newDefinitionListCmd())
-	return cmd
-}
-
-func newDefinitionFetchCmd() *cobra.Command {
+// NewKeyboardFetchCmd is `keyboard fetch`. It belongs to the keyboard command
+// because what it fetches belongs to a board: the file is looked up by the
+// board's vendor and product ID, so there is nothing to fetch without one.
+func NewKeyboardFetchCmd() *cobra.Command {
 	return &cobra.Command{
 	return &cobra.Command{
 		Use:   "fetch",
 		Use:   "fetch",
 		Short: "Download the definition file for the connected keyboard",
 		Short: "Download the definition file for the connected keyboard",
@@ -53,20 +43,24 @@ func newDefinitionFetchCmd() *cobra.Command {
 			"an unknown one with its own web page, so a downloaded file is parsed and\n" +
 			"an unknown one with its own web page, so a downloaded file is parsed and\n" +
 			"matched against the keyboard before it is stored.",
 			"matched against the keyboard before it is stored.",
 		Args: cobra.NoArgs,
 		Args: cobra.NoArgs,
-		RunE: runDefinitionFetch,
+		RunE: runKeyboardFetch,
 	}
 	}
 }
 }
 
 
-func newDefinitionListCmd() *cobra.Command {
+// NewKeyboardDefinitionsCmd is `keyboard definitions`, and the name is the noun
+// rather than a verb on purpose. `keyboard list` reads as listing the
+// keyboards, which is what `keyboard info` does, and the root already has a
+// `list` that means the profile store; one verb must not mean two stores.
+func NewKeyboardDefinitionsCmd() *cobra.Command {
 	return &cobra.Command{
 	return &cobra.Command{
-		Use:   "list",
-		Short: "List the definition files in the data directory",
+		Use:   "definitions",
+		Short: "List the definition files in the data directory, and those built in",
 		Args:  cobra.NoArgs,
 		Args:  cobra.NoArgs,
-		RunE:  runDefinitionList,
+		RunE:  runKeyboardDefinitions,
 	}
 	}
 }
 }
 
 
-func runDefinitionFetch(cmd *cobra.Command, args []string) error {
+func runKeyboardFetch(cmd *cobra.Command, args []string) error {
 	target, err := prepareTarget()
 	target, err := prepareTarget()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -94,7 +88,7 @@ func runDefinitionFetch(cmd *cobra.Command, args []string) error {
 	return nil
 	return nil
 }
 }
 
 
-func runDefinitionList(cmd *cobra.Command, args []string) error {
+func runKeyboardDefinitions(cmd *cobra.Command, args []string) error {
 	dir := definitionsPath()
 	dir := definitionsPath()
 	// Both sources are listed, not just the directory: the file built into the
 	// Both sources are listed, not just the directory: the file built into the
 	// binary is where the names for a board that cannot be fetched come from, and
 	// binary is where the names for a board that cannot be fetched come from, and

+ 13 - 19
cmd/qmk-rgb-tool/definition_test.go

@@ -29,14 +29,13 @@ func TestFetchDefinitionStoresTheFileForTheBoard(t *testing.T) {
 		return nil, http.StatusNotFound, errors.New("404")
 		return nil, http.StatusNotFound, errors.New("404")
 	})
 	})
 
 
-	cmd := NewDefinitionCmd()
+	cmd := NewKeyboardFetchCmd()
 	var out, errOut strings.Builder
 	var out, errOut strings.Builder
 	cmd.SetOut(&out)
 	cmd.SetOut(&out)
 	cmd.SetErr(&errOut)
 	cmd.SetErr(&errOut)
-	cmd.SetArgs([]string{"fetch"})
 
 
 	if err := cmd.Execute(); err != nil {
 	if err := cmd.Execute(); err != nil {
-		t.Fatalf("definition fetch error = %v (stderr %q)", err, errOut.String())
+		t.Fatalf("keyboard fetch error = %v (stderr %q)", err, errOut.String())
 	}
 	}
 
 
 	if len(requested) != 1 || !strings.Contains(requested[0], "/v3/305419896.json") {
 	if len(requested) != 1 || !strings.Contains(requested[0], "/v3/305419896.json") {
@@ -67,15 +66,14 @@ func TestFetchDefinitionRejectsAPageThatIsNotADefinition(t *testing.T) {
 		return []byte("<!doctype html><html><head><title>VIA</title></head></html>"), http.StatusOK, nil
 		return []byte("<!doctype html><html><head><title>VIA</title></head></html>"), http.StatusOK, nil
 	})
 	})
 
 
-	cmd := NewDefinitionCmd()
+	cmd := NewKeyboardFetchCmd()
 	var out, errOut strings.Builder
 	var out, errOut strings.Builder
 	cmd.SetOut(&out)
 	cmd.SetOut(&out)
 	cmd.SetErr(&errOut)
 	cmd.SetErr(&errOut)
-	cmd.SetArgs([]string{"fetch"})
 
 
 	err := cmd.Execute()
 	err := cmd.Execute()
 	if err == nil {
 	if err == nil {
-		t.Fatal("definition fetch = nil error, want a failure for a non-definition")
+		t.Fatal("keyboard fetch = nil error, want a failure for a non-definition")
 	}
 	}
 	if !strings.Contains(err.Error(), "no definition") {
 	if !strings.Contains(err.Error(), "no definition") {
 		t.Errorf("error = %q, want it to say there is no definition", err)
 		t.Errorf("error = %q, want it to say there is no definition", err)
@@ -247,7 +245,7 @@ func stubTargetData(vendorID, productID uint16) targetDeviceData {
 	}
 	}
 }
 }
 
 
-// definition list is structured data, so --json has to reach it like every other
+// keyboard definitions is structured data, so --json has to reach it like every other
 // listing command; text by default is no excuse for a missing machine shape. The
 // listing command; text by default is no excuse for a missing machine shape. The
 // definitions built into the binary are listed beside the ones in the user
 // definitions built into the binary are listed beside the ones in the user
 // directory, and each carries which of the two it is.
 // directory, and each carries which of the two it is.
@@ -259,13 +257,12 @@ func TestDefinitionListHonoursTheJSONFlag(t *testing.T) {
 	t.Cleanup(forceDefinitionsDir(t, dir))
 	t.Cleanup(forceDefinitionsDir(t, dir))
 
 
 	var out, errOut strings.Builder
 	var out, errOut strings.Builder
-	cmd := NewDefinitionCmd()
+	cmd := NewKeyboardDefinitionsCmd()
 	cmd.SetOut(&out)
 	cmd.SetOut(&out)
 	cmd.SetErr(&errOut)
 	cmd.SetErr(&errOut)
-	cmd.SetArgs([]string{"list"})
 	withJSON(t)
 	withJSON(t)
 	if err := cmd.Execute(); err != nil {
 	if err := cmd.Execute(); err != nil {
-		t.Fatalf("definition list --json error = %v", err)
+		t.Fatalf("keyboard definitions --json error = %v", err)
 	}
 	}
 
 
 	var payload struct {
 	var payload struct {
@@ -311,13 +308,12 @@ func TestDefinitionListShowsTheBuiltInDefinitions(t *testing.T) {
 	t.Cleanup(forceDefinitionsDir(t, t.TempDir()))
 	t.Cleanup(forceDefinitionsDir(t, t.TempDir()))
 
 
 	var out, errOut strings.Builder
 	var out, errOut strings.Builder
-	cmd := NewDefinitionCmd()
+	cmd := NewKeyboardDefinitionsCmd()
 	cmd.SetOut(&out)
 	cmd.SetOut(&out)
 	cmd.SetErr(&errOut)
 	cmd.SetErr(&errOut)
-	cmd.SetArgs([]string{"list"})
 	withJSON(t)
 	withJSON(t)
 	if err := cmd.Execute(); err != nil {
 	if err := cmd.Execute(); err != nil {
-		t.Fatalf("definition list --json error = %v", err)
+		t.Fatalf("keyboard definitions --json error = %v", err)
 	}
 	}
 
 
 	var payload struct {
 	var payload struct {
@@ -351,12 +347,11 @@ func TestDefinitionListReportsThatAUserFileShadowsABuiltInOne(t *testing.T) {
 	t.Cleanup(forceDefinitionsDir(t, dir))
 	t.Cleanup(forceDefinitionsDir(t, dir))
 
 
 	var out, errOut strings.Builder
 	var out, errOut strings.Builder
-	cmd := NewDefinitionCmd()
+	cmd := NewKeyboardDefinitionsCmd()
 	cmd.SetOut(&out)
 	cmd.SetOut(&out)
 	cmd.SetErr(&errOut)
 	cmd.SetErr(&errOut)
-	cmd.SetArgs([]string{"list"})
 	if err := cmd.Execute(); err != nil {
 	if err := cmd.Execute(); err != nil {
-		t.Fatalf("definition list error = %v", err)
+		t.Fatalf("keyboard definitions error = %v", err)
 	}
 	}
 	if !strings.Contains(out.String(), "shadows the built-in copy") {
 	if !strings.Contains(out.String(), "shadows the built-in copy") {
 		t.Errorf("stdout = %q, want it to say the user file overrides the built-in one", out.String())
 		t.Errorf("stdout = %q, want it to say the user file overrides the built-in one", out.String())
@@ -371,12 +366,11 @@ func TestDefinitionListPrintsTextByDefault(t *testing.T) {
 	t.Cleanup(forceDefinitionsDir(t, dir))
 	t.Cleanup(forceDefinitionsDir(t, dir))
 
 
 	var out, errOut strings.Builder
 	var out, errOut strings.Builder
-	cmd := NewDefinitionCmd()
+	cmd := NewKeyboardDefinitionsCmd()
 	cmd.SetOut(&out)
 	cmd.SetOut(&out)
 	cmd.SetErr(&errOut)
 	cmd.SetErr(&errOut)
-	cmd.SetArgs([]string{"list"})
 	if err := cmd.Execute(); err != nil {
 	if err := cmd.Execute(); err != nil {
-		t.Fatalf("definition list error = %v", err)
+		t.Fatalf("keyboard definitions error = %v", err)
 	}
 	}
 	var probe any
 	var probe any
 	if err := json.Unmarshal([]byte(out.String()), &probe); err == nil {
 	if err := json.Unmarshal([]byte(out.String()), &probe); err == nil {

+ 1 - 1
cmd/qmk-rgb-tool/enable.go

@@ -25,7 +25,7 @@ func NewEnableCmd() *cobra.Command {
 			for _, ch := range channels {
 			for _, ch := range channels {
 				if _, ok := catalog.DefaultEffect(ch); !ok {
 				if _, ok := catalog.DefaultEffect(ch); !ok {
 					return fmt.Errorf("this keyboard has no effect names, so `enable` cannot choose an effect: " +
 					return fmt.Errorf("this keyboard has no effect names, so `enable` cannot choose an effect: " +
-						"run `definition fetch` for its VIA definition, or set one with `effect <index>`")
+						"run `keyboard fetch` for its VIA definition, or set one with `effect <index>`")
 				}
 				}
 			}
 			}
 
 

+ 13 - 2
cmd/qmk-rgb-tool/main.go

@@ -61,10 +61,18 @@ func main() {
 	Execute()
 	Execute()
 }
 }
 
 
+// keyboardCmd is the namespace for everything that is about one board rather
+// than about its lighting: which boards are connected, and the definition file
+// whose effect names belong to a board. The definition prose lives here because
+// there is no `definition` command of its own any more.
 var keyboardCmd = &cobra.Command{
 var keyboardCmd = &cobra.Command{
 	Use:     "keyboard",
 	Use:     "keyboard",
 	Short:   "Keyboard management commands",
 	Short:   "Keyboard management commands",
 	GroupID: groupKeyboard,
 	GroupID: groupKeyboard,
+	Long: "A keyboard's effect names come from a VIA definition file, the same one VIA\n" +
+		"itself uses. `fetch` downloads the file for the connected keyboard into the\n" +
+		"data directory; to use a file you already have, put it in that directory or\n" +
+		"pass it with --definition.",
 }
 }
 
 
 // inGroup places a command in one of the root help's groups.
 // inGroup places a command in one of the root help's groups.
@@ -126,7 +134,11 @@ func init() {
 // same tree the binary runs and check the grouping is real, rather than repeating
 // same tree the binary runs and check the grouping is real, rather than repeating
 // a list that could fall behind the one that ships.
 // a list that could fall behind the one that ships.
 func registerCommands(root *cobra.Command) {
 func registerCommands(root *cobra.Command) {
-	keyboardCmd.AddCommand(keyboardInfoCmd)
+	keyboardCmd.AddCommand(
+		keyboardInfoCmd,
+		NewKeyboardFetchCmd(),
+		NewKeyboardDefinitionsCmd(),
+	)
 
 
 	root.AddCommand(
 	root.AddCommand(
 		inGroup(NewEnableCmd(), groupLighting),
 		inGroup(NewEnableCmd(), groupLighting),
@@ -140,7 +152,6 @@ func registerCommands(root *cobra.Command) {
 		inGroup(NewProfileLoadCmd(), groupProfile),
 		inGroup(NewProfileLoadCmd(), groupProfile),
 		inGroup(NewProfileListCmd(), groupProfile),
 		inGroup(NewProfileListCmd(), groupProfile),
 		inGroup(NewProfileDeleteCmd(), groupProfile),
 		inGroup(NewProfileDeleteCmd(), groupProfile),
-		inGroup(NewDefinitionCmd(), groupKeyboard),
 		inGroup(keyboardCmd, groupKeyboard),
 		inGroup(keyboardCmd, groupKeyboard),
 	)
 	)
 }
 }

+ 2 - 2
cmd/qmk-rgb-tool/profile.go

@@ -224,7 +224,7 @@ func loadProfileFromDevice(name string, warn io.Writer) error {
 		// restored. Say so here, where the user can still act on it.
 		// restored. Say so here, where the user can still act on it.
 		fmt.Fprintf(warn,
 		fmt.Fprintf(warn,
 			"Warning: this keyboard has no effect names, so the profile records effect %q and cannot restore it; "+
 			"Warning: this keyboard has no effect names, so the profile records effect %q and cannot restore it; "+
-				"run `definition fetch` for its VIA definition, or set an effect with `effect <index>`\n", "unknown")
+				"run `keyboard fetch` for its VIA definition, or set an effect with `effect <index>`\n", "unknown")
 	}
 	}
 
 
 	p := &Profile{
 	p := &Profile{
@@ -329,7 +329,7 @@ func NewProfileLoadCmd() *cobra.Command {
 			if catalog == nil {
 			if catalog == nil {
 				fmt.Fprintf(cmd.ErrOrStderr(),
 				fmt.Fprintf(cmd.ErrOrStderr(),
 					"Warning: profile %q has no effect names for this keyboard, so nothing applied; "+
 					"Warning: profile %q has no effect names for this keyboard, so nothing applied; "+
-						"run `definition fetch` for its VIA definition\n", name)
+						"run `keyboard fetch` for its VIA definition\n", name)
 				return nil
 				return nil
 			}
 			}
 
 

+ 1 - 1
definitions/README.md

@@ -5,7 +5,7 @@ in this directory, so that `go install` delivers them: it copies a binary and
 creates no data directory, and a board VIA does not carry cannot be fetched.
 creates no data directory, and a board VIA does not carry cannot be fetched.
 
 
 This is not the directory the tool reads at runtime. That is the per-user
 This is not the directory the tool reads at runtime. That is the per-user
-`definitions/` directory the top-level README names; `definition fetch` writes
+`definitions/` directory the top-level README names; `keyboard fetch` writes
 there, and a file placed there — or passed with `--definition` — takes precedence
 there, and a file placed there — or passed with `--definition` — takes precedence
 over the one built in. To use or correct a definition, put it there, not here.
 over the one built in. To use or correct a definition, put it there, not here.
 
 

+ 1 - 1
internal/rgb/catalog.go

@@ -205,7 +205,7 @@ func ResolveEffect(catalog *Catalog, name string, channels []via.Channel, explic
 		// Both ways out belong in the message: the board is still drivable by
 		// Both ways out belong in the message: the board is still drivable by
 		// number, and the names are one command away. A user who reads only the
 		// number, and the names are one command away. A user who reads only the
 		// error should not have to find either out elsewhere.
 		// error should not have to find either out elsewhere.
-		return nil, nil, fmt.Errorf("no effect names for this keyboard: run `definition fetch` for its VIA definition, " +
+		return nil, nil, fmt.Errorf("no effect names for this keyboard: run `keyboard fetch` for its VIA definition, " +
 			"or set an effect by number with `effect <index>`")
 			"or set an effect by number with `effect <index>`")
 	}
 	}
 
 

+ 1 - 1
internal/rgb/definition_test.go

@@ -373,7 +373,7 @@ func TestMissingCatalogErrorNamesBothWaysOut(t *testing.T) {
 	if err == nil {
 	if err == nil {
 		t.Fatal("ResolveEffect(nil catalog) = nil error, want one")
 		t.Fatal("ResolveEffect(nil catalog) = nil error, want one")
 	}
 	}
-	for _, want := range []string{"`definition fetch`", "`effect <index>`"} {
+	for _, want := range []string{"`keyboard fetch`", "`effect <index>`"} {
 		if !strings.Contains(err.Error(), want) {
 		if !strings.Contains(err.Error(), want) {
 			t.Errorf("error = %q, want it to mention %q", err, want)
 			t.Errorf("error = %q, want it to mention %q", err, want)
 		}
 		}