Skip to content
Merged
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
internal/
tmp/
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Added

- `flagtype.EnumDefault` constructor for enums with an initial default value
- `Cmd` field on `State` for accessing the terminal command selected by parsing
- `Summary` field on `Command` for the short text shown in command lists
- `UsageErrorf` for opt-in usage errors; `Run` prints command help to stderr before returning the
underlying error

### Changed

- **BREAKING**: Replace `Command.UsageFunc` with `Command.Help`, which returns the full help string
for a command
- **BREAKING**: Replace `Command.ShortHelp` with `Command.Summary` for command lists and
`Command.Description` for longer command help text
- **BREAKING**: Rename `FlagOption` to `FlagConfig` and `Command.FlagOptions` to
`Command.FlagConfigs`
- Commands with subcommands and no `Exec` now report a usage error when no child command is
selected, before enforcing required flags inherited by child commands
- Help output keeps the default automatic `--help` behavior through `ParseAndRun`; `Command.Help`
replaces the generated help string when a command needs full control

### Removed

- **BREAKING**: Remove `ErrHelp`; check `errors.Is(err, flag.ErrHelp)` when handling `Parse`
directly
- **BREAKING**: Remove `DefaultUsage` and the top-level `Usage` function from the public API

## [v0.6.0] - 2026-02-18

Expand Down
140 changes: 126 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,21 @@ Requires Go 1.21 or higher.

```go
root := &cli.Command{
Name: "greet",
ShortHelp: "Print a greeting",
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 {
fmt.Fprintln(s.Stdout, "hello, world!")
text := strings.Join(s.Args, " ")
if cli.GetFlag[bool](s, "capitalize") {
text = strings.ToUpper(text)
}
fmt.Fprintln(s.Stdout, text)
return nil
},
}
Expand All @@ -39,17 +50,29 @@ if err := cli.ParseAndRun(ctx, root, os.Args[1:], nil); err != nil {
resolved command. For applications that need work between parsing and execution, use `Parse` and
`Run` separately. See the [examples](examples/) directory for more complete applications.

The command above gets usable help without any extra setup:

```text
Print text

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

Flags:
-c, --capitalize capitalize the input
```

## Flags

`FlagsFunc` is a convenience for defining flags inline. Use `FlagOptions` to extend the standard
`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:

```go
Flags: cli.FlagsFunc(func(f *flag.FlagSet) {
f.Bool("verbose", false, "enable verbose output")
f.String("output", "", "output file")
}),
FlagOptions: []cli.FlagOption{
FlagConfigs: []cli.FlagConfig{
{Name: "verbose", Short: "v"},
{Name: "output", Short: "o", Required: true},
},
Expand All @@ -74,13 +97,17 @@ Commands can have nested subcommands, each with their own flags and `Exec` funct

```go
root := &cli.Command{
Name: "todo",
Usage: "todo <command> [flags]",
ShortHelp: "A simple CLI for managing your tasks",
Name: "todo",
Usage: "todo <command> [flags]",
Summary: "Manage tasks",
Description: "todo manages tasks stored in a local file.",
SubCommands: []*cli.Command{
{
Name: "list",
ShortHelp: "List all tasks",
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
Expand All @@ -90,17 +117,102 @@ root := &cli.Command{
}
```

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

```text
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:

```text
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](examples/cmd/task/).

## Help

Help text is generated automatically and displayed when `--help` is passed. To customize it, set the
`UsageFunc` field on a command.
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:

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

Usage:
greet <name>

Examples:
greet margo`
},
```

See [docs/usage-syntax.md](docs/usage-syntax.md) for conventions used in usage strings.
That replaces the built-in help with:

```text
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.

```go
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:

```go
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 `...`:

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

## Status

Expand Down
Loading
Loading