Repository navigation
chore(cli): replace the huh form library with a built-in forms package - #92
Conversation
Generated CLIs built their guided forms (configure, auth login, and missing-input prompts) on charm.land/huh/v2. They now generate internal/forms, a reduced copy of huh v2.0.3 that keeps only what the CLI calls: forms and groups, input, text, select and confirm fields, the base theme, the keymap, and accessible line-prompt mode. File pickers, multi-select, notes, spinners, dynamic *Func fields, extra layouts and themes, and timeouts are dropped. The MIT notice is kept in the package's doc.go. Retained code is unchanged apart from the removed paths, with two deliberate differences: accessible password input reads through golang.org/x/term (already a dependency) instead of charmbracelet/x/term, and program errors are wrapped as "form: ..." instead of "huh: ...". Generated CLIs drop huh, catppuccin, hashstructure, go-humanize and the charmbracelet/x/exp modules. bubbletea and bubbles become direct dependencies whenever interactiveAuth or interactiveMode is enabled. Custom code in the auth login regions that calls huh must switch to the forms package; the setter API is kept so the switch is a rename.
There was a problem hiding this comment.
All reported issues were addressed across 44 files
Shadow auto-approve: would not auto-approve because issues were found.
View guided diff | Turn on auto-fix | Re-trigger cubic
internal/forms is now a single generated file, about half its previous size. Every call site rendered with the same theme, the terminal width, and help hidden, so those become fixed behaviour of the package. The theme, width and help options, and the copies of the theme and width helpers in auth.go, configure.go and interactive.go, are removed. Also removed because no generated code reaches them: select filtering, the external-editor shortcut on text fields, inline layouts, custom confirm labels, character limits, hidden groups, field keys, value accessors, and the dark-background plumbing (the theme ignores it). Select stores strings only, and Input.EchoMode(EchoModePassword) becomes Input.Password(). Two key handling changes follow: "/" no longer opens a filter in selects, and ctrl+e in an input moves to the end of the line instead of being swallowed by an unused suggestion binding.
There was a problem hiding this comment.
All reported issues were addressed across 40 files (changes from recent commits).
Shadow auto-approve: would not auto-approve because issues were found.
View guided diff | Turn on auto-fix | Re-trigger cubic
internal/forms is rewritten around what generated CLIs ask for: pages of text, secret, JSON, choice and yes/no fields. One Field type and one Form model replace the per-field models, groups, viewports and themes of the previous package, which was adapted from huh and is removed along with its license notice. The API binds each answer at construction (NewInput(&v), NewSelect(&v, options...), NewConfirm(&b), NewText(&v)), groups fields with NewPage(title, fields...), and runs with New(pages...).Accessible(b).Run(). Selects show a window of options sized to the terminal height. Line-prompt mode now reads all answers through one buffered reader, so piped input reaches every field instead of being consumed by the first, and a secret read from a non-terminal is taken from the next line rather than failing. Blank answers keep the current value and end of input stops prompting, as before. The package ships with unit tests covering navigation, validation, selects, confirms, abort, and the line prompts.
There was a problem hiding this comment.
All reported issues were addressed across 12 files (changes from recent commits).
Shadow auto-approve: would not auto-approve because issues were found.
View guided diff | Turn on auto-fix | Re-trigger cubic
Form fixes: - Line prompts read and write through Form.IO, so configure and auth login use the command's streams; prompts for missing inputs write to stderr to keep stdout for command output. - Read errors other than end of input are returned from Run instead of being treated as a finished form. - Answers keep their surrounding whitespace; only blank answers fall back to the current value. - Enter on the last field submits even when empty pages follow it. - Page titles and option labels wrap to the terminal width, and multi-line option labels stay aligned under their first line. - Selects window options by rendered height and support ctrl+d and ctrl+u half-page moves. The tests move from internal/forms into tests/primary so they run against the generated test CLI without shipping in generated output. They cover the scenarios huh's own suite exercises for the fields used here: multi-page walk-through with validation, input, password, placeholder and paste, text with newlines, confirm keys, select navigation including page moves and wrap-around, previous page, titles and descriptions, width and resize, abort, and line prompts for every field kind including defaults, invalid answers, piped multi-field input, end of input and read errors.
There was a problem hiding this comment.
0 issues found across 13 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Shadow auto-approve: would require human review. Replaces huh with a generated internal forms package and moves configure/auth call sites to it. The dismissed styling issue doesn't apply to single-select, but the PR breaks customized auth/configure regions that embedded huh fields; that breaking contract needs human sign-off.
View guided diff | Turn on auto-fix | Re-trigger cubic
Pages taller than the terminal now scroll: the form renders the window of lines that contains the focused field, moving it as little as possible, keeping the page title in view with the first field, and showing the input end of a field that is itself taller than the terminal. Previously the terminal showed only the bottom of the page, so on an 80x24 terminal the first fields of a long configure page were off screen while being edited. Selects size their option window from the space left after their own title and description, so the heading stays visible on short terminals. Widths below the previous 10-column floor no longer overflow, and confirm buttons drop their padding when the terminal is too narrow for it. Dumb terminals always get line prompts, even when a caller asks for the interactive form. Tests cover tall pages at several heights in both directions, the page title with the first field, a field taller than the terminal, selects with headings on short terminals, narrow widths, and TERM=dumb.
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Shadow auto-approve: would not auto-approve because issues were found.
View guided diff | Turn on auto-fix | Re-trigger cubic
APIs without global security scheme groups generate an auth login that never builds a form, so the unconditional import failed to compile.
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Shadow auto-approve: would not auto-approve. Auto-approval blocked by 2 unresolved issues from previous reviews.
View guided diff | Turn on auto-fix | Re-trigger cubic
The input width went negative below 8 columns, which panicked when the field rendered. Clamp it, and show compact Y/N confirm buttons when the full labels no longer fit.
There was a problem hiding this comment.
0 issues found across 4 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Shadow auto-approve: would require human review. Replaces huh with a generated internal/forms package, rewires configure/auth/interactive prompts, and drops the huh dependency with added tests. The remaining human judgment is accepting the breaking change to custom auth/configure regions that embedded huh fields.
View guided diff | Turn on auto-fix | Re-trigger cubic
2ynn
left a comment
There was a problem hiding this comment.
Three P2 correctness issues are detailed inline: textarea visibility on short terminals, unwrapped validation errors, and skipped accessible select validators. A P3 inline note covers the half-page navigation assertions. The remaining buffered-input issue across separate forms is documented as an inherited/incomplete-fix follow-up in the existing buffering thread, not a newly introduced regression.
One additional coverage gap: TestFormsCtrlCAborts checks the quit command and empty view but does not exercise Run returning ErrUserAborted. Retaining the cancellation smoke test in the suite would protect that public error contract.
All assertions in the 33 new test functions were inspected. Focused model and line-prompt probes reproduce the reported failures; CI-registered tests were not rerun. No additional issue was found in dependency gating or the remaining call-site changes.
…ompt input - Wrap validation errors to the field width and budget their rows when sizing select windows and the multi-line editor, which now shrinks to fit short terminals instead of hiding its first line. - Keep the focused input in view when a field and its error are taller than the terminal. - Validate select answers in line prompts, including a kept default, and re-ask when the validator rejects them. - Add forms.Reader so consecutive forms over one input share buffered answers; auth login and command prompts use one reader per command. - Read interactive keys from the configured input when it is not stdin, so ctrl+c on scripted input returns ErrUserAborted.
|
Ctrl-C note: added TestFormsRunReturnsErrUserAbortedOnCtrlC in 2cf1559. Run now reads keys from a non-stdin input passed to IO. |
There was a problem hiding this comment.
All reported issues were addressed across 9 files (changes from recent commits).
Shadow auto-approve: would not auto-approve because issues were found.
View guided diff | Turn on auto-fix | Re-trigger cubic
Reject trailing characters in the integer validator used by the form tests, and assert which authentication method the piped login picks.
There was a problem hiding this comment.
0 issues found across 2 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Shadow auto-approve: would require human review. Swaps huh for an in-repo forms package across auth/configure/interactive prompts, with tests and dependency cleanup. Needs sign-off for the breaking change: custom auth/configure regions embedding huh fields must migrate to forms.
View guided diff | Turn on auto-fix | Re-trigger cubic
# Conflicts: # zSDKs/sdk-cli/.speakeasy/gen.lock
Why
Generated CLIs depended on
charm.land/huh/v2for their guided forms (configure,auth login, and the prompts for missing required inputs), yet used only a small part of it. Generated CLIs now carry a small forms package built around exactly those questions, which removes the dependency and its transitive modules.What changed
internal/formspackage, generated wheninteractiveAuthorinteractiveModeis enabled:forms.go(about 590 lines) shipped without tests. OneFieldtype covers text input, secret input, multi-line JSON, single choice and yes/no. OneFormmodel steps through pages of fields, built directly onbubbletea,bubbles(textinput,textarea) andlipgloss, using the CLI's theme colours.forms.NewInput(&v).Title(…).Description(…).Placeholder(…).Password().Validate(fn),forms.NewText(&v),forms.NewSelect(&v, forms.NewOption(label, value)…),forms.NewConfirm(&b),forms.NewPage(title, fields…),forms.New(pages…).Accessible(b).IO(in, out).Run().user aborted.Run.configureandauth loginwrite to stdout, and prompts for missing inputs write to stderr.auth.go,configure.go,interactive.go,includes/security.ts) use the new API. The per-file theme and width helpers are removed, as are the duplicated security-field emitters.catppuccin/go,hashstructure,go-humanize,x/exp/orderedandx/exp/strings.bubbleteaandbubblesbecome direct dependencies whenever either interactive option is enabled.zSDKs/sdk-cli) andtemplates/perms.go, and added a patch changeset forcli.Breaking for customised CLIs: custom code in the
auth login#region custom-auth-*blocks that builds huh fields must move to theformsAPI above.Testing
tests/primary/forms_test.go(33 tests) runs against the generated primary test CLI and isn't part of generated output. It mirrors the scenarios huh's own suite covers for these field types:TERM=dumbI checked the tests can fail by making deliberate behaviour-changing edits to
forms.go: all 38 were caught.Terminal matrix (out of tree). A configure-shaped form (8 credentials, global parameters, a select with a long label, a confirm) ran in a pseudo-terminal, with its output replayed into a terminal emulator of the same size. At every step the test checked that the user can see what they typed and that passwords stay masked, then checked every answer arrived:
TERMset toxterm-256color,xterm,vt100,screenandlinux, plusNO_COLOR=1andCOLORTERM=truecolorTERM=dumbOut-of-tree pseudo-terminal smoke tests ran a built program through the interactive form (secret input, validation, the JSON newline, select, confirm, ctrl+c) and through line prompts at a terminal (secret not echoed).
TARGET=primary make test-cli: 1753 tests pass, including the forms suite, run with agent-detection environment variables unset.TARGET=review make test-cli: 609 pass, 2 skipped.Generated the review spec with each of the four
interactiveAuth/interactiveModecombinations. Each builds and passes staticcheck,internal/formscontains onlyforms.go, and it and the charm modules appear only when an interactive option is enabled.go generate ./...,prettier --check(3.0.3 from the lockfile) andmake check-template-clipass.make lintsteps 1–4 pass. The golangci-lint step didn't run locally because the installed binary was built with Go 1.25 and the repository targets Go 1.26.8.Public-safety check
.claude/skills/public-repo-communication/SKILL.md).git diff --check.