Skip to content

RFD 0004: Smart Interactive Mode via Flag Detection

Summary

This RFD documents the decision to make CLI interactive prompts detect when users provide command-line flags and automatically skip prompts, rather than requiring an explicit --non-interactive flag. This pattern provides better UX for both interactive users and scripted automation.

Problem Statement

Users reported that running commands like samuel init --template full test-project would hang indefinitely, only proceeding after pressing Ctrl+C. The CLI would prompt for confirmation even when all required values were provided via flags.

Observed behavior:

$ samuel init --template full my-project
? Do you want to proceed? (Y/n)   # Hangs here despite --template being provided

Expected behavior:

$ samuel init --template full my-project
Initializing project with 'full' template...  # Proceeds automatically

Root Cause Analysis

Three interactive prompts were triggering even when CLI flags were provided:

  1. Confirmation prompt - Always triggered regardless of flags
  2. Language selection prompt - Triggered when template wasn't "full"
  3. Framework selection prompt - Same issue

Additionally, promptui.Prompt with IsConfirm: true had quirky behavior where Enter on empty input returned ErrAbort instead of using the default value.

Background

CLI Interaction Modes

CLI tools typically support two modes:

  1. Interactive mode: Prompts user for input, used in terminals
  2. Non-interactive mode: Uses defaults or flags, used in scripts/CI

Common Patterns

Pattern 1: Explicit flag

command --non-interactive  # Must explicitly disable prompts
command -y                 # Alternative: auto-yes flag

Pattern 2: TTY detection

# Automatically detects if stdin is a terminal
if [ -t 0 ]; then
  # Interactive
else
  # Non-interactive
fi

Pattern 3: Flag detection (chosen)

# If user provided flags, assume they know what they want
if flags_provided; then
  skip_prompts()
fi

Options Considered

Option A: Add Explicit --non-interactive Flag

Require users to add --non-interactive or -y flag to skip prompts.

Pros:

  • Explicit and clear
  • Common pattern (apt, npm, etc.)
  • Simple implementation

Cons:

  • Extra flag to remember
  • Verbose for scripted use
  • Users who provide all flags still need to add one more

Effort: Low

Option B: Track CLI-Provided Flags and Skip Prompts (Chosen)

Automatically detect when users provide CLI flags and skip related prompts.

Pros:

  • Better UX - if user provided flags, they know what they want
  • Works for both interactive and scripted use
  • No extra flags to remember
  • More intelligent behavior

Cons:

  • Slightly more complex logic
  • Implicit behavior (some may prefer explicit)

Effort: Low-Medium

Proposal

Chosen: Option B - Smart Flag Detection

Implementation

Track whether the user provided any CLI flags:

// Track if user provided CLI flags (skip prompts if so)
cliProvided := templateFlag != "" ||
               len(languageFlags) > 0 ||
               len(frameworkFlags) > 0

// Working variable for template name (may be set interactively)
templateName := templateFlag

Use !cliProvided as a condition for all interactive prompts:

// Only prompt if no CLI flags were provided
if !cliProvided {
    // Show interactive language selection
    selectedLangs, err = ui.SelectLanguages(availableLanguages)
}

// Only ask for confirmation if no CLI flags were provided
if !cliProvided {
    confirmed, err := ui.Confirm("Proceed with initialization?", true)
}

promptui Quirk Fix

The promptui.Prompt with IsConfirm: true has a quirk where pressing Enter on empty input returns ErrAbort instead of using the default. Fixed by not using IsConfirm:

// Before (buggy)
prompt := promptui.Prompt{
    Label:     label,
    IsConfirm: true,  // Enter returns ErrAbort
}

// After (fixed)
prompt := promptui.Prompt{
    Label:   label + " (Y/n)",
    Default: "y",  // Default shown in prompt
}
result, _ := prompt.Run()
return strings.ToLower(result) == "y" || result == ""

Implementation Considerations

Affected Code

  • internal/commands/init.go - Add cliProvided tracking
  • internal/ui/prompts.go - Fix Confirm function

Behavior Matrix

Scenario Behavior
No flags provided Full interactive mode
--template provided Skip template prompt, still ask for languages
--template + --languages Skip all prompts, proceed directly
Any flag provided Skip confirmation prompt

Testing

# All should work without hanging
samuel init --template minimal test-dir
samuel init --template starter test-dir
samuel init --template full test-dir
samuel init --languages go,rust test-dir

# Interactive mode still works
samuel init test-dir  # Prompts for all options

Security Considerations

No security implications. This is a UX improvement only.

Compatibility

  • Breaking changes: None (behavior change is improvement)
  • Migration path: N/A
  • Backwards compatibility: Existing scripts that use --template will now work better

Consequences

Positive

  • Users can use samuel init --template full <dir> without prompts
  • Interactive mode still works fully when no flags provided
  • Ctrl+C properly cancels operations
  • Enter on confirmation now correctly uses default value
  • Better CI/script experience

Negative

  • Users who wanted to provide partial flags and still be prompted won't get prompts
  • Implicit behavior may surprise some users (though it matches expectations)

Pattern Established

This pattern can be reused for other commands that mix interactive and non-interactive modes:

// Pattern: Track CLI-provided flags
cliProvided := flag1 != "" || flag2 != "" || len(flags3) > 0

// Only prompt if user didn't provide flags
if !cliProvided {
    // Interactive prompt
}

References

  • Decision documented: .claude/memory/2026-01-14-cli-interactive-mode-fix.md
  • promptui: github.com/manifoldco/promptui
  • Related pattern: .claude/patterns.md (CLI Flag Detection Pattern)