README
ΒΆ
π Tap
Beautiful, interactive command-line prompts for Go - A Go port of the popular TypeScript Clack library.
β οΈ Heavy Development: This project is currently in heavy development. APIs may change, and some features are still being implemented. Use with caution in production environments.
Why Tap?
Building interactive CLI applications shouldn't be complex. Tap brings the elegant, minimal design philosophy of Clack to the Go ecosystem, offering:
- Event-driven architecture for responsive, race-condition-free prompts
- Beautiful styling with consistent visual design and Unicode symbols
- Type-safe APIs with generic support for strongly-typed selections
- Comprehensive testing with built-in mock utilities
- Minimal dependencies - Only essential terminal and keyboard libraries
Features
β Available Now
Core Components:
- Text Input - Single-line input with cursor navigation, validation, placeholders, and default values
- Confirm - Yes/No prompts with customizable labels and keyboard navigation
- Select - Single selection from typed options with hints and color-coded display
- Progress Bar - Animated progress indicators with multiple styles (light, heavy, block)
- Message Primitives - Intro, outro, cancel messages, and styled boxes
- Event System - Race-condition-free event loop architecture
Developer Experience:
- Mock Testing - Built-in utilities for reliable prompt testing
- Type Safety - Generic APIs for strongly-typed selections
- Terminal Management - Raw mode handling, keyboard input, and cursor control
π Coming Soon
- Password Input - Masked text input for sensitive data
- Multi-Select - Multiple selection from lists with checkboxes
- Autocomplete - Text input with suggestion dropdown
- Spinner - Loading indicators for long-running operations
- Group - Grouped prompts for complex workflows
Installation
go get github.com/yarlson/tap@latest
Quick Start
package main
import (
"fmt"
"github.com/yarlson/tap/core"
"github.com/yarlson/tap/prompts"
"github.com/yarlson/tap/terminal"
)
func main() {
// Initialize terminal
term, err := terminal.New()
if err != nil {
panic(err)
}
defer term.Close()
// Text input
name := prompts.Text(prompts.TextOptions{
Message: "What's your name?",
Input: term.Reader,
Output: term.Writer,
})
// Check for cancellation
if core.IsCancel(name) {
prompts.Cancel("Operation cancelled.", prompts.MessageOptions{Output: term.Writer})
return
}
// Confirmation
confirmed := prompts.Confirm(prompts.ConfirmOptions{
Message: fmt.Sprintf("Hello %s! Continue?", name),
Input: term.Reader,
Output: term.Writer,
})
if core.IsCancel(confirmed) {
prompts.Cancel("Operation cancelled.", prompts.MessageOptions{Output: term.Writer})
return
}
if confirmed.(bool) {
prompts.Outro("Let's go! π", prompts.MessageOptions{Output: term.Writer})
}
}
API Examples
Text Input with Validation
import "errors"
email := prompts.Text(prompts.TextOptions{
Message: "Enter your email:",
Placeholder: "user@example.com",
DefaultValue: "anonymous@example.com",
Validate: func(input string) error {
if !strings.Contains(input, "@") {
return errors.New("Please enter a valid email")
}
return nil
},
Input: term.Reader,
Output: term.Writer,
})
Confirmation with Custom Labels
proceed := prompts.Confirm(prompts.ConfirmOptions{
Message: "Deploy to production?",
Active: "Deploy",
Inactive: "Cancel",
InitialValue: false,
Input: term.Reader,
Output: term.Writer,
})
Type-Safe Selection
type Environment string
envs := []prompts.SelectOption[Environment]{
{Value: "dev", Label: "Development", Hint: "Local development"},
{Value: "staging", Label: "Staging", Hint: "Pre-production testing"},
{Value: "prod", Label: "Production", Hint: "Live environment"},
}
env := prompts.Select(prompts.SelectOptions[Environment]{
Message: "Choose deployment target:",
Options: envs,
Input: term.Reader,
Output: term.Writer,
})
Progress Bar
prog := prompts.NewProgress(prompts.ProgressOptions{
Style: "heavy", // "light", "heavy", or "block"
Max: 100, // total units of work
Size: 40, // bar width in characters
Output: term.Writer,
})
prog.Start("Processing...")
for i := 0; i <= 100; i += 10 {
time.Sleep(200 * time.Millisecond)
prog.Advance(10, fmt.Sprintf("Step %d/10", i/10+1))
}
prog.Stop("Complete!", 0) // 0=success, 1=cancel, 2=error
Architecture
Tap uses a clean, layered architecture designed for performance and reliability:
Package Structure
core- Core prompt engine with unstyled, functional promptsprompts- Beautifully styled prompts built on top of coreterminal- Terminal management, keyboard input, and cursor control
Event-Driven Design
All prompts use a pure event loop architecture for race-condition-free operation:
// Single event loop processes all state changes
for event := range prompt.events {
event(&state) // Update state
prompt.render(&state) // Render changes
prompt.updateSnapshot(&state) // Update atomic snapshot
}
This approach eliminates the need for mutexes while providing excellent performance and thread safety through atomic snapshots.
Testing
Tap includes comprehensive test coverage with built-in mock utilities:
# Run all tests
go test ./...
# Test with race detection
go test -race ./...
# Test specific packages
go test ./prompts -v
go test ./core -v
Mock Testing Example
func TestTextPrompt(t *testing.T) {
mockInput := core.NewMockReader()
mockOutput := core.NewMockWriter()
// Simulate user typing "hello" and pressing enter
mockInput.SendString("hello")
mockInput.SendKey("return")
result := prompts.Text(prompts.TextOptions{
Message: "Enter text:",
Input: mockInput,
Output: mockOutput,
})
assert.Equal(t, "hello", result)
}
Examples
Explore working examples in the examples/ directory:
# Try different prompt types
go run examples/text/main.go # Text input with validation
go run examples/confirm/main.go # Yes/No confirmations
go run examples/select/main.go # Single selection menus
go run examples/progress/main.go # Progress bars and status
go run examples/multiple/main.go # Complete workflow example
Each example demonstrates different features and can serve as starting points for your applications.
Development Status
Tap is in active development. Core functionality is stable and tested, but APIs may evolve. Current status:
- β Stable: Text, Confirm, Select prompts with full styling
- β Stable: Progress bars and message primitives
- β Stable: Event loop architecture and terminal management
- π In Progress: Additional prompt types and enhanced styling
- π Planned: Documentation site, themes, and advanced features
Contributing
Contributions welcome! Areas where help is needed:
- New Prompt Types - Multi-select, password, autocomplete
- Enhanced Styling - Themes, custom symbols, color schemes
- Documentation - API docs, tutorials, more examples
- Testing - Cross-platform testing, edge cases, performance
- Bug Reports - Issues with keyboard handling or rendering
Development Setup
git clone https://github.com/yarlson/tap.git
cd tap
go test ./... # Run tests
go run examples/text/main.go # Try examples
Please follow Go best practices, maintain test coverage above 80%, and use the event-driven architecture patterns established in the codebase.
License
MIT License - see LICENSE file for details.
Acknowledgments
- Clack - The original TypeScript library that inspired this project
- @eiannone/keyboard - Cross-platform keyboard input for Go
- The Go community for excellent tooling and libraries
Links
- Documentation: Package docs on pkg.go.dev
- Original Clack: clack.cc
- Issues & Feature Requests: GitHub Issues
- Examples:
examples/directory
Making interactive CLI development effortless in Go. π