Prompts
Create interactive experiences with prompts, selections, and user input
Prompts
The Forge CLI framework provides rich interactive prompt capabilities that make your CLI applications more user-friendly and engaging. From simple text input to complex multi-select menus with arrow key navigation, prompts help create intuitive command-line experiences.
Basic Prompts
Text Input
Collect text input from users:
func setupHandler(ctx cli.CommandContext) error {
// Simple text prompt
name, err := ctx.Prompt("What's your name?")
if err != nil {
return err
}
// Prompt with default value
email, err := ctx.PromptWithDefault("Email address:", "user@example.com")
if err != nil {
return err
}
// Prompt with validation
username, err := ctx.PromptWithValidator(
"Username (3-20 chars, alphanumeric):",
func(input string) error {
if len(input) < 3 || len(input) > 20 {
return fmt.Errorf("username must be 3-20 characters")
}
if !regexp.MustCompile(`^[a-zA-Z0-9_]+$`).MatchString(input) {
return fmt.Errorf("username can only contain letters, numbers, and underscores")
}
return nil
},
)
if err != nil {
return err
}
ctx.Success(fmt.Sprintf("Welcome, %s (%s)!", name, email))
return nil
}Password Input
Securely collect sensitive information:
func loginHandler(ctx cli.CommandContext) error {
username, err := ctx.Prompt("Username:")
if err != nil {
return err
}
// Password input (hidden)
password, err := ctx.PromptPassword("Password:")
if err != nil {
return err
}
// Password with confirmation
newPassword, err := ctx.PromptPassword("New password:")
if err != nil {
return err
}
confirmPassword, err := ctx.PromptPassword("Confirm password:")
if err != nil {
return err
}
if newPassword != confirmPassword {
return fmt.Errorf("passwords do not match")
}
ctx.Success("Password updated successfully!")
return nil
}Confirmation Prompts
Yes/No Questions
Get boolean confirmation from users:
func deleteHandler(ctx cli.CommandContext) error {
resource := ctx.Args()[0]
// Simple confirmation
confirmed, err := ctx.Confirm(fmt.Sprintf("Delete %s?", resource))
if err != nil {
return err
}
if !confirmed {
ctx.Info("Operation cancelled")
return nil
}
// Confirmation with custom default
forceDelete, err := ctx.ConfirmWithDefault(
"This action cannot be undone. Are you absolutely sure?",
false, // Default to "no"
)
if err != nil {
return err
}
if forceDelete {
ctx.Success(fmt.Sprintf("Deleted %s", resource))
} else {
ctx.Warning("Deletion cancelled for safety")
}
return nil
}Custom Confirmation Messages
func deployHandler(ctx cli.CommandContext) error {
env := ctx.String("environment")
if env == "production" {
// Production deployment warning
confirmed, err := ctx.ConfirmWithOptions(cli.ConfirmOptions{
Message: "⚠️ You are about to deploy to PRODUCTION!",
Default: false,
YesLabel: "Deploy to Production",
NoLabel: "Cancel",
WarningStyle: true,
})
if err != nil {
return err
}
if !confirmed {
ctx.Warning("Production deployment cancelled")
return nil
}
}
ctx.Success("Deployment started!")
return nil
}Selection Prompts
Single Selection
Let users choose one option from a list:
func configHandler(ctx cli.CommandContext) error {
// Simple selection with arrow keys
environment, err := ctx.Select("Choose environment:", []string{
"development",
"staging",
"production",
})
if err != nil {
return err
}
// Selection with descriptions
database, err := ctx.SelectWithDescriptions("Choose database:", []cli.SelectOption{
{Value: "postgresql", Label: "PostgreSQL", Description: "Advanced open-source database"},
{Value: "mysql", Label: "MySQL", Description: "Popular relational database"},
{Value: "sqlite", Label: "SQLite", Description: "Lightweight embedded database"},
{Value: "mongodb", Label: "MongoDB", Description: "Document-oriented NoSQL database"},
})
if err != nil {
return err
}
// Selection with default
logLevel, err := ctx.SelectWithDefault(
"Log level:",
[]string{"debug", "info", "warn", "error"},
"info", // Default selection
)
if err != nil {
return err
}
ctx.Success(fmt.Sprintf("Configuration: %s, %s, %s", environment, database, logLevel))
return nil
}Multi-Selection
Allow users to select multiple options:
func featuresHandler(ctx cli.CommandContext) error {
// Multi-select with spacebar toggle
features, err := ctx.MultiSelect("Select features to enable:", []string{
"authentication",
"database",
"caching",
"logging",
"metrics",
"api-docs",
"websockets",
"file-upload",
})
if err != nil {
return err
}
if len(features) == 0 {
ctx.Warning("No features selected")
return nil
}
// Multi-select with pre-selected options
plugins, err := ctx.MultiSelectWithDefaults(
"Select plugins:",
[]string{"auth", "cache", "db", "events", "storage"},
[]string{"auth", "db"}, // Pre-selected
)
if err != nil {
return err
}
ctx.Success(fmt.Sprintf("Selected features: %v", features))
ctx.Success(fmt.Sprintf("Selected plugins: %v", plugins))
return nil
}Advanced Selection Options
func advancedSelectionHandler(ctx cli.CommandContext) error {
// Selection with custom styling
theme, err := ctx.SelectWithOptions(cli.SelectOptions{
Message: "Choose UI theme:",
Options: []cli.SelectOption{
{Value: "light", Label: "☀️ Light Theme", Description: "Clean and bright"},
{Value: "dark", Label: "🌙 Dark Theme", Description: "Easy on the eyes"},
{Value: "auto", Label: "🔄 Auto Theme", Description: "Follows system preference"},
},
Default: "auto",
PageSize: 5,
ShowHelp: true,
HelpText: "Use ↑↓ arrows to navigate, Enter to select",
})
if err != nil {
return err
}
// Searchable selection for large lists
country, err := ctx.SearchableSelect("Select country:", []string{
"United States", "Canada", "United Kingdom", "Germany",
"France", "Japan", "Australia", "Brazil", "India", "China",
// ... many more options
})
if err != nil {
return err
}
ctx.Success(fmt.Sprintf("Selected theme: %s, country: %s", theme, country))
return nil
}Interactive Wizards
Step-by-Step Setup
Create guided setup experiences:
func setupWizardHandler(ctx cli.CommandContext) error {
ctx.Info("🚀 Welcome to the Project Setup Wizard!")
ctx.Info("This wizard will help you configure your new project.\n")
// Step 1: Project basics
ctx.Info("📋 Step 1: Project Information")
projectName, err := ctx.PromptWithValidator(
"Project name:",
func(name string) error {
if len(name) < 3 {
return fmt.Errorf("project name must be at least 3 characters")
}
return nil
},
)
if err != nil {
return err
}
description, err := ctx.PromptWithDefault(
"Project description:",
"A new project built with Forge",
)
if err != nil {
return err
}
// Step 2: Technology choices
ctx.Info("\n🛠️ Step 2: Technology Stack")
language, err := ctx.Select("Programming language:", []string{
"Go",
"TypeScript",
"Python",
"Rust",
})
if err != nil {
return err
}
framework, err := ctx.Select("Framework:", []string{
"Forge (Go web framework)",
"Express.js (Node.js)",
"FastAPI (Python)",
"Actix (Rust)",
})
if err != nil {
return err
}
// Step 3: Features
ctx.Info("\n⚡ Step 3: Features")
features, err := ctx.MultiSelect("Select features:", []string{
"Authentication & Authorization",
"Database Integration",
"Caching (Redis)",
"Message Queue",
"File Storage",
"Real-time Features (WebSocket)",
"API Documentation",
"Monitoring & Metrics",
})
if err != nil {
return err
}
// Step 4: Deployment
ctx.Info("\n🚀 Step 4: Deployment")
deployment, err := ctx.Select("Deployment target:", []string{
"Docker Container",
"Kubernetes",
"Cloud Functions",
"Traditional Server",
})
if err != nil {
return err
}
// Step 5: Confirmation
ctx.Info("\n📋 Configuration Summary:")
ctx.Info(fmt.Sprintf(" Project: %s", projectName))
ctx.Info(fmt.Sprintf(" Description: %s", description))
ctx.Info(fmt.Sprintf(" Language: %s", language))
ctx.Info(fmt.Sprintf(" Framework: %s", framework))
ctx.Info(fmt.Sprintf(" Features: %v", features))
ctx.Info(fmt.Sprintf(" Deployment: %s", deployment))
confirmed, err := ctx.Confirm("\nCreate project with this configuration?")
if err != nil {
return err
}
if !confirmed {
ctx.Warning("Project creation cancelled")
return nil
}
// Create project with progress
progress := ctx.ProgressBar(len(features) + 3)
ctx.Info("Creating project structure...")
time.Sleep(500 * time.Millisecond)
progress.Increment()
ctx.Info("Installing dependencies...")
time.Sleep(800 * time.Millisecond)
progress.Increment()
for _, feature := range features {
ctx.Info(fmt.Sprintf("Setting up %s...", feature))
time.Sleep(300 * time.Millisecond)
progress.Increment()
}
ctx.Info("Finalizing configuration...")
time.Sleep(400 * time.Millisecond)
progress.Increment()
progress.Finish("Project created successfully! 🎉")
ctx.Success(fmt.Sprintf("Project '%s' is ready!", projectName))
ctx.Info("Run 'cd %s && go run main.go' to start your application", projectName)
return nil
}Prompt Validation
Input Validation
Validate user input with custom functions:
func validatedInputHandler(ctx cli.CommandContext) error {
// Email validation
email, err := ctx.PromptWithValidator(
"Email address:",
func(input string) error {
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
if !emailRegex.MatchString(input) {
return fmt.Errorf("invalid email format")
}
return nil
},
)
if err != nil {
return err
}
// Port number validation
port, err := ctx.PromptWithValidator(
"Port number (1-65535):",
func(input string) error {
port, err := strconv.Atoi(input)
if err != nil {
return fmt.Errorf("port must be a number")
}
if port < 1 || port > 65535 {
return fmt.Errorf("port must be between 1 and 65535")
}
return nil
},
)
if err != nil {
return err
}
// URL validation
apiUrl, err := ctx.PromptWithValidator(
"API URL:",
func(input string) error {
_, err := url.Parse(input)
if err != nil {
return fmt.Errorf("invalid URL format")
}
if !strings.HasPrefix(input, "http://") && !strings.HasPrefix(input, "https://") {
return fmt.Errorf("URL must start with http:// or https://")
}
return nil
},
)
if err != nil {
return err
}
ctx.Success(fmt.Sprintf("Configuration saved: %s, %s, %s", email, port, apiUrl))
return nil
}Conditional Prompts
Show prompts based on previous answers:
func conditionalPromptsHandler(ctx cli.CommandContext) error {
// Initial choice
deploymentType, err := ctx.Select("Deployment type:", []string{
"local",
"cloud",
"hybrid",
})
if err != nil {
return err
}
var cloudProvider, region string
// Conditional prompts based on deployment type
if deploymentType == "cloud" || deploymentType == "hybrid" {
cloudProvider, err = ctx.Select("Cloud provider:", []string{
"AWS",
"Google Cloud",
"Azure",
"DigitalOcean",
})
if err != nil {
return err
}
// Region selection based on provider
var regions []string
switch cloudProvider {
case "AWS":
regions = []string{"us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"}
case "Google Cloud":
regions = []string{"us-central1", "us-east1", "europe-west1", "asia-east1"}
case "Azure":
regions = []string{"East US", "West US 2", "West Europe", "Southeast Asia"}
case "DigitalOcean":
regions = []string{"NYC1", "SFO3", "AMS3", "SGP1"}
}
region, err = ctx.Select(fmt.Sprintf("%s region:", cloudProvider), regions)
if err != nil {
return err
}
}
// Database choice
useDatabase, err := ctx.Confirm("Do you need a database?")
if err != nil {
return err
}
var databaseType string
if useDatabase {
databaseType, err = ctx.Select("Database type:", []string{
"PostgreSQL",
"MySQL",
"MongoDB",
"Redis",
})
if err != nil {
return err
}
// Database-specific configuration
if databaseType == "PostgreSQL" || databaseType == "MySQL" {
backupEnabled, err := ctx.Confirm("Enable automated backups?")
if err != nil {
return err
}
if backupEnabled {
_, err = ctx.Select("Backup frequency:", []string{
"Daily",
"Weekly",
"Monthly",
})
if err != nil {
return err
}
}
}
}
ctx.Success("Configuration completed!")
return nil
}Prompt Styling
Custom Appearance
Customize prompt appearance and behavior:
func styledPromptsHandler(ctx cli.CommandContext) error {
// Prompt with custom styling
name, err := ctx.PromptWithOptions(cli.PromptOptions{
Message: "👤 Enter your name:",
Default: "",
Placeholder: "John Doe",
Style: cli.PromptStyle{
Prefix: "🔹",
SuccessIcon: "✅",
ErrorIcon: "❌",
Colors: cli.PromptColors{
Message: cli.ColorCyan,
Input: cli.ColorWhite,
Success: cli.ColorGreen,
Error: cli.ColorRed,
},
},
})
if err != nil {
return err
}
// Selection with custom styling
choice, err := ctx.SelectWithOptions(cli.SelectOptions{
Message: "🎨 Choose a theme:",
Options: []cli.SelectOption{
{Value: "light", Label: "☀️ Light", Description: "Bright and clean"},
{Value: "dark", Label: "🌙 Dark", Description: "Dark and elegant"},
{Value: "auto", Label: "🔄 Auto", Description: "System preference"},
},
PageSize: 3,
Style: cli.SelectStyle{
Cursor: "▶",
Selected: "●",
Unselected: "○",
Colors: cli.SelectColors{
Cursor: cli.ColorBlue,
Selected: cli.ColorGreen,
Unselected: cli.ColorGray,
Description: cli.ColorDarkGray,
},
},
})
if err != nil {
return err
}
ctx.Success(fmt.Sprintf("Hello %s! Theme set to %s", name, choice))
return nil
}Error Handling
Graceful Error Handling
Handle prompt errors gracefully:
func errorHandlingHandler(ctx cli.CommandContext) error {
// Handle user cancellation (Ctrl+C)
name, err := ctx.Prompt("Enter your name:")
if err != nil {
if cli.IsUserCancelled(err) {
ctx.Warning("Operation cancelled by user")
return nil
}
return fmt.Errorf("failed to get input: %v", err)
}
// Retry on validation failure
var email string
for attempts := 0; attempts < 3; attempts++ {
email, err = ctx.PromptWithValidator(
"Email address:",
func(input string) error {
if !strings.Contains(input, "@") {
return fmt.Errorf("invalid email format")
}
return nil
},
)
if err == nil {
break
}
if cli.IsValidationError(err) {
ctx.Error(fmt.Sprintf("Validation failed: %v", err))
if attempts < 2 {
ctx.Info("Please try again...")
}
} else {
return err
}
}
if email == "" {
return fmt.Errorf("failed to get valid email after 3 attempts")
}
ctx.Success(fmt.Sprintf("Welcome, %s (%s)!", name, email))
return nil
}Best Practices
Clear and Concise Messages
// Good: Clear and specific
name, err := ctx.Prompt("Project name (3-50 characters):")
// Bad: Vague
name, err := ctx.Prompt("Name:")Provide Helpful Defaults
// Good: Sensible default
port, err := ctx.PromptWithDefault("Server port:", "8080")
// Good: Environment-based default
env := os.Getenv("NODE_ENV")
if env == "" {
env = "development"
}
environment, err := ctx.PromptWithDefault("Environment:", env)Use Validation
// Always validate critical input
password, err := ctx.PromptPasswordWithValidator(
"Password:",
func(input string) error {
if len(input) < 8 {
return fmt.Errorf("password must be at least 8 characters")
}
return nil
},
)Group Related Prompts
// Group related configuration
ctx.Info("📊 Database Configuration")
dbHost, _ := ctx.PromptWithDefault("Host:", "localhost")
dbPort, _ := ctx.PromptWithDefault("Port:", "5432")
dbName, _ := ctx.Prompt("Database name:")
ctx.Info("\n🔐 Authentication")
username, _ := ctx.Prompt("Username:")
password, _ := ctx.PromptPassword("Password:")Provide Progress Feedback
func setupWithProgress(ctx cli.CommandContext) error {
ctx.Info("🚀 Starting setup process...")
// Collect all input first
name, _ := ctx.Prompt("Project name:")
features, _ := ctx.MultiSelect("Features:", []string{"auth", "db", "cache"})
// Then show progress
progress := ctx.ProgressBar(len(features) + 2)
ctx.Info("Creating project structure...")
progress.Increment()
for _, feature := range features {
ctx.Info(fmt.Sprintf("Setting up %s...", feature))
time.Sleep(200 * time.Millisecond)
progress.Increment()
}
ctx.Info("Finalizing...")
progress.Increment()
progress.Finish("Setup complete!")
return nil
}Next Steps
How is this guide?
Last updated on