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
go get github.com/pressly/cli@latestRequires Go 1.21 or higher.
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
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.
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 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.
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>..."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.
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.
This project is licensed under the MIT License - see the LICENSE file for details.