Skip to content

Commit 62f8779

Browse files
authored
Merge pull request #1745 from entireio/group-root-help-commands
cli: group root help output by user journey
2 parents 0d3d1d7 + 61b3c54 commit 62f8779

2 files changed

Lines changed: 126 additions & 25 deletions

File tree

cmd/entire/cli/root.go

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,23 @@ Environment Variables:
3232
TUI elements, which works better with screen readers.
3333
`
3434

35+
// Help groups for the root command. AddGroup order is display order.
36+
// Visible commands without a GroupID render under "Additional Commands"
37+
// (version, labs, agent-help, help) — that placement is intentional.
38+
const (
39+
groupSetup = "setup"
40+
groupSessions = "sessions"
41+
groupAccount = "account"
42+
groupControlPlane = "controlplane"
43+
)
44+
45+
// inGroup assigns a help group to a command at registration time so all
46+
// grouping stays visible in NewRootCmd rather than spread across constructors.
47+
func inGroup(c *cobra.Command, groupID string) *cobra.Command {
48+
c.GroupID = groupID
49+
return c
50+
}
51+
3552
func NewRootCmd() *cobra.Command {
3653
cmd := &cobra.Command{
3754
Use: "entire",
@@ -85,39 +102,47 @@ func NewRootCmd() *cobra.Command {
85102
},
86103
}
87104

105+
// Help groups; AddGroup order is display order in `entire --help`.
106+
cmd.AddGroup(
107+
&cobra.Group{ID: groupSetup, Title: "Entire Setup:"},
108+
&cobra.Group{ID: groupSessions, Title: "Sessions & Checkpoints:"},
109+
&cobra.Group{ID: groupAccount, Title: "Account:"},
110+
&cobra.Group{ID: groupControlPlane, Title: "Control Plane:"},
111+
)
112+
88113
// Noun groups (canonical homes for subcommands).
89-
cmd.AddCommand(newSessionsCmd()) // 'session' (with 'sessions' as Cobra alias)
90-
cmd.AddCommand(newCheckpointGroupCmd()) // 'checkpoint' / 'cp' / 'checkpoints'
91-
experimental.Register(cmd, newTokensGroupCmd()) // 'tokens' (experimental)
92-
cmd.AddCommand(newAgentGroupCmd()) // 'agent'
93-
cmd.AddCommand(newAuthCmd()) // 'auth'
94-
cmd.AddCommand(newDoctorCmd()) // 'doctor' (group: trace/logs/bundle)
95-
cmd.AddCommand(newLabsCmd()) // 'labs' (experimental workflow discovery)
96-
cmd.AddCommand(newPluginGroupCmd()) // 'plugin' (managed install/list/remove)
97-
experimental.Register(cmd, newImportCmd()) // 'import' (experimental; import pre-existing agent history)
98-
cmd.AddCommand(newOrgCmd()) // 'org' — control-plane org management
99-
cmd.AddCommand(newProjectCmd()) // 'project' — control-plane project management
100-
cmd.AddCommand(newRepoCmd()) // 'repo' — control-plane repo lifecycle
101-
cmd.AddCommand(newGrantCmd()) // 'grant' — control-plane access grants
114+
cmd.AddCommand(inGroup(newSessionsCmd(), groupSessions)) // 'session' (with 'sessions' as Cobra alias)
115+
cmd.AddCommand(inGroup(newCheckpointGroupCmd(), groupSessions)) // 'checkpoint' / 'cp' / 'checkpoints'
116+
experimental.Register(cmd, newTokensGroupCmd()) // 'tokens' (experimental)
117+
cmd.AddCommand(inGroup(newAgentGroupCmd(), groupSetup)) // 'agent'
118+
cmd.AddCommand(inGroup(newAuthCmd(), groupAccount)) // 'auth'
119+
cmd.AddCommand(inGroup(newDoctorCmd(), groupSetup)) // 'doctor' (group: trace/logs/bundle)
120+
cmd.AddCommand(newLabsCmd()) // 'labs' (experimental workflow discovery)
121+
cmd.AddCommand(inGroup(newPluginGroupCmd(), groupSetup)) // 'plugin' (managed install/list/remove)
122+
experimental.Register(cmd, newImportCmd()) // 'import' (experimental; import pre-existing agent history)
123+
cmd.AddCommand(inGroup(newOrgCmd(), groupControlPlane)) // 'org' — control-plane org management
124+
cmd.AddCommand(inGroup(newProjectCmd(), groupControlPlane)) // 'project' — control-plane project management
125+
cmd.AddCommand(inGroup(newRepoCmd(), groupControlPlane)) // 'repo' — control-plane repo lifecycle
126+
cmd.AddCommand(inGroup(newGrantCmd(), groupControlPlane)) // 'grant' — control-plane access grants
102127

103128
// Top-level lifecycle and standalone commands.
104129
experimental.Register(cmd, cliReview.NewCommand(buildReviewDeps())) // `review` (experimental)
105130
experimental.Register(cmd, investigate.NewCommand(buildInvestigateDeps())) // `investigate` (experimental); multi-agent investigation
106-
cmd.AddCommand(newCleanCmd())
107-
cmd.AddCommand(newSetupCmd()) // 'configure' — non-agent settings; agent CRUD lives under 'agent'
108-
cmd.AddCommand(newEnableCmd())
109-
cmd.AddCommand(newDisableCmd())
110-
cmd.AddCommand(newStatusCmd())
131+
cmd.AddCommand(inGroup(newCleanCmd(), groupSetup))
132+
cmd.AddCommand(inGroup(newSetupCmd(), groupSetup)) // 'configure' — non-agent settings; agent CRUD lives under 'agent'
133+
cmd.AddCommand(inGroup(newEnableCmd(), groupSetup))
134+
cmd.AddCommand(inGroup(newDisableCmd(), groupSetup))
135+
cmd.AddCommand(inGroup(newStatusCmd(), groupSetup))
111136
experimental.Register(cmd, newBlameCmd()) // 'blame' (experimental)
112137
experimental.Register(cmd, newWhyCmd()) // 'why' (experimental)
113-
cmd.AddCommand(newLoginCmd())
114-
cmd.AddCommand(newLogoutCmd())
138+
cmd.AddCommand(inGroup(newLoginCmd(), groupAccount))
139+
cmd.AddCommand(inGroup(newLogoutCmd(), groupAccount))
115140
cmd.AddCommand(newVersionCmd())
116-
cmd.AddCommand(newDispatchCmd())
117-
cmd.AddCommand(newActivityCmd())
118-
cmd.AddCommand(newRecapCmd())
119-
cmd.AddCommand(newAPICmd()) // authenticated passthrough to core/cell APIs
120-
cmd.AddCommand(newAgentHelpCmd(cmd)) // visible: agents on transports without context injection discover it via `entire help`
141+
cmd.AddCommand(inGroup(newDispatchCmd(), groupSessions))
142+
cmd.AddCommand(inGroup(newActivityCmd(), groupSessions))
143+
cmd.AddCommand(inGroup(newRecapCmd(), groupSessions))
144+
cmd.AddCommand(inGroup(newAPICmd(), groupControlPlane)) // authenticated passthrough to core/cell APIs
145+
cmd.AddCommand(newAgentHelpCmd(cmd)) // visible: agents on transports without context injection discover it via `entire help`
121146

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

cmd/entire/cli/root_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,82 @@ func TestCheckpointPolicyCommandIsExperimental(t *testing.T) {
262262
}
263263
}
264264

265+
func TestRoot_VisibleCommandsAreGrouped(t *testing.T) {
266+
t.Parallel()
267+
268+
// Commands intentionally left out of any group. version, labs, agent-help,
269+
// and help render under cobra's "Additional Commands"; completion is
270+
// allowlisted for completeness but never renders (hidden via
271+
// CompletionOptions.HiddenDefaultCmd in NewRootCmd).
272+
ungrouped := map[string]bool{
273+
"version": true,
274+
"labs": true,
275+
"agent-help": true,
276+
"help": true,
277+
"completion": true,
278+
}
279+
280+
wantGroups := map[string]string{
281+
"enable": groupSetup,
282+
"disable": groupSetup,
283+
"configure": groupSetup,
284+
"agent": groupSetup,
285+
"plugin": groupSetup,
286+
"status": groupSetup,
287+
"doctor": groupSetup,
288+
"clean": groupSetup,
289+
"session": groupSessions,
290+
"checkpoint": groupSessions,
291+
"recap": groupSessions,
292+
"activity": groupSessions,
293+
"dispatch": groupSessions,
294+
"login": groupAccount,
295+
"logout": groupAccount,
296+
"auth": groupAccount,
297+
"org": groupControlPlane,
298+
"project": groupControlPlane,
299+
"repo": groupControlPlane,
300+
"grant": groupControlPlane,
301+
"api": groupControlPlane,
302+
}
303+
304+
root := NewRootCmd()
305+
306+
registered := make(map[string]bool)
307+
for _, g := range root.Groups() {
308+
registered[g.ID] = true
309+
}
310+
311+
for _, c := range root.Commands() {
312+
if c.Hidden || c.Deprecated != "" {
313+
continue
314+
}
315+
// Experimental commands are grouped by experimental.Register (visible
316+
// only in developer/nightly builds) — not part of this table.
317+
if c.GroupID == experimental.GroupID {
318+
continue
319+
}
320+
name := c.Name()
321+
if ungrouped[name] {
322+
if c.GroupID != "" {
323+
t.Errorf("%q should stay ungrouped, got GroupID %q", name, c.GroupID)
324+
}
325+
continue
326+
}
327+
want, ok := wantGroups[name]
328+
if !ok {
329+
t.Errorf("visible command %q missing from group table; assign it a group or add it to the ungrouped allowlist", name)
330+
continue
331+
}
332+
if c.GroupID != want {
333+
t.Errorf("%q GroupID = %q, want %q", name, c.GroupID, want)
334+
}
335+
if !registered[want] {
336+
t.Errorf("group %q used by %q is not registered on root (cobra panics at Execute)", want, name)
337+
}
338+
}
339+
}
340+
265341
func containsString(values []string, want string) bool {
266342
for _, value := range values {
267343
if value == want {

0 commit comments

Comments
 (0)