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:
2026-06-22 21:11:40 +01:00
parent 7087533644
commit 1b2fe99055
6 changed files with 209 additions and 5 deletions
+73
View File
@@ -0,0 +1,73 @@
package cli
import (
"strings"
"testing"
)
func TestMainHelpListsAllCommands(t *testing.T) {
// help / --help / -h / no-args all print the command catalogue, exit 0,
// and require no EMCLI_KEY (help must work before any DB access).
for _, args := range [][]string{{"help"}, {"--help"}, {"-h"}, {}} {
code, out, errOut := run(t, args...)
text := out + errOut
if code != 0 {
t.Fatalf("%v: want exit 0, got %d\n%s", args, code, text)
}
for _, want := range []string{
"Usage", "list", "get", "search", "ack", "send",
"account", "whitelist", "config", "audit", "doctor", "version",
"EMCLI_KEY", "EMCLI_DB",
} {
if !strings.Contains(text, want) {
t.Fatalf("%v: help missing %q\n%s", args, want, text)
}
}
}
}
func TestHelpForSpecificCommand(t *testing.T) {
code, out, errOut := run(t, "help", "send")
text := out + errOut
if code != 0 {
t.Fatalf("help send exit=%d", code)
}
if !strings.Contains(text, "Usage: emcli send") || !strings.Contains(text, "--to") {
t.Fatalf("help send missing synopsis:\n%s", text)
}
}
func TestAgentHelpDoesNotEmitJSON(t *testing.T) {
// `list --help` must NOT print a JSON envelope on stdout (an agent parses
// stdout) and must exit 0 — even with no EMCLI_KEY set.
code, out, errOut := run(t, "list", "--help")
if code != 0 {
t.Fatalf("list --help exit=%d (out=%q err=%q)", code, out, errOut)
}
if strings.TrimSpace(out) != "" {
t.Fatalf("agent help must keep stdout clean, got: %q", out)
}
if !strings.Contains(errOut, "Usage: emcli list") || !strings.Contains(errOut, "--account") {
t.Fatalf("list --help should print usage+flags on stderr:\n%s", errOut)
}
}
func TestSendHelpExitsZero(t *testing.T) {
code, _, errOut := run(t, "send", "--help")
if code != 0 || !strings.Contains(errOut, "--to") {
t.Fatalf("send --help: code=%d err=%q", code, errOut)
}
}
func TestAdminCommandHelpExitsZero(t *testing.T) {
for _, c := range []string{"account", "whitelist", "config", "audit", "doctor"} {
code, out, errOut := run(t, c, "--help")
text := out + errOut
if code != 0 {
t.Fatalf("%s --help exit=%d\n%s", c, code, text)
}
if !strings.Contains(text, "Usage: emcli "+c) {
t.Fatalf("%s --help missing usage line:\n%s", c, text)
}
}
}