tap

package module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2025 License: MIT Imports: 6 Imported by: 4

README

🚀 Tap

Beautiful, interactive command-line prompts for Go - A Go port of the popular TypeScript Clack library.

Tap Demo

⚠️ 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
  • Password Input - Masked text input for sensitive data
  • 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)
  • Spinner - Loading indicators with dots, timer, or custom frames
  • 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
  • Multi-Select - Multiple selection from lists with checkboxes
  • Autocomplete - Text input with suggestion dropdown
  • Group - Grouped prompts for complex workflows

Installation

go get github.com/yarlson/tap@latest

Quick Start

Usage

Use the tap package:

package main

import (
    "fmt"

    "github.com/yarlson/tap"
)

func main() {
    // Use package-level helpers (auto-inits a session under the hood)
    name := tap.Text(tap.TextOptions{Message: "What's your name?"})
    if tap.IsCancel(name) {
        tap.Cancel("Operation cancelled.")
        return
    }

    confirmed := tap.Confirm(tap.ConfirmOptions{Message: fmt.Sprintf("Hello %v! Continue?", name)})
    if tap.IsCancel(confirmed) {
        tap.Cancel("Operation cancelled.")
        return
    }

    if confirmed.(bool) {
        tap.Outro("Let's go! 🎉")
    }
}

Optionally manage a session explicitly:

package main

import (
    "fmt"

    "github.com/yarlson/tap"
)

func main() {
    s, err := tap.Init() // or tap.New()
    if err != nil { panic(err) }
    defer tap.CloseDefault() // or s.Close()

    name := tap.Text(tap.TextOptions{Message: "What's your name?"})
    if tap.IsCancel(name) { tap.Cancel("Operation cancelled."); return }

    confirmed := tap.Confirm(tap.ConfirmOptions{Message: fmt.Sprintf("Hello %v! Continue?", name)})
    if tap.IsCancel(confirmed) { tap.Cancel("Operation cancelled."); return }

    if confirmed.(bool) { tap.Outro("Let's go! 🎉") }
}

API Examples

Text Input with Validation (tap API)
import "errors"

email := tap.Text(tap.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
    },
})
Password Input (masked)
pwd := tap.Password(tap.PasswordOptions{Message: "Enter your password:"})
if tap.IsCancel(pwd) { tap.Cancel("Operation cancelled."); return }
fmt.Printf("Password length: %d\n", len(pwd.(string)))
Confirmation with Custom Labels
proceed := tap.Confirm(tap.ConfirmOptions{
    Message:      "Deploy to production?",
    Active:       "Deploy",
    Inactive:     "Cancel",
    InitialValue: false,
})
Type-Safe Selection
type Environment string

envs := []tap.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 := tap.Select(tap.SelectOptions[Environment]{
    Message: "Choose deployment target:",
    Options: envs,
})
Progress Bar
prog := tap.NewProgress(tap.ProgressOptions{
    Style:  "heavy",     // "light", "heavy", or "block"
    Max:    100,         // total units of work
    Size:   40,          // bar width in characters
})

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
Spinner
// Default spinner (dots)
spin := tap.NewSpinner(tap.SpinnerOptions{})
spin.Start("Connecting")
// ... do work ...
spin.Stop("Connected", 0)

// Timer indicator
timerSpin := tap.NewSpinner(tap.SpinnerOptions{Indicator: "timer"})
timerSpin.Start("Fetching data")
// ... do work ...
timerSpin.Stop("Done", 0)

// Custom frames and delay
customSpin := tap.NewSpinner(tap.SpinnerOptions{Frames: []string{"-", "\\", "|", "/"}, Delay: 100 * time.Millisecond})
customSpin.Start("Working")
// ... do work ...
customSpin.Stop("Complete", 0)

Architecture

Tap uses a clean, layered architecture designed for performance and reliability:

Package Structure
  • core - Core prompt engine with unstyled, functional prompts
  • prompts - Beautifully styled prompts built on top of core
  • terminal - 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/password/main.go  # Password input (masked)
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/spinner/main.go   # Spinners (dots, timer, custom frames)
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, 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

Built with ❤️ for developers who value simplicity and speed.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Box

func Box(message string, title string, opts BoxOptions)

func Cancel

func Cancel(message string)

func CloseDefault

func CloseDefault()

CloseDefault closes the default session, if any.

func Confirm

func Confirm(opts ConfirmOptions) any

func CyanBorder

func CyanBorder(s string) string

func GrayBorder

func GrayBorder(s string) string

Re-export common border formatters for convenience

func Intro

func Intro(title string)

func IsCancel

func IsCancel(v any) bool

func NewProgress

func NewProgress(opts ProgressOptions) *prompts.Progress

func NewSpinner

func NewSpinner(opts SpinnerOptions) *prompts.Spinner

func Outro

func Outro(message string)

func Password

func Password(opts PasswordOptions) any

func Select

func Select[T any](opts SelectOptions[T]) any

func Text

func Text(opts TextOptions) any

Types

type BoxAlignment

type BoxAlignment = prompts.BoxAlignment

type BoxOptions

type BoxOptions struct {
	Columns        int
	WidthFraction  float64
	WidthAuto      bool
	TitlePadding   int
	ContentPadding int
	TitleAlign     BoxAlignment
	ContentAlign   BoxAlignment
	Rounded        bool
	IncludePrefix  bool
	FormatBorder   func(string) string
}

type CancelSymbol

type CancelSymbol = core.CancelSymbol

type ConfirmOptions

type ConfirmOptions struct {
	Message      string
	Active       string
	Inactive     string
	InitialValue bool
}

ConfirmOptions mirrors prompts.ConfirmOptions but without Input/Output.

type PasswordOptions

type PasswordOptions struct {
	Message      string
	DefaultValue string
	InitialValue string
	Validate     func(string) error
}

PasswordOptions mirrors prompts.PasswordOptions but without Input/Output.

type ProgressOptions

type ProgressOptions struct {
	Style string
	Max   int
	Size  int
}

ProgressOptions mirrors prompts.ProgressOptions but without Output.

type SelectOption

type SelectOption[T any] struct {
	Value T
	Label string
	Hint  string
}

SelectOption mirrors prompts.SelectOption.

type SelectOptions

type SelectOptions[T any] struct {
	Message      string
	Options      []SelectOption[T]
	InitialValue *T
	MaxItems     *int
}

SelectOptions mirrors prompts.SelectOptions but without Input/Output.

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session owns a terminal and provides high-level prompt helpers that hide reader/writer from the caller.

func Init

func Init() (*Session, error)

Init initializes a default session for package-level helpers.

func New

func New() (*Session, error)

New creates a new Session with its own terminal.

func (*Session) Box

func (s *Session) Box(message string, title string, opts BoxOptions)

func (*Session) Cancel

func (s *Session) Cancel(message string)

func (*Session) Close

func (s *Session) Close()

Close releases session resources.

func (*Session) Confirm

func (s *Session) Confirm(opts ConfirmOptions) any

func (*Session) Intro

func (s *Session) Intro(title string)

Message helpers bound to the session writer.

func (*Session) NewProgress

func (s *Session) NewProgress(opts ProgressOptions) *prompts.Progress

func (*Session) NewSpinner

func (s *Session) NewSpinner(opts SpinnerOptions) *prompts.Spinner

NewSpinner creates a spinner bound to the session's output.

func (*Session) Outro

func (s *Session) Outro(message string)

func (*Session) Password

func (s *Session) Password(opts PasswordOptions) any

func (*Session) Text

func (s *Session) Text(opts TextOptions) any

type SpinnerOptions

type SpinnerOptions struct {
	Indicator     string
	Frames        []string
	Delay         time.Duration
	CancelMessage string
	ErrorMessage  string
}

SpinnerOptions mirrors prompts.SpinnerOptions but without Output.

type TextOptions

type TextOptions struct {
	Message      string
	Placeholder  string
	DefaultValue string
	InitialValue string
	Validate     func(string) error
}

TextOptions mirrors prompts.TextOptions but without Input/Output.

Directories

Path Synopsis
examples
confirm command
multiple command
password command
progress command
select command
spinner command
text command
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL