Skip to content

tui

import "github.com/linuskendall/cosmonaut/internal/tui"

Package tui provides terminal UI components built on Bubbletea.

The package owns two surfaces:

  • The persistent applet (AppletModel et al. in applet*.go) — a full-screen Bubbletea program that mirrors the Fyne GUI's workspace list, detail, settings, and create views.
  • A small set of shared utilities used by both the applet and the scripted CLI flow: colored status lines, a generic spinner helper, and the shared lipgloss palette.

Index

func RunApplet

func RunApplet(data *AppletData, initial ...AppletInitial) error

RunApplet starts the persistent TUI applet using the given data layer. Pass an optional AppletInitial to pre-seed the view (filter, create pre-fill). Blocks until the user quits.

func RunWithSpinner

func RunWithSpinner(message string, task func() error) error

RunWithSpinner runs a task with a spinner, exiting the process with status 0 if the user interrupts with ctrl+c.

func RunWithSpinnerResult

func RunWithSpinnerResult[T any](message string, task func() (T, error)) (T, error)

RunWithSpinnerResult runs a task that returns a value, with a spinner.

func Status

func Status(icon, msg string)

Status prints a colored status line to stderr. Used by the CLI launch path to surface a one-line "ok / waiting / done" without owning a full Bubbletea program.

func StatusErr

func StatusErr(icon, msg string)

StatusErr prints a colored error status line to stderr.

type AppletCreateSeed

AppletCreateSeed seeds the Create view with a chosen provider and an initial repository (GitHub) or workspace name (Coder).

type AppletCreateSeed struct {
    Provider   string
    Repository string
}

type AppletData

AppletData owns the TUI applet's mutable state — workspace lists, provider health, port snapshots, and the long-lived port-forward supervisor. It's the TUI-side equivalent of daemon.Daemon, minus the Fyne pieces (windows, tray, hotkey listener) which a terminal doesn't need.

All exported methods are safe for concurrent use; the TUI's tea.Cmd callbacks read state from background goroutines while the foreground model is rendering.

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

func NewAppletData

func NewAppletData(cfg *config.Config, cfgPath string) *AppletData

NewAppletData wires the long-lived TUI state. Pass the parsed config and the absolute path it was loaded from so per-workspace SSH toggles can be persisted.

func (*AppletData) Codespaces

func (d *AppletData) Codespaces() []codespace.Codespace

Codespaces returns a defensive copy of the last-polled codespace list.

func (*AppletData) Config

func (d *AppletData) Config() *config.Config

Config returns the live config pointer. Callers may mutate it (e.g. to toggle a per-workspace SSH option) and then call PersistConfig.

func (*AppletData) ConfigPath

func (d *AppletData) ConfigPath() string

ConfigPath returns the config file path used by PersistConfig.

func (*AppletData) DeleteWorkspace

func (d *AppletData) DeleteWorkspace(providerName, name string) error

DeleteWorkspace deletes a workspace through the right provider manager.

func (*AppletData) EnsurePortsCache

func (d *AppletData) EnsurePortsCache(csName string, onReady func()) PortCacheEntry

EnsurePortsCache returns the cached ports entry for a codespace, kicking off ONE async refresh when there is no cached entry yet. An entry that is already Loading is returned as-is — every repaint used to stack another `gh codespace ports` subprocess onto the in-flight one. The Loading placeholder is written under the lock before the goroutine starts so concurrent callers deduplicate too.

func (*AppletData) HealthCatalog

func (d *AppletData) HealthCatalog() []doctor.Check

HealthCatalog builds the doctor catalog the settings Health section renders. Like the GUI, it includes every provider's auth check wired to that provider's own cached error, so a Coder login problem and a GitHub scope problem surface independently rather than only the effective provider's.

func (*AppletData) ListErr

func (d *AppletData) ListErr() error

ListErr returns the most recent provider list error, or nil.

func (*AppletData) ManagerForProvider

func (d *AppletData) ManagerForProvider(providerName string) (provider.Manager, error)

ManagerForProvider returns a provider.Manager for the named provider.

func (*AppletData) PersistConfig

func (d *AppletData) PersistConfig() error

PersistConfig writes the in-memory config back to disk. Returns nil if no path was configured (which can happen in tests).

func (*AppletData) Poll

func (d *AppletData) Poll() PollResult

Poll fetches the workspace list from every configured provider, refreshes per-provider status, and returns a single combined slice. Safe to call from a goroutine; concurrent calls collapse to one in-flight at a time.

func (*AppletData) PortCache

func (d *AppletData) PortCache(name string) PortCacheEntry

PortCache returns a snapshot of the ports section for the named codespace. Loading is true while a refresh is in flight.

func (*AppletData) PortForwards

func (d *AppletData) PortForwards() *daemon.PortForwardManager

PortForwards returns the shared supervisor used to start/stop forwards.

func (*AppletData) RefreshPorts

func (d *AppletData) RefreshPorts(csName string) PortCacheEntry

RefreshPorts fetches the ports section for a codespace and stores the result in the cache. Marks Loading=true for the duration so the detail view can render a spinner placeholder.

func (*AppletData) ResolveWorkspace

func (d *AppletData) ResolveWorkspace(providerName, name string) (*provider.Workspace, error)

ResolveWorkspace finds a workspace by name through the right provider.

func (*AppletData) StatusFor

func (d *AppletData) StatusFor(name string) ProviderStatus

StatusFor returns a snapshot of the named provider's local-setup health.

func (*AppletData) Workspaces

func (d *AppletData) Workspaces() []provider.Workspace

Workspaces returns a defensive copy of the last-polled combined workspace list (GitHub codespaces + Coder workspaces, both translated to the unified provider.Workspace shape).

type AppletInitial

AppletInitial lets callers (notably the root `cosmonaut \<target>` flow) seed the applet's initial state — pre-filter the list to a repo, or jump straight to the Create view with a repo filled in. Zero value means "open the list view, no filter."

type AppletInitial struct {
    // Filter pre-populates the list view's filter-as-you-type buffer.
    // Useful when the user typed `cosmonaut <target>` and multiple
    // workspaces matched — we open the applet narrowed to those.
    Filter string
    // Create, if non-nil, opens directly on the Create view with these
    // fields pre-filled. Useful when `cosmonaut <target>` finds no
    // existing workspace and the user wanted one created.
    Create *AppletCreateSeed
}

type AppletModel

AppletModel is the top-level Bubbletea model. It owns the data layer and the currently-mounted sub-view, and routes Update / View calls to it.

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

func NewAppletModel

func NewAppletModel(data *AppletData, initial ...AppletInitial) AppletModel

NewAppletModel constructs the top-level model. Pass the shared data layer; the model will trigger an initial poll on Init.

func (AppletModel) Init

func (m AppletModel) Init() tea.Cmd

func (AppletModel) Update

func (m AppletModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (AppletModel) View

func (m AppletModel) View() string

type PollResult

PollResult is sent back by Poll as a tea.Msg so the foreground model can re-render when fresh data lands. Workspaces is always set; ListErr is non-nil when the provider call failed end-to-end.

type PollResult struct {
    Workspaces []provider.Workspace
    ListErr    error
}

type PortCacheEntry

PortCacheEntry mirrors the daemon's port cache shape: a per-codespace snapshot used to render the ports section without re-fetching on every repaint.

type PortCacheEntry struct {
    Ports     []codespace.Port
    Err       error
    CheckedAt time.Time
    Loading   bool
}

type ProviderStatus

ProviderStatus mirrors daemon.ProviderStatus inside the TUI package so the list/detail/settings views don't have to import daemon directly.

type ProviderStatus struct {
    Available bool
    Err       error
    CheckedAt time.Time
}

type SpinnerModel

SpinnerModel runs a single background task with a spinner. Used by the CLI launch path for "Listing workspaces", "Preparing SSH config", etc.

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

func NewSpinnerModel

func NewSpinnerModel(message string, task func() error) SpinnerModel

NewSpinnerModel creates a spinner that runs the given task in the background.

func (SpinnerModel) Init

func (m SpinnerModel) Init() tea.Cmd

Init kicks off both the spinner ticker and the background task.

func (SpinnerModel) Result

func (m SpinnerModel) Result() SpinnerResult

Result returns the spinner result.

func (SpinnerModel) Update

func (m SpinnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update advances the spinner and exits when the background task signals done.

func (SpinnerModel) View

func (m SpinnerModel) View() string

View renders the spinner + message line.

type SpinnerResult

SpinnerResult holds the outcome of a spinner task.

type SpinnerResult struct {
    Err  error
    Quit bool
}

Generated by gomarkdoc