From 52a9a6ae5f13793e170835fd92b7084a3e8824da Mon Sep 17 00:00:00 2001 From: devv-eve Date: Sat, 4 Apr 2026 15:30:40 -0700 Subject: [PATCH] refactor(cli): overhaul help output to match gh CLI style (#423) * refactor(cli): overhaul help output to match gh CLI style - Add gh-style grouped help with CORE/RUNTIME/ADDITIONAL COMMANDS sections - Use UPPERCASE section headers (USAGE, FLAGS, EXAMPLES, LEARN MORE) - Format commands as "name: description" with automatic alignment - Add ENVIRONMENT VARIABLES and EXAMPLES sections to root help - Apply consistent templates to root, subcommand, and leaf commands - Update descriptions from "Manage X" to "Work with X" for gh parity Co-Authored-By: Claude Opus 4.6 (1M context) * fix(execenv): add explicit instruction for agents to always use multica CLI Agents were using curl/wget to access Multica attachment URLs directly, which fails due to authentication. Add a prominent "Important" section to the generated CLAUDE.md template that explicitly prohibits direct HTTP access and instructs agents to escalate missing CLI functionality to their workspace owner. --------- Co-authored-by: Devv Co-authored-by: Claude Opus 4.6 (1M context) --- server/cmd/multica/cmd_agent.go | 2 +- server/cmd/multica/cmd_attachment.go | 2 +- server/cmd/multica/cmd_auth.go | 2 +- server/cmd/multica/cmd_config.go | 2 +- server/cmd/multica/cmd_daemon.go | 2 +- server/cmd/multica/cmd_issue.go | 4 +- server/cmd/multica/cmd_repo.go | 2 +- server/cmd/multica/cmd_runtime.go | 2 +- server/cmd/multica/cmd_skill.go | 4 +- server/cmd/multica/cmd_workspace.go | 2 +- server/cmd/multica/help.go | 142 ++++++++++++++++++ server/cmd/multica/main.go | 39 +++-- .../internal/daemon/execenv/runtime_config.go | 7 + 13 files changed, 191 insertions(+), 21 deletions(-) create mode 100644 server/cmd/multica/help.go diff --git a/server/cmd/multica/cmd_agent.go b/server/cmd/multica/cmd_agent.go index a6ca6c2a..37c0d1a9 100644 --- a/server/cmd/multica/cmd_agent.go +++ b/server/cmd/multica/cmd_agent.go @@ -17,7 +17,7 @@ import ( var agentCmd = &cobra.Command{ Use: "agent", - Short: "Manage agents", + Short: "Work with agents", } var agentListCmd = &cobra.Command{ diff --git a/server/cmd/multica/cmd_attachment.go b/server/cmd/multica/cmd_attachment.go index 69128685..f23f5820 100644 --- a/server/cmd/multica/cmd_attachment.go +++ b/server/cmd/multica/cmd_attachment.go @@ -14,7 +14,7 @@ import ( var attachmentCmd = &cobra.Command{ Use: "attachment", - Short: "Manage attachments", + Short: "Work with attachments", } var attachmentDownloadCmd = &cobra.Command{ diff --git a/server/cmd/multica/cmd_auth.go b/server/cmd/multica/cmd_auth.go index 0c502cdd..3c6f6c90 100644 --- a/server/cmd/multica/cmd_auth.go +++ b/server/cmd/multica/cmd_auth.go @@ -22,7 +22,7 @@ import ( var authCmd = &cobra.Command{ Use: "auth", - Short: "Manage authentication", + Short: "Authenticate multica with Multica", } var authLoginCmd = &cobra.Command{ diff --git a/server/cmd/multica/cmd_config.go b/server/cmd/multica/cmd_config.go index a6cea730..1c9d3a65 100644 --- a/server/cmd/multica/cmd_config.go +++ b/server/cmd/multica/cmd_config.go @@ -11,7 +11,7 @@ import ( var configCmd = &cobra.Command{ Use: "config", - Short: "Show CLI configuration", + Short: "Manage configuration for multica", RunE: runConfigShow, } diff --git a/server/cmd/multica/cmd_daemon.go b/server/cmd/multica/cmd_daemon.go index fc57703a..38863d97 100644 --- a/server/cmd/multica/cmd_daemon.go +++ b/server/cmd/multica/cmd_daemon.go @@ -23,7 +23,7 @@ import ( var daemonCmd = &cobra.Command{ Use: "daemon", - Short: "Manage the local agent runtime daemon", + Short: "Control the local agent runtime daemon", } var daemonStartCmd = &cobra.Command{ diff --git a/server/cmd/multica/cmd_issue.go b/server/cmd/multica/cmd_issue.go index 322cbf1e..b3ed8d19 100644 --- a/server/cmd/multica/cmd_issue.go +++ b/server/cmd/multica/cmd_issue.go @@ -16,7 +16,7 @@ import ( var issueCmd = &cobra.Command{ Use: "issue", - Short: "Manage issues", + Short: "Work with issues", } var issueListCmd = &cobra.Command{ @@ -63,7 +63,7 @@ var issueStatusCmd = &cobra.Command{ var issueCommentCmd = &cobra.Command{ Use: "comment", - Short: "Manage issue comments", + Short: "Work with issue comments", } var issueCommentListCmd = &cobra.Command{ diff --git a/server/cmd/multica/cmd_repo.go b/server/cmd/multica/cmd_repo.go index e5b692ad..3be0efd9 100644 --- a/server/cmd/multica/cmd_repo.go +++ b/server/cmd/multica/cmd_repo.go @@ -14,7 +14,7 @@ import ( var repoCmd = &cobra.Command{ Use: "repo", - Short: "Manage repositories", + Short: "Work with repositories", } var repoCheckoutCmd = &cobra.Command{ diff --git a/server/cmd/multica/cmd_runtime.go b/server/cmd/multica/cmd_runtime.go index 417efec0..12ea8733 100644 --- a/server/cmd/multica/cmd_runtime.go +++ b/server/cmd/multica/cmd_runtime.go @@ -13,7 +13,7 @@ import ( var runtimeCmd = &cobra.Command{ Use: "runtime", - Short: "Manage agent runtimes", + Short: "Work with agent runtimes", } var runtimeListCmd = &cobra.Command{ diff --git a/server/cmd/multica/cmd_skill.go b/server/cmd/multica/cmd_skill.go index f6de394a..5ad0fee8 100644 --- a/server/cmd/multica/cmd_skill.go +++ b/server/cmd/multica/cmd_skill.go @@ -16,7 +16,7 @@ import ( var skillCmd = &cobra.Command{ Use: "skill", - Short: "Manage skills", + Short: "Work with skills", } var skillListCmd = &cobra.Command{ @@ -62,7 +62,7 @@ var skillImportCmd = &cobra.Command{ var skillFilesCmd = &cobra.Command{ Use: "files", - Short: "Manage skill files", + Short: "Work with skill files", } var skillFilesListCmd = &cobra.Command{ diff --git a/server/cmd/multica/cmd_workspace.go b/server/cmd/multica/cmd_workspace.go index 4461ffa4..7470280d 100644 --- a/server/cmd/multica/cmd_workspace.go +++ b/server/cmd/multica/cmd_workspace.go @@ -15,7 +15,7 @@ import ( var workspaceCmd = &cobra.Command{ Use: "workspace", - Short: "Manage workspaces", + Short: "Work with workspaces", } var workspaceListCmd = &cobra.Command{ diff --git a/server/cmd/multica/help.go b/server/cmd/multica/help.go new file mode 100644 index 00000000..2117db9a --- /dev/null +++ b/server/cmd/multica/help.go @@ -0,0 +1,142 @@ +package main + +import ( + "fmt" + "strings" + "text/template" + + "github.com/spf13/cobra" +) + +// Command group IDs used across the CLI. +const ( + groupCore = "core" + groupRuntime = "runtime" + groupAdditional = "additional" +) + +// initHelp configures the root command to use gh-style help output. +func initHelp(root *cobra.Command) { + root.SetHelpTemplate(rootHelpTemplate) + root.SetUsageTemplate(rootHelpTemplate) + root.CompletionOptions.HiddenDefaultCmd = true + + root.AddGroup( + &cobra.Group{ID: groupCore, Title: "CORE COMMANDS"}, + &cobra.Group{ID: groupRuntime, Title: "RUNTIME COMMANDS"}, + &cobra.Group{ID: groupAdditional, Title: "ADDITIONAL COMMANDS"}, + ) + + // Apply gh-style templates to all commands recursively. + applyTemplates(root) +} + +func applyTemplates(cmd *cobra.Command) { + for _, c := range cmd.Commands() { + if c.HasSubCommands() { + c.SetHelpTemplate(subHelpTemplate) + c.SetUsageTemplate(subHelpTemplate) + } else { + c.SetHelpTemplate(leafHelpTemplate) + c.SetUsageTemplate(leafHelpTemplate) + } + applyTemplates(c) + } +} + +// formatCommandList formats a list of commands in "name: description" style +// with automatic alignment, matching gh's output. +func formatCommandList(cmds []*cobra.Command) string { + if len(cmds) == 0 { + return "" + } + + maxLen := 0 + for _, c := range cmds { + if c.IsAvailableCommand() && len(c.Name()) > maxLen { + maxLen = len(c.Name()) + } + } + + var b strings.Builder + for _, c := range cmds { + if !c.IsAvailableCommand() { + continue + } + padding := strings.Repeat(" ", maxLen-len(c.Name())) + fmt.Fprintf(&b, " %s:%s %s\n", c.Name(), padding, c.Short) + } + return b.String() +} + +// commandsInGroup returns commands that belong to a specific group. +func commandsInGroup(cmds []*cobra.Command, groupID string) []*cobra.Command { + var result []*cobra.Command + for _, c := range cmds { + if c.GroupID == groupID && c.IsAvailableCommand() { + result = append(result, c) + } + } + return result +} + +func init() { + cobra.AddTemplateFuncs(template.FuncMap{ + "formatCommandList": formatCommandList, + "commandsInGroup": commandsInGroup, + }) +} + +var rootHelpTemplate = `Work seamlessly with Multica from the command line. + +USAGE + multica [flags] +{{range .Groups}} +{{.Title}} +{{formatCommandList (commandsInGroup $.Commands .ID)}} +{{- end}} +FLAGS +{{.LocalFlags.FlagUsages}} +EXAMPLES + $ multica login + $ multica issue list --output json + $ multica daemon start + $ multica agent list --output json + +ENVIRONMENT VARIABLES + MULTICA_SERVER_URL Override the default server URL + MULTICA_WORKSPACE_ID Set the active workspace + +LEARN MORE + Use ` + "`multica --help`" + ` for more information about a command. +` + +var subHelpTemplate = `{{.Short}} + +USAGE + {{.CommandPath}} [flags] + +COMMANDS +{{formatCommandList .Commands}} +INHERITED FLAGS + --help Show help for command + +LEARN MORE + Use ` + "`{{.CommandPath}} --help`" + ` for more information about a command. +` + +var leafHelpTemplate = `{{if .Long}}{{.Long}}{{else}}{{.Short}}{{end}} + +USAGE + {{.UseLine}} +{{- if .HasLocalFlags}} + +FLAGS +{{.LocalFlags.FlagUsages}} +{{- end}} +INHERITED FLAGS + --help Show help for command + +LEARN MORE + Use ` + "`multica --help`" + ` for more information about a command. +` diff --git a/server/cmd/multica/main.go b/server/cmd/multica/main.go index 53cc903f..b6de18b6 100644 --- a/server/cmd/multica/main.go +++ b/server/cmd/multica/main.go @@ -15,7 +15,7 @@ var ( var rootCmd = &cobra.Command{ Use: "multica", Short: "Multica CLI — local agent runtime and management tool", - Long: "multica manages local agent runtimes and provides control commands for the Multica platform.", + Long: "Work seamlessly with Multica from the command line.", SilenceUsage: true, SilenceErrors: true, } @@ -25,19 +25,40 @@ func init() { rootCmd.PersistentFlags().String("workspace-id", "", "Workspace ID (env: MULTICA_WORKSPACE_ID)") rootCmd.PersistentFlags().String("profile", "", "Configuration profile name (e.g. dev) — isolates config, daemon state, and workspaces") - rootCmd.AddCommand(loginCmd) - rootCmd.AddCommand(authCmd) - rootCmd.AddCommand(daemonCmd) + // Core commands + issueCmd.GroupID = groupCore + agentCmd.GroupID = groupCore + workspaceCmd.GroupID = groupCore + repoCmd.GroupID = groupCore + skillCmd.GroupID = groupCore + + // Runtime commands + daemonCmd.GroupID = groupRuntime + runtimeCmd.GroupID = groupRuntime + + // Additional commands + authCmd.GroupID = groupAdditional + loginCmd.GroupID = groupAdditional + attachmentCmd.GroupID = groupAdditional + configCmd.GroupID = groupAdditional + updateCmd.GroupID = groupAdditional + versionCmd.GroupID = groupAdditional + + rootCmd.AddCommand(issueCmd) rootCmd.AddCommand(agentCmd) rootCmd.AddCommand(workspaceCmd) - rootCmd.AddCommand(configCmd) - rootCmd.AddCommand(issueCmd) - rootCmd.AddCommand(attachmentCmd) rootCmd.AddCommand(repoCmd) - rootCmd.AddCommand(versionCmd) - rootCmd.AddCommand(updateCmd) rootCmd.AddCommand(skillCmd) + rootCmd.AddCommand(daemonCmd) rootCmd.AddCommand(runtimeCmd) + rootCmd.AddCommand(authCmd) + rootCmd.AddCommand(loginCmd) + rootCmd.AddCommand(attachmentCmd) + rootCmd.AddCommand(configCmd) + rootCmd.AddCommand(updateCmd) + rootCmd.AddCommand(versionCmd) + + initHelp(rootCmd) } func main() { diff --git a/server/internal/daemon/execenv/runtime_config.go b/server/internal/daemon/execenv/runtime_config.go index 479a18cf..e9e8f9ce 100644 --- a/server/internal/daemon/execenv/runtime_config.go +++ b/server/internal/daemon/execenv/runtime_config.go @@ -143,6 +143,13 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string { b.WriteString("This downloads the file to the current directory and prints the local path. Use `-o ` to save elsewhere.\n") b.WriteString("After downloading, you can read the file directly (e.g. view an image, read a document).\n\n") + b.WriteString("## Important: Always Use the `multica` CLI\n\n") + b.WriteString("All interactions with Multica platform resources — including issues, comments, attachments, images, files, and any other platform data — **must** go through the `multica` CLI. ") + b.WriteString("Do NOT use `curl`, `wget`, or any other HTTP client to access Multica URLs or APIs directly. ") + b.WriteString("Multica resource URLs require authenticated access that only the `multica` CLI can provide.\n\n") + b.WriteString("If you need to perform an operation that is not covered by any existing `multica` command, ") + b.WriteString("do NOT attempt to work around it. Instead, post a comment mentioning the workspace owner to request the missing functionality.\n\n") + b.WriteString("## Output\n\n") b.WriteString("Keep comments concise and natural — state the outcome, not the process.\n") b.WriteString("Good: \"Fixed the login redirect. PR: https://...\"\n")