tui

package
v0.8.2 Latest Latest
Warning

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

Go to latest
Published: Apr 5, 2026 License: MIT Imports: 14 Imported by: 5

README

tui

A full-screen terminal UI framework for building interactive CLI applications — chat interfaces, log viewers, AI assistants, and more.

Layout

  Scrollable output / conversation history
  (auto-scrolls to bottom; Page Up/Down/wheel to scroll)

  Command palette (visible when / is typed)

┌─────────────────────────────────────── spinner / status ─┐
│                                                          │
│ > input goes here                                        │
│                                                          │
└─ myapp ──────────────────────────────────────────────────┘

The input box top border carries the spinner, progress bar, scroll hint, or StatusRight text (in that priority order). StatusLeft is embedded in the bottom border.

Multi-Panel Layout

The TUI supports multi-panel layouts using a two-step builder pattern: create panels, then add them to the layout.

┌─ logs ───┬─ main ──────────────────────┬─ stats ──┐
│          │                             │          │
│ [INFO]   │ Chat content here           │ CPU: 45% │
│ [WARN]   │                             │ MEM: 60% │
│          │                             │          │
└──────────┴─────────────────────────────┴──────────┘

Create panels and build the layout:

// Step 1: Create panels
logs := t.CreatePanel(tui.PanelConfig{
    Name:       "logs",
    Width:      -25,       // 25% of terminal width (negative = percentage)
    MinWidth:   15,        // Hide if narrower than 15 columns
    Scrollable: true,
    Title:      "Logs",
})
stats := t.CreatePanel(tui.PanelConfig{
    Name:       "stats",
    Width:      20,        // 20 columns (positive = fixed)
    MinWidth:   10,        // Hide if narrower than 10 columns
    Scrollable: false,     // Fixed content, overwrites on update
    Title:      "Stats",
    NoBorder:   false,     // Set true to hide border
})

// Step 2: Add panels to layout
t.AddLeft(logs)
t.AddRight(stats)

Panels can be nested with vertical splits using AddRow:

right := t.CreatePanel(tui.PanelConfig{Width: -30, MinWidth: 16})
statsCpu := t.CreatePanel(tui.PanelConfig{Name: "cpu", Height: -50, Title: "CPU"})
statsMem := t.CreatePanel(tui.PanelConfig{Name: "mem", Height: -50, Title: "Memory"})

right.AddRow(statsCpu)
right.AddRow(statsMem)

t.AddRight(right)

Clear and rebuild layouts at runtime:

// Remove all panels from layout (panels keep their content)
t.ClearLayout()

// Re-attach panels
t.AddLeft(logs)
t.AddRight(right)

Get a panel reference and write to it:

logs := t.Panel("logs")
logs.WriteString("[INFO] Server started\n")
logs.WriteString(logs.StyledWith("error", "[ERROR]") + " Connection failed\n")

stats := t.Panel("stats")
stats.SetContent("CPU: 45%\nMEM: 60%")  // Replaces all content

main := t.Panel("main")  // Main panel always exists
main.AddMessage(tui.RoleUser, "Hello!")

Panel features:

  • All panels (including main) support AddMessage, StartStreaming, etc.
  • WriteString for raw text with ANSI passthrough
  • SetContent to replace content entirely (useful for fixed panels)
  • WriteAt(row, col, text) for positioned writes
  • ClearLine(row), ClearRegion(r1, c1, r2, c2) for partial clearing
  • ScrollToTop, ScrollToBottom, ScrollUp(n), ScrollDown(n)
  • Size() returns (width, height), ContentLines() returns line count
  • SetTitle("") to remove title (unnamed panel)
  • SetBorder(false) to hide border
  • SetColor(color) for border accent
  • Styled and StyledWith for themed colors

Navigation:

  • Tab cycles focus through visible panels
  • Focused panel has highlighted border
  • Page Up/Down scrolls the focused panel
  • Mouse wheel scrolls the panel under cursor

See tui/example-panels for a complete demo.

Quick Start

package main

import (
    "context"
    "fmt"
    "os"

    "github.com/paularlott/cli/tui"
)

func main() {
    var t *tui.TUI

    t = tui.New(tui.Config{
        Theme:       tui.ThemeAmber,
        StatusLeft:  "myapp",
        StatusRight: "Ctrl+C to exit",
        Commands: []*tui.Command{
            {
                Name:        "exit",
                Description: "Exit the application",
                Handler:     func(_ string) { t.Exit() },
            },
            {
                Name:        "clear",
                Description: "Clear conversation history",
                Handler:     func(_ string) { t.ClearOutput() },
            },
        },
        OnSubmit: func(text string) {
            t.AddMessage(tui.RoleUser, text)
            t.AddMessage(tui.RoleAssistant, "Echo: "+text)
        },
    })

    t.AddMessage(tui.RoleSystem, "Welcome! Type / for commands.")

    if err := t.Run(context.Background()); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

Configuration

type Config struct {
    Theme          *Theme      // Active theme. Defaults to ThemeDefault.
    Themes         []*Theme    // Additional themes registered into the global registry.
    Commands       []*Command  // Slash commands shown in the palette.
    OnSubmit       func(text string)  // Called when the user submits input.
    OnEscape       func()             // Called when Escape is pressed outside the palette.
    OnFocusChange  func(panel *Panel) // Called when panel focus changes via Tab cycling.
    UserLabel      string      // Label for user messages. Empty string hides the header.
    AssistantLabel string      // Label for assistant messages. Empty string hides the header.
    SystemLabel    string      // Label for system messages. Empty string hides the header.
    StatusLeft     string      // Text shown bottom-left.
    StatusRight    string      // Text shown bottom-right (overridden by spinner/progress/scroll hint).
    ShowCharCount  bool        // Show character counter below the input box. Default: false.
    HideHeaders    bool        // Suppress all role headers regardless of label values. Default: false.
    InputEnabled   *bool       // Set to false for output-only / log-viewer mode.
}

Labels

Headers are shown only when a label is set. Empty string (the default) hides the header for that role.

// Set at construction time:
t = tui.New(tui.Config{
    UserLabel:      "You",
    AssistantLabel: "GPT-4o",
    SystemLabel:    "System",
})

// Update at runtime (empty string leaves that label unchanged):
t.SetLabels("You", "Claude 3.5", "")

Individual messages can override the default label:

t.AddMessageAs(tui.RoleAssistant, "GPT-4o", "Here is my answer…")
t.StartStreamingAs("Claude 3.5")

Messages

// Append a message from a standard role.
t.AddMessage(tui.RoleUser, "Hello!")
t.AddMessage(tui.RoleAssistant, "Hi there.")
t.AddMessage(tui.RoleSystem, "Connected.")

// Append a message with a custom label (overrides the role label for this message).
t.AddMessageAs(tui.RoleAssistant, "GPT-4o", "Here is my answer…")

// Clear all messages.
t.ClearOutput()

Message content supports fenced code blocks:

t.AddMessage(tui.RoleAssistant, "Example:\n\n```go\nfmt.Println(\"hello\")\n```")

Streaming

For token-by-token responses:

t.StartStreamingAs("Claude 3.5") // or t.StartStreaming() for the default label
for chunk := range tokenCh {
    if !t.IsStreaming() {
        break // stopped externally via StopStreaming()
    }
    t.StreamChunk(chunk)
}
t.StreamComplete()

// To stop early and finalise the message (e.g. from OnEscape):
t.StopStreaming()

Commands

All commands are supplied by the caller — there are no built-ins. Register them in Config.Commands at construction time, or add/remove them at runtime:

Commands: []*tui.Command{
    {
        Name:        "exit",
        Description: "Exit the application",
        Handler:     func(_ string) { t.Exit() },
    },
    {
        Name:        "theme",
        Description: "Switch theme",
        Args:        []string{"amber", "blue", "green"}, // shown as sub-options
        Handler: func(args string) {
            if th, ok := tui.ThemeByName(args); ok {
                t.SetTheme(th)
            }
        },
    },
},

// Add a command after construction:
t.AddCommand(&tui.Command{
    Name:        "save",
    Description: "Save current session",
    Handler:     func(_ string) { saveSession() },
})

// Remove a command by name:
t.RemoveCommand("save")

Type / to open the palette. Use / to navigate, Tab to complete, Enter to execute, Esc to close.

Spinner

Displays an animated braille spinner in the input box top border:

t.StartSpinner("Thinking…")
// … do work …
t.StopSpinner()

Calling StartSpinner while one is already running replaces the text. Starting a progress bar stops the spinner automatically.

Progress Bar

Displays a labelled progress bar in the input box top border:

for i := 0; i <= 100; i++ {
    t.SetProgress("Uploading…", float64(i)/100)
}
t.ClearProgress()

The border renders: ┌──────── Uploading… [████████████░░░░░░░░] 60% ┐

Spinner and progress bar are mutually exclusive. Both are overridden by the scroll hint when the user has scrolled up.

Status Bar

t.SetStatus("myapp", "v1.2.3")   // set both at once
t.SetStatusLeft("myapp")
t.SetStatusRight("v1.2.3")

Styled Text

Styled wraps a string in a theme color for use in message content:

t.AddMessage(tui.RoleSystem,
    tui.Styled(t.Theme().Text, "myapp") + "\n" +
    tui.Styled(t.Theme().Primary, "v1.2.3"),
)

t.Theme() returns the active theme so you can reference its color fields (Primary, Secondary, Text, Dim, Error, etc.) at call time, picking up any theme changes automatically.

Menus

t.OpenMenu(m) replaces the input box with a navigable bordered panel. t.CloseMenu() dismisses it programmatically.

t.OpenMenu(&tui.Menu{
    Title: "Settings",
    Items: []*tui.MenuItem{
        {
            Label: "Theme",
            Children: []*tui.MenuItem{          // sub-menu — shows › indicator
                {Label: "Amber", Value: "amber", OnSelect: func(item *tui.MenuItem, _ string) {
                    if th, ok := tui.ThemeByName(item.Value); ok { t.SetTheme(th) }
                }},
            },
        },
        {
            Label:  "API Key",
            Prompt: "Enter API key:",            // text-entry mode
            OnSelect: func(_ *tui.MenuItem, input string) {
                // input holds what the user typed
            },
        },
        {
            Label: "About",
            OnSelect: func(_ *tui.MenuItem, _ string) {
                t.AddMessage(tui.RoleSystem, "v1.0.0")
            },
        },
    },
})
MenuItem fields
Field Type Description
Label string Display text
Value string Optional data passed to OnSelect
Prompt string If set, selecting this item opens a text-entry prompt (shows indicator)
Children []*MenuItem If set, selecting pushes a sub-menu (shows indicator)
OnSelect func(item *MenuItem, input string) Called when item is confirmed; input is non-empty for Prompt items
Navigation
Key Action
/ Move selection
Enter Select item / confirm prompt
Esc Cancel prompt → back to list; pop sub-menu → close at root

The title bar shows a breadcrumb when inside a sub-menu: Settings › Theme.

Output-Only Mode

Set InputEnabled to false to hide the input box, char count, and palette. Only scrolling and Ctrl+C remain active. Useful for log viewers or progress displays driven entirely by the application.

enabled := false
t = tui.New(tui.Config{
    InputEnabled: &enabled,
    // …
})

Context & Shutdown

Run accepts a context.Context. Cancelling the context shuts down the event loop cleanly:

ctx, cancel := context.WithCancel(context.Background())
// cancel() from anywhere to exit

t.Run(ctx)

// Retrieve the context later (e.g. inside a command handler):
ctx := t.Context()

t.Exit() is a convenience method that sets the quit flag directly — wire it to a /exit command.

Themes

Built-in themes
Name Description
default Dark navy, cyan primary, crimson secondary (default)
amber Dark warm background, amber primary, teal secondary
blue Deep dark background, periwinkle primary, sky secondary
green Dark terminal, mint primary, gold secondary
purple Dark background, lavender primary, rose secondary
light Light background, blue primary, green secondary
plain No colors (monochrome)
Selecting by name
if th, ok := tui.ThemeByName("amber"); ok {
    t.SetTheme(th)
}

// List all available theme names:
names := tui.ThemeNames() // ["amber", "blue", "default", "green", "light", "plain", "purple"]
Custom themes

Define a Theme struct and either pass it in Config.Themes (registered automatically) or call RegisterTheme directly:

myTheme := &tui.Theme{
    Name:      "solarized",
    Primary:   0x268BD2,
    Secondary: 0x2AA198,
    Text:      0x839496,
    UserText:  0x268BD2,
    Dim:       0x586E75,
    CodeBG:    0x073642,
    CodeText:  0x839496,
    Error:     0xDC322F,
}

// Option A — via Config (registered before Run):
t = tui.New(tui.Config{
    Themes: []*tui.Theme{myTheme},
    Theme:  myTheme,
})

// Option B — global registry at any time:
tui.RegisterTheme(myTheme)

Color values are 24-bit RGB packed as 0xRRGGBB. A zero value means the terminal's default color.

Keyboard Reference

Key Action
Enter Submit input / execute selected command
Shift+Enter Insert newline
/ Move cursor up/down in multi-line input, navigate history (single-line), or navigate palette
/ Move cursor left/right
Home / End Jump to start/end of line
Backspace Delete character before cursor
Delete Delete character at cursor
Ctrl+A Move to start of line
Ctrl+K Delete to end of line
Ctrl+U Delete to start of line
Ctrl+W Delete word before cursor
Page Up/Down Scroll focused panel half a page
Mouse wheel Scroll panel under cursor 3 lines
Mouse drag Select text; release to copy to clipboard
Tab Complete palette command/arg, or cycle panel focus when palette closed
Esc Close palette / fire OnEscape
Ctrl+C Exit

Documentation

Overview

Package tui provides a full-screen terminal UI framework for building interactive CLI applications, inspired by modern AI assistants.

Layout

The screen is divided into three vertical regions:

┌─────────────────────────────────────────────────────────┐
│  Scrollable output / conversation history               │
│  (auto-scrolls to bottom; Page Up/Down/wheel to scroll) │
├─────────────────────────────────────────────────────────┤
│  Command palette (visible when / is typed)              │
├─────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────┐    │
│  │ > input goes here      Ctrl+C to exit           │    │
│  └─────────────────────────────────────────────────┘    │
│  myapp                                                  │
└─────────────────────────────────────────────────────────┘

Quick Start

t := tui.New(tui.Config{
    Theme: tui.ThemeAmber,
    Commands: []*tui.Command{
        {Name: "clear", Description: "Clear history", Handler: func(_ string) { t.ClearOutput() }},
    },
    OnSubmit: func(text string) {
        t.AddMessage(tui.RoleUser, text)
        t.AddMessage(tui.RoleAssistant, "Echo: "+text)
    },
})
t.Run(context.Background())

Streaming

For token-by-token responses:

t.StartStreamingAs("GPT-4o")
for chunk := range tokenCh {
    t.StreamChunk(chunk)
}
t.StreamComplete()

Themes

Seven built-in themes: ThemeAmber, ThemeBlue, ThemeGreen, ThemePurple, ThemeLight, ThemePlain, ThemeDefault. Look up by name with ThemeByName. Register custom themes with RegisterTheme or via Config.Themes.

Index

Constants

View Source
const (
	ThemePrimary   = "primary"
	ThemeSecondary = "secondary"
	ThemeError     = "error"
	ThemeDim       = "dim"
	ThemeText      = "text"
	ThemeUser      = "user"
)

Theme color names for use with Panel.StyledWith.

Variables

View Source
var (
	// ThemeDefault is the default theme — dark navy background, cyan primary, crimson secondary.
	ThemeDefault = &Theme{
		Name:      "default",
		Primary:   0x4EB8C8,
		Secondary: 0xC0395A,
		Text:      0xE8EAF0,
		UserText:  0x4EB8C8,
		Dim:       0x7A8492,
		CodeBG:    0x111A26,
		CodeText:  0xE8EAF0,
		Error:     0xC0395A,
	}

	// ThemeAmber — warm dark background, amber primary, teal secondary.
	ThemeAmber = &Theme{
		Name:      "amber",
		Primary:   0xE8A87C,
		Secondary: 0x7EC8A4,
		Text:      0xCDD6F4,
		UserText:  0xE8A87C,
		Dim:       0x6C6F85,
		CodeBG:    0x1E1A14,
		CodeText:  0xCDD6F4,
		Error:     0xF38BA8,
	}

	// ThemeBlue — deep dark background, periwinkle primary, sky secondary.
	ThemeBlue = &Theme{
		Name:      "blue",
		Primary:   0x7BA7E8,
		Secondary: 0x5BC8D8,
		Text:      0xD0D8F0,
		UserText:  0x7BA7E8,
		Dim:       0x5A6070,
		CodeBG:    0x0D1117,
		CodeText:  0xD0D8F0,
		Error:     0xE06C75,
	}

	// ThemeGreen — dark terminal, mint primary, gold secondary.
	ThemeGreen = &Theme{
		Name:      "green",
		Primary:   0x7EC87A,
		Secondary: 0xD4A843,
		Text:      0xD8E0D0,
		UserText:  0x7EC87A,
		Dim:       0x5A6650,
		CodeBG:    0x0D1A0F,
		CodeText:  0xD8E0D0,
		Error:     0xE05050,
	}

	// ThemePurple — dark background, lavender primary, rose secondary.
	ThemePurple = &Theme{
		Name:      "purple",
		Primary:   0xB48EE8,
		Secondary: 0xE87EB4,
		Text:      0xE0D8F0,
		UserText:  0xB48EE8,
		Dim:       0x6A6080,
		CodeBG:    0x130D1E,
		CodeText:  0xE0D8F0,
		Error:     0xF07070,
	}

	// ThemeLight — light background, blue primary, green secondary.
	ThemeLight = &Theme{
		Name:      "light",
		Primary:   0x1A56CC,
		Secondary: 0x0A7A50,
		Text:      0x1A1A2E,
		UserText:  0x1A56CC,
		Dim:       0x666677,
		CodeBG:    0xE8EAF0,
		CodeText:  0x1A1A2E,
		Error:     0xCC2020,
	}

	// ThemePlain uses no colors (monochrome).
	ThemePlain = &Theme{
		Name: "plain",
	}
)

Built-in themes.

Functions

func CopyToClipboard added in v0.7.12

func CopyToClipboard(text string) string

CopyToClipboard writes text to the system clipboard via OSC 52. Supported by: iTerm2, Ghostty, kitty, WezTerm, tmux (with set-clipboard on).

func Hyperlink(url, text string) string

Hyperlink wraps text in an OSC 8 hyperlink. Supported by: iTerm2, Kitty, WezTerm, Alacritty, Windows Terminal, VTE (GNOME Terminal), Ghostty, Terminal.app (macOS 10.15+). If url is empty, returns text unchanged.

func Notify added in v0.7.12

func Notify(message string) string

Notify returns an OSC 9 sequence for a desktop notification. Supported by: iTerm2, Ghostty, ConEmu.

func RegisterTheme

func RegisterTheme(t *Theme)

RegisterTheme adds a custom theme to the global registry, keyed by Theme.Name.

func SetWindowTitle added in v0.7.12

func SetWindowTitle(title string) string

SetWindowTitle returns an OSC 0 sequence to set the window title and icon. Supported by virtually all terminal emulators.

func Styled

func Styled(color Color, text string) string

Styled returns text wrapped in the given color, reset after. Use the theme color fields directly: t.Theme().Primary, t.Theme().Secondary, etc.

func ThemeNames

func ThemeNames() []string

ThemeNames returns a sorted slice of all registered theme names.

Types

type Color

type Color uint32

Color is a 24-bit RGB color packed as 0xRRGGBB. Zero means "default terminal color".

type Command

type Command struct {
	Name        string
	Description string
	Args        []string // Optional sub-options shown in palette after a space
	Handler     func(args string)
}

Command is a slash command that can be registered with the TUI.

type Config

type Config struct {
	// Theme controls colors. Defaults to ThemeAmber if nil.
	Theme *Theme

	// Commands are the slash commands available in the palette.
	Commands []*Command

	// Themes registers additional themes into the global theme registry,
	// making them available via ThemeByName.
	Themes []*Theme

	// OnSubmit is called when the user presses Enter to submit input.
	// The TUI does NOT add a user message automatically; the caller decides.
	OnSubmit func(text string)

	// OnEscape is called when Escape is pressed and the palette is not active.
	OnEscape func()

	// UserLabel is the label shown for user messages. Defaults to "You".
	UserLabel string

	// AssistantLabel is the default label for assistant messages. Defaults to "Assistant".
	AssistantLabel string

	// SystemLabel is the label shown for system messages. Defaults to "System".
	SystemLabel string

	// ThinkingLabel is the label shown for thinking messages. Defaults to "Thinking".
	ThinkingLabel string

	// ToolLabel is the label shown for tool messages. Defaults to "Tool".
	ToolLabel string

	// HideHeaders suppresses the role header line between messages.
	HideHeaders bool

	// StatusLeft is optional text shown in the bottom-left status bar.
	StatusLeft string

	// StatusRight is optional text shown in the bottom-right status bar.
	StatusRight string

	// ShowCharCount enables the character counter below the input box. Defaults to false.
	ShowCharCount bool

	// InputEnabled controls whether the input box is shown. Defaults to true.
	// When false, the input box, char count, and palette are hidden and
	// keyboard input only handles scrolling and Ctrl+C.
	InputEnabled *bool

	// OnFocusChange is called when panel focus changes via Tab cycling.
	// The callback receives the newly focused panel.
	OnFocusChange func(panel *Panel)
}

Config holds the configuration for a TUI instance.

type Menu struct {
	Title string
	Items []*MenuItem
}

Menu is a navigable panel that replaces the input box.

type MenuItem struct {
	Label    string
	Value    string                             // optional value passed to OnSelect
	Prompt   string                             // if set, selecting this item opens a text-entry prompt with this label
	Children []*MenuItem                        // non-nil → sub-menu
	OnSelect func(item *MenuItem, input string) // input is non-empty only for Prompt items
}

MenuItem is a single entry in a Menu.

type MessageRole

type MessageRole int

MessageRole identifies who sent a message.

const (
	RoleAssistant MessageRole = iota
	RoleUser
	RoleSystem
	RoleThinking
	RoleTool
)

type Panel added in v0.8.0

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

Panel represents a content area within the TUI. It implements io.Writer for easy integration with other systems. Panels render raw text content via WriteString, SetContent, etc. For chat-like message history, use the TUI's main output methods instead.

func (*Panel) AddColumn added in v0.8.0

func (p *Panel) AddColumn(child *Panel)

AddColumn appends a child panel as a horizontal column (left to right). No-op if the panel already has rows.

func (*Panel) AddMessage added in v0.8.0

func (p *Panel) AddMessage(role MessageRole, content string)

AddMessage appends a complete message with role.

func (*Panel) AddMessageAs added in v0.8.0

func (p *Panel) AddMessageAs(role MessageRole, label, content string)

AddMessageAs appends a complete message with a custom label.

func (*Panel) AddRow added in v0.8.0

func (p *Panel) AddRow(child *Panel)

AddRow appends a child panel as a vertical row (top to bottom). No-op if the panel already has columns.

func (*Panel) Clear added in v0.8.0

func (p *Panel) Clear()

Clear removes all content from the panel.

func (*Panel) ClearLine added in v0.8.0

func (p *Panel) ClearLine(row int)

ClearLine clears a specific line in raw content. The panel must be in ModeRaw for this to have visible effect.

func (*Panel) ClearRegion added in v0.8.0

func (p *Panel) ClearRegion(startRow, startCol, endRow, endCol int)

ClearRegion clears a rectangular region in raw content. The panel must be in ModeRaw for this to have visible effect.

func (*Panel) Color added in v0.8.0

func (p *Panel) Color() Color

Color returns the panel's current color.

func (*Panel) ContentLines added in v0.8.0

func (p *Panel) ContentLines() int

ContentLines returns the total number of content lines.

func (*Panel) HasBorder added in v0.8.0

func (p *Panel) HasBorder() bool

HasBorder returns true if the border is enabled.

func (*Panel) IsStreaming added in v0.8.0

func (p *Panel) IsStreaming() bool

IsStreaming returns true if a streaming message is in progress.

func (*Panel) Name added in v0.8.0

func (p *Panel) Name() string

Name returns the panel's identifier.

func (*Panel) ScrollDown added in v0.8.0

func (p *Panel) ScrollDown(n int)

ScrollDown scrolls down by n lines.

func (*Panel) ScrollOffset added in v0.8.0

func (p *Panel) ScrollOffset() int

ScrollOffset returns the current scroll offset.

func (*Panel) ScrollToBottom added in v0.8.0

func (p *Panel) ScrollToBottom()

ScrollToBottom scrolls to the bottom of the content.

func (*Panel) ScrollToTop added in v0.8.0

func (p *Panel) ScrollToTop()

ScrollToTop scrolls to the top of the content.

func (*Panel) ScrollUp added in v0.8.0

func (p *Panel) ScrollUp(n int)

ScrollUp scrolls up by n lines.

func (*Panel) Scrollable added in v0.8.0

func (p *Panel) Scrollable() bool

Scrollable returns whether the panel is scrollable.

func (*Panel) SetBorder added in v0.8.0

func (p *Panel) SetBorder(enabled bool)

SetBorder enables or disables the border for this panel.

func (*Panel) SetColor added in v0.8.0

func (p *Panel) SetColor(color Color)

SetColor sets the panel's border/accent color.

func (*Panel) SetContent added in v0.8.0

func (p *Panel) SetContent(s string)

SetContent replaces all raw content with the given string. The panel must be in ModeRaw to render this content.

func (*Panel) SetScrollable added in v0.8.0

func (p *Panel) SetScrollable(scrollable bool)

SetScrollable sets whether the panel content scrolls.

func (*Panel) SetSkipFocus added in v0.8.0

func (p *Panel) SetSkipFocus(skip bool)

SetSkipFocus sets whether the panel is excluded from Tab focus cycling.

func (*Panel) SetTitle added in v0.8.0

func (p *Panel) SetTitle(title string)

SetTitle sets the title shown in the border. Empty string = no title.

func (*Panel) Size added in v0.8.0

func (p *Panel) Size() (width, height int)

Size returns the panel's content dimensions (width, height). Returns 0, 0 if the panel has not been rendered yet.

func (*Panel) SkipFocus added in v0.8.0

func (p *Panel) SkipFocus() bool

SkipFocus returns whether the panel is excluded from focus cycling.

func (*Panel) StartStreaming added in v0.8.0

func (p *Panel) StartStreaming()

StartStreaming begins a new assistant message built incrementally.

func (*Panel) StartStreamingAs added in v0.8.0

func (p *Panel) StartStreamingAs(label string)

StartStreamingAs begins a new streaming message with a custom label.

func (*Panel) StartStreamingWithRole added in v0.8.2

func (p *Panel) StartStreamingWithRole(role MessageRole, label string)

StartStreamingWithRole begins a new streaming message with a custom role and label.

func (*Panel) StopStreaming added in v0.8.0

func (p *Panel) StopStreaming()

StopStreaming finalises any in-progress streaming message.

func (*Panel) StreamChunk added in v0.8.0

func (p *Panel) StreamChunk(chunk string)

StreamChunk appends a chunk to the in-progress streaming message.

func (*Panel) StreamComplete added in v0.8.0

func (p *Panel) StreamComplete()

StreamComplete finalises the streaming message.

func (*Panel) Styled added in v0.8.0

func (p *Panel) Styled(color Color, text string) string

Styled returns text wrapped in the given color.

func (*Panel) StyledWith added in v0.8.0

func (p *Panel) StyledWith(name string, text string) string

StyledWith returns text wrapped in a named theme color. Name can be: "primary", "secondary", "error", "dim", "text", "user"

func (*Panel) Title added in v0.8.0

func (p *Panel) Title() string

Title returns the current title (empty string if unset).

func (*Panel) Write added in v0.8.0

func (p *Panel) Write(b []byte) (int, error)

Write implements io.Writer. It appends bytes to the panel's raw content.

func (*Panel) WriteAt added in v0.8.0

func (p *Panel) WriteAt(row, col int, s string)

WriteAt writes text at a specific position (0-indexed, relative to panel). If the position is outside current content, content is extended with blank lines. The panel must be in ModeRaw to render this content.

func (*Panel) WriteString added in v0.8.0

func (p *Panel) WriteString(s string)

WriteString appends a string to the panel's raw content. The panel must be in ModeRaw to render this content.

type PanelConfig added in v0.8.0

type PanelConfig struct {
	Name       string // Panel identifier
	Width      int    // Positive = columns, negative = percentage (e.g., -30 = 30%)
	Height     int    // Positive = rows, negative = percentage (e.g., -50 = 50%). Used when panel is split vertically
	MinWidth   int    // Minimum width to render; 0 = always show
	Scrollable bool   // True = content scrolls, false = fixed viewport
	Title      string // Optional title for border; empty = no title
	Color      *Color // Optional border/accent color; nil = auto-assign
	NoBorder   bool   // True = hide border for this panel
	SkipFocus  bool   // True = exclude from Tab focus cycle
}

PanelConfig configures a panel in the layout.

type TUI

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

TUI is the main terminal UI instance.

func New

func New(cfg Config) *TUI

New creates a new TUI with the given configuration.

func (*TUI) AddCommand added in v0.7.6

func (t *TUI) AddCommand(cmd *Command)

AddCommand registers a new slash command at runtime.

func (*TUI) AddLeft added in v0.8.0

func (t *TUI) AddLeft(panel *Panel)

AddLeft attaches the given panel (and any children) to the left of the main panel.

func (*TUI) AddMessage

func (t *TUI) AddMessage(role MessageRole, content string)

AddMessage appends a complete message to the output region.

func (*TUI) AddMessageAs

func (t *TUI) AddMessageAs(role MessageRole, label, content string)

AddMessageAs appends a complete message with a custom label.

func (*TUI) AddRight added in v0.8.0

func (t *TUI) AddRight(panel *Panel)

AddRight attaches the given panel (and any children) to the right of the main panel.

func (*TUI) ClearLayout added in v0.8.0

func (t *TUI) ClearLayout()

ClearLayout removes the layout tree but keeps all panels and their content.

func (*TUI) ClearOutput

func (t *TUI) ClearOutput()

ClearOutput removes all messages from the output region.

func (*TUI) ClearProgress

func (t *TUI) ClearProgress()

ClearProgress removes the progress bar from the separator.

func (*TUI) CloseMenu

func (t *TUI) CloseMenu()

CloseMenu closes the menu and restores the input box.

func (*TUI) Context

func (t *TUI) Context() context.Context

Context returns the context that was passed to Run. Returns nil if Run has not been called yet.

func (*TUI) CreatePanel added in v0.8.0

func (t *TUI) CreatePanel(cfg PanelConfig) *Panel

CreatePanel creates a new panel without adding it to the layout. The panel is stored in the TUI's panel map by name (if name is non-empty). Use AddLeft or AddRight to attach it to the layout.

func (*TUI) CycleFocus added in v0.8.0

func (t *TUI) CycleFocus()

CycleFocus moves focus to the next panel.

func (*TUI) Exit

func (t *TUI) Exit()

Exit cleanly shuts down the TUI event loop. Useful as a /exit command handler: func(_ string) { t.Exit() }

func (*TUI) FocusPanel added in v0.8.0

func (t *TUI) FocusPanel(idx int)

FocusPanel sets the focused panel by index.

func (*TUI) FocusedPanel added in v0.8.0

func (t *TUI) FocusedPanel() *Panel

FocusedPanel returns the currently focused panel. Returns nil if no panel is focused (shouldn't happen in normal use).

func (*TUI) HasMultiplePanels added in v0.8.0

func (t *TUI) HasMultiplePanels() bool

HasMultiplePanels returns true if there's more than one visible panel.

func (*TUI) IsStreaming

func (t *TUI) IsStreaming() bool

IsStreaming returns true if a streaming message is in progress.

func (*TUI) OpenMenu

func (t *TUI) OpenMenu(m *Menu)

OpenMenu opens a navigable menu panel, replacing the input box.

func (*TUI) Panel added in v0.8.0

func (t *TUI) Panel(name string) *Panel

Panel returns the panel with the given name. The special name "main" returns the main panel. Returns nil if no panel with that name exists.

func (*TUI) RemoveCommand added in v0.7.6

func (t *TUI) RemoveCommand(name string)

RemoveCommand removes a slash command by name.

func (*TUI) Run

func (t *TUI) Run(ctx context.Context) error

Run enters the event loop. It blocks until the user exits (Ctrl+C, t.Exit(), or ctx cancellation).

func (*TUI) SetAuxLabels added in v0.8.2

func (t *TUI) SetAuxLabels(thinking, tool string)

SetAuxLabels updates the default labels for thinking and tool messages.

func (*TUI) SetInputEnabled added in v0.7.9

func (t *TUI) SetInputEnabled(enabled bool)

SetInputEnabled toggles the input box at runtime.

func (*TUI) SetLabels added in v0.7.6

func (t *TUI) SetLabels(user, assistant, system string)

SetLabels updates the default role labels shown in message headers. Empty strings leave the corresponding label unchanged.

func (*TUI) SetProgress

func (t *TUI) SetProgress(label string, value float64)

SetProgress shows a labelled progress bar in the separator (0.0–1.0). Stops any active spinner. Call ClearProgress to remove it.

func (*TUI) SetStatus

func (t *TUI) SetStatus(left, right string)

SetStatus updates both status bar texts.

func (*TUI) SetStatusLeft

func (t *TUI) SetStatusLeft(s string)

SetStatusLeft updates the left status bar text.

func (*TUI) SetStatusRight

func (t *TUI) SetStatusRight(s string)

SetStatusRight updates the right status bar text.

func (*TUI) SetTheme

func (t *TUI) SetTheme(theme *Theme)

SetTheme changes the active theme.

func (*TUI) StartSpinner

func (t *TUI) StartSpinner(text string)

StartSpinner shows an animated spinner in the separator with the given text. Calling StartSpinner while one is running replaces the text.

func (*TUI) StartStreaming

func (t *TUI) StartStreaming()

StartStreaming begins a new streaming assistant message.

func (*TUI) StartStreamingAs

func (t *TUI) StartStreamingAs(label string)

StartStreamingAs begins a new streaming assistant message with a custom label.

func (*TUI) StartStreamingWithRole added in v0.8.2

func (t *TUI) StartStreamingWithRole(role MessageRole, label string)

StartStreamingWithRole begins a new streaming assistant/system/thinking/tool message with a custom label.

func (*TUI) StopSpinner

func (t *TUI) StopSpinner()

StopSpinner stops the spinner and clears it from the separator.

func (*TUI) StopStreaming

func (t *TUI) StopStreaming()

StopStreaming finalises any in-progress streaming message.

func (*TUI) StreamChunk

func (t *TUI) StreamChunk(chunk string)

StreamChunk appends a chunk to the current streaming message.

func (*TUI) StreamComplete

func (t *TUI) StreamComplete()

StreamComplete finalises the streaming message.

func (*TUI) TerminalSize added in v0.8.0

func (t *TUI) TerminalSize() (width, height int)

TerminalSize returns the full terminal dimensions (width, height). Returns 0, 0 if the terminal size has not been determined yet.

func (*TUI) Theme

func (t *TUI) Theme() *Theme

Theme returns the active theme, allowing callers to access color values for use with Styled.

func (*TUI) WriteString added in v0.8.0

func (t *TUI) WriteString(s string)

type Theme

type Theme struct {
	Name      string
	Primary   Color // Accents, prompt >
	Secondary Color // Muted text, hints
	Text      Color // Normal content
	UserText  Color // User message text
	Dim       Color // Very muted (scrollbar, borders)
	CodeBG    Color // Code block background
	CodeText  Color // Code block text
	Error     Color // Error messages
}

Theme defines the visual identity of the TUI.

func ThemeByName

func ThemeByName(name string) (*Theme, bool)

ThemeByName returns the theme registered under name, or (nil, false).

Directories

Path Synopsis
Example TUI application.
Example TUI application.
OSC sequence test - outputs directly to terminal without TUI.
OSC sequence test - outputs directly to terminal without TUI.
Example TUI with multiple panels — demonstrates the builder panel API.
Example TUI with multiple panels — demonstrates the builder panel API.
Example TUI log viewer — output-only mode.
Example TUI log viewer — output-only mode.

Jump to

Keyboard shortcuts

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