This file is for AI agents (Claude, Codex, Aider, etc.) working on huhx. It is shorter and more directive than the human-facing CONTRIBUTING.md. If you are a human, read CONTRIBUTING.md instead.
A thin wrapper around charmbracelet/huh that adds a non-interactive Runner. Forms are built with huhx builders; the Runner drives them interactively on a TTY (delegating to huh) or non-interactively by walking groups in order and resolving each field's answer from configured sources.
Module path: github.com/cabljac/huhx. Go version: 1.25.
field.go internal field interface
input.go text.go string fields
confirm.go bool field
select.go multiselect.go generic [T comparable] fields
group.go form.go composition wrappers
mode.go Mode enum (AutoDetect/Always/Never)
options.go Option type + With* setters
sources.go answer-file (YAML/JSON), --answer parsing, env key formatting
runner.go Runner.Run, walk, resolve, missing-err
*_test.go unit + integration tests, all in package huhx
e2e_test.go subprocess tests in package huhx_test
examples/deploy/main.go cobra-wired example used by e2e
- Tests: use
testnotit. Subtests viat.Run. Each forwarder added on a field wrapper must come with a chainability test and, if the forwarder is observable in non-interactive mode, an integration test. - Comments: default to none. Add one only when the why is non- obvious (a hidden constraint, a workaround, surprising behavior). Never restate what well-named code already says.
- Errors: wrap with
%wand field/source context. Validators are surfaced via the runner asfield %q: %w. - No
any/interface{}unless required by an external signature. - Atomic commits: one logical change per commit. Conventional Commits style. Subject ≤72 chars. Body explains the why.
- No co-author attribution for AI tools. Don't add Claude (or any agent) as a Co-Authored-By.
Every field type follows the same shape:
type X struct {
inner *huh.X
k string
value *T
accessor huh.Accessor[T]
validate func(T) error
optional bool
// type-specific extras: options, optionsFunc, etc.
}When adding a new chainable forwarder, mirror huh's exact signature and
return the huhx wrapper. For state-affecting calls (Accessor,
OptionsFunc) also store the state on the wrapper so the non-interactive
set() can use it.
- Find the method's signature in the huh source.
- Add the method to the matching huhx file with
inner.<Method>(args...)andreturn wrapper. - Add a one-line doc comment.
- Add the method to the chainability test in
<type>_test.go.
- Plumb the option through
options.go(WithFoo) and store onRunner. - Add the resolution step to
Runner.resolveat the right precedence layer (see README "Answer source precedence"). - Add an integration test in
runner_test.goproving it wins over the layer below and loses to the layer above.
go test ./... # all tests, ~46
go test -run TestE2E ./... # subprocess suite
go test -coverprofile=/tmp/c.cov ./... && go tool cover -func=/tmp/c.cov
go vet ./...
gofmt -l . # must be empty
go build ./...- Forking huh's TUI rendering. File upstream against huh.
- huhx-only fields without a huh backing type.
- Changes that diverge huhx's API from huh's. Parity is a non-goal- breaker — when in doubt, mirror huh.
- Don't add
// removed Xstubs for things you delete. Just delete them. - Don't write "this method ..." doc comments that restate the name.
- Don't add error handling for impossible cases. Trust internal guarantees.
- Don't bypass
go vetorgofmt. Fix the issue. - Don't push to
mainwithout atomic commits. - Don't add yourself as Co-Authored-By.