cli

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: May 14, 2026 License: MIT Imports: 16 Imported by: 2

README

cli

GoDoc CI Docs

An intentionally minimal Go package for building CLI applications. Extends the standard library's flag package to support flags anywhere in command arguments, adds nested subcommands, and gets out of the way.

Docs: https://pressly.github.io/cli

Installation

go get github.com/pressly/cli@latest

Requires Go 1.21 or higher.

Quick Start

root := &cli.Command{
	Name:    "echo",
	Usage:   "echo [flags] <text>...",
	Summary: "Print text",
	Flags: cli.FlagsFunc(func(f *flag.FlagSet) {
		f.Bool("capitalize", false, "capitalize the input")
	}),
	FlagConfigs: []cli.FlagConfig{
		{Name: "capitalize", Short: "c"},
	},
	Exec: func(ctx context.Context, s *cli.State) error {
		text := strings.Join(s.Args, " ")
		if cli.GetFlag[bool](s, "capitalize") {
			text = strings.ToUpper(text)
		}
		fmt.Fprintln(s.Stdout, text)
		return nil
	},
}
if err := cli.ParseAndRun(ctx, root, os.Args[1:], nil); err != nil {
	fmt.Fprintf(os.Stderr, "error: %v\n", err)
	os.Exit(1)
}

ParseAndRun parses the command hierarchy, handles --help automatically, and executes the resolved command. For applications that need work between parsing and execution, use Parse and Run separately. See the examples directory for more complete applications.

The command above gets usable help without any extra setup:

Print text

Usage:
  echo [flags] <text>...

Flags:
  -c, --capitalize    capitalize the input

Flags

FlagsFunc is a convenience for defining flags inline. Use FlagConfigs to extend the standard flag package with features like required flag enforcement and short aliases:

Flags: cli.FlagsFunc(func(f *flag.FlagSet) {
	f.Bool("verbose", false, "enable verbose output")
	f.String("output", "", "output file")
}),
FlagConfigs: []cli.FlagConfig{
	{Name: "verbose", Short: "v"},
	{Name: "output", Short: "o", Required: true},
},

Short aliases register -v as an alias for --verbose, -o as an alias for --output, and so on. Both forms are shown in help output automatically.

Access flags inside Exec with the type-safe GetFlag function:

verbose := cli.GetFlag[bool](s, "verbose")
output := cli.GetFlag[string](s, "output")

Child commands automatically inherit flags from parent commands, so a --verbose flag on the root is accessible from any subcommand via GetFlag.

Subcommands

Commands can have nested subcommands, each with their own flags and Exec function:

root := &cli.Command{
	Name:        "todo",
	Usage:       "todo <command> [flags]",
	Summary:     "Manage tasks",
	Description: "todo manages tasks stored in a local file.",
	SubCommands: []*cli.Command{
		{
			Name:    "list",
			Summary: "List tasks",
			Description: `List tasks in the current workspace.

By default, completed tasks are hidden.`,
			Exec: func(ctx context.Context, s *cli.State) error {
				// ...
				return nil
			},
		},
	},
}

Summary is the short sentence shown when a command appears in another command's help:

Available Commands:
  list    List tasks

When a command only groups subcommands, leave Exec unset. Selecting it without a child command returns a usage error and shows that command's help.

Description is the longer text shown at the top of that command's own help:

List tasks in the current workspace.

By default, completed tasks are hidden.

Usage:
  todo list

If a command only needs one sentence, set Summary and leave Description empty. If Description is set and Summary is empty, command lists use the first line of Description.

For a more complete example with deeply nested subcommands, see the todo example.

Help

Help text is generated automatically and displayed when --help is passed. Most commands only need Name, Summary, flags, subcommands, and Exec.

Set the Help field only when a command needs to replace the generated help entirely:

Help: func(c *cli.Command) string {
	return `Print a greeting.

Usage:
  greet <name>

Examples:
  greet margo`
},

That replaces the built-in help with:

Print a greeting.

Usage:
  greet <name>

Examples:
  greet margo

If you use Parse directly, handle flag.ErrHelp yourself. Most applications should use ParseAndRun when they want cli to print help automatically.

if err := cli.Parse(root, args); err != nil {
	if errors.Is(err, flag.ErrHelp) {
		// Print custom help here, or use ParseAndRun for built-in help.
		return nil
	}
	return err
}

Inside Exec, State exposes the resolved command as Cmd, so usage errors can stay explicit:

Exec: func(ctx context.Context, s *cli.State) error {
	if len(s.Args) == 0 {
		return cli.UsageErrorf("must supply a name")
	}
	fmt.Fprintf(s.Stdout, "hello, %s\n", s.Args[0])
	return nil
},

UsageErrorf is opt-in: Run prints the resolved command's help to stderr before returning the underlying error. Normal errors are returned unchanged.

For command-aware errors, use s.Cmd.Path() to get the resolved command path.

Usage Strings

Set Command.Usage when the default usage line is too broad. A common convention is to write required values as <name>, optional values as [name], and repeated values with ...:

Usage: "echo [flags] <text>..."

Status

This project is in active development and undergoing changes as the API gets refined. Please open an issue if you encounter any problems or have suggestions for improvement.

Acknowledgements

There are many great CLI libraries out there, but I always felt they were too heavy for my needs.

Inspired by Peter Bourgon's ff library, specifically the v3 branch, which was so close to what I wanted. The v4 branch took a different direction, and I wanted to keep the simplicity of v3. This library carries that idea forward.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package cli builds command-line programs on top of the standard library flag package. It adds nested subcommands and lets users place flags anywhere in command arguments.

Features:

  • Nested subcommands via [Command.SubCommands]
  • Flags placed anywhere on the command line
  • Parent flags inherited by child commands
  • Type-safe flag access via GetFlag
  • Generated help, replaceable per command via [Command.Help]
  • "Did you mean" suggestions for misspelled subcommands

Quick example:

root := &cli.Command{
    Name:        "echo",
    Usage:       "echo [flags] <text>...",
    Summary:     "Print text",
    Description: "echo prints the provided text.",
    Flags: cli.FlagsFunc(func(f *flag.FlagSet) {
        f.Bool("c", false, "capitalize the input")
    }),
    Exec: func(ctx context.Context, s *cli.State) error {
        output := strings.Join(s.Args, " ")
        if cli.GetFlag[bool](s, "c") {
            output = strings.ToUpper(output)
        }
        fmt.Fprintln(s.Stdout, output)
        return nil
    },
}
if err := cli.ParseAndRun(ctx, root, os.Args[1:], nil); err != nil {
    fmt.Fprintf(os.Stderr, "error: %v\n", err)
    os.Exit(1)
}

The API is small on purpose. cli uses the standard library flag package instead of replacing it, so most of what you write is your program.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FlagsFunc

func FlagsFunc(fn func(f *flag.FlagSet)) (fset *flag.FlagSet)

FlagsFunc creates a flag.FlagSet inline so you don't have to make one and assign it separately. The returned FlagSet uses flag.ContinueOnError, so parsing errors are returned instead of being fatal.

Flags: cli.FlagsFunc(func(f *flag.FlagSet) {
    f.Bool("verbose", false, "enable verbose output")
    f.String("output", "", "output file")
    f.Int("count", 0, "number of items")
}),

func GetFlag

func GetFlag[T any](s *State, name string) T

GetFlag returns the value of a flag as type T. Call it from inside [Command.Exec] with the same Go type that was used when the flag was defined.

GetFlag looks for the flag on the picked command first, then in its parent commands. A flag defined on the root command can be read from any subcommand. An unknown flag name or a wrong type is a programming error: GetFlag panics, and Run catches the panic and returns the error.

verbose := cli.GetFlag[bool](s, "verbose")
count   := cli.GetFlag[int](s, "count")
path    := cli.GetFlag[string](s, "path")

func Parse

func Parse(root *Command, args []string) error

Parse picks the right command and parses its flags from args, but does not run [Command.Exec]. Use Parse with Run when you need to do work between parsing and running. For the common case, call ParseAndRun.

Parse returns flag.ErrHelp when the user passes -h or --help. You have to print the help yourself when this happens. ParseAndRun does it for you.

func ParseAndRun

func ParseAndRun(ctx context.Context, root *Command, args []string, options *RunOptions) error

ParseAndRun parses args, picks the right command, and runs its [Command.Exec]. This is the normal way to start a CLI program:

if err := cli.ParseAndRun(ctx, root, os.Args[1:], nil); err != nil {
    fmt.Fprintf(os.Stderr, "error: %v\n", err)
    os.Exit(1)
}

When the user passes -h or --help, ParseAndRun prints the picked command's help to stdout and returns nil. Use Parse and Run separately when you need to do work between parsing and running, such as setting up resources based on parsed flags.

func Run

func Run(ctx context.Context, root *Command, options *RunOptions) error

Run runs the command picked by a previous call to Parse. Use Run only when you call Parse separately. For the common case, use ParseAndRun.

If [Command.Exec] returns an error created by UsageErrorf, Run prints the command's help to stderr and returns the error you passed to UsageErrorf. Other errors are returned as-is. A nil ctx defaults to context.Background.

func UsageErrorf added in v0.7.0

func UsageErrorf(format string, args ...any) error

UsageErrorf returns an error that means the command was used incorrectly. Return it from [Command.Exec] when the command itself was right but the arguments or flag combination are wrong:

if len(s.Args) == 0 {
    return cli.UsageErrorf("must supply a name")
}

When Run sees a UsageErrorf error, it prints the command's help to stderr and returns the error message you passed in. Return a normal error if you do not want help printed.

Types

type Command

type Command struct {
	// Name is the word users type to pick this command. It must start with a letter and can contain
	// letters, digits, dashes, or underscores. For the root command it is also the program name
	// shown in help.
	Name string

	// Usage replaces the usage line shown at the top of help. Set it to show the expected
	// arguments. The default usage line shows only the command path, plus "[flags]" when the
	// command has flags.
	//
	// A common convention is to write required values as "<name>", optional values as "[name]", and
	// repeated values with "...".
	//
	//	Example: "todo list <view> [flags]"
	//	Example: "todo add <text> [flags]"
	//	Example: "todo remove <id>"
	//	Example: "echo [flags] <text>..."
	//	Example: "serve [flags] [addr]"
	Usage string

	// Summary is the one-line description shown next to this command in its parent's command list.
	// It is also shown at the top of this command's own help when [Command.Description] is empty.
	//
	// Most commands only need Summary. Use [Command.Description] when one line is not enough.
	Summary string

	// Description is the longer help text shown at the top of this command's own help. Use it to
	// explain behavior, defaults, or anything else worth knowing.
	//
	// When [Command.Summary] is empty, the first line of Description is used in command lists
	// instead.
	Description string

	// Help replaces the built-in help text for this command. Leave it nil to use the default help.
	//
	// The function is given the command and returns the full help string. Help is used for --help
	// and for [UsageErrorf] errors. Each command can set its own Help, and only the selected
	// command's Help is called.
	Help func(*Command) string

	// Flags holds this command's flags as a standard library [flag.FlagSet]. Build it with
	// [flag.NewFlagSet], or use [FlagsFunc] to define flags inline.
	//
	// Subcommands inherit these flags unless they are marked [FlagConfig.Local] in
	// [Command.FlagConfigs]. Read flag values inside [Command.Exec] with [GetFlag].
	Flags *flag.FlagSet

	// FlagConfigs adds extra behavior to flags already defined in [Command.Flags]. See [FlagConfig]
	// for the available options.
	//
	// Each entry must point to a flag defined in [Command.Flags]. Otherwise [Parse] returns an
	// error.
	FlagConfigs []FlagConfig

	// SubCommands are the commands users can pick after this command's name.
	//
	// When a command has SubCommands, the first non-flag argument must match one of them. An
	// unknown name returns an "unknown command" error with suggestions. Commands without
	// SubCommands pass any non-flag arguments through to [State.Args]. Leave [Command.Exec] nil on
	// a command that only groups subcommands; selecting it without a child returns a usage error.
	SubCommands []*Command

	// Exec is the function that runs when this command is picked. It is given a [State] holding the
	// parsed inputs the command needs.
	//
	// Return [UsageErrorf] for bad arguments or flag combinations so [Run] prints the command's
	// help to stderr. Return a normal error for everything else; [Run] returns it without printing
	// help.
	Exec func(ctx context.Context, s *State) error
	// contains filtered or unexported fields
}

Command describes a single command in the CLI.

Pass a Command to ParseAndRun (or Parse and Run) to run a program. To add a subcommand, list it in another command's [Command.SubCommands].

func (*Command) Path

func (c *Command) Path() []*Command

Path returns the list of commands from the root down to this command. It is usually called inside [Command.Exec] as s.Cmd.Path() to build error messages that include the full command path.

Path returns nil if called before Parse.

type FlagConfig added in v0.7.0

type FlagConfig struct {
	// Name is the long flag name as registered in the command's [flag.FlagSet].
	Name string

	// Short is a one-letter alias for the flag, such as "v" so users can type -v instead of
	// --verbose. Both forms are shown in help.
	Short string

	// Required, when true, makes [Parse] fail unless the user sets the flag. The default value is
	// not enough; the user must pass it.
	Required bool

	// Local, when true, keeps the flag on this command only and stops it from being inherited by
	// subcommands. Parent flags are inherited by default.
	Local bool
}

FlagConfig adds extra behavior to a single flag already defined in [Command.Flags]. It is used as an entry in [Command.FlagConfigs].

type RunOptions

type RunOptions struct {
	// Stdin, Stdout, and Stderr replace os.Stdin, os.Stdout, and os.Stderr when set. A nil field
	// falls back to its os equivalent.
	Stdin          io.Reader
	Stdout, Stderr io.Writer
}

RunOptions replaces the standard streams used by Run and ParseAndRun. Pass nil for normal programs to use os.Stdin, os.Stdout, and os.Stderr.

Use RunOptions in tests, or anywhere you need to capture output or supply your own input.

type State

type State struct {
	// Args holds the positional arguments left after the command name and flags are parsed.
	// Anything after "--" is included as-is, even if it looks like a flag.
	Args []string

	// Stdin, Stdout, and Stderr are the streams to use in your command code instead of os.Stdin,
	// os.Stdout, and os.Stderr. Tests can swap them via [RunOptions].
	Stdin          io.Reader
	Stdout, Stderr io.Writer

	// Cmd is the command that was picked. Call Cmd.Path() to get the full list of commands from the
	// root down, useful for error messages that include the command path.
	Cmd *Command
	// contains filtered or unexported fields
}

State is the value passed to [Command.Exec]. It holds the parsed inputs the command needs to run.

Directories

Path Synopsis
examples
cmd/echo command
cmd/task command
Package flagtype provides common flag.Value implementations for use with flag.FlagSet.Var.
Package flagtype provides common flag.Value implementations for use with flag.FlagSet.Var.
Package graceful provides utilities for running long-lived processes with predictable, well-behaved shutdown semantics.
Package graceful provides utilities for running long-lived processes with predictable, well-behaved shutdown semantics.
internal
helpdoc
Package helpdoc builds the default command help document.
Package helpdoc builds the default command help document.
usage
Package usage contains experimental building blocks for command help documents.
Package usage contains experimental building blocks for command help documents.
pkg
Package xflag extends the standard library's flag package to support parsing flags interleaved with positional arguments.
Package xflag extends the standard library's flag package to support parsing flags interleaved with positional arguments.

Jump to

Keyboard shortcuts

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