Commit 1c22065e25

1c22065e2524cfed36a3af744500a34adef75b88

parent: 1aaf5bca7b

Verified · cmc ci/build: success ci/test: success ci/vuln: success

cmc <hello@cleberg.net> · 2026-09-03 00:10 UTC

cli: help forwards to the server for a prefix or --json

Cobra's built-in help owned `gitbay help`, so `gitbay help --json`
failed on an unknown flag and `gitbay help mr` printed cobra's one-line
summaries while the wiki documented the server's reference.

`gitbay help` bare is still the local tree. With a prefix or --json it
runs the server's help, which is the only place flags are written down
and whose JSON is the contract. The coverage test attaches the help
command the way Execute does, so `help` is wired like any other path.

Closes #86

Layout: unified · split

cmd/gitbay/coverage_test.go +3 −2
@@ -13,7 +13,6 @@ import (
13// registry must be reachable by typing it, or the CLI is not the 13// registry must be reachable by typing it, or the CLI is not the
14// complete interface the design claims. 14// complete interface the design claims.
15var notInCLI = map[string]string{ 15var notInCLI = map[string]string{
16 "help": "cobra owns `gitbay help`; the registry's is reached by `<cmd> --help`",
17 "runner next": "the CI runner's wire protocol, not for humans", 16 "runner next": "the CI runner's wire protocol, not for humans",
18 "runner done": "the CI runner's wire protocol, not for humans", 17 "runner done": "the CI runner's wire protocol, not for humans",
19 "runner log": "the CI runner's wire protocol, not for humans", 18 "runner log": "the CI runner's wire protocol, not for humans",
@@ -36,7 +35,9 @@ func TestEveryCommandIsReachable(t *testing.T) {
36 walk(sub) 35 walk(sub)
37 } 36 }
38 } 37 }
39 walk(newRoot()) 38 root := newRoot()
39 root.InitDefaultHelpCmd() // attaches the help command the way Execute does
40 walk(root)
40 41
41 for _, cmd := range control.Commands() { 42 for _, cmd := range control.Commands() {
42 path := strings.Join(cmd.Path, " ") 43 path := strings.Join(cmd.Path, " ")
cmd/gitbay/main.go +27
@@ -32,6 +32,7 @@ func newRoot() *cobra.Command {
32 SilenceUsage: true, 32 SilenceUsage: true,
33 SilenceErrors: true, 33 SilenceErrors: true,
34 } 34 }
35 root.SetHelpCommand(helpCmd(root))
35 36
36 root.AddCommand( 37 root.AddCommand(
37 authCmd(), 38 authCmd(),
@@ -576,3 +577,29 @@ func manCmd(root *cobra.Command) *cobra.Command {
576 cmd.Flags().StringVar(&dir, "dir", "man", "output directory") 577 cmd.Flags().StringVar(&dir, "dir", "man", "output directory")
577 return cmd 578 return cmd
578} 579}
580
581// helpCmd is `gitbay help`. Bare, it is cobra's tree of local commands.
582// With anything after it — a prefix such as `mr`, or --json — it is the
583// server's help: the registry is the only place flags are written down,
584// and its JSON is the contract. Cobra's built-in help used to swallow
585// both forms, so `gitbay help --json` failed on an unknown flag.
586func helpCmd(root *cobra.Command) *cobra.Command {
587 return &cobra.Command{
588 Use: "help [<prefix>...] [--json]",
589 Short: "this list, or the server's command reference for a prefix",
590 Annotations: map[string]string{serverPath: "help", stdinMode: "none"},
591 DisableFlagParsing: true,
592 RunE: func(cmd *cobra.Command, args []string) error {
593 if len(args) == 0 {
594 return root.Help()
595 }
596 t, err := resolveTarget()
597 if err != nil {
598 fmt.Fprintln(os.Stderr, "gitbay:", err)
599 os.Exit(protocol.ExitFailure)
600 }
601 os.Exit(runSSH(t, append([]string{"help"}, args...), strings.NewReader("")))
602 return nil
603 },
604 }
605}