Skip to content

indaco/prompti

Repository files navigation

prompti

Interactive TUI prompts for Go CLI applications, powered by Charm.

CI Code coverage Go Report Card Security Scan version Go Reference License Built with Devbox

Note This project was previously hosted at github.com/sveltinio/prompti. The repository has been transferred to github.com/indaco/prompti under the same maintainer.

prompti is a collection of interactive TUI prompt components for Go CLI applications, built on the Charm ecosystem (bubbletea, bubbles, lipgloss).

Features

  • 6 prompt types: input, choose, confirm, toggle, detail, progressbar
  • Customizable styles: every component accepts a Styles struct for full visual control
  • Sentinel errors: distinguish user cancellation (ErrCancelled) from empty input (ErrEmpty)
  • Built-in validation: ready-to-use validators for alphanumeric, digits, integers, floats, email, and URL

Install

Requires Go 1.25 or higher.

go get github.com/indaco/prompti@latest

Prompts

Input

Preview Input example

input is a text input prompt supporting default values, validation (type ValidateFunc func(string) error), password echo mode, and customizable styles.

Built-in validation rules:

  • alphanumeric
  • digits only
  • integers
  • floats
  • email address
  • URL

Default

package main

import (
	"fmt"

	"github.com/indaco/prompti/input"
)

func main() {
	questionPrompt := &input.Config{
		Message:     "What's the name of your project?",
		Placeholder: "Please, provide a name for your project",
		ErrorMsg:    "Project name is mandatory",
	}

	result, _ := input.Run(questionPrompt)
	fmt.Println(result)
}

Default value and validation

Preview Input initial value example
package main

import (
	"fmt"

	"github.com/indaco/prompti/input"
)

func main() {
	questionPrompt := &input.Config{
		Message:      "What's your lucky number?",
		Placeholder:  "Please, tell me your lucky number",
		Initial:      "23",
		ErrorMsg:     "Cannot be blank",
		ValidateFunc: input.ValidateInteger,
	}

	result, _ := input.Run(questionPrompt)
	fmt.Println(result)
}

Custom styles

Preview Input custom styles example
package main

import (
	"fmt"

	"charm.land/lipgloss/v2"
	"charm.land/lipgloss/v2/compat"
	"github.com/indaco/prompti/input"
)

func main() {
	questionPrompt := &input.Config{
		Message:     "What's the name of your project?",
		Placeholder: "Please, provide a name for your project",
		ErrorMsg:    "Project name is mandatory",
		Styles: input.Styles{
			PrefixIcon:      "*",
			PrefixIconColor: compat.AdaptiveColor{Light: lipgloss.Color("#ef4444"), Dark: lipgloss.Color("#ef4444")},
			PlaceholderStyle: lipgloss.NewStyle().
				Background(compat.AdaptiveColor{Light: lipgloss.Color("#3b82f6"), Dark: lipgloss.Color("#3b82f6")}).
				Foreground(compat.AdaptiveColor{Light: lipgloss.Color("#fde68a"), Dark: lipgloss.Color("#fffbeb")}),
		},
	}

	result, _ := input.Run(questionPrompt)
	fmt.Println(result)
}

Email

package main

import (
	"fmt"

	"github.com/indaco/prompti/input"
)

func main() {
	questionPrompt := &input.Config{
		Message:      "What's your email address?",
		Placeholder:  "Please, provide an email address",
		ErrorMsg:     "Email is mandatory",
		ValidateFunc: input.ValidateEmail,
	}

	result, _ := input.Run(questionPrompt)
	fmt.Println(result)
}

Password

package main

import (
	"fmt"

	"github.com/indaco/prompti/input"
)

func main() {
	passwordPrompt := &input.Config{
		Message:     "What's  your password?",
		Placeholder: "Please, provide your password",
		ErrorMsg:    "Password is mandatory",
		Password:    true,
	}

	result, _ := input.Run(passwordPrompt)
	fmt.Println(result)
}

Choose

Preview Choose example

choose is a single-select list prompt for browsing a set of items.

Default

package main

import (
	"fmt"

	"github.com/indaco/prompti/choose"
)

func main() {
	foodSelectionPrompt := &choose.Config{
		Title:    "What do you wanna eat tonight?",
		ErrorMsg: "Please, select your meal.",
	}

	entries := []choose.Item{
		{Name: "pizza", Desc: "It's always pizza time!"},
		{Name: "kebab", Desc: "I feel turkish today, kebab!"},
		{Name: "carbonara", Desc: "Carbonara, NO cream, please!"},
	}

	result, _ := choose.Run(foodSelectionPrompt, entries)
	fmt.Println(result)
}

Custom styles

Preview Choose custom styles example
package main

import (
	"fmt"

	"charm.land/lipgloss/v2"
	"charm.land/lipgloss/v2/compat"
	"github.com/indaco/prompti/choose"
)

var (
	amber  = compat.AdaptiveColor{Light: lipgloss.Color("#f59e0b"), Dark: lipgloss.Color("#fbbf24")}
	purple = compat.AdaptiveColor{Light: lipgloss.Color("#7e22ce"), Dark: lipgloss.Color("#a855f7")}
	green  = compat.AdaptiveColor{Light: lipgloss.Color("#166534"), Dark: lipgloss.Color("#22c55e")}

	myCustomStyle = choose.Styles{
		PrefixIcon:        "★",
		TitleStyle:        lipgloss.NewStyle().Background(green).Foreground(purple).Padding(0, 1),
		TitleBarStyle:     lipgloss.NewStyle(),
		ItemIcon:          "#",
		ItemStyle:         lipgloss.NewStyle().Foreground(amber),
		SelectedItemStyle: lipgloss.NewStyle().Foreground(purple),
	}
)

func main() {
	foodSelectionPrompt := &choose.Config{
		Title:    "What do you wanna eat tonight?",
		ErrorMsg: "Please, select your meal.",
		ShowHelp: true,
		Styles:   myCustomStyle,
	}

	entries := []choose.Item{
		{Name: "pizza", Desc: "It's always pizza time!"},
		{Name: "kebab", Desc: "I feel turkish today, kebab!"},
		{Name: "carbonara", Desc: "Carbonara, NO cream, please!"},
	}

	result, _ := choose.Run(foodSelectionPrompt, entries)
	fmt.Println(result)
}

Confirm

Preview Confirm example

confirm is a yes/no confirmation dialog rendered in a styled box.

Default

package main

import (
	"fmt"

	"github.com/indaco/prompti/confirm"
)

func main() {
	result, _ := confirm.Run(&confirm.Config{Question: "Continue?"})
	fmt.Println(result)
}

Custom styles

Preview Confirm custom styles example
package main

import (
	"fmt"

	"charm.land/lipgloss/v2"
	"charm.land/lipgloss/v2/compat"
	"github.com/indaco/prompti/confirm"
)

var (
	cyan  = compat.AdaptiveColor{Light: lipgloss.Color("#4f46e5"), Dark: lipgloss.Color("#c7d2fe")}
	green = compat.AdaptiveColor{Light: lipgloss.Color("#166534"), Dark: lipgloss.Color("#22c55e")}

	infoText = `Lorem ipsum dolor sit amet,
consectetur adipiscing elit %s...`

	Green   = lipgloss.NewStyle().Foreground(green).Render
	message = fmt.Sprintf(infoText, Green("elit"))

	myCustomStyle = confirm.Styles{
		Width:       60,
		BorderColor: cyan,
	}

	confirmConfig = &confirm.Config{
		Message:  message,
		Question: "Continue?",
		Styles:   myCustomStyle,
	}
)

func main() {
	result, _ := confirm.Run(confirmConfig)
	fmt.Println(result)
}

Toggle

Preview Toggle example

toggle is an inline yes/no prompt. It works like confirm but renders the options inline rather than in a box.

Default

package main

import (
	"fmt"

	"github.com/indaco/prompti/toggle"
)

func main() {
	result, _ := toggle.Run(&toggle.Config{Question: "Continue?"})
	fmt.Println(result)
}

Custom styles

Preview Toggle custom styles example
package main

import (
	"fmt"

	"charm.land/lipgloss/v2"
	"charm.land/lipgloss/v2/compat"
	"github.com/indaco/prompti/toggle"
)

var (
	cyan   = compat.AdaptiveColor{Light: lipgloss.Color("#4f46e5"), Dark: lipgloss.Color("#c7d2fe")}
	green  = compat.AdaptiveColor{Light: lipgloss.Color("#166534"), Dark: lipgloss.Color("#22c55e")}
	red    = compat.AdaptiveColor{Light: lipgloss.Color("#ef4444"), Dark: lipgloss.Color("#ef4444")}
	purple = compat.AdaptiveColor{Light: lipgloss.Color("#7e22ce"), Dark: lipgloss.Color("#a855f7")}

	myCustomStyle = toggle.Styles{
		PrefixIcon:        "★",
		PrefixIconColor:   red,
		DialogStyle:       lipgloss.NewStyle().Margin(1, 0),
		ButtonStyle:       lipgloss.NewStyle().Bold(true).Foreground(cyan),
		ActiveButtonStyle: lipgloss.NewStyle().Foreground(green),
	}

	toggleConfig = &toggle.Config{
		Question:          "How do you feel?",
		OkButtonLabel:     "I'm super ok",
		CancelButtonLabel: "Next question, please!",
		Divider:           "|",
		Styles:            myCustomStyle,
	}
)

func main() {
	result, _ := toggle.Run(toggleConfig)
	fmt.Println(result)
}

Detail

Preview Detail example

detail is a collapsible detail/summary section, similar to the HTML <details> element. It displays a summary line with an expand/collapse indicator and toggles content visibility on user interaction.

Default

package main

import (
	"fmt"

	"github.com/indaco/prompti/detail"
)

func main() {
	expanded, err := detail.Run(&detail.Config{
		Summary: "What is prompti?",
		Content: "prompti is a collection of interactive terminal UI prompts\nbuilt on the charmbracelet bubbletea framework.",
	})
	fmt.Println("expanded:", expanded, "err:", err)
}

Custom indicators

package main

import (
	"fmt"

	"github.com/indaco/prompti/detail"
)

var detailConfig = &detail.Config{
	Summary:            "Configuration Options",
	Content:            "verbose: true\nlog_level: debug\nmax_retries: 3",
	CollapsedIndicator: "[+]",
	ExpandedIndicator:  "[-]",
}

func main() {
	expanded, _ := detail.Run(detailConfig)
	fmt.Println("expanded:", expanded)
}

Custom styles

Preview Detail custom styles example
package main

import (
	"fmt"

	"charm.land/lipgloss/v2"
	"charm.land/lipgloss/v2/compat"
	"github.com/indaco/prompti/detail"
)

var (
	cyan   = compat.AdaptiveColor{Light: lipgloss.Color("#4f46e5"), Dark: lipgloss.Color("#c7d2fe")}
	green  = compat.AdaptiveColor{Light: lipgloss.Color("#166534"), Dark: lipgloss.Color("#22c55e")}
	red    = compat.AdaptiveColor{Light: lipgloss.Color("#ef4444"), Dark: lipgloss.Color("#ef4444")}
	purple = compat.AdaptiveColor{Light: lipgloss.Color("#7e22ce"), Dark: lipgloss.Color("#a855f7")}

	myCustomStyle = detail.Styles{
		PrefixIcon:      "★",
		PrefixIconColor: red,
		SummaryStyle:    lipgloss.NewStyle().Bold(true).Foreground(purple).PaddingRight(1),
		IndicatorStyle:  lipgloss.NewStyle().Foreground(green).PaddingRight(1),
		ContentStyle:    lipgloss.NewStyle().Foreground(cyan).PaddingLeft(3).MarginTop(1),
		DialogStyle:     lipgloss.NewStyle().Margin(1, 0),
	}

	detailConfig = &detail.Config{
		Summary: "Release Notes v0.3.0",
		Content: "- Added detail/summary component\n- Improved theme system\n- Bug fixes and performance improvements",
		Styles:  myCustomStyle,
	}
)

func main() {
	expanded, _ := detail.Run(detailConfig)
	fmt.Println("expanded:", expanded)
}

Multiline content

package main

import (
	"fmt"

	"github.com/indaco/prompti/detail"
)

var detailConfig = &detail.Config{
	Summary: "Error Details",
	Content: `Status:  503 Service Unavailable
Endpoint: /api/v1/users
Trace ID: abc-123-def-456

The upstream service did not respond within
the configured timeout of 30 seconds.

Suggested actions:
  1. Check service health dashboard
  2. Verify network connectivity
  3. Review recent deployments`,
}

func main() {
	expanded, _ := detail.Run(detailConfig)
	fmt.Println("expanded:", expanded)
}

ProgressBar

Preview ProgressBar example

progressbar is an animated progress meter that iterates over a list of items.

Default

package main

import (
	"fmt"
	"os"

	"github.com/indaco/prompti/progressbar"
)

func main() {
	fruits := []string{
		"apple",
		"banana",
		"orange",
		"grapes",
		"mellon",
		"strawberry",
		"mango",
		"lemon",
		"apricot",
		"peach",
		"papaya",
		"kiwi",
		"pear",
		"guava",
		"almond",
		"coconut",
		"blackberry",
		"cherry",
		"grapes",
	}

	pbConfig := &progressbar.Config{Items: fruits}

	if err := progressbar.Run(pbConfig); err != nil {
		fmt.Println("error running program:", err)
		os.Exit(1)
	}
}

Custom styles

Preview ProgressBar custom styles example
package main

import (
	"fmt"
	"os"

	"charm.land/lipgloss/v2"
	"github.com/indaco/prompti/progressbar"
)

func main() {
	fruits := []string{
		"apple",
		"banana",
		"orange",
		"grapes",
		"mellon",
		"strawberry",
		"mango",
		"lemon",
		"apricot",
		"peach",
		"papaya",
		"kiwi",
		"pear",
		"guava",
		"almond",
		"coconut",
		"blackberry",
		"cherry",
		"grapes",
	}

	pbConfig := &progressbar.Config{
		Items:         fruits,
		OnProgressMsg: "Eating:",
		Styles: progressbar.Styles{
			CurrentItemStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("411")),
			ShowLabel:        true,
			GradientFrom:     lipgloss.Color("#FF7CCB"),
			GradientTo:       lipgloss.Color("#FDFF8C"),
		}}

	if err := progressbar.Run(pbConfig); err != nil {
		fmt.Println("error running program:", err)
		os.Exit(1)
	}
}

Concurrent execution

package main

import (
	"fmt"
	"os"

	"github.com/indaco/prompti/progressbar"
)

func main() {
	fruits := []string{
		"apple",
		"banana",
		"orange",
		"grapes",
		"mellon",
		"strawberry",
		"mango",
		"lemon",
		"apricot",
		"peach",
		"papaya",
		"kiwi",
		"pear",
		"guava",
		"almond",
		"coconut",
		"blackberry",
		"cherry",
		"grapes",
	}

	pbConfig := &progressbar.Config{Items: fruits, RunConcurrently: true}

	fmt.Println("Run commands concurrently")
	if err := progressbar.Run(pbConfig); err != nil {
		fmt.Println("error running program:", err)
		os.Exit(1)
	}
}

License

This project is licensed under the MIT License - see the LICENSE file for details.


Made with Charm.

About

Interactive TUI prompts for Go CLI applications, powered by Charm.

Topics

Resources

License

Stars

Watchers

Forks

Contributors