feat(cli): add help for all commands
emcli had only raw flag usage and no command listing; `--help` on agent commands even emitted a JSON error envelope and exited 2. Add real help: - Top-level `emcli` / `help` / `-h` / `--help` prints a grouped command catalogue (agent vs admin) with one-line summaries and the EMCLI_KEY/EMCLI_DB env vars. - `emcli help <command>` prints that command's synopsis + summary. - `emcli <command> --help` prints synopsis + summary + flags and exits 0. Agent commands keep stdout JSON-free (usage goes to stderr); admin commands print to stdout. Help works without EMCLI_KEY (no DB access). - help.go holds the command catalogue; flag.ErrHelp is handled as success, and admin handlers short-circuit help before opening the store. Unknown commands still error (exit 2). Full suite passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+21
-3
@@ -1,6 +1,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -70,8 +71,12 @@ func newDepsLive(st *store.Store, out io.Writer) Deps {
|
||||
func runDoctor(args []string, out, errOut io.Writer) int {
|
||||
fs := flag.NewFlagSet("doctor", flag.ContinueOnError)
|
||||
fs.SetOutput(errOut)
|
||||
usageFlags(fs, "doctor", errOut)
|
||||
account := fs.String("account", "", "check only this account")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return 0
|
||||
}
|
||||
return 2
|
||||
}
|
||||
st, err := openStore()
|
||||
@@ -89,9 +94,14 @@ func runDoctor(args []string, out, errOut io.Writer) int {
|
||||
|
||||
// Run routes a command line and returns an exit code.
|
||||
func Run(args []string, out, errOut io.Writer) int {
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintln(errOut, "emcli: no command given")
|
||||
return 2
|
||||
if len(args) == 0 || helpRequested(args[0]) {
|
||||
// `emcli`, `emcli help`, `emcli -h`, `emcli --help`, and `emcli help <cmd>`.
|
||||
if len(args) >= 2 {
|
||||
printCmdUsage(out, args[1])
|
||||
} else {
|
||||
printMainHelp(out)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
cmd, rest := args[0], args[1:]
|
||||
switch cmd {
|
||||
@@ -121,6 +131,7 @@ func Run(args []string, out, errOut io.Writer) int {
|
||||
func runAgent(cmd string, args []string, out, errOut io.Writer) int {
|
||||
fs := flag.NewFlagSet(cmd, flag.ContinueOnError)
|
||||
fs.SetOutput(errOut)
|
||||
usageFlags(fs, cmd, errOut)
|
||||
account := fs.String("account", "", "account name")
|
||||
folder := fs.String("folder", "INBOX", "folder/mailbox")
|
||||
onlyNew := fs.Bool("new", false, "only new (unacked) messages")
|
||||
@@ -135,6 +146,9 @@ func runAgent(cmd string, args []string, out, errOut io.Writer) int {
|
||||
beforeDate := fs.String("before-date", "", "search: RFC3339 date upper bound")
|
||||
ackUIDs := fs.String("uid-list", "", "ack: comma-separated UIDs")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return 0 // usage already printed to stderr; help isn't an error
|
||||
}
|
||||
_ = Failure(CodeUsage, err.Error()).Write(out)
|
||||
return 2
|
||||
}
|
||||
@@ -213,6 +227,7 @@ func (s *stringSlice) Set(v string) error {
|
||||
func runSend(args []string, out, errOut io.Writer) int {
|
||||
fs := flag.NewFlagSet("send", flag.ContinueOnError)
|
||||
fs.SetOutput(errOut)
|
||||
usageFlags(fs, "send", errOut)
|
||||
account := fs.String("account", "", "account name")
|
||||
var to, cc, bcc, attach stringSlice
|
||||
fs.Var(&to, "to", "recipient (repeatable / comma-separated)")
|
||||
@@ -224,6 +239,9 @@ func runSend(args []string, out, errOut io.Writer) int {
|
||||
replyTo := fs.Uint("reply-to", 0, "source UID to reply to (threading)")
|
||||
folder := fs.String("folder", "INBOX", "folder of the reply source")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return 0
|
||||
}
|
||||
_ = Failure(CodeUsage, err.Error()).Write(out)
|
||||
return 2
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user