Skip to content

Interactive prompt layer for high-field-count commands (tendlc, tfv, customer-profile) #23

Description

@kshahbw

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:

  • band tendlc brand create — 14 required / 10 optional, 3 enums
  • band tendlc campaign create — 7 required / 22 optional, 1 enum
  • band tfv submit — 16 required, 1 enum
  • band customer-profile create
  • band portin create — 25 flags, evaluate after the first three land

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:

  1. --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.
  2. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions