{"id":30769522,"url":"https://github.com/yarlson/tap","last_synced_at":"2025-12-25T14:09:16.602Z","repository":{"id":310959756,"uuid":"1040517725","full_name":"yarlson/tap","owner":"yarlson","description":"Beautiful, interactive command-line prompts for Go ","archived":false,"fork":false,"pushed_at":"2025-09-01T07:49:41.000Z","size":6298,"stargazers_count":28,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-01T09:58:40.659Z","etag":null,"topics":["cli","command-line-app","command-line-tool","go","golang","golang-library","prompt","prompts"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yarlson.png","metadata":{"files":{"readme":"README.LLM.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-19T05:26:00.000Z","updated_at":"2025-09-01T07:49:20.000Z","dependencies_parsed_at":"2025-09-01T09:20:45.323Z","dependency_job_id":null,"html_url":"https://github.com/yarlson/tap","commit_stats":null,"previous_names":["yarlson/tap"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/yarlson/tap","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yarlson%2Ftap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yarlson%2Ftap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yarlson%2Ftap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yarlson%2Ftap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yarlson","download_url":"https://codeload.github.com/yarlson/tap/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yarlson%2Ftap/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273685177,"owners_count":25149719,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-04T02:00:08.968Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","command-line-app","command-line-tool","go","golang","golang-library","prompt","prompts"],"created_at":"2025-09-04T22:37:23.196Z","updated_at":"2025-12-25T14:09:16.595Z","avatar_url":"https://github.com/yarlson.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tap: LLM Usage Guide\n\nThis document gives language models a compact, precise map of Tap’s public API so you can confidently generate working code that uses this library.\n\nTap is a Go library for building interactive, clack-style terminal prompts (text/password/confirm/select/multiselect), spinners, progress bars, streams, and message boxes.\n\n- Module path: `github.com/yarlson/tap`\n- Runtime model: each helper opens a terminal internally, runs, and restores the TTY\n- Return values are typed (not `any`) and cancel maps to a sensible zero value\n- A small test-only override is available to inject I/O without a real terminal\n\n## Import\n\n```go\nimport (\n  \"context\"\n  \"fmt\"\n  \"github.com/yarlson/tap\"\n)\n```\n\n## Core helpers and types\n\nAll helpers create and close a terminal per call, unless I/O is overridden in tests.\n\n- `func tap.Text(ctx context.Context, opts tap.TextOptions) string`\n  - Options:\n    - `Message string`\n    - `Placeholder string`\n    - `DefaultValue string`\n    - `InitialValue string`\n    - `Validate func(string) error` (return non-nil to block submission and show error)\n    - `Input tap.Reader` (optional)\n    - `Output tap.Writer` (optional)\n\n- `func tap.Autocomplete(ctx context.Context, opts tap.AutocompleteOptions) string`\n  - Options:\n    - `Message string`\n    - `Placeholder string`\n    - `DefaultValue string`\n    - `InitialValue string`\n    - `Validate func(string) error`\n    - `Suggest func(string) []string` (return candidate suggestions for current input)\n    - `MaxResults int` (max suggestions to display; default 5)\n    - `Input tap.Reader` (optional)\n    - `Output tap.Writer` (optional)\n  - Behavior:\n    - Arrow Up/Down navigates suggestions; `Tab` accepts the highlighted suggestion\n    - `Enter` submits the current input (or accepted suggestion)\n    - `Ctrl+C`/`Esc` cancel and return an empty string\n\n- `func tap.Password(ctx context.Context, opts tap.PasswordOptions) string`\n  - Same options as `TextOptions` (input is masked in the UI)\n  - Includes `Input tap.Reader` and `Output tap.Writer`\n\n- `func tap.Confirm(ctx context.Context, opts tap.ConfirmOptions) bool`\n  - Options:\n    - `Message string`\n    - `Active string` (label for true)\n    - `Inactive string` (label for false)\n    - `InitialValue bool`\n    - `Input tap.Reader`\n    - `Output tap.Writer`\n\n- `type tap.SelectOption[T any] struct { Value T; Label, Hint string }`\n- `type tap.SelectOptions[T any] struct { Message string; Options []tap.SelectOption[T]; InitialValue *T; MaxItems *int; Input tap.Reader; Output tap.Writer }`\n- `func tap.Select[T any](ctx context.Context, opts tap.SelectOptions[T]) T`\n\n- `type tap.MultiSelectOptions[T any] struct { Message string; Options []tap.SelectOption[T]; InitialValues []T; MaxItems *int; Input tap.Reader; Output tap.Writer }`\n- `func tap.MultiSelect[T any](ctx context.Context, opts tap.MultiSelectOptions[T]) []T`\n\n- Spinner\n  - `type tap.SpinnerOptions struct { Indicator string; Frames []string; Delay time.Duration; Output tap.Writer; CancelMessage, ErrorMessage string }`\n  - `type tap.Spinner struct { /* unexported */ }`\n  - `func tap.NewSpinner(opts tap.SpinnerOptions) *tap.Spinner`\n  - `func (s *tap.Spinner) Start(msg string)`\n  - `func (s *tap.Spinner) Message(msg string)`\n  - `func (s *tap.Spinner) Stop(msg string, code int)` // 0=success, 1=cancel, \u003e1=error\n  - `func (s *tap.Spinner) IsCancelled() bool`\n\n- Progress\n  - `type tap.ProgressOptions struct { Style string; Max, Size int; Output tap.Writer }`\n  - `type tap.Progress struct { /* unexported */ }`\n  - `func tap.NewProgress(opts tap.ProgressOptions) *tap.Progress`\n  - `func (p *tap.Progress) Start(msg string)`\n  - `func (p *tap.Progress) Advance(step int, msg string)`\n  - `func (p *tap.Progress) Message(msg string)`\n  - `func (p *tap.Progress) Stop(msg string, code int)` // 0=success, 1=cancel, \u003e1=error\n\n- Stream (live output)\n  - `type tap.StreamOptions struct { ShowTimer bool; Output tap.Writer }`\n  - `type tap.Stream struct { /* unexported */ }`\n  - `func tap.NewStream(opts tap.StreamOptions) *tap.Stream`\n  - `func (s *tap.Stream) Start(msg string)`\n  - `func (s *tap.Stream) WriteLine(line string)`\n  - `func (s *tap.Stream) Pipe(r io.Reader)`\n  - `func (s *tap.Stream) Stop(msg string, code int)` // 0=success, 1=cancel, \u003e1=error\n\n- Messages, Box, and Table\n  - `func tap.Intro(title string)`\n  - `func tap.Outro(message string)`\n  - `func tap.Message(message string)`\n  - `type tap.BoxOptions struct { Columns int; WidthFraction float64; WidthAuto bool; TitlePadding, ContentPadding int; TitleAlign, ContentAlign tap.BoxAlignment; Rounded, IncludePrefix bool; FormatBorder func(string) string }`\n  - `func tap.Box(message string, title string, opts tap.BoxOptions)`\n  - `func tap.GrayBorder(s string) string`\n  - `func tap.CyanBorder(s string) string`\n  - `type tap.TableOptions struct { Output tap.Writer; ShowBorders, IncludePrefix bool; MaxWidth int; ColumnAlignments []tap.TableAlignment; HeaderStyle tap.TableStyle; HeaderColor tap.TableColor; FormatBorder func(string) string }`\n  - `func tap.Table(headers []string, rows [][]string, opts tap.TableOptions)`\n\n## Behavior and conventions\n\n- **Context Support**\n  - All interactive prompt functions (Text, Password, Confirm, Select, MultiSelect) require a `context.Context` as the first parameter\n  - Use `context.Background()` for basic usage, or pass custom contexts for cancellation/timeouts\n  - If context is cancelled, prompts return zero values (empty string, false, etc.)\n\n- **Typed returns**\n  - `Text`/`Password` → `string`\n  - `Confirm` → `bool`\n  - `Select[T]` → `T`\n  - `MultiSelect[T]` → `[]T`\n  - If the user cancels, helpers return a reasonable zero value (`\"\"`, `false`, `var zero T`).\n\n- **Keybindings**\n  - Navigate: Arrow keys or `h`/`j`/`k`/`l`\n  - Submit: `Enter`\n  - Cancel: `Ctrl+C` or `Esc`\n  - Toggle (MultiSelect): `Space`\n  - Accept suggestion (Autocomplete): `Tab`\n\n- **Validation errors** (Text/Password)\n  - Provide `Validate: func(string) error { ... }`\n  - If non-nil error is returned on submit, the prompt stays active and shows a yellow error line below the input:\n    - Yellow left bar for the input line\n    - Yellow bottom-left corner (└) for the error line and a yellow error message\n\n- **Lifecycle**\n  - Helpers open and close a terminal per call; you do not need to manage sessions\n  - `Spinner`/`Progress` create a terminal under the hood and close it on `Stop` (unless I/O is overridden in tests)\n\n## Basic usage\n\n```go\nctx := context.Background()\nname := tap.Text(ctx, tap.TextOptions{Message: \"What is your name?\"})\nlang := tap.Text(ctx, tap.TextOptions{Message: fmt.Sprintf(\"Hi %s! Favorite language?\", name)})\nproceed := tap.Confirm(ctx, tap.ConfirmOptions{Message: \"Proceed?\", InitialValue: true})\nif proceed {\n  tap.Outro(\"Let's go! 🎉\")\n}\n```\n\n### Validation\n\n```go\nemail := tap.Text(ctx, tap.TextOptions{\n  Message: \"Enter email:\",\n  Validate: func(s string) error {\n    if !strings.Contains(s, \"@\") { return errors.New(\"please enter a valid email\") }\n    return nil\n  },\n})\n```\n\n### Autocomplete\n\n```go\n// Suggest function to filter a static list\nsuggest := func(input string) []string {\n  all := []string{\"Go\", \"Golang\", \"Python\", \"Rust\", \"Java\"}\n  if input == \"\" { return all }\n  low := strings.ToLower(input)\n  var out []string\n  for _, s := range all {\n    if strings.Contains(strings.ToLower(s), low) {\n      out = append(out, s)\n    }\n  }\n  return out\n}\n\nval := tap.Autocomplete(ctx, tap.AutocompleteOptions{\n  Message:     \"Search language:\",\n  Placeholder: \"Start typing...\",\n  Suggest:     suggest,\n  MaxResults:  6,\n})\n```\n\n### Select with typed values\n\n```go\ntype Env string\nenv := tap.Select(ctx, tap.SelectOptions[Env]{\n  Message: \"Choose environment:\",\n  Options: []tap.SelectOption[Env]{\n    {Value: \"dev\", Label: \"Development\", Hint: \"Local\"},\n    {Value: \"prod\", Label: \"Production\", Hint: \"Live\"},\n  },\n})\n```\n\n### Multi-Select with typed values\n\n```go\nlangs := tap.MultiSelect(ctx, tap.MultiSelectOptions[string]{\n  Message: \"Choose languages:\",\n  Options: []tap.SelectOption[string]{\n    {Value: \"go\", Label: \"Go\"},\n    {Value: \"py\", Label: \"Python\"},\n    {Value: \"js\", Label: \"JavaScript\"},\n  },\n  InitialValues: []string{\"go\"},\n})\nfmt.Println(langs) // []string{\"go\", ...}\n```\n\n### Spinner/Progress/Stream\n\n```go\nsp := tap.NewSpinner(tap.SpinnerOptions{Indicator: \"dots\"})\nsp.Start(\"Connecting\")\n// ... do work ...\nsp.Stop(\"Connected\", 0)\n\npr := tap.NewProgress(tap.ProgressOptions{Style: \"heavy\", Max: 100, Size: 40})\npr.Start(\"Processing…\")\nfor i := 0; i \u003c 10; i++ { pr.Advance(10, fmt.Sprintf(\"%d/10\", i+1)) }\npr.Stop(\"Complete\", 0)\n\nst := tap.NewStream(tap.StreamOptions{ShowTimer: true})\nst.Start(\"Building project\")\nst.WriteLine(\"step 1: deps\")\nst.WriteLine(\"step 2: compile\")\nst.Stop(\"Done\", 0)\n```\n\n### Messages\n\n```go\ntap.Message(\"Here's a summary table:\")\n```\n\n### Table\n\n```go\nheaders := []string{\"Field\", \"Value\"}\nrows := [][]string{{\"Name\", name}, {\"Languages\", strings.Join(langs, \", \")}}\n\ntap.Table(headers, rows, tap.TableOptions{\n  ShowBorders:   true,\n  IncludePrefix: true,\n  HeaderStyle:   tap.TableStyleBold,\n  HeaderColor:   tap.TableColorCyan,\n})\n```\n\n## Testing (override terminal I/O)\n\nIn tests, route I/O to mocks:\n\n```go\nin := tap.NewMockReadable()\nout := tap.NewMockWritable()\n\ntap.SetTermIO(in, out)\ndefer tap.SetTermIO(nil, nil)\n\n_ = tap.Text(ctx, tap.TextOptions{Message: \"Your name:\"})\n// feed input\nin.EmitKeypress(\"A\", tap.Key{Name: \"a\"})\nin.EmitKeypress(\"\", tap.Key{Name: \"return\"})\n// assert frames in out.Buffer\n```\n\nAlternatively, pass per-call I/O via options: set `Input` and `Output` on the options (e.g., `TextOptions`, `ConfirmOptions`) to avoid global overrides.\n\n## Gotchas and guidance for code generation\n\n- For `Select[T]`, supply `[]tap.SelectOption[T]`. Labels/hints are optional.\n- Always call `Stop` on `Spinner`/`Progress`/`Stream` to restore the terminal when used.\n- For validation, return `error` (not `bool`) from the provided function; non-nil blocks submit and shows an error line.\n- The library uses ANSI; ensure output is sent to a TTY when running examples.\n- Use `context.Background()` for basic usage, or create custom contexts for cancellation/timeout behavior.\n- Generics inference: you can omit type parameters on `Select`/`MultiSelect` if the compiler can infer them from `SelectOptions[T]`/`MultiSelectOptions[T]`.\n\n## Minimal end-to-end example\n\n```go\npackage main\n\nimport (\n  \"context\"\n  \"fmt\"\n  \"strings\"\n  \"errors\"\n  \"github.com/yarlson/tap\"\n)\n\nfunc main() {\n  ctx := context.Background()\n  name := tap.Text(ctx, tap.TextOptions{Message: \"Name:\"})\n  email := tap.Text(ctx, tap.TextOptions{\n    Message: \"Email:\",\n    Validate: func(s string) error {\n      if !strings.Contains(s, \"@\") { return errors.New(\"please enter a valid email\") }\n      return nil\n    },\n  })\n  ok := tap.Confirm(ctx, tap.ConfirmOptions{Message: fmt.Sprintf(\"Submit for %s?\", name)})\n  if ok { tap.Outro(fmt.Sprintf(\"Saved %s\", email)) }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyarlson%2Ftap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyarlson%2Ftap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyarlson%2Ftap/lists"}