feat(tpagectl): add interactive mode #606

Merged
fuzzy merged 1 commit from feat/tpagectl-interactive into main 2026-08-08 11:44:11 +00:00
Owner

What

Implements Phase 7 interactive mode (roadmap #276).

  • New internal/tpagectl/interactive — active only when both stdin and stdout are terminals (a clean gate that keeps all tests and piped output on the existing non-interactive paths). Provides:
    • Prompt / ResolveValue — free-text prompts for omitted required values (#274).
    • ResolveSite — lists registered sites and lets the user pick via a fuzzy finder (#275): live case-insensitive subsequence filter (position-ranked), arrow-key navigation, Enter confirms, Esc/Ctrl-C cancels, raw-mode stdin via x/term, rendered with konsoru cursor/screen/style, keys parsed with konsoru/input.
  • Prompting (#274)site create fills in missing --name/--owner/--visibility interactively (the urfave Required flags are relaxed; non-interactive missing values now fall through to the existing buildCreateRequest validation). deployment get prompts for --version/--latest; preview get/deploy/delete prompt for --branch.
  • Fuzzy site selection (#275) — wired into every site-taking command: site get/delete/update, deployment list/get/promote/rollback, preview list/get/deploy/delete. Omitted values without a terminal still return the exact same validation errors.

Why

Roadmap task #276, the last item in Phase 7.

Testing

  • interactive unit tests: subsequence filter (case-insensitive, position-ranked, empty query), subsequenceAt, selectInteractive fed synthetic key bytes ("bl\r" confirms "blog", "\x1b[B\r" arrow-down selects index 1, "\x1b" cancels), Enabled false for buffered writer, ResolveValue provided-wins and non-interactive validation, Prompt
  • Existing command tests pass unchanged (non-interactive fallback); site create required-flag test updated to the new validation message
  • go test -race ./... passes (28 packages)
  • golangci-lint run clean
  • pre-commit hooks pass (gofmt, go mod tidy, go test)

Breaking Changes

None for scripting: without a terminal the behavior and messages are identical. site create no longer relies on urfave's Required: true for the three flags (same validation, now with interactive prompting).

Notes

konsoru/input added to the vendor tree. True TTY end-to-end interaction is not unit-tested (no pty dependency); the io.Reader-based core plus the both-terminals gate give deterministic coverage.

Closes #274
Closes #275
Closes #276

## What Implements Phase 7 interactive mode (roadmap #276). - **New `internal/tpagectl/interactive`** — active only when **both** stdin and stdout are terminals (a clean gate that keeps all tests and piped output on the existing non-interactive paths). Provides: - `Prompt` / `ResolveValue` — free-text prompts for omitted required values (#274). - `ResolveSite` — lists registered sites and lets the user pick via a **fuzzy finder** (#275): live case-insensitive subsequence filter (position-ranked), arrow-key navigation, Enter confirms, Esc/Ctrl-C cancels, raw-mode stdin via `x/term`, rendered with konsoru `cursor`/`screen`/`style`, keys parsed with `konsoru/input`. - **Prompting (#274)** — `site create` fills in missing `--name/--owner/--visibility` interactively (the urfave `Required` flags are relaxed; non-interactive missing values now fall through to the existing `buildCreateRequest` validation). `deployment get` prompts for `--version`/`--latest`; `preview get/deploy/delete` prompt for `--branch`. - **Fuzzy site selection (#275)** — wired into every site-taking command: `site get/delete/update`, `deployment list/get/promote/rollback`, `preview list/get/deploy/delete`. Omitted values without a terminal still return the exact same validation errors. ## Why Roadmap task #276, the last item in Phase 7. ## Testing - [x] `interactive` unit tests: subsequence filter (case-insensitive, position-ranked, empty query), `subsequenceAt`, `selectInteractive` fed synthetic key bytes (`"bl\r"` confirms "blog", `"\x1b[B\r"` arrow-down selects index 1, `"\x1b"` cancels), `Enabled` false for buffered writer, `ResolveValue` provided-wins and non-interactive validation, `Prompt` - [x] Existing command tests pass unchanged (non-interactive fallback); `site create` required-flag test updated to the new validation message - [x] `go test -race ./...` passes (28 packages) - [x] `golangci-lint run` clean - [x] pre-commit hooks pass (gofmt, go mod tidy, go test) ## Breaking Changes None for scripting: without a terminal the behavior and messages are identical. `site create` no longer relies on urfave's `Required: true` for the three flags (same validation, now with interactive prompting). ## Notes `konsoru/input` added to the vendor tree. True TTY end-to-end interaction is not unit-tested (no pty dependency); the `io.Reader`-based core plus the both-terminals gate give deterministic coverage. Closes #274 Closes #275 Closes #276
feat(tpagectl): add interactive mode
All checks were successful
Test and Release / lint (pull_request) Successful in 4m32s
Test and Release / test (pull_request) Successful in 24m22s
5f5e501aa5
Add internal/tpagectl/interactive, active only when both stdin and stdout
are terminals, so redirected and piped output keeps the existing
non-interactive error paths exactly.

- Prompts (#274): interactive.ResolveValue prompts for omitted required
  values (branch, version); site create fills in missing --name/--owner/
  --visibility interactively (relaxing the urfave Required flags).
- Fuzzy site picker (#275): interactive.ResolveSite lists registered sites
  and lets the user pick via a konsoru-input fuzzy finder (live
  case-insensitive subsequence filter, arrow-key navigation, Enter
  confirms, Esc/Ctrl-C cancels, raw-mode stdin).

Wired into every site-taking command (site get/delete/update, deployment
list/get/promote/rollback, preview list/get/deploy/delete); omitted values
without a terminal still return the same validation errors. konsoru/input
added to the vendor tree.

Closes #274
Closes #275
Closes #276
fuzzy scheduled this pull request to auto merge when all checks succeed 2026-08-08 11:37:30 +00:00
the.auditor left a comment

Well-constructed interactive layer. The both-terminals gate cleanly preserves all non-interactive paths, raw mode is set and restored correctly around the picker, escape/Ctrl-C cancel semantics work with the vendored konsoru/input (verified KeyEvent.Ctrl and ParseSequence behavior), and the fuzzy filter (case-insensitive subsequence, position-ranked) is sound. ResolveSite/ResolveValue/prompt wiring across site/deployment/preview is consistent and non-terminal behavior is unchanged. Approving; one robustness gap filed.

Suggestions

  1. internal/tpagectl/interactive/interactive.go:35 – No escape hatch for interactive mode. Pty-based automation (script, expect, CI with a pty, tmux, ssh -t) satisfies "stdin and stdout are terminals", so a missing required value now blocks on a prompt where it previously failed fast with exit 5 — scripts can hang. Add an override (TPAGECTL_INTERACTIVE=0, --no-interactive, or disable under CI/TERM=dumb). Filed as #607.

Notes

  • selectInteractive places the cursor at the fixed start-of-query position on every redraw (cursor.MoveTo(1, len(prompt)+3)); it never tracks the typed query. Input remains correct (append/backspace at the string level), purely a cosmetic cursor placement issue.
  • deployment get interactive version prompt can't select --latest (the label hints at it, but there's no picker entry for it) — a minor UX dead-end.
  • promptCreateFlags (empty prompt → skip to buildCreateRequest) and ResolveValue (empty → direct validation error) handle empty input slightly differently, converging on the same validation messages.

Praise

  • Clean gate keeps all existing tests and piped output on the non-interactive path.
  • Raw-mode setup tolerates MakeRaw failure (proceeds cooked) and always restores the terminal via defer.
  • Fuzzy picker guards against empty result sets (Enter no-ops, empty-site list short-circuits before the picker).
Well-constructed interactive layer. The both-terminals gate cleanly preserves all non-interactive paths, raw mode is set and restored correctly around the picker, escape/Ctrl-C cancel semantics work with the vendored `konsoru/input` (verified `KeyEvent.Ctrl` and `ParseSequence` behavior), and the fuzzy filter (case-insensitive subsequence, position-ranked) is sound. `ResolveSite`/`ResolveValue`/prompt wiring across site/deployment/preview is consistent and non-terminal behavior is unchanged. Approving; one robustness gap filed. ## Suggestions 1. `internal/tpagectl/interactive/interactive.go:35` – No escape hatch for interactive mode. Pty-based automation (`script`, `expect`, CI with a pty, tmux, `ssh -t`) satisfies "stdin and stdout are terminals", so a missing required value now blocks on a prompt where it previously failed fast with exit 5 — scripts can hang. Add an override (`TPAGECTL_INTERACTIVE=0`, `--no-interactive`, or disable under `CI`/`TERM=dumb`). Filed as #607. ## Notes - `selectInteractive` places the cursor at the fixed start-of-query position on every redraw (`cursor.MoveTo(1, len(prompt)+3)`); it never tracks the typed query. Input remains correct (append/backspace at the string level), purely a cosmetic cursor placement issue. - `deployment get` interactive version prompt can't select `--latest` (the label hints at it, but there's no picker entry for it) — a minor UX dead-end. - `promptCreateFlags` (empty prompt → skip to `buildCreateRequest`) and `ResolveValue` (empty → direct validation error) handle empty input slightly differently, converging on the same validation messages. ## Praise - Clean gate keeps all existing tests and piped output on the non-interactive path. - Raw-mode setup tolerates `MakeRaw` failure (proceeds cooked) and always restores the terminal via defer. - Fuzzy picker guards against empty result sets (Enter no-ops, empty-site list short-circuits before the picker).
fuzzy merged commit 5f5e501aa5 into main 2026-08-08 11:44:11 +00:00
fuzzy deleted branch feat/tpagectl-interactive 2026-08-08 11:44:11 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
thwap/thwap-pagesd!606
No description provided.