refactor(cli): improve help UX — add examples support, show help on arg errors

- Add EXAMPLES section to leaf and sub help templates (gh CLI style)
- Add example to attachment download command
- Simplify attachment download description
- Show help output when required args are missing (error first, then help)
- Replace cobra.ExactArgs with custom exactArgs that prints help on failure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
yushen 2026-04-05 07:00:19 +08:00
parent d6a5ba4d5e
commit 4c0dbbf1c8
10 changed files with 70 additions and 33 deletions

View file

@ -29,7 +29,7 @@ var agentListCmd = &cobra.Command{
var agentGetCmd = &cobra.Command{
Use: "get <id>",
Short: "Get agent details",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runAgentGet,
}
@ -42,28 +42,28 @@ var agentCreateCmd = &cobra.Command{
var agentUpdateCmd = &cobra.Command{
Use: "update <id>",
Short: "Update an agent",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runAgentUpdate,
}
var agentArchiveCmd = &cobra.Command{
Use: "archive <id>",
Short: "Archive an agent",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runAgentArchive,
}
var agentRestoreCmd = &cobra.Command{
Use: "restore <id>",
Short: "Restore an archived agent",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runAgentRestore,
}
var agentTasksCmd = &cobra.Command{
Use: "tasks <id>",
Short: "List tasks for an agent",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runAgentTasks,
}
@ -77,14 +77,14 @@ var agentSkillsCmd = &cobra.Command{
var agentSkillsListCmd = &cobra.Command{
Use: "list <agent-id>",
Short: "List skills assigned to an agent",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runAgentSkillsList,
}
var agentSkillsSetCmd = &cobra.Command{
Use: "set <agent-id>",
Short: "Set skills for an agent (replaces all current assignments)",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runAgentSkillsSet,
}

View file

@ -20,8 +20,13 @@ var attachmentCmd = &cobra.Command{
var attachmentDownloadCmd = &cobra.Command{
Use: "download <attachment-id>",
Short: "Download an attachment to a local file",
Long: "Fetches the attachment metadata from the API, then downloads the file using its signed URL. Prints the local file path on success.",
Args: cobra.ExactArgs(1),
Long: "Download an attachment by its ID to a local file.",
Example: ` # Download an image attachment to the current directory
$ multica attachment download abc123
# Download to a specific directory
$ multica attachment download abc123 -o /tmp/images`,
Args: exactArgs(1),
RunE: runAttachmentDownload,
}

View file

@ -25,7 +25,7 @@ var configSetCmd = &cobra.Command{
Use: "set <key> <value>",
Short: "Set a CLI configuration value",
Long: "Supported keys: server_url, app_url, workspace_id",
Args: cobra.ExactArgs(2),
Args: exactArgs(2),
RunE: runConfigSet,
}

View file

@ -28,7 +28,7 @@ var issueListCmd = &cobra.Command{
var issueGetCmd = &cobra.Command{
Use: "get <id>",
Short: "Get issue details",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runIssueGet,
}
@ -41,21 +41,21 @@ var issueCreateCmd = &cobra.Command{
var issueUpdateCmd = &cobra.Command{
Use: "update <id>",
Short: "Update an issue",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runIssueUpdate,
}
var issueAssignCmd = &cobra.Command{
Use: "assign <id>",
Short: "Assign an issue to a member or agent",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runIssueAssign,
}
var issueStatusCmd = &cobra.Command{
Use: "status <id> <status>",
Short: "Change issue status",
Args: cobra.ExactArgs(2),
Args: exactArgs(2),
RunE: runIssueStatus,
}
@ -69,21 +69,21 @@ var issueCommentCmd = &cobra.Command{
var issueCommentListCmd = &cobra.Command{
Use: "list <issue-id>",
Short: "List comments on an issue",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runIssueCommentList,
}
var issueCommentAddCmd = &cobra.Command{
Use: "add <issue-id>",
Short: "Add a comment to an issue",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runIssueCommentAdd,
}
var issueCommentDeleteCmd = &cobra.Command{
Use: "delete <comment-id>",
Short: "Delete a comment",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runIssueCommentDelete,
}
@ -92,14 +92,14 @@ var issueCommentDeleteCmd = &cobra.Command{
var issueRunsCmd = &cobra.Command{
Use: "runs <issue-id>",
Short: "List execution history for an issue",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runIssueRuns,
}
var issueRunMessagesCmd = &cobra.Command{
Use: "run-messages <task-id>",
Short: "List messages for an execution",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runIssueRunMessages,
}

View file

@ -21,7 +21,7 @@ var repoCheckoutCmd = &cobra.Command{
Use: "checkout <url>",
Short: "Check out a repository into the working directory",
Long: "Creates a git worktree from the daemon's bare clone cache. Used by agents to check out repos on demand.",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runRepoCheckout,
}

View file

@ -25,28 +25,28 @@ var runtimeListCmd = &cobra.Command{
var runtimeUsageCmd = &cobra.Command{
Use: "usage <runtime-id>",
Short: "Get token usage for a runtime",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runRuntimeUsage,
}
var runtimeActivityCmd = &cobra.Command{
Use: "activity <runtime-id>",
Short: "Get hourly task activity for a runtime",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runRuntimeActivity,
}
var runtimePingCmd = &cobra.Command{
Use: "ping <runtime-id>",
Short: "Ping a runtime to check connectivity",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runRuntimePing,
}
var runtimeUpdateCmd = &cobra.Command{
Use: "update <runtime-id>",
Short: "Initiate a CLI update on a runtime",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runRuntimeUpdate,
}

View file

@ -28,7 +28,7 @@ var skillListCmd = &cobra.Command{
var skillGetCmd = &cobra.Command{
Use: "get <id>",
Short: "Get skill details (includes files)",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runSkillGet,
}
@ -41,14 +41,14 @@ var skillCreateCmd = &cobra.Command{
var skillUpdateCmd = &cobra.Command{
Use: "update <id>",
Short: "Update a skill",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runSkillUpdate,
}
var skillDeleteCmd = &cobra.Command{
Use: "delete <id>",
Short: "Delete a skill",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runSkillDelete,
}
@ -68,21 +68,21 @@ var skillFilesCmd = &cobra.Command{
var skillFilesListCmd = &cobra.Command{
Use: "list <skill-id>",
Short: "List files for a skill",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runSkillFilesList,
}
var skillFilesUpsertCmd = &cobra.Command{
Use: "upsert <skill-id>",
Short: "Create or update a skill file",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runSkillFilesUpsert,
}
var skillFilesDeleteCmd = &cobra.Command{
Use: "delete <skill-id> <file-id>",
Short: "Delete a skill file",
Args: cobra.ExactArgs(2),
Args: exactArgs(2),
RunE: runSkillFilesDelete,
}

View file

@ -41,14 +41,14 @@ var workspaceMembersCmd = &cobra.Command{
var workspaceWatchCmd = &cobra.Command{
Use: "watch <workspace-id>",
Short: "Add a workspace to the daemon watch list",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runWatch,
}
var workspaceUnwatchCmd = &cobra.Command{
Use: "unwatch <workspace-id>",
Short: "Remove a workspace from the daemon watch list",
Args: cobra.ExactArgs(1),
Args: exactArgs(1),
RunE: runUnwatch,
}

View file

@ -15,6 +15,26 @@ const (
groupAdditional = "additional"
)
// errSilent is returned when the error message has already been printed.
var errSilent = fmt.Errorf("")
// exactArgs returns a cobra.PositionalArgs that validates the arg count
// and prints help on failure, so users see usage context with the error.
func exactArgs(n int) cobra.PositionalArgs {
return func(cmd *cobra.Command, args []string) error {
if len(args) != n {
if n == 1 {
fmt.Fprintf(cmd.ErrOrStderr(), "Error: accepts 1 arg, received %d\n\n", len(args))
} else {
fmt.Fprintf(cmd.ErrOrStderr(), "Error: accepts %d args, received %d\n\n", n, len(args))
}
cmd.Help()
return errSilent
}
return nil
}
}
// initHelp configures the root command to use gh-style help output.
func initHelp(root *cobra.Command) {
root.SetHelpTemplate(rootHelpTemplate)
@ -120,6 +140,11 @@ COMMANDS
{{formatCommandList .Commands}}
INHERITED FLAGS
--help Show help for command
{{- if .Example}}
EXAMPLES
{{.Example}}
{{- end}}
LEARN MORE
Use ` + "`{{.CommandPath}} <command> --help`" + ` for more information about a command.
@ -136,6 +161,11 @@ FLAGS
{{- end}}
INHERITED FLAGS
--help Show help for command
{{- if .Example}}
EXAMPLES
{{.Example}}
{{- end}}
LEARN MORE
Use ` + "`multica <command> <subcommand> --help`" + ` for more information about a command.

View file

@ -63,7 +63,9 @@ func init() {
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
if err != errSilent {
fmt.Fprintln(os.Stderr, "Error:", err)
}
os.Exit(1)
}
}