BLUF
Several band commands require 15–29 hand-typed flags, many of them unguessable enums. Add an opt-in interactive prompt layer (internal/prompt) that fills missing required fields when — and only when — a human is driving. Agents and scripts must see byte-identical behavior to today.
Why
It isn't the field count that hurts, it's the enums. Registering a 10DLC brand means knowing that vertical is one of 12 values, brandType one of 5, stockExchange one of 12; a campaign needs usecase from 12. None of that is guessable from --help without a doc round-trip. Same story for band tfv submit — 16 required flags including an entity-type enum.
Concretely, today:
band tendlc brand create \
--customer-profile-id 3IIzIFnRRQBE3AMzPpMTNo --brand-type PRIVATE_PROFIT \
--display-name "Acme Corp" --company-name "Acme Corporation, Inc." \
--ein 21-4573154 --ein-country USA --phone +18005551234 \
--email ops@acme.com --street "123 Main St" --city Raleigh \
--state NC --postal-code 27601 --country USA --vertical RETAIL
Fourteen required flags, three enums, and cobra reports missing flags one at a time — so a human discovers the shape of this command through roughly a dozen sequential failures.
Scope
New internal/prompt package, no new dependencies. golang.org/x/term and cmdutil.IsInteractive() are already in the tree, and cmd/account/register.go establishes the hand-rolled bufio + internal/ui pattern.
Surface, roughly:
AskRequired(label, validate) — re-asks until valid
AskOptional(label, default)
AskEnum(label, values) — numbered picker
AskBool(label, default)
Confirm(summary) — final review before the write
Then a ~15-line hook per command. Retrofit targets, in priority order:
The trigger (explicit, not inferred)
The wizard engages only when --interactive is passed. Missing required flags without it fail immediately and completely, exactly as today.
An earlier draft of this issue proposed auto-detecting "human is driving" from TTY + missing flags + output format. That is wrong on two counts:
--format defaults to json (cmd/root.go:93), so a "not --format json" condition would disable the wizard everywhere unless it distinguished an explicitly-set flag from its default. The repo already has the right primitive for this — cmd/root.go:71 checks .Changed — but the gate as written would have silently never fired.
- A TTY does not mean a human. Agents routinely run inside pseudo-TTYs. Auto-prompting would hang the exact first-class user this CLI is built for, and a hang is worse than any error.
If auto-detection is ever revisited, it must require all of: stdin and stderr are TTYs, no --plain, stdout not piped, no CI environment marker, plus a persistent --no-input escape hatch. Explicit opt-in is the safer default and costs the user seven characters.
Agent-nativity requirements
This layer exists for humans and must be invisible to everything else.
- All prompt output goes to stderr. Stdout stays reserved for structured data — this is already the house pattern in
cmd/account/register.go.
- Piping stdout must never change what lands on stdout.
- No prompt may be the only way to set a field. Every field reachable interactively must have a flag, so any wizard session is expressible as a one-line non-interactive command.
- On completion the wizard should echo the equivalent full
band ... invocation to stderr, so a human can copy it into a script or hand it to an agent.
--no-input force-disables the wizard even on a TTY, for CI that allocates a pty.
- Doc-contract test (
cmd/doccontract_test.go) still applies: any command shown in README/AGENTS.md must resolve.
Dependency
Blocked on the 10DLC direct / Registration Center work, which creates band tendlc brand create, band tendlc campaign create, and band customer-profile create. Design decision recorded there: flags first, wizard after.
Two structural requirements land with that work so this issue is a bolt-on rather than a refactor:
- Typed option structs per command, with
Validate(opts, changed) / BuildRequest(opts) / Execute(ctx, opts) split out. Flags populate the struct; the wizard populates the same struct. Neither validation nor request-building may know where the values came from. Field metadata — enums, help text, conditional requirements — lives in one place, not duplicated between flag registration and prompts.
- No
MarkFlagRequired on wizard-capable commands. Cobra rejects missing required flags before RunE runs, so the wizard would never get a chance to prompt. Required-ness is enforced by custom aggregation inside RunE instead, which also lets us emit one deterministic error listing every missing flag (exit 6, ExitFlagError) rather than cobra's one-at-a-time.
Whether a flag was explicitly supplied must be preserved — false, 0, and "" are legitimate values for several of these fields.
Not in scope
- Full-screen TUI / bubbletea. Sequential stderr prompts only.
- Prompting for anything that isn't a missing required field.
- Editing existing resources interactively (
update commands stay flags-only for now).
BLUF
Several
bandcommands require 15–29 hand-typed flags, many of them unguessable enums. Add an opt-in interactive prompt layer (internal/prompt) that fills missing required fields when — and only when — a human is driving. Agents and scripts must see byte-identical behavior to today.Why
It isn't the field count that hurts, it's the enums. Registering a 10DLC brand means knowing that
verticalis one of 12 values,brandTypeone of 5,stockExchangeone of 12; a campaign needsusecasefrom 12. None of that is guessable from--helpwithout a doc round-trip. Same story forband tfv submit— 16 required flags including anentity-typeenum.Concretely, today:
Fourteen required flags, three enums, and cobra reports missing flags one at a time — so a human discovers the shape of this command through roughly a dozen sequential failures.
Scope
New
internal/promptpackage, no new dependencies.golang.org/x/termandcmdutil.IsInteractive()are already in the tree, andcmd/account/register.goestablishes the hand-rolledbufio+internal/uipattern.Surface, roughly:
AskRequired(label, validate)— re-asks until validAskOptional(label, default)AskEnum(label, values)— numbered pickerAskBool(label, default)Confirm(summary)— final review before the writeThen a ~15-line hook per command. Retrofit targets, in priority order:
band tendlc brand create— 14 required / 10 optional, 3 enumsband tendlc campaign create— 7 required / 22 optional, 1 enumband tfv submit— 16 required, 1 enumband customer-profile createband portin create— 25 flags, evaluate after the first three landThe trigger (explicit, not inferred)
The wizard engages only when
--interactiveis passed. Missing required flags without it fail immediately and completely, exactly as today.An earlier draft of this issue proposed auto-detecting "human is driving" from TTY + missing flags + output format. That is wrong on two counts:
--formatdefaults tojson(cmd/root.go:93), so a "not--format json" condition would disable the wizard everywhere unless it distinguished an explicitly-set flag from its default. The repo already has the right primitive for this —cmd/root.go:71checks.Changed— but the gate as written would have silently never fired.If auto-detection is ever revisited, it must require all of: stdin and stderr are TTYs, no
--plain, stdout not piped, no CI environment marker, plus a persistent--no-inputescape hatch. Explicit opt-in is the safer default and costs the user seven characters.Agent-nativity requirements
This layer exists for humans and must be invisible to everything else.
cmd/account/register.go.band ...invocation to stderr, so a human can copy it into a script or hand it to an agent.--no-inputforce-disables the wizard even on a TTY, for CI that allocates a pty.cmd/doccontract_test.go) still applies: any command shown in README/AGENTS.md must resolve.Dependency
Blocked on the 10DLC direct / Registration Center work, which creates
band tendlc brand create,band tendlc campaign create, andband customer-profile create. Design decision recorded there: flags first, wizard after.Two structural requirements land with that work so this issue is a bolt-on rather than a refactor:
Validate(opts, changed)/BuildRequest(opts)/Execute(ctx, opts)split out. Flags populate the struct; the wizard populates the same struct. Neither validation nor request-building may know where the values came from. Field metadata — enums, help text, conditional requirements — lives in one place, not duplicated between flag registration and prompts.MarkFlagRequiredon wizard-capable commands. Cobra rejects missing required flags beforeRunEruns, so the wizard would never get a chance to prompt. Required-ness is enforced by custom aggregation insideRunEinstead, which also lets us emit one deterministic error listing every missing flag (exit 6,ExitFlagError) rather than cobra's one-at-a-time.Whether a flag was explicitly supplied must be preserved —
false,0, and""are legitimate values for several of these fields.Not in scope
updatecommands stay flags-only for now).