Merge pull request #1745 from entireio/group-root-help-commands · Entire

Merge pull request #1745 from entireio/group-root-help-commands

62f8779→main·

pjbgf·2d ago·2 files·+126 added/-25 removed

cli: group root help output by user journey

Changes

2

31 unmodified lines

TUI elements, which works better with screen readers.

// Help groups for the root command. AddGroup order is display order. // Visible commands without a GroupID render under "Additional Commands" // (version, labs, agent-help, help) — that placement is intentional. const ( groupSetup = "setup" groupSessions = "sessions" groupAccount = "account" groupControlPlane = "controlplane" )

// inGroup assigns a help group to a command at registration time so all // grouping stays visible in NewRootCmd rather than spread across constructors. func inGroup(c *cobra.Command, groupID string) *cobra.Command { c.GroupID = groupID return c }

func NewRootCmd() *cobra.Command { cmd := &cobra.Command{ Use: "entire", , }

// Help groups; AddGroup order is display order in entire --help. cmd.AddGroup( &cobra.Group{ID: groupSetup, Title: "Entire Setup:"}, &cobra.Group{ID: groupSessions, Title: "Sessions & Checkpoints:"}, &cobra.Group{ID: groupAccount, Title: "Account:"}, &cobra.Group{ID: groupControlPlane, Title: "Control Plane:"}, )

// Noun groups (canonical homes for subcommands). cmd.AddCommand(newSessionsCmd()) // 'session' (with 'sessions' as Cobra alias) cmd.AddCommand(newCheckpointGroupCmd()) // 'checkpoint' / 'cp' / 'checkpoints' experimental.Register(cmd, newTokensGroupCmd()) // 'tokens' (experimental) cmd.AddCommand(newAgentGroupCmd()) // 'agent' cmd.AddCommand(newAuthCmd()) // 'auth' cmd.AddCommand(newDoctorCmd()) // 'doctor' (group: trace/logs/bundle) cmd.AddCommand(newLabsCmd()) // 'labs' (experimental workflow discovery) cmd.AddCommand(newPluginGroupCmd()) // 'plugin' (managed install/list/remove) experimental.Register(cmd, newImportCmd()) // 'import' (experimental; import pre-existing agent history) cmd.AddCommand(newOrgCmd()) // 'org' — control-plane org management cmd.AddCommand(newProjectCmd()) // 'project' — control-plane project management cmd.AddCommand(newRepoCmd()) // 'repo' — control-plane repo lifecycle cmd.AddCommand(newGrantCmd()) // 'grant' — control-plane access grants cmd.AddCommand(inGroup(newSessionsCmd(), groupSessions)) // 'session' (with 'sessions' as Cobra alias) cmd.AddCommand(inGroup(newCheckpointGroupCmd(), groupSessions)) // 'checkpoint' / 'cp' / 'checkpoints' experimental.Register(cmd, newTokensGroupCmd()) // 'tokens' (experimental) cmd.AddCommand(inGroup(newAgentGroupCmd(), groupSetup)) // 'agent' cmd.AddCommand(inGroup(newAuthCmd(), groupAccount)) // 'auth' cmd.AddCommand(inGroup(newDoctorCmd(), groupSetup)) // 'doctor' (group: trace/logs/bundle) cmd.AddCommand(newLabsCmd()) // 'labs' (experimental workflow discovery) cmd.AddCommand(inGroup(newPluginGroupCmd(), groupSetup)) // 'plugin' (managed install/list/remove) experimental.Register(cmd, newImportCmd()) // 'import' (experimental; import pre-existing agent history) cmd.AddCommand(inGroup(newOrgCmd(), groupControlPlane)) // 'org' — control-plane org management cmd.AddCommand(inGroup(newProjectCmd(), groupControlPlane)) // 'project' — control-plane project management cmd.AddCommand(inGroup(newRepoCmd(), groupControlPlane)) // 'repo' — control-plane repo lifecycle cmd.AddCommand(inGroup(newGrantCmd(), groupControlPlane)) // 'grant' — control-plane access grants

// Top-level lifecycle and standalone commands. experimental.Register(cmd, cliReview.NewCommand(buildReviewDeps())) // review (experimental) experimental.Register(cmd, investigate.NewCommand(buildInvestigateDeps())) // investigate (experimental); multi-agent investigation cmd.AddCommand(newCleanCmd()) cmd.AddCommand(newSetupCmd()) // 'configure' — non-agent settings; agent CRUD lives under 'agent' cmd.AddCommand(newEnableCmd()) cmd.AddCommand(newDisableCmd()) cmd.AddCommand(newStatusCmd()) cmd.AddCommand(inGroup(newCleanCmd(), groupSetup)) cmd.AddCommand(inGroup(newSetupCmd(), groupSetup)) // 'configure' — non-agent settings; agent CRUD lives under 'agent' cmd.AddCommand(inGroup(newEnableCmd(), groupSetup)) cmd.AddCommand(inGroup(newDisableCmd(), groupSetup)) cmd.AddCommand(inGroup(newStatusCmd(), groupSetup)) experimental.Register(cmd, newBlameCmd()) // 'blame' (experimental) experimental.Register(cmd, newWhyCmd()) // 'why' (experimental) cmd.AddCommand(newLoginCmd()) cmd.AddCommand(newLogoutCmd()) cmd.AddCommand(inGroup(newLoginCmd(), groupAccount)) cmd.AddCommand(inGroup(newLogoutCmd(), groupAccount)) cmd.AddCommand(newVersionCmd()) cmd.AddCommand(newDispatchCmd()) cmd.AddCommand(newActivityCmd()) cmd.AddCommand(newRecapCmd()) cmd.AddCommand(newAPICmd()) // authenticated passthrough to core/cell APIs cmd.AddCommand(newAgentHelpCmd(cmd)) // visible: agents on transports without context injection discover it via entire help cmd.AddCommand(inGroup(newDispatchCmd(), groupSessions)) cmd.AddCommand(inGroup(newActivityCmd(), groupSessions)) cmd.AddCommand(inGroup(newRecapCmd(), groupSessions)) cmd.AddCommand(inGroup(newAPICmd(), groupControlPlane)) // authenticated passthrough to core/cell APIs cmd.AddCommand(newAgentHelpCmd(cmd)) // visible: agents on transports without context injection discover it via entire help

// Hidden top-level shortcuts. Functional but print a deprecation hint. cmd.AddCommand(hideAsAlias(newResumeCmd(), "entire session resume"))


Mcmd/entire/cli/root.go+50/-25

261 unmodified lines

...


func TestRoot_VisibleCommandsAreGrouped(t *testing.T) {
    t.Parallel()

// Commands intentionally left out of any group. version, labs, agent-help,
    // and help render under cobra's "Additional Commands"; completion is
    // allowlisted for completeness but never renders (hidden via
    // CompletionOptions.HiddenDefaultCmd in NewRootCmd).
    ungrouped := map[string]bool{
        "version":    true,
        "labs":       true,
        "agent-help": true,
        "help":       true,
        "completion": true,
    }

wantGroups := map[string]string{
        "enable":     groupSetup,
        "disable":    groupSetup,
        "configure":  groupSetup,
        "agent":      groupSetup,
        "plugin":     groupSetup,
        "status":     groupSetup,
        "doctor":     groupSetup,
        "clean":      groupSetup,
        "session":    groupSessions,
        "checkpoint": groupSessions,
        "recap":      groupSessions,
        "activity":   groupSessions,
        "dispatch":   groupSessions,
        "login":      groupAccount,
        "logout":     groupAccount,
        "auth":       groupAccount,
        "org":        groupControlPlane,
        "project":    groupControlPlane,
        "repo":       groupControlPlane,
        "grant":      groupControlPlane,
        "api":        groupControlPlane,
    }

root := NewRootCmd()

registered := make(map[string]bool)
    for _, g := range root.Groups() {
        registered[g.ID] = true
    }

for _, c := range root.Commands() {
        if c.Hidden || c.Deprecated != "" {
            continue
        }
        // Experimental commands are grouped by experimental.Register (visible
        // only in developer/nightly builds) — not part of this table.
        if c.GroupID == experimental.GroupID {
            continue
        }
        name := c.Name()
        if ungrouped[name] {
            if c.GroupID != "" {
                t.Errorf(%q should stay ungrouped, got GroupID %q", name, c.GroupID)
            }
            continue
        }
        want, ok := wantGroups[name]
        if !ok {
            t.Errorf("visible command %q missing from group table; assign it a group or add it to the ungrouped allowlist", name)
            continue
        }
        if c.GroupID != want {
            t.Errorf(%q GroupID = %q, want %q", name, c.GroupID, want)
        }
        if !registered[want] {
            t.Errorf("group %q used by %q is not registered on root (cobra panics at Execute)", want, name)
        }
    }
}

func containsString(values []string, want string) bool {
    for _, value := range values {
        if value == want {
            return true
        }
    }
    return false
}