Skip to content

feat(interactive): add reflection-driven interactive prompt engine#249

Open
ben-kalmus wants to merge 1 commit into
mainfrom
feat/interactive-package
Open

feat(interactive): add reflection-driven interactive prompt engine#249
ben-kalmus wants to merge 1 commit into
mainfrom
feat/interactive-package

Conversation

@ben-kalmus

@ben-kalmus ben-kalmus commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds pkg/interactive, a generic package that builds a request body by walking any Go struct with reflection and prompting for each field. It is the foundation for --interactive modes across the CLI.

  • This PR introduces the engine only. No command is wired to it yet (see the follow-up PR adding --interactive to compositions upsert).

Demo

compositions-upsert

Stacked PRs

How it works

  • Builder.Build(&v) walks the struct and prompts for inputs: optional pointer fields can be skipped, large optional-only parameter objects let you multi-select which fields to fill.

  • Enums (named string types the SDK generates with an IsValid() bool method) are validated against the SDK's own check via reflection.

  • Bad input does not abort the build. It re-prompts in place (non-numeric integer, malformed JSON, empty required field) using validators.

  • A depth/cycle guard degrades self-referential structures to a raw-JSON prompt instead of looping forever.

Design notes

  • SDK-agnostic: the package imports only pkg/iostreams and pkg/prompt, no SDK or command packages, so any command can reuse it.

  • Input is gathered through a small Prompter interface (Input/Confirm/Select/MultiSelect). Production uses SurveyPrompter; tests use ScriptedPrompter, a label-keyed fake whose answers match by field label rather than call order, so tests do not break when SDK changes.

Changes

  • pkg/interactive/prompter.go: Prompter interface and SurveyPrompter (survey/v2 via pkg/prompt).
  • pkg/interactive/scripted_prompter.go: ScriptedPrompter, the reusable label-keyed test fake.
  • pkg/interactive/builder.go: the reflection walk (scalars, optional pointers, unions, parameter bags, slices, maps, enum and input validation).
  • pkg/interactive/classify.go: reflection helpers (union, parameter-bag, required-field detection).
  • pkg/interactive/validators.go: input validator builders (required string, integer, number, boolean, JSON).

Tests

Unit tests under pkg/interactive covering:

  • Scalars, optional pointers (set and skipped), and enums (valid and rejected)
  • oneOf unions, parameter bags, slices, and map[string]string
  • Pre-populated fields preserved, and the depth/cycle guards falling back to raw JSON
  • Validator behavior and both the scripted and survey prompters

@codacy-production

codacy-production Bot commented Jun 17, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 236 complexity · 7 duplication

Metric Results
Complexity 236
Duplication 7

View in Codacy

TIP This summary will be updated as you push new changes.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a new pkg/interactive package intended to build request bodies interactively by reflecting over Go structs and prompting the user for each field, enabling future --interactive command modes across the CLI.

Changes:

  • Added a reflection-driven Builder that traverses structs (including optional pointers, unions, slices, maps, and “parameter bag” structs) and assigns values from prompts.
  • Added a Prompter abstraction with a survey-backed implementation for production and a label-keyed scripted fake for stable unit tests.
  • Added reusable input validators and unit tests covering the builder, prompters, and classification helpers.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
pkg/interactive/validators.go Adds reusable validator builders for interactive text input.
pkg/interactive/validators_test.go Unit tests for the validator builders and user-facing messages.
pkg/interactive/scripted_prompter.go Adds a label-keyed scripted Prompter fake for deterministic tests.
pkg/interactive/scripted_prompter_test.go Tests matching/defaulting behavior of ScriptedPrompter.
pkg/interactive/prompter.go Defines Prompter and implements SurveyPrompter via pkg/prompt + survey.
pkg/interactive/prompter_test.go Tests that SurveyPrompter routes calls through prompt.SurveyAskOne.
pkg/interactive/classify.go Reflection helpers for identifying unions, param-bags, required fields, etc.
pkg/interactive/classify_test.go Unit tests for type/field classification helpers.
pkg/interactive/builder.go Core reflection traversal + prompting and assignment logic.
pkg/interactive/builder_test.go Unit tests covering scalar/composite traversal, enums, guards, and error propagation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/interactive/prompter.go
Comment thread pkg/interactive/prompter_test.go
Comment thread pkg/interactive/builder.go
Comment thread pkg/interactive/builder.go Outdated
Comment thread pkg/interactive/builder.go Outdated
Comment thread pkg/interactive/builder.go
Comment thread pkg/interactive/builder.go Outdated
Comment thread pkg/interactive/builder.go
@ben-kalmus ben-kalmus force-pushed the feat/interactive-package branch from c17e3a6 to 9a22ae5 Compare June 19, 2026 16:12
A generic package that builds a request body by walking any struct via
reflection and prompting for each field through a small Prompter
interface
(survey-backed in production, a scripted fake for tests). Handles
scalars,
optional pointers, enums (validated through the SDK's own IsValid
method),
oneOf unions, parameter bags, slices and maps, with per-field input
validation and re-prompt on invalid entries. No SDK or command coupling.
@ben-kalmus ben-kalmus force-pushed the feat/interactive-package branch from 9a22ae5 to d0c4a17 Compare July 6, 2026 11:31

@LorrisSaintGenez LorrisSaintGenez left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this system 👏 Left a couple of comments, and both are suggestions for an improved UX (warning/error on overwritting + select for enum field).

if err := b.buildValue(fmt.Sprintf("%s[%q]", label, key), valPtr.Elem(), depth+1, stack); err != nil {
return err
}
out.SetMapIndex(reflect.ValueOf(key), valPtr.Elem())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Non-blocking) We can technically overwrite a previously set key/value pair here. We should first check if the key exist in the map and print something to the user, either error or warning (+ confirmation?) imo

var validate func(string) error
switch {
case isEnumType(v.Type()):
validate = enumValidator(v.Type(), label, optional)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Non-blocking) I made a PR to add a Values method for enum field for the Go package.
We should be able to use reflect on it, and display a Select instead of free-text. This can be done in a follow-up PR, not blocking for this review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants