Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions command_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,17 @@ func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context
}
}

// Handle interactive mode if enabled
for _, c := range cmdChain {
if c.shouldRunInteractive() {
tracef("running interactive mode (cmd=%[1]q)", c.Name)
if err := c.handleInteractiveMode(ctx); err != nil {
deferErr = c.handleExitCoder(ctx, err)
return ctx, deferErr
}
}
}

if err := cmd.checkAllRequiredFlags(); err != nil {
cmd.isInError = true
if cmd.OnUsageError != nil {
Expand Down
136 changes: 136 additions & 0 deletions docs/v3/examples/flags/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -532,3 +532,139 @@ Will result in help output like:
```
Flag port value 70000 out of range[0-65535]
```

#### Interactive Mode

`urfave/cli` supports an interactive mode that allows users to input missing parameters
through a series of prompts. This is particularly useful for creating user-friendly
CLI tools that guide users through the configuration process.

##### Enabling Interactive Mode

To enable interactive mode, add the `--interactive` (or `-i`) flag to your command:

```go
package main

import (
"context"
"fmt"
"log"
"os"

"github.com/urfave/cli/v3"
)

func main() {
cmd := &cli.Command{
Name: "user-config",
Usage: "Configure user settings interactively",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "interactive",
Aliases: []string{"i"},
Usage: "Enable interactive mode",
},
&cli.InteractiveStringFlag{
StringFlag: cli.StringFlag{
Name: "name",
Value: "Guest",
Usage: "Your name",
},
Prompt: "Enter your name",
Required: true,
},
&cli.InteractiveIntFlag{
Int64Flag: cli.Int64Flag{
Name: "age",
Value: 18,
Usage: "Your age",
},
Prompt: "Enter your age",
Required: true,
},
&cli.InteractiveBoolFlag{
BoolFlag: cli.BoolFlag{
Name: "subscribe",
Value: false,
Usage: "Subscribe to newsletter",
},
Prompt: "Do you want to subscribe to our newsletter",
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
fmt.Printf("Name: %s\n", cmd.String("name"))
fmt.Printf("Age: %d\n", cmd.Int("age"))
fmt.Printf("Subscribed: %v\n", cmd.Bool("subscribe"))
return nil
},
}

if err := cmd.Run(context.Background(), os.Args); err != nil {
log.Fatal(err)
}
}
```

##### Using Interactive Mode

When you run the command with the `--interactive` or `-i` flag:

```sh-session
$ user-config --interactive
Enter your name [Guest]: John
Enter your age [18]: 25
Do you want to subscribe to our newsletter [y/N]: y

Name: John
Age: 25
Subscribed: true
```

If you press Enter without typing anything, the default value will be used.

##### Interactive Flag Types

The following interactive flag types are available:

- `InteractiveStringFlag` - For string inputs
- `InteractiveIntFlag` - For integer inputs
- `InteractiveBoolFlag` - For yes/no confirmations
- `InteractiveFloatFlag` - For float inputs
- `InteractiveDurationFlag` - For duration inputs (e.g., 1s, 2m, 3h)

Each interactive flag has these additional fields:

- `Prompt` - The text to display when prompting the user
- `Required` - Whether the user must provide a value (cannot use default)

##### Required Fields

When `Required` is set to `true`, the user cannot skip the prompt by pressing Enter:

```go
&cli.InteractiveStringFlag{
StringFlag: cli.StringFlag{
Name: "email",
Usage: "Your email address",
},
Prompt: "Enter your email",
Required: true,
}
```

##### Custom Prompter

You can also use the `InteractivePrompter` interface to create custom prompt behavior:

```go
type InteractivePrompter interface {
Prompt(prompt string, defaultValue string) (string, error)
PromptRequired(prompt string) (string, error)
PromptConfirm(prompt string, defaultValue bool) (bool, error)
PromptSelect(prompt string, options []string) (int, string, error)
}
```

The `DefaultPrompter` uses standard input/output, but you can implement your own
prompter for testing or custom UI scenarios.
128 changes: 128 additions & 0 deletions examples/example-interactive/example-interactive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package main

import (
"context"
"fmt"
"log"
"os"

"github.com/urfave/cli/v3"
)

func main() {
cmd := &cli.Command{
Name: "interactive-demo",
Usage: "A demonstration of interactive mode in urfave/cli",
Description: `This example demonstrates how to use the interactive mode
to prompt users for missing parameters. Use --interactive or -i flag
to enable interactive prompting.`,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "interactive",
Aliases: []string{"i"},
Usage: "Enable interactive mode for missing parameters",
},
&cli.InteractiveStringFlag{
StringFlag: cli.StringFlag{
Name: "name",
Value: "Guest",
Usage: "Your name",
},
Prompt: "Enter your name",
Required: true,
},
&cli.InteractiveIntFlag{
Int64Flag: cli.Int64Flag{
Name: "age",
Value: 18,
Usage: "Your age",
},
Prompt: "Enter your age",
Required: true,
},
&cli.InteractiveStringFlag{
StringFlag: cli.StringFlag{
Name: "email",
Usage: "Your email address",
},
Prompt: "Enter your email (optional)",
Required: false,
},
&cli.InteractiveBoolFlag{
BoolFlag: cli.BoolFlag{
Name: "subscribe",
Value: false,
Usage: "Subscribe to newsletter",
},
Prompt: "Do you want to subscribe to our newsletter",
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
fmt.Println("\n=== User Profile ===")
fmt.Printf("Name: %s\n", cmd.String("name"))
fmt.Printf("Age: %d\n", cmd.Int("age"))
if email := cmd.String("email"); email != "" {
fmt.Printf("Email: %s\n", email)
} else {
fmt.Println("Email: Not provided")
}
fmt.Printf("Subscribed: %v\n", cmd.Bool("subscribe"))

if cmd.Bool("subscribe") {
fmt.Println("\nThank you for subscribing!")
}

return nil
},
Commands: []*cli.Command{
{
Name: "create",
Usage: "Create a new project interactively",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "interactive",
Aliases: []string{"i"},
Usage: "Enable interactive mode",
},
&cli.InteractiveStringFlag{
StringFlag: cli.StringFlag{
Name: "project-name",
Usage: "Name of the project",
},
Prompt: "Enter project name",
Required: true,
},
&cli.InteractiveStringFlag{
StringFlag: cli.StringFlag{
Name: "description",
Value: "A new project",
Usage: "Project description",
},
Prompt: "Enter project description",
Required: false,
},
&cli.InteractiveStringFlag{
StringFlag: cli.StringFlag{
Name: "language",
Value: "go",
Usage: "Programming language",
},
Prompt: "Enter programming language",
Required: false,
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
fmt.Println("\n=== Project Created ===")
fmt.Printf("Project Name: %s\n", cmd.String("project-name"))
fmt.Printf("Description: %s\n", cmd.String("description"))
fmt.Printf("Language: %s\n", cmd.String("language"))
return nil
},
},
},
}

if err := cmd.Run(context.Background(), os.Args); err != nil {
log.Fatal(err)
}
}
Loading
Loading