The Interactive Selection System provides a keyboard-driven, arrow-based menu interface for user input during project initialization. This system enables developers to navigate options using arrow keys and make selections without typing, creating a streamlined CLI experience similar to modern interactive installers. This page focuses specifically on the selection UI mechanism; for command-line argument handling, see CLI Commands Reference, and for the overall initialization workflow, see Project Initialization.
The selection system consists of three core components:
| Component | Purpose | Key Function |
|---|---|---|
| Input Handler | Cross-platform keyboard detection | get_key() |
| Selection Engine | Arrow-based navigation logic | select_with_arrows() |
| Display Renderer | Real-time UI updates with Rich | create_selection_panel() |
The system is invoked twice during specify init:
sh) or PowerShell (ps)Sources: src/specify_cli/__init__.py350-423
Sources: src/specify_cli/__init__.py330-423 src/specify_cli/__init__.py126-231 src/specify_cli/__init__.py1086-1124
The get_key() function provides normalized keyboard input across Windows, macOS, and Linux by wrapping the readchar library.
| User Input | Normalized Output | Purpose |
|---|---|---|
↑ or Ctrl+P | 'up' | Navigate to previous option |
↓ or Ctrl+N | 'down' | Navigate to next option |
Enter | 'enter' | Confirm selection |
Esc | 'escape' | Cancel and exit |
Ctrl+C | KeyboardInterrupt | Force abort |
The function uses readchar.readkey() for blocking keyboard reads and normalizes platform-specific key codes into simple strings. Vim-style navigation (Ctrl+P/Ctrl+N) provides alternative input methods for users who prefer keyboard shortcuts.
Sources: src/specify_cli/__init__.py330-348
The core selection function implements a state machine that tracks the current selection index and responds to keyboard events.
| Variable | Type | Purpose |
|---|---|---|
option_keys | list[str] | Ordered list of option keys |
selected_index | int | Current cursor position (0-based) |
selected_key | `str | None` |
The selection index wraps around using modulo arithmetic:
selected_index = (selected_index - 1) % len(option_keys)selected_index = (selected_index + 1) % len(option_keys)This creates infinite circular navigation where moving up from the first item jumps to the last, and vice versa.
Sources: src/specify_cli/__init__.py350-423
The selection UI is rendered using Rich's Live context manager, which enables real-time updates without terminal flicker.
The nested create_selection_panel() function src/specify_cli/__init__.py370-390 builds the UI:
Key Rendering Decisions:
▶ indicator with cyan-colored keytransient=True removes the panel after selection completesSources: src/specify_cli/__init__.py370-390
The event loop src/specify_cli/__init__.py394-415 runs within a Rich Live context:
Critical Implementation Notes:
nonlocal declarations allow modification of outer scope variablesauto_refresh=False prevents automatic updates; only live.update() triggers re-renderstransient=True ensures the panel disappears after selection, leaving a clean terminalEsc key and Ctrl+C for graceful cancellationSources: src/specify_cli/__init__.py394-415
The AGENT_CONFIG dictionary src/specify_cli/__init__.py126-229 defines all supported AI agents:
| Key | Agent Name | CLI Required | Example |
|---|---|---|---|
copilot | GitHub Copilot | No (IDE-based) | Default selection |
claude | Claude Code | Yes | CLI tool check |
gemini | Gemini CLI | Yes | CLI tool check |
cursor-agent | Cursor | No (IDE-based) | IDE integration |
windsurf | Windsurf | No (IDE-based) | IDE integration |
qwen | Qwen Code | Yes | CLI tool check |
| ... | (17 total) | Varies | See full list |
Selection Usage src/specify_cli/__init__.py1086-1093:
The function receives a transformed dict where keys are agent IDs (claude, gemini) and values are human-readable names (Claude Code, Gemini CLI).
The SCRIPT_TYPE_CHOICES dictionary src/specify_cli/__init__.py231 defines shell script options:
Platform-Aware Defaults src/specify_cli/__init__.py1119-1124:
The system automatically defaults to PowerShell on Windows (os.name == "nt") and POSIX shell on Unix-like systems. If the terminal is non-interactive (not sys.stdin.isatty()), the system skips selection and uses the platform default directly.
Sources: src/specify_cli/__init__.py126-231 src/specify_cli/__init__.py1086-1124
The selection system integrates into two specific points in the init command workflow:
Command-Line Override Precedence:
--ai or --script flags take highest precedenceSources: src/specify_cli/__init__.py1081-1124
The system detects non-interactive environments to prevent hanging:
Use Cases:
echo "..." | specify init my-project| Platform | Terminal | Arrow Keys | Escape | Notes |
|---|---|---|---|---|
| Windows | Command Prompt | ✓ | ✓ | Native support |
| Windows | PowerShell | ✓ | ✓ | Native support |
| Windows | Git Bash | ✓ | ✓ | MinTTY compatible |
| macOS | Terminal.app | ✓ | ✓ | Native support |
| macOS | iTerm2 | ✓ | ✓ | Native support |
| Linux | gnome-terminal | ✓ | ✓ | Native support |
| Linux | xterm | ✓ | ✓ | Native support |
| Remote | SSH session | ✓ | ✓ | Requires TTY allocation |
The readchar library src/specify_cli/__init__.py51 handles platform-specific terminal codes internally, ensuring consistent behavior across all supported environments.
Sources: src/specify_cli/__init__.py51 src/specify_cli/__init__.py1119-1124
| Scenario | Exit Code | User Feedback |
|---|---|---|
| Enter pressed | 0 (success) | Returns selected key |
| Esc pressed | 1 (failure) | [yellow]Selection cancelled[/yellow] |
| Ctrl+C pressed | 1 (failure) | [yellow]Selection cancelled[/yellow] |
| Selection failed | 1 (failure) | [red]Selection failed.[/red] |
Post-Selection Validation src/specify_cli/__init__.py419-421:
This catch-all ensures that if the loop exits without setting selected_key, the command fails gracefully rather than returning invalid data.
Sources: src/specify_cli/__init__.py408-421
Interactive Session:
$ specify init my-project
Choose your AI assistant:
▶ copilot (GitHub Copilot)
claude (Claude Code)
gemini (Gemini CLI)
cursor-agent (Cursor)
qwen (Qwen Code)
...
Use ↑/↓ to navigate, Enter to select, Esc to cancel
Command-Line Override:
Windows Default:
Choose script type (or press Enter):
▶ ps (PowerShell)
sh (POSIX Shell (bash/zsh))
Use ↑/↓ to navigate, Enter to select, Esc to cancel
Unix Default:
Choose script type (or press Enter):
▶ sh (POSIX Shell (bash/zsh))
ps (PowerShell)
Use ↑/↓ to navigate, Enter to select, Esc to cancel
Sources: src/specify_cli/__init__.py1086-1127
| Package | Purpose | Version Requirement |
|---|---|---|
readchar | Cross-platform keyboard input | Listed in dependencies |
rich | Terminal UI rendering | Listed in dependencies |
typer | CLI framework | Listed in dependencies |
Import Statements src/specify_cli/__init__.py38-48:
The readchar library specifically handles the cross-platform keyboard detection that makes arrow-based navigation possible across Windows, macOS, and Linux.
Sources: src/specify_cli/__init__.py1-52
Refresh this wiki