tap

package module
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2025 License: MIT Imports: 5 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
  • Multi-Select - Multiple selection with checkboxes, selection count, and limits
  • Progress Bar - Animated progress indicators with multiple styles (light, heavy, block)
  • Spinner - Loading indicators with dots, timer, or custom frames
  • Stream - Real-time output areas with Start/Pipe/Stop API
  • 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
  • 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() {
    name := tap.Text(tap.TextOptions{Message: "What's your name?"})
    confirmed := tap.Confirm(tap.ConfirmOptions{Message: fmt.Sprintf("Hello %v! Continue?", name)})
    if confirmed {
        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:"})
fmt.Printf("Password length: %d\n", len(pwd))
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
}
Stream (live output)
st := tap.NewStream(tap.StreamOptions{ShowTimer: true})
st.Start("Building project")
st.WriteLine("step 1: fetch deps")
st.WriteLine("step 2: compile")
// or pipe an io.Reader: st.Pipe(reader)
st.Stop("Done", 0) // 0=success, 1=cancel, >1=error

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)
}
Using Tap with LLMs

To help language models reliably use this library, provide them a compact API reference and constraints. This repo includes README.LLM.md for that purpose.

  • Prefer pasting the contents of README.LLM.md into the LLM context (system or first user message)
  • Explicitly state the module path github.com/yarlson/tap and that returns are typed
  • Ask for runnable Go code with proper imports

Prompt template:

System:
You are a Go coding agent. Use this library to build interactive terminal prompts.

API cheat sheet:
<paste README.LLM.md here>

User:
Write a Go program that:
- asks for name and email (validate email)
- asks to confirm, then shows an outro.
Return a complete main.go.

Testing template (mock I/O):

System:
Use mocks for terminal I/O. In tests, call tap.SetTermIO(in, out) with core mocks.

User:
Write a table-driven test for Text/Confirm using core.NewMockReadable/NewMockWritable and tap.SetTermIO.
Testing tap helpers (override terminal I/O)

Tap helpers open a terminal per call by default. In tests, you can override input/output to avoid opening a real terminal:

in := core.NewMockReadable()
out := core.NewMockWritable()

tap.SetTermIO(in, out)
defer tap.SetTermIO(nil, nil)

go func() {
  _ = tap.Text(tap.TextOptions{Message: "Your name:"})
}()

in.EmitKeypress("A", core.Key{Name: "a"})
in.EmitKeypress("", core.Key{Name: "return"})

// Assert output frames via out.Buffer

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/stream/main.go    # Stream live output
go run examples/multiselect/main.go  # Multiple selection menus
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, Password, Confirm, Select, Multi-Select
  • Stable: Progress bars and message primitives
  • Stable: Event loop architecture and terminal management
  • 🔄 In Progress: Autocomplete, grouped prompts, enhanced styling
  • 📋 Planned: Documentation site, themes, and advanced features

Contributing

Contributions welcome! Areas where help is needed:

  • New Prompt Types - 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

Overview

Package tap provides high-level, clack-style terminal prompts, spinners, progress bars, and message helpers. The package exposes simple synchronous helper functions and manages a default interactive session under the hood.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Box

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

Box renders a framed message with optional title and alignment using the current session writer or stdout if no session is active.

func Confirm

func Confirm(opts ConfirmOptions) bool

Confirm displays a yes/no confirmation prompt and returns the selection. A terminal is created and cleaned up automatically per call.

func CyanBorder

func CyanBorder(s string) string

CyanBorder formats a string with a cyan box-drawing border.

func GrayBorder

func GrayBorder(s string) string

GrayBorder formats a string with a gray box-drawing border.

func Intro

func Intro(title string)

Intro prints an introductory message using the current session writer or stdout if no session is active.

func MultiSelect added in v0.5.0

func MultiSelect[T any](opts MultiSelectOptions[T]) []T

MultiSelect displays a multi-selection list and returns the chosen typed values. A terminal is created and cleaned up automatically per call.

func Outro

func Outro(message string)

Outro prints a closing message using the current session writer or stdout if no session is active.

func Password

func Password(opts PasswordOptions) string

Password displays a masked text input prompt and returns the entered value. A terminal is created and cleaned up automatically per call.

func Select

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

Select displays a single-selection list and returns the chosen typed value. A terminal is created and cleaned up automatically per call.

func SetTermIO added in v0.4.0

func SetTermIO(in core.Reader, out core.Writer)

SetTermIO sets a custom reader and writer used by helpers. Pass nil values to restore default terminal behavior.

func Text

func Text(opts TextOptions) string

Text displays an interactive single-line text input prompt and returns the entered value. A terminal is created and cleaned up automatically per call.

Types

type BoxAlignment

type BoxAlignment = prompts.BoxAlignment

BoxAlignment is an alias of prompts.BoxAlignment to control box content alignment.

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
}

BoxOptions configures the Box message renderer.

type ConfirmOptions

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

ConfirmOptions configures the Confirm prompt. I/O fields are managed by tap.

type MultiSelectOptions added in v0.5.0

type MultiSelectOptions[T any] struct {
	Message       string
	Options       []SelectOption[T]
	InitialValues []T
	MaxItems      *int
}

MultiSelectOptions configures the MultiSelect prompt. I/O fields are managed by tap.

type PasswordOptions

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

PasswordOptions configures the Password prompt. I/O fields are managed by tap.

type Progress added in v0.4.0

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

Progress wraps a progress bar and ensures terminal cleanup on Stop.

func NewProgress

func NewProgress(opts ProgressOptions) *Progress

NewProgress creates a progress bar bound to a terminal writer (or the override writer set via SetTermIO in tests). The underlying terminal, when created, is cleaned up on Stop.

func (*Progress) Advance added in v0.4.0

func (p *Progress) Advance(step int, msg string)

Advance moves the progress bar forward by step and updates the message.

func (*Progress) Message added in v0.4.0

func (p *Progress) Message(msg string)

Message updates the progress bar message.

func (*Progress) Start added in v0.4.0

func (p *Progress) Start(msg string)

Start begins the progress bar with an initial message.

func (*Progress) Stop added in v0.4.0

func (p *Progress) Stop(msg string, code int)

Stop stops the progress bar with a final message and exit code (0=success, 1=cancel, >1=error).

type ProgressOptions

type ProgressOptions struct {
	Style string
	Max   int
	Size  int
}

ProgressOptions configures a progress bar. Output is managed by tap.

type SelectOption

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

SelectOption represents a selectable item with a typed value, label, and optional hint for display.

type SelectOptions

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

SelectOptions configures the Select prompt. I/O fields are managed by tap.

type Spinner added in v0.4.0

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

Spinner wraps a spinner and ensures terminal cleanup on Stop.

func NewSpinner

func NewSpinner(opts SpinnerOptions) *Spinner

NewSpinner creates a spinner bound to a terminal writer (or the override writer set via SetTermIO in tests). The underlying terminal, when created, is cleaned up on Stop.

func (*Spinner) IsCanceled added in v0.4.0

func (s *Spinner) IsCanceled() bool

IsCanceled reports whether the spinner was canceled by the user.

func (*Spinner) IsCancelled added in v0.4.0

func (s *Spinner) IsCancelled() bool

IsCancelled reports whether the spinner was cancelled by the user. Deprecated: Use IsCanceled for Go-idiomatic spelling.

func (*Spinner) Message added in v0.4.0

func (s *Spinner) Message(msg string)

Message updates the spinner message.

func (*Spinner) Start added in v0.4.0

func (s *Spinner) Start(msg string)

Start begins the spinner with an initial message.

func (*Spinner) Stop added in v0.4.0

func (s *Spinner) Stop(msg string, code int)

Stop stops the spinner with a final message and exit code (0=success, 1=cancel, >1=error).

type SpinnerOptions

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

SpinnerOptions configures a spinner. Output is managed by tap.

type Stream added in v0.6.0

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

Stream wraps a styled live stream renderer and ensures terminal cleanup on Stop.

func NewStream added in v0.6.0

func NewStream(opts StreamOptions) *Stream

NewStream creates a live stream bound to a terminal writer (or override), and ensures the underlying terminal is closed on Stop.

func (*Stream) Pipe added in v0.6.0

func (s *Stream) Pipe(r io.Reader)

Pipe reads from r line-by-line and writes to the stream.

func (*Stream) Start added in v0.6.0

func (s *Stream) Start(msg string)

Start prints the stream header and prepares to receive lines.

func (*Stream) Stop added in v0.6.0

func (s *Stream) Stop(msg string, code int)

Stop finalizes the stream with a status symbol and code (0=success, 1=cancel, >1=error).

func (*Stream) WriteLine added in v0.6.0

func (s *Stream) WriteLine(line string)

WriteLine appends a single line to the stream area.

type StreamOptions added in v0.6.0

type StreamOptions struct {
	ShowTimer bool
}

StreamOptions configures a live output stream. Output is managed by tap.

type TextOptions

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

TextOptions configures the Text prompt. I/O fields are managed by tap.

Directories

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

Jump to

Keyboard shortcuts

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