PlaneTUI is a composable terminal user interface (TUI) framework for Go. It extends the original minimal command loop with structured command metadata, lifecycle hooks, dependency injection, context navigation, typed argument parsing, output management, and background task execution—while preserving compatibility with legacy commands.
- Structured commands with
CommandSpec, rich metadata, aliases, tags, and auto-generated usage strings - Lifecycle + middleware support so authentication, logging, and validation run consistently before/after commands
- Built-in argument parsing using declarative
ArgSpec/FlagSpecwith typed accessors - Context manager providing hierarchical navigation, alias resolution, and payload/state passing
- Session + services stores for sharing data and dependencies across commands
- Async/background tasks with cancellation, progress output, and task inspection
- Output channels enabling leveled messaging, JSON/table rendering, and test-friendly capture
- Legacy compatibility through
RegisterLegacyCommandadapters so existing commands keep working
go get github.com/network-plane/planetuipackage main
import (
"log"
"github.com/chzyer/readline"
tui "github.com/network-plane/planetui"
)
type helloFactory struct {
spec tui.CommandSpec
}
type helloCommand struct {
spec tui.CommandSpec
}
func newHelloFactory() tui.CommandFactory {
spec := tui.CommandSpec{
Name: "hello",
Summary: "Print a greeting",
Description: "Outputs a greeting and optional name.",
Context: "demo",
Args: []tui.ArgSpec{
{Name: "name", Type: tui.ArgTypeString, Required: false, Description: "Name to greet"},
},
}
return &helloFactory{spec: spec}
}
func (f *helloFactory) Spec() tui.CommandSpec { return f.spec }
func (f *helloFactory) New(rt tui.CommandRuntime) (tui.Command, error) {
return &helloCommand{spec: f.spec}, nil
}
func (c *helloCommand) Spec() tui.CommandSpec { return c.spec }
func (c *helloCommand) Execute(rt tui.CommandRuntime, input tui.CommandInput) tui.CommandResult {
name := input.Args.String("name")
if name == "" {
name = "world"
}
rt.Output().Info("hello " + name + "!")
return tui.CommandResult{Status: tui.StatusSuccess}
}
func main() {
rl, err := readline.NewEx(&readline.Config{})
if err != nil {
log.Fatal(err)
}
defer rl.Close()
tui.RegisterContext("demo", "Example commands")
tui.RegisterCommand(newHelloFactory())
if err := tui.Run(rl); err != nil {
log.Fatal(err)
}
}- Describe metadata in
CommandSpec; PlaneTUI uses it for help text, autocomplete, and validation. - Use
CommandInput.Args/Flagstyped helpers (String,Int,Bool,Duration,DecodeJSON, etc.). - Return a
CommandResultto signal success, surface structured errors, pass pipeline payloads, or request context navigation. - Access shared session data via
CommandRuntime.Session(), services viaServices(), and spawn background work withTaskManager().Spawn. - Emit output through
CommandRuntime.Output(); messages are automatically captured for tests and respect verbosity levels. - Register middleware with
tui.UseMiddlewareor when constructing a customEngineto add logging, auth, timing, etc.
The original planetui package exposed a very small surface area:
type Command interface {
Name() string
Help() string
Exec(args []string)
}
func RegisterContext(name, description string)
func RegisterCommand(ctx string, cmd Command)
func Run(rl *readline.Instance)Moving to the new framework provides far richer behaviour. The steps below help migrate existing apps incrementally:
- Wrap legacy commands (optional bridge). Call
tui.RegisterLegacyCommand(ctx, legacyCmd)to keep using the oldCommandinterface while you migrate. Legacy commands run exactly as before, but without access to new features. - Adopt factories. Replace direct command instances with a
CommandFactorythat returns a freshCommandper execution. This unlocks dependency injection and isolates per-run state. - Describe metadata. Implement
Spec() CommandSpecon your command (and factory) to declare name, aliases, contexts, arguments, and flags. PlaneTUI now drives help/autocomplete from the spec. - Return results instead of printing. Change
Execimplementations toExecute(rt, input) CommandResult. UseCommandResult.Status,Error,Messages, andPayloadto communicate outcomes instead of callingfmt.Printdirectly. - Use typed inputs. Replace manual
[]stringparsing withinput.Args/input.Flagsbased on the specs declared in step 3. - Adopt runtime services. Access session storage, shared dependencies, output channels, context navigation, and task management through the provided
CommandRuntimemethods rather than global variables. - Clean up legacy helpers. Once all commands implement the new interface, remove
RegisterLegacyCommandcalls and rely exclusively onRegisterCommandwith factories.
| Legacy API | New API |
|---|---|
Command.Name/Help/Exec([]string) |
Command.Spec() CommandSpec + Execute(CommandRuntime, CommandInput) |
RegisterCommand(ctx string, cmd Command) |
RegisterCommand(factory CommandFactory) |
fmt.Print inside commands |
rt.Output().Info/Warn/Error/WriteJSON/WriteTable |
currentCtx global / manual navigation |
rt.NavigateTo, rt.PushContext, rt.PopContext |
| No argument parsing helpers | Declarative ArgSpec/FlagSpec + typed ValueSet access |
| No async support | rt.TaskManager().Spawn with cancellation + inspection |
Following these steps lets you layer the advanced command system on top of existing functionality without a flag-day rewrite.
Contributions are always welcome! All contributions are required to follow the Google Go Style Guide.
I will always follow the Linux Kernel License as primary, if you require any other OPEN license please let me know and I will try to accomodate it.