tui

package
v0.2.8 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2026 License: AGPL-3.0 Imports: 37 Imported by: 0

Documentation

Index

Constants

View Source
const MaxRecentSessionsBeforeBucket = 5

MaxRecentSessionsBeforeBucket is how many of the most-recent sessions inside an expanded worktree stay visible before the rest collapse into a per-worktree "+ show more" bucket. Sessions don't follow the week-window worktree rule because a single worktree often accumulates many sessions across multiple days; a count cap keeps the list short.

View Source
const RecentWindowDays = 7

RecentWindowDays is the rolling window — anchored on the most-recent worktree's activity — within which a worktree stays visible. Wide enough that creating a fresh session "today" doesn't suddenly hide everything you touched earlier in the week, narrow enough that projects untouched for over a week fall away. If you go heads-down on a single repo for a week, only that one ends up visible — which matches the mental "this is my current focus" model.

Variables

View Source
var ErrSessionTaskTimeout = errors.New("agent task timed out")

ErrSessionTaskTimeout classifies an inline task that exceeded its bound.

Functions

func ApplyPreferredTheme

func ApplyPreferredTheme()

ApplyPreferredTheme loads and applies the persisted TUI color scheme. Missing or unreadable preferences use the default scheme.

func PartitionSessionsByRecency

func PartitionSessionsByRecency(sessions []agent.SessionInfo) (recent, older []agent.SessionInfo)

PartitionSessionsByRecency keeps the first MaxRecentSessionsBeforeBucket sessions visible and folds the rest into a per-worktree overflow bucket. Sorted-newest-first input is assumed.

func PartitionWorktreesByActivityWindow

func PartitionWorktreesByActivityWindow(worktrees []worktreeNode, cwdLocalPath string) (recent, older []worktreeNode)

PartitionWorktreesByActivityWindow keeps every worktree whose latest activity sits within RecentWindowDays of the most-recently-active worktree. The cwd's worktree is always kept visible regardless of age — opening the TUI from a folder is the strongest possible signal that you care about whatever sessions live there. Everything else moves into the top-level overflow bucket.

The caller is responsible for having sorted the input by latest activity descending; the head of the slice anchors the window. cwdLocalPath may be empty (no canonical cwd repo resolved); in that case only the window filter applies.

func TimeAgo

func TimeAgo(t time.Time) string

TimeAgo returns a human-readable relative time string (e.g. "just now", "5m ago", "2h ago", "3d ago").

Types

type ColorScheme

type ColorScheme struct {
	Name      string `json:"name"`
	Primary   string `json:"primary"`
	Secondary string `json:"secondary"`
	Success   string `json:"success"`
	Warning   string `json:"warning"`
	Danger    string `json:"danger"`
	Muted     string `json:"muted"`
	Text      string `json:"text"`
	Dim       string `json:"dim"`
	Draft     string `json:"draft"`
}

ColorScheme is a named palette. All color fields are hex strings (e.g. "#7C3AED") so the struct is trivially JSON-serializable and easy to read/edit.

type ConnectModel

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

ConnectModel drives `clank connect` end to end.

func NewConnectModel

func NewConnectModel(caller ProviderAuthCaller, backend agent.BackendType) *ConnectModel

NewConnectModel returns the connect program. A non-empty backend jumps straight into that backend's provider flow; an empty one shows the backend picker first.

func (*ConnectModel) Init

func (m *ConnectModel) Init() tea.Cmd

func (*ConnectModel) Result

func (m *ConnectModel) Result() ConnectResult

Result reports what the run achieved. Read after the program exits.

IsConnected comes from the auth flow's own terminal state, not from the message that dismisses it: the credential is written before the success screen appears, so quitting at that screen instead of pressing a key still connected the provider.

func (*ConnectModel) Update

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

func (*ConnectModel) View

func (m *ConnectModel) View() tea.View

View renders inline (no alt screen) so the connect flow stays in the terminal's scrollback alongside whatever ran before it — `clank preview` prints around this.

type ConnectResult

type ConnectResult struct {
	Backend     agent.BackendType
	IsConnected bool
}

ConnectResult is what the CLI reads off the model after the program exits. Backend is set as soon as the user picks one, so a canceled run still reports what they were connecting.

type ImportSessionsRequestedMsg

type ImportSessionsRequestedMsg struct{}

ImportSessionsRequestedMsg is emitted when the user activates the "↓ Import Sessions" footer entry in the sidebar.

type InboxModel

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

InboxModel is the top-level Bubble Tea model for the agent inbox. It uses a sidebar + main layout: sidebar shows branches, main area shows sessions. In narrow terminals, only the session pane is shown.

func NewInboxModel

func NewInboxModel(client *daemonclient.Client) *InboxModel

NewInboxModel creates the inbox TUI connected to the given daemon client.

func (*InboxModel) Init

func (m *InboxModel) Init() tea.Cmd

func (*InboxModel) Update

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

func (*InboxModel) View

func (m *InboxModel) View() tea.View

type ProviderAuthCaller

type ProviderAuthCaller interface {
	ListAuthProviders(ctx context.Context, backend agent.BackendType) ([]agent.ProviderAuthInfo, error)
	StartAuthDeviceFlow(ctx context.Context, providerID string) (agent.DeviceFlowStart, error)
	SubmitAuthAPIKey(ctx context.Context, providerID, key string, metadata map[string]string) (agent.DeviceFlowStart, error)
	StartAuthOAuthCodeFlow(ctx context.Context, providerID string) (agent.DeviceFlowStart, error)
	SubmitAuthCode(ctx context.Context, providerID, flowID, code string) error
	AuthFlowStatus(ctx context.Context, providerID, flowID string) (agent.DeviceFlowStatus, error)
	CancelAuthFlow(ctx context.Context, providerID, flowID string) error
}

ProviderAuthCaller is the call surface the modal needs to drive an auth flow against a host. Two implementations exist today:

  • daemonclient.HostClient via hub.Host(hostname), used by the Settings entry to target the local clank-host through the hub, and by `clank connect` outside the inbox entirely.
  • cloud.AuthCaller, used by the Cloud panel's "Connect provider (in sandbox)" entry to talk directly to the active remote gateway with the user's OAuth bearer.

Mirrors the names on daemonclient.HostClient so existing call sites satisfy the interface without changes.

type SessionPickerModel

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

SessionPickerModel drives the attach-session picker end to end.

func NewSessionPickerModel

func NewSessionPickerModel(client *daemonclient.Client, projectDir string) *SessionPickerModel

NewSessionPickerModel returns the picker program. projectDir seeds the rediscover action's backend scan.

func (*SessionPickerModel) Init

func (m *SessionPickerModel) Init() tea.Cmd

func (*SessionPickerModel) Result

Result reports what the run chose. Read after the program exits.

func (*SessionPickerModel) Update

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

func (*SessionPickerModel) View

func (m *SessionPickerModel) View() tea.View

type SessionPickerResult

type SessionPickerResult struct {
	SessionID string
	IsAborted bool
}

SessionPickerResult is what the CLI reads off the model after the program exits. No SessionID means the user canceled (esc/ctrl+c) and the preview run should stop — --attach was explicit, so there is no silent fall-through to a fresh session.

type SessionTaskModel

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

SessionTaskModel is a non-fullscreen, non-composing presentation of the existing session model. It retains the session event and permission paths.

func NewSessionTaskModel

func NewSessionTaskModel(client *daemonclient.Client, sessionID string, options TaskOptions) (*SessionTaskModel, error)

NewSessionTaskModel creates an inline view that exits when one turn settles.

func (*SessionTaskModel) Init

func (m *SessionTaskModel) Init() tea.Cmd

func (*SessionTaskModel) Result

func (m *SessionTaskModel) Result() TaskResult

Result returns the terminal status after the program exits.

func (*SessionTaskModel) SetEventChannel

func (m *SessionTaskModel) SetEventChannel(ch <-chan agent.Event, cancel context.CancelFunc)

SetEventChannel provides the same race-free preconnected event stream used by the full session view.

func (*SessionTaskModel) Update

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

func (*SessionTaskModel) View

func (m *SessionTaskModel) View() tea.View

type SessionViewModel

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

SessionViewModel shows a single agent session with streaming output. It also handles the "composing" mode where no session exists yet — the user types their first prompt and the session is created on send.

func NewSessionViewComposing

func NewSessionViewComposing(client *daemonclient.Client, projectDir string) *SessionViewModel

NewSessionViewComposing creates a SessionViewModel in composing mode. No daemon session exists yet — the user writes their first prompt here.

The gitRef is built from projectDir (LocalPath) plus the stamped worktree ID, if any (read via agent.ReadLocalWorktreeID), so the on-demand config-options probe can target it. Without a stamp the ref is local-only and any cross-host operations will fail at launch.

func NewSessionViewModel

func NewSessionViewModel(client *daemonclient.Client, sessionID string) *SessionViewModel

NewSessionViewModel creates a session detail TUI for an existing session.

func (*SessionViewModel) DraftText

func (m *SessionViewModel) DraftText() string

DraftText returns the current unsent text in the input textarea.

func (*SessionViewModel) Init

func (m *SessionViewModel) Init() tea.Cmd

func (*SessionViewModel) RestoreDraft

func (m *SessionViewModel) RestoreDraft(text string)

RestoreDraft sets the textarea content to the given draft text and activates the input so the user can continue typing immediately.

func (*SessionViewModel) SetEventChannel

func (m *SessionViewModel) SetEventChannel(ch <-chan agent.Event, cancel context.CancelFunc)

SetEventChannel provides a pre-connected SSE event channel and cancel func. When set, Init() skips subscribing and immediately starts reading events. This avoids the race where CreateSession emits events before the TUI subscribes.

func (*SessionViewModel) Update

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

func (*SessionViewModel) View

func (m *SessionViewModel) View() tea.View

type SettingsRequestedMsg

type SettingsRequestedMsg struct{}

SettingsRequestedMsg is emitted by the inbox when the user activates the "⚙ Settings" footer entry in the sidebar.

type SidebarModel

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

SidebarModel renders the sidebar tree and tracks cursor/expand state.

func NewSidebarModel

func NewSidebarModel(client *daemonclient.Client, hostname string, gitRef agent.GitRef, projectDir string) SidebarModel

NewSidebarModel creates a sidebar for the given repo identity. projectDir is retained for display purposes only.

func (*SidebarModel) AdvanceTitleAnimations

func (m *SidebarModel) AdvanceTitleAnimations()

AdvanceTitleAnimations steps every active typewriter forward by titleAnimationCharsPerTick runes. Completed animations are dropped so subsequent frames render the full title without the cursor glyph. Cheap to call every spinner tick even when nothing is animating.

func (*SidebarModel) CursorOnCloud

func (m *SidebarModel) CursorOnCloud() bool

CursorOnCloud reports whether the cursor is on the cloud row.

func (*SidebarModel) CursorOnHome

func (m *SidebarModel) CursorOnHome() bool

CursorOnHome reports whether the cursor is on the Home entry.

func (*SidebarModel) CursorOnImport

func (m *SidebarModel) CursorOnImport() bool

CursorOnImport reports whether the cursor is on the import row.

func (*SidebarModel) CursorOnSettings

func (m *SidebarModel) CursorOnSettings() bool

CursorOnSettings reports whether the cursor is on the settings row.

func (*SidebarModel) CursorWorktreePath

func (m *SidebarModel) CursorWorktreePath() string

CursorWorktreePath returns the LocalPath of the worktree the cursor is currently on (or whose session the cursor is on). Empty means the cursor isn't anywhere worktree-shaped (Older buckets, footer rows). Callers fall back to the cwd's worktree in that case.

Used by the unified "n" gesture to prefill the compose view's target worktree from context.

func (*SidebarModel) Focused

func (m *SidebarModel) Focused() bool

Focused reports whether the sidebar has keyboard focus.

func (*SidebarModel) HandleWheel

func (m *SidebarModel) HandleWheel(button tea.MouseButton) bool

HandleWheel moves the sidebar cursor by one row in the direction of the wheel button, clamped to the list bounds. Returns whether the cursor actually moved.

Wheel scroll moves cursor by 1 row per tick (matching modelpicker's list semantics), independent of the chat's wheelScrollLines step size — sidebar rows are taller and a per-tick row move keeps the gesture predictable. No wraparound: wheel scroll past the ends stops, while keyboard up/down (handleKey) wraps because that's an explicit gesture.

func (*SidebarModel) Init

func (m *SidebarModel) Init() tea.Cmd

Init is a no-op; the sidebar is populated via SetSessions.

func (*SidebarModel) NodeAtRow

func (m *SidebarModel) NodeAtRow(y int) int

NodeAtRow maps a sidebar-local screen row (mouse Y — the sidebar occupies the left columns starting at row 0) to the flat node index rendered there, or -1 when the row is a border / blank / separator or out of range. Reads the map cached by the last View().

func (*SidebarModel) SelectedBranchInfo

func (m *SidebarModel) SelectedBranchInfo() *host.BranchInfo

SelectedBranchInfo always returns nil; merge overlay disabled until sessions carry git branch metadata.

func (*SidebarModel) SelectedSessionID

func (m *SidebarModel) SelectedSessionID() string

SelectedSessionID returns the session id under the cursor, or "" when the cursor isn't on a session row.

func (*SidebarModel) SetActiveSessionID

func (m *SidebarModel) SetActiveSessionID(id string)

SetActiveSessionID stamps the session currently rendered in the right pane. The sidebar uses it to paint a left-edge rail next to that session's row, so the user can see "what's open" independent of where their arrow-key cursor sits. Pass "" to clear.

func (*SidebarModel) SetCloudSpinnerFrame

func (m *SidebarModel) SetCloudSpinnerFrame(frame string)

SetCloudSpinnerFrame feeds the current spinner glyph from the inbox.

func (*SidebarModel) SetCloudStatus

func (m *SidebarModel) SetCloudStatus(s cloudAuthStatus)

SetCloudStatus updates the cloud connection indicator shown next to the "☁ Cloud" footer row.

func (*SidebarModel) SetCursor

func (m *SidebarModel) SetCursor(idx int)

SetCursor moves the cursor to the given flat index, clamped to the list. Used by mouse-click selection to jump straight to a row.

func (*SidebarModel) SetExpanded

func (m *SidebarModel) SetExpanded(seed map[string]bool)

SetExpanded seeds the persisted user-toggle map (e.g. from Preferences.SidebarExpanded). Older buckets always start collapsed regardless of what was persisted — see sanitizeExpanded for the reset rules. Auto-defaults (visible worktrees auto-expand) are applied during rebuildFlat, not stored here. Safe to call before SetSessions.

func (*SidebarModel) SetFocused

func (m *SidebarModel) SetFocused(focused bool)

SetFocused sets whether the sidebar has keyboard focus.

func (*SidebarModel) SetHomeActive

func (m *SidebarModel) SetHomeActive(active bool)

SetHomeActive marks whether the welcome/home screen is the one currently shown in the right pane. The sidebar uses it to keep the Home row highlighted (via a left-edge rail) even when the sidebar itself is unfocused, mirroring the active-session rail.

func (*SidebarModel) SetSessions

func (m *SidebarModel) SetSessions(sessions []agent.SessionInfo)

SetSessions rebuilds the tree from the provided sessions. Cached so toggle operations can re-flatten without re-fetching.

func (*SidebarModel) SetSize

func (m *SidebarModel) SetSize(width, height int)

SetSize sets the sidebar dimensions.

func (*SidebarModel) SnapshotExpanded

func (m *SidebarModel) SnapshotExpanded() map[string]bool

SnapshotExpanded returns a shallow copy of the explicit user toggle map. Auto-defaults (visible worktrees) are deliberately NOT included — they're computed every launch, persisting them would make a future default change invisible to existing users.

func (*SidebarModel) Update

func (m *SidebarModel) Update(msg tea.Msg) tea.Cmd

Update handles messages for the sidebar.

func (*SidebarModel) View

func (m *SidebarModel) View() string

View renders the sidebar by emitting one or more terminal rows per sidebarNode. Worktree and bucket rows render on a single line; session rows render on two (title + relative time). The cursor's row is highlighted with the standard "> " selection prefix.

type TaskOptions

type TaskOptions struct {
	Title           string
	Timeout         time.Duration
	MaxVisibleLines int
}

TaskOptions defines the bounded inline presentation for one agent turn.

type TaskResult

type TaskResult struct {
	Status agent.SessionStatus
	Err    error
}

TaskResult describes why an inline session task stopped.

Jump to

Keyboard shortcuts

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