go get github.com/pt-main/tapTap is a lightweight library for building beautiful CLI applications in Go.
It features a simple command-based API, automatic --flag parsing, colored output, interactive keyboard dialogs, in-place terminal rewriting, and fully customisable help messages.
- Commands with required / optional arguments and unlimited trailing args
- Flag parsing -
--flag,--flag=value,--flag:value - Built‑in colour support - shortcodes like
[?GN],[?RD],[?YW]- easy and readable - Background colours & colour stack - advanced ANSI control for rich layouts
- Auto‑generated help - groups aliases, shows arguments, respects custom format
- Fully configurable - change the look of
helpviaParserConfig, add your colors tocolor.Colors, disable colors withcolor.ColorEnabled = false - Hide commands from help using
DONT_SHOWdocstring - Verbose / debug flags - built‑in
--verboseand--debugwith conditional printing - Interactive dialogs - selection menus (using input/selecting)
- In-place terminal rewriting - update previous output without scrolling
Create a simple CLI with a hello command:
package main
import (
"fmt"
"github.com/pt-main/tap"
"github.com/pt-main/tap/color"
)
func helloHandler(p *tap.Parser, args []string) error {
color.PrintlnColored("[?GN]Hello[?RT], world! Args: %v", args)
return nil
}
func main() {
cfg := tap.NewParserConfig("", "", "", "", "", "") // defaults
p := tap.NewParser("demo", "Demo CLI v1.0", nil, cfg)
p.AddCommand("hello", helloHandler, "Prints a friendly greeting", nil, nil, true)
if err := p.Main(); err != nil {
fmt.Println("Error:", err)
}
}Add a command using AddCommand:
p.AddCommand(
name string,
handler HandlerFuncType, // func(*Parser, []string) error
docstring string,
requiredArgs []string,
optionalArgs []string,
unlimitedMaxArgs bool,
)- requiredArgs - shown as
<arg>in help. The command fails if they are missing. - optionalArgs - shown as
[arg]in help. - unlimitedMaxArgs - if
true, the command accepts any number of trailing arguments. shown as...in help.
You can create aliases for existing commands using AddAlias:
p.AddCommand("help", helpHandler, tap.HELP_DOCS, nil, nil, false)
p.AddAlias("h", "help") // now "h" works exactly like "help"The alias inherits all properties (handler, arguments, docstring) from the original command.
p.AddCommand("copy",
copyHandler,
"Copy source to destination",
[]string{"src", "dst"}, // required
[]string{"force"}, // optional
true,
)Help output would show:
copy <src>, <dst>, [force]...
Flags are written as --flag or --key=value (also --key:value).
They are parsed automatically and stored in p.Flags (a map[string]string). A flag without a value gets an empty string.
Built‑in flags:
--verbose- enables verbose output (messages printed withp.Print("verbose", ...))--debug- enables debug output (similar)
Your handlers can read flags directly:
func myHandler(p *tap.Parser, args []string) error {
if val, ok := p.Flags["output"]; ok {
fmt.Println("Output file:", val)
}
return nil
}Use p.Print(flag, format, ...) to output messages only when a specific flag (e.g., --verbose or --debug) is present:
func myHandler(p *tap.Parser, args []string) error {
p.Print("verbose", "Starting with args: %v", args)
p.Print("debug", "Debug info...")
// ...
}The output is automatically prefixed with the flag name and coloured.
You can just write [?COLOR] with uppercased color name from list to set color. Like [?RED] for red.
All colors: Bold, Underline, Reset, Black, Red, Green, Yellow, Blue, Magenta, Cyan.
Also you can set color using first and last letters of color. Like [?RD] for red.
Bright variants: [?BRED], [?BRD], ...
Use them with color.PrintlnColored or color.PrintColored:
color.PrintlnColored("[?GN]Success[?RT] - file saved as [?YW]%s[?RT]", filename)To disable colours globally:
color.ColorEnabled = falseYou can set color to string with color.Set:
text := color.Set("[?RD]Test")(Reset will be auto pasted in the end of text)
Background colours use the BACK prefix or BK shortcode:
color.PrintlnColored("[?BKRD] ERROR: [?RT] error text...")Available all colors for text except bold and underlinne.
You can restore the previous colour with [?<] (or [?BACK]) and clear the stack with [?SRT] (or [?SRESET]):
color.PrintlnColored("[?BE][?UE]test [?BD]bold [?RT][?<]restored")Tap includes a small utility for interactive user prompts. It supports arrow-key navigation and validated text input.
import "github.com/pt-main/tap/utils"
// Arrow-key selection
dialogue := utils.NewDialogue(utils.ArrowsDialogueType, "[?CN]Select action:[?RT]")
choice, err := dialogue.Run([]string{"install", "update", "remove"})
// Navigate with up/down, press enter to confirm, esc to cancel.For simple typed input with validation against allowed variants:
inputDlg := utils.NewDialogue(utils.InputDialogueType, "Enter mode (fast/slow): ")
mode, err := inputDlg.Run([]string{"fast", "slow"})For in-place updates (progress bars, live menus, spinners) use Rewriter:
rw := utils.NewRewriter()
rw.Write("[?YW]Loading... 0%[?RT]")
// ... later ...
rw.Write("[?GN]Loading... 100%[?RT]") // replaces the previous line without scrollingCreate a ParserConfig and pass it to NewParser.
All fields support format strings - use %s for the command name or argument list.
cfg := tap.NewParserConfig(
"[?CN]>>> Command [?RT]%s[?CN] <<<[?RT]",
"[?CN]Args:[?RT]",
" %s",
"[?CN]Description:[?RT]",
" %s",
"[?CN]---[?RT]",
)
p := tap.NewParser("mycli", "My tool", nil, cfg)If you pass an empty string for any field, the default (coloured, nice looking) will be used.
If multiple commands share the same docstring, they are displayed together in help:
helpDocs := "Show help"
p.AddCommand("help", helpHandler, helpDocd, nil, nil, false)
p.AddCommand("h", helpHandler, helpDocd, nil, nil, false) // Equals to p.AddAlias("h", help)Help shows: [help / h]
Use tap.DONT_SHOW as the docstring:
p.AddCommand("internal", internalHandler, tap.DONT_SHOW, nil, nil, false)This command will work but will never appear in the help output.
A minimal but complete CLI with multiple commands:
package main
import (
"fmt"
"os"
"github.com/pt-main/tap"
"github.com/pt-main/tap/color"
)
func main() {
cfg := tap.NewParserConfig("", "", "", "", "", "")
p := tap.NewParser("myapp", "My application v0.1", nil, cfg)
p.AddCommand("greet", func(p *tap.Parser, args []string) error {
name := "world"
if len(args) > 0 {
name = args[0]
}
color.PrintlnColored("[?GN]Hello, %s![?RT]", name)
return nil
}, "Say hello", []string{}, []string{"name"}, false)
p.AddCommand("print", func(p *tap.Parser, args []string) error {
color.PrintlnColored("[?YW]%s[?RT]", args[0])
return nil
}, "Print first argument", []string{"text"}, nil, false)
if err := p.Main(); err != nil {
fmt.Fprintln(os.Stderr, "Fatal:", err)
os.Exit(1)
}
}MIT - see LICENSE file.
