yeahno

package module
v0.0.0-...-7953cb0 Latest Latest
Warning

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

Go to latest
Published: Feb 6, 2026 License: MIT Imports: 13 Imported by: 0

README

yeahno

[!WARNING] This project is experimental. API may change.

Define a form once. Run it as a TUI, CLI, or MCP tools. For administrative bots and humans alike!

Built on huh, Cobra, and Go's official MCP SDK.

Install

go get github.com/mhpenta/yeahno

Usage

var choice string

menu := yeahno.NewSelect[string]().
    Title("Site Manager").
    ToolPrefix("site").  // Tools will be "site_add", "site_list"
    Options(
        yeahno.NewOption("Add", "add").
            Description("Register a new site for monitoring. Saves to the sites table.").
            WithField(yeahno.NewInput().Key("domain").Format("domain")).
            MCP(true),

        yeahno.NewOption("List", "list").
            Description("List all monitored sites. Returns a formatted text list.").
            MCP(true),

        yeahno.NewOption("Admin", "admin"),  // TUI only
    ).
    Value(&choice).
    Handler(func(ctx context.Context, action string, fields map[string]string) (any, error) {
        // Handle both TUI and MCP calls here
        return nil, nil
    })

// Run as TUI
result, err := menu.Run(ctx)

// Or generate CLI commands (uses Cobra + fang for styled output)
rootCmd := &cobra.Command{Use: "myapp", Short: "My app"}
menu.RegisterCLI(rootCmd)
fang.Execute(ctx, rootCmd, fang.WithColorSchemeFunc(yeahno.DefaultTheme().FangColorScheme()))

// Or register as MCP tools
menu.RegisterTools(server)

Each MCP-enabled option becomes its own tool (or CLI subcommand) with a clean schema containing only that option's fields.

API

Select Methods
Method Description
.ToolPrefix(prefix) Prefix for all tool names (e.g., "site" → "site_add")
.Handler(fn) Shared handler for TUI, CLI, and MCP
.ToTools() Generate []ToolDef (tool + handler pairs)
.RegisterTools(server) Register all tools with MCP server
.RegisterCLI(cmd) Register all subcommands with Cobra command
.CLI() Generate standalone Cobra command tree
Option Methods
Method Description
.MCP(true) Expose option as an MCP tool
.Description(text) Tool description
.ToolName(name) Override default tool name
.WithField(input) Attach input field to this option
Input Methods
Method Description
.Key(key) Field key in schema
.Title(text) Field description
.Required(bool) Mark field as required (default: true)
.Format(format) Validation format: "domain", "uri"
.Validate(fn) Custom validation function

Options not marked with .MCP(true) are hidden from LLMs and CLI but available in TUI.

CLI

The CLI is generated from the same menu definition. Required flags are shown inline in help output:

$ myapp --help

  COMMANDS

    add-task --title <value> [--flags]    Create a new task
    complete-task --id <value>            Mark task as done
    list-tasks                            Show all tasks
Theming

yeahno includes a default theme, or customize with your own colors:

// Use default theme
theme := yeahno.DefaultTheme()

// Or customize
theme := yeahno.DefaultTheme().
    WithPrimary(lipgloss.Color("#00ff00")).
    WithError(lipgloss.Color("#ff0000"))

// Pass to fang
fang.Execute(ctx, rootCmd, fang.WithColorSchemeFunc(theme.FangColorScheme()))
Theme Field Usage
Primary Titles, commands
Secondary Flags, arguments
Muted Descriptions, dimmed text
Surface Code block background (light terminal)
SurfaceLight Code block background (dark terminal)
Error Error header

Tips for MCP Tools

Write Clear Descriptions

The .Description() text becomes the MCP tool's description that LLMs see when deciding which tool to use. Write descriptions that:

  • Explain what the tool does clearly and concisely
  • Specify output format (e.g., "Returns JSON data" vs "Returns Markdown text")
  • Mention where data is stored if applicable (e.g., "Saved to the users table")
  • Distinguish similar tools - if you have multiple tools that sound similar, make their differences explicit
// ❌ Bad: Vague, doesn't help LLM distinguish from similar tools
yeahno.NewOption("Create Job", "create_job").
    Description("Create a job")

// ✅ Good: Clear output format, storage location, and purpose
yeahno.NewOption("Create Job", "create_job").
    Description("Create a new repair job ticket. Saves to the jobs table and returns the job ID.")
Avoid fmt.Print in Handlers

[!CAUTION] Never use fmt.Print, fmt.Println, or fmt.Printf in MCP tool handlers when running in stdio mode.

MCP stdio transport uses stdout for JSON-RPC communication. Any text written to stdout (including debug prints) will corrupt the JSON stream and cause errors like:

ERROR calling "tools/call": invalid character '=' looking for beginning of value

Use slog or another logger configured to write to stderr instead:

// ❌ Bad: Corrupts MCP stdio communication
fmt.Println("Processing request...")

// ✅ Good: Logs to stderr, not stdout
slog.Info("Processing request...")

Example

menu := yeahno.NewSelect[string]().
    Title("Company").
    ToolPrefix("company").
    Options(
        yeahno.NewOption("Add & Monitor", "add_monitor").
            Description("Add a company and begin monitoring for changes. Saves to companies table.").
            WithField(yeahno.NewInput().Key("domain").Format("domain")).
            WithField(yeahno.NewInput().Key("name").Title("Company name")).
            WithField(yeahno.NewInput().Key("ticker").Required(false)).
            MCP(true),

        yeahno.NewOption("List", "list").
            Description("List all monitored companies. Returns a formatted text list.").
            MCP(true),

        yeahno.NewOption("Cancel", "cancel"),  // TUI only
    ).
    Handler(myHandler)

// Generates 2 tools:
// - company_add_monitor (with domain, name, ticker fields)
// - company_list (no fields)
tools, _ := menu.ToTools()

Supported Types

huh yeahno
NewSelect[T]() Yes
NewInput() Yes
NewText() Yes
NewConfirm() Yes
NewMultiSelect[T]() Yes

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ValidateFormat

func ValidateFormat(format, value string) error

Types

type Confirm

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

func NewConfirm

func NewConfirm() *Confirm

func (*Confirm) Affirmative

func (c *Confirm) Affirmative(text string) *Confirm

func (*Confirm) Description

func (c *Confirm) Description(description string) *Confirm

func (*Confirm) Key

func (c *Confirm) Key(key string) *Confirm

func (*Confirm) Negative

func (c *Confirm) Negative(text string) *Confirm

func (*Confirm) Run

func (c *Confirm) Run() error

func (*Confirm) Title

func (c *Confirm) Title(title string) *Confirm

func (*Confirm) Validate

func (c *Confirm) Validate(validate func(bool) error) *Confirm

func (*Confirm) Value

func (c *Confirm) Value(value *bool) *Confirm

func (*Confirm) WithTheme

func (c *Confirm) WithTheme(theme *huh.Theme) *Confirm

type Input

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

func NewInput

func NewInput() *Input

func (*Input) CharLimit

func (i *Input) CharLimit(limit int) *Input

func (*Input) Description

func (i *Input) Description(description string) *Input

func (*Input) Format

func (i *Input) Format(format string) *Input

func (*Input) Key

func (i *Input) Key(key string) *Input

func (*Input) Placeholder

func (i *Input) Placeholder(placeholder string) *Input

func (*Input) Required

func (i *Input) Required(required bool) *Input

func (*Input) Run

func (i *Input) Run() error

func (*Input) Title

func (i *Input) Title(title string) *Input

func (*Input) Validate

func (i *Input) Validate(validate func(string) error) *Input

func (*Input) Value

func (i *Input) Value(value *string) *Input

func (*Input) WithTheme

func (i *Input) WithTheme(theme *huh.Theme) *Input

type MultiSelect

type MultiSelect[T comparable] struct {
	// contains filtered or unexported fields
}

func NewMultiSelect

func NewMultiSelect[T comparable]() *MultiSelect[T]

func (*MultiSelect[T]) Description

func (m *MultiSelect[T]) Description(description string) *MultiSelect[T]

func (*MultiSelect[T]) Height

func (m *MultiSelect[T]) Height(height int) *MultiSelect[T]

func (*MultiSelect[T]) Key

func (m *MultiSelect[T]) Key(key string) *MultiSelect[T]

func (*MultiSelect[T]) Limit

func (m *MultiSelect[T]) Limit(limit int) *MultiSelect[T]

func (*MultiSelect[T]) Options

func (m *MultiSelect[T]) Options(options ...Option[T]) *MultiSelect[T]

func (*MultiSelect[T]) Run

func (m *MultiSelect[T]) Run() error

func (*MultiSelect[T]) Title

func (m *MultiSelect[T]) Title(title string) *MultiSelect[T]

func (*MultiSelect[T]) Validate

func (m *MultiSelect[T]) Validate(validate func([]T) error) *MultiSelect[T]

func (*MultiSelect[T]) Value

func (m *MultiSelect[T]) Value(value *[]T) *MultiSelect[T]

func (*MultiSelect[T]) WithTheme

func (m *MultiSelect[T]) WithTheme(theme *huh.Theme) *MultiSelect[T]

type Option

type Option[T comparable] struct {
	Key   string
	Value T
	// contains filtered or unexported fields
}

func NewOption

func NewOption[T comparable](key string, value T) Option[T]

func NewOptions

func NewOptions[T comparable](values ...T) []Option[T]

func (Option[T]) Description

func (o Option[T]) Description(desc string) Option[T]

func (Option[T]) MCP

func (o Option[T]) MCP(include bool) Option[T]

func (Option[T]) Selected

func (o Option[T]) Selected(selected bool) Option[T]

func (Option[T]) String

func (o Option[T]) String() string

func (Option[T]) ToolName

func (o Option[T]) ToolName(name string) Option[T]

func (Option[T]) WithField

func (o Option[T]) WithField(field *Input) Option[T]

type Select

type Select[T comparable] struct {
	// contains filtered or unexported fields
}

func NewSelect

func NewSelect[T comparable]() *Select[T]

func (*Select[T]) CLI

func (s *Select[T]) CLI() (*cobra.Command, error)

CLI returns a single Cobra command tree. Alias for ToCLI.

func (*Select[T]) Description

func (s *Select[T]) Description(description string) *Select[T]

func (*Select[T]) Handler

func (s *Select[T]) Handler(h func(ctx context.Context, value T, fields map[string]string) (any, error)) *Select[T]

func (*Select[T]) Height

func (s *Select[T]) Height(height int) *Select[T]

func (*Select[T]) Options

func (s *Select[T]) Options(options ...Option[T]) *Select[T]

func (*Select[T]) RegisterCLI

func (s *Select[T]) RegisterCLI(parent *cobra.Command) error

RegisterCLI adds all generated subcommands to an existing Cobra command.

func (*Select[T]) RegisterTools

func (s *Select[T]) RegisterTools(server *mcp.Server) error

func (*Select[T]) Run

func (s *Select[T]) Run(ctx context.Context) (any, error)

func (*Select[T]) Title

func (s *Select[T]) Title(title string) *Select[T]

func (*Select[T]) ToCLI

func (s *Select[T]) ToCLI() (*cobra.Command, error)

ToCLI generates Cobra commands from the Select menu. Each MCP-enabled option becomes a subcommand. Fields become flags on the subcommand.

func (*Select[T]) ToSubcommands

func (s *Select[T]) ToSubcommands() ([]*cobra.Command, error)

ToSubcommands generates Cobra subcommands without a root wrapper. Use this to attach commands directly to an existing Cobra root.

func (*Select[T]) ToTools

func (s *Select[T]) ToTools() ([]ToolDef, error)

func (*Select[T]) ToolPrefix

func (s *Select[T]) ToolPrefix(prefix string) *Select[T]

func (*Select[T]) Validate

func (s *Select[T]) Validate(validate func(T) error) *Select[T]

func (*Select[T]) Value

func (s *Select[T]) Value(value *T) *Select[T]

func (*Select[T]) WithTheme

func (s *Select[T]) WithTheme(theme *huh.Theme) *Select[T]

type Text

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

func NewText

func NewText() *Text

func (*Text) CharLimit

func (t *Text) CharLimit(limit int) *Text

func (*Text) Description

func (t *Text) Description(description string) *Text

func (*Text) Key

func (t *Text) Key(key string) *Text

func (*Text) Lines

func (t *Text) Lines(lines int) *Text

func (*Text) Placeholder

func (t *Text) Placeholder(placeholder string) *Text

func (*Text) Required

func (t *Text) Required(required bool) *Text

func (*Text) Run

func (t *Text) Run() error

func (*Text) Title

func (t *Text) Title(title string) *Text

func (*Text) Validate

func (t *Text) Validate(validate func(string) error) *Text

func (*Text) Value

func (t *Text) Value(value *string) *Text

func (*Text) WithTheme

func (t *Text) WithTheme(theme *huh.Theme) *Text

type Theme

type Theme struct {
	// Primary is the main accent color (titles, commands)
	Primary color.Color
	// Secondary is for less prominent elements (descriptions, comments)
	Secondary color.Color
	// Muted is for dimmed/inactive elements
	Muted color.Color
	// Surface is for backgrounds (e.g., code blocks, usage boxes)
	Surface color.Color
	// SurfaceLight is for backgrounds on dark terminals (defaults to slightly lighter Surface)
	SurfaceLight color.Color
	// Error is for error states
	Error color.Color
}

Theme defines colors for yeahno CLI output. Use NewTheme() or DefaultTheme() to create one.

func DefaultTheme

func DefaultTheme() *Theme

DefaultTheme returns the yeahno default color theme.

func NewTheme

func NewTheme(primary, secondary, muted, surface, surfaceLight, errorColor color.Color) *Theme

NewTheme creates a theme with the given colors. Any nil colors will use defaults.

func (*Theme) FangColorScheme

func (t *Theme) FangColorScheme() func(lipgloss.LightDarkFunc) fang.ColorScheme

FangColorScheme converts the theme to a fang.ColorScheme. Use this with fang.WithColorSchemeFunc().

func (*Theme) WithError

func (t *Theme) WithError(c color.Color) *Theme

WithError returns a copy of the theme with a new error color.

func (*Theme) WithMuted

func (t *Theme) WithMuted(c color.Color) *Theme

WithMuted returns a copy of the theme with a new muted color.

func (*Theme) WithPrimary

func (t *Theme) WithPrimary(c color.Color) *Theme

WithPrimary returns a copy of the theme with a new primary color.

func (*Theme) WithSecondary

func (t *Theme) WithSecondary(c color.Color) *Theme

WithSecondary returns a copy of the theme with a new secondary color.

func (*Theme) WithSurface

func (t *Theme) WithSurface(c color.Color) *Theme

WithSurface returns a copy of the theme with a new surface color.

func (*Theme) WithSurfaceLight

func (t *Theme) WithSurfaceLight(c color.Color) *Theme

WithSurfaceLight returns a copy of the theme with a new surface light color.

type ToolDef

type ToolDef struct {
	Tool    *mcp.Tool
	Handler mcp.ToolHandler
}

Directories

Path Synopsis
examples
cli command
mcp command
repair command

Jump to

Keyboard shortcuts

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