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>
98 lines
2.7 KiB
Go
98 lines
2.7 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
"git.dcglab.co.uk/steve/emcli/internal/store"
|
|
"git.dcglab.co.uk/steve/emcli/internal/tui"
|
|
)
|
|
|
|
// runForm launches the bubbletea account form and returns the final model.
|
|
// This is interactive terminal glue (not unit-tested); all logic it relies on
|
|
// lives in the tui and store packages, which are tested.
|
|
func runForm(initial tui.Fields, editing bool) (tui.AccountForm, error) {
|
|
p := tea.NewProgram(tui.NewAccountForm(initial, editing))
|
|
m, err := p.Run()
|
|
if err != nil {
|
|
return tui.AccountForm{}, err
|
|
}
|
|
return m.(tui.AccountForm), nil
|
|
}
|
|
|
|
// addInteractive runs the form for a new account and persists it.
|
|
func addInteractive(st *store.Store, initial tui.Fields, out, errOut io.Writer) int {
|
|
form, err := runForm(initial, false)
|
|
if err != nil {
|
|
fmt.Fprintf(errOut, "form: %v\n", err)
|
|
return 1
|
|
}
|
|
if form.Cancelled() {
|
|
fmt.Fprintln(out, "cancelled")
|
|
return 1
|
|
}
|
|
acc := form.Account()
|
|
if _, err := st.AddAccount(acc); err != nil {
|
|
fmt.Fprintf(errOut, "add account: %v\n", err)
|
|
return 1
|
|
}
|
|
fmt.Fprintf(out, "account %q added (%s)\n", acc.Name, acc.Mode)
|
|
return 0
|
|
}
|
|
|
|
// editInteractive runs the form prefilled from an existing account and saves it.
|
|
func editInteractive(st *store.Store, name string, out, errOut io.Writer) int {
|
|
acc, err := st.GetAccount(name)
|
|
if err != nil {
|
|
fmt.Fprintf(errOut, "edit: %v\n", err)
|
|
return 1
|
|
}
|
|
form, err := runForm(tui.FieldsFromAccount(acc), true)
|
|
if err != nil {
|
|
fmt.Fprintf(errOut, "form: %v\n", err)
|
|
return 1
|
|
}
|
|
if form.Cancelled() {
|
|
fmt.Fprintln(out, "cancelled")
|
|
return 1
|
|
}
|
|
updated := form.Account()
|
|
if !form.PasswordSet() {
|
|
updated.Password = "" // blank ⇒ UpdateAccount keeps the existing password
|
|
}
|
|
if err := st.UpdateAccount(updated); err != nil {
|
|
fmt.Fprintf(errOut, "edit: %v\n", err)
|
|
return 1
|
|
}
|
|
fmt.Fprintf(out, "account %q updated\n", name)
|
|
return 0
|
|
}
|
|
|
|
// runInit creates/opens the DB and adds the first account via the TUI form,
|
|
// seeding a default audit retention if unset.
|
|
func runInit(args []string, out, errOut io.Writer) int {
|
|
if len(args) > 0 && helpRequested(args[0]) {
|
|
printCmdUsage(out, "init")
|
|
return 0
|
|
}
|
|
st, err := openStore()
|
|
if err != nil {
|
|
fmt.Fprintf(errOut, "emcli: %v\n", err)
|
|
return 1
|
|
}
|
|
defer st.Close()
|
|
|
|
if _, err := st.GetSetting("audit_retention_days"); err != nil {
|
|
_ = st.SetSetting("audit_retention_days", "90")
|
|
}
|
|
accs, _ := st.ListAccounts()
|
|
if len(accs) > 0 {
|
|
fmt.Fprintf(out, "emcli is already initialized (%d account(s)); adding another.\n", len(accs))
|
|
} else {
|
|
fmt.Fprintln(out, "Initializing emcli — add your first account.")
|
|
}
|
|
return addInteractive(st, tui.Fields{}, out, errOut)
|
|
}
|