1b2fe99055
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>
84 lines
3.5 KiB
Go
84 lines
3.5 KiB
Go
package cli
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
type cmdHelp struct {
|
|
name string
|
|
synopsis string
|
|
summary string
|
|
}
|
|
|
|
// agentCmds emit machine-readable JSON; adminCmds are human-readable.
|
|
var agentCmds = []cmdHelp{
|
|
{"list", "list --account <name> [--folder F] [--new] [--limit N] [--before U] [--since U]", "List message headers, newest first."},
|
|
{"get", "get --account <name> [--folder F] --uid <uid>", "Fetch one full message (body + attachments)."},
|
|
{"search", "search --account <name> [--folder F] [--from A] [--subject-contains S] [--text S] [--since-date D] [--before-date D] [--limit N]", "Server-side IMAP search."},
|
|
{"ack", "ack --account <name> [--folder F] --uid-list U1,U2,…", "Mark message(s) processed."},
|
|
{"send", "send --account <name> --to A… [--cc A…] [--bcc A…] --subject S --body B [--attach P]… [--reply-to U [--folder F]]", "Send or reply (RW accounts only)."},
|
|
}
|
|
|
|
var adminCmds = []cmdHelp{
|
|
{"init", "init", "Create the database and add the first account (interactive)."},
|
|
{"account", "account <add|edit|remove|list> [flags]", "Manage accounts (add/edit accept flags, or run with none for an interactive form)."},
|
|
{"whitelist", "whitelist <in|out> <add|remove|list> --account <name> [--address A]", "Manage inbound/outbound whitelists."},
|
|
{"config", "config <set|get> <key> [value]", "Get or set global settings (e.g. audit_retention_days)."},
|
|
{"audit", "audit list [--account <name>] [--limit N]", "Show recent audit-log entries."},
|
|
{"doctor", "doctor [--account <name>]", "Check each account's IMAP/SMTP connectivity and auth."},
|
|
{"version", "version", "Print the emcli version."},
|
|
{"help", "help [command]", "Show this help, or detailed usage for one command."},
|
|
}
|
|
|
|
func helpIndex() map[string]cmdHelp {
|
|
m := make(map[string]cmdHelp, len(agentCmds)+len(adminCmds))
|
|
for _, c := range append(append([]cmdHelp{}, agentCmds...), adminCmds...) {
|
|
m[c.name] = c
|
|
}
|
|
return m
|
|
}
|
|
|
|
// helpRequested reports whether an argument is a help flag/word.
|
|
func helpRequested(s string) bool {
|
|
return s == "help" || s == "-h" || s == "--help"
|
|
}
|
|
|
|
// printMainHelp writes the top-level command catalogue.
|
|
func printMainHelp(w io.Writer) {
|
|
fmt.Fprint(w, "emcli — guard-railed email gateway for agents\n\n")
|
|
fmt.Fprint(w, "Usage:\n emcli <command> [flags]\n\n")
|
|
fmt.Fprint(w, "Agent commands (machine-readable JSON on stdout):\n")
|
|
for _, c := range agentCmds {
|
|
fmt.Fprintf(w, " %-10s %s\n", c.name, c.summary)
|
|
}
|
|
fmt.Fprint(w, "\nAdmin commands (human-readable):\n")
|
|
for _, c := range adminCmds {
|
|
fmt.Fprintf(w, " %-10s %s\n", c.name, c.summary)
|
|
}
|
|
fmt.Fprint(w, "\nRun \"emcli <command> --help\" for a command's flags.\n")
|
|
fmt.Fprint(w, "\nEnvironment:\n")
|
|
fmt.Fprint(w, " EMCLI_KEY base64-encoded 32-byte AES key; required for any command that uses the database\n")
|
|
fmt.Fprint(w, " EMCLI_DB database path (default ~/.config/emcli/emcli.db; %AppData%\\emcli\\emcli.db on Windows)\n")
|
|
}
|
|
|
|
// printCmdUsage writes "Usage: emcli <synopsis>" and the summary for one command.
|
|
func printCmdUsage(w io.Writer, name string) {
|
|
if h, ok := helpIndex()[name]; ok {
|
|
fmt.Fprintf(w, "Usage: emcli %s\n\n%s\n", h.synopsis, h.summary)
|
|
return
|
|
}
|
|
fmt.Fprintf(w, "Usage: emcli %s\n", name)
|
|
}
|
|
|
|
// usageFlags makes a flag set print the command's synopsis/summary followed by
|
|
// its flags whenever flag prints usage (on -h/--help or a flag error).
|
|
func usageFlags(fs *flag.FlagSet, name string, w io.Writer) {
|
|
fs.Usage = func() {
|
|
printCmdUsage(w, name)
|
|
fmt.Fprintln(w, "\nFlags:")
|
|
fs.PrintDefaults()
|
|
}
|
|
}
|