Skip to main content

Overview

Prompter provides a clean interface for gathering user input in CLI applications. Built on top of AlecAivazis/survey, it supports single selection, multi-selection, text input, and yes/no confirmations.

Interface

The Prompter interface defines methods for interactive user input.
type Prompter interface {
    Select(message, defaultValue string, options []string) (int, error)
    MultiSelect(message, defaultValue string, options []string) ([]int, error)
    Input(message, defaultValue string) (string, error)
    Confirm(message string, defaultValue bool) (bool, error)
}

Functions

New

Creates a new Prompter instance.
func New() Prompter
Prompter
Prompter
A new Prompter instance ready for user interaction
Example:
prompter := prompter.New()

Methods

Select

Prompts the user to select one option from a list.
func (p *Prompter) Select(message, defaultValue string, options []string) (int, error)
message
string
required
The prompt message to display to the user
defaultValue
string
The default selected option (must match one of the options)
options
[]string
required
List of options to display (page size: 20)
index
int
Zero-based index of the selected option
error
error
Error if the prompt fails or user cancels (Ctrl+C)
Example:
prompter := prompter.New()

options := []string{"Development", "Staging", "Production"}
index, err := prompter.Select(
    "Select environment:",
    "Development",
    options,
)
if err != nil {
    log.Fatal(err)
}

selected := options[index]
fmt.Printf("Deploying to %s\n", selected)
Output:
? Select environment: [Use arrows to move, type to filter]
> Development
  Staging
  Production

MultiSelect

Prompts the user to select multiple options from a list.
func (p *Prompter) MultiSelect(message, defaultValue string, options []string) ([]int, error)
message
string
required
The prompt message to display to the user
defaultValue
string
Comma-separated default values (must match options)
options
[]string
required
List of options to display (page size: 20)
indices
[]int
Slice of zero-based indices of selected options
error
error
Error if the prompt fails or user cancels (Ctrl+C)
Example:
prompter := prompter.New()

services := []string{"api", "web", "worker", "scheduler"}
indices, err := prompter.MultiSelect(
    "Select services to restart:",
    "",
    services,
)
if err != nil {
    log.Fatal(err)
}

if len(indices) == 0 {
    fmt.Println("No services selected")
    return
}

fmt.Println("Restarting services:")
for _, idx := range indices {
    fmt.Printf("  - %s\n", services[idx])
}
Output:
? Select services to restart: [Use arrows to move, space to select, type to filter]
  [ ] api
  [x] web
  [x] worker
  [ ] scheduler

Input

Prompts the user for text input.
func (p *Prompter) Input(message, defaultValue string) (string, error)
message
string
required
The prompt message to display to the user
defaultValue
string
Default value shown in the input field
input
string
User’s text input
error
error
Error if the prompt fails or user cancels (Ctrl+C)
Example:
prompter := prompter.New()

name, err := prompter.Input(
    "Enter project name:",
    "my-project",
)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Creating project: %s\n", name)
Output:
? Enter project name: my-project

Confirm

Prompts the user for a yes/no confirmation.
func (p *Prompter) Confirm(message string, defaultValue bool) (bool, error)
message
string
required
The confirmation question to display
defaultValue
bool
required
Default answer (true for yes, false for no)
confirmed
bool
True if user confirmed (yes), false otherwise (no)
error
error
Error if the prompt fails or user cancels (Ctrl+C)
Example:
prompter := prompter.New()

confirmed, err := prompter.Confirm(
    "Delete all data?",
    false,
)
if err != nil {
    log.Fatal(err)
}

if confirmed {
    fmt.Println("Deleting data...")
} else {
    fmt.Println("Cancelled")
}
Output:
? Delete all data? (y/N)

Error Handling

All methods return an error if:
  • User cancels the prompt (Ctrl+C)
  • Terminal doesn’t support interactive input
  • Internal survey error occurs
Example:
index, err := prompter.Select("Choose option:", "", options)
if err != nil {
    if err.Error() == "interrupt" {
        fmt.Println("\nOperation cancelled")
        os.Exit(0)
    }
    log.Fatal(err)
}

Best Practices

Always provide default values to improve user experience:
// Good: User can just press Enter
env, err := prompter.Select(
    "Select environment:",
    "Development",  // Default
    []string{"Development", "Staging", "Production"},
)

// Avoid: No default, user must navigate
env, err := prompter.Select(
    "Select environment:",
    "",  // No default
    []string{"Development", "Staging", "Production"},
)
Check for interrupt errors and exit cleanly:
result, err := prompter.Input("Enter value:", "")
if err != nil {
    if strings.Contains(err.Error(), "interrupt") {
        fmt.Println("\nCancelled")
        return nil
    }
    return err
}
Validate user input before using it:
for {
    name, err := prompter.Input("Project name:", "")
    if err != nil {
        return err
    }
    
    if isValidName(name) {
        break
    }
    
    fmt.Println("Invalid name. Use alphanumeric characters only.")
}
Always confirm before destructive actions:
confirmed, err := prompter.Confirm(
    "This will delete all data. Continue?",
    false,  // Default to No
)
if err != nil || !confirmed {
    fmt.Println("Operation cancelled")
    return
}

Complete Example

package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/raystack/salt/cli/prompter"
    "github.com/raystack/salt/cli/printer"
)

func main() {
    p := prompter.New()

    // Get project name
    projectName, err := p.Input(
        "Enter project name:",
        "my-app",
    )
    if err != nil {
        handleError(err)
        return
    }

    // Select environment
    environments := []string{"Development", "Staging", "Production"}
    envIdx, err := p.Select(
        "Select deployment environment:",
        "Development",
        environments,
    )
    if err != nil {
        handleError(err)
        return
    }
    environment := environments[envIdx]

    // Select services
    services := []string{"api", "web", "worker", "scheduler"}
    serviceIndices, err := p.MultiSelect(
        "Select services to deploy:",
        "",
        services,
    )
    if err != nil {
        handleError(err)
        return
    }

    if len(serviceIndices) == 0 {
        printer.Warningln("No services selected")
        return
    }

    // Show summary
    fmt.Println("\nDeployment Configuration:")
    fmt.Printf("  Project: %s\n", projectName)
    fmt.Printf("  Environment: %s\n", environment)
    fmt.Printf("  Services: ")
    for i, idx := range serviceIndices {
        if i > 0 {
            fmt.Print(", ")
        }
        fmt.Print(services[idx])
    }
    fmt.Println()

    // Confirm deployment
    confirmed, err := p.Confirm(
        "\nProceed with deployment?",
        true,
    )
    if err != nil {
        handleError(err)
        return
    }

    if !confirmed {
        printer.Infoln("Deployment cancelled")
        return
    }

    // Proceed with deployment
    printer.Successln("Starting deployment...")
}

func handleError(err error) {
    if strings.Contains(err.Error(), "interrupt") {
        fmt.Println("\nOperation cancelled")
        return
    }
    log.Fatal(err)
}

Build docs developers (and LLMs) love