diff --git a/README.md b/README.md index 8ceafdf..787420f 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Implemented: - Go module and binary entrypoint - Config file resolution relative to binary directory - Runtime mode detection (`pass_mgr` present/absent) -- Help and help-brief rendering system +- Top-level and group/leaf help rendering system (`--help` / `--help-brief` / `not permitted` stubs) - HTTP client wrapper - Output formatting (human-readable + `--json`) - `hf version`, `hf health`, `hf config` diff --git a/cmd/hf/main.go b/cmd/hf/main.go index 92ce0f4..3f25c50 100644 --- a/cmd/hf/main.go +++ b/cmd/hf/main.go @@ -27,12 +27,16 @@ func main() { case "--help-brief": fmt.Print(help.RenderTopHelpBrief(commands.Version, topGroups())) case "version": - commands.RunVersion() + handleLeafOrRun("version", args[1:], commands.RunVersion) case "health": - commands.RunHealth() + handleLeafOrRun("health", args[1:], commands.RunHealth) case "config": - runConfig(args[1:]) + handleConfig(args[1:]) default: + if group, ok := findGroup(args[0]); ok { + handleGroup(group, args[1:]) + return + } fmt.Fprintf(os.Stderr, "unknown command: %s\n", args[0]) fmt.Fprintf(os.Stderr, "Run 'hf --help' for usage.\n") os.Exit(1) @@ -53,7 +57,22 @@ func parseGlobalFlags(args []string) []string { return remaining } -func runConfig(args []string) { +func handleLeafOrRun(name string, args []string, run func()) { + if isHelpFlagOnly(args) { + fmt.Printf("hf %s\n", name) + return + } + if len(args) > 0 { + output.Errorf("unknown arguments for %s: %v", name, args) + } + run() +} + +func handleConfig(args []string) { + if isHelpFlagOnly(args) { + runConfigHelp() + return + } if len(args) == 0 { commands.RunConfigShow() return @@ -72,22 +91,77 @@ func runConfig(args []string) { } commands.RunConfigAccMgrToken(args[i+1]) return - case "--help", "-h": - fmt.Println("hf config - View and manage CLI configuration") - fmt.Println() - fmt.Println("Usage:") - fmt.Println(" hf config Show current config") - fmt.Println(" hf config --url Set HarborForge API URL") - if !mode.IsPaddedCell() { - fmt.Println(" hf config --acc-mgr-token Set account-manager token") - } - return default: output.Errorf("unknown config flag: %s", args[i]) } } } +func runConfigHelp() { + fmt.Println("hf config - View and manage CLI configuration") + fmt.Println() + fmt.Println("Usage:") + fmt.Println(" hf config Show current config") + fmt.Println(" hf config --url Set HarborForge API URL") + if !mode.IsPaddedCell() { + fmt.Println(" hf config --acc-mgr-token Set account-manager token") + } +} + +func handleGroup(group help.Group, args []string) { + if len(args) == 0 || isHelpFlagOnly(args) { + fmt.Print(help.RenderGroupHelp(group.Name, group.SubCommands)) + return + } + if len(args) == 1 && args[0] == "--help-brief" { + fmt.Print(help.RenderGroupHelpBrief(group.Name, group.SubCommands)) + return + } + + sub, ok := findSubCommand(group, args[0]) + if !ok { + output.Errorf("unknown %s subcommand: %s", group.Name, args[0]) + } + + if len(args) > 1 && isHelpFlagOnly(args[1:]) { + if !sub.Permitted { + fmt.Println(help.RenderNotPermitted(group.Name, sub.Name)) + return + } + fmt.Printf("hf %s %s\n", group.Name, sub.Name) + return + } + + if !sub.Permitted { + fmt.Println(help.RenderNotPermitted(group.Name, sub.Name)) + return + } + + output.Errorf("hf %s %s is recognized but not implemented yet", group.Name, sub.Name) +} + +func isHelpFlagOnly(args []string) bool { + return len(args) == 1 && (args[0] == "--help" || args[0] == "-h") +} + +func findGroup(name string) (help.Group, bool) { + for _, group := range topGroups() { + if group.Name == name { + return group, true + } + } + return help.Group{}, false +} + +func findSubCommand(group help.Group, name string) (help.Command, bool) { + for _, cmd := range group.SubCommands { + if cmd.Name == name { + return cmd, true + } + } + return help.Command{}, false +} + // topGroups returns the full command tree for help rendering. // TODO: permission awareness will be added when auth introspection is available. func topGroups() []help.Group { @@ -95,15 +169,136 @@ func topGroups() []help.Group { {Name: "version", Description: "Show CLI version", Permitted: true}, {Name: "health", Description: "Check API health", Permitted: true}, {Name: "config", Description: "View and manage CLI configuration", Permitted: true}, - {Name: "user", Description: "Manage users", Permitted: true}, - {Name: "role", Description: "Manage roles and permissions", Permitted: true}, - {Name: "permission", Description: "List permissions", Permitted: true}, - {Name: "project", Description: "Manage projects", Permitted: true}, - {Name: "milestone", Description: "Manage milestones", Permitted: true}, - {Name: "task", Description: "Manage tasks", Permitted: true}, - {Name: "meeting", Description: "Manage meetings", Permitted: true}, - {Name: "support", Description: "Manage support tickets", Permitted: true}, - {Name: "propose", Description: "Manage proposals", Permitted: true}, - {Name: "monitor", Description: "Monitor servers and API keys", Permitted: true}, + { + Name: "user", + Description: "Manage users", + Permitted: true, + SubCommands: []help.Command{ + {Name: "create", Description: "Create a user account (uses account-manager token flow)", Permitted: false}, + {Name: "list", Description: "List users", Permitted: false}, + {Name: "get", Description: "Show a user by username", Permitted: false}, + {Name: "update", Description: "Update a user", Permitted: false}, + {Name: "activate", Description: "Activate a user", Permitted: false}, + {Name: "deactivate", Description: "Deactivate a user", Permitted: false}, + {Name: "delete", Description: "Delete a user", Permitted: false}, + }, + }, + { + Name: "role", + Description: "Manage roles and permissions", + Permitted: true, + SubCommands: []help.Command{ + {Name: "list", Description: "List roles", Permitted: false}, + {Name: "get", Description: "Show a role by name", Permitted: false}, + {Name: "create", Description: "Create a role", Permitted: false}, + {Name: "update", Description: "Update a role", Permitted: false}, + {Name: "delete", Description: "Delete a role", Permitted: false}, + {Name: "set-permissions", Description: "Replace role permissions", Permitted: false}, + {Name: "add-permissions", Description: "Add permissions to a role", Permitted: false}, + {Name: "remove-permissions", Description: "Remove permissions from a role", Permitted: false}, + }, + }, + { + Name: "permission", + Description: "List permissions", + Permitted: true, + SubCommands: []help.Command{ + {Name: "list", Description: "List permissions", Permitted: false}, + }, + }, + { + Name: "project", + Description: "Manage projects", + Permitted: true, + SubCommands: []help.Command{ + {Name: "list", Description: "List projects", Permitted: false}, + {Name: "get", Description: "Show a project by code", Permitted: false}, + {Name: "create", Description: "Create a project", Permitted: false}, + {Name: "update", Description: "Update a project", Permitted: false}, + {Name: "delete", Description: "Delete a project", Permitted: false}, + {Name: "members", Description: "List project members", Permitted: false}, + {Name: "add-member", Description: "Add a project member", Permitted: false}, + {Name: "remove-member", Description: "Remove a project member", Permitted: false}, + }, + }, + { + Name: "milestone", + Description: "Manage milestones", + Permitted: true, + SubCommands: []help.Command{ + {Name: "list", Description: "List milestones", Permitted: false}, + {Name: "get", Description: "Show a milestone by code", Permitted: false}, + {Name: "create", Description: "Create a milestone", Permitted: false}, + {Name: "update", Description: "Update a milestone", Permitted: false}, + {Name: "delete", Description: "Delete a milestone", Permitted: false}, + {Name: "progress", Description: "Show milestone progress", Permitted: false}, + }, + }, + { + Name: "task", + Description: "Manage tasks", + Permitted: true, + SubCommands: []help.Command{ + {Name: "list", Description: "List tasks", Permitted: false}, + {Name: "get", Description: "Show a task by code", Permitted: false}, + {Name: "create", Description: "Create a task", Permitted: false}, + {Name: "update", Description: "Update a task", Permitted: false}, + {Name: "transition", Description: "Transition a task to a new status", Permitted: false}, + {Name: "take", Description: "Assign a task to the current user", Permitted: false}, + {Name: "delete", Description: "Delete a task", Permitted: false}, + {Name: "search", Description: "Search tasks", Permitted: false}, + }, + }, + { + Name: "meeting", + Description: "Manage meetings", + Permitted: true, + SubCommands: []help.Command{ + {Name: "list", Description: "List meetings", Permitted: false}, + {Name: "get", Description: "Show a meeting by code", Permitted: false}, + {Name: "create", Description: "Create a meeting", Permitted: false}, + {Name: "update", Description: "Update a meeting", Permitted: false}, + {Name: "attend", Description: "Attend a meeting", Permitted: false}, + {Name: "delete", Description: "Delete a meeting", Permitted: false}, + }, + }, + { + Name: "support", + Description: "Manage support tickets", + Permitted: true, + SubCommands: []help.Command{ + {Name: "list", Description: "List support tickets", Permitted: false}, + {Name: "get", Description: "Show a support ticket by code", Permitted: false}, + {Name: "create", Description: "Create a support ticket", Permitted: false}, + {Name: "update", Description: "Update a support ticket", Permitted: false}, + {Name: "take", Description: "Assign a support ticket to the current user", Permitted: false}, + {Name: "transition", Description: "Transition a support ticket to a new status", Permitted: false}, + {Name: "delete", Description: "Delete a support ticket", Permitted: false}, + }, + }, + { + Name: "propose", + Description: "Manage proposals", + Permitted: true, + SubCommands: []help.Command{ + {Name: "list", Description: "List proposals", Permitted: false}, + {Name: "get", Description: "Show a proposal by code", Permitted: false}, + {Name: "create", Description: "Create a proposal", Permitted: false}, + {Name: "update", Description: "Update a proposal", Permitted: false}, + {Name: "accept", Description: "Accept a proposal", Permitted: false}, + {Name: "reject", Description: "Reject a proposal", Permitted: false}, + {Name: "reopen", Description: "Reopen a proposal", Permitted: false}, + }, + }, + { + Name: "monitor", + Description: "Monitor servers and API keys", + Permitted: true, + SubCommands: []help.Command{ + {Name: "overview", Description: "Show monitor overview", Permitted: false}, + {Name: "server", Description: "Manage monitor servers", Permitted: false}, + {Name: "api-key", Description: "Manage monitor API keys", Permitted: false}, + }, + }, } }