feat(help): group root help, show relevant flags and add help topics

The root help lists commands in gh-style groups (core, Actions,
additional and alias commands) followed by help topics, examples and
pointers. Every help page uses USAGE, ALIASES, FLAGS, INHERITED FLAGS,
ARGUMENTS, JSON FIELDS, EXAMPLES and LEARN MORE sections. Inherited
flags show --repo, --remote and --cwd only where a repository is
selected, --json and --jq where JSON is printed, --yes where the
command confirms, and one line for the remaining global flags.

tea help environment, exit-codes and formatting document global flags,
environment variables, exit codes and JSON output. tea help with an
unknown topic is a usage error with suggestions instead of printing the
root help. A test keeps Shorts, examples and Long texts complete, with
allowlists for the commands that still lack them.

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-11 22:55:27 +02:00
1 parent f6a0bcfa6a
commit 48c1e5b781
3 files changed
+781 -9

No files matched your search

+31 -9
View File
@@ -5,6 +5,7 @@
package app
import (
"cmp"
"context"
"errors"
"fmt"
@@ -98,18 +99,18 @@ func NewRoot(deps Dependencies) *cobra.Command {
return client(instance, token, insecure)
}
}
root := &cobra.Command{Use: "tea", Short: "Work with Gitea from your terminal", Version: version.Format(), SilenceErrors: true, SilenceUsage: true}
root := &cobra.Command{Use: "tea", Short: "Work with Gitea from your terminal", Long: "Work seamlessly with Gitea from the command line.", Version: version.Format(), SilenceErrors: true, SilenceUsage: true}
root.SetIn(deps.Streams.In)
root.SetOut(deps.Streams.Out)
root.SetErr(deps.Streams.ErrOut)
root.SetFlagErrorFunc(func(_ *cobra.Command, err error) error { return clierr.Wrap(clierr.Usage, err) })
flags := root.PersistentFlags()
flags.StringVar(&a.options.host, "host", "", "Gitea instance URL, including any base path")
flags.StringVar(&a.options.user, "user", "", "Account on the selected instance")
flags.StringVar(&a.options.sudo, "sudo", "", "Perform API requests as this user (requires an administrator account)")
flags.StringVarP(&a.options.repo, "repo", "R", "", "Repository owner/name or URL")
flags.StringVar(&a.options.cwd, "cwd", "", "Working directory for repository discovery")
flags.StringVar(&a.options.remote, "remote", "", "Git remote used for repository discovery")
flags.StringVar(&a.options.host, "host", "", "Gitea instance `URL`, including any base path")
flags.StringVar(&a.options.user, "user", "", "Account `name` on the selected instance")
flags.StringVar(&a.options.sudo, "sudo", "", "Perform API requests as this `user` (requires an administrator account)")
flags.StringVarP(&a.options.repo, "repo", "R", "", "Select a repository as `OWNER/REPO` or URL")
flags.StringVar(&a.options.cwd, "cwd", "", "Working `directory` for repository discovery")
flags.StringVar(&a.options.remote, "remote", "", "Git `remote` used for repository discovery")
flags.StringVar(&a.options.json, "json", "", "Output JSON with the specified comma-separated `fields`, or all fields")
flags.Lookup("json").NoOptDefVal = "*"
flags.StringVarP(&a.options.jq, "jq", "q", "", "Filter JSON output using a jq `expression`; implies --json")
@@ -142,18 +143,39 @@ func NewRoot(deps Dependencies) *cobra.Command {
}
root.AddCommand(a.configCommand(), a.aliasCommand(), a.authCommand(), a.apiCommand(), a.repoCommand(), a.issueCommand(), a.prCommand(), a.doctorCommand(), a.workflowCommand(), a.runCommand(), a.labelCommand(), a.milestoneCommand(), a.sshKeyCommand(), a.timeCommand(), a.wikiCommand(), a.actionValueCommand(true), a.actionValueCommand(false), a.releaseCommand(), a.organizationCommand(), a.notificationCommand(), a.teamCommand(), a.branchCommand(), a.webhookCommand(), a.adminCommand(), a.searchCommand(), a.statusCommand(false))
root.AddCommand(a.manCommand())
root.AddCommand(&cobra.Command{Use: "version", Short: "Print tea's version", Annotations: textOutput("version prints text"), RunE: func(cmd *cobra.Command, _ []string) error {
root.AddCommand(&cobra.Command{Use: "version", Short: "Print tea's version", Example: "$ tea version", Annotations: textOutput("version prints text"), RunE: func(cmd *cobra.Command, _ []string) error {
_, err := fmt.Fprintln(cmd.OutOrStdout(), version.Format())
return err
}})
root.AddCommand(helpTopics(flags)...)
root.SetHelpCommand(a.helpCommand())
root.InitDefaultHelpCmd()
root.InitDefaultCompletionCmd()
root.SetVersionTemplate("{{.Version}}\n")
root.SetHelpFunc(a.writeHelp)
root.AddGroup(
&cobra.Group{ID: "core", Title: "Core commands"},
&cobra.Group{ID: "actions", Title: "Actions commands"},
&cobra.Group{ID: "additional", Title: "Additional commands"},
&cobra.Group{ID: "alias", Title: "Alias commands"},
)
for _, cmd := range root.Commands() {
if cmd.Annotations[topicAnnotation] == "" {
cmd.GroupID = cmp.Or(rootGroups[cmd.Name()], "additional")
}
}
rejectUnknownSubcommands(root)
addVerbAliases(root)
return root
}
// rootGroups places the core and Actions commands in their root help groups (D26); every other
// command is additional.
var rootGroups = map[string]string{
"auth": "core", "issue": "core", "org": "core", "pr": "core", "release": "core", "repo": "core",
"run": "actions", "secret": "actions", "variable": "actions", "workflow": "actions",
}
// verbAliases are the gh-style short names that every command with a common verb accepts (D6).
// Commands add other gh verbs, such as new, co, add and get, to their own Aliases.
var verbAliases = map[string]string{"list": "ls", "delete": "rm"}
@@ -285,7 +307,7 @@ func Execute(ctx context.Context, deps Dependencies, args []string) error {
if loadErr == nil {
for name, expansion := range snapshot.Preferences.Aliases {
if aliases.Check(name, snapshot.Preferences.Aliases, names) == nil {
root.AddCommand(&cobra.Command{Use: name, Short: "Alias for " + expansion, Annotations: map[string]string{"tea.alias": "true"}, RunE: func(*cobra.Command, []string) error {
root.AddCommand(&cobra.Command{Use: name, Short: fmt.Sprintf("Alias for %q", expansion), GroupID: "alias", Annotations: map[string]string{"tea.alias": "true"}, RunE: func(*cobra.Command, []string) error {
return clierr.Wrap(clierr.Usage, errors.New("alias must be expanded before execution"))
}})
}