cli

package module
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Jun 21, 2026 License: MIT Imports: 8 Imported by: 0

README

cli

Quality Go Reference GitHub Tag License

Effortless Go CLI apps

demo

github.com/toaweme/cli is a small, generics-based lib for building command-line apps where a command is just a struct. Its flags, positional arguments, environment bindings, defaults, and validation rules are declared once as struct tags, and the module does the parsing, merging, validating, help, and dispatch.

Module

  • cli.NewApp(Config, GlobalFlags) builds an [App]; chain the setters to wire it up.
    • App.Add(name, cmd) registers a command and returns it, so subcommands chain off the result.
    • App.Default(cmd) sets the command that runs on a bare invocation (no args).
    • App.Resolve(...Resolver) registers an ordered config resolver chain (lowest precedence first).
    • App.Help(cmd) registers the help renderer; App.HelpOutputs(...) adds output codecs.
    • App.Run(osArgs) parses, merges, validates, and dispatches.
  • cli.BaseCommand[T] is embedded in your command struct; T is the config struct whose tags define the flags and args. cli.NewBaseCommand[T]() constructs it; the parsed config lands in c.Inputs.
  • cli.Command[T] is the interface every command satisfies (mostly free via BaseCommand): Run, plus help providers (Help, Description, Examples, Args, Flags).
  • cli.IsRealError(err) filters the ErrShowingHelp / ErrShowingVersion clean-exit sentinels from genuine failures.
  • cli.Verbosity is an optional embeddable -v/-vv/-vvv flag group with a Level() query.

Overview

A command is a struct

Define the config (flags and positional args, via tags) and a command that embeds BaseCommand[ThatConfig]:

type GreetConfig struct {
	Name  string `arg:"0" env:"GREET_NAME" help:"Name to greet" rules:"required"`
	Shout bool   `arg:"shout" short:"s" help:"Uppercase the greeting"`
	cli.Verbosity // optional -v/-vv/-vvv switches with Level()/Verbose()/AtLeast()
}

type GreetCommand struct {
	cli.BaseCommand[GreetConfig]
}

func (c *GreetCommand) Run(_ cli.GlobalFlags, _ cli.Unknowns) error {
	msg := fmt.Sprintf("hello, %s!", c.Inputs.Name)
	if c.Inputs.Shout {
		msg = strings.ToUpper(msg)
	}
	fmt.Println(msg)
	return nil
}

func (c *GreetCommand) Help() string { return "Greet someone by name" }

The tags drive everything:

  • arg:"0" is a positional argument (by zero-based index); arg:"shout" is a named flag (--shout).
  • short:"s" adds -s.
  • env:"GREET_NAME" binds an environment variable.
  • default:"..." seeds a value when nothing else sets it.
  • help:"..." is the one-line help text.
  • rules:"required" (and rules:"oneof:a,b,c") validate the merged value.
  • secret:"true" masks the resolved value in --help-values output.
  • sep:"," splits a single string into a scalar slice ([]string, []int, ...).
Merge precedence

Before Run, the module merges values for the matched command in this order of increasing precedence:

struct default  <  resolver chain (files / mapping)  <  environment  <  parsed flags

Flags always win. With no resolvers registered, only defaults, env, and flags apply. Env is folded in by the core, so file config is entirely optional.

Subcommand trees

Add returns the command it registered, so trees chain naturally:

db := app.Add("db", &DBCommand{BaseCommand: cli.NewBaseCommand[struct{}]()})
db.Add("migrate", &MigrateCommand{BaseCommand: cli.NewBaseCommand[struct{}]()})
// runs the leaf:  tool db migrate

A parent that only groups subcommands (a "namespace" like db, with no behavior of its own) shouldn't need a real command. Register help.NewParentPlaceholder() for it: invoking the parent directly prints its child list instead of doing nothing. Under the hood the placeholder's Run returns cli.ErrDisplaySubCommands, which any command can return to get the same listing:

db := help.NewParentPlaceholder()
app.Add("db", db)
db.Add("migrate", &MigrateCommand{BaseCommand: cli.NewBaseCommand[struct{}]()})
db.Add("seed", &SeedCommand{BaseCommand: cli.NewBaseCommand[struct{}]()})
// `tool db` lists migrate and seed; `tool db migrate` runs the leaf
Embedded vs nested config

Following Go's own field-promotion rules (and structs): an anonymous embedded struct has its fields promoted to plain top-level flags (no prefix), while a named (tagged) nested struct groups under a dotted path (database.host). Embed a shared RepoFlags to give several commands the same flags with zero duplication.

Built-in globals

--help/-h, --version/-V, --cwd, and --help-format are parsed before dispatch and passed to every Run as cli.GlobalFlags. The reserved shorts are deliberately minimal (-h, -V) so they never squat on your own DX: -v, -c, and --format stay yours. -h and -V trigger regardless of position. Help and version are handled by the module, which then returns the ErrShowingHelp / ErrShowingVersion sentinels; IsRealError filters them at the call site.

Install

go get github.com/toaweme/cli

Quickstart

package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/toaweme/cli"
)

type GreetConfig struct {
	Name  string `arg:"0" env:"GREET_NAME" help:"Name to greet" rules:"required"`
	Shout bool   `arg:"shout" short:"s" help:"Uppercase the greeting"`
	cli.Verbosity // optional -v/-vv/-vvv switches with Level()/Verbose()/AtLeast()
}

type GreetCommand struct {
	cli.BaseCommand[GreetConfig]
}

func (c *GreetCommand) Run(_ cli.GlobalFlags, _ cli.Unknowns) error {
	msg := fmt.Sprintf("hello, %s!", c.Inputs.Name)
	if c.Inputs.Shout {
		msg = strings.ToUpper(msg)
	}
	fmt.Println(msg)
	if c.Inputs.Verbose() {
		fmt.Printf("verbosity: %d\n", c.Inputs.Level())
	}
	return nil
}

func (c *GreetCommand) Help() string { return "Greet someone by name" }

func main() {
	app := cli.NewApp(
		cli.Config{Name: "greet", Version: "1.0.0"},
		cli.GlobalFlags{},
	)
	app.Add("hello", &GreetCommand{BaseCommand: cli.NewBaseCommand[GreetConfig]()})

	if err := app.Run(os.Args[1:]); cli.IsRealError(err) {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(1)
	}
}
greet hello Ada            # hello, Ada!
greet hello Ada --shout    # HELLO, ADA!
greet hello Ada -vv        # hello, Ada! + "verbosity: 2"
GREET_NAME=Ada greet hello # hello, Ada!  (env binding)
greet hello --help         # generated help for the command
greet --version            # greet 1.0.0

Features

  • Command-as-struct - declare flags, positional args, env bindings, defaults, validation, and help once as struct tags; embed BaseCommand[T] and implement Run. The parsed config is c.Inputs.
  • Layered config merge - default < resolver chain < env < flags, in that order, with flags always winning. Env is folded by the core, so file config stays optional.
  • Decoupled resolvers - the only config seam in core is the Resolver interface; resolvers compose like middleware. The core never imports the file-config package.
  • Subcommand trees - Add chaining and parent placeholders; a default command for bare invocation.
  • Type coercion and slice splitting - loosely typed inputs (an env string "9090") land in the field's real type; a single string splits into a scalar slice via sep.
  • Embedded and nested config - embedded structs promote to top-level flags (no prefix); named nested structs group under a dotted path.
  • Minimal, non-squatting globals - only -h and -V are reserved; --cwd is long-only and help formatting is --help-format, leaving -v/-c/--format for you.
  • Optional verbosity - embed cli.Verbosity for -v/-vv/-vvv with Level()/Verbose()/AtLeast(); the module imposes no verbosity of its own.
  • Clean-exit sentinels - ErrShowingHelp / ErrShowingVersion plus the IsRealError helper so the call site filters them in one call.
  • Rich, multi-format help - one-line Help() plus Description, Examples, Args, Flags providers; output as plain, pretty, md, json, or jsonschema, with pluggable OutputCodecs.
  • Resolved-value help - --help-values annotates each flag with its merged value (defaults < config < env < flags), with secrets prefix-masked so they never leak into pasted help.
  • Shell completion - bash/zsh/fish scripts and the __complete hook via commands/completion.
  • Docs generation - commands/gendocs renders the app's own command tree to files in every help format, using the same in-process renderers as --help-format, so docs never go stale.
  • .env loading - LoadDotEnv() sets unset env vars; GetDotEnv()/GetDotEnvs() parse into a map without touching the environment.

Sub-packages (opt-in)

The core is dependency-light. Pull these in only when you need them:

  • commands/help - help.NewHelpCommand(...) (register with app.Help(...)) and help.NewParentPlaceholder() for grouping subcommands.
  • commands/completion - completion.NewCompletionCommand(appName) for shell completion scripts.
  • commands/gendocs - gendocs.NewGenDocsCommand(...) to generate reference docs.
  • config - file-backed configuration:
    • config.NewFileStore(dir, name, ensureConfigDir, codec...) - one config file with whole-file (Read/Write/Exists/Delete) and dotted-key (KeyRead/KeyWrite/...) access. Reads create nothing and report absence explicitly (ErrConfigNotFound / ErrKeyNotFound).
    • config.FileSecrets(dir, codec...) - the same store at 0600, named secrets.
    • config.NewResolver(store, rules) - one resolver per store, satisfying cli.Resolver structurally; layer several via app.Resolve(global, project, secrets). Optional per-command field mapping rules.
    • config.Discover(...) / config.HomePath(appName) helpers; ~ home expansion.
    • Codecs are addons: config/addons/json, config/addons/yaml, config/addons/toml (each New(exts...)); JSON is the default and YAML/TOML are separate modules carrying their own third-party deps. The CLI works with none registered.

A fully wired app using all of the above:

app := cli.NewApp(
	cli.Config{Name: "full", Version: "0.1.0"},
	cli.GlobalFlags{},
).Resolve(
	config.NewResolver(config.NewFileStore(config.HomePath("full"), "config", true), nil),
	config.NewResolver(config.NewFileStore(cwd, "config", true), nil),
	config.NewResolver(config.FileSecrets(config.HomePath("full")), nil),
)

app.Help(help.NewHelpCommand(app.Config, app.Commands, app.OutputFormats, app.DefaultCommand))
app.Add("completion", completion.NewCompletionCommand("full"))
app.Add("gendocs", gendocs.NewGenDocsCommand(app.Config, app.Commands, app.OutputFormats))

Runnable examples

See example_test.go for short, runnable versions of everything above, and examples/ for complete programs (basic, greet, server, deploy, full, full_3rd_party).

go test -run Example -v

Documentation

Overview

Package cli is a small, generics-based framework for building command-line applications whose flags, positional arguments, environment bindings, and validation rules are declared once as struct tags.

A command is a struct that embeds BaseCommand (parameterized by its config type) and implements Run. The config type's fields, tagged with arg/short/env/default/help/rules, define everything the framework needs to parse, validate, and document the command:

type GreetConfig struct {
	Name  string `arg:"0" env:"GREET_NAME" help:"Name to greet" rules:"required"`
	Shout bool   `arg:"shout" short:"s" help:"Uppercase the greeting"`
}

type GreetCommand struct {
	cli.BaseCommand[GreetConfig]
}

func (c *GreetCommand) Run(_ cli.GlobalFlags, _ cli.Unknowns) error {
	fmt.Printf("hello, %s!\n", c.Inputs.Name)
	return nil
}

Build an App with NewApp, register commands with App.Add, App.Default, and App.Help, then dispatch os.Args with App.Run. The framework merges values from struct defaults, the resolver chain (see Resolver), environment variables, and parsed flags, in that order of increasing precedence, before calling Run.

Help (-h/--help) and version (-V/--version) are built in. Run returns the ErrShowingHelp / ErrShowingVersion sentinels once it has handled those requests itself; use IsRealError to filter them at the call site.

File-backed configuration, output codecs, the help command, and the docs generator live in sub-packages so the core stays dependency-light. See the runnable programs under examples/ for complete applications.

Example

Example builds a one-command app and dispatches a positional argument to it.

package main

import (
	"fmt"
	"strings"

	"github.com/toaweme/cli"
)

// helloConfig declares the hello command's flags and positional args via struct tags.
type helloConfig struct {
	Name  string `arg:"0" env:"HELLO_NAME" help:"Who to greet"`
	Shout bool   `arg:"shout" short:"s" help:"Uppercase the greeting"`
}

// helloCommand greets someone by name, optionally shouting.
type helloCommand struct {
	cli.BaseCommand[helloConfig]
}

func (c *helloCommand) Run(_ cli.GlobalFlags, _ cli.Unknowns) error {
	name := c.Inputs.Name
	if name == "" {
		name = "world"
	}
	msg := fmt.Sprintf("hello, %s!", name)
	if c.Inputs.Shout {
		msg = strings.ToUpper(msg)
	}
	fmt.Println(msg)
	return nil
}

func (c *helloCommand) Help() string { return "Greet someone by name" }

func main() {
	app := cli.NewApp(
		cli.Config{Name: "greet", Version: "1.0.0"},
		cli.GlobalFlags{},
	)
	app.Add("hello", &helloCommand{BaseCommand: cli.NewBaseCommand[helloConfig]()})

	if err := app.Run([]string{"hello", "Ada"}); cli.IsRealError(err) {
		fmt.Println("error:", err)
	}
}
Output:
hello, Ada!

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrCommandNotFound = errors.New("command not found")

ErrCommandNotFound is returned when no registered command matches the args.

View Source
var ErrDisplaySubCommands = errors.New("print sub commands")

ErrDisplaySubCommands signals that sub commands should be printed.

View Source
var ErrDotenvNotFound = errors.New("dotenv file not found")

ErrDotenvNotFound is returned when a requested .env file does not exist.

View Source
var ErrNoCommands = errors.New("no commands registered")

ErrNoCommands is returned when the app has no commands registered.

View Source
var ErrShowingHelp = errors.New("showing help")

ErrShowingHelp signals that help output was shown instead of running a command.

View Source
var ErrShowingVersion = errors.New("showing version")

ErrShowingVersion signals that the version was shown instead of running a command.

View Source
var ErrValidationFailed = errors.New("validation failed")

ErrValidationFailed is returned by Validate when one or more struct rules fail.

Functions

func FormatAliases

func FormatAliases(codec OutputCodec) []string

FormatAliases returns every --help-format name a codec answers to: each extension it reports (Extensions() when implemented, otherwise its Extension()), with the leading dot trimmed and empties dropped. The first is the primary, used for the help hint and for writing; the rest are accepted aliases.

func GetDotEnv

func GetDotEnv(file string) (map[string]string, error)

GetDotEnv parses a single .env file and returns its key/value pairs without touching the process environment. Returns ErrDotenvNotFound if the file does not exist.

func GetDotEnvs

func GetDotEnvs(files ...string) (map[string]string, error)

GetDotEnvs parses several .env files and merges them into a single map without touching the process environment. Earlier files take precedence: a key set by an earlier file is not overwritten by a later one. Returns the first error encountered, including ErrDotenvNotFound for a missing file.

func IsRealError

func IsRealError(err error) bool

IsRealError reports whether err is a genuine failure worth surfacing, as opposed to a clean-exit sentinel the framework returns once it has already handled the request itself (printing help or the version). It returns false for nil and for the ErrShowingHelp / ErrShowingVersion sentinels, and true for anything else, so a caller need not enumerate the sentinels by hand:

if err := app.Run(os.Args[1:]); cli.IsRealError(err) {
	fmt.Fprintf(os.Stderr, "error: %v\n", err)
	os.Exit(1)
}
Example

ExampleIsRealError filters the clean-exit sentinels from genuine failures.

package main

import (
	"errors"
	"fmt"

	"github.com/toaweme/cli"
)

func main() {
	fmt.Println(cli.IsRealError(nil))
	fmt.Println(cli.IsRealError(cli.ErrShowingHelp))
	fmt.Println(cli.IsRealError(cli.ErrShowingVersion))
	fmt.Println(cli.IsRealError(errors.New("boom")))
}
Output:
false
false
false
true

func LoadDotEnv

func LoadDotEnv(paths ...string) error

LoadDotEnv reads .env files and sets environment variables that are not already set. With no arguments, it loads ".env" from the current working directory. Silently skips files that do not exist.

Types

type App

type App interface {
	// Commands returns the registered top-level commands.
	Commands() []Command[any]
	// DefaultCommand returns the command registered via Default, or nil when none is set.
	DefaultCommand() Command[any]
	// Config returns the app identity (the serializable DTO).
	Config() Config
	// OutputFormats returns the registered help output codecs, in registration order.
	OutputFormats() []OutputCodec
	// Resolve appends config Resolvers to the chain used to populate each command's Options() before Run,
	// and returns the app for chaining. Resolvers run in the order registered (across all Resolve calls),
	// lowest precedence first, then env, then flags. With none registered, only env and flags apply.
	Resolve(resolvers ...Resolver) App
	// HelpOutputs registers additional help output codecs (e.g. the yaml/toml addons) and returns the app for chaining.
	// Each codec's name, derived from its Extension (".yml" -> "yml"), becomes a valid --help-format value
	// and is advertised in help.
	HelpOutputs(formats ...OutputCodec) App
	// Default sets the command run when no arguments are given; it returns cmd.
	Default(cmd Command[any]) Command[any]
	// Add registers cmd under name and returns it, so subcommands chain off the result.
	Add(name string, cmd Command[any]) Command[any]
	// Run parses osArgs and dispatches to the matched command.
	// Help and version requests surface as the ErrShowingHelp/ErrShowingVersion sentinels.
	Run(osArgs []string) error
	// Help registers cmd as the command that renders help, so callers never have to know the reserved name.
	// Use it instead of Add: app.Help(help.NewHelpCommand(...)).
	Help(cmd Command[any]) Command[any]
}

App is the top-level CLI application. It owns the command set, global flags, and an ordered chain of config Resolvers, and dispatches osArgs to the matched command.

func NewApp

func NewApp(config Config, opts GlobalFlags) App

NewApp creates an application from config (the serializable identity and merge strategy) and the default values for global flags. Attach config Resolvers and output Formats with the chainable setters (Resolve and HelpOutputs), then register commands with Add, Default, and Help, and dispatch with Run.

Example

ExampleNewApp shows the minimal app: identity, one command, dispatch.

package main

import (
	"fmt"
	"strings"

	"github.com/toaweme/cli"
)

// helloConfig declares the hello command's flags and positional args via struct tags.
type helloConfig struct {
	Name  string `arg:"0" env:"HELLO_NAME" help:"Who to greet"`
	Shout bool   `arg:"shout" short:"s" help:"Uppercase the greeting"`
}

// helloCommand greets someone by name, optionally shouting.
type helloCommand struct {
	cli.BaseCommand[helloConfig]
}

func (c *helloCommand) Run(_ cli.GlobalFlags, _ cli.Unknowns) error {
	name := c.Inputs.Name
	if name == "" {
		name = "world"
	}
	msg := fmt.Sprintf("hello, %s!", name)
	if c.Inputs.Shout {
		msg = strings.ToUpper(msg)
	}
	fmt.Println(msg)
	return nil
}

func (c *helloCommand) Help() string { return "Greet someone by name" }

func main() {
	app := cli.NewApp(
		cli.Config{Name: "greet", Version: "1.0.0"},
		cli.GlobalFlags{},
	)
	app.Add("hello", &helloCommand{BaseCommand: cli.NewBaseCommand[helloConfig]()})

	_ = app.Run([]string{"hello", "Ada", "--shout"})
}
Output:
HELLO, ADA!

type BaseCommand

type BaseCommand[T any] struct {
	Inputs *T
	// contains filtered or unexported fields
}

BaseCommand provides default implementations for the Command interface. Embed it in your command struct to get name management, subcommand registration, config struct handling, validation, and no-op help providers for free. Override Description/Examples/Args/Flags to enrich help output.

func NewBaseCommand

func NewBaseCommand[T any]() BaseCommand[T]

NewBaseCommand returns a BaseCommand with an initialized subcommand slice.

func (*BaseCommand[T]) Add

func (c *BaseCommand[T]) Add(name string, cmd Command[any])

Add registers cmd as a subcommand under the given name.

func (*BaseCommand[T]) Args

func (c *BaseCommand[T]) Args() map[int][]string

Args returns no positional-argument descriptions by default. Override to provide them.

func (*BaseCommand[T]) Commands

func (c *BaseCommand[T]) Commands() []Command[any]

Commands returns the registered subcommands.

func (*BaseCommand[T]) Description

func (c *BaseCommand[T]) Description() string

Description returns no long-form description by default. Override to provide one.

func (*BaseCommand[T]) Examples

func (c *BaseCommand[T]) Examples() [][]string

Examples returns no usage examples by default. Override to provide them.

func (*BaseCommand[T]) Flags

func (c *BaseCommand[T]) Flags() map[string][]string

Flags returns no flag descriptions by default. Override to provide them.

func (*BaseCommand[T]) Name

func (c *BaseCommand[T]) Name(name string) string

Name returns the command's name when called with an empty string, or sets and returns it otherwise.

func (*BaseCommand[T]) Options

func (c *BaseCommand[T]) Options() any

Options returns the pointer the parser fills (and the merge populates). It allocates a fresh T only when Inputs is unset, so an app can make a command operate on a slice of a larger config struct by assigning Inputs before Run:

cmd.Inputs = &appCfg.Server // flags, env, and merge now write into appCfg

That is the top-down "single source of truth" pattern: one app config struct, with each command viewing the field it owns. Leaving Inputs nil keeps the command's config independent and portable, which is the default.

func (*BaseCommand[T]) Validate

func (c *BaseCommand[T]) Validate(options map[string]any) error

Validate checks the parsed options against the struct rules on the command's config type, returning ErrValidationFailed when any rule fails.

type Command

type Command[T any] interface {
	// Name gets or sets the command name. Pass "" to get, non-empty to set.
	Name(name string) string
	// Add registers a subcommand under this command.
	Add(name string, cmd Command[any])
	// Options returns a pointer to the config struct for flag parsing.
	Options() any
	// Commands returns the list of registered subcommands.
	Commands() []Command[any]
	// Run executes the command logic with parsed global options and unknown args.
	Run(options GlobalFlags, unknowns Unknowns) error
	// Validate checks the parsed options map against struct validation rules.
	Validate(options map[string]any) error
	// Help returns a short one-line description shown in command listings.
	Help() string
	// Description returns a longer, multi-line description shown in detailed and agent help.
	// Help stays the one-line listing summary; Description carries the richer body
	// (paragraphs, install instructions, ...). Empty by default.
	Description() string
	// Examples returns usage examples shown in detailed and agent help.
	// Each example is a slice of lines: the first is the invocation,
	// any following lines are sample output shown beneath it. Nil by default.
	Examples() [][]string
	// Args returns multi-line descriptions for positional arguments, keyed by zero-based position.
	// Augments the single-line `help:` tag. Nil by default.
	Args() map[int][]string
	// Flags returns multi-line descriptions for flags, keyed by the flag as written (e.g. "--query, -q").
	// Augments the single-line `help:` tag. Nil by default.
	Flags() map[string][]string
}

Command is the interface every CLI command must implement. T is the config struct type whose fields define the command's flags and positional args.

type Config

type Config struct {
	// Name is the application binary name, shown in help and usage output.
	Name string `json:"name" yaml:"name"`
	// Version is the semantic version string printed by the built-in --version / -V flag.
	Version string `json:"version" yaml:"version"`
}

Config is the serializable application identity: plain values only, so it stays a light DTO that round-trips through json/yaml. Config resolution is attached to the App separately via the App.Resolve setter; output codecs via App.HelpOutputs.

type GlobalFlags

type GlobalFlags struct {
	// Cwd overrides the working directory for the command.
	// Long-only: "cwd" is too niche to justify squatting on -c, the most common short for a "config" flag.
	Cwd string `arg:"cwd" env:"CWD" help:"Current working directory"`
	// Help triggers help display instead of running the matched command.
	// -h/--help is the one short the whole ecosystem reserves, so it keeps its short.
	Help bool `arg:"help" short:"h" env:"HELP" help:"Show help"`
	// HelpValues, with --help, annotates each flag with its resolved value
	// (the merge of defaults, config, env, and flags for the invoked command).
	// Values are prefix-masked so secrets sourced from env or a .env file are not
	// exposed in full in help that gets pasted into logs, issues, or screenshots.
	// Off by default; passing --help-values implies --help.
	HelpValues bool `arg:"help-values" help:"With --help, show each flag's resolved value (prefix-masked)"`
	// HelpFormat controls help output. It is --help-format, not --format:
	// the bare name is the one apps most often want for their own command output (json/yaml/table/csv),
	// and squatting on it also meant the framework rejected any unrecognized value app-wide before the command ran.
	// The allowed values come from the oneof rule, which also drives the "(one of: ...)" hint shown in help.
	HelpFormat string `arg:"help-format" help:"Help output format" rules:"oneof:plain,plain-flags,pretty,md,json,jsonschema"`
	// Version prints the application version and exits.
	// Short is capital -V (clap-style) so lowercase -v stays free for the author's own "verbose" flag,
	// which is what users overwhelmingly expect -v to mean.
	Version bool `arg:"version" short:"V" env:"VERSION" help:"Show version"`
}

GlobalFlags are built-in flags available to every command. These are parsed before command dispatch and passed to every command's Run method.

type OutputCodec

type OutputCodec interface {
	// Marshal encodes v into bytes.
	Marshal(v any) ([]byte, error)
	// Extension returns the file extension for this codec (e.g. ".yml").
	Extension() string
}

OutputCodec renders help output for a custom --help-format value. It is satisfied structurally by the yaml/toml/json config addon codecs (which also implement config.Codec), so registering one never pulls an encoding library into the core. The format name a user types is derived from Extension by trimming the leading dot (".yml" -> "yml").

type Resolver

type Resolver interface {
	// Resolve overlays this resolver's layer onto values, the map accumulated by
	// earlier resolvers in the chain, and returns it. cmd is the command path
	// (space-joined, e.g. "db migrate"). The first resolver receives an empty map.
	Resolve(cmd string, values map[string]any) (map[string]any, error)
}

Resolver contributes values to a command's Options() before Run. Resolvers compose like middleware: the framework registers any number on the App, then runs them in order, threading each one's output into the next. After the chain it folds in env, then overlays parsed flags, so a typed flag always wins. File-backed resolution lives in the config package (config.NewResolver, one per Store), which satisfies this interface structurally so core never imports config.

type Unknowns

type Unknowns struct {
	// Args are positional arguments not matched to numbered struct tags.
	Args []string
	// Options are key-value flags not defined in the command's config struct.
	Options map[string]any
}

Unknowns holds arguments and options that were not matched to any defined field. Commands receive these to support pass-through or dynamic flag handling.

type Verbosity

type Verbosity struct {
	V1 bool `short:"v" help:"Verbose output (-v)"`
	V2 bool `short:"vv" help:"More verbose output (-vv)"`
	V3 bool `short:"vvv" help:"Most verbose output (-vvv)"`
}

Verbosity is an optional embeddable flag group giving an author the conventional -v/-vv/-vvv switches without the framework imposing any verbosity policy of its own. Embed it in a command's input struct to get the flags and the level query for free:

type MyInputs struct {
	cli.Verbosity
	Name string `arg:"name"`
}

The three flags are independent switches matched as their own short tokens (-v, -vv, -vvv), not a true repeat-counter. Use Level to read the highest one the user passed rather than touching the bool fields directly.

func (Verbosity) AtLeast

func (v Verbosity) AtLeast(level int) bool

AtLeast reports whether the selected verbosity is at or above level.

func (Verbosity) Level

func (v Verbosity) Level() int

Level returns the verbosity the user selected: 0 when no flag was passed, up to 3 for -vvv. The highest flag wins, so -vvv reports 3 even if -v is also set.

func (Verbosity) Verbose

func (v Verbosity) Verbose() bool

Verbose reports whether any verbosity flag was passed (Level > 0).

Directories

Path Synopsis
commands
completion
Package completion provides a command that generates shell completion scripts.
Package completion provides a command that generates shell completion scripts.
gendocs
Package gendocs provides a command that renders the application's command tree to documentation files.
Package gendocs provides a command that renders the application's command tree to documentation files.
help
Package help provides a command that displays usage information for the application.
Package help provides a command that displays usage information for the application.
addons/json
Package json provides a config codec that serializes values as JSON.
Package json provides a config codec that serializes values as JSON.
addons/yaml module
examples
basic command
basic demonstrates the minimal setup for a CLI app: creating an app, registering built-in commands, and adding a custom command.
basic demonstrates the minimal setup for a CLI app: creating an app, registering built-in commands, and adding a custom command.
deploy command
deploy demonstrates parent commands with subcommands.
deploy demonstrates parent commands with subcommands.
full command
full demonstrates every framework feature: dotenv loading, default commands, shell completion, config store, subcommands, ExampleProvider, and all help formats.
full demonstrates every framework feature: dotenv loading, default commands, shell completion, config store, subcommands, ExampleProvider, and all help formats.
greet command
greet demonstrates positional args, named flags, short flags, environment variable binding, and validation rules.
greet demonstrates positional args, named flags, short flags, environment variable binding, and validation rules.
server command
server demonstrates a CLI app that starts an HTTP server with graceful shutdown, dotenv loading, config persistence, and signal handling.
server demonstrates a CLI app that starts an HTTP server with graceful shutdown, dotenv loading, config persistence, and signal handling.
Package help renders command usage information in several formats: plain text, agent-oriented markdown, JSON, and JSON schema.
Package help renders command usage information in several formats: plain text, agent-oriented markdown, JSON, and JSON schema.
gendocs
Package gendocs renders an application's command tree to documentation files on disk, in every help format the app supports.
Package gendocs renders an application's command tree to documentation files on disk, in every help format the app supports.

Jump to

Keyboard shortcuts

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