Documentation
¶
Overview ¶
Package cli is a composable CLI framework built on small interfaces.
Commands are Go types that implement Commander. The framework discovers capabilities through type assertions on optional interfaces — there is no base struct to embed and no configuration DSL.
Core Interface ¶
Every command must implement Commander:
type Commander interface {
Run(ctx context.Context, args []string) error
}
RunFunc adapts a plain function into a Commander for simple cases.
Discovery Interfaces ¶
Implement any combination of these optional interfaces to declare metadata, subcommands, aliases, examples, and hidden status:
- Namer — override the command name (default: lowercase struct type name)
- Descriptor — provide a one-line description
- LongDescriptor — provide extended description for help output
- Aliaser — declare alternate names
- Subcommander — return subcommands
- Hider — hide the command from help output
- Exampler — provide usage examples
- Versioner — report a version string via --version / -V
- Deprecator — mark a command as deprecated with a warning message
- Categorizer — group subcommands under headings in help output
- Fallbacker — provide a fallback subcommand when no name matches
- Exiter — control error printing and process exit in ExecuteAndExit
- Discoverer — provide runtime-discovered commands (plugins)
Lifecycle Interfaces ¶
- Beforer — run setup logic before Run (parent-first), returns modified context
- Afterer — run teardown logic after Run (child-first, always runs)
- Validator — validate state after flag parsing, before Run
Flags ¶
The default flag parser reads struct tags:
type ServeCmd struct {
Port int `flag:"port" short:"p" default:"8080" help:"Port to listen on" env:"PORT"`
Host string `flag:"host" default:"localhost" help:"Host to bind to"`
Tags []string `flag:"tag" short:"t" help:"Tags to apply (repeatable)"`
Env map[string]string `flag:"env" help:"Environment variables as key=value"`
Format string `flag:"format" enum:"text,json,yaml" default:"text" help:"Output format"`
Verbose int `flag:"verbose" short:"v" counter:"true" help:"Increase verbosity"`
Color bool `flag:"color" default:"true" negate:"true" help:"Colorize output"`
}
Supported types: string, int, int64, float64, bool, time.Duration, slices of any scalar type, map[string]string, and any type implementing FlagUnmarshaler.
Struct tag keys:
- flag — the flag name (if empty, derived from field name: OutputFormat → output-format)
- short — single-character short form
- default — default value if not provided
- help — description shown in help output
- env — environment variable name; standalone (without flag/arg) for env/config/default-only fields
- enum — comma-separated list of allowed values
- required — "true" to require the flag
- counter — "true" to increment an int on each occurrence (-vvv)
- negate — "true" to add a --no- prefix that sets a bool to false
- alt — comma-separated additional long flag names (e.g. "output,out")
- sep — separator for splitting a single value into slice elements (e.g. ",")
- hidden — "true" to hide the flag from help output (flag still works)
- deprecated — message shown when the flag is used; prints a warning to stderr
- category — group heading for the flag in help output
- mask — displayed instead of default in help (e.g. "****" for secrets)
- placeholder — value name shown in help (e.g. "PORT" in --port PORT)
- prefix — flag name prefix for named struct fields (e.g. "db-" yields --db-host)
Priority: explicit flag > env var > config > default > zero value.
Use WithEnvVarPrefix to scope all env var lookups under a common prefix. For example, WithEnvVarPrefix("APP_") causes `env:"PORT"` to look up APP_PORT.
Flags can appear anywhere — before or after subcommand names.
The framework performs strict tag validation at parse time. Invalid combinations (flag + arg, required + default, flag-only tags without flag, etc.) return ErrInvalidTag.
Env-Only Fields ¶
A field with an env tag but no flag or arg tag is env/config/default-only. It does not appear in help output and cannot be passed via command-line arguments. This is useful for secrets that should never appear in shell history:
type DeployCmd struct {
Token string `env:"DEPLOY_TOKEN" required:"true"`
Env string `flag:"env" enum:"prod,staging,dev" help:"Target environment"`
}
The logical name is derived from the field name (Token → token) and is used for config resolver lookups and context storage via Set/Get.
Priority for env-only fields: env var > config > default > zero value.
Embedded Structs and Prefix ¶
Anonymous embedded structs have their flags promoted, just like Go's own field promotion:
type OutputFlags struct {
Format string `flag:"format" enum:"json,table" default:"table" help:"Output format"`
}
type ListCmd struct {
OutputFlags // promoted: --format works as if declared on ListCmd
Limit int `flag:"limit" default:"50"`
}
Named struct fields with the prefix tag namespace their flags:
type DBFlags struct {
Host string `flag:"host" default:"localhost" help:"Database host"`
Port int `flag:"port" default:"5432" help:"Database port"`
}
type ServeCmd struct {
DB DBFlags `prefix:"db-"` // --db-host, --db-port
Port int `flag:"port" default:"8080" help:"Listen port"`
}
Prefixes nest: a prefix:"a-" containing prefix:"b-" yields --a-b-name. When an outer and embedded field share a flag name, the outer field wins (matching Go's own promotion semantics).
Inheritance ¶
Flags flow from parent commands to child subcommands via automatic flag inheritance: when a parent and child both declare a flag with the same name and type, the child inherits the parent's parsed value if the child's flag was not explicitly provided via CLI args or env var. The child's flag still appears in help output and accepts CLI args normally.
type App struct {
Env string `flag:"env" required:"true" enum:"dev,qa,prod" help:"Target environment"`
}
type ServeCmd struct {
Env string `flag:"env" help:"Target environment"`
Port int `flag:"port" default:"8080" help:"Listen port"`
}
To inherit a parent flag without exposing it as a child flag, use a hidden flag with the same name:
type ServeCmd struct {
Env string `flag:"env" hidden:"true"`
Port int `flag:"port" default:"8080" help:"Listen port"`
}
Priority for automatic flag inheritance: explicit child flag > child env var > inherited from parent > child default > zero value.
Context Values ¶
The framework automatically stores every parsed flag value in the context before Beforer hooks run. Subcommands can retrieve any ancestor's flag value without declaring struct fields:
func (s *ServeCmd) Run(ctx context.Context, args []string) error {
env := cli.Get[string](ctx, "env") // from parent's --env flag
// ...
}
Three functions are provided:
- Set — store a named value in the context (returns new context)
- Get — retrieve a value by name; returns zero value if missing or type mismatch
- Lookup — retrieve a value by name; returns (value, ok) for safe checking
User code can also call Set in a [Beforer.Before] hook to share arbitrary values (database connections, loggers, etc.) with downstream commands:
func (a *App) Before(ctx context.Context) (context.Context, error) {
return cli.Set(ctx, "db", a.db), nil
}
This complements struct-based inheritance: use context values when you want loose coupling or need to share non-flag data.
Config ¶
Flag values can be loaded from external configuration sources via a ConfigResolver. A resolver is a single function:
type ConfigResolver func(key ConfigKey) (value string, found bool)
Given a ConfigKey, it returns the string value and whether it was found. The key provides both the full flag name ([ConfigKey.Name]) and decomposed parts ([ConfigKey.Parts]) for resolvers backed by nested formats. The framework handles all type conversion, validation, required checks, and enum enforcement — the resolver only needs to return strings.
Priority chain: explicit CLI flag > env var > config > default > zero value.
Set a global resolver via WithConfigResolver:
f, _ := os.Open("config.json")
resolver, _ := config.FromJSON(f)
cli.Execute(ctx, root, os.Args[1:],
cli.WithConfigResolver(resolver),
)
Or implement ConfigProvider on a command for per-command resolvers:
func (c *ServeCmd) ConfigResolver() cli.ConfigResolver {
return config.FromMap(map[string]string{"port": "9090"})
}
Command-level resolvers take priority over the global resolver. Use [config.Chain] to try multiple sources in order:
resolver := config.Chain(
config.FromMap(overrides),
jsonResolver,
)
The [config] subpackage ships [config.FromMap], [config.FromJSON], and [config.Chain]. Because ConfigResolver is a plain function and [config.FromMap] accepts any map[string]string, adding support for any configuration format — YAML, TOML, HCL, .env files, remote stores — is a matter of decoding into a map and calling [config.FromMap]. See the [config] package documentation for copy-paste adapter examples.
Flag Groups ¶
Flags can be constrained to enforce relationships using FlagGrouper:
func (c *Cmd) FlagGroups() []cli.FlagGroup {
return []cli.FlagGroup{
cli.MutuallyExclusive("json", "yaml", "text"),
cli.RequiredTogether("username", "password"),
cli.OneRequired("file", "stdin"),
}
}
Three constraint kinds are supported:
- MutuallyExclusive — at most one flag in the group may be set
- RequiredTogether — if any flag in the group is set, all must be set
- OneRequired — exactly one flag in the group must be set
Validation runs after flag parsing and inheritance, before Validator.
Plugins ¶
Commands can be extended at runtime with external executables (plugins). A command that implements Discoverer provides additional subcommands discovered at runtime, merged with any static subcommands from Subcommander. Built-in commands always take priority on name collisions.
The Discover function scans directories and optionally the system PATH for plugin executables:
func (a *App) Discover() ([]cli.Commander, error) {
return cli.Discover("myapp",
cli.WithDirs(cli.DefaultDirs("myapp")...),
cli.WithPATH(),
)
}
DefaultDirs returns conventional plugin directories in priority order:
- ./<name>/plugins — project-level (highest priority)
- $HOME/.config/<name>/plugins — user-level
- /etc/<name>/plugins — system-level (Unix only)
These paths are configurable. Use WithDir and WithDirs to specify custom directories in any order, and WithPATH to optionally scan PATH for executables matching "<prefix>-<command>".
Plugin Metadata Protocol ¶
When a plugin is discovered, the framework executes it with the --cli-info flag (customizable via WithInfoFlag) and expects optional JSON:
{"name":"deploy","description":"Deploy to cloud","aliases":["d"]}
If the plugin does not support the flag or returns invalid JSON, it still works — it just has no description or aliases in help output. The only requirement for a plugin is that it be an executable file.
This means plugins can be written in any language:
#!/bin/bash
if [ "$1" = "--cli-info" ]; then
echo '{"name":"deploy","description":"Deploy to cloud environments"}'
exit 0
fi
echo "deploying to $1..."
Plugin Discovery Modes ¶
Directory-based discovery (primary): all executable files in the scanned directories become plugins. The filename is the command name.
PATH-based discovery (via WithPATH): executables matching "<prefix>-*" on the system PATH are discovered. The prefix and hyphen are stripped to derive the command name (e.g., "myapp-deploy" → "deploy").
Priority: directories are scanned first in the order added. PATH results have lower priority than any directory result. First match wins on name collision.
Enumerating All Subcommands ¶
AllSubcommands returns the merged set of static and discovered subcommands for a command. This is useful for custom HelpRenderer implementations, documentation generators, and shell completion scripts:
subs, err := cli.AllSubcommands(cmd)
Extensibility ¶
Every major subsystem is replaceable:
- FlagParser — replace the flag parsing engine per-command or globally
- HelpRenderer — replace help rendering per-command or globally
- Helper — override help text for a single command
- HelpAppender / HelpPrepender — add custom sections to the default help output
- Middlewarer — wrap the run function with middleware
- Suggester — custom "did you mean?" per-command
Global overrides are set via Option functions passed to Execute:
cli.Execute(ctx, root, os.Args[1:],
cli.WithStdout(os.Stdout),
cli.WithFlagParser(myParser),
cli.WithShortOptionHandling(true),
cli.WithPrefixMatching(true),
)
Documentation Generation ¶
The doc subpackage generates documentation from the command tree:
doc.GenMarkdown(root) // markdown for a single command doc.GenMarkdownTree(root, "docs/") // markdown files for all commands doc.GenManPage(root, header) // man page for a single command doc.GenManTree(root, "man/", header) // man pages for all commands
Hidden commands and flags are excluded from generated documentation.
Shell Completion ¶
The completion subpackage generates shell completion scripts:
completion.Bash(root, "myapp") // bash completion script completion.Zsh(root, "myapp") // zsh completion script completion.Fish(root, "myapp") // fish completion script completion.PowerShell(root, "myapp") // PowerShell completion script
Generated scripts call the binary at runtime via the __complete protocol: when the binary receives "__complete" as the first argument, it runs RuntimeComplete instead of normal execution. This avoids side effects (no lifecycle hooks, flag parsing, or validation) and provides dynamic completions based on the current command tree.
Commands implementing Completer can provide custom completion candidates. When a command implements Completer, its Complete method is called during tab-completion and the returned strings are offered as candidates. The returned ShellCompDirective controls shell behavior (e.g. suppressing space or file completion). If Complete returns nil completions, the framework falls back to static completion of subcommands and flags.
Example ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// A complete CLI with a root command, subcommand, and flags.
// This is the typical starting point for building a CLI with this package.
type exRoot struct{}
func (r *exRoot) Name() string { return "mytool" }
func (r *exRoot) Description() string { return "A CLI built with the cli package" }
func (r *exRoot) Version() string { return "1.0.0" }
func (r *exRoot) Subcommands() []cli.Commander {
return []cli.Commander{&exHello{}}
}
func (r *exRoot) Run(_ context.Context) error {
return cli.ErrShowHelp
}
type exHello struct {
Recipient string `flag:"name" short:"n" default:"World" help:"Who to greet"`
}
func (h *exHello) Name() string { return "hello" }
func (h *exHello) Description() string { return "Say hello" }
func (h *exHello) Run(_ context.Context) error {
fmt.Fprintf(os.Stdout, "Hello, %s!\n", h.Recipient)
return nil
}
func main() {
app := &exRoot{}
_ = cli.Execute(context.Background(), app, []string{"hello", "--name", "Alice"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: Hello, Alice!
Example (Help) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// A complete CLI with a root command, subcommand, and flags.
// This is the typical starting point for building a CLI with this package.
type exRoot struct{}
func (r *exRoot) Name() string { return "mytool" }
func (r *exRoot) Description() string { return "A CLI built with the cli package" }
func (r *exRoot) Version() string { return "1.0.0" }
func (r *exRoot) Subcommands() []cli.Commander {
return []cli.Commander{&exHello{}}
}
func (r *exRoot) Run(_ context.Context) error {
return cli.ErrShowHelp
}
type exHello struct {
Recipient string `flag:"name" short:"n" default:"World" help:"Who to greet"`
}
func (h *exHello) Name() string { return "hello" }
func (h *exHello) Description() string { return "Say hello" }
func (h *exHello) Run(_ context.Context) error {
fmt.Fprintf(os.Stdout, "Hello, %s!\n", h.Recipient)
return nil
}
func main() {
app := &exRoot{}
_ = cli.Execute(context.Background(), app, []string{"--help"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: A CLI built with the cli package Usage: mytool [command] mytool [args...] Commands: hello Say hello Use "mytool [command] --help" for more information about a command.
Index ¶
- Variables
- func DefaultDirs(name string) []string
- func ExactArgs(n int) func([]string) error
- func Execute(ctx context.Context, root Commander, args []string, opts ...Option) error
- func ExecuteAndExit(ctx context.Context, root Commander, args []string, opts ...Option)
- func Exit(message string, code int) error
- func Exitf(code int, format string, args ...any) error
- func Get[T any](ctx context.Context, name string) T
- func Lookup[T any](ctx context.Context, name string) (T, bool)
- func MaxArgs(n int) func([]string) error
- func MinArgs(n int) func([]string) error
- func NoArgs(args []string) error
- func RangeArgs(lo, hi int) func([]string) error
- func RuntimeComplete(ctx context.Context, root Commander, args []string, w io.Writer)
- func Set[T any](ctx context.Context, name string, val T) context.Context
- func ValidateFlags(cmd Commander, provided map[string]bool) error
- type Afterer
- type Aliaser
- type ArgDef
- type Args
- type ArgsValidator
- type Beforer
- type Categorizer
- type Commander
- type Completer
- type ConfigKey
- type ConfigProvider
- type ConfigResolver
- type Deprecator
- type Descriptor
- type DiscoverOption
- type Discoverer
- type Example
- type Exampler
- type ExitCoder
- type Exiter
- type ExternalCommand
- type Fallbacker
- type FlagCompleter
- type FlagDef
- type FlagGroup
- type FlagGroupKind
- type FlagGrouper
- type FlagParser
- type FlagUnmarshaler
- type HelpAppender
- type HelpPrepender
- type HelpRenderer
- type HelpSection
- type Helper
- type Hider
- type LongDescriptor
- type Middlewarer
- type Namer
- type Option
- func Bind(v any) Option
- func BindProvider[T any](fn func() (T, error)) Option
- func BindSingleton[T any](fn func() (T, error)) Option
- func BindTo(v any, iface any) Option
- func WithCaseInsensitive(enabled bool) Option
- func WithConfigResolver(r ConfigResolver) Option
- func WithEnvVarPrefix(prefix string) Option
- func WithFlagNormalization(fn func(string) string) Option
- func WithFlagParser(p FlagParser) Option
- func WithHelpRenderer(r HelpRenderer) Option
- func WithIgnoreUnknown(enabled bool) Option
- func WithInteractive(enabled bool) Option
- func WithPrefixMatching(enabled bool) Option
- func WithShortOptionHandling(enabled bool) Option
- func WithSignalHandling(enabled bool) Option
- func WithSortedHelp(enabled bool) Option
- func WithStderr(w io.Writer) Option
- func WithStdin(r io.Reader) Option
- func WithStdout(w io.Writer) Option
- func WithSuggest(enabled bool) Option
- type Passthrougher
- type PluginInfo
- type Prompter
- type RunFunc
- type ShellCompDirective
- type Subcommander
- type Suggester
- type Validator
- type Versioner
Examples ¶
- Package
- Package (Help)
- Execute (Categories)
- Execute (ConfigProvider)
- Execute (ConfigResolver)
- Execute (Counter)
- Execute (DefaultCommand)
- Execute (Deprecated)
- Execute (Enum)
- Execute (FlagInheritance)
- Execute (Flags)
- Execute (HiddenInherit)
- Execute (Lifecycle)
- Execute (MapFlags)
- Execute (Middleware)
- Execute (NegatableBool)
- Execute (PrefixMatching)
- Execute (ShortOptionHandling)
- Execute (SliceFlags)
- Execute (Subcommands)
- Execute (Versioner)
- Exit
- RunFunc
Constants ¶
This section is empty.
Variables ¶
var ( ErrUnknownFlag = errors.New("unknown flag") ErrFlagRequiresVal = errors.New("flag requires a value") ErrRequiredFlag = errors.New("required flag not provided") ErrUnsupportedType = errors.New("unsupported flag type") ErrInvalidFlagValue = errors.New("invalid flag value") ErrInvalidTag = errors.New("invalid struct tag") )
Sentinel errors for flag parsing.
var ErrShowHelp = errors.New("show help")
ErrShowHelp can be returned from [Commander.Run] to make the framework render help for the current command. The error is not propagated to the caller.
Functions ¶
func DefaultDirs ¶
DefaultDirs returns the conventional plugin directories for the given application name, in priority order:
- ./<name>/plugins — project-level (highest priority)
- $HOME/.config/<name>/plugins — user-level
- /etc/<name>/plugins — system-level (lowest priority, Unix only)
Missing directories are silently skipped by Discover. The returned paths are suitable for passing directly to WithDirs:
cli.Discover("myapp", cli.WithDirs(cli.DefaultDirs("myapp")...))
func Execute ¶
Execute runs the command tree rooted at root with the given args and options. It resolves subcommands, parses flags, runs lifecycle hooks, and executes the target command.
Example (Categories) ¶
package main
import (
"context"
"os"
"github.com/bjaus/cli"
)
// Categorizer interface groups subcommands in help output.
// Use to organize large CLIs with many subcommands into logical groups.
type AdminCmd struct{}
func (a *AdminCmd) Run(_ context.Context) error { return nil }
func (a *AdminCmd) Name() string { return "users" }
func (a *AdminCmd) Description() string { return "Manage users" }
func (a *AdminCmd) Category() string { return "Admin Commands" }
type CoreCmd struct{}
func (c *CoreCmd) Run(_ context.Context) error { return nil }
func (c *CoreCmd) Name() string { return "run" }
func (c *CoreCmd) Description() string { return "Run the app" }
type CategorizedApp struct{}
func (a *CategorizedApp) Run(_ context.Context) error { return nil }
func (a *CategorizedApp) Name() string { return "myapp" }
func (a *CategorizedApp) Description() string { return "Categorized example" }
func (a *CategorizedApp) Subcommands() []cli.Commander {
return []cli.Commander{&CoreCmd{}, &AdminCmd{}}
}
func main() {
app := &CategorizedApp{}
_ = cli.Execute(context.Background(), app, []string{"--help"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: Categorized example Usage: myapp [command] myapp [args...] Commands: run Run the app Admin Commands: users Manage users Use "myapp [command] --help" for more information about a command.
Example (ConfigProvider) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// ConfigProvider lets a command supply its own resolver.
// The command-level resolver takes priority over the global one.
type ConfigProviderCmd struct {
Port int `flag:"port" default:"8080" help:"Listen port"`
}
func (c *ConfigProviderCmd) Run(_ context.Context) error {
fmt.Fprintf(os.Stdout, "port=%d\n", c.Port)
return nil
}
func (c *ConfigProviderCmd) ConfigResolver() cli.ConfigResolver {
return func(key cli.ConfigKey) (string, bool) {
if key.Name == "port" {
return "3000", true
}
return "", false
}
}
func main() {
cmd := &ConfigProviderCmd{}
_ = cli.Execute(context.Background(), cmd, nil, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: port=3000
Example (ConfigResolver) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// ConfigResolver loads flag values from an external source.
// Config values sit between defaults and env vars in priority:
// explicit flag > env > config > default > zero.
type ConfigServeCmd struct {
Port int `flag:"port" default:"8080" help:"Listen port"`
Host string `flag:"host" default:"localhost" help:"Host to bind to"`
}
func (c *ConfigServeCmd) Run(_ context.Context) error {
fmt.Fprintf(os.Stdout, "host=%s port=%d\n", c.Host, c.Port)
return nil
}
func main() {
resolver := cli.ConfigResolver(func(key cli.ConfigKey) (string, bool) {
m := map[string]string{"port": "9090", "host": "0.0.0.0"}
v, ok := m[key.Name]
return v, ok
})
cmd := &ConfigServeCmd{}
_ = cli.Execute(context.Background(), cmd, nil, //nolint:errcheck // example
cli.WithStdout(os.Stdout),
cli.WithConfigResolver(resolver),
)
}
Output: host=0.0.0.0 port=9090
Example (Counter) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// Counter flags increment an int each time the flag appears.
// Classic use case: -v for info, -vv for debug, -vvv for trace.
type VerboseCmd struct {
Verbosity int `flag:"verbose" short:"v" counter:"true" help:"Increase verbosity"`
}
func (c *VerboseCmd) Run(_ context.Context) error {
fmt.Fprintf(os.Stdout, "verbosity: %d\n", c.Verbosity)
return nil
}
func main() {
cmd := &VerboseCmd{}
_ = cli.Execute(context.Background(), cmd, []string{"-v", "-v", "-v"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: verbosity: 3
Example (DefaultCommand) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// Fallbacker interface provides a fallback subcommand when none is specified.
// Use for CLIs where the "main" action shouldn't require a subcommand name.
type ServeCmd struct{}
func (s *ServeCmd) Run(_ context.Context) error {
fmt.Fprintln(os.Stdout, "serving on :8080")
return nil
}
func (s *ServeCmd) Name() string { return "serve" }
type DefaultApp struct{}
func (a *DefaultApp) Run(_ context.Context) error { return nil }
func (a *DefaultApp) Name() string { return "app" }
func (a *DefaultApp) Subcommands() []cli.Commander { return []cli.Commander{&ServeCmd{}} }
func (a *DefaultApp) Fallback() cli.Commander { return &ServeCmd{} }
func main() {
app := &DefaultApp{}
// No subcommand specified — Fallback() is invoked automatically.
_ = cli.Execute(context.Background(), app, nil, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: serving on :8080
Example (Deprecated) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// Deprecator interface prints a warning to stderr when a command runs.
// Use when retiring a command — keep it functional but warn users to migrate.
type OldCmd struct{}
func (o *OldCmd) Run(_ context.Context) error {
fmt.Fprintln(os.Stdout, "still works")
return nil
}
func (o *OldCmd) Name() string { return "old-cmd" }
func (o *OldCmd) Deprecated() string { return "use new-cmd instead" }
func main() {
cmd := &OldCmd{}
// Redirect stderr to stdout so the example can capture it.
_ = cli.Execute(context.Background(), cmd, nil, cli.WithStdout(os.Stdout), cli.WithStderr(os.Stdout)) //nolint:errcheck // example
}
Output: Warning: "old-cmd" is deprecated: use new-cmd instead still works
Example (Enum) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// Enum validation restricts a flag to a fixed set of values.
// The framework validates automatically — no need to check in Run.
type OutputCmd struct {
Format string `flag:"format" short:"f" default:"text" enum:"text,json,yaml" help:"Output format"`
}
func (o *OutputCmd) Run(_ context.Context) error {
fmt.Fprintf(os.Stdout, "format: %s\n", o.Format)
return nil
}
func main() {
cmd := &OutputCmd{}
_ = cli.Execute(context.Background(), cmd, []string{"--format", "json"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
// Invalid values produce a clear error.
err := cli.Execute(context.Background(), cmd, []string{"--format", "xml"})
fmt.Fprintln(os.Stdout, err) //nolint:errcheck // example output
}
Output: format: json invalid flag value: --format must be one of [text,json,yaml]
Example (FlagInheritance) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// Automatic flag inheritance: parent and child both declare --env.
// The child inherits the parent's parsed value when its flag is not set.
type InheritApp struct {
Env string `flag:"env" help:"Target environment"`
}
func (a *InheritApp) Run(_ context.Context) error { return nil }
func (a *InheritApp) Name() string { return "app" }
func (a *InheritApp) Subcommands() []cli.Commander { return []cli.Commander{&InheritServe{}} }
type InheritServe struct {
Env string `flag:"env" help:"Target environment"`
Port int `flag:"port" default:"8080" help:"Listen port"`
}
func (s *InheritServe) Name() string { return "serve" }
func (s *InheritServe) Run(_ context.Context) error {
fmt.Fprintf(os.Stdout, "env=%s port=%d\n", s.Env, s.Port)
return nil
}
func main() {
app := &InheritApp{}
// --env is set on the parent; child inherits it automatically.
_ = cli.Execute(context.Background(), app, []string{"--env", "prod", "serve"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: env=prod port=8080
Example (Flags) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// A command struct with flags parsed from struct tags.
type GreetCmd struct {
Name string `flag:"name" short:"n" default:"World" help:"Who to greet"`
Excited bool `flag:"excited" short:"e" help:"Add exclamation mark"`
}
func (g *GreetCmd) Run(_ context.Context) error {
punct := "."
if g.Excited {
punct = "!"
}
fmt.Fprintf(os.Stdout, "Hello, %s%s\n", g.Name, punct)
return nil
}
func main() {
cmd := &GreetCmd{}
_ = cli.Execute(context.Background(), cmd, []string{"--name", "Alice", "-e"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: Hello, Alice!
Example (HiddenInherit) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// Hidden flag inheritance: the child gets the parent's flag value via a hidden
// flag with the same name. It does not appear in help but still participates
// in automatic flag inheritance.
type HiddenInheritApp struct {
Env string `flag:"env" help:"Target environment"`
}
func (a *HiddenInheritApp) Run(_ context.Context) error { return nil }
func (a *HiddenInheritApp) Name() string { return "app" }
func (a *HiddenInheritApp) Subcommands() []cli.Commander {
return []cli.Commander{&HiddenInheritServe{}}
}
type HiddenInheritServe struct {
Env string `flag:"env" hidden:"true"`
Port int `flag:"port" default:"8080" help:"Listen port"`
}
func (s *HiddenInheritServe) Name() string { return "serve" }
func (s *HiddenInheritServe) Run(_ context.Context) error {
fmt.Fprintf(os.Stdout, "env=%s port=%d\n", s.Env, s.Port)
return nil
}
func main() {
app := &HiddenInheritApp{}
// --env is set on the parent; child's hidden --env receives it via auto-inheritance.
_ = cli.Execute(context.Background(), app, []string{"--env", "staging", "serve"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: env=staging port=8080
Example (Lifecycle) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// Demonstrating lifecycle hooks with Before and After.
type SetupApp struct {
child *WorkerCmd
}
func (s *SetupApp) Run(_ context.Context) error { return nil }
func (s *SetupApp) Name() string { return "app" }
func (s *SetupApp) Subcommands() []cli.Commander { return []cli.Commander{s.child} }
func (s *SetupApp) Before(ctx context.Context) (context.Context, error) {
fmt.Fprintln(os.Stdout, "setup: before")
return ctx, nil
}
func (s *SetupApp) After(_ context.Context) error {
fmt.Fprintln(os.Stdout, "setup: after")
return nil
}
type WorkerCmd struct{}
func (w *WorkerCmd) Run(_ context.Context) error {
fmt.Fprintln(os.Stdout, "worker: run")
return nil
}
func (w *WorkerCmd) Name() string { return "work" }
func main() {
app := &SetupApp{child: &WorkerCmd{}}
_ = cli.Execute(context.Background(), app, []string{"work"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: setup: before worker: run setup: after
Example (MapFlags) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// Map flags accept key=value pairs. Repeated flags add entries.
// Use for label-like data: --env DB_HOST=localhost --env DB_PORT=5432.
type DeployCmd struct {
Env map[string]string `flag:"env" short:"e" help:"Environment variables"`
}
func (d *DeployCmd) Run(_ context.Context) error {
for k, v := range d.Env {
fmt.Fprintf(os.Stdout, "%s=%s\n", k, v)
}
return nil
}
func main() {
cmd := &DeployCmd{}
_ = cli.Execute(context.Background(), cmd, []string{"--env", "REGION=us-east-1"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: REGION=us-east-1
Example (Middleware) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// Demonstrating middleware wrapping around Run.
type LoggedCmd struct{}
func (l *LoggedCmd) Run(_ context.Context) error {
fmt.Fprintln(os.Stdout, "executing")
return nil
}
func (l *LoggedCmd) Middleware() []func(next cli.RunFunc) cli.RunFunc {
return []func(next cli.RunFunc) cli.RunFunc{
func(next cli.RunFunc) cli.RunFunc {
return func(ctx context.Context) error {
fmt.Fprintln(os.Stdout, "middleware: before")
err := next(ctx)
fmt.Fprintln(os.Stdout, "middleware: after")
return err
}
},
}
}
func main() {
cmd := &LoggedCmd{}
_ = cli.Execute(context.Background(), cmd, nil, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: middleware: before executing middleware: after
Example (NegatableBool) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// Negatable bools support --flag and --no-flag patterns.
// Use for features with sensible defaults that users may want to explicitly disable:
// --color is on by default, --no-color turns it off.
type ColorCmd struct {
Color bool `flag:"color" default:"true" negate:"true" help:"Colorize output"`
}
func (c *ColorCmd) Run(_ context.Context) error {
fmt.Fprintf(os.Stdout, "color: %v\n", c.Color)
return nil
}
func main() {
cmd := &ColorCmd{}
_ = cli.Execute(context.Background(), cmd, []string{"--no-color"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: color: false
Example (PrefixMatching) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// Prefix matching resolves unique prefixes to full subcommand names.
// "sta" matches "status" if no other subcommand starts with "sta".
// Ambiguous prefixes (matching multiple commands) produce an error.
type StatusCmd struct{}
func (s *StatusCmd) Run(_ context.Context) error {
fmt.Fprintln(os.Stdout, "all systems go")
return nil
}
func (s *StatusCmd) Name() string { return "status" }
type PrefixApp struct{}
func (a *PrefixApp) Run(_ context.Context) error { return nil }
func (a *PrefixApp) Name() string { return "app" }
func (a *PrefixApp) Subcommands() []cli.Commander { return []cli.Commander{&StatusCmd{}} }
func main() {
app := &PrefixApp{}
_ = cli.Execute(context.Background(), app, []string{"sta"}, //nolint:errcheck // example
cli.WithStdout(os.Stdout),
cli.WithPrefixMatching(true),
)
}
Output: all systems go
Example (ShortOptionHandling) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// Short option combining lets users merge single-character flags.
// Enable with WithShortOptionHandling. -abc expands to -a -b -c.
// Combine with counters: -vvv is equivalent to -v -v -v.
type CompactCmd struct {
All bool `flag:"all" short:"a" help:"Show all"`
Long bool `flag:"long" short:"l" help:"Long format"`
Reverse bool `flag:"reverse" short:"r" help:"Reverse order"`
}
func (c *CompactCmd) Run(_ context.Context) error {
fmt.Fprintf(os.Stdout, "all=%v long=%v reverse=%v\n", c.All, c.Long, c.Reverse)
return nil
}
func main() {
cmd := &CompactCmd{}
_ = cli.Execute(context.Background(), cmd, []string{"-alr"}, //nolint:errcheck // example
cli.WithStdout(os.Stdout),
cli.WithShortOptionHandling(true),
)
}
Output: all=true long=true reverse=true
Example (SliceFlags) ¶
package main
import (
"context"
"fmt"
"os"
"strings"
"github.com/bjaus/cli"
)
// Slice flags accumulate values from repeated flags.
// Use when a flag can be specified multiple times, like --tag staging --tag prod.
type BuildCmd struct {
Tags []string `flag:"tag" short:"t" help:"Tags to apply"`
}
func (b *BuildCmd) Run(_ context.Context) error {
fmt.Fprintf(os.Stdout, "tags: %s\n", strings.Join(b.Tags, ", "))
return nil
}
func main() {
cmd := &BuildCmd{}
_ = cli.Execute(context.Background(), cmd, []string{"--tag", "v1", "--tag", "latest"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: tags: v1, latest
Example (Subcommands) ¶
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
// A command struct with flags parsed from struct tags.
type GreetCmd struct {
Name string `flag:"name" short:"n" default:"World" help:"Who to greet"`
Excited bool `flag:"excited" short:"e" help:"Add exclamation mark"`
}
func (g *GreetCmd) Run(_ context.Context) error {
punct := "."
if g.Excited {
punct = "!"
}
fmt.Fprintf(os.Stdout, "Hello, %s%s\n", g.Name, punct)
return nil
}
// A parent command with subcommands demonstrating the tree structure.
type App struct{}
func (a *App) Run(_ context.Context) error {
fmt.Fprintln(os.Stdout, "Use a subcommand. Try --help.")
return nil
}
func (a *App) Name() string { return "myapp" }
func (a *App) Description() string { return "My example application" }
func (a *App) Subcommands() []cli.Commander {
return []cli.Commander{&GreetCmd{}, &VersionCmd{}}
}
type VersionCmd struct{}
func (v *VersionCmd) Run(_ context.Context) error {
fmt.Fprintln(os.Stdout, "v1.0.0")
return nil
}
func (v *VersionCmd) Name() string { return "version" }
func (v *VersionCmd) Description() string { return "Print version" }
func main() {
app := &App{}
_ = cli.Execute(context.Background(), app, []string{"version"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: v1.0.0
Example (Versioner) ¶
package main
import (
"context"
"os"
"github.com/bjaus/cli"
)
// Versioner interface adds --version / -V support.
// Implement on your root command to report the application version.
type MyApp struct{}
func (a *MyApp) Run(_ context.Context) error { return nil }
func (a *MyApp) Name() string { return "myapp" }
func (a *MyApp) Version() string { return "2.1.0" }
func main() {
app := &MyApp{}
_ = cli.Execute(context.Background(), app, []string{"--version"}, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: 2.1.0
func ExecuteAndExit ¶
ExecuteAndExit calls Execute and exits the process. If root implements Exiter, its Exit method is called with the error and controls the exit entirely. Otherwise, the error is printed to stderr. For flag and command errors (unknown flag, missing required flag, etc.) a usage hint is appended. If the error implements ExitCoder, its exit code is used; non-nil errors default to exit code 1.
func Exit ¶
Exit returns an error that implements ExitCoder with the given message and exit code.
Example ¶
Demonstrating error handling with Exit.
package main
import (
"context"
"errors"
"fmt"
"os"
"github.com/bjaus/cli"
)
func main() {
cmd := cli.RunFunc(func(_ context.Context) error {
return cli.Exit("port already in use", 2)
})
err := cli.Execute(context.Background(), cmd, nil)
if err != nil {
fmt.Fprintln(os.Stdout, err) //nolint:errcheck // example output
var ec cli.ExitCoder
if ok := errors.As(err, &ec); ok {
fmt.Fprintf(os.Stdout, "exit code: %d\n", ec.ExitCode()) //nolint:errcheck // example output
}
}
}
Output: port already in use exit code: 2
func Exitf ¶
Exitf returns an error that implements ExitCoder with a formatted message and exit code.
func Get ¶
Get retrieves a named value from the context. It returns the zero value of T if the name is not found or if the stored value's type does not match T.
Use Lookup when you need to distinguish between a missing value and a stored zero value.
env := cli.Get[string](ctx, "env")
func Lookup ¶
Lookup retrieves a named value from the context. It returns the zero value and false if the name is not found or if the stored value's type does not match T.
env, ok := cli.Lookup[string](ctx, "env")
func RangeArgs ¶
RangeArgs returns an arg validator that requires between lo and hi arguments inclusive.
func RuntimeComplete ¶
RuntimeComplete generates completion candidates for the given args and writes them to w. Each candidate is one line, optionally followed by a tab and description. The last line is a directive in the format ":<int>".
func Set ¶
Set stores a named value in the context. The returned context contains the value and should be used for subsequent operations.
The framework automatically calls Set for every parsed flag value before Beforer hooks run, so flag values are available to all commands in the chain via Get or Lookup. User code can also call Set in a [Beforer.Before] hook to share arbitrary values with downstream commands:
func (a *App) Before(ctx context.Context) (context.Context, error) {
db, err := openDB(a.DSN)
if err != nil {
return ctx, err
}
return cli.Set(ctx, "db", db), nil
}
func (s *ServeCmd) Run(ctx context.Context, args []string) error {
db := cli.Get[*sql.DB](ctx, "db")
// ...
}
func ValidateFlags ¶
ValidateFlags runs required and enum checks on a command using the given provided set. This is called after flag inheritance so that inherited values satisfy required constraints and are checked against enum lists.
Types ¶
type Afterer ¶
Afterer runs teardown logic after Run. Called child-first through the command chain. After hooks always run, even if Run returned an error.
type Aliaser ¶
type Aliaser interface {
Aliases() []string
}
Aliaser declares alternate names for the command.
type ArgDef ¶
type ArgDef struct {
Name string
Help string
Default string
Mask string
Env string
Enum string
Required bool
TypeName string
IsSlice bool
}
ArgDef describes a positional argument for use by custom HelpRenderer implementations.
func ScanArgs ¶
ScanArgs inspects a command's struct tags and returns positional arg definitions. This is exported so custom HelpRenderer implementations can inspect a command's args.
type Args ¶
type Args []string
Args is a slice of positional arguments. Commands can declare an Args field to receive positional arguments via dependency injection:
type ServeCmd struct {
Args cli.Args
Port int `flag:"port"`
}
func (s *ServeCmd) Run(ctx context.Context) error {
for _, file := range s.Args {
// process file
}
}
type ArgsValidator ¶
ArgsValidator validates positional arguments after flag parsing. Checked on the leaf command before Validator.
type Beforer ¶
Beforer runs setup logic before Run. Called parent-first through the command chain. The returned context flows forward to subsequent hooks and Run.
type Categorizer ¶
type Categorizer interface {
Category() string
}
Categorizer groups the command under a heading in help output.
type Commander ¶
Commander is the core interface every command must implement.
func AllSubcommands ¶
AllSubcommands returns all subcommands for a command by merging static subcommands from Subcommander with runtime-discovered subcommands from Discoverer. Built-in commands from Subcommander take priority — discovered commands whose name or alias collides with a built-in are silently dropped.
This is useful for custom HelpRenderer implementations, documentation generators, and shell completion scripts that need to enumerate all available subcommands including plugins.
func Discover ¶
func Discover(prefix string, opts ...DiscoverOption) ([]Commander, error)
Discover scans directories and optionally PATH for plugin executables and returns them as Commander values. Each discovered executable is wrapped in an ExternalCommand.
In directories, every executable file becomes a plugin. The command name is derived from the filename (e.g., a file named "deploy" in the directory becomes the "deploy" command).
On PATH (enabled with WithPATH), executables matching "<prefix>-*" are discovered. The prefix and hyphen are stripped to derive the command name (e.g., prefix "mytool" + executable "mytool-deploy" → command "deploy").
For each discovered executable, Discover runs it with the info flag (default "--cli-info") to retrieve optional PluginInfo JSON. If the executable does not support the flag, the plugin still works — it just has no description or aliases in help output.
Priority rules:
- Directories are scanned in the order added via WithDir/WithDirs.
- Within each directory, all executables are found.
- First match wins on command name collision across directories.
- PATH results (from WithPATH) have lower priority than any directory result.
Example:
func (a *App) Discover() ([]cli.Commander, error) {
return cli.Discover("myapp",
cli.WithDirs(cli.DefaultDirs("myapp")...),
cli.WithPATH(),
)
}
func HelpCommand ¶
HelpCommand returns a Commander that displays help for a named command. It accepts command names as positional arguments and prints the help output for that command. Typically added as a hidden "help" subcommand:
func (a *App) Subcommands() []cli.Commander {
return []cli.Commander{&serveCmd{}, cli.HelpCommand(a, os.Stdout)}
}
Usage:
myapp help serve # shows help for "serve" myapp help cluster list # shows help for "cluster list"
func Leaf ¶
Leaf returns the leaf (target) command from the context. The framework stores the leaf before Beforer hooks run, so parent commands can inspect the leaf to make decisions based on consumer-defined interfaces. Returns nil if called on a context that did not originate from Execute.
A common use case is centralized auth: define marker interfaces in your application and check them in a root Beforer:
type Authenticated interface{ Authenticate() }
type Authorized interface{ Permissions() []string }
func (r *Root) Before(ctx context.Context) (context.Context, error) {
leaf := cli.Leaf(ctx)
if _, ok := leaf.(Authenticated); !ok {
return ctx, nil // no auth required
}
token, err := auth.Login(ctx)
if err != nil {
return ctx, err
}
ctx = auth.WithToken(ctx, token)
if az, ok := leaf.(Authorized); ok {
if err := auth.Check(token, az.Permissions()); err != nil {
return ctx, err
}
}
return ctx, nil
}
type Completer ¶
type Completer interface {
Complete(ctx context.Context, args []string) ([]string, ShellCompDirective)
}
Completer provides shell completion candidates. The returned ShellCompDirective controls shell behavior after completing (e.g. suppressing space or file completion). Return nil completions to fall through to static completion of subcommands and flags.
type ConfigKey ¶
ConfigKey identifies a flag for config resolution. Name is the full prefixed flag name (e.g. "db-host"). Parts decomposes the name into prefix segments and base name (e.g. ["db", "host"]), useful for resolvers backed by nested configuration formats (YAML, TOML). For unprefixed flags, Parts contains a single element equal to Name.
type ConfigProvider ¶
type ConfigProvider interface {
ConfigResolver() ConfigResolver
}
ConfigProvider is implemented by commands that supply their own resolver. Checked before the global resolver set via WithConfigResolver.
type ConfigResolver ¶
ConfigResolver resolves flag values from an external source such as a config file. Given a ConfigKey, it returns the string value and whether the flag was found. The framework handles type conversion. Use [ConfigKey.Name] for flat lookups or [ConfigKey.Parts] for nested lookups.
type Deprecator ¶
type Deprecator interface {
Deprecated() string
}
Deprecator marks a command as deprecated. A non-empty return value is printed as a warning to stderr before the command runs.
type Descriptor ¶
type Descriptor interface {
Description() string
}
Descriptor provides a one-line description shown in help output.
type DiscoverOption ¶
type DiscoverOption func(*discoverConfig)
DiscoverOption configures how Discover finds plugin executables.
func WithDir ¶
func WithDir(dir string) DiscoverOption
WithDir adds a directory to scan for plugin executables. Directories are scanned in the order they are added. When multiple directories contain a plugin with the same command name, the first one found wins.
Missing directories are silently skipped — this is not an error. Only directories that exist but cannot be read produce an error.
func WithDirs ¶
func WithDirs(dirs ...string) DiscoverOption
WithDirs adds multiple directories to scan for plugin executables. Equivalent to calling WithDir for each directory in order.
func WithInfoFlag ¶
func WithInfoFlag(flag string) DiscoverOption
WithInfoFlag sets the flag name used to query plugin metadata. The default is "--cli-info". When discovering plugins, the framework executes each plugin with this flag to retrieve optional PluginInfo JSON metadata. If the plugin does not support the flag, returns non-zero, or returns invalid JSON, the plugin is still registered — it just has no description or aliases.
func WithPATH ¶
func WithPATH() DiscoverOption
WithPATH enables scanning the system PATH for executables matching "<prefix>-<command>". For example, if the prefix is "mytool", an executable named "mytool-deploy" on PATH becomes the "deploy" command.
PATH-discovered plugins have lower priority than directory-discovered plugins. Unreadable PATH entries are silently skipped.
type Discoverer ¶
Discoverer provides runtime-discovered commands (plugins). Discovered commands are merged with [Subcommander.Subcommands] — built-in commands take priority on name collisions. A command may implement both Subcommander and Discoverer; it may also implement Discoverer alone (without Subcommander) to have only dynamically discovered subcommands.
Use Discover to scan directories and PATH for plugin executables:
func (a *App) Discover() ([]Commander, error) {
return cli.Discover("myapp",
cli.WithDirs(cli.DefaultDirs("myapp")...),
cli.WithPATH(),
)
}
type Exampler ¶
type Exampler interface {
Examples() []Example
}
Exampler provides usage examples shown in help output.
type ExitCoder ¶
type ExitCoder interface {
ExitCode() int
}
ExitCoder is implemented by errors that carry a process exit code.
type Exiter ¶
type Exiter interface {
Exit(err error)
}
Exiter controls process exit behavior. When implemented on the root command, ExecuteAndExit delegates to Exit instead of calling os.Exit directly. The implementation is responsible for printing the error and exiting the process.
type ExternalCommand ¶
type ExternalCommand struct {
// Path is the absolute path to the plugin executable.
Path string
// Cmd is the command name used for subcommand matching and help output.
Cmd string
// Desc is the one-line description shown in help output.
Desc string
// CommandAliases are alternate names for the command.
CommandAliases []string
// Args receives positional arguments via injection.
Args Args
}
ExternalCommand wraps an external executable as a Commander. When Run is called, it executes the binary, wiring stdin, stdout, and stderr to the parent process.
ExternalCommand implements Namer, Descriptor, and Aliaser.
func (*ExternalCommand) Aliases ¶
func (e *ExternalCommand) Aliases() []string
Aliases implements Aliaser.
func (*ExternalCommand) Description ¶
func (e *ExternalCommand) Description() string
Description implements Descriptor.
type Fallbacker ¶
type Fallbacker interface {
Fallback() Commander
}
Fallbacker provides a fallback subcommand to run when no subcommand name matches.
type FlagCompleter ¶
type FlagCompleter interface {
CompleteFlag(ctx context.Context, flag string, value string) ([]string, ShellCompDirective)
}
FlagCompleter provides dynamic completion for flag values. When a flag requires a value and the command implements this interface, the framework calls CompleteFlag with the flag name and partial value. Return nil to fall through to enum-based completion.
func (c *DeployCmd) CompleteFlag(ctx context.Context, flag, value string) ([]string, ShellCompDirective) {
if flag == "region" {
return []string{"us-east-1", "us-west-2", "eu-west-1"}, cli.ShellCompDirectiveNoFileComp
}
return nil, cli.ShellCompDirectiveDefault
}
type FlagDef ¶
type FlagDef struct {
Name string
Short string
Alt []string // additional long flag names
Help string
Default string
Mask string // displayed instead of Default in help (e.g. "****" for secrets)
Env string
Enum string
Sep string // separator for splitting values into slice elements (e.g. ",")
Category string
Deprecated string
Placeholder string // shown in help as value name (e.g. "PORT" in --port PORT)
Required bool
Hidden bool
TypeName string
IsBool bool
IsCounter bool
Negate bool
}
FlagDef describes a single flag for use by custom HelpRenderer implementations.
func ScanFlags ¶
ScanFlags inspects a command's struct tags and returns flag definitions. This is exported so custom HelpRenderer and FlagParser implementations can inspect a command's flags.
type FlagGroup ¶
type FlagGroup struct {
Kind FlagGroupKind
Flags []string
}
FlagGroup defines a relationship constraint between flags.
func MutuallyExclusive ¶
MutuallyExclusive creates a flag group where at most one flag may be set.
func OneRequired ¶
OneRequired creates a flag group where exactly one flag must be set.
func RequiredTogether ¶
RequiredTogether creates a flag group where if any flag is set, all must be set.
type FlagGroupKind ¶
type FlagGroupKind int
FlagGroupKind describes the type of constraint a flag group enforces.
const ( // GroupMutuallyExclusive means at most one flag in the group may be set. GroupMutuallyExclusive FlagGroupKind = iota // GroupRequiredTogether means if any flag in the group is set, all must be set. GroupRequiredTogether // GroupOneRequired means exactly one flag in the group must be set. GroupOneRequired )
type FlagGrouper ¶
type FlagGrouper interface {
FlagGroups() []FlagGroup
}
FlagGrouper declares flag group constraints on a command. Validation runs after flag parsing and inheritance.
type FlagParser ¶
type FlagParser interface {
ParseFlags(cmd Commander, args []string) (remaining []string, err error)
}
FlagParser replaces the flag parsing engine. Checked on the command first, then falls back to the global parser set via WithFlagParser, then to the default struct-tag parser.
type FlagUnmarshaler ¶
FlagUnmarshaler allows custom types to be used as flag values.
type HelpAppender ¶
type HelpAppender interface {
AppendHelp() []HelpSection
}
HelpAppender declares sections appended after the main help content (after Arguments, before Global Flags). Sections are rendered in order.
func (j *JiraCmd) AppendHelp() []cli.HelpSection {
return []cli.HelpSection{{
Header: "Required Tokens",
Body: " JIRA_TOKEN Jira API token (env: JIRA_TOKEN)",
}}
}
type HelpPrepender ¶
type HelpPrepender interface {
PrependHelp() []HelpSection
}
HelpPrepender declares sections prepended before the main help content (before Usage). Sections are rendered in order.
func (j *JiraCmd) PrependHelp() []cli.HelpSection {
return []cli.HelpSection{{
Header: "Notice",
Body: " This command requires VPN access.",
}}
}
type HelpRenderer ¶
type HelpRenderer interface {
RenderHelp(cmd Commander, chain []Commander, flags []FlagDef, args []ArgDef, globalFlags []FlagDef) string
}
HelpRenderer replaces the help rendering engine. Checked on the command first, then falls back to the global renderer set via WithHelpRenderer, then to the default renderer.
Parameters:
- cmd: the leaf command being rendered
- chain: the full command chain from root to leaf
- flags: the leaf command's flags
- args: the leaf command's positional arg definitions
- globalFlags: visible flags from parent commands
type HelpSection ¶
HelpSection is a custom section rendered in the default help output. The Header is rendered as a section title (like "Flags:" or "Commands:"), and Body is rendered as-is beneath it. Use this with HelpAppender or HelpPrepender to add context-specific information (required tokens, environment setup, etc.) without replacing the entire help renderer.
type Helper ¶
type Helper interface {
Help() string
}
Helper overrides help text for a single command.
type LongDescriptor ¶
type LongDescriptor interface {
LongDescription() string
}
LongDescriptor provides extended description text shown in the command's own help output. When implemented, this replaces Descriptor in the help body while Descriptor remains used in subcommand listings.
type Middlewarer ¶
Middlewarer provides middleware that wraps the command's Run function.
type Namer ¶
type Namer interface {
Name() string
}
Namer overrides the command name. The default is the lowercase struct type name.
type Option ¶
type Option func(*options)
Option configures the execution environment.
func Bind ¶
Bind registers a dependency for injection into command structs. The value is matched by its concrete type against struct fields.
db := openDB() cli.Execute(ctx, root, args, cli.Bind(db))
Commands declare the dependency as a struct field (no tag needed):
type ServeCmd struct {
DB *sql.DB
Port int `flag:"port"`
}
func (s *ServeCmd) Run(ctx context.Context) error {
s.DB.Query("SELECT ...") // ready to use
}
Fields with flag:, arg:, or env: tags are not eligible for injection.
func BindProvider ¶
BindProvider registers a lazy dependency that is resolved by calling fn each time bindings are injected. The provider is called once per Execute call. If fn returns an error, execution is aborted.
cli.Execute(ctx, root, args,
cli.BindProvider(func() (*sql.DB, error) {
return sql.Open("postgres", dsn)
}),
)
func BindSingleton ¶
BindSingleton registers a lazy dependency that is resolved at most once. The first call to fn caches the result; subsequent injections reuse the cached value. If fn returns an error, the error is cached and execution is aborted.
cli.Execute(ctx, root, args,
cli.BindSingleton(func() (*sql.DB, error) {
return sql.Open("postgres", dsn)
}),
)
func BindTo ¶
BindTo registers a dependency as a specific interface type. Use this when you want commands to depend on an interface rather than a concrete type.
cli.Execute(ctx, root, args,
cli.BindTo(redisCache, (*Cache)(nil)),
)
Commands declare the interface type:
type ServeCmd struct {
Store Cache
}
func WithCaseInsensitive ¶
WithCaseInsensitive enables case-insensitive subcommand matching. When enabled, "Serve" matches "serve".
func WithConfigResolver ¶
func WithConfigResolver(r ConfigResolver) Option
WithConfigResolver sets a global config resolver for flag values. Config values have lower priority than env vars and explicit CLI flags, but higher priority than defaults: explicit flag > env > config > default > zero.
func WithEnvVarPrefix ¶
WithEnvVarPrefix sets a prefix prepended to all env var names declared via the env struct tag. For example, WithEnvVarPrefix("APP_") causes a flag tagged with `env:"PORT"` to look up the APP_PORT environment variable.
func WithFlagNormalization ¶
WithFlagNormalization sets a function that normalizes flag names before lookup. For example, to treat underscores as dashes:
cli.WithFlagNormalization(func(s string) string {
return strings.ReplaceAll(s, "_", "-")
})
func WithFlagParser ¶
func WithFlagParser(p FlagParser) Option
WithFlagParser sets a global flag parser, overriding the default struct-tag parser.
func WithHelpRenderer ¶
func WithHelpRenderer(r HelpRenderer) Option
WithHelpRenderer sets a global help renderer, overriding the default renderer.
func WithIgnoreUnknown ¶
WithIgnoreUnknown causes unknown flags to be treated as positional args instead of returning an error. Useful for wrapper tools that forward flags to child processes.
func WithInteractive ¶
WithInteractive enables interactive prompting for missing required flags when stdin is a terminal. Commands can implement Prompter to customize the prompting behavior.
func WithPrefixMatching ¶
WithPrefixMatching enables unique prefix matching for subcommand names. When enabled, "ser" matches "serve" if no other subcommand starts with "ser".
func WithShortOptionHandling ¶
WithShortOptionHandling enables POSIX-style short option combining. When enabled, -abc is expanded to -a -b -c (all but last must be bool/counter).
func WithSignalHandling ¶
WithSignalHandling enables automatic signal handling. When enabled, Execute wraps the context with signal.NotifyContext for SIGINT and SIGTERM, causing the context to be canceled when either signal is received.
func WithSortedHelp ¶
WithSortedHelp sorts subcommands and flags alphabetically in help output.
func WithStderr ¶
WithStderr sets the writer used for standard error output.
func WithStdout ¶
WithStdout sets the writer used for standard output (e.g., help text).
func WithSuggest ¶
WithSuggest enables or disables "did you mean?" suggestions for unknown commands and flags. Enabled by default.
type Passthrougher ¶
type Passthrougher interface {
Passthrough() bool
}
Passthrougher enables passthrough mode for a command. When enabled, recognized flags declared via struct tags are parsed normally, but unknown flags are passed through as positional arguments instead of producing errors. This is useful for wrapper commands like "exec" that have their own flags but also forward args to child processes.
type PluginInfo ¶
type PluginInfo struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Aliases []string `json:"aliases,omitempty"`
}
PluginInfo is the optional JSON metadata a plugin can return via its info flag (default "--cli-info"). All fields are optional — a plugin that does not support the flag or returns invalid JSON still works; it simply has no description or aliases in help output.
type Prompter ¶
Prompter customizes how a command prompts for missing required flags in interactive mode. When WithInteractive is enabled and stdin is a terminal, the framework calls Prompt for each missing required flag before validation. Returning an empty string causes the flag to remain unset (validation will catch it). Return an error to abort execution.
type RunFunc ¶
RunFunc adapts a plain function into a Commander.
Example ¶
A minimal command using RunFunc.
package main
import (
"context"
"fmt"
"os"
"github.com/bjaus/cli"
)
func main() {
hello := cli.RunFunc(func(_ context.Context) error {
fmt.Fprintln(os.Stdout, "Hello, world!") //nolint:errcheck // example output
return nil
})
_ = cli.Execute(context.Background(), hello, nil, cli.WithStdout(os.Stdout)) //nolint:errcheck // example
}
Output: Hello, world!
type ShellCompDirective ¶
type ShellCompDirective int
ShellCompDirective controls shell behavior after completing.
const ( // ShellCompDirectiveDefault indicates default completion behavior. ShellCompDirectiveDefault ShellCompDirective = 0 // ShellCompDirectiveNoSpace prevents adding a space after the completion. ShellCompDirectiveNoSpace ShellCompDirective = 1 << iota // ShellCompDirectiveNoFileComp disables file completion when no candidates match. ShellCompDirectiveNoFileComp // ShellCompDirectiveError indicates an error occurred during completion. ShellCompDirectiveError // ShellCompDirectiveFilterFileExt indicates completions are file extensions // to filter by (e.g. ".yaml", ".json"). Shells will only show files matching // these extensions. ShellCompDirectiveFilterFileExt // ShellCompDirectiveFilterDirs indicates only directories should be completed. ShellCompDirectiveFilterDirs )
type Subcommander ¶
type Subcommander interface {
Subcommands() []Commander
}
Subcommander declares subcommands.
type Suggester ¶
Suggester provides a custom suggestion algorithm for a command. Given an unknown name, it returns a suggestion or empty string.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package completion generates shell completion scripts from a cli.Commander command tree.
|
Package completion generates shell completion scripts from a cli.Commander command tree. |
|
Package config provides cli.ConfigResolver implementations for common configuration sources.
|
Package config provides cli.ConfigResolver implementations for common configuration sources. |
|
Package doc generates documentation from a cli.Commander command tree.
|
Package doc generates documentation from a cli.Commander command tree. |