Documentation
¶
Overview ¶
Package tmux provides theme support for GongShow tmux sessions.
Package tmux provides a wrapper for tmux session operations via subprocess.
Index ¶
- Constants
- Variables
- func IsInsideTmux() bool
- func ListThemeNames() []string
- type SessionInfo
- type SessionSet
- type Theme
- type Tmux
- func (t *Tmux) AcceptBypassPermissionsWarning(session string) error
- func (t *Tmux) ApplyTheme(session string, theme Theme) error
- func (t *Tmux) AttachSession(session string) error
- func (t *Tmux) CapturePane(session string, lines int) (string, error)
- func (t *Tmux) CapturePaneAll(session string) (string, error)
- func (t *Tmux) CapturePaneLines(session string, lines int) ([]string, error)
- func (t *Tmux) ClearHistory(pane string) error
- func (t *Tmux) ConfigureGasTownSession(session string, theme Theme, rig, worker, role string) error
- func (t *Tmux) DisplayMessage(session, message string, durationMs int) error
- func (t *Tmux) DisplayMessageDefault(session, message string) error
- func (t *Tmux) EnableMouseMode(session string) error
- func (t *Tmux) EnsureSessionFresh(name, workDir string) error
- func (t *Tmux) FindSessionByWorkDir(targetDir string, processNames []string) ([]string, error)
- func (t *Tmux) GetAllEnvironment(session string) (map[string]string, error)
- func (t *Tmux) GetEnvironment(session, key string) (string, error)
- func (t *Tmux) GetPaneCommand(session string) (string, error)
- func (t *Tmux) GetPaneID(session string) (string, error)
- func (t *Tmux) GetPanePID(session string) (string, error)
- func (t *Tmux) GetPaneWorkDir(session string) (string, error)
- func (t *Tmux) GetSessionInfo(name string) (*SessionInfo, error)
- func (t *Tmux) GetSessionSet() (*SessionSet, error)
- func (t *Tmux) HasSession(name string) (bool, error)
- func (t *Tmux) IsAgentRunning(session string, expectedPaneCommands ...string) bool
- func (t *Tmux) IsAvailable() bool
- func (t *Tmux) IsClaudeRunning(session string) bool
- func (t *Tmux) IsRuntimeRunning(session string, processNames []string) bool
- func (t *Tmux) KillServer() error
- func (t *Tmux) KillSession(name string) error
- func (t *Tmux) KillSessionWithProcesses(name string) error
- func (t *Tmux) ListSessionIDs() (map[string]string, error)
- func (t *Tmux) ListSessions() ([]string, error)
- func (t *Tmux) NewSession(name, workDir string) error
- func (t *Tmux) NewSessionWithCommand(name, workDir, command string) error
- func (t *Tmux) NudgePane(pane, message string) error
- func (t *Tmux) NudgeSession(session, message string) error
- func (t *Tmux) RenameSession(oldName, newName string) error
- func (t *Tmux) RespawnPane(pane, command string) error
- func (t *Tmux) SelectWindow(session string, index int) error
- func (t *Tmux) SendKeys(session, keys string) error
- func (t *Tmux) SendKeysDebounced(session, keys string, debounceMs int) error
- func (t *Tmux) SendKeysDelayed(session, keys string, delayMs int) error
- func (t *Tmux) SendKeysDelayedDebounced(session, keys string, preDelayMs, debounceMs int) error
- func (t *Tmux) SendKeysRaw(session, keys string) error
- func (t *Tmux) SendKeysReplace(session, keys string, clearDelayMs int) error
- func (t *Tmux) SendNotificationBanner(session, from, subject string) error
- func (t *Tmux) SetCrewCycleBindings(session string) error
- func (t *Tmux) SetCycleBindings(session string) error
- func (t *Tmux) SetDynamicStatus(session string) error
- func (t *Tmux) SetEnvironment(session, key, value string) error
- func (t *Tmux) SetFeedBinding(session string) error
- func (t *Tmux) SetMailClickBinding(session string) error
- func (t *Tmux) SetPaneDiedHook(session, agentID string) error
- func (t *Tmux) SetStatusFormat(session, rig, worker, role string) error
- func (t *Tmux) SetTownCycleBindings(session string) error
- func (t *Tmux) SwitchClient(targetSession string) error
- func (t *Tmux) WaitForCommand(session string, excludeCommands []string, timeout time.Duration) error
- func (t *Tmux) WaitForRuntimeReady(session string, rc *config.RuntimeConfig, timeout time.Duration) error
- func (t *Tmux) WaitForShellReady(session string, timeout time.Duration) error
Constants ¶
const ( // SIGTERMGracePeriod is the time to wait after SIGTERM before sending SIGKILL. // 500ms gives processes time to handle cleanup gracefully. SIGTERMGracePeriod = 500 * time.Millisecond // DescendantRescanDelay is the delay between descendant discovery passes. // This helps catch processes that fork during the initial scan. DescendantRescanDelay = 50 * time.Millisecond // DescendantRescanAttempts is the number of times to rescan for new descendants. // Multiple passes help catch race conditions where processes fork during cleanup. DescendantRescanAttempts = 3 )
Process cleanup constants
Variables ¶
var ( ErrNoServer = errors.New("no tmux server running") ErrSessionExists = errors.New("session already exists") ErrSessionNotFound = errors.New("session not found") )
Common errors
var DefaultPalette = []Theme{
{Name: "ocean", BG: "#1e3a5f", FG: "#e0e0e0"},
{Name: "forest", BG: "#2d5a3d", FG: "#e0e0e0"},
{Name: "rust", BG: "#8b4513", FG: "#f5f5dc"},
{Name: "plum", BG: "#4a3050", FG: "#e0e0e0"},
{Name: "slate", BG: "#4a5568", FG: "#e0e0e0"},
{Name: "ember", BG: "#b33a00", FG: "#f5f5dc"},
{Name: "midnight", BG: "#1a1a2e", FG: "#c0c0c0"},
{Name: "wine", BG: "#722f37", FG: "#f5f5dc"},
{Name: "teal", BG: "#0d5c63", FG: "#e0e0e0"},
{Name: "copper", BG: "#6d4c41", FG: "#f5f5dc"},
}
DefaultPalette is the curated set of distinct, professional color themes. Each theme has good contrast and is visually distinct from others.
Functions ¶
func IsInsideTmux ¶
func IsInsideTmux() bool
IsInsideTmux checks if the current process is running inside a tmux session. This is detected by the presence of the TMUX environment variable.
func ListThemeNames ¶
func ListThemeNames() []string
ListThemeNames returns the names of all themes in the default palette.
Types ¶
type SessionInfo ¶
type SessionInfo struct {
Name string
Windows int
Created string
Attached bool
Activity string // Last activity time
LastAttached string // Last time the session was attached
}
SessionInfo contains information about a tmux session.
type SessionSet ¶
type SessionSet struct {
// contains filtered or unexported fields
}
SessionSet provides O(1) session existence checks by caching session names. Use this when you need to check multiple sessions to avoid N+1 subprocess calls.
func (*SessionSet) Has ¶
func (s *SessionSet) Has(name string) bool
Has returns true if the session exists in the set. This is an O(1) lookup - no subprocess is spawned.
func (*SessionSet) Names ¶
func (s *SessionSet) Names() []string
Names returns all session names in the set.
type Theme ¶
type Theme struct {
Name string // Human-readable name
BG string // Background color (hex or tmux color name)
FG string // Foreground color (hex or tmux color name)
}
Theme represents a tmux status bar color scheme.
func AssignTheme ¶
AssignTheme picks a theme for a rig based on its name. Uses consistent hashing so the same rig always gets the same color.
func AssignThemeFromPalette ¶
AssignThemeFromPalette picks a theme using a custom palette.
func DeaconTheme ¶
func DeaconTheme() Theme
DeaconTheme returns the special theme for the Deacon session. Purple/silver - ecclesiastical, distinct from Mayor's gold.
func GetThemeByName ¶
GetThemeByName finds a theme by name from the default palette. Returns nil if not found.
func MayorTheme ¶
func MayorTheme() Theme
MayorTheme returns the special theme for the Mayor session. Gold/dark to distinguish it from rig themes.
type Tmux ¶
type Tmux struct{}
Tmux wraps tmux operations.
func (*Tmux) AcceptBypassPermissionsWarning ¶
AcceptBypassPermissionsWarning dismisses the Claude Code bypass permissions warning dialog. When Claude starts with --dangerously-skip-permissions, it shows a warning dialog that requires pressing Down arrow to select "Yes, I accept" and then Enter to confirm. This function checks if the warning is present before sending keys to avoid interfering with sessions that don't show the warning (e.g., already accepted or different config).
Call this after starting Claude and waiting for it to initialize (WaitForCommand), but before sending any prompts.
func (*Tmux) ApplyTheme ¶
ApplyTheme sets the status bar style for a session.
func (*Tmux) AttachSession ¶
AttachSession attaches to an existing session. Note: This replaces the current process with tmux attach.
func (*Tmux) CapturePane ¶
CapturePane captures the visible content of a pane.
func (*Tmux) CapturePaneAll ¶
CapturePaneAll captures all scrollback history.
func (*Tmux) CapturePaneLines ¶
CapturePaneLines captures the last N lines of a pane as a slice.
func (*Tmux) ClearHistory ¶
ClearHistory clears the scrollback history buffer for a pane. This resets copy-mode display from 0/N to 0/0. The pane parameter should be a pane ID (e.g., "%0") or session:window.pane format.
func (*Tmux) ConfigureGasTownSession ¶
ConfigureGasTownSession applies full GongShow theming to a session. This is a convenience method that applies theme, status format, and dynamic status.
func (*Tmux) DisplayMessage ¶
DisplayMessage shows a message in the tmux status line. This is non-disruptive - it doesn't interrupt the session's input. Duration is specified in milliseconds.
func (*Tmux) DisplayMessageDefault ¶
DisplayMessageDefault shows a message with default duration (5 seconds).
func (*Tmux) EnableMouseMode ¶
EnableMouseMode enables mouse support for a tmux session. This allows clicking to select panes/windows, scrolling with mouse wheel, and dragging to resize panes. Hold Shift for native terminal text selection.
func (*Tmux) EnsureSessionFresh ¶
EnsureSessionFresh ensures a session is available and healthy. If the session exists but is a zombie (Claude not running), it kills the session first. This prevents "session already exists" errors when trying to restart dead agents.
A session is considered a zombie if: - The tmux session exists - But Claude (node process) is not running in it
Returns nil if session was created successfully.
func (*Tmux) FindSessionByWorkDir ¶
FindSessionByWorkDir finds tmux sessions where the pane's current working directory matches or is under the target directory. Returns session names that match. If processNames is provided, only returns sessions that match those processes. If processNames is nil or empty, returns all sessions matching the directory.
func (*Tmux) GetAllEnvironment ¶
GetAllEnvironment returns all environment variables for a session.
func (*Tmux) GetEnvironment ¶
GetEnvironment gets an environment variable from the session.
func (*Tmux) GetPaneCommand ¶
GetPaneCommand returns the current command running in a pane. Returns "bash", "zsh", "claude", "node", etc.
func (*Tmux) GetPaneID ¶
GetPaneID returns the pane identifier for a session's first pane. Returns a pane ID like "%0" that can be used with RespawnPane.
func (*Tmux) GetPanePID ¶
GetPanePID returns the PID of the pane's main process.
func (*Tmux) GetPaneWorkDir ¶
GetPaneWorkDir returns the current working directory of a pane.
func (*Tmux) GetSessionInfo ¶
func (t *Tmux) GetSessionInfo(name string) (*SessionInfo, error)
GetSessionInfo returns detailed information about a session.
func (*Tmux) GetSessionSet ¶
func (t *Tmux) GetSessionSet() (*SessionSet, error)
GetSessionSet returns a SessionSet containing all current sessions. Call this once at the start of an operation, then use Has() for O(1) checks. This replaces multiple HasSession() calls with a single ListSessions() call.
Builds the map directly from tmux output to avoid intermediate slice allocation.
func (*Tmux) HasSession ¶
HasSession checks if a session exists (exact match). Uses "=" prefix for exact matching, preventing prefix matches (e.g., "gt-deacon-boot" won't match when checking for "gt-deacon").
func (*Tmux) IsAgentRunning ¶
IsAgentRunning checks if an agent appears to be running in the session.
If expectedPaneCommands is non-empty, the pane's current command must match one of them. If expectedPaneCommands is empty, any non-shell command counts as "agent running".
func (*Tmux) IsAvailable ¶
IsAvailable checks if tmux is installed and can be invoked.
func (*Tmux) IsClaudeRunning ¶
IsClaudeRunning checks if Claude appears to be running in the session. Only trusts the pane command - UI markers in scrollback cause false positives. Claude can report as "node", "claude", or a version number like "2.0.76". Also checks for child processes when the pane is a shell running claude via "bash -c".
func (*Tmux) IsRuntimeRunning ¶
IsRuntimeRunning checks if a runtime appears to be running in the session. Only trusts the pane command - UI markers in scrollback cause false positives. This is the runtime-config-aware version of IsAgentRunning.
func (*Tmux) KillServer ¶
KillServer terminates the entire tmux server and all sessions.
func (*Tmux) KillSession ¶
KillSession terminates a tmux session.
func (*Tmux) KillSessionWithProcesses ¶
KillSessionWithProcesses explicitly kills all processes in a session before terminating it. This prevents orphan processes that survive tmux kill-session due to SIGHUP being ignored.
Process: 1. Get the pane's main process PID 2. Find all descendant processes with retry/rescan to catch forks 3. Send SIGTERM to all descendants (deepest first to avoid orphaning grandchildren) 4. Wait for graceful shutdown (SIGTERMGracePeriod) 5. Rescan for any processes that forked during SIGTERM handling 6. Send SIGKILL to all descendants (deepest first, including newly discovered) 7. Kill the tmux session
The retry/rescan approach addresses race conditions where processes fork new children after the initial descendant list is built but before kill signals are sent. SIGKILL is sent in deepest-first order to prevent brief orphaning of grandchildren.
Performance: Uses native Go syscalls instead of spawning shell commands for each signal.
func (*Tmux) ListSessionIDs ¶
ListSessionIDs returns a map of session name to session ID. Session IDs are in the format "$N" where N is a number.
func (*Tmux) ListSessions ¶
ListSessions returns all session names.
func (*Tmux) NewSession ¶
NewSession creates a new detached tmux session.
func (*Tmux) NewSessionWithCommand ¶
NewSessionWithCommand creates a new detached tmux session that immediately runs a command. Unlike NewSession + SendKeys, this avoids race conditions where the shell isn't ready or the command arrives before the shell prompt. The command runs directly as the initial process of the pane. See: https://github.com/anthropics/gongshow/issues/280
func (*Tmux) NudgePane ¶
NudgePane sends a message to a specific pane reliably. Same pattern as NudgeSession but targets a pane ID (e.g., "%9") instead of session name.
func (*Tmux) NudgeSession ¶
NudgeSession sends a message to a Claude Code session reliably. This is the canonical way to send messages to Claude sessions. Uses: literal mode + 500ms debounce + ESC (for vim mode) + separate Enter. Verification is the Witness's job (AI), not this function.
func (*Tmux) RenameSession ¶
RenameSession renames a session.
func (*Tmux) RespawnPane ¶
RespawnPane kills all processes in a pane and starts a new command. This is used for "hot reload" of agent sessions - instantly restart in place. The pane parameter should be a pane ID (e.g., "%0") or session:window.pane format.
func (*Tmux) SelectWindow ¶
SelectWindow selects a window by index.
func (*Tmux) SendKeys ¶
SendKeys sends keystrokes to a session and presses Enter. Always sends Enter as a separate command for reliability. Uses a debounce delay between paste and Enter to ensure paste completes.
func (*Tmux) SendKeysDebounced ¶
SendKeysDebounced sends keystrokes with a configurable delay before Enter. The debounceMs parameter controls how long to wait after paste before sending Enter. This prevents race conditions where Enter arrives before paste is processed.
func (*Tmux) SendKeysDelayed ¶
SendKeysDelayed sends keystrokes after a delay (in milliseconds). Useful for waiting for a process to be ready before sending input.
func (*Tmux) SendKeysDelayedDebounced ¶
SendKeysDelayedDebounced sends keystrokes after a pre-delay, with a custom debounce before Enter. Use this when sending input to a process that needs time to initialize AND the message needs extra time between paste and Enter (e.g., Claude prompt injection). preDelayMs: time to wait before sending text (for process readiness) debounceMs: time to wait between text paste and Enter key (for paste completion)
func (*Tmux) SendKeysRaw ¶
SendKeysRaw sends keystrokes without adding Enter.
func (*Tmux) SendKeysReplace ¶
SendKeysReplace sends keystrokes, clearing any pending input first. This is useful for "replaceable" notifications where only the latest matters. Uses Ctrl-U to clear the input line before sending the new message. The delay parameter controls how long to wait after clearing before sending (ms).
func (*Tmux) SendNotificationBanner ¶
SendNotificationBanner sends a visible notification banner to a tmux session. This interrupts the terminal to ensure the notification is seen. Uses echo to print a boxed banner with the notification details.
func (*Tmux) SetCrewCycleBindings ¶
SetCrewCycleBindings sets up C-b n/p to cycle through sessions. This is now an alias for SetCycleBindings - the unified command detects session type automatically.
IMPORTANT: We pass #{session_name} to the command because run-shell doesn't reliably preserve the session context. tmux expands #{session_name} at binding resolution time (when the key is pressed), giving us the correct session.
func (*Tmux) SetCycleBindings ¶
SetCycleBindings sets up C-b n/p to cycle through related sessions. The gt cycle command automatically detects the session type and cycles within the appropriate group: - Town sessions: Mayor ↔ Deacon - Crew sessions: All crew members in the same rig
IMPORTANT: These bindings are conditional - they only run gt cycle for GongShow sessions (those starting with "gt-" or "hq-"). For non-GT sessions, the default tmux behavior (next-window/previous-window) is preserved. See: https://github.com/KeithWyatt/gongshow/issues/13
IMPORTANT: We pass #{session_name} to the command because run-shell doesn't reliably preserve the session context. tmux expands #{session_name} at binding resolution time (when the key is pressed), giving us the correct session.
func (*Tmux) SetDynamicStatus ¶
SetDynamicStatus configures the right side with dynamic content. Uses a shell command that tmux calls periodically to get current status.
func (*Tmux) SetEnvironment ¶
SetEnvironment sets an environment variable in the session.
func (*Tmux) SetFeedBinding ¶
SetFeedBinding configures C-b a to jump to the activity feed window. This creates the feed window if it doesn't exist, or switches to it if it does. Uses `gt feed --window` which handles both creation and switching.
IMPORTANT: This binding is conditional - it only runs for GongShow sessions (those starting with "gt-" or "hq-"). For non-GT sessions, a help message is shown. See: https://github.com/KeithWyatt/gongshow/issues/13
func (*Tmux) SetMailClickBinding ¶
SetMailClickBinding configures left-click on status-right to show mail preview. This creates a popup showing the first unread message when clicking the mail icon area.
func (*Tmux) SetPaneDiedHook ¶
SetPaneDiedHook sets a pane-died hook on a session to detect crashes. When the pane exits, tmux runs the hook command with exit status info. The agentID is used to identify the agent in crash logs (e.g., "gongshow/Toast").
func (*Tmux) SetStatusFormat ¶
SetStatusFormat configures the left side of the status bar. Shows compact identity: icon + minimal context
func (*Tmux) SetTownCycleBindings ¶
SetTownCycleBindings sets up C-b n/p to cycle through sessions. This is now an alias for SetCycleBindings - the unified command detects session type automatically.
func (*Tmux) SwitchClient ¶
SwitchClient switches the current tmux client to a different session. Used after remote recycle to move the user's view to the recycled session.
func (*Tmux) WaitForCommand ¶
func (t *Tmux) WaitForCommand(session string, excludeCommands []string, timeout time.Duration) error
WaitForCommand polls until the pane is NOT running one of the excluded commands. Useful for waiting until a shell has started a new process (e.g., claude). Returns nil when a non-excluded command is detected, or error on timeout.
func (*Tmux) WaitForRuntimeReady ¶
func (t *Tmux) WaitForRuntimeReady(session string, rc *config.RuntimeConfig, timeout time.Duration) error
WaitForRuntimeReady polls until the runtime's prompt indicator appears in the pane. Runtime is ready when we see the configured prompt prefix at the start of a line.
IMPORTANT: Bootstrap vs Steady-State Observation
This function uses regex to detect runtime prompts - a ZFC violation. ZFC (Zero False Commands) principle: AI should observe AI, not regex.
Bootstrap (acceptable):
During cold startup when no AI agent is running, the daemon uses this function to get the Deacon online. Regex is acceptable here.
Steady-State (use AI observation instead):
Once any AI agent is running, observation should be AI-to-AI: - Deacon starting polecats → use 'gt deacon pending' + AI analysis - Deacon restarting → Mayor watches via 'gt peek' - Mayor restarting → Deacon watches via 'gt peek'
See: gt deacon pending (ZFC-compliant AI observation) See: gt deacon trigger-pending (bootstrap mode, regex-based)