nabat

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 5, 2026 License: Apache-2.0 Imports: 39 Imported by: 0

Documentation

Overview

Package nabat provides a CLI framework for Go built on Cobra.

Core concepts

An App is the root of a CLI. Add subcommands with App.Command, install extensions with WithExtension, enable the built-in version feature with WithVersion, configure the root by passing RootOption values directly to New, and run the tree with App.Run.

A Command wraps a single github.com/spf13/cobra.Command and Nabat metadata (positional args, flags, hooks). Nested commands use Command.Command (returns `(*Command, error)`) or Command.MustCommand (panics on failure; suitable for `main()` chains). For aggregated registration errors across multiple commands, declare the entire tree with WithCommand inside New; every problem aggregates into the *ConfigErrors returned by New. There is no deferred error path.

Each invoked handler receives a Context with resolved args and flags. Handlers are RunFunc values set with WithRun. Context implements context.Context for deadlines and cancellation.

Positional args resolve in order: CLI arg, environment variable (only if WithEnv is set), interactive prompt (TTY only, only if WithPrompt is attached), then the typed default passed to the constructor. Flags resolve: CLI flag, environment variable (only if WithEnv is set), then default. All names passed to WithEnv are combined with the app env prefix. Use WithEnvAlias for verbatim env names that must not receive the prefix. Define args with WithArg and related helpers; define flags with WithFlag and related helpers.

Quick start

Construct an app and its command tree in one call, then run it:

app, err := nabat.New("myctl",
    nabat.WithVersion("0.1.0"),
    nabat.WithCompletion(),
    nabat.WithExtension(manpage.New()),

    nabat.WithCommand("deploy",
        nabat.WithDescription("Deploy application"),
        nabat.WithSelectArg("env", "", []string{"staging", "production"},
            nabat.WithRequired(),
            nabat.WithPrompt("Target environment", ""),
        ),
        nabat.WithFlag("replicas", 3,
            nabat.WithEnv("replicas"),
            nabat.WithUsage("Number of replicas"),
        ),
        nabat.WithRun(func(c *nabat.Context) error {
            var args struct {
                Env string `nabat:"env"`
            }
            if err := c.Bind(&args); err != nil {
                return err
            }
            c.Success("deployed", "environment", args.Env)
            return nil
        }),
    ),
)
if err != nil {
    log.Fatal(err)
}

if err := app.Run(context.Background()); err != nil {
    os.Exit(1)
}

On failure, App.Run prints a styled error and a "run --help" hint to stderr before returning the error, unless you override rendering with WithErrorHandler.

Help has a two-axis design. The persistent `--help` (`-h`) flag is on by default for every app (GNU/POSIX convention) and is configured via WithHelpFlagName, WithHelpShorthand, WithoutHelpFlag, and WithoutHelpShorthand. The `help <subcmd>` subcommand is opt-in via WithHelpCommand and WithHelpCommandName (mirroring WithVersion). Use WithoutHelp to opt out of the entire feature and let Cobra's defaults take over.

Version is opt-in: pass WithVersion to install a `version` subcommand and `--version`/`-v` flag with themed output. Customize via the nested VersionOption family (WithVersionCommandName, WithVersionFlagName, WithVersionShorthand, etc.); omit WithVersion entirely to install nothing.

Shell completion is opt-in: pass WithCompletion to install a `completion` subcommand that emits bash, zsh, fish, and PowerShell scripts. Customize via the nested CompletionOption family (WithCompletionName, WithCompletionHidden, WithCompletionShells); omit WithCompletion entirely to install nothing. Per-flag and per-positional dynamic completion candidates are wired with WithCompleter and WithPositionalCompleter and work whether or not WithCompletion is enabled.

Interactive forms

Context.Form collects multiple values in one typed form. Field constructors (WithFormField, WithSelectField, WithMultiSelectField) satisfy both FormOption and GroupOption, so they slot into either Context.Form directly or inside WithFormGroup (multi-page forms):

// Single-page form
c.Form(
    nabat.WithFormField(&name, "Name", "", nabat.WithDefault("alice")),
    nabat.WithSelectField(&env, "Environment", "",
        []string{"staging", "production"}, "staging"),
)

// Multi-page wizard
c.Form(
    nabat.WithFormGroup(
        nabat.WithGroupTitle("Identity"),
        nabat.WithFormField(&name, "Name", "", nabat.WithDefault("")),
    ),
    nabat.WithFormGroup(
        nabat.WithGroupTitle("Deployment"),
        nabat.WithSelectField(&env, "Environment", "",
            []string{"staging", "production"}, "staging"),
    ),
)

WithDefault sets the non-interactive fallback value; T is inferred from the value. Select fields use the positional defaultVal parameter instead, which keeps compile-time E-type checking. WithFormNote adds display-only instructional text. WithOptionsFunc provides dynamic select choices. WithFormAccessible enables screen-reader-friendly mode.

Examples

See the examples/ directory for complete programs. Runnable godoc examples are in example_test.go.

Example
package main

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
)

func main() {
	var out bytes.Buffer
	app, err := nabat.New("myctl",
		nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)),
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	app.MustCommand("hello",
		nabat.WithArg("name", "world"),
		nabat.WithRun(func(c *nabat.Context) error {
			var args struct {
				Name string `nabat:"name"`
			}
			if bindErr := c.Bind(&args); bindErr != nil {
				return bindErr
			}
			c.Print(args.Name)
			return nil
		}),
	)
	if err = app.RunArgs(context.Background(), "hello"); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(out.String())
}
Output:
world
Example (ThemeAndOverride)

Example_themeAndOverride shows the canonical end-to-end shape: pick a built-in theme by name, optionally tweak one slot via WithThemeOverride, then read styles back through App.Theme in a command's run handler. The tweaked StatusError color flows through every output path that consumes that token (Context.Error, the "error:" prefix on uncaught errors, etc.).

io, _, out, _ := nabattest.NewIO()

app, err := nabat.New("myctl",
	nabat.WithIO(io),
	nabat.WithTheme(theme.Minimal),
	nabat.WithThemeOverride(theme.StatusError, lipgloss.NewStyle().Bold(true)),
)
if err != nil {
	fmt.Println(err)
	return
}

// The resolved theme is a snapshot of every styled slot; consumers
// (Context.Success, Context.Error, the help renderer, the logging
// extension) all read through the same accessor.
rt := app.Theme()
fmt.Println("name:", rt.Name())
fmt.Println("variant:", rt.Variant())
fmt.Println("status.error bold:", rt.Style(theme.StatusError).GetBold())
_ = out
Output:
name: minimal
variant: notty
status.error bold: true

Index

Examples

Constants

View Source
const DefaultWidth = 80

DefaultWidth is the terminal width assumed when the underlying stream is not a terminal or its size cannot be determined.

View Source
const HeaderRow = table.HeaderRow

HeaderRow denotes the header row index passed to WithTableStyleFunc. Use this value to identify header cells inside a style function callback.

Example:

WithTableStyleFunc(func(row, col int) lipgloss.Style {
	if row == HeaderRow {
		return lipgloss.NewStyle().Bold(true)
	}
	return lipgloss.NewStyle()
})

Variables

View Source
var (
	// ErrNilOption indicates a nil option was passed to a constructor.
	ErrNilOption = errors.New("nabat: option cannot be nil")
	// ErrInvalidOption indicates one or more options were invalid.
	ErrInvalidOption = errors.New("nabat: invalid option")
	// ErrInvalidValueType indicates an unrecognized ValueKind.
	ErrInvalidValueType = errors.New("nabat: invalid value type")

	// ErrHandled is a sentinel returned by global pre-run hooks ([App.OnPreRun])
	// or per-command pre-run hooks ([WithPreRun]) to signal that the hook has
	// fully handled the invocation (e.g. printed the version string or help
	// text). The framework treats it as a successful short-circuit: remaining
	// hooks and the command handler are skipped, and [App.Run] returns nil.
	ErrHandled = errors.New("nabat: handled")

	// ErrRegistrationFrozen is returned by App.Command and Command.Command
	// when called after App.Run / App.RunArgs has been invoked. Register
	// every command before running the app.
	ErrRegistrationFrozen = errors.New("nabat: command registration is frozen after App.Run")

	// ErrArgFlagNameCollision is returned (wrapped, with command and field
	// names) when a command declares a positional arg and a flag that share
	// a name. Match it with [errors.Is] in tests instead of substring
	// matching the formatted message.
	ErrArgFlagNameCollision = errors.New("nabat: name used by both arg and flag")
)
View Source
var (
	ListBullet   = list.Bullet
	ListDash     = list.Dash
	ListAsterisk = list.Asterisk
	ListNumbered = list.Arabic
	ListRoman    = list.Roman
	ListAlphabet = list.Alphabet
)

Enumerator presets for use with WithListEnumerator. These are aliases for lipgloss list enumerators, so callers do not need to import the list package directly.

Available presets:

View Source
var (
	ProgressFillHalfBlock = progress.DefaultFullCharHalfBlock
	ProgressFillFullBlock = progress.DefaultFullCharFullBlock
	ProgressEmptyLight    = progress.DefaultEmptyCharBlock
)

Fill presets for use with WithProgressBarFillCharacters. These mirror charm.land/bubbles/v2/progress defaults so callers do not need to import the progress package for common glyphs. Half-block is the bubbles default fill rune for finer color blending than full block.

Available presets:

Functions

func BindAs

func BindAs[T any](c *Context, name string) (T, error)

BindAs returns the resolved value for a single arg or flag name. It is a convenience for tests and one-off reads; prefer binding into a struct with Context.Bind in command handlers.

Errors:

  • "nabat: BindAs: context is nil" when c is nil
  • "nabat: BindAs: %q has no resolved value" when the name was not resolved
  • "nabat: BindAs %q: value type ... does not match requested type ..." on type mismatch
Example (String)
package main

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/theme"
)

func main() {
	var out bytes.Buffer
	app := nabat.MustNew("getctl",
		nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)),
		nabat.WithTheme(theme.Minimal),
	)
	app.MustCommand("show",
		nabat.WithFlag("label", "default"),
		nabat.WithRun(func(c *nabat.Context) error {
			label, err := nabat.BindAs[string](c, "label")
			if err != nil {
				return err
			}
			c.Print(label)
			return nil
		}),
	)
	if err := app.RunArgs(context.Background(), "show", "--label", "from-flag"); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(strings.TrimSpace(out.String()))
}
Output:
from-flag

func BorderASCII

func BorderASCII() lipgloss.Border

BorderASCII returns a plain ASCII border drawn with +, -, and | characters.

func BorderBlock

func BorderBlock() lipgloss.Border

BorderBlock returns a border drawn with full-block characters.

func BorderDouble

func BorderDouble() lipgloss.Border

BorderDouble returns a double-line Unicode box-drawing border.

func BorderHidden

func BorderHidden() lipgloss.Border

BorderHidden returns a border made of blank characters; it occupies the same width as a real border but renders nothing visible.

func BorderMarkdown

func BorderMarkdown() lipgloss.Border

BorderMarkdown returns a pipe-and-dash border that mimics Markdown table style.

func BorderNormal

func BorderNormal() lipgloss.Border

BorderNormal returns a single-line Unicode box-drawing border (default for Context.Table).

func BorderRounded

func BorderRounded() lipgloss.Border

BorderRounded returns a single-line Unicode border with rounded corners.

func BorderThick

func BorderThick() lipgloss.Border

BorderThick returns a heavy Unicode box-drawing border.

func MultiSelect

func MultiSelect[E comparable](c *Context, prompt string, choices, defaultVal []E, opts ...MultiSelectOption) ([]E, error)

MultiSelect asks for multiple choices when interactive. E is inferred from the choices slice.

MultiSelect is a package-level function (not a method) because Go does not allow type parameters on methods.

Errors:

  • "nabat: multi-select requires interactive terminal" when not interactive
  • *ConfigErrors from option validation
  • errors from the prompt layer

func Select

func Select[E comparable](c *Context, prompt string, choices []E, defaultVal E, opts ...SelectOption) (E, error)

Select asks for one choice from choices when interactive. E is inferred from the choices slice, enabling typed enum selects.

Select is a package-level function (not a method) because Go does not allow type parameters on methods.

Errors:

  • "nabat: select requires interactive terminal" when not interactive and no WithDefault set via a FieldOption[E]
  • *ConfigErrors from option validation
  • errors from the prompt layer

func VersionFromBuildInfo

func VersionFromBuildInfo() string

VersionFromBuildInfo returns the module version stamped by the Go toolchain when the binary was installed via go install (e.g. "v1.2.3"). Falls back to "(devel)" for local builds where no module version is available.

Pass the result directly to WithVersion:

nabat.WithVersion(nabat.VersionFromBuildInfo())

func WithDeprecated

func WithDeprecated(message string, sub ...DeprecationOption) interface {
	CommandOption
	FlagOption
}

WithDeprecated marks a subcommand or a flag as deprecated. cobra prints the composed message when the deprecated symbol is used; the Nabat help renderer also surfaces it in --help output.

The same helper covers commands and flags so callers do not have to learn two parallel APIs. Positional args are NOT supported because cobra/pflag expose no deprecation hook for them — passing this to WithArg is a compile-time error rather than a silent runtime no-op.

Sub-options (WithDeprecatedSince, WithDeprecatedReplacement) compose into the same message string; they do not introduce new runtime behavior.

Examples:

app.MustCommand("legacy",
    WithDeprecated("use `new-cmd` instead",
        WithDeprecatedSince("v0.7.0"),
    ),
    WithRun(handler),
)

WithFlag("config", "",
    WithShort('c'),
    WithDeprecated("use --settings instead",
        WithDeprecatedReplacement("--settings"),
    ),
)
Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("depctl")
	_, err := app.Command("legacy",
		nabat.WithDeprecated("use `new` instead"),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true
Example (Flag)
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("flagdep")
	_, err := app.Command("run",
		nabat.WithFlag("legacy", "", nabat.WithDeprecated("use --new instead")),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true

func WithEnv

func WithEnv(names ...string) interface {
	ArgOption
	FlagOption
}

WithEnv enables environment-variable fallback for an arg or flag. Each name is appended to the WithEnvPrefix prefix when resolving.

Example:

WithArg("environment", "", WithEnv("environment"))
WithFlag("token", "", WithEnv("token"))
Example
package main

import (
	"bytes"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/nabattest"
	"nabat.dev/theme"
)

func main() {
	var out bytes.Buffer
	app, err := nabat.New("envex",
		nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)),
		nabat.WithTheme(theme.Minimal),
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	app.MustCommand("region",
		nabat.WithFlag("region", "", nabat.WithEnv("region")),
		nabat.WithRun(func(c *nabat.Context) error {
			region, bindErr := nabat.BindAs[string](c, "region")
			if bindErr != nil {
				return bindErr
			}
			c.Print(region)
			return nil
		}),
	)
	if err = nabattest.Run(nil, app, []string{"region"}, nabattest.WithEnvVars(map[string]string{
		"ENVEX_REGION": "eu-west-1",
	})); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(strings.TrimSpace(out.String()))
}
Output:
eu-west-1

func WithEnvAlias

func WithEnvAlias(names ...string) interface {
	ArgOption
	FlagOption
}

WithEnvAlias adds verbatim env var names that must NOT receive the WithEnvPrefix prefix. Use for legacy/external env names like `GITHUB_TOKEN`.

func WithFormField

func WithFormField[T FormFieldValue](
	target *T, title, description string, opts ...FieldOption[T],
) interface {
	FormOption
	GroupOption
}

WithFormField adds a typed field to a form or group. T determines the widget kind:

  • string renders a single-line input by default; use WithMultiline for multi-line or WithFilePicker for a file browser.
  • bool renders a confirm widget.
  • Numeric types render a text input with runtime parsing.

The description is rendered as a subtitle beneath the field title; pass "" to omit it.

Use WithSelectField or WithMultiSelectField for select-style widgets.

func WithFormNote

func WithFormNote(title, body string) interface {
	FormOption
	GroupOption
}

WithFormNote adds a display-only note (heading + markdown body) to a form or group. Notes are interactive-only: in non-interactive mode they are silently skipped.

func WithHidden

func WithHidden() interface {
	CommandOption
	FlagOption
}

WithHidden hides a subcommand or a flag from help listings. The command/flag remains invokable; it simply does not appear in --help.

Returns an interface that satisfies CommandOption AND FlagOption, so one helper covers both call sites:

app.MustCommand("internal", WithHidden(), WithRun(func(c *Context) error {
    return nil
}))

WithFlag("secret", false, WithHidden())

For conditional hiding, use a plain Go `if` instead of a dedicated helper:

opts := []CommandOption{WithRun(handler)}
if cfg.hidden {
    opts = append(opts, WithHidden())
}
app.MustCommand("internal", opts...)

Returns the union interface (not a RootOption); passing this directly to New is a build error because the root command has no parent listing to be hidden from.

Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("hidectl")
	_, err := app.Command("secret",
		nabat.WithHidden(),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true

func WithMultiSelectField

func WithMultiSelectField[E comparable](
	target *[]E, title, description string,
	choices, defaultVal []E,
	opts ...MultiSelectOption,
) interface {
	FormOption
	GroupOption
}

WithMultiSelectField adds a multi-select field to a form or group. E is inferred from the choices slice. The defaultVal value is used in non-interactive mode. The description is rendered as a subtitle; pass "" to omit it.

func WithRequired

func WithRequired() interface {
	ArgOption
	FlagOption
}

WithRequired marks an arg or flag as required; resolution fails if no value is found from CLI, env, prompt, or default.

Example (positional):

WithArg("name", "", WithRequired())

Example (flag):

WithFlag("token", "", WithRequired(), WithEnv("token"))

func WithSelectField

func WithSelectField[E comparable](
	target *E, title, description string,
	choices []E, defaultVal E,
	opts ...SelectOption,
) interface {
	FormOption
	GroupOption
}

WithSelectField adds a single-select field to a form or group. E is inferred from the choices slice and target pointer. The defaultVal value is used in non-interactive mode. The description is rendered as a subtitle; pass "" to omit it.

func WithUsage

func WithUsage(text string) interface {
	ArgOption
	FlagOption
}

WithUsage sets the usage text shown next to an arg or flag in --help.

Types

type App

type App struct {
	// contains filtered or unexported fields
}

App is the root CLI application for a single binary.

It owns the underlying Cobra root command, global configuration, installed extensions (see WithExtension), and the cached color profile used for output.

Construct an App with New or MustNew. Pass Option values to configure it: app-level options (WithTheme, WithIO, WithLogger, WithExtension, WithVersion, the With(out)Help* family), RootOption values to configure the root command directly (WithDescription, WithFlag, WithRun, etc.), and WithCommand to declare subcommands. The entire command tree can be declared in one call to New; every registration error aggregates into the returned error. For runtime/dynamic registration, use App.Command (returns `(*Command, error)`) or App.MustCommand (panics on failure). Run the program with App.Run.

An App is safe to use from multiple goroutines only AFTER registration is complete. All command and flag registration — including extensions calling App.Command or App.MustCommand from inside [Extension.Init] — must happen before App.Run (or App.RunArgs) is invoked, and before any goroutine forks. Concurrent registration is not supported. Calls to App.Command / Command.Command after App.Run return an error; App.MustCommand / Command.MustCommand panic with the same message.

func MustNew

func MustNew(name string, opts ...Option) *App

MustNew is like New but panics on construction failure. Use New when errors should propagate (tests and libraries).

Panics if name is empty, any Option is nil, or any WithCommand / extension registration aggregated by New fails.

Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("demo")
	fmt.Println(app != nil)
}
Output:
true

func New

func New(name string, opts ...Option) (*App, error)

New constructs an App with the given CLI name and options.

The name becomes the root command's primary word and the default environment variable prefix (uppercased with a trailing underscore) unless WithEnvPrefix overrides it.

Construction order is fixed: options are applied to the config (including RootOption values that configure the root command's spec directly), the theme is resolved, the root command is built, help, version, and completion are installed, root flags are registered on Cobra, and finally [Extension.Init] runs for every WithExtension in declaration order. Errors from any phase short-circuit and are returned.

Errors:

  • ErrNilOption: a nil entry appears in opts
  • wrapped errors from any Option or from root command configuration
  • "nabat: app name cannot be empty", stdin/stdout/stderr cannot be nil, or other messages aggregated in a *ConfigErrors value
  • errors from registering flags or validating flag definitions
  • "nabat: extension <name>: ..." when an [Extension.Init] returns an error
Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app, err := nabat.New("myctl")
	if err != nil {
		fmt.Println(err)
		return
	}
	if app == nil {
		fmt.Println("nil")
		return
	}
	fmt.Println("ready")
}
Output:
ready
Example (NilOption)
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	_, err := nabat.New("myctl", nil)
	fmt.Println(err != nil)
}
Output:
true
Example (RootCommand)
package main

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/theme"
)

func main() {
	var out bytes.Buffer
	app, err := nabat.New("rootctl",
		nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)),
		nabat.WithTheme(theme.Minimal),
		nabat.WithDescription("demo root"),
		nabat.WithRun(func(c *nabat.Context) error {
			c.Print("root-run")
			return nil
		}),
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	if err = app.RunArgs(context.Background()); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(strings.TrimSpace(out.String()))
}
Output:
root-run
Example (SingleCommand)

Single-command CLI declared with the flat declarative form: root config and the run handler all live as direct options on nabat.New, no separate subcommand needed.

package main

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/theme"
)

func main() {
	var out bytes.Buffer
	app, err := nabat.New("greet",
		nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)),
		nabat.WithTheme(theme.Minimal),
		nabat.WithDescription("Print a friendly greeting"),
		nabat.WithArg("name", "world"),
		nabat.WithRun(func(c *nabat.Context) error {
			name, err := nabat.BindAs[string](c, "name")
			if err != nil {
				return err
			}
			c.Print("hello, " + name)
			return nil
		}),
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	if err = app.RunArgs(context.Background()); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(strings.TrimSpace(out.String()))
}
Output:
hello, world

func (*App) Command

func (a *App) Command(name string, opts ...CommandOption) (*Command, error)

Command creates a direct subcommand under the app's root command.

Returns the new command and a non-nil error if registration fails (empty name, nil CommandOption, invalid options, name collisions, flag-registration errors, etc.). On error, no command is registered and the returned *Command is nil — callers must handle the error before chaining further registrations.

For aggregated registration errors (multiple commands surfaced together with option errors and validation errors), declare commands with WithCommand inside New instead. For panicking chains in main() or test setup, use App.MustCommand.

Errors:

Example
package main

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/theme"
)

func main() {
	var out bytes.Buffer
	app, err := nabat.New("shipctl",
		nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)),
		nabat.WithTheme(theme.Minimal),
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	app.MustCommand("status",
		nabat.WithDescription("Show deployment status"),
		nabat.WithFlag("env", "staging"),
		nabat.WithRun(func(c *nabat.Context) error {
			env, bindErr := nabat.BindAs[string](c, "env")
			if bindErr != nil {
				return bindErr
			}
			c.Print("env=" + env)
			return nil
		}),
	)
	if err = app.RunArgs(context.Background(), "status", "--env", "production"); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(strings.TrimSpace(out.String()))
}
Output:
env=production

func (*App) EnvPrefix

func (a *App) EnvPrefix() string

EnvPrefix returns the App's configured env-variable prefix (set by WithEnvPrefix; defaults to UPPER(name)+"_").

func (*App) IO

func (a *App) IO() *IOStreams

IO returns the stdin/stdout/stderr bundle and terminal capability detection for this app. The result is never nil for a successfully constructed App.

func (*App) MustCommand

func (a *App) MustCommand(name string, opts ...CommandOption) *Command

MustCommand is like App.Command but panics on registration failure.

Use MustCommand in main() and test setup where chaining (`app.MustCommand("cluster").MustCommand("scale", ...)`) is more readable than scoped error checks. A registration error here is a programmer bug, not runtime input. Mirrors the relationship between MustNew and New.

Panics if App.Command returns an error. See its documentation for the full per-error list.

func (*App) Name

func (a *App) Name() string

Name returns the app's name (the value passed to New).

func (*App) OnPreRun

func (a *App) OnPreRun(fn func(*Context) error) error

OnPreRun registers a global pre-run hook that fires before every command's handler, in registration order. Global hooks run before the command's own preRun hooks. The framework manages ordering; extensions never need to chain hooks manually.

If the hook returns ErrHandled, the command handler and remaining hooks are skipped and App.Run returns nil (success).

Returns ErrNilOption if fn is nil.

func (*App) Run

func (a *App) Run(ctx context.Context) error

Run parses os.Args and executes the matching command using ctx as the base context.Context for each Context passed to RunFunc.

On failure (except when help or version output was handled internally), Run prints a styled error message and a usage hint to stderr before returning the error, unless WithErrorHandler replaced that behavior.

Errors:

  • errors returned by Cobra for unknown commands, usage, or flag parsing
  • errors from resolving positional args or flags, validation hooks, or the command handler
Example
package main

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/theme"
)

func main() {
	var out bytes.Buffer
	app := nabat.MustNew("runctl",
		nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)),
		nabat.WithTheme(theme.Minimal),
	)
	app.MustCommand("ping",
		nabat.WithRun(func(c *nabat.Context) error {
			c.Print("pong")
			return nil
		}),
	)
	// In a real program, App.Run reads os.Args. RunArgs is used here so the
	// example is self-contained and runnable by go test.
	if err := app.RunArgs(context.Background(), "ping"); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(strings.TrimSpace(out.String()))
}
Output:
pong

func (*App) RunArgs

func (a *App) RunArgs(ctx context.Context, args ...string) error

RunArgs parses args as the command line instead of os.Args, then executes the matching command using ctx as the base context.Context.

This is useful for tests, embedded CLIs, and programs that construct their own argument lists at runtime. In tests prefer the [nabattest] package which wraps this method with testing.TB helpers.

Errors and stderr behavior match those described on App.Run.

Example
package main

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/theme"
)

func main() {
	var out bytes.Buffer
	app := nabat.MustNew("argctl",
		nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)),
		nabat.WithTheme(theme.Minimal),
	)
	app.MustCommand("echo",
		nabat.WithFlag("msg", "default"),
		nabat.WithRun(func(c *nabat.Context) error {
			msg, err := nabat.BindAs[string](c, "msg")
			if err != nil {
				return err
			}
			c.Print(msg)
			return nil
		}),
	)
	if err := app.RunArgs(context.Background(), "echo", "--msg", "hello"); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(strings.TrimSpace(out.String()))
}
Output:
hello

func (*App) SetLogger

func (a *App) SetLogger(l *slog.Logger)

SetLogger installs the structured logger returned by Context.Logger during command invocations. This is the plugin-facing setter; user code should generally use WithLogger at construction time instead.

Calling SetLogger after WithLogger replaces the previously installed logger. Passing nil resets to the discard logger returned by Context.Logger when no logger is configured.

SetLogger is not safe for concurrent use and must be called before App.Run. Extensions install their logger from [Extension.Init], which runs inside New before any command can execute.

func (*App) Theme

func (a *App) Theme() theme.ResolvedTheme

Theme returns the resolved theme installed via WithTheme or WithCustomTheme (or the built-in default if neither was used). The returned theme.ResolvedTheme is immutable; consumers query it by theme.Token for lipgloss.Style values, or by accessor for the chroma name, glamour name, huh.Theme, list enumerator, and table border. Extensions read from this same value via the public method — they should never reach into [App.cfg].

func (*App) UnsafeRoot

func (a *App) UnsafeRoot() *cobra.Command

UnsafeRoot returns the underlying *cobra.Command for the App's root. This is an escape hatch for operations that Nabat does not abstract (e.g. Cobra completion generators, command tree traversal for man page rendering). Prefer App.Command, App.MustCommand, and the App-level options (WithVersion, the With(out)Help* family) for standard use.

Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("rootacc")
	fmt.Println(app.UnsafeRoot() != nil)
}
Output:
true

type AppSurface

type AppSurface interface {
	// Command registration
	Command(name string, opts ...CommandOption) (*Command, error)
	MustCommand(name string, opts ...CommandOption) *Command
	OnPreRun(fn func(*Context) error) error

	// Identity
	Name() string
	EnvPrefix() string

	// Output handles
	IO() *IOStreams
	Theme() theme.ResolvedTheme
	SetLogger(l *slog.Logger)

	// UnsafeRoot is the escape hatch for extensions that need to traverse or
	// inspect the command tree (for example, man-page generation). Prefer the
	// typed surface above for all standard use.
	UnsafeRoot() *cobra.Command
}

AppSurface is the interface extensions interact with during [Extension.Init]. It is a strict subset of *App; extensions that reach outside it won't compile, making the extension boundary enforceable by the type system.

The *App type satisfies AppSurface, so existing code that holds a *App can be passed wherever AppSurface is required.

type ArgOption

type ArgOption interface {
	// contains filtered or unexported methods
}

ArgOption configures one declarative positional argument. It is satisfied by every helper that targets WithArg, WithSelectArg, or WithMultiSelectArg — for example WithRequired, WithUsage, WithEnv, and the generic WithPrompt helper.

ArgOption is an interface (not a function alias) so misuse such as passing a flag-only option (e.g. WithShort) to WithArg is rejected by the Go compiler at the call site instead of failing later at registration time or being silently ignored at runtime.

Options return an error so validation failures (invalid values, bad combinations) aggregate into a ConfigErrors at command build time.

func ArgOptions

func ArgOptions(opts ...ArgOption) ArgOption

ArgOptions composes multiple ArgOption values into one. Options apply in slice order on the same arg slot.

Errors:

  • ErrNilOption: a nil entry appears in opts
  • errors from individual options
Example

ExampleArgOptions shows bundling multiple arg options for reuse.

package main

import (
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/nabattest"
)

func main() {
	io, _, out, _ := nabattest.NewIO()
	app, err := nabat.New("myctl", nabat.WithIO(io),
		nabat.WithCommand("deploy",
			nabat.WithArg("env", "staging",
				nabat.ArgOptions(
					nabat.WithUsage("deployment environment"),
					nabat.WithEnv("env"),
				),
			),
			nabat.WithRun(func(c *nabat.Context) error {
				v, err := nabat.BindAs[string](c, "env")
				if err != nil {
					return err
				}
				c.Print(v)
				return nil
			}),
		),
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	if err = app.RunArgs(context.Background(), "deploy"); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(strings.TrimSpace(out.String()))
}
Output:
staging

func WithPrompt

func WithPrompt[T any](title, description string, opts ...FieldOption[T]) ArgOption

WithPrompt attaches an interactive prompt to a declarative arg. T is inferred from the arg's bind type, so no explicit type annotation is needed:

nabat.WithArg("name", "", nabat.WithPrompt("Your name", "",
    nabat.WithHint("alice"),
    nabat.WithDefault("anon"),
))

nabat.WithArg("force", false, nabat.WithPrompt("Force overwrite?", "",
    nabat.WithAffirmative("Yes"),
    nabat.WithNegative("Cancel"),
))

The description argument is rendered as a subtitle beneath the title; pass "" to omit it.

Kind-specific sub-options are constrained by the phantom T so misuse like WithEditor on a bool prompt fails at compile time.

type ArgValue

type ArgValue interface {
	string | bool | int | int64 | uint | float64 | time.Duration | []string
}

ArgValue is the set of value kinds accepted by WithArg.

type ArityOption

type ArityOption func(*arityConfig)

ArityOption configures positional argument count validation for Cobra's cobra.Command.Args. Use with WithArgArity. Nabat maps these to Cobra validators internally.

func WithExactArgCount

func WithExactArgCount(n int) ArityOption

WithExactArgCount requires exactly n positional arguments before any "--" separator.

Example:

WithArgArity(WithExactArgCount(2))

func WithMaxArgCount

func WithMaxArgCount(n int) ArityOption

WithMaxArgCount allows at most n positional arguments.

Example:

WithArgArity(WithMaxArgCount(1))

func WithMinArgCount

func WithMinArgCount(n int) ArityOption

WithMinArgCount requires at least n positional arguments.

Example:

WithArgArity(WithMinArgCount(1), WithMaxArgCount(3))

type Command

type Command struct {
	// contains filtered or unexported fields
}

Command is a Nabat view of one node in the command tree backed by Cobra.

Use App.Command and Command.Command to create instances. The wrapped Cobra command is used internally for parsing and execution.

func (*Command) Command

func (c *Command) Command(name string, opts ...CommandOption) (*Command, error)

Command creates a child command under this command. Same error contract as App.Command: returns the new command and a non-nil error if registration fails. On error, no child is added and the returned *Command is nil.

Errors:

func (*Command) MustCommand

func (c *Command) MustCommand(name string, opts ...CommandOption) *Command

MustCommand is the panicking variant of Command.Command, mirroring App.MustCommand. Use in main() and test setup where chaining (`cluster.MustCommand("scale").MustCommand("up", ...)`) is more readable than scoped error checks.

Panics if Command.Command returns an error. See its documentation for the full per-error list.

func (*Command) OnPostRun

func (c *Command) OnPostRun(fn func(*Context) error) error

OnPostRun registers a post-run hook. It is only valid before App.Run.

func (*Command) OnPreRun

func (c *Command) OnPreRun(fn func(*Context) error) error

OnPreRun registers a command-level pre-run hook. It is only valid before App.Run.

func (*Command) OnValidate

func (c *Command) OnValidate(fn func(*Context) error) error

OnValidate registers a validation hook. It is only valid before App.Run.

func (*Command) UnsafeCobra

func (c *Command) UnsafeCobra() *cobra.Command

UnsafeCobra returns the underlying *cobra.Command for operations that Nabat does not abstract (for example C-only Cobra hooks or dynamic registration). Mutating the command tree after construction may bypass Nabat invariants — prefer Command.Command / Command.MustCommand and the CommandOption family (e.g. WithRun, WithArg, WithFlag) for standard use.

Example:

parent := app.MustCommand("exec", WithRun(func(c *Context) error { return nil }))
raw := parent.UnsafeCobra()
raw.Annotations = map[string]string{"x": "y"}
Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	cmd := nabat.MustNew("cobacc").MustCommand("leaf", nabat.WithRun(func(c *nabat.Context) error { return nil }))
	fmt.Println(cmd.UnsafeCobra() != nil)
}
Output:
true

type CommandOption

type CommandOption interface {
	// contains filtered or unexported methods
}

CommandOption configures a subcommand when passed to App.Command or Command.Command. Every value returned by a `With*` constructor in this package satisfies CommandOption.

CommandOption is the wider of nabat's two command-option types:

  • RootOption is the strict subset of CommandOption that is also valid on the root command. Every RootOption also satisfies Option and so can be passed directly to New to configure the root.
  • CommandOption (this type) accepts every option, including those that have no meaningful effect on the root command (such as WithGroup, WithHidden, WithAliases, WithTypoHints).

Use the `With*` functions in this package to build a list of options.

Passing a nil CommandOption to App.Command or Command.Command returns an inline error (or panics, in App.MustCommand / Command.MustCommand).

func CommandOptions

func CommandOptions(opts ...CommandOption) CommandOption

CommandOptions composes multiple CommandOption values into one. Options apply in slice order.

Errors:

  • ErrNilOption: a nil entry appears in opts
  • errors from individual options

func WithAliases

func WithAliases(aliases ...string) CommandOption

WithAliases sets command aliases used by the parent command's lookup and suggestion logic (see cobra.Command.Aliases).

Returns a CommandOption, not a RootOption; passing this directly to New is a build error because the binary's invocation name is set by the OS executable name, so cobra cannot honor aliases on the root command.

func WithCommandInit

func WithCommandInit(fn func(*Command) error) CommandOption

WithCommandInit is a CommandOption that runs after the command is built, receiving the live *Command (for example to call Command.OnPreRun).

func WithGroup

func WithGroup(name string) CommandOption

WithGroup sets a command group identifier used to visually group commands in help output.

Returns a CommandOption, not a RootOption; passing this directly to New is a build error because the root command has no parent listing to be grouped under.

func WithTypoHints

func WithTypoHints(aliases ...string) CommandOption

WithTypoHints sets suggested spellings when the user mistypes the subcommand name (see cobra.Command.SuggestFor).

Returns a CommandOption, not a RootOption; passing this directly to New is a build error because suggestion lookup iterates a command's siblings and the root command has none.

Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("hintctl")
	_, err := app.Command("migrate",
		nabat.WithTypoHints("migr", "migration"),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true

type CompletionDirective

type CompletionDirective = cobra.ShellCompDirective

CompletionDirective signals to the shell how to handle a CompletionFunc result. The constants mirror Cobra's directives so behavior is identical; the alias type lets the public option signatures stay free of cobra imports.

const (
	// CompletionDefault delegates to the shell's default completion.
	CompletionDefault CompletionDirective = cobra.ShellCompDirectiveDefault
	// CompletionError indicates an error while computing completions.
	CompletionError CompletionDirective = cobra.ShellCompDirectiveError
	// CompletionNoSpace prevents the shell from appending a trailing space.
	CompletionNoSpace CompletionDirective = cobra.ShellCompDirectiveNoSpace
	// CompletionNoFileComp suppresses file completion when no candidates match.
	CompletionNoFileComp CompletionDirective = cobra.ShellCompDirectiveNoFileComp
	// CompletionFilterFileExt restricts file completion to the listed extensions.
	CompletionFilterFileExt CompletionDirective = cobra.ShellCompDirectiveFilterFileExt
	// CompletionFilterDirs restricts file completion to directories.
	CompletionFilterDirs CompletionDirective = cobra.ShellCompDirectiveFilterDirs
	// CompletionKeepOrder preserves the order in which completions are returned.
	CompletionKeepOrder CompletionDirective = cobra.ShellCompDirectiveKeepOrder
)

type CompletionFunc

type CompletionFunc func(args []string, toComplete string) ([]string, CompletionDirective)

CompletionFunc returns shell completion candidates for an arg or flag value. args contains the command-line tokens before the cursor (excluding the partial token); toComplete is the partial token under the cursor.

type CompletionOption

type CompletionOption func(*completionConfig) error

CompletionOption configures the built-in completion feature inside WithCompletion.

func WithCompletionHidden

func WithCompletionHidden() CompletionOption

WithCompletionHidden hides the completion subcommand from help listings. The command remains invokable; it just does not appear in `--help` output.

func WithCompletionName

func WithCompletionName(name string) CompletionOption

WithCompletionName overrides the subcommand name (default "completion"). Empty strings return ErrInvalidOption; omit WithCompletion entirely to disable the subcommand surface.

func WithCompletionShells

func WithCompletionShells(shells ...string) CompletionOption

WithCompletionShells restricts the installed generators to the listed shells. When omitted, all four (bash, zsh, fish, powershell) are installed. Unknown shell names return ErrInvalidOption.

type ConfigErrors

type ConfigErrors struct {
	// contains filtered or unexported fields
}

ConfigErrors collects configuration validation failures into one error value.

New returns a *ConfigErrors when app-level validation fails after options are applied. Use errors.Is and errors.As with ConfigErrors.Unwrap to inspect individual wrapped errors, or call ConfigErrors.Error for a human-readable summary.

Example
package main

import (
	"errors"
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	_, err := nabat.New("")
	if err == nil {
		fmt.Println("unexpected")
		return
	}
	var ce *nabat.ConfigErrors
	if !errors.As(err, &ce) || len(ce.Unwrap()) == 0 {
		fmt.Println("not ConfigErrors")
		return
	}
	fmt.Println(ce.Unwrap()[0])
}
Output:
nabat: app name cannot be empty

func (*ConfigErrors) AddErr

func (e *ConfigErrors) AddErr(err error)

AddErr appends a pre-formed error, preserving its wrapping chain so errors.Is and errors.As can match individual issues after ConfigErrors.Unwrap.

func (*ConfigErrors) Error

func (e *ConfigErrors) Error() string

Error implements the error interface.

func (*ConfigErrors) HasIssues

func (e *ConfigErrors) HasIssues() bool

HasIssues reports whether one or more validation issues exist.

func (*ConfigErrors) Unwrap

func (e *ConfigErrors) Unwrap() []error

Unwrap returns each underlying issue so errors.Is and errors.As can inspect a ConfigErrors value as a multi-error.

type Context

type Context struct {
	// contains filtered or unexported fields
}

Context is the per-invocation runtime passed to each RunFunc.

It holds the resolved values for declared positional args and flags, the raw positional argument strings, and the App that created the command. Use Context.Bind (or BindAs) to read resolved values into a struct or a single typed name.

Context implements context.Context by delegating to the context passed to App.Run (or context.Background if none was set on the Cobra command).

Context is not safe for concurrent use. Each command invocation receives its own Context; do not share it across goroutines or retain it after the RunFunc returns.

Example (Output)

ExampleContext_output demonstrates semantic output helpers. stdout and stderr share one buffer here because go test's // Output: directive captures only stdout; see [nabattest.NewIO] for tests that must keep the streams separate.

package main

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/theme"
)

func main() {
	var out bytes.Buffer
	app := nabat.MustNew("outctl",
		nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)),
		nabat.WithTheme(theme.Minimal),
	)
	app.MustCommand("report",
		nabat.WithRun(func(c *nabat.Context) error {
			c.Success("done", "id", 42)
			c.Warn("late", "ms", 9)
			return nil
		}),
	)
	if err := app.RunArgs(context.Background(), "report"); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(out.String())
}
Output:
✓ done id=42
⚠ late ms=9

func (*Context) Args

func (c *Context) Args() []string

Args returns a copy of the positional arguments before any "--". The returned slice is always non-nil; iterate or take len without a nil check. Arguments after "--" are accessible via Context.Passthrough.

func (*Context) Bind

func (c *Context) Bind(target any) error

Bind copies resolved positional arg and flag values into target, which must be a non-nil pointer to a struct.

For each exported field tagged `nabat:"name"`, Bind assigns the resolved value for name when one exists. Fields without a tag are skipped. Anonymous embedded structs without a nabat tag are walked recursively.

Value fields (int, string, time.Duration, …) are always assigned when a value exists in the context, including registered defaults when the user did not supply a value.

Pointer fields (*int, *string, *time.Duration, …) encode optionality: the field stays nil when the value was resolved only from a default or when the name was never resolved. When the user supplied the value via CLI, environment variable, or interactive prompt, Bind allocates a pointer and stores the resolved value.

Errors:

  • "nabat: bind target: context is nil" when c is nil
  • "nabat: bind target must be a non-nil pointer to struct" when target is not a non-nil *struct
  • "nabat: bind target must be a pointer to struct" when target is not a pointer to struct
  • "nabat: bind field ...: field is not settable" for unexported or non-settable fields
  • "nabat: bind field ...: expected ..., got <nil>" when the resolved value is nil
  • "nabat: bind field ...: expected T, got U" when the resolved type does not assign to the field
  • "nabat: bind field ...: tag ... does not match any declared arg or flag" when the tag names no field on the current command (typo or stale struct)
Example
package main

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
)

func main() {
	var out bytes.Buffer
	app := nabat.MustNew("bindctl", nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)))
	app.MustCommand("show",
		nabat.WithFlag("region", "us-east"),
		nabat.WithRun(func(c *nabat.Context) error {
			var cfg struct {
				Region string `nabat:"region"`
			}
			if err := c.Bind(&cfg); err != nil {
				return err
			}
			c.Print(cfg.Region)
			return nil
		}),
	)
	if err := app.RunArgs(context.Background(), "show"); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(out.String())
}
Output:
us-east

func (*Context) Confirm

func (c *Context) Confirm(prompt string, opts ...FieldOption[bool]) (bool, error)

Confirm asks a yes/no question when Context.IsInteractive is true.

Errors:

  • "nabat: confirm requires interactive terminal" when not interactive and no WithDefault is set
  • *ConfigErrors from option validation
  • errors from the prompt layer

func (*Context) Deadline

func (c *Context) Deadline() (time.Time, bool)

Deadline reports the current deadline, if any.

func (*Context) Done

func (c *Context) Done() <-chan struct{}

Done returns a channel closed when work should be canceled.

func (*Context) Encode

func (c *Context) Encode(v any, f Format) error

Encode writes v using FormatJSON, FormatYAML, or FormatTOML, delegating to Context.JSON, Context.YAML, or Context.TOML.

Example:

return c.Encode(payload, FormatJSON)

Errors:

  • "nabat: unknown format %q" when f is not one of the Format constants
  • errors from the selected encoder
Example
package main

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/theme"
)

func main() {
	var out bytes.Buffer
	app := nabat.MustNew("encctl",
		nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)),
		nabat.WithTheme(theme.Minimal),
	)
	app.MustCommand("dump",
		nabat.WithRun(func(c *nabat.Context) error {
			v := struct {
				Hello string `json:"hello"`
			}{Hello: "world"}
			return c.Encode(&v, nabat.FormatJSON)
		}),
	)
	if err := app.RunArgs(context.Background(), "dump"); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(strings.TrimSpace(out.String()))
}
Output:
{
  "hello": "world"
}

func (*Context) Err

func (c *Context) Err() error

Err reports why Done was closed.

func (*Context) Error

func (c *Context) Error(msg string, args ...any)

Error writes msg to stderr with an error symbol and optional key/value pairs.

Diagnostics go to stderr (POSIX) so error messages do not corrupt downstream pipeline data on stdout.

Example:

c.Error("deploy blocked", "reason", err)

func (*Context) Explicit

func (c *Context) Explicit(name string) bool

Explicit reports whether the named arg or flag was provided via the command line, environment variable, or interactive prompt, as opposed to using only a registered default. Use it when handler logic must distinguish "user supplied" from "default".

func (*Context) FilePicker

func (c *Context) FilePicker(prompt string, opts ...FieldOption[string]) (string, error)

FilePicker collects a file path when interactive. This is equivalent to c.Input with WithFilePicker, provided as a convenience method.

Errors:

  • "nabat: file picker requires interactive terminal" when not interactive and no WithDefault is set
  • *ConfigErrors from option validation
  • errors from the prompt layer

func (*Context) Form

func (c *Context) Form(opts ...FormOption) error

Form runs a typed form built from FormOption values. In interactive mode, Huh widgets are built and presented to the user. In non-interactive mode, every field must have a default (set with WithDefault); missing defaults are aggregated into a *ConfigErrors. Use Context.UnsafeForm when the declarative API cannot express the layout.

Errors:

  • *ConfigErrors when option application or fallback walk fails
  • errors from the underlying Huh form in interactive mode

func (*Context) HasPassthrough

func (c *Context) HasPassthrough() bool

HasPassthrough reports whether "--" appeared on the command line, even when no tokens followed it. Pair with Context.Passthrough to read the tokens.

func (*Context) Highlight

func (c *Context) Highlight(code, lang string) error

Highlight writes code to [Context.IO.Out] using a Chroma lexer named lang. When the lexer or formatter is unavailable, or when the [Theme] disables chroma, it writes the original code unchanged.

Errors:

  • errors from writing to [Context.IO.Out]

func (*Context) IO

func (c *Context) IO() *IOStreams

IO returns the stdin/stdout/stderr bundle for this invocation. The result matches App.IO on the parent app and is never nil during a normal RunFunc.

func (*Context) Info

func (c *Context) Info(msg string, args ...any)

Info writes msg to stderr with an info symbol and optional key/value pairs.

Info messages are status narrative ("retrying", "connecting", "skipped") and so travel on the diagnostics stream alongside Context.Success, Context.Warn, and Context.Error. This keeps stdout reserved for the command's product, matching clig.dev's "send status output to stderr" guidance and so a piped stdout stays uncorrupted.

Example:

c.Info("retrying", "attempt", n)

func (*Context) Input

func (c *Context) Input(prompt string, opts ...FieldOption[string]) (string, error)

Input asks for one string value when Context.IsInteractive is true.

Errors:

  • "nabat: input requires interactive terminal" when not interactive and no WithDefault is set
  • *ConfigErrors from option validation
  • errors from the prompt layer

func (*Context) IsInteractive

func (c *Context) IsInteractive() bool

IsInteractive reports whether stdin/stdout are both terminals.

func (*Context) JSON

func (c *Context) JSON(v any) error

JSON writes v as indented JSON to [Context.IO.Out], using the active [Theme] chroma style when set, or plain text when highlighting is disabled.

Errors:

func (*Context) List

func (c *Context) List(items []string, opts ...ListOption)

List prints a styled list to the command's output writer. It applies the current [Theme] list styles by default. Pass ListOption values to override the enumerator or styling.

Example:

c.List([]string{"Foo", "Bar", "Baz"})

c.List(items,
	WithListEnumerator(ListRoman),
	WithListEnumeratorStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("#FF79C6"))),
)

func (*Context) Logger

func (c *Context) Logger() *slog.Logger

Logger returns the structured logger for this command invocation.

The logger is sourced from (in order of precedence):

func (*Context) Markdown

func (c *Context) Markdown(content string) error

Markdown renders content as markdown and writes it to [Context.IO.Out]. When stdout is a terminal, glamour applies styles from the active theme's glamour accessor (see theme.ResolvedTheme.Glamour and theme.ResolvedTheme.GlamourName). When stdout is not a terminal, the raw markdown is written.

Markdown returns a non-nil error only if writing the rendered output fails. If glamour fails to initialize, [App.renderMarkdown] falls back to raw content.

func (*Context) Passthrough

func (c *Context) Passthrough() []string

Passthrough returns a copy of the tokens that appeared after "--", or an empty slice when none did. The returned slice is always non-nil. Use Context.HasPassthrough to tell whether "--" appeared on the command line at all (including with no tokens after it). Declare passthrough support on the command with WithPassthrough to document it in help output.

func (*Context) Print

func (c *Context) Print(msg string)

Print writes msg as plain text to [Context.IO.Out] without an implicit newline. Use Context.Println for a newline-terminated line.

func (*Context) Printf

func (c *Context) Printf(format string, args ...any)

Printf writes formatted text to [Context.IO.Out] without an implicit newline.

func (*Context) Println

func (c *Context) Println(msg string)

Println writes msg followed by a newline to [Context.IO.Out].

Example:

c.Println("plain status line")

func (*Context) ProgressBar

func (c *Context) ProgressBar(total int, opts ...ProgressBarOption) (*ProgressBar, error)

ProgressBar creates a bar for total logical steps. Call ProgressBar.Increment, ProgressBar.Add, or ProgressBar.Set to advance it, then ProgressBar.Done when finished.

total must be > 0; option errors and an invalid total are aggregated into a ConfigErrors returned without constructing the bar.

Errors:

  • "nabat: ProgressBar: total must be > 0" when total <= 0
  • errors returned by any ProgressBarOption

Example:

bar, err := c.ProgressBar(len(files))
if err != nil {
    return err
}
for _, f := range files {
    process(f)
    bar.Increment()
}
bar.Done()

func (*Context) Spinner

func (c *Context) Spinner(title string, fn func() error, opts ...SpinnerOption) error

Spinner runs fn while showing a spinner on stderr in interactive terminals.

Errors:

  • any error returned by fn
  • errors from applying SpinnerOption values
  • errors from the underlying spinner implementation

func (*Context) Success

func (c *Context) Success(msg string, args ...any)

Success writes msg to stderr with a success symbol and optional key/value pairs.

Status and confirmation messages travel on the diagnostics stream so a piped stdout (e.g. `mycli deploy --json | jq`) carries only the command's product. The "product" itself goes through Context.Print, Context.JSON, Context.Table, etc., which write to [Context.IO.Out].

Example:

c.Success("deployed", "environment", env)

func (*Context) TOML

func (c *Context) TOML(v any) error

TOML writes v as TOML to [Context.IO.Out], with highlighting behavior like Context.JSON.

Errors:

  • "nabat: toml encoding failed: ..." when encoding fails
  • errors from writing to [Context.IO.Out]

func (*Context) Table

func (c *Context) Table(headers []string, rows [][]string, opts ...TableOption)

Table prints a styled table to the command's output writer. It applies the current [Theme] table styles by default. Pass TableOption values to override borders, styling, width, or wrapping.

Example:

c.Table([]string{"Name", "Status"}, [][]string{
	{"api", "running"},
	{"web", "stopped"},
})

c.Table(headers, rows,
	WithTableBorder(BorderRounded()),
	WithTableWidth(80),
)

func (*Context) TextInput

func (c *Context) TextInput(prompt string, opts ...FieldOption[string]) (string, error)

TextInput collects multi-line text when interactive. This is equivalent to c.Input with WithMultiline, provided as a convenience method.

Errors:

  • "nabat: text input requires interactive terminal" when not interactive and no WithDefault is set
  • *ConfigErrors from option validation
  • errors from the prompt layer

func (*Context) Tree

func (c *Context) Tree(root string, children []TreeNode, opts ...TreeOption)

Tree prints a styled tree to the command's output writer. It applies the current [Theme] tree styles by default. Pass TreeOption values to override the enumerator, indenter, or styling.

Example:

c.Tree("myproject", []TreeNode{
	{Value: ".git"},
	{Value: "cmd/", Children: []TreeNode{
		{Value: "root.go"},
		{Value: "serve.go"},
	}},
	{Value: "main.go"},
})

c.Tree("root", nodes,
	WithTreeEnumerator(TreeRoundedEnumerator()),
	WithTreeRootStyle(lipgloss.NewStyle().Bold(true)),
)

func (*Context) UnsafeForm

func (c *Context) UnsafeForm(fields ...huh.Field) error

UnsafeForm runs a Huh form built from raw huh.Field values. Use this escape hatch when the typed form API cannot express a specific layout.

Errors:

  • "nabat: form requires interactive terminal" when not interactive
  • errors from the underlying Huh form

func (*Context) Value

func (c *Context) Value(key any) any

Value looks up a value in the underlying Go context.

func (*Context) Warn

func (c *Context) Warn(msg string, args ...any)

Warn writes msg to stderr with a warning symbol and optional key/value pairs.

Diagnostics go to stderr (POSIX) so they remain visible when the command's stdout is piped or redirected (e.g. `mycli deploy | jq`).

Example:

c.Warn("slow query", "ms", elapsed)

func (*Context) YAML

func (c *Context) YAML(v any) error

YAML writes v as YAML to [Context.IO.Out], with highlighting behavior like Context.JSON.

Errors:

  • "nabat: yaml encoding failed: ..." when marshaling fails
  • errors from writing to [Context.IO.Out]

type DeprecationOption

type DeprecationOption interface {
	// contains filtered or unexported methods
}

DeprecationOption refines the message produced by WithDeprecated and WithDeprecatedShorthand. Sub-options compose into the single deprecation string that Cobra/pflag prints when the deprecated symbol is used and that the Nabat help renderer surfaces in --help output.

Sub-options are intentionally narrow: cobra/pflag exposes only one deprecation message per command/flag/shorthand, so the helpers in this family (WithDeprecatedSince, WithDeprecatedReplacement) augment that string rather than introducing new runtime behavior.

func WithDeprecatedReplacement

func WithDeprecatedReplacement(symbol string) DeprecationOption

WithDeprecatedReplacement names the symbol that should be used instead of the deprecated one. The symbol is appended to the deprecation message as "; use <symbol> instead".

Example:

WithDeprecated("legacy",
    WithDeprecatedReplacement("--output"),
)

func WithDeprecatedSince

func WithDeprecatedSince(version string) DeprecationOption

WithDeprecatedSince annotates a deprecation with the version (or date) the symbol was deprecated. The version string is appended to the deprecation message as " (since <version>)".

Example:

WithDeprecated("use --output instead",
    WithDeprecatedSince("v0.7.0"),
)

type Extension

type Extension interface {
	fmt.Stringer
	Init(AppSurface) error
}

Extension contributes subcommands, hooks, or a logger to an App during construction. Extensions are installed via WithExtension and their [Init] method runs inside New after the root command and core features (help, version, completion) are wired up.

First-party extensions live under nabat.dev subpackages (manpage, logging) and are constructed by their package's New() function, which returns (Extension, error). Third-party extensions follow the same pattern.

Invariant

Extensions install subcommands (via [AppSurface.Command]) and global hooks (via [AppSurface.OnPreRun]), and may set the logger (via [AppSurface.SetLogger]). They MUST NOT modify the root command or any command they did not create.

fmt.Stringer is required so error messages can identify the extension by name.

type ExtensionWithRequirements

type ExtensionWithRequirements interface {
	Extension
	ThemeRequires() theme.Requirement
}

ExtensionWithRequirements is an optional sub-interface for extensions that read tokens from theme.ResolvedTheme. The framework collects the theme.Requirement every implementing extension declares and validates it against the active resolved theme at App.finalize time.

Extensions that don't read tokens (a man-page generator that only writes roff, for example) leave this method off. Extensions that do read tokens implement it so the App can surface "your theme is missing the slots my extension needs" diagnostics before the user hits the unstyled output:

func (e *Extension) ThemeRequires() theme.Requirement {
    return theme.Require("logging extension",
        theme.StatusInfo, theme.StatusWarning, theme.StatusError,
        theme.AccentPrimary, theme.TextPrimary,
    )
}

The check is warn-by-default (writes to [IOStreams.ErrOut] at App construction time) so an opinionated minimal theme that intentionally omits some tokens does not break apps. Pass WithStrictThemeRequirements to promote the warning into a *ConfigErrors entry that blocks construction.

type FieldOption

type FieldOption[T any] interface {
	// contains filtered or unexported methods
}

FieldOption is the kind-checked option family for interactive prompts, form fields, and declarative arg prompts. T is the bind type at the call site. Misuse (e.g. WithEditor on a bool field) is a build error.

FieldOption[T] is the kind-agnostic, annotation-free option family. The phantom method fieldOpt(T) is the compile-time type witness. Each helper's concrete type implements fieldOpt(T) only for the T values it is valid on. When the user writes:

nabat.WithFormField(&proceed, "Proceed?",
    nabat.WithAffirmative("Yes"),    // FieldOption[bool]
    nabat.WithDefault(false),        // FieldOption[bool]  (T from false)
)

Go infers T=bool from the *bool target and verifies every option satisfies FieldOption[bool]. WithAffirmative returns FieldOption[bool]; passing it to a string field is a build error because its fieldOpt method only takes bool. Multi-kind helpers (WithHint, WithDefault, WithValidate) use generics so T is inferred from their own value argument — no annotation required. Single-kind helpers (WithAffirmative, WithEditor, etc.) use concrete struct types whose fieldOpt(T) is implemented only for the one valid kind.

Most helpers produce a FieldOption[T] with T inferred from their value argument, so no explicit type annotation is needed. The handful of zero-argument, single-kind helpers (WithEditor, WithMultiline, WithPassword, WithAffirmative, WithFilePicker, …) return a concrete type that only satisfies FieldOption for the relevant kind.

Usage contexts:

func WithAffirmative

func WithAffirmative(label string) FieldOption[bool]

WithAffirmative sets the "yes" label for confirm prompts.

func WithAllowedTypes

func WithAllowedTypes(types ...string) FieldOption[string]

WithAllowedTypes restricts file selection to the given extensions. Only meaningful when WithFilePicker is also set.

func WithCurrentDir

func WithCurrentDir(dir string) FieldOption[string]

WithCurrentDir sets the starting directory for the file picker. Only meaningful when WithFilePicker is also set.

func WithDefault

func WithDefault[T any](v T) FieldOption[T]

WithDefault sets the value returned when the terminal is non-interactive. T is inferred from the value: WithDefault("anon") ⇒ T=string.

func WithDirAllowed

func WithDirAllowed() FieldOption[string]

WithDirAllowed permits directory selection in the file picker. Only meaningful when WithFilePicker is also set.

func WithEditor

func WithEditor() FieldOption[string]

WithEditor enables the external editor for multi-line string inputs. Implies WithMultiline.

func WithEditorCmd

func WithEditorCmd(cmd string) FieldOption[string]

WithEditorCmd sets the external editor binary. Only meaningful when WithEditor is also set; a registration error is reported otherwise.

func WithEditorExtension

func WithEditorExtension(ext string) FieldOption[string]

WithEditorExtension sets the temp-file extension used by the external editor. Only meaningful when WithEditor is also set.

func WithFilePicker

func WithFilePicker() FieldOption[string]

WithFilePicker switches the string field widget to a file-system browser. Mutually exclusive with WithMultiline.

func WithHint

func WithHint[T TextInput](v T) FieldOption[T]

WithHint sets the greyed-out example text inside text-style inputs. T is inferred from the value: WithHint("alice") ⇒ T=string, WithHint(3) ⇒ T=int. Only compiles for TextInput types.

Unlike the old [WithPlaceholder], this takes a typed value so T is inferred naturally by Go without requiring an explicit type annotation.

func WithInlineBool

func WithInlineBool() FieldOption[bool]

WithInlineBool renders the bool (confirm) prompt title and value on the same line.

func WithInlineString

func WithInlineString() FieldOption[string]

WithInlineString renders the string prompt title and value on the same line.

func WithMaxChars

func WithMaxChars(n int) FieldOption[string]

WithMaxChars caps the number of characters accepted by a string input. n must be > 0.

func WithMultiline

func WithMultiline() FieldOption[string]

WithMultiline switches the string field widget to a multi-line text area. Mutually exclusive with WithFilePicker; combining them is a registration error.

func WithNegative

func WithNegative(label string) FieldOption[bool]

WithNegative sets the "no" label for confirm prompts.

func WithPassword

func WithPassword() FieldOption[string]

WithPassword switches the string input to masked echo mode.

func WithShowHidden

func WithShowHidden() FieldOption[string]

WithShowHidden makes the file picker display hidden files. Requires WithFilePicker; a registration-time error is returned otherwise.

func WithShowPermissions

func WithShowPermissions() FieldOption[string]

WithShowPermissions makes the file picker display file permissions. Requires WithFilePicker; a registration-time error is returned otherwise.

func WithShowSize

func WithShowSize() FieldOption[string]

WithShowSize makes the file picker display file sizes. Requires WithFilePicker; a registration-time error is returned otherwise.

func WithSuggestions

func WithSuggestions(v ...string) FieldOption[string]

WithSuggestions sets tab-completion suggestions for string inputs.

func WithValidate

func WithValidate[T any](fn func(T) error) FieldOption[T]

WithValidate attaches a typed validation callback. fn must not be nil. T is inferred from fn: WithValidate(func(s string) error { ... }) ⇒ T=string.

type FlagOption

type FlagOption interface {
	// contains filtered or unexported methods
}

FlagOption configures one named flag. It is satisfied by every helper that targets WithFlag, WithSelectFlag, or WithMultiSelectFlag — for example WithRequired, WithUsage, WithEnv, WithShort, WithPersistent, WithCompleter, and WithDeprecated.

FlagOption is an interface (not a function alias) so misuse such as passing an arg-only option to WithFlag is rejected by the Go compiler at the call site.

Options return an error so validation failures aggregate into a ConfigErrors at command build time.

func FlagOptions

func FlagOptions(opts ...FlagOption) FlagOption

FlagOptions composes multiple FlagOption values into one. Options apply in slice order on the same flag slot.

Errors:

  • ErrNilOption: a nil entry appears in opts
  • errors from individual options

func WithCompleter

func WithCompleter(fn CompletionFunc) FlagOption

WithCompleter registers a shell completion function for a flag's value. fn must not be nil. The returned candidates are shown by the user's shell when WithCompletion (or any equivalent install path) has been set up.

"Completer" names the per-flag callback to keep it distinct from WithCompletion, the App-level switch that installs the `completion` subcommand and its shell-script generators.

Example:

WithFlag("cluster", "", WithCompleter(
    func(args []string, toComplete string) ([]string, CompletionDirective) {
        return []string{"eu-1", "us-1"}, CompletionNoFileComp
    },
))

Errors:

  • "nabat: WithCompleter function cannot be nil": fn is nil.
Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("fcctl")
	_, err := app.Command("use",
		nabat.WithFlag("cluster", "", nabat.WithCompleter(
			func(args []string, toComplete string) ([]string, nabat.CompletionDirective) {
				return []string{"eu-1"}, nabat.CompletionDefault
			})),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true

func WithCount

func WithCount() FlagOption

WithCount registers the flag as a counter (for example `-v`, `-vv`, `-vvv`). Each occurrence increments an [int] value. Combine with WithFlag and an int default, typically [0]:

WithFlag("verbose", 0, WithShort('v'), WithCount())

WithCount is valid only when the flag's default type is int.

func WithDeprecatedShorthand

func WithDeprecatedShorthand(message string, sub ...DeprecationOption) FlagOption

WithDeprecatedShorthand marks a flag's shorthand as deprecated while the long form (`--name`) remains current. Requires WithShort on the same flag — that dependency is enforced at registration time in [flagDef.validate].

This helper is a top-level FlagOption (not a DeprecationOption) so the "shorthand only" semantics live in the type system: passing it to anything other than WithFlag / WithSelectFlag / WithMultiSelectFlag is a compile-time error.

Example:

WithFlag("config", "",
    WithShort('c'),
    WithDeprecatedShorthand("use --config instead of -c"),
)
Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("shctl")
	_, err := app.Command("run",
		nabat.WithFlag("cfg", "", nabat.WithShort('c'), nabat.WithDeprecatedShorthand("use --cfg")),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true

func WithPersistent

func WithPersistent() FlagOption

WithPersistent registers the flag as a Cobra persistent flag: it lives on the defining command and every descendant inherits it (unless shadowed).

func WithShort

func WithShort(r rune) FlagOption

WithShort sets the one-character shorthand for a flag (for example `-c` for `--config`).

type FlagValue

type FlagValue interface {
	string | bool | int | int64 | uint | float64 | time.Duration | []string | []bool
}

FlagValue is the set of value kinds accepted by WithFlag.

type FormFieldValue

type FormFieldValue interface {
	string | bool | int | int64 | uint | float64 | time.Duration
}

FormFieldValue is the set of non-select value types accepted by WithFormField. Select types use WithSelectField or WithMultiSelectField instead.

String fields use WithMultiline or WithFilePicker to switch widget mode. The old nabat.Text and nabat.File types have been removed; use plain string with the appropriate mode option.

type FormOption

type FormOption interface {
	// contains filtered or unexported methods
}

FormOption configures a form built with Context.Form. It is satisfied by field constructors (WithFormField, WithSelectField, WithMultiSelectField, WithFormNote) and chrome helpers (WithFormTitle, WithFormDescription, WithFormSubmit, WithFormCancel, WithFormAccessible, WithFormKeyMap, WithFormTimeout).

func WithFormAccessible

func WithFormAccessible() FormOption

WithFormAccessible enables huh's screen-reader-friendly accessible mode. Recommended pattern: drive from an environment variable:

nabat.WithFormAccessible(), // always on; or:
// conditionally: use c.Form opts builder with os.Getenv("ACCESSIBLE") != ""

func WithFormCancel

func WithFormCancel(label string) FormOption

WithFormCancel sets the cancel button label.

func WithFormDescription

func WithFormDescription(s string) FormOption

WithFormDescription sets the form-level description. It is applied to the first group when that group has no WithGroupDescription set.

func WithFormGroup

func WithFormGroup(opts ...GroupOption) FormOption

WithFormGroup wraps fields and group chrome into a new page within the form. Fields used outside any WithFormGroup land in the default first group.

Example:

c.Form(
    nabat.WithFormGroup(
        nabat.WithGroupTitle("Identity"),
        nabat.WithFormField(&name, "Name", ""),
    ),
    nabat.WithFormGroup(
        nabat.WithGroupTitle("Deployment"),
        nabat.WithSelectField(&env, "Environment", "", choices, "staging"),
    ),
)

func WithFormKeyMap

func WithFormKeyMap(km *huh.KeyMap) FormOption

WithFormKeyMap installs a custom huh.KeyMap. Use huh.NewDefaultKeyMap and modify before passing. Has no effect in non-interactive mode.

func WithFormSubmit

func WithFormSubmit(label string) FormOption

WithFormSubmit sets the submit button label.

func WithFormTimeout

func WithFormTimeout(d time.Duration) FormOption

WithFormTimeout aborts the form after d if the user has not submitted. Has no effect in non-interactive mode.

func WithFormTitle

func WithFormTitle(s string) FormOption

WithFormTitle sets the form-level title. It is applied to the first group when that group has no WithGroupTitle set (chrome precedence rule).

type Format

type Format uint8

Format names a structured encoding used by Context.Encode. It is a type-safe enum; the zero value is invalid so accidentally defaulting to JSON is impossible.

const (
	// FormatJSON selects indented JSON output.
	FormatJSON Format = iota + 1
	// FormatYAML selects YAML output.
	FormatYAML
	// FormatTOML selects TOML output.
	FormatTOML
)

func (Format) String

func (f Format) String() string

String implements fmt.Stringer and returns the canonical lowercase name of the format ("json", "yaml", "toml"). Unknown values return "unknown".

type GroupOption

type GroupOption interface {
	// contains filtered or unexported methods
}

GroupOption configures a single page (group) inside a multi-page form. Field constructors satisfy both FormOption and GroupOption so they slot into either WithFormGroup or directly into Context.Form.

func WithGroupDescription

func WithGroupDescription(s string) GroupOption

WithGroupDescription sets the description for a form group (page). Only valid inside WithFormGroup.

func WithGroupTitle

func WithGroupTitle(s string) GroupOption

WithGroupTitle sets the title for a form group (page). Only valid inside WithFormGroup.

type HelpCommandOption

type HelpCommandOption func(*helpCommandConfig) error

HelpCommandOption configures the opt-in `help <subcmd>` surface inside WithHelpCommand.

func WithHelpCommandName

func WithHelpCommandName(name string) HelpCommandOption

WithHelpCommandName overrides the help subcommand name (default "help"). Use inside WithHelpCommand; empty strings return ErrInvalidOption.

Example:

app := MustNew("myctl", WithHelpCommand(
    WithHelpCommandName("aide"),
))
// `myctl aide deploy` prints help for the deploy subcommand.

type IOStreams

type IOStreams struct {
	// In is the input stream. It is the user-supplied [io.Reader] (typically
	// [os.Stdin]) and is not wrapped. Treat as read-only after construction.
	In io.Reader

	// Out is the primary output stream — "the product" in POSIX terms. It
	// is wrapped at construction with a colorprofile-aware writer; do not
	// reassign after construction or the color-detection wrapping is lost.
	Out io.Writer

	// ErrOut is the diagnostics stream — errors, warnings, progress. It
	// shares the same wrapping policy as Out; do not reassign after
	// construction.
	ErrOut io.Writer
	// contains filtered or unexported fields
}

IOStreams bundles the input and output streams a CLI uses, alongside terminal capability detection and a uniform color/escape policy. A single IOStreams instance is shared across an App and every Context it produces.

Out and ErrOut are wrapped at construction with a colorprofile-aware writer that strips ANSI escapes when the underlying stream is not a terminal (or when NO_COLOR is set) and preserves the original file descriptor for libraries that perform their own terminal detection.

IOStreams is safe to construct concurrently with other operations but not safe for concurrent mutation via the SetXxxTTY methods.

func NewIO

func NewIO(in io.Reader, out, errOut io.Writer) *IOStreams

NewIO returns an IOStreams over the supplied streams. Out and ErrOut are wrapped with the colorprofile policy; In is passed through unchanged.

Pass *os.File values for production code (TTY detection works against the real file descriptor). For tests prefer [nabattest.NewIO], which also returns the underlying buffers for assertion.

Example
package main

import (
	"fmt"
	"io"
	"strings"

	"nabat.dev/nabat"
)

func main() {
	out := &strings.Builder{}
	s := nabat.NewIO(strings.NewReader("input"), out, &strings.Builder{})
	body, err := io.ReadAll(s.In)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(body))
}
Output:
input
Example (Buffers)
package main

import (
	"fmt"
	"strings"

	"nabat.dev/nabat"
)

func main() {
	in := &strings.Builder{}
	out := &strings.Builder{}
	errOut := &strings.Builder{}
	s := nabat.NewIO(strings.NewReader(""), out, errOut)
	if _, err := fmt.Fprint(s.Out, "primary\n"); err != nil {
		panic(err)
	}
	if _, err := fmt.Fprint(s.ErrOut, "diagnostics\n"); err != nil {
		panic(err)
	}
	_ = in
	fmt.Print(out.String())
	fmt.Print(errOut.String())
}
Output:
primary
diagnostics
Example (TestBuffers)
package main

import (
	"fmt"

	"nabat.dev/nabattest"
)

func main() {
	s, in, out, errOut := nabattest.NewIO()
	if _, err := in.WriteString("typed\n"); err != nil {
		panic(err)
	}
	if _, err := fmt.Fprint(s.Out, "stdout\n"); err != nil {
		panic(err)
	}
	if _, err := fmt.Fprint(s.ErrOut, "stderr\n"); err != nil {
		panic(err)
	}
	fmt.Print(in.String())
	fmt.Print(out.String())
	fmt.Print(errOut.String())
}
Output:
typed
stdout
stderr

func NewSystemIO

func NewSystemIO() *IOStreams

NewSystemIO returns an IOStreams backed by os.Stdin, os.Stdout, and os.Stderr. Color profile and TTY status are detected against the real file descriptors and the process environment.

func (*IOStreams) CanPrompt

func (s *IOStreams) CanPrompt() bool

CanPrompt reports whether interactive prompts are usable. It is true when both stdin and stdout are terminals.

func (*IOStreams) ColorEnabled

func (s *IOStreams) ColorEnabled() bool

ColorEnabled reports whether colorized output is in effect for the primary output stream.

func (*IOStreams) Err

func (s *IOStreams) Err() error

Err returns the first write error encountered by Out or ErrOut, or nil if all writes have succeeded so far.

func (*IOStreams) IsStderrTTY

func (s *IOStreams) IsStderrTTY() bool

IsStderrTTY reports whether the diagnostics stream is a terminal.

func (*IOStreams) IsStdinTTY

func (s *IOStreams) IsStdinTTY() bool

IsStdinTTY reports whether the input stream is a terminal.

func (*IOStreams) IsStdoutTTY

func (s *IOStreams) IsStdoutTTY() bool

IsStdoutTTY reports whether the output stream is a terminal.

func (*IOStreams) RawErrOut

func (s *IOStreams) RawErrOut() io.Writer

RawErrOut returns the unwrapped diagnostics stream supplied to NewIO or NewSystemIO.

func (*IOStreams) RawIn

func (s *IOStreams) RawIn() io.Reader

RawIn returns the unwrapped input stream supplied to NewIO or NewSystemIO.

func (*IOStreams) RawOut

func (s *IOStreams) RawOut() io.Writer

RawOut returns the unwrapped output stream supplied to NewIO or NewSystemIO.

func (*IOStreams) SetStderrTTY

func (s *IOStreams) SetStderrTTY(v bool)

SetStderrTTY overrides the cached stderr TTY status.

func (*IOStreams) SetStdinTTY

func (s *IOStreams) SetStdinTTY(v bool)

SetStdinTTY overrides the cached stdin TTY status. Tests use this to exercise the interactive code path against a buffer-backed stream.

func (*IOStreams) SetStdoutTTY

func (s *IOStreams) SetStdoutTTY(v bool)

SetStdoutTTY overrides the cached stdout TTY status.

func (*IOStreams) TerminalWidth

func (s *IOStreams) TerminalWidth() int

TerminalWidth returns the column width of the controlling terminal, or DefaultWidth when stdout is not a terminal or its size cannot be queried.

type ListEnumerator

type ListEnumerator = list.Enumerator

ListEnumerator is the function signature for custom list enumerators.

type ListItems

type ListItems = list.Items

ListItems is the type passed to list style and enumerator functions.

type ListOption

type ListOption func(*listConfig)

ListOption configures the Context.List output method. Pass one or more options to customize the enumerator and styling.

Example:

c.List(items,
	WithListEnumerator(ListNumbered),
	WithListItemStyle(lipgloss.NewStyle().Bold(true)),
)

func WithListEnumerator

func WithListEnumerator(e list.Enumerator) ListOption

WithListEnumerator sets the enumerator used to prefix each list item. Pass one of the ListBullet, ListDash, ListNumbered, ListRoman, or ListAlphabet presets, or a custom ListEnumerator function. The default is ListBullet.

Example:

WithListEnumerator(ListNumbered)

func WithListEnumeratorStyle

func WithListEnumeratorStyle(s lipgloss.Style) ListOption

WithListEnumeratorStyle sets the lipgloss style for all enumerator markers. The default comes from [Theme.ListEnumeratorStyle]. Use WithListEnumeratorStyleFunc for per-item control.

Example:

WithListEnumeratorStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("245")))

func WithListEnumeratorStyleFunc

func WithListEnumeratorStyleFunc(fn list.StyleFunc) ListOption

WithListEnumeratorStyleFunc sets a per-item enumerator style function. When set, it overrides WithListEnumeratorStyle.

Example:

WithListEnumeratorStyleFunc(func(_ ListItems, i int) lipgloss.Style {
	if i == 0 {
		return lipgloss.NewStyle().Foreground(lipgloss.Color("#FF79C6"))
	}
	return lipgloss.NewStyle()
})

func WithListItemStyle

func WithListItemStyle(s lipgloss.Style) ListOption

WithListItemStyle sets the lipgloss style for all list items. The default comes from [Theme.ListItemStyle]. Use WithListItemStyleFunc for per-item control.

Example:

WithListItemStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("252")))

func WithListItemStyleFunc

func WithListItemStyleFunc(fn list.StyleFunc) ListOption

WithListItemStyleFunc sets a per-item style function. When set, it overrides WithListItemStyle.

Example:

WithListItemStyleFunc(func(_ ListItems, i int) lipgloss.Style {
	if i == 0 {
		return lipgloss.NewStyle().Bold(true)
	}
	return lipgloss.NewStyle()
})

type ListStyleFunc

type ListStyleFunc = list.StyleFunc

ListStyleFunc is a function that determines the style of a list item based on the item set and the current index.

type MultiSelectOption

type MultiSelectOption interface {
	SelectOption
	// contains filtered or unexported methods
}

MultiSelectOption configures multi-select fields. Satisfies SelectOption so it can also be passed to a single-select field, but WithLimit is rejected there at registration time.

func WithLimit

func WithLimit(n int) MultiSelectOption

WithLimit caps how many items a multi-select field lets the user pick. n must be > 0.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option configures an App during construction with New or MustNew.

Three categories of values satisfy Option:

Passing a CommandOption that is not a RootOption (such as WithGroup, WithHidden, WithAliases, WithTypoHints) to New is a build error: those options have no meaningful effect on the root command. They remain valid inside WithCommand for nested subcommands.

Passing a nil Option to New returns ErrNilOption.

func AppOptions

func AppOptions(opts ...Option) Option

AppOptions composes multiple Option values into one. Options apply in slice order.

Errors:

  • ErrNilOption: a nil entry appears in opts
  • errors from individual options

func AsExtension

func AsExtension(name string, fn func(AppSurface) error) Option

AsExtension returns an Option that registers an inline extension. fn runs in declaration order with other extensions, after help, version, and completion are registered on the root command.

This is the inline alternative to implementing the Extension interface. For third-party extensions built as separate packages, use WithExtension with the package's New() constructor instead.

Errors:

Example

ExampleAsExtension wraps a function as an extension; Init runs during New after help, version, and completion are registered.

package main

import (
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/nabattest"
)

func main() {
	var ran bool
	io, _, out, _ := nabattest.NewIO()
	app, err := nabat.New("myctl", nabat.WithIO(io),
		nabat.AsExtension("probe", func(a nabat.AppSurface) error {
			ran = true
			return a.OnPreRun(func(c *nabat.Context) error { return nil })
		}),
		nabat.WithRun(func(c *nabat.Context) error {
			c.Print("done")
			return nil
		}),
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	if !ran {
		fmt.Println("init did not run")
		return
	}
	if err = app.RunArgs(context.Background()); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(strings.TrimSpace(out.String()))
}
Output:
done

func WithCompletion

func WithCompletion(opts ...CompletionOption) Option

WithCompletion enables the built-in completion feature, installing a `completion` subcommand with bash, zsh, fish, and PowerShell generators. Pass CompletionOption values to override defaults or restrict the set of installed generators. Omit WithCompletion entirely to skip the subcommand surface (per-flag WithCompleter still works without it).

Example:

New("ctl",
    WithCompletion(),
)

New("ctl",
    WithCompletion(
        WithCompletionName("comp"),
        WithCompletionHidden(),
        WithCompletionShells("bash", "zsh"),
    ),
)

Result: `ctl completion bash`, `ctl completion zsh`, etc., each emitting a ready-to-source shell script with install instructions in `--help`.

Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app, err := nabat.New("ctl",
		nabat.WithCompletion(),
	)
	if err != nil {
		fmt.Println("err:", err)
		return
	}
	found := false
	for _, sub := range app.UnsafeRoot().Commands() {
		if sub.Name() == "completion" {
			found = true
			break
		}
	}
	fmt.Println(found)
}
Output:
true

func WithCustomTheme

func WithCustomTheme(r theme.Resolver) Option

WithCustomTheme installs a programmatically-defined theme.Resolver. Use this when a theme cannot be expressed as a JSON manifest — for example, when it ships an owned chroma.Style or a closure-based huh.Theme that depends on values not available at manifest-load time. The common case (a fully-declared theme.Theme struct) just passes the value directly because theme.Theme satisfies theme.Resolver:

acme := theme.Theme{
    Name:    "acme",
    Default: theme.VariantDark,
    Variants: map[theme.Variant]theme.Palette{
        theme.VariantDark: {
            Tokens:  acmeTokens,
            Chroma:  acmeChromaStyle,
            Glamour: acmeGlamourStyle,
            Huh:     acmeHuhTheme,
        },
    },
}
app, _ := nabat.New("myctl", nabat.WithCustomTheme(acme))

The rare case (a Resolver whose palette choice depends on runtime theme.Capabilities in a way one Variant per Palette cannot express) implements theme.Resolver directly and passes that.

WithCustomTheme composes with WithTheme: the last call wins. Per-token tweaks should reach for WithThemeOverride instead of constructing a derived theme.Theme for one slot.

Errors:

func WithEnvPrefix

func WithEnvPrefix(prefix string) Option

WithEnvPrefix sets the prefix prepended to the primary key from WithEnv on each field. Fields without WithEnv do not read the environment. The value should include a trailing underscore when logical keys should read as separate words (for example "MYAPP_" plus key "token" becomes MYAPP_TOKEN).

Example:

New("myctl", WithEnvPrefix("MYAPP_"))

func WithErrorHandler

func WithErrorHandler(fn func(error)) Option

WithErrorHandler replaces the default error rendering used by App.Run when execution fails (after Cobra parsing and Nabat resolution). The handler receives the same error returned by App.Run. When unset, Nabat prints a styled "error:" line and a styled "Run <command> --help for usage." hint to stderr.

Passing a nil function returns ErrNilOption.

func WithExtension

func WithExtension(ext Extension, err error) Option

WithExtension installs an Extension. Extensions run in declaration order inside New, after the root command, help, version, and completion are configured. Later extensions can observe state set by earlier ones.

WithExtension accepts the (Extension, error) pair returned by extension constructors (e.g. manpage.New(), logging.New()) so the construction error flows directly into New's *ConfigErrors without a separate error check:

New("myctl",
    WithExtension(manpage.New()),
    WithExtension(logging.New(logging.WithVerboseFlag("verbose"))),
)

Errors:

  • err is non-nil: the constructor error is forwarded as-is.
  • ext is nil (and err is nil): ErrNilOption.
  • errors from [Extension.Init] cause New to return immediately.

func WithHelpCommand

func WithHelpCommand(opts ...HelpCommandOption) Option

WithHelpCommand installs the `help <subcmd>` subcommand with Nabat's themed renderer. The subcommand is off by default; pass this option to turn it on, mirroring how WithVersion opts into the version feature.

Pass HelpCommandOption values such as WithHelpCommandName to customize the subcommand. A nil option in the slice returns ErrNilOption.

Example:

MustNew("ctl", WithHelpCommand())
// `ctl help run` prints help for the `run` subcommand.

MustNew("ctl", WithHelpCommand(
    WithHelpCommandName("aide"),
))
// `ctl aide run` prints help for the `run` subcommand.

func WithHelpFlagName

func WithHelpFlagName(name string) Option

WithHelpFlagName overrides the help flag name (default "help"). Combine with WithHelpShorthand to also change the shorthand.

Empty string returns ErrInvalidOption; use WithoutHelpFlag to disable the flag instead.

When the flag name is not "help", a hidden alias --help is also registered to preempt Cobra's auto-injected --help and avoid two help flags coexisting.

Example:

app := MustNew("myctl",
    WithHelpFlagName("info"),
    WithHelpShorthand('i'),
)
// `myctl --info` and `myctl -i` show help.

func WithHelpShorthand

func WithHelpShorthand(r rune) Option

WithHelpShorthand sets the one-character shorthand for the help flag (default 'h').

func WithIO

func WithIO(s *IOStreams) Option

WithIO replaces the App's IOStreams bundle. Tests typically build one with [nabattest.NewIO] so they can capture output for assertions; production code rarely needs to override the default (NewSystemIO).

Passing nil returns ErrNilOption.

Example:

io, _, out, _ := nabattest.NewIO()
app := MustNew("myctl", WithIO(io))
// ... run app ...
require.Contains(t, out.String(), "deployed")

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the structured logger returned by Context.Logger during command invocations. Pass any *slog.Logger; Nabat treats it as opaque.

Use this when you want to bring your own logger. As an alternative, install the logging plugin via WithExtension for an opinionated styled charm logger with --verbose / --log-level flag wiring.

When neither WithLogger nor the logging plugin is used, Context.Logger returns a discard logger that silently drops all records.

Passing a nil *slog.Logger returns ErrNilOption.

func WithStrictThemeRequirements

func WithStrictThemeRequirements() Option

WithStrictThemeRequirements promotes the framework's theme-requirement warning into a hard error returned from New. Without this option, the App writes a multi-line diagnostic to [IOStreams.ErrOut] when an installed theme misses tokens declared by core consumers or by an ExtensionWithRequirements; with it, the same diagnostic becomes a *ConfigErrors entry and construction fails.

Use case: tests that assert "this theme covers everything we need" — flip the option and let the framework's check do the regression catching for you. Production CLIs typically leave it off so an opinionated minimal theme can still ship without breaking apps that picked it.

func WithTheme

func WithTheme(name string) Option

WithTheme installs the named built-in theme from the embedded catalog (see nabat.dev/theme for the available names). Use the untyped string constants in theme for IDE autocomplete and a build-time guarantee that the spelling matches an embedded manifest:

New("myctl", WithTheme(theme.Dracula))

Plain strings work too — pass a flag value, env var, or user-supplied configuration directly:

name := os.Getenv("MYCTL_THEME")
if name == "" { name = theme.Default }
New("myctl", WithTheme(name))

WithTheme and WithCustomTheme compose: the last one wins. Multiple calls do not error; the override slot WithThemeOverride handles per-token tweaks without forcing the caller to construct a derived theme by hand.

Errors:

  • The theme name is not in the embedded catalog. The error joins into the *ConfigErrors returned by New and lists every available name so the caller (or end user) can correct the typo.

For a programmatic theme that does not live as a manifest, use WithCustomTheme.

Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
	"nabat.dev/theme"
)

func main() {
	_, err := nabat.New("themectl", nabat.WithTheme(theme.Dracula))
	fmt.Println(err == nil)
}
Output:
true

func WithThemeOverride

func WithThemeOverride(t theme.Token, s lipgloss.Style) Option

WithThemeOverride registers a per-token style override applied on top of the active theme at finalize time. Multiple calls compose: later overrides for the same token win, overrides on different tokens stack.

The common use case is "use the bundled theme, but with my brand color":

app, _ := nabat.New("myctl",
    nabat.WithTheme(theme.Dracula),
    nabat.WithThemeOverride(theme.StatusError, magenta),
)

Overrides apply to every variant of the underlying theme, so a multi-variant manifest stays multi-variant after the override — the same one-line tweak affects whichever variant the runtime capabilities pick.

Overrides are silently ignored when the active theme is a bespoke theme.Recipe (anything other than a theme.Theme value); the recipe's Resolve method is opaque, so the framework cannot meaningfully apply per-Palette overrides into it.

func WithThemeOverrides

func WithThemeOverrides(overrides ...theme.Override) Option

WithThemeOverrides registers a batch of theme.Override mutations — useful for callers that want to apply more than one slot in one call (chroma swap plus a token tweak, for example) or that want to drop in an override produced by another helper such as theme.SetAlias or theme.SetChroma.

Overrides apply in slice order; the same composition rules described on WithThemeOverride apply.

Errors:

func WithVersion

func WithVersion(version string, opts ...VersionOption) Option

WithVersion enables the built-in version feature. The version string is required and printed by both the `version` subcommand and the `--version` flag. Empty strings return ErrInvalidOption; pass an explicit value such as one read from runtime/debug.ReadBuildInfo for build-info derivation.

Pass VersionOption values to override defaults or disable individual pieces. Disabling both the flag and the command at the same time is an error: omit WithVersion entirely instead.

Example:

New("ctl",
    WithVersion("1.2.3",
        WithVersionCommit("abc1234"),
        WithVersionShorthand('V'),
    ),
)

Result: `ctl version`, `ctl version --format short`, `ctl --version`, `ctl -V`.

func WithoutHelp

func WithoutHelp() Option

WithoutHelp disables the built-in help feature entirely: no Nabat custom renderer is installed, no opt-in `help` subcommand, no persistent `--help` flag. Cobra's defaults take over (auto-injected --help with the stock template).

Mixing WithoutHelp with any other With(out)Help* option returns ErrInvalidOption. The check runs in New after all options have been applied so order does not matter.

func WithoutHelpFlag

func WithoutHelpFlag() Option

WithoutHelpFlag disables the persistent --help flag. The opt-in help subcommand (when enabled via WithHelpCommand) keeps working.

Cobra's auto-injected --help is suppressed by registering a hidden no-op flag in its place; if the user explicitly wants Cobra's default behavior, use WithoutHelp instead.

func WithoutHelpShorthand

func WithoutHelpShorthand() Option

WithoutHelpShorthand disables the help flag's shorthand. The long form (default --help) keeps working.

type ParseOption

type ParseOption func(*parseConfig)

ParseOption configures low-level Cobra parsing for a single command (including the root by passing options directly to New). Use with WithParseOptions.

func WithAllowUnknownFlags

func WithAllowUnknownFlags() ParseOption

WithAllowUnknownFlags sets cobra.Command.FParseErrWhitelist.UnknownFlags so unknown flags do not fail parsing. Useful for wrapper CLIs that forward argv.

Example:

WithParseOptions(WithAllowUnknownFlags())

func WithDisableFlagParsing

func WithDisableFlagParsing() ParseOption

WithDisableFlagParsing sets cobra.Command.DisableFlagParsing: every token after the command name is treated as a positional argument. Often combined with WithPassthrough for exec-style commands.

Example:

WithParseOptions(WithDisableFlagParsing())

func WithTraverseChildren

func WithTraverseChildren(enabled bool) ParseOption

WithTraverseChildren sets cobra.Command.TraverseChildren. When true, the parent command parses local flags before dispatching to a subcommand.

Example:

WithParseOptions(WithTraverseChildren(true))

type ProgressBar

type ProgressBar struct {
	// contains filtered or unexported fields
}

ProgressBar tracks a finite number of steps and writes progress to the command's stderr ([Context.IO.ErrOut]).

Per clig.dev guidance, progress indicators belong on stderr so they do not corrupt machine-readable data on stdout when the user pipes the command's output. When stderr is a terminal, the bar updates in place; otherwise each update prints a "current/total" line.

Fill colors and percentage styling come from the active App.Theme when stderr is a terminal and WithoutProgressBarTheme is not set: a blend between theme.AccentPrimary and theme.TextLink, empty segments use theme.TextMuted, and the numeric percentage uses theme.TextSecondary. To customize colors, use WithTheme, WithThemeOverride, or a custom theme.Theme — not per-bar color arguments.

Nabat renders with progress.Model.ViewAs only. Options such as WithProgressBarSpring tune the embedded bubbles model but do not run spring animation here; smooth animation requires Bubble Tea's progress.Model.Update and frame messages.

ProgressBar is safe for concurrent use after construction. Callers may invoke ProgressBar.Increment, ProgressBar.Add, ProgressBar.Set, and ProgressBar.Done from multiple goroutines.

func (*ProgressBar) Add

func (p *ProgressBar) Add(n int)

Add advances the bar by n steps. The position stays clamped between 0 and the configured total.

func (*ProgressBar) Done

func (p *ProgressBar) Done()

Done marks the bar as complete and moves to the next line. It uses the same mutex as ProgressBar.Increment, ProgressBar.Add, and ProgressBar.Set; call it once when work is finished.

func (*ProgressBar) Increment

func (p *ProgressBar) Increment()

Increment advances the bar by one step, equivalent to Add(1).

func (*ProgressBar) Set

func (p *ProgressBar) Set(n int)

Set sets the bar to n completed steps. n is clamped to [0, total].

type ProgressBarOption

type ProgressBarOption func(*progressBarConfig) error

ProgressBarOption configures Context.ProgressBar.

Options return an error so validation failures aggregate into a ConfigErrors at Context.ProgressBar call time, matching the pattern used by FieldOption.

func WithProgressBarFillCharacters

func WithProgressBarFillCharacters(full, empty rune) ProgressBarOption

WithProgressBarFillCharacters sets the runes for filled and empty segments.

Example:

WithProgressBarFillCharacters(nabat.ProgressFillFullBlock, nabat.ProgressEmptyLight)

func WithProgressBarScaledBlend

func WithProgressBarScaledBlend(enabled bool) ProgressBarOption

WithProgressBarScaledBlend sets whether a multi-color blend scales to the filled width only (enabled) or spans the full bar width until 100% (disabled, the bubbles default).

Example:

WithProgressBarScaledBlend(true)

func WithProgressBarSpring

func WithProgressBarSpring(frequency, damping float64) ProgressBarOption

WithProgressBarSpring sets frequency and damping for the bubbles progress spring (see harmonica). This configures the embedded model only; Nabat does not run Bubble Tea frame ticks, so the bar does not animate springs — rendering uses progress.Model.ViewAs with your step percentage.

Errors:

  • "nabat: WithProgressBarSpring: frequency must be > 0" when frequency <= 0
  • "nabat: WithProgressBarSpring: damping must be > 0" when damping <= 0

Example:

WithProgressBarSpring(25.0, 1.0)

func WithProgressBarWidth

func WithProgressBarWidth(w int) ProgressBarOption

WithProgressBarWidth sets the character width of the bar. w must be > 0.

Errors:

  • "nabat: WithProgressBarWidth: w must be > 0" when w <= 0

func WithoutProgressBarPercentage

func WithoutProgressBarPercentage() ProgressBarOption

WithoutProgressBarPercentage hides the trailing numeric percentage on the bar.

Example:

WithoutProgressBarPercentage()

func WithoutProgressBarTheme

func WithoutProgressBarTheme() ProgressBarOption

WithoutProgressBarTheme skips colors from App.Theme when stderr is a terminal. The bar uses the bubbles library default gradient instead.

Non-terminal stderr already renders "current/total" lines without themed ANSI styling.

type RootOption

type RootOption interface {
	CommandOption
	Option
	// contains filtered or unexported methods
}

RootOption is the strict subset of CommandOption that is also valid on the root command. RootOption values are passed directly to nabat.New to configure the root command (every RootOption also satisfies Option); they can also be used inside WithCommand, App.Command, and Command.Command because every RootOption is a CommandOption.

Constructors that return only CommandOption (because cobra ignores them on the root command — WithGroup, WithHidden, WithAliases, WithTypoHints) are build errors when passed directly to New, but remain valid inside WithCommand for nested subcommands.

func RootOptions

func RootOptions(opts ...RootOption) RootOption

RootOptions composes multiple RootOption values into one. Options apply in slice order.

Errors:

  • ErrNilOption: a nil entry appears in opts
  • errors from individual options

func WithAnnotation

func WithAnnotation(key, value string) RootOption

WithAnnotation sets a key/value entry on cobra.Command.Annotations. Repeat calls accumulate; the same key set later overwrites earlier values.

Example:

app.MustCommand("pods",
    WithAnnotation("kubectl.kubernetes.io/default_container", "app"),
    WithRun(func(c *Context) error { return nil }),
)

Errors:

  • "nabat: WithAnnotation key cannot be empty": key is "".
Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("annctl")
	_, err := app.Command("pods",
		nabat.WithAnnotation("sample", "value"),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true

func WithArg

func WithArg[T ArgValue](name string, defaultVal T, opts ...ArgOption) RootOption

WithArg defines one positional argument with adaptive resolution. The type argument T is inferred from defaultVal and selects the stored value kind (for example int vs uint vs int64 vs time.Duration). Use typed zero values such as uint(0) or int64(0) rather than untyped 0 when the field must not be an int.

func WithArgArity

func WithArgArity(opts ...ArityOption) RootOption

WithArgArity applies nested arity rules. WithArgArity may only be used once per command. Passing a nil ArityOption returns ErrNilOption.

Example:

app.MustCommand("copy",
    WithArg("src", "", WithRequired()),
    WithArg("dst", "", WithRequired()),
    WithArgArity(WithExactArgCount(2)),
    WithRun(func(c *Context) error { return nil }),
)
Example (ExactCount)
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("arityctl")
	_, err := app.Command("copy",
		nabat.WithArg("src", "", nabat.WithRequired()),
		nabat.WithArg("dst", "", nabat.WithRequired()),
		nabat.WithArgArity(nabat.WithExactArgCount(2)),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true
Example (MinMax)
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("mmctl")
	_, err := app.Command("pick",
		nabat.WithArg("one", ""),
		nabat.WithArg("two", ""),
		nabat.WithArgArity(nabat.WithMinArgCount(1), nabat.WithMaxArgCount(2)),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true

func WithCommand

func WithCommand(name string, opts ...CommandOption) RootOption

WithCommand declares a subcommand. The same value works at every nesting level:

  • Passed to nabat.New, it registers a top-level subcommand under root.
  • Nested inside another WithCommand, it registers a child under that command.

Errors aggregate into the *ConfigErrors returned by New, alongside option errors and validation errors. For inline (per-call) error handling or runtime/dynamic registration, use App.Command / Command.Command instead.

Errors:

  • registration errors (nil option, empty name, name collisions, flag-registration failures) are surfaced by New in the returned *ConfigErrors. See App.Command for the full per-error list.

Example (declarative tree):

app, err := New("myctl",
    WithDescription("My CLI"),
    WithCommand("cluster",
        WithDescription("Cluster management"),
        WithCommand("scale", WithRun(scaleHandler)),
        WithCommand("status", WithRun(statusHandler)),
    ),
    WithCommand("deploy", WithRun(deployHandler)),
)
Example

Multi-command tree using nabat.WithCommand. Every error from one New() call.

package main

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/theme"
)

func main() {
	var out bytes.Buffer
	app, err := nabat.New("myctl",
		nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)),
		nabat.WithTheme(theme.Minimal),
		nabat.WithDescription("My CLI"),
		nabat.WithCommand("up", nabat.WithRun(func(c *nabat.Context) error {
			c.Print("up")
			return nil
		})),
		nabat.WithCommand("down", nabat.WithRun(func(c *nabat.Context) error {
			c.Print("down")
			return nil
		})),
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	if err = app.RunArgs(context.Background(), "up"); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(strings.TrimSpace(out.String()))
}
Output:
up
Example (Nested)

Nested subcommand tree: nabat.WithCommand inside another nabat.WithCommand.

package main

import (
	"bytes"
	"context"
	"fmt"
	"strings"

	"nabat.dev/nabat"
	"nabat.dev/theme"
)

func main() {
	var out bytes.Buffer
	app, err := nabat.New("kubectl-myext",
		nabat.WithIO(nabat.NewIO(strings.NewReader(""), &out, &out)),
		nabat.WithTheme(theme.Minimal),
		nabat.WithCommand("cluster",
			nabat.WithDescription("Cluster management"),
			nabat.WithCommand("scale", nabat.WithRun(func(c *nabat.Context) error {
				c.Print("scaled")
				return nil
			})),
			nabat.WithCommand("status", nabat.WithRun(func(c *nabat.Context) error {
				c.Print("ok")
				return nil
			})),
		),
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	if err = app.RunArgs(context.Background(), "cluster", "scale"); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Print(strings.TrimSpace(out.String()))
}
Output:
scaled

func WithDescription

func WithDescription(text string) RootOption

WithDescription sets the one-line description shown in parent command help listings.

func WithExample

func WithExample(md string) RootOption

WithExample sets the example block shown in the command's help output. Write it as plain shell text: comment lines starting with # are dimmed, the program name on each line is highlighted, flags (--flag, -f) are accented, quoted strings and shell operators are styled accordingly.

WithExample(`
# Deploy to production:
myapp deploy production --replicas 3
`)

func WithFlag

func WithFlag[T FlagValue](name string, defaultVal T, opts ...FlagOption) RootOption

WithFlag defines a named flag on a command. The type argument T is inferred from defaultVal and selects the flag's value kind. Use typed zero values when an int default is wrong; choose kinds such as [uint] or time.Duration explicitly. For count flags (for example -vvv), pass an int default (typically 0) and add WithCount.

func WithLongDescription

func WithLongDescription(desc string) RootOption

WithLongDescription sets a detailed command description shown in full help output.

func WithMultiSelectArg

func WithMultiSelectArg(name string, defaultVal, choices []string, opts ...ArgOption) RootOption

WithMultiSelectArg defines one positional multi-select argument with adaptive resolution. defaultVal are the pre-selected items; each must be in choices (or pass nil when WithRequired is set). Add WithPrompt to enable interactive prompting when the terminal is a TTY. Handlers read the value with Context.Bind or BindAs.

func WithMultiSelectFlag

func WithMultiSelectFlag(name string, defaultVal, choices []string, opts ...FlagOption) RootOption

WithMultiSelectFlag defines a named flag accepting multiple values from choices. defaultVal items must each be one of choices (or empty slice if WithRequired is also set).

func WithParseOptions

func WithParseOptions(opts ...ParseOption) RootOption

WithParseOptions applies nested parsing options to the command. Passing a nil ParseOption returns ErrNilOption. An empty opts slice is a no-op.

Example:

app.MustCommand("proxy",
    WithParseOptions(
        WithAllowUnknownFlags(),
        WithTraverseChildren(true),
    ),
    WithRun(func(c *Context) error { return nil }),
)
Example (AllowUnknownFlags)
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("popctl")
	_, err := app.Command("proxy",
		nabat.WithParseOptions(nabat.WithAllowUnknownFlags()),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true
Example (DisableFlagParsing)
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("dfpctl")
	_, err := app.Command("raw",
		nabat.WithParseOptions(nabat.WithDisableFlagParsing()),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true
Example (TraverseChildren)
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("travctl")
	_, err := app.Command("parent",
		nabat.WithParseOptions(nabat.WithTraverseChildren(true)),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true

func WithPassthrough

func WithPassthrough(label string, desc ...string) RootOption

WithPassthrough declares that this command accepts arguments after --. label is shown in the usage line as [-- label] (e.g. "command..." → [-- command...]). The optional desc is shown as a row under the Arguments section in help output. Access the passthrough args in the handler via Context.Passthrough; use Context.HasPassthrough to tell whether "--" appeared even with no tokens after it.

Example:

app.MustCommand("exec",
    WithArg("service", "", WithRequired()),
    WithPassthrough("command [args...]", "command to run once the service is ready"),
    WithRun(func(c *Context) error {
        if c.HasPassthrough() {
            return exec(c.Passthrough())
        }
        return nil
    }),
)

Errors:

  • "nabat: WithPassthrough label cannot be empty": label is "".

func WithPositionalCompleter

func WithPositionalCompleter(fn CompletionFunc) RootOption

WithPositionalCompleter overrides automatic positional shell completions. Use this option only with a non-nil CompletionFunc; automatic completions are derived from a single WithSelectArg or WithArg with [WithStringSuggestions] when this option is omitted.

"Completer" names the per-positional callback to keep it distinct from WithCompletion, the App-level switch that installs the `completion` subcommand.

Example:

app.MustCommand("env",
    WithPositionalCompleter(func(args []string, toComplete string) ([]string, CompletionDirective) {
        return []string{"staging", "prod"}, CompletionNoFileComp
    }),
    WithRun(func(c *Context) error { return nil }),
)
Example
package main

import (
	"fmt"

	"nabat.dev/nabat"
)

func main() {
	app := nabat.MustNew("compctl")
	_, err := app.Command("env",
		nabat.WithPositionalCompleter(func(args []string, toComplete string) ([]string, nabat.CompletionDirective) {
			return []string{"staging", "prod"}, nabat.CompletionDefault
		}),
		nabat.WithRun(func(c *nabat.Context) error { return nil }),
	)
	fmt.Println(err == nil)
}
Output:
true

func WithPostRun

func WithPostRun(fn func(*Context) error) RootOption

WithPostRun adds a hook that runs after the command handler returns, regardless of whether it succeeded. Multiple calls accumulate in order. Post-run errors are returned only when the command handler itself succeeded.

Use for cleanup, audit logging, or telemetry flushing.

Errors:

  • "nabat: post-run function cannot be nil": fn is nil.

func WithPreRun

func WithPreRun(fn func(*Context) error) RootOption

WithPreRun adds a hook that runs after arg resolution but before the command handler. Multiple calls accumulate in order. A non-nil error aborts the run.

Use for auth checks, telemetry setup, or any pre-flight work.

Errors:

  • "nabat: pre-run function cannot be nil": fn is nil.

func WithRootInit

func WithRootInit(fn func(*Command) error) RootOption

WithRootInit is like WithCommandInit but satisfies RootOption, so it can be passed directly to New. For non-root commands, use WithCommandInit inside WithCommand.

func WithRun

func WithRun(fn RunFunc) RootOption

WithRun sets the command handler.

Example:

app.MustCommand("deploy",
    WithFlag("env", "staging"),
    WithRun(func(c *Context) error {
        c.Success("done", "env", args.Env) // after c.Bind(&args)
        return nil
    }),
)

Errors:

  • "nabat: command run cannot be nil": fn is nil.

func WithSelectArg

func WithSelectArg(name, defaultVal string, choices []string, opts ...ArgOption) RootOption

WithSelectArg defines one positional select argument with adaptive resolution. defaultVal must be one of choices (or empty when WithRequired is set). Add WithPrompt to enable interactive prompting when the terminal is a TTY. Handlers read the value with Context.Bind or BindAs.

Example:

app.MustCommand("deploy",
    WithSelectArg("env", "", []string{"staging", "production"},
        WithRequired(),
        WithPrompt("Target environment", ""),
    ),
    WithRun(handler),
)

func WithSelectFlag

func WithSelectFlag(name, defaultVal string, choices []string, opts ...FlagOption) RootOption

WithSelectFlag defines a named flag whose value must be one of the given choices. defaultVal must be one of choices (or empty string if WithRequired is also set).

func WithValidation

func WithValidation(fn func(*Context) error) RootOption

WithValidation adds a cross-field validation function that runs after all positional args and flags are resolved but before the command handler. Multiple calls accumulate; all validations must pass.

WithValidation(func(c *Context) error {
    if format == "json" && !c.Explicit("output") { // use BindAs[string](c, "format"), etc.
        return errors.New("--output is required when --format=json")
    }
    return nil
})

Errors:

  • "nabat: validation function cannot be nil": fn is nil.

type RunFunc

type RunFunc func(*Context) error

RunFunc is the handler invoked after positional args and flags are resolved on a Context.

Return nil to indicate success. Return a non-nil error to report failure; the error is propagated to the caller of App.Run (or [Run] in tests).

RunFunc implementations must not retain the Context or use it after the function returns.

type SelectOption

type SelectOption interface {
	// contains filtered or unexported methods
}

SelectOption configures select and multi-select fields. Its implementors satisfy neither FieldOption[string] nor FieldOption[bool], so passing them to a non-select field is a build error. SelectOption and MultiSelectOption are separate sealed interfaces for WithFiltering, WithHeight, and WithLimit.

func WithFiltering

func WithFiltering(enabled bool) SelectOption

WithFiltering enables or disables incremental option filtering on select and multi-select fields.

func WithHeight

func WithHeight(n int) SelectOption

WithHeight sets the visible row count for select and multi-select lists. n must be > 0.

func WithOptionsFunc

func WithOptionsFunc[E comparable](fn func() []E, binds any) SelectOption

WithOptionsFunc recomputes the choices for a select field when the binding changes. E is inferred from the function's return type:

nabat.WithOptionsFunc(func() []string { return states[country] }, &country)

The binds argument matches huh.Select.OptionsFunc's bindings parameter.

type SpinnerOption

type SpinnerOption func(*spinnerConfig) error

SpinnerOption configures Context.Spinner.

func WithSpinnerType

func WithSpinnerType(t SpinnerType) SpinnerOption

WithSpinnerType sets the spinner animation preset.

Example:

c.Spinner("Deploying...", func() error {
	return nil
}, WithSpinnerType(SpinnerDots()))

type SpinnerType

type SpinnerType spinner.Type

SpinnerType selects spinner frames for WithSpinnerType and Context.Spinner.

func SpinnerDots

func SpinnerDots() SpinnerType

SpinnerDots returns the Braille-dot spinner preset (default for Context.Spinner).

func SpinnerEllipsis

func SpinnerEllipsis() SpinnerType

SpinnerEllipsis returns the animated-ellipsis spinner preset (., .., ...).

func SpinnerGlobe

func SpinnerGlobe() SpinnerType

SpinnerGlobe returns the rotating-globe-emoji spinner preset.

func SpinnerHamburger

func SpinnerHamburger() SpinnerType

SpinnerHamburger returns the three-line-hamburger-animation spinner preset.

func SpinnerJump

func SpinnerJump() SpinnerType

SpinnerJump returns the jumping-dot spinner preset.

func SpinnerLine

func SpinnerLine() SpinnerType

SpinnerLine returns the rotating-line spinner preset (| / - \).

func SpinnerMeter

func SpinnerMeter() SpinnerType

SpinnerMeter returns the filling-meter-animation spinner preset.

func SpinnerMiniDot

func SpinnerMiniDot() SpinnerType

SpinnerMiniDot returns the single-Braille-dot spinner preset.

func SpinnerMonkey

func SpinnerMonkey() SpinnerType

SpinnerMonkey returns the see/hear/speak-no-evil-monkey-emoji spinner preset.

func SpinnerMoon

func SpinnerMoon() SpinnerType

SpinnerMoon returns the moon-phase-animation spinner preset.

func SpinnerPoints

func SpinnerPoints() SpinnerType

SpinnerPoints returns the three-pulsing-points spinner preset.

func SpinnerPulse

func SpinnerPulse() SpinnerType

SpinnerPulse returns the pulsing-block spinner preset.

type TableOption

type TableOption func(*tableConfig)

TableOption configures the Context.Table output method. Pass one or more options to customize borders, styling, width, and wrapping.

Example:

c.Table(headers, rows,
	WithTableBorder(BorderRounded()),
	WithTableWidth(80),
	WithTableBorderRow(true),
)

func WithTableBorder

func WithTableBorder(b lipgloss.Border) TableOption

WithTableBorder sets the border shape drawn around and inside the table. Pass one of the Border* presets or a custom lipgloss.Border value. The default is BorderNormal.

Example:

WithTableBorder(BorderRounded())

func WithTableBorderBottom

func WithTableBorderBottom(enabled bool) TableOption

WithTableBorderBottom toggles the bottom border of the table. Defaults to enabled.

func WithTableBorderColumn

func WithTableBorderColumn(enabled bool) TableOption

WithTableBorderColumn toggles the vertical separators between columns. Defaults to enabled.

func WithTableBorderHeader

func WithTableBorderHeader(enabled bool) TableOption

WithTableBorderHeader toggles the horizontal separator between the header row and the first data row. Defaults to enabled.

func WithTableBorderLeft

func WithTableBorderLeft(enabled bool) TableOption

WithTableBorderLeft toggles the left border of the table. Defaults to enabled.

func WithTableBorderRight

func WithTableBorderRight(enabled bool) TableOption

WithTableBorderRight toggles the right border of the table. Defaults to enabled.

func WithTableBorderRow

func WithTableBorderRow(enabled bool) TableOption

WithTableBorderRow toggles the horizontal separators between data rows. Defaults to disabled.

func WithTableBorderStyle

func WithTableBorderStyle(s lipgloss.Style) TableOption

WithTableBorderStyle sets the lipgloss style applied to border characters. Use this to change border color or decoration independently of the border shape. The default comes from [Theme.TableBorderStyle].

Example:

WithTableBorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("#FF79C6")))

func WithTableBorderTop

func WithTableBorderTop(enabled bool) TableOption

WithTableBorderTop toggles the top border of the table. Defaults to enabled.

func WithTableCellStyle

func WithTableCellStyle(s lipgloss.Style) TableOption

WithTableCellStyle sets the base lipgloss style for data cells. It applies to all non-header rows. The default comes from [Theme.TableCellStyle]. Use WithTableStyleFunc for per-cell control.

Example:

WithTableCellStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("252")))

func WithTableHeaderStyle

func WithTableHeaderStyle(s lipgloss.Style) TableOption

WithTableHeaderStyle sets the lipgloss style for header row cells. The default comes from [Theme.TableHeaderStyle].

Example:

WithTableHeaderStyle(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("111")))

func WithTableStyleFunc

func WithTableStyleFunc(fn func(row, col int) lipgloss.Style) TableOption

WithTableStyleFunc sets a per-cell style function that receives the row and column indices and returns the lipgloss style for that cell. When set, it overrides WithTableHeaderStyle and WithTableCellStyle. The row parameter is HeaderRow (-1) for header cells.

Example:

WithTableStyleFunc(func(row, col int) lipgloss.Style {
	if row == HeaderRow {
		return lipgloss.NewStyle().Bold(true)
	}
	if row%2 == 0 {
		return lipgloss.NewStyle().Foreground(lipgloss.Color("252"))
	}
	return lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
})

func WithTableWidth

func WithTableWidth(w int) TableOption

WithTableWidth sets a fixed total width for the table in columns. Column widths are auto-sized to fit within this constraint. A value of 0 or less is ignored, and the table sizes to its content.

Example:

WithTableWidth(80)

func WithTableWrap

func WithTableWrap(enabled bool) TableOption

WithTableWrap enables or disables text wrapping inside data cells. When disabled, long cell content is truncated with an ellipsis. Headers are never wrapped. Defaults to enabled.

Example:

WithTableWrap(false)

type TextInput

type TextInput interface {
	string | int | int64 | uint | float64 | time.Duration
}

TextInput is the set of Go types that are presented as single-line (or multi-line) text inputs. Bool and select types are excluded. It constrains WithExample and WithMaxChars: exact types (not ~T) so that bool, []string, and future custom types are excluded cleanly.

type TreeChildren

type TreeChildren = tree.Children

TreeChildren is the type passed to tree style functions.

type TreeEnumerator

type TreeEnumerator = tree.Enumerator

TreeEnumerator is the function signature for custom tree enumerators.

func TreeDefaultEnumerator

func TreeDefaultEnumerator() TreeEnumerator

TreeDefaultEnumerator returns the classic box-drawing tree enumerator (├── / └──). It is the default enumerator used by Context.Tree.

func TreeRoundedEnumerator

func TreeRoundedEnumerator() TreeEnumerator

TreeRoundedEnumerator returns a rounded box-drawing tree enumerator (╭── / ╰──).

type TreeIndenter

type TreeIndenter = tree.Indenter

TreeIndenter is the function signature for custom tree indenters.

func TreeDefaultIndenter

func TreeDefaultIndenter() TreeIndenter

TreeDefaultIndenter returns the default indentation function. It draws a vertical line (│) connecting siblings and blank space for the last child.

type TreeNode

type TreeNode struct {
	Value    string
	Children []TreeNode
}

TreeNode represents a node in a tree structure. Use it to build nested trees without importing the tree package directly.

Tree construction recurses on the calling goroutine's stack; pathologically deep trees (tens of thousands of levels) may overflow it. Typical CLI uses (filesystem listings, dependency graphs, command trees) are unaffected.

Example:

nodes := []TreeNode{
	{Value: ".git"},
	{Value: "cmd/", Children: []TreeNode{
		{Value: "root.go"},
		{Value: "serve.go"},
	}},
	{Value: "main.go"},
}
c.Tree("myproject", nodes)

type TreeOption

type TreeOption func(*treeConfig)

TreeOption configures the Context.Tree output method. Pass one or more options to customize the enumerator, indenter, and styling.

Example:

c.Tree("root", nodes,
	WithTreeEnumerator(TreeRoundedEnumerator()),
	WithTreeItemStyle(lipgloss.NewStyle().Bold(true)),
)

func WithTreeEnumerator

func WithTreeEnumerator(e tree.Enumerator) TreeOption

WithTreeEnumerator sets the enumerator used to prefix each tree node. Pass one of TreeDefaultEnumerator or TreeRoundedEnumerator, or a custom TreeEnumerator function. The default is TreeDefaultEnumerator.

Example:

WithTreeEnumerator(TreeRoundedEnumerator())

func WithTreeEnumeratorStyle

func WithTreeEnumeratorStyle(s lipgloss.Style) TreeOption

WithTreeEnumeratorStyle sets the lipgloss style for all enumerator markers (├──, └──, etc.). The default comes from [Theme.TreeEnumeratorStyle]. Use WithTreeEnumeratorStyleFunc for per-node control.

Example:

WithTreeEnumeratorStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("245")))

func WithTreeEnumeratorStyleFunc

func WithTreeEnumeratorStyleFunc(fn tree.StyleFunc) TreeOption

WithTreeEnumeratorStyleFunc sets a per-node enumerator style function. When set, it overrides WithTreeEnumeratorStyle.

Example:

WithTreeEnumeratorStyleFunc(func(_ TreeChildren, i int) lipgloss.Style {
	if i == 0 {
		return lipgloss.NewStyle().Foreground(lipgloss.Color("#FF79C6"))
	}
	return lipgloss.NewStyle()
})

func WithTreeIndenter

func WithTreeIndenter(ind tree.Indenter) TreeOption

WithTreeIndenter sets the indenter used to draw connectors between sibling nodes. The default is TreeDefaultIndenter.

Example:

WithTreeIndenter(func(children TreeChildren, index int) string {
	return "→ "
})

func WithTreeIndenterStyle

func WithTreeIndenterStyle(s lipgloss.Style) TreeOption

WithTreeIndenterStyle sets the lipgloss style for all indentation connectors (│, etc.).

Example:

WithTreeIndenterStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("245")))

func WithTreeIndenterStyleFunc

func WithTreeIndenterStyleFunc(fn tree.StyleFunc) TreeOption

WithTreeIndenterStyleFunc sets a per-node indenter style function. When set, it overrides WithTreeIndenterStyle.

Example:

WithTreeIndenterStyleFunc(func(_ TreeChildren, i int) lipgloss.Style {
	return lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
})

func WithTreeItemStyle

func WithTreeItemStyle(s lipgloss.Style) TreeOption

WithTreeItemStyle sets the lipgloss style for all tree items. The default comes from [Theme.TreeItemStyle]. Use WithTreeItemStyleFunc for per-node control.

Example:

WithTreeItemStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("252")))

func WithTreeItemStyleFunc

func WithTreeItemStyleFunc(fn tree.StyleFunc) TreeOption

WithTreeItemStyleFunc sets a per-node item style function. When set, it overrides WithTreeItemStyle.

Example:

WithTreeItemStyleFunc(func(_ TreeChildren, i int) lipgloss.Style {
	if i == 0 {
		return lipgloss.NewStyle().Bold(true)
	}
	return lipgloss.NewStyle()
})

func WithTreeRootStyle

func WithTreeRootStyle(s lipgloss.Style) TreeOption

WithTreeRootStyle sets the lipgloss style for the root node.

Example:

WithTreeRootStyle(lipgloss.NewStyle().Bold(true))

func WithTreeWidth

func WithTreeWidth(w int) TreeOption

WithTreeWidth sets a fixed total width for the tree in columns. Items will be padded to account for the entire width. A value of 0 or less is ignored.

Example:

WithTreeWidth(80)

type TreeStyleFunc

type TreeStyleFunc = tree.StyleFunc

TreeStyleFunc is a function that determines the style of a tree node based on the children set and the current index.

type VersionOption

type VersionOption func(*versionConfig) error

VersionOption configures the built-in version feature inside WithVersion.

Version is a built-in core feature: pass WithVersion to New or MustNew and the App grows a `version` subcommand and a `--version` (`-v`) flag with Nabat's themed output. Without WithVersion, no version surface is installed.

Defaults: subcommand "version", flag "--version", shorthand "-v". Tweak the defaults by passing VersionOption values to WithVersion; disable a piece with WithoutVersionCommand, WithoutVersionFlag, or WithoutVersionShorthand. To opt out of the feature entirely, simply omit WithVersion.

All polish options are VersionOption values that nest inside WithVersion rather than living at the App level. This groups version-related config in one place and keeps the App-level option list short.

func WithVersionCommandName

func WithVersionCommandName(name string) VersionOption

WithVersionCommandName overrides the subcommand name (default "version"). Empty strings return ErrInvalidOption; use WithoutVersionCommand to disable the subcommand.

func WithVersionCommit

func WithVersionCommit(commit string) VersionOption

WithVersionCommit explicitly sets the VCS commit SHA shown next to the version string. When omitted, the value is read from runtime/debug.ReadBuildInfo (vcs.revision, truncated to 7 chars).

func WithVersionCommitDate

func WithVersionCommitDate(date string) VersionOption

WithVersionCommitDate explicitly sets the commit date shown next to the version string. The value is used verbatim; WithVersionCommitDateTimeFormat has no effect on it. When omitted, the value is read from runtime/debug.ReadBuildInfo (vcs.time).

func WithVersionCommitDateTimeFormat

func WithVersionCommitDateTimeFormat(layout string) VersionOption

WithVersionCommitDateTimeFormat sets the Go time layout used to format the commit timestamp read from runtime/debug.ReadBuildInfo (vcs.time, always RFC3339). Has no effect when the date is set explicitly via WithVersionCommitDate, since that value is used verbatim.

Example:

WithVersionCommitDateTimeFormat("2006-01-02")          // date only
WithVersionCommitDateTimeFormat("2006-01-02 15:04 MST") // compact with timezone

func WithVersionFlagName

func WithVersionFlagName(name string) VersionOption

WithVersionFlagName overrides the long flag name (default "version"). Empty strings return ErrInvalidOption; use WithoutVersionFlag to disable the flag.

func WithVersionShorthand

func WithVersionShorthand(r rune) VersionOption

WithVersionShorthand sets the one-character shorthand for the version flag (default 'v'). The rune must be a printable ASCII character (0x21 to 0x7E); Cobra does not support multi-byte shorthands.

func WithoutVersionCommand

func WithoutVersionCommand() VersionOption

WithoutVersionCommand disables the `version` subcommand. The `--version` flag is still installed unless WithoutVersionFlag is also passed (in which case WithVersion returns ErrInvalidOption).

func WithoutVersionFlag

func WithoutVersionFlag() VersionOption

WithoutVersionFlag disables the `--version` flag. The `version` subcommand is still installed unless WithoutVersionCommand is also passed (in which case WithVersion returns ErrInvalidOption).

func WithoutVersionShorthand

func WithoutVersionShorthand() VersionOption

WithoutVersionShorthand disables the version flag's shorthand. The long form (default --version) keeps working.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL