Documentation
¶
Index ¶
- Constants
- func AddProject(name, baseDir, baseBranch string) error
- func CheckClaudeHooksInstalled(configDir string) bool
- func CleanStaleEventFiles()
- func ClearMCPCache(projectPath string)
- func ClearProjectMCPs(projectPath string) error
- func ClearUserConfigCache()
- func ConductorSessionTitle(name string) string
- func ConvertToClaudeDirName(path string) string
- func CreateExampleConfig() error
- func CreateProfile(profile string) error
- func DeleteProfile(profile string) error
- func DetectBaseBranch(repoPath string) string
- func ExpandPath(path string) string
- func GenerateSessionName() string
- func GenerateUniqueSessionName(instances []*Instance, groupPath string) string
- func GetAvailableGeminiModels() ([]string, error)
- func GetAvailableMCPs() map[string]MCPDef
- func GetClaudeCommand() string
- func GetClaudeConfigDir() string
- func GetClaudeSessionID(projectPath string) (string, error)
- func GetConfigPath() (string, error)
- func GetCustomToolNames() []string
- func GetDBPathForProfile(profile string) (string, error)
- func GetDefaultTool() string
- func GetDirectoryCompletions(input string) ([]string, error)
- func GetEffectiveProfile(explicit string) string
- func GetEventsDir() string
- func GetGeminiConfigDir() string
- func GetGeminiMCPNames() []string
- func GetGeminiSessionsDir(projectPath string) string
- func GetGlobalMCPNames() []string
- func GetGroupLevel(path string) int
- func GetHangarDir() (string, error)
- func GetHooksDir() string
- func GetMCPDefaultScope() string
- func GetManageMCPJson() bool
- func GetProfileDir(profile string) (string, error)
- func GetProfilesDir() (string, error)
- func GetProjectMCPNames(projectPath string) []string
- func GetProjectsFilePath() (string, error)
- func GetTheme() string
- func GetToolBusyPatterns(toolName string) []string
- func GetToolIcon(toolName string) string
- func GetUserConfigPath() (string, error)
- func GetUserMCPNames() []string
- func GetUserMCPRootPath() string
- func GroupByProject(instances []*Instance) map[string][]*Instance
- func HashProjectPath(projectPath string) string
- func InjectClaudeHooks(configDir string) (bool, error)
- func InstallBridgeScript() error
- func IsClaudeConfigDirExplicit() bool
- func ListProfiles() ([]string, error)
- func MarshalToolOptions(opts ToolOptions) (json.RawMessage, error)
- func MergeToolPatterns(toolName string) *tmux.RawPatterns
- func NeedsMigration() (bool, error)
- func ProfileExists(profile string) (bool, error)
- func PruneMCPCache(maxAge time.Duration)
- func ReadPrevEventStatus(instanceID string) string
- func RemoveClaudeHooks(configDir string) (bool, error)
- func RemoveProject(name string) error
- func RenameProject(oldName, newName string) error
- func ResolveTheme() string
- func RestoreFromArchive(baseDir string) error
- func SaveConfig(config *Config) error
- func SaveProjects(projects []*Project) error
- func SaveUserConfig(config *UserConfig) error
- func SendSessionMessageReliable(profile, sessionRef, message string) error
- func SetDefaultProfile(profile string) error
- func ShouldNotifyTransition(fromStatus, toStatus string) bool
- func StartMaintenanceWorker(ctx context.Context, onComplete func(MaintenanceResult))
- func TierName(tier SearchTier) string
- func ToggleLocalMCP(projectPath, mcpName string) error
- func UpdateClaudeSessionsWithDedup(instances []*Instance)
- func UpdateGeminiAnalyticsFromDisk(projectPath, sessionID string, analytics *GeminiSessionAnalytics) error
- func WriteGeminiMCPSettings(enabledNames []string) error
- func WriteGlobalMCP(enabledNames []string) error
- func WriteMCPJsonFromConfig(projectPath string, enabledNames []string) error
- func WriteStatusEvent(event StatusEvent) error
- func WriteUserMCP(enabledNames []string) error
- type AnalyticsDisplaySettings
- type BillingBlock
- type ClaudeConfig
- type ClaudeOptions
- type ClaudeProject
- type ClaudeSettings
- type CodexOptions
- type CodexSettings
- type CompletionCycler
- type ConductorMeta
- type ConductorSettings
- type Config
- type ContentBuffer
- type ExperimentsSettings
- type FileTracker
- type GeminiMCPConfig
- type GeminiModelPricing
- type GeminiSessionAnalytics
- type GeminiSessionInfo
- type GeminiSettings
- type GlobalSearchIndex
- func (idx *GlobalSearchIndex) Close()
- func (idx *GlobalSearchIndex) EntryCount() int
- func (idx *GlobalSearchIndex) FuzzySearch(query string) []*SearchResult
- func (idx *GlobalSearchIndex) GetTier() SearchTier
- func (idx *GlobalSearchIndex) IsLoading() bool
- func (idx *GlobalSearchIndex) Search(query string) []*SearchResult
- type GlobalSearchSettings
- type Group
- type GroupData
- type GroupTree
- func (t *GroupTree) AddSession(inst *Instance)
- func (t *GroupTree) CollapseGroup(path string)
- func (t *GroupTree) CreateGroup(name string) *Group
- func (t *GroupTree) CreateSubgroup(parentPath, name string) *Group
- func (t *GroupTree) DefaultPathForGroup(groupPath string) string
- func (t *GroupTree) DeleteGroup(path string) []*Instance
- func (t *GroupTree) ExpandGroup(path string)
- func (t *GroupTree) ExpandGroupWithParents(path string)
- func (t *GroupTree) Flatten() []Item
- func (t *GroupTree) GetAllInstances() []*Instance
- func (t *GroupTree) GetGroupNames() []string
- func (t *GroupTree) GetGroupPaths() []string
- func (t *GroupTree) GroupCount() int
- func (t *GroupTree) MoveGroupDown(path string)
- func (t *GroupTree) MoveGroupUp(path string)
- func (t *GroupTree) MoveSessionDown(inst *Instance)
- func (t *GroupTree) MoveSessionToGroup(inst *Instance, newGroupPath string)
- func (t *GroupTree) MoveSessionUp(inst *Instance)
- func (t *GroupTree) RemoveSession(inst *Instance)
- func (t *GroupTree) RenameGroup(oldPath, newName string)
- func (t *GroupTree) SessionCount() int
- func (t *GroupTree) SessionCountForGroup(groupPath string) int
- func (t *GroupTree) SetDefaultPathForGroup(groupPath, defaultPath string) bool
- func (t *GroupTree) ShallowCopyForSave() *GroupTree
- func (t *GroupTree) SyncWithInstances(instances []*Instance)
- func (t *GroupTree) ToggleGroup(path string)
- type HTTPServerConfig
- type HookStatus
- type Instance
- func DiscoverExistingTmuxSessions(existingInstances []*Instance) ([]*Instance, error)
- func FilterByQuery(instances []*Instance, query string) []*Instance
- func NewInstance(title, projectPath string) *Instance
- func NewInstanceWithGroup(title, projectPath, groupPath string) *Instance
- func NewInstanceWithGroupAndTool(title, projectPath, groupPath, tool string) *Instance
- func NewInstanceWithTool(title, projectPath, tool string) *Instance
- func (i *Instance) CanFork() bool
- func (i *Instance) CanForkOpenCode() bool
- func (i *Instance) CanRestart() bool
- func (i *Instance) CanRestartGeneric() bool
- func (i *Instance) CaptureLoadedMCPs()
- func (i *Instance) ClearHookStatus()
- func (inst *Instance) ClearParent()
- func (i *Instance) CreateForkedInstance(newTitle, newGroupPath string) (*Instance, string, error)
- func (i *Instance) CreateForkedInstanceWithOptions(newTitle, newGroupPath string, opts *ClaudeOptions) (*Instance, string, error)
- func (i *Instance) CreateForkedOpenCodeInstance(newTitle, newGroupPath string) (*Instance, string, error)
- func (i *Instance) CreateForkedOpenCodeInstanceWithOptions(newTitle, newGroupPath string, opts *OpenCodeOptions) (*Instance, string, error)
- func (i *Instance) DetectCodexSession()
- func (i *Instance) DetectOpenCodeSession()
- func (i *Instance) Exists() bool
- func (i *Instance) ForceNextStatusCheck()
- func (i *Instance) Fork(newTitle, newGroupPath string) (string, error)
- func (i *Instance) ForkOpenCode(newTitle, newGroupPath string) (string, error)
- func (i *Instance) ForkOpenCodeWithOptions(newTitle, newGroupPath string, opts *OpenCodeOptions) (string, error)
- func (i *Instance) ForkWithOptions(newTitle, newGroupPath string, opts *ClaudeOptions) (string, error)
- func (i *Instance) GetActualWorkDir() string
- func (i *Instance) GetClaudeOptions() *ClaudeOptions
- func (i *Instance) GetCodexOptions() *CodexOptions
- func (i *Instance) GetGenericSessionID() string
- func (i *Instance) GetHookStatus() (string, bool)
- func (i *Instance) GetJSONLPath() string
- func (inst *Instance) GetLastActivityTime() time.Time
- func (i *Instance) GetLastResponse() (*ResponseOutput, error)
- func (i *Instance) GetLastResponseBestEffort() (*ResponseOutput, error)
- func (i *Instance) GetMCPInfo() *MCPInfo
- func (i *Instance) GetOpenCodeOptions() *OpenCodeOptions
- func (i *Instance) GetSessionIDFromTmux() string
- func (inst *Instance) GetStatusThreadSafe() Status
- func (i *Instance) GetTmuxSession() *tmux.Session
- func (inst *Instance) GetToolThreadSafe() string
- func (inst *Instance) GetWaitingSince() time.Time
- func (i *Instance) HasUpdated() bool
- func (inst *Instance) IsSubSession() bool
- func (inst *Instance) IsWorktree() bool
- func (i *Instance) Kill() error
- func (inst *Instance) MarkAccessed()
- func (i *Instance) PostStartSync(maxWait time.Duration)
- func (i *Instance) Preview() (string, error)
- func (i *Instance) PreviewFull() (string, error)
- func (i *Instance) Restart() error
- func (i *Instance) SendText(text string) error
- func (i *Instance) SetAcknowledgedFromShared(ack bool)
- func (i *Instance) SetClaudeOptions(opts *ClaudeOptions) error
- func (i *Instance) SetCodexOptions(opts *CodexOptions) error
- func (i *Instance) SetGeminiModel(model string) error
- func (i *Instance) SetGeminiYoloMode(enabled bool)
- func (i *Instance) SetOpenCodeOptions(opts *OpenCodeOptions) error
- func (inst *Instance) SetParent(parentID string)
- func (inst *Instance) SetParentWithPath(parentID, parentProjectPath string)
- func (inst *Instance) SetStatusThreadSafe(s Status)
- func (inst *Instance) SetToolThreadSafe(t string)
- func (i *Instance) Start() error
- func (i *Instance) StartWithMessage(message string) error
- func (i *Instance) SyncSessionIDsToTmux()
- func (i *Instance) SyncTmuxDisplayName()
- func (i *Instance) UpdateClaudeSession(excludeIDs map[string]bool)
- func (i *Instance) UpdateCodexSession(excludeIDs map[string]bool)
- func (i *Instance) UpdateGeminiSession(excludeIDs map[string]bool)
- func (i *Instance) UpdateHookStatus(status *HookStatus)
- func (i *Instance) UpdateStatus() error
- func (i *Instance) WaitForClaudeSession(maxWait time.Duration) string
- func (i *Instance) WaitForClaudeSessionWithExclude(maxWait time.Duration, excludeIDs map[string]bool) string
- type InstanceData
- type InstanceSettings
- type Item
- type ItemType
- type LocalMCP
- type LogSettings
- type MCPDef
- type MCPInfo
- type MCPMode
- type MCPServer
- type MCPServerConfig
- type MaintenanceResult
- type MaintenanceSettings
- type MatchRange
- type MigrationResult
- type ModelPricing
- type NotificationEntry
- type NotificationManager
- func (nm *NotificationManager) Add(inst *Instance) error
- func (nm *NotificationManager) Clear()
- func (nm *NotificationManager) Count() int
- func (nm *NotificationManager) FormatBar() string
- func (nm *NotificationManager) GetEntries() []*NotificationEntry
- func (nm *NotificationManager) GetSessionByKey(key string) *NotificationEntry
- func (nm *NotificationManager) Has(sessionID string) bool
- func (nm *NotificationManager) IsMinimal() bool
- func (nm *NotificationManager) Remove(sessionID string)
- func (nm *NotificationManager) SyncFromInstances(instances []*Instance, currentSessionID string) (added, removed []string)
- type NotificationsConfig
- type OpenCodeOptions
- type OpenCodeSettings
- type PreviewSettings
- type ProfileClaudeSettings
- type ProfileSettings
- type Project
- type ProjectMCPSettings
- type ProjectsManager
- type ResponseOutput
- type SearchEntry
- type SearchResult
- type SearchTier
- type SessionAnalytics
- type ShellSettings
- type SlackSettings
- type Status
- type StatusEvent
- type StatusEventWatcher
- type StatusFileWatcher
- type StatusSettings
- type Storage
- func (s *Storage) Close() error
- func (s *Storage) DeleteInstance(id string) error
- func (s *Storage) DeleteTodo(id string) error
- func (s *Storage) DeleteTodosForSession(sessionID string) error
- func (s *Storage) FindTodoBySessionID(sessionID string) (*Todo, error)
- func (s *Storage) GetDB() *statedb.StateDB
- func (s *Storage) GetFileMtime() (time.Time, error)
- func (s *Storage) GetUpdatedAt() (time.Time, error)
- func (s *Storage) Load() ([]*Instance, error)
- func (s *Storage) LoadLite() ([]*InstanceData, []*GroupData, error)
- func (s *Storage) LoadTodos(projectPath string) ([]*Todo, error)
- func (s *Storage) LoadWithGroups() ([]*Instance, []*GroupData, error)
- func (s *Storage) OrphanTodosForSession(sessionID string) error
- func (s *Storage) Path() string
- func (s *Storage) Profile() string
- func (s *Storage) Save(instances []*Instance) error
- func (s *Storage) SaveGroupsOnly(groupTree *GroupTree) error
- func (s *Storage) SaveTodo(todo *Todo) error
- func (s *Storage) SaveWithGroups(instances []*Instance, groupTree *GroupTree) error
- func (s *Storage) UpdateTodoStatus(id string, status TodoStatus, sessionID string) error
- type StorageData
- type SubagentInfo
- type TelegramSettings
- type TmuxSettings
- type Todo
- type TodoStatus
- type ToolCall
- type ToolDef
- type ToolOptions
- type ToolOptionsWrapper
- type TransitionDaemon
- type TransitionNotificationEvent
- type TransitionNotifier
- type UpdateSettings
- type UserConfig
- type WorktreeSettings
Constants ¶
const ( // DefaultProfile is the name of the default profile DefaultProfile = "default" // ProfilesDirName is the directory containing all profiles ProfilesDirName = "profiles" // ConfigFileName is the global config file name ConfigFileName = "config.json" )
const TierThresholdBalanced = 500 * 1024 * 1024
TierThresholdBalanced is the max size for balanced tier (500MB)
const TierThresholdInstant = 100 * 1024 * 1024
TierThresholdInstant is the max size for instant tier (100MB)
const UserConfigFileName = "config.toml"
UserConfigFileName is the TOML config file for user preferences
Variables ¶
This section is empty.
Functions ¶
func AddProject ¶
AddProject adds a new project. Returns error if name already exists.
func CheckClaudeHooksInstalled ¶
CheckClaudeHooksInstalled checks if hangar hooks are present in settings.json.
func CleanStaleEventFiles ¶
func CleanStaleEventFiles()
CleanStaleEventFiles removes event files older than 24 hours.
func ClearMCPCache ¶
func ClearMCPCache(projectPath string)
ClearMCPCache invalidates the MCP cache for a project path and all parent directories This is important because getMCPInfoUncached walks up parent directories to find .mcp.json
func ClearProjectMCPs ¶
ClearProjectMCPs removes all MCPs from projects[path].mcpServers in Claude's config
func ClearUserConfigCache ¶
func ClearUserConfigCache()
ClearUserConfigCache clears the cached user config, allowing tests to reset state This does NOT reload - the next LoadUserConfig() call will read fresh from disk
func ConductorSessionTitle ¶
ConductorSessionTitle returns the tmux session title for a conductor (stub).
func ConvertToClaudeDirName ¶
ConvertToClaudeDirName converts a filesystem path to Claude's directory naming format. Claude Code replaces all non-alphanumeric characters (except hyphens) with hyphens. Example: /Users/master/Code cloud/!Project → -Users-master-Code-cloud--Project
func CreateExampleConfig ¶
func CreateExampleConfig() error
CreateExampleConfig creates an example config file if none exists
func CreateProfile ¶
CreateProfile creates a new empty profile
func DeleteProfile ¶
DeleteProfile deletes a profile and all its data
func DetectBaseBranch ¶
DetectBaseBranch auto-detects the default branch for a git repo. Tries: git symbolic-ref refs/remotes/origin/HEAD, then falls back to "main" or "master".
func ExpandPath ¶
ExpandPath expands environment variables and ~ prefix in a path. Use resolvePath when relative paths also need to be resolved against a working directory.
func GenerateSessionName ¶
func GenerateSessionName() string
GenerateSessionName returns a random "adjective-noun" name.
func GenerateUniqueSessionName ¶
GenerateUniqueSessionName generates a name that doesn't collide with existing session titles in the given group. Retries up to 10 times, then falls back to appending a timestamp.
func GetAvailableGeminiModels ¶
GetAvailableGeminiModels returns a sorted list of Gemini models that support generateContent. Priority: 1) GEMINI_MODELS_OVERRIDE env var, 2) cached API result, 3) live API call, 4) fallback list.
func GetAvailableMCPs ¶
GetAvailableMCPs returns MCPs from config.toml as a map This replaces the old catalog-based approach with explicit user configuration
func GetClaudeCommand ¶
func GetClaudeCommand() string
GetClaudeCommand returns the configured Claude command/alias Priority: 1) UserConfig setting, 2) Default "claude" This allows users to configure an alias like "cdw" or "cdp" that sets CLAUDE_CONFIG_DIR automatically, avoiding the need for config_dir setting
func GetClaudeConfigDir ¶
func GetClaudeConfigDir() string
GetClaudeConfigDir returns the Claude config directory for the active profile. Priority: 1. CLAUDE_CONFIG_DIR env var 2. profile-specific override: [profiles.<profile>.claude].config_dir 3. global setting: [claude].config_dir 4. default: ~/.claude
func GetClaudeSessionID ¶
GetClaudeSessionID returns the ACTIVE session ID for a project path It first tries to find the currently running session by checking recently modified .jsonl files, then falls back to lastSessionId from config
func GetConfigPath ¶
GetConfigPath returns the path to the global config file
func GetCustomToolNames ¶
func GetCustomToolNames() []string
GetCustomToolNames returns sorted custom tool names from config.toml, excluding names that shadow built-in tools (claude, gemini, opencode, codex, shell, cursor, aider). Returns nil if no custom tools are configured.
func GetDBPathForProfile ¶
GetDBPathForProfile returns the path to the state.db file for a specific profile.
func GetDefaultTool ¶
func GetDefaultTool() string
GetDefaultTool returns the user's preferred default tool for new sessions Returns empty string if not configured (defaults to shell)
func GetDirectoryCompletions ¶
GetDirectoryCompletions returns a list of directories that match the input prefix. Supports absolute, relative, and tilde-prefixed (~) paths.
func GetEffectiveProfile ¶
GetEffectiveProfile returns the profile to use, considering: 1. Explicitly provided profile (from -p flag) 2. Environment variable HANGAR_PROFILE 3. Config default profile 4. Fallback to "default"
func GetEventsDir ¶
func GetEventsDir() string
GetEventsDir returns the path to the events directory.
func GetGeminiConfigDir ¶
func GetGeminiConfigDir() string
GetGeminiConfigDir returns ~/.gemini Unlike Claude, Gemini has no GEMINI_CONFIG_DIR env var override
func GetGeminiMCPNames ¶
func GetGeminiMCPNames() []string
GetGeminiMCPNames returns names of configured MCPs from settings.json
func GetGeminiSessionsDir ¶
GetGeminiSessionsDir returns the chats directory for a project Format: ~/.gemini/tmp/<project_hash>/chats/
func GetGlobalMCPNames ¶
func GetGlobalMCPNames() []string
GetGlobalMCPNames returns the names of MCPs currently in Claude's global config
func GetGroupLevel ¶
GetGroupLevel returns the nesting level of a group (0 for root, 1 for child, etc.)
func GetHangarDir ¶
GetHangarDir returns the base hangar directory (~/.hangar)
func GetHooksDir ¶
func GetHooksDir() string
GetHooksDir returns the path to the hooks status directory.
func GetMCPDefaultScope ¶
func GetMCPDefaultScope() string
GetMCPDefaultScope returns the configured default MCP scope. Returns "local", "global", or "user". Defaults to "local" if unset or invalid.
func GetManageMCPJson ¶
func GetManageMCPJson() bool
GetManageMCPJson returns whether hangar should write to .mcp.json files. Defaults to true when unset.
func GetProfileDir ¶
GetProfileDir returns the path to a specific profile's directory
func GetProfilesDir ¶
GetProfilesDir returns the path to the profiles directory
func GetProjectMCPNames ¶
GetProjectMCPNames returns MCPs from projects[path].mcpServers in Claude's config
func GetProjectsFilePath ¶
GetProjectsFilePath returns ~/.hangar/projects.toml
func GetToolBusyPatterns ¶
GetToolBusyPatterns returns busy patterns for a tool (custom + built-in)
func GetToolIcon ¶
GetToolIcon returns the icon for a tool (custom or built-in)
func GetUserConfigPath ¶
GetUserConfigPath returns the path to the user config file
func GetUserMCPNames ¶
func GetUserMCPNames() []string
GetUserMCPNames returns the names of MCPs in ~/.claude.json (ROOT config)
func GetUserMCPRootPath ¶
func GetUserMCPRootPath() string
GetUserMCPRootPath returns the path to ~/.claude.json (ROOT config, always read by Claude)
func GroupByProject ¶
GroupByProject groups sessions by their parent project directory
func HashProjectPath ¶
HashProjectPath generates SHA256 hash of absolute project path This matches Gemini CLI's project hash algorithm for session storage VERIFIED: echo -n "/Users/ashesh" | shasum -a 256 NOTE: Must resolve symlinks (e.g., /tmp -> /private/tmp on macOS)
func InjectClaudeHooks ¶
InjectClaudeHooks injects hangar hook entries into Claude Code's settings.json. Uses read-preserve-modify-write pattern to preserve all existing settings and user hooks. Returns true if hooks were newly installed, false if already present.
func InstallBridgeScript ¶
func InstallBridgeScript() error
InstallBridgeScript is a stub; conductor bridge is no longer installed.
func IsClaudeConfigDirExplicit ¶
func IsClaudeConfigDirExplicit() bool
IsClaudeConfigDirExplicit returns true if the Claude config directory is explicitly configured (via CLAUDE_CONFIG_DIR env var, profile override, or global config.toml setting). When false, the user is using the default path and we should NOT override CLAUDE_CONFIG_DIR in commands, allowing the shell's environment to be respected.
This is critical for WSL and other environments where users may have CLAUDE_CONFIG_DIR set in their .bashrc/.zshrc - hangar should not override that with a hardcoded default path.
func ListProfiles ¶
ListProfiles returns all available profile names
func MarshalToolOptions ¶
func MarshalToolOptions(opts ToolOptions) (json.RawMessage, error)
MarshalToolOptions serializes tool options to JSON
func MergeToolPatterns ¶
func MergeToolPatterns(toolName string) *tmux.RawPatterns
MergeToolPatterns returns merged RawPatterns for a tool, combining built-in defaults with any user overrides/extras from config.toml. Works for ALL tools: built-in (claude, gemini, etc.) and custom. Returns nil only if there are no defaults AND no config entry.
func NeedsMigration ¶
NeedsMigration checks if migration from old layout is needed
func ProfileExists ¶
ProfileExists checks if a profile exists
func PruneMCPCache ¶
PruneMCPCache removes cache entries older than maxAge to prevent unbounded growth. Called periodically from the TUI tick handler.
func ReadPrevEventStatus ¶
ReadPrevEventStatus reads the previous status from an existing event file. Returns empty string if no event file exists or can't be read.
func RemoveClaudeHooks ¶
RemoveClaudeHooks removes hangar hook entries from Claude Code's settings.json. Returns true if hooks were removed, false if none found.
func RenameProject ¶
RenameProject renames a project from oldName to newName, preserving all other fields (BaseDir, BaseBranch, Order). Matching is case-insensitive. Returns an error if the project is not found.
func ResolveTheme ¶
func ResolveTheme() string
ResolveTheme resolves the configured theme to "dark" or "light". If theme is "system", detects the OS dark mode setting. Falls back to "dark" on detection failure.
func RestoreFromArchive ¶
RestoreFromArchive moves all files from archive/ subdirectories back to their parent directories under baseDir/profiles/*/.
func SaveProjects ¶
SaveProjects writes the projects list atomically.
func SaveUserConfig ¶
func SaveUserConfig(config *UserConfig) error
SaveUserConfig writes the config to config.toml using atomic write pattern This clears the cache so next LoadUserConfig() reads fresh values
func SendSessionMessageReliable ¶
SendSessionMessageReliable sends a message using the same queued/reliable semantics as `hangar session send` (default mode, no --no-wait). It invokes the CLI command to keep behavior identical across callers.
func SetDefaultProfile ¶
SetDefaultProfile sets the default profile in the config
func ShouldNotifyTransition ¶
func StartMaintenanceWorker ¶
func StartMaintenanceWorker(ctx context.Context, onComplete func(MaintenanceResult))
StartMaintenanceWorker launches a background goroutine that runs maintenance on a 15-minute ticker with an immediate first run. It checks GetMaintenanceSettings().Enabled before each run.
func TierName ¶
func TierName(tier SearchTier) string
TierName returns a human-readable name for the tier
func ToggleLocalMCP ¶
ToggleLocalMCP toggles a Local MCP on/off It respects the existing mode (whitelist vs blacklist) or initializes with blacklist
func UpdateClaudeSessionsWithDedup ¶
func UpdateClaudeSessionsWithDedup(instances []*Instance)
UpdateClaudeSessionsWithDedup clears duplicate Claude session IDs across instances. The oldest session (by CreatedAt) keeps its ID, newer duplicates are cleared. With tmux env being authoritative, duplicates shouldn't occur in normal use, but we handle them defensively for loaded/migrated sessions.
func UpdateGeminiAnalyticsFromDisk ¶
func UpdateGeminiAnalyticsFromDisk(projectPath, sessionID string, analytics *GeminiSessionAnalytics) error
UpdateGeminiAnalyticsFromDisk updates the analytics struct from the session file on disk. Uses mtime caching to skip re-parsing unchanged files (important for 40MB+ session files).
func WriteGeminiMCPSettings ¶
WriteGeminiMCPSettings writes MCPs to ~/.gemini/settings.json Preserves existing config fields (security, theme, etc.) Uses atomic write with .tmp file for safety
func WriteGlobalMCP ¶
WriteGlobalMCP adds or removes MCPs from Claude's global config
func WriteMCPJsonFromConfig ¶
WriteMCPJsonFromConfig writes enabled MCPs from config.toml to project's .mcp.json It preserves any existing entries not managed by hangar (not defined in config.toml)
func WriteStatusEvent ¶
func WriteStatusEvent(event StatusEvent) error
WriteStatusEvent atomically writes a status event to the events directory. Uses tmp file + rename to avoid partial reads by watchers.
func WriteUserMCP ¶
WriteUserMCP writes MCPs to ~/.claude.json (ROOT config) WARNING: MCPs written here affect ALL Claude sessions regardless of profile!
Types ¶
type AnalyticsDisplaySettings ¶
type AnalyticsDisplaySettings struct {
// ShowContextBar shows the context window usage bar (default: true)
ShowContextBar *bool `toml:"show_context_bar"`
// ShowTokens shows the token breakdown (In/Out/Cache/Total) (default: false)
ShowTokens *bool `toml:"show_tokens"`
// ShowSessionInfo shows duration, turns, start time (default: false)
ShowSessionInfo *bool `toml:"show_session_info"`
// ShowTools shows the top tool calls (default: true)
ShowTools *bool `toml:"show_tools"`
// ShowCost shows the estimated cost (default: false)
ShowCost *bool `toml:"show_cost"`
}
AnalyticsDisplaySettings configures which analytics sections to display All settings use pointers to distinguish "not set" from "explicitly false"
func (*AnalyticsDisplaySettings) GetShowContextBar ¶
func (a *AnalyticsDisplaySettings) GetShowContextBar() bool
GetShowContextBar returns whether to show context bar, defaulting to true
func (*AnalyticsDisplaySettings) GetShowCost ¶
func (a *AnalyticsDisplaySettings) GetShowCost() bool
GetShowCost returns whether to show cost estimate, defaulting to false
func (*AnalyticsDisplaySettings) GetShowSessionInfo ¶
func (a *AnalyticsDisplaySettings) GetShowSessionInfo() bool
GetShowSessionInfo returns whether to show session info, defaulting to false
func (*AnalyticsDisplaySettings) GetShowTokens ¶
func (a *AnalyticsDisplaySettings) GetShowTokens() bool
GetShowTokens returns whether to show token breakdown, defaulting to false
func (*AnalyticsDisplaySettings) GetShowTools ¶
func (a *AnalyticsDisplaySettings) GetShowTools() bool
GetShowTools returns whether to show tool calls, defaulting to false
type BillingBlock ¶
type BillingBlock struct {
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
TokensUsed int `json:"tokens_used"`
IsActive bool `json:"is_active"`
}
BillingBlock represents a 5-hour billing window
func CalculateBillingBlocks ¶
func CalculateBillingBlocks(timestamps []time.Time, windowSize time.Duration) []BillingBlock
CalculateBillingBlocks groups timestamps into billing windows. Claude Code API bills in 5-hour windows. Each block represents a billing period. Timestamps are sorted chronologically and grouped - a new block starts when a timestamp exceeds the windowSize from the current block's start time. The last block is marked as "active" if it's still within the window from now.
type ClaudeConfig ¶
type ClaudeConfig struct {
Projects map[string]ClaudeProject `json:"projects"`
}
ClaudeConfig represents the structure of .claude.json
type ClaudeOptions ¶
type ClaudeOptions struct {
// SessionMode: "new" (default), "continue" (-c), or "resume" (-r)
SessionMode string `json:"session_mode,omitempty"`
// ResumeSessionID is the session ID for -r flag (only when SessionMode="resume")
ResumeSessionID string `json:"resume_session_id,omitempty"`
// SkipPermissions adds --dangerously-skip-permissions flag
SkipPermissions bool `json:"skip_permissions,omitempty"`
// AllowSkipPermissions adds --allow-dangerously-skip-permissions flag
// Only used when SkipPermissions is false (SkipPermissions takes precedence)
AllowSkipPermissions bool `json:"allow_skip_permissions,omitempty"`
// UseChrome adds --chrome flag
UseChrome bool `json:"use_chrome,omitempty"`
// UseTeammateMode adds --teammate-mode tmux flag
UseTeammateMode bool `json:"use_teammate_mode,omitempty"`
// Transient fields for worktree fork (not persisted)
WorkDir string `json:"-"`
WorktreePath string `json:"-"`
WorktreeRepoRoot string `json:"-"`
WorktreeBranch string `json:"-"`
}
ClaudeOptions holds launch options for Claude Code sessions
func NewClaudeOptions ¶
func NewClaudeOptions(config *UserConfig) *ClaudeOptions
NewClaudeOptions creates ClaudeOptions with defaults from config
func UnmarshalClaudeOptions ¶
func UnmarshalClaudeOptions(data json.RawMessage) (*ClaudeOptions, error)
UnmarshalClaudeOptions deserializes ClaudeOptions from JSON wrapper
func (*ClaudeOptions) ToArgs ¶
func (o *ClaudeOptions) ToArgs() []string
ToArgs returns command-line arguments based on options
func (*ClaudeOptions) ToArgsForFork ¶
func (o *ClaudeOptions) ToArgsForFork() []string
ToArgsForFork returns arguments suitable for fork resume command Fork always uses --resume internally, so session mode flags are not included
func (*ClaudeOptions) ToolName ¶
func (o *ClaudeOptions) ToolName() string
ToolName returns "claude"
type ClaudeProject ¶
type ClaudeProject struct {
LastSessionId string `json:"lastSessionId"`
}
ClaudeProject represents a project entry in Claude's config
type ClaudeSettings ¶
type ClaudeSettings struct {
// Command is the Claude CLI command or alias to use (e.g., "claude", "cdw", "cdp")
// Default: "claude"
// This allows using shell aliases that set CLAUDE_CONFIG_DIR automatically
Command string `toml:"command"`
// ConfigDir is the path to Claude's config directory
// Default: ~/.claude (or CLAUDE_CONFIG_DIR env var)
ConfigDir string `toml:"config_dir"`
// DangerousMode enables --dangerously-skip-permissions flag for Claude sessions
// Default: true (nil = use default true, explicitly set false to disable)
// Power users typically want this enabled for faster iteration
DangerousMode *bool `toml:"dangerous_mode"`
// AllowDangerousMode enables --allow-dangerously-skip-permissions flag
// This unlocks bypass as an option without activating it by default.
// Ignored when dangerous_mode is true (the stronger flag takes precedence).
// Default: false
AllowDangerousMode bool `toml:"allow_dangerous_mode"`
// EnvFile is a .env file specific to Claude sessions
// Sourced AFTER global [shell].env_files
// Path can be absolute, ~ for home, $HOME/${VAR} for env vars, or relative to session working directory
EnvFile string `toml:"env_file"`
// HooksEnabled enables Claude Code hooks for real-time status detection.
// When enabled, hangar uses lifecycle hooks (SessionStart, Stop, etc.)
// for instant, deterministic status updates instead of polling tmux content.
// Default: true (nil = use default true, set false to disable)
HooksEnabled *bool `toml:"hooks_enabled"`
}
ClaudeSettings defines Claude Code configuration
func (*ClaudeSettings) GetDangerousMode ¶
func (c *ClaudeSettings) GetDangerousMode() bool
GetDangerousMode returns whether dangerous mode is enabled, defaulting to true Power users (the primary audience) typically want this enabled for faster iteration
func (*ClaudeSettings) GetHooksEnabled ¶
func (c *ClaudeSettings) GetHooksEnabled() bool
GetHooksEnabled returns whether Claude Code hooks are enabled, defaulting to true
type CodexOptions ¶
type CodexOptions struct {
// YoloMode enables --yolo flag (bypass approvals and sandbox)
// nil = inherit from global config, true/false = explicit override
YoloMode *bool `json:"yolo_mode,omitempty"`
}
CodexOptions holds launch options for Codex CLI sessions
func NewCodexOptions ¶
func NewCodexOptions(config *UserConfig) *CodexOptions
NewCodexOptions creates CodexOptions with defaults from global config
func UnmarshalCodexOptions ¶
func UnmarshalCodexOptions(data json.RawMessage) (*CodexOptions, error)
UnmarshalCodexOptions deserializes CodexOptions from JSON wrapper
func (*CodexOptions) ToArgs ¶
func (o *CodexOptions) ToArgs() []string
ToArgs returns command-line arguments based on options
type CodexSettings ¶
type CodexSettings struct {
// YoloMode enables --yolo flag for Codex sessions (bypass approvals and sandbox)
// Default: false
YoloMode bool `toml:"yolo_mode"`
}
CodexSettings defines Codex CLI configuration
type CompletionCycler ¶
type CompletionCycler struct {
// contains filtered or unexported fields
}
CompletionCycler manages the state of directory autocomplete.
func (*CompletionCycler) IsActive ¶
func (c *CompletionCycler) IsActive() bool
IsActive returns true if the cycler has active matches.
func (*CompletionCycler) Next ¶
func (c *CompletionCycler) Next() string
Next returns the next match in the cycle. Wraps around to the beginning if the end is reached.
func (*CompletionCycler) SetMatches ¶
func (c *CompletionCycler) SetMatches(matches []string)
SetMatches sets the matches for the cycler and resets the index.
type ConductorMeta ¶
type ConductorMeta struct {
Name string `json:"name"`
Profile string `json:"profile"`
HeartbeatEnabled bool `json:"heartbeat_enabled"`
HeartbeatInterval int `json:"heartbeat_interval"`
Description string `json:"description,omitempty"`
CreatedAt string `json:"created_at"`
}
ConductorMeta holds metadata about a conductor instance (stub; conductor is removed).
func ListConductorsForProfile ¶
func ListConductorsForProfile(_ string) ([]ConductorMeta, error)
ListConductorsForProfile returns conductors for a profile (stub; always empty).
type ConductorSettings ¶
type ConductorSettings struct {
Enabled bool `toml:"enabled"`
HeartbeatInterval int `toml:"heartbeat_interval"`
Profiles []string `toml:"profiles"`
Telegram TelegramSettings `toml:"telegram"`
Slack SlackSettings `toml:"slack"`
}
ConductorSettings defines conductor (meta-agent orchestration) settings. Kept as a stub so config.toml files with [conductor] sections parse without errors.
type Config ¶
type Config struct {
// DefaultProfile is the profile to use when none is specified
DefaultProfile string `json:"default_profile"`
// LastUsed is the most recently used profile (for future use)
LastUsed string `json:"last_used,omitempty"`
// Version tracks config format for future migrations
Version int `json:"version"`
}
Config represents the global hangar configuration
type ContentBuffer ¶
type ContentBuffer struct {
// contains filtered or unexported fields
}
ContentBuffer stores full content and a lowercased copy for fast search. It is safe for concurrent reads and append-only writes.
func (*ContentBuffer) Append ¶
func (b *ContentBuffer) Append(data []byte)
func (*ContentBuffer) CopyData ¶
func (b *ContentBuffer) CopyData() []byte
func (*ContentBuffer) Size ¶
func (b *ContentBuffer) Size() int64
Size returns the total memory used by this buffer (data + lowercased copy)
func (*ContentBuffer) With ¶
func (b *ContentBuffer) With(fn func(data, lower []byte))
type ExperimentsSettings ¶
type ExperimentsSettings struct {
// Directory is the base directory for experiments
// Default: ~/src/tries
Directory string `toml:"directory"`
// DatePrefix adds YYYY-MM-DD- prefix to new experiment folders
// Default: true
DatePrefix bool `toml:"date_prefix"`
// DefaultTool is the AI tool to use for experiment sessions
// Default: "claude"
DefaultTool string `toml:"default_tool"`
}
ExperimentsSettings defines experiment folder configuration
func GetExperimentsSettings ¶
func GetExperimentsSettings() ExperimentsSettings
GetExperimentsSettings returns experiments settings with defaults applied
type FileTracker ¶
FileTracker tracks file state for incremental updates
type GeminiMCPConfig ¶
type GeminiMCPConfig struct {
MCPServers map[string]MCPServerConfig `json:"mcpServers"`
}
GeminiMCPConfig represents settings.json structure VERIFIED: Actual settings.json does NOT have mcp.allowed/excluded (Simplified structure compared to research docs)
type GeminiModelPricing ¶
GeminiModelPricing holds pricing per million tokens
type GeminiSessionAnalytics ¶
type GeminiSessionAnalytics struct {
// Token usage
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
// Current context size (last turn's input + cache read tokens)
CurrentContextTokens int `json:"current_context_tokens"`
// Session metrics
TotalTurns int `json:"total_turns"`
Duration time.Duration `json:"duration"`
StartTime time.Time `json:"start_time"`
LastActive time.Time `json:"last_active"`
// Cost estimation
EstimatedCost float64 `json:"estimated_cost"`
// Model detected from session file messages
Model string `json:"model,omitempty"`
// In-memory cache: last file modification time (skip re-parse if unchanged)
LastFileModTime time.Time `json:"-"`
}
GeminiSessionAnalytics holds metrics for a Gemini session
func (*GeminiSessionAnalytics) CalculateCost ¶
func (a *GeminiSessionAnalytics) CalculateCost(model string) float64
CalculateCost estimates session cost based on token usage and model pricing
func (*GeminiSessionAnalytics) TotalTokens ¶
func (a *GeminiSessionAnalytics) TotalTokens() int
TotalTokens returns the sum of input and output tokens
type GeminiSessionInfo ¶
type GeminiSessionInfo struct {
SessionID string // Full UUID
Filename string // session-2025-12-26T15-09-4d8fcb4d.json
StartTime time.Time
LastUpdated time.Time
MessageCount int
}
GeminiSessionInfo holds parsed session metadata
func ListGeminiSessions ¶
func ListGeminiSessions(projectPath string) ([]GeminiSessionInfo, error)
ListGeminiSessions returns all sessions for a project path Scans ~/.gemini/tmp/<hash>/chats/ and parses session files Sorted by LastUpdated (most recent first)
type GeminiSettings ¶
type GeminiSettings struct {
// YoloMode enables --yolo flag for Gemini sessions (auto-approve all actions)
// Default: false
YoloMode bool `toml:"yolo_mode"`
// DefaultModel is the model to use for new Gemini sessions (e.g., "gemini-2.5-flash")
// If empty, Gemini CLI uses its own default
DefaultModel string `toml:"default_model"`
// EnvFile is a .env file specific to Gemini sessions
// Sourced AFTER global [shell].env_files
// Path can be absolute, ~ for home, $HOME/${VAR} for env vars, or relative to session working directory
EnvFile string `toml:"env_file"`
}
GeminiSettings defines Gemini CLI configuration
type GlobalSearchIndex ¶
type GlobalSearchIndex struct {
// contains filtered or unexported fields
}
GlobalSearchIndex manages the searchable session index
func NewGlobalSearchIndex ¶
func NewGlobalSearchIndex(claudeDir string, config GlobalSearchSettings) (*GlobalSearchIndex, error)
NewGlobalSearchIndex creates a new search index
func (*GlobalSearchIndex) Close ¶
func (idx *GlobalSearchIndex) Close()
Close shuts down the index and releases all memory
func (*GlobalSearchIndex) EntryCount ¶
func (idx *GlobalSearchIndex) EntryCount() int
EntryCount returns the number of indexed entries
func (*GlobalSearchIndex) FuzzySearch ¶
func (idx *GlobalSearchIndex) FuzzySearch(query string) []*SearchResult
FuzzySearch performs fuzzy matching with typo tolerance
func (*GlobalSearchIndex) GetTier ¶
func (idx *GlobalSearchIndex) GetTier() SearchTier
GetTier returns the current search tier
func (*GlobalSearchIndex) IsLoading ¶
func (idx *GlobalSearchIndex) IsLoading() bool
IsLoading returns true if the index is still loading
func (*GlobalSearchIndex) Search ¶
func (idx *GlobalSearchIndex) Search(query string) []*SearchResult
Search performs a simple substring search
type GlobalSearchSettings ¶
type GlobalSearchSettings struct {
// Enabled enables/disables global search feature (default: true when loaded via LoadUserConfig)
Enabled bool `toml:"enabled"`
// Tier controls search strategy: "auto", "instant", "balanced", "disabled"
// auto: Auto-detect based on data size (recommended)
// instant: Force full in-memory (fast, uses more RAM)
// balanced: Force LRU cache mode (slower, capped RAM)
// disabled: Disable global search entirely
Tier string `toml:"tier"`
// MemoryLimitMB caps memory usage for search index (default: 100)
// Only applies to balanced tier
MemoryLimitMB int `toml:"memory_limit_mb"`
// RecentDays limits search to sessions from last N days (0 = all)
// Reduces index size for users with long history (default: 90)
RecentDays int `toml:"recent_days"`
// IndexRateLimit limits files indexed per second during background indexing
// Lower = less CPU impact (default: 20)
IndexRateLimit int `toml:"index_rate_limit"`
}
GlobalSearchSettings defines global conversation search configuration
type Group ¶
type Group struct {
Name string
Path string // Full path like "projects" or "projects/devops"
Expanded bool
Sessions []*Instance
Order int
DefaultPath string // Explicit default path for new sessions in this group
}
Group represents a group of sessions
type GroupData ¶
type GroupData struct {
Name string `json:"name"`
Path string `json:"path"`
Expanded bool `json:"expanded"`
Order int `json:"order"`
DefaultPath string `json:"default_path,omitempty"`
}
GroupData represents serializable group data
type GroupTree ¶
type GroupTree struct {
Groups map[string]*Group // path -> group
GroupList []*Group // Ordered list of groups
Expanded map[string]bool // Collapsed state persistence
}
GroupTree manages hierarchical session organization
func NewGroupTree ¶
NewGroupTree creates a new group tree from instances
func NewGroupTreeFromProjects ¶
func NewGroupTreeFromProjects(instances []*Instance, projects []*Project, storedGroups []*GroupData) *GroupTree
NewGroupTreeFromProjects builds a GroupTree using projects as the authoritative list of top-level groups. Sessions are assigned to groups by matching their GroupPath against each project's slug. Sessions whose GroupPath matches no project are silently ignored (orphans). Stored group data is used only to restore the Expanded state; all structural data comes from projects.
func NewGroupTreeWithGroups ¶
NewGroupTreeWithGroups creates a group tree from instances and stored group data
func (*GroupTree) AddSession ¶
AddSession adds a session to the appropriate group
func (*GroupTree) CollapseGroup ¶
CollapseGroup collapses a group
func (*GroupTree) CreateGroup ¶
CreateGroup creates a new empty group
func (*GroupTree) CreateSubgroup ¶
CreateSubgroup creates a new empty group under a parent group
func (*GroupTree) DefaultPathForGroup ¶
DefaultPathForGroup returns the effective default path for creating new sessions in the group: explicit configured default_path first, then most recent session path.
func (*GroupTree) DeleteGroup ¶
DeleteGroup deletes a group and all its subgroups. Returns nil if the group doesn't exist or has active sessions (deletion refused). Deletion is refused if the group or any subgroup contains sessions.
func (*GroupTree) ExpandGroup ¶
ExpandGroup expands a group
func (*GroupTree) ExpandGroupWithParents ¶
ExpandGroupWithParents expands a group and all its parent groups This ensures the group and its contents are visible in the flattened view
func (*GroupTree) GetAllInstances ¶
GetAllInstances returns all instances in order
func (*GroupTree) GetGroupNames ¶
GetGroupNames returns all group names for selection
func (*GroupTree) GetGroupPaths ¶
GetGroupPaths returns all group paths for selection
func (*GroupTree) GroupCount ¶
GroupCount returns total group count
func (*GroupTree) MoveGroupDown ¶
MoveGroupDown moves a group down in the order (only within siblings at same level)
func (*GroupTree) MoveGroupUp ¶
MoveGroupUp moves a group up in the order (only within siblings at same level)
func (*GroupTree) MoveSessionDown ¶
MoveSessionDown moves a session down within its group
func (*GroupTree) MoveSessionToGroup ¶
MoveSessionToGroup moves a session to a different group
func (*GroupTree) MoveSessionUp ¶
MoveSessionUp moves a session up within its group
func (*GroupTree) RemoveSession ¶
RemoveSession removes a session from its group
func (*GroupTree) RenameGroup ¶
RenameGroup renames a group and updates all subgroups
func (*GroupTree) SessionCount ¶
SessionCount returns total session count
func (*GroupTree) SessionCountForGroup ¶
SessionCountForGroup returns session count for a group INCLUDING all its subgroups This enables hierarchical counts like "Project (5)" where 5 includes all nested sessions
func (*GroupTree) SetDefaultPathForGroup ¶
SetDefaultPathForGroup sets (or clears) an explicit default path for a group.
func (*GroupTree) ShallowCopyForSave ¶
ShallowCopyForSave creates a copy of the GroupTree that's safe to use from a goroutine for saving purposes. It deep copies the Group structs to prevent data races when the main thread modifies group fields (Name, Path, Expanded, Order) while a background goroutine reads them.
func (*GroupTree) SyncWithInstances ¶
SyncWithInstances updates the tree with a new set of instances while preserving existing group structure (including empty groups)
func (*GroupTree) ToggleGroup ¶
ToggleGroup toggles the expanded state of a group
type HTTPServerConfig ¶
type HTTPServerConfig struct {
// Command is the executable to run (e.g., "uvx", "python", "node")
Command string `toml:"command"`
// Args are command-line arguments for the server
Args []string `toml:"args"`
// Env is environment variables for the server process
Env map[string]string `toml:"env"`
// StartupTimeout is milliseconds to wait for server to become ready (default: 5000)
StartupTimeout int `toml:"startup_timeout"`
// HealthCheck is an optional health endpoint URL to poll (e.g., "http://localhost:30000/health")
// If not set, the main URL is used for health checking
HealthCheck string `toml:"health_check"`
}
HTTPServerConfig defines how to auto-start an HTTP MCP server
func (*HTTPServerConfig) GetStartupTimeout ¶
func (c *HTTPServerConfig) GetStartupTimeout() int
GetStartupTimeout returns the startup timeout in milliseconds, defaulting to 5000ms
type HookStatus ¶
type HookStatus struct {
Status string // running, idle, waiting, dead
SessionID string // Claude session ID
Event string // Hook event name
UpdatedAt time.Time // When this status was received
}
HookStatus holds the decoded status from a hook status file.
type Instance ¶
type Instance struct {
ID string `json:"id"`
Title string `json:"title"`
ProjectPath string `json:"project_path"`
GroupPath string `json:"group_path"` // e.g., "projects/devops"
Order int `json:"order"` // Position within group (for reorder persistence)
ParentSessionID string `json:"parent_session_id,omitempty"` // Links to parent session (makes this a sub-session)
ParentProjectPath string `json:"parent_project_path,omitempty"` // Parent's project path (for --add-dir access)
// Git worktree support
WorktreePath string `json:"worktree_path,omitempty"` // Path to worktree (if session is in worktree)
WorktreeRepoRoot string `json:"worktree_repo_root,omitempty"` // Original repo root
WorktreeBranch string `json:"worktree_branch,omitempty"` // Branch name in worktree
Command string `json:"command"`
Wrapper string `json:"wrapper,omitempty"` // Optional wrapper command with {command} placeholder
Tool string `json:"tool"`
Status Status `json:"status"`
CreatedAt time.Time `json:"created_at"`
LastAccessedAt time.Time `json:"last_accessed_at,omitempty"` // When user last attached
// Claude Code integration
ClaudeSessionID string `json:"claude_session_id,omitempty"`
ClaudeDetectedAt time.Time `json:"claude_detected_at,omitempty"`
// Gemini CLI integration
GeminiSessionID string `json:"gemini_session_id,omitempty"`
GeminiDetectedAt time.Time `json:"gemini_detected_at,omitempty"`
GeminiYoloMode *bool `json:"gemini_yolo_mode,omitempty"` // Per-session override (nil = use global config)
GeminiModel string `json:"gemini_model,omitempty"` // Active model for this session
GeminiAnalytics *GeminiSessionAnalytics `json:"gemini_analytics,omitempty"` // Per-session analytics
// OpenCode CLI integration
OpenCodeSessionID string `json:"opencode_session_id,omitempty"`
OpenCodeDetectedAt time.Time `json:"opencode_detected_at,omitempty"`
OpenCodeStartedAt int64 `json:"-"` // Unix millis when we started OpenCode (for session matching, not persisted)
// Codex CLI integration
CodexSessionID string `json:"codex_session_id,omitempty"`
CodexDetectedAt time.Time `json:"codex_detected_at,omitempty"`
CodexStartedAt int64 `json:"-"` // Unix millis when we started Codex (for session matching, not persisted)
// Latest user input for context (extracted from session files)
LatestPrompt string `json:"latest_prompt,omitempty"`
// MCP tracking - which MCPs were loaded when session started/restarted
// Used to detect pending MCPs (added after session start) and stale MCPs (removed but still running)
LoadedMCPNames []string `json:"loaded_mcp_names,omitempty"`
// ToolOptions stores tool-specific launch options (Claude, Codex, Gemini, etc.)
// JSON structure: {"tool": "claude", "options": {...}}
ToolOptionsJSON json.RawMessage `json:"tool_options,omitempty"`
// SkipMCPRegenerate skips .mcp.json regeneration on next Restart()
// Set by MCP dialog Apply() to avoid race condition where Apply writes
// config then Restart immediately overwrites it with different pool state
SkipMCPRegenerate bool `json:"-"` // Don't persist, transient flag
// contains filtered or unexported fields
}
Instance represents a single agent/shell session
func DiscoverExistingTmuxSessions ¶
DiscoverExistingTmuxSessions finds all tmux sessions and converts them to instances
func FilterByQuery ¶
FilterByQuery filters sessions by title, project path, tool, or status Supports status filters: "waiting", "running", "idle", "error"
func NewInstance ¶
NewInstance creates a new session instance
func NewInstanceWithGroup ¶
NewInstanceWithGroup creates a new session instance with explicit group
func NewInstanceWithGroupAndTool ¶
NewInstanceWithGroupAndTool creates a new session with explicit group and tool
func NewInstanceWithTool ¶
NewInstanceWithTool creates a new session with tool-specific initialization
func (*Instance) CanForkOpenCode ¶
CanForkOpenCode returns true if this OpenCode session can be forked
func (*Instance) CanRestart ¶
CanRestart returns true if the session can be restarted For Claude sessions with known ID: can always restart (interrupt and resume) For Gemini sessions with known ID: can always restart (interrupt and resume) For OpenCode sessions with known ID: can always restart (interrupt and resume) For Codex sessions with known ID: can always restart (interrupt and resume) For custom tools with session resume config: can restart if session ID available For other sessions: only if dead/error state
func (*Instance) CanRestartGeneric ¶
CanRestartGeneric returns true if a custom tool can be restarted with session resume
func (*Instance) CaptureLoadedMCPs ¶
func (i *Instance) CaptureLoadedMCPs()
CaptureLoadedMCPs captures the current MCP names as the "loaded" state This should be called when a session starts or restarts, so we can track which MCPs are actually loaded in the running Claude session vs just configured
func (*Instance) ClearHookStatus ¶
func (i *Instance) ClearHookStatus()
ClearHookStatus resets the hook-based status, forcing the next UpdateStatus() to fall through to polling. Used when the user manually overrides status (e.g., pressing 'u' to unacknowledge after an Escape interrupt where the Stop hook didn't fire).
func (*Instance) ClearParent ¶
func (inst *Instance) ClearParent()
ClearParent removes the parent session link
func (*Instance) CreateForkedInstance ¶
CreateForkedInstance creates a new Instance configured for forking Deprecated: Use CreateForkedInstanceWithOptions instead
func (*Instance) CreateForkedInstanceWithOptions ¶
func (i *Instance) CreateForkedInstanceWithOptions(newTitle, newGroupPath string, opts *ClaudeOptions) (*Instance, string, error)
CreateForkedInstanceWithOptions creates a new Instance configured for forking with custom options
func (*Instance) CreateForkedOpenCodeInstance ¶
func (i *Instance) CreateForkedOpenCodeInstance(newTitle, newGroupPath string) (*Instance, string, error)
CreateForkedOpenCodeInstance creates a new Instance configured for forking an OpenCode session Deprecated: Use CreateForkedOpenCodeInstanceWithOptions instead.
func (*Instance) CreateForkedOpenCodeInstanceWithOptions ¶
func (i *Instance) CreateForkedOpenCodeInstanceWithOptions(newTitle, newGroupPath string, opts *OpenCodeOptions) (*Instance, string, error)
CreateForkedOpenCodeInstanceWithOptions creates a new Instance configured for forking with custom options
func (*Instance) DetectCodexSession ¶
func (i *Instance) DetectCodexSession()
DetectCodexSession is the public wrapper for async Codex session detection Call this for restored sessions that don't have a session ID yet
func (*Instance) DetectOpenCodeSession ¶
func (i *Instance) DetectOpenCodeSession()
DetectOpenCodeSession is the public wrapper for async OpenCode session detection Call this for restored sessions that don't have a session ID yet
func (*Instance) ForceNextStatusCheck ¶
func (i *Instance) ForceNextStatusCheck()
ForceNextStatusCheck clears the idle polling optimization so the next UpdateStatus() performs a full check instead of short-circuiting. Call this before UpdateStatus() when a status-affecting change was made externally (e.g. the u key toggling acknowledged state).
func (*Instance) Fork ¶
Fork returns the command to create a forked Claude session Uses capture-resume pattern: starts fork in print mode to get new session ID, stores in tmux environment, then resumes interactively Deprecated: Use ForkWithOptions instead
func (*Instance) ForkOpenCode ¶
ForkOpenCode returns the command to create a forked OpenCode session. Uses export/import to clone the session with a new ID, then launches the forked session with opencode -s <new-id>. Deprecated: Use ForkOpenCodeWithOptions instead.
func (*Instance) ForkOpenCodeWithOptions ¶
func (i *Instance) ForkOpenCodeWithOptions(newTitle, newGroupPath string, opts *OpenCodeOptions) (string, error)
ForkOpenCodeWithOptions returns the command to create a forked OpenCode session with custom options. Uses export/import to clone the session with a new ID, then launches the forked session with opencode -s <new-id> plus any model/agent flags.
func (*Instance) ForkWithOptions ¶
func (i *Instance) ForkWithOptions(newTitle, newGroupPath string, opts *ClaudeOptions) (string, error)
ForkWithOptions returns the command to create a forked Claude session with custom options Uses capture-resume pattern: starts fork in print mode to get new session ID, stores in tmux environment, then resumes interactively
func (*Instance) GetActualWorkDir ¶
GetActualWorkDir returns the actual working directory from tmux, or falls back to ProjectPath
func (*Instance) GetClaudeOptions ¶
func (i *Instance) GetClaudeOptions() *ClaudeOptions
GetClaudeOptions returns Claude-specific options, or nil if not set
func (*Instance) GetCodexOptions ¶
func (i *Instance) GetCodexOptions() *CodexOptions
GetCodexOptions returns Codex-specific options, or nil if not set
func (*Instance) GetGenericSessionID ¶
GetGenericSessionID gets session ID from tmux environment for a custom tool Uses the session_id_env field from tool config
func (*Instance) GetHookStatus ¶
GetHookStatus returns the current hook-based status and its freshness. Freshness window is tool-specific.
func (*Instance) GetJSONLPath ¶
GetJSONLPath returns the path to the Claude session JSONL file for analytics Returns empty string if this is not a Claude session or no session ID is available
func (*Instance) GetLastActivityTime ¶
GetLastActivityTime returns when the session was last active (content changed) Returns CreatedAt if no activity has been tracked yet
func (*Instance) GetLastResponse ¶
func (i *Instance) GetLastResponse() (*ResponseOutput, error)
GetLastResponse returns the last assistant response from the session For Claude: Parses the JSONL file for the last assistant message For Gemini: Parses the JSON session file for the last assistant message For Codex/Others: Attempts to parse terminal output
func (*Instance) GetLastResponseBestEffort ¶
func (i *Instance) GetLastResponseBestEffort() (*ResponseOutput, error)
GetLastResponseBestEffort returns the last assistant response with fallback logic intended for CLI read paths (like `session output`) where we prefer useful output over hard errors.
Behavior for Claude: 1. Try structured JSONL read via stored ClaudeSessionID. 2. Refresh ID from tmux env and retry. 3. Scan disk for active session ID and retry. 4. Fallback to terminal parsing. 5. If still unavailable, return an empty response (no error).
func (*Instance) GetMCPInfo ¶
GetMCPInfo returns MCP server information for this session Returns nil if not a Claude or Gemini session
func (*Instance) GetOpenCodeOptions ¶
func (i *Instance) GetOpenCodeOptions() *OpenCodeOptions
GetOpenCodeOptions returns OpenCode-specific options, or nil if not set
func (*Instance) GetSessionIDFromTmux ¶
GetSessionIDFromTmux reads Claude session ID from tmux environment This is the primary method for sessions started with the capture-resume pattern
func (*Instance) GetStatusThreadSafe ¶
GetStatusThreadSafe returns the session status with read-lock protection. Use this when reading Status from a goroutine concurrent with backgroundStatusUpdate.
func (*Instance) GetTmuxSession ¶
GetTmuxSession returns the tmux session object
func (*Instance) GetToolThreadSafe ¶
GetToolThreadSafe returns the tool name with read-lock protection.
func (*Instance) GetWaitingSince ¶
GetWaitingSince returns when the session transitioned to waiting status Used for sorting notification bar (newest waiting sessions first)
func (*Instance) HasUpdated ¶
HasUpdated checks if there's new output since last check
func (*Instance) IsSubSession ¶
IsSubSession returns true if this session has a parent
func (*Instance) IsWorktree ¶
IsWorktree returns true if this session is running in a git worktree
func (*Instance) MarkAccessed ¶
func (inst *Instance) MarkAccessed()
MarkAccessed updates the LastAccessedAt timestamp to now
func (*Instance) PostStartSync ¶
PostStartSync captures session IDs from tmux environment after Start() or Restart(). Designed for CLI commands that exit after starting. The TUI doesn't need this because its background worker handles detection.
For Claude: polls tmux env for CLAUDE_SESSION_ID (set by bash uuidgen before exec). For Gemini: reads session ID from filesystem. For OpenCode/Codex: no-op (async goroutine detection, too slow for sync CLI).
func (*Instance) PreviewFull ¶
PreviewFull returns all terminal output
func (*Instance) Restart ¶
Restart restarts the Claude session For Claude sessions with known ID: sends Ctrl+C twice and resume command to existing session For dead sessions or unknown ID: recreates the tmux session
func (*Instance) SendText ¶
SendText sends text to the session's tmux pane as if the user typed it. Used to deliver initial prompts (e.g. /pr-review) after session start.
func (*Instance) SetAcknowledgedFromShared ¶
SetAcknowledgedFromShared applies an acknowledgment from another TUI instance (read from SQLite). This transitions a YELLOW (waiting) session to GRAY (idle) without requiring the user to interact with this specific TUI instance.
func (*Instance) SetClaudeOptions ¶
func (i *Instance) SetClaudeOptions(opts *ClaudeOptions) error
SetClaudeOptions stores Claude-specific options
func (*Instance) SetCodexOptions ¶
func (i *Instance) SetCodexOptions(opts *CodexOptions) error
SetCodexOptions stores Codex-specific options
func (*Instance) SetGeminiModel ¶
SetGeminiModel sets the Gemini model for this session and triggers a restart if running.
func (*Instance) SetGeminiYoloMode ¶
SetGeminiYoloMode sets the YOLO mode for Gemini and syncs it to the tmux environment. This ensures the background status worker sees the correct state during restarts.
func (*Instance) SetOpenCodeOptions ¶
func (i *Instance) SetOpenCodeOptions(opts *OpenCodeOptions) error
SetOpenCodeOptions stores OpenCode-specific options
func (*Instance) SetParentWithPath ¶
SetParentWithPath sets both parent session ID and parent's project path The project path is used to grant subagent access via --add-dir
func (*Instance) SetStatusThreadSafe ¶
SetStatusThreadSafe sets the session status with write-lock protection.
func (*Instance) SetToolThreadSafe ¶
SetToolThreadSafe sets the tool name with write-lock protection.
func (*Instance) StartWithMessage ¶
StartWithMessage starts the session and sends an initial message when ready The message is sent synchronously after detecting the agent's prompt This approach is more reliable than embedding send logic in the tmux command Works for Claude, Gemini, OpenCode, and other agents
func (*Instance) SyncSessionIDsToTmux ¶
func (i *Instance) SyncSessionIDsToTmux()
SyncSessionIDsToTmux syncs session IDs from Instance to tmux environment. PERFORMANCE: This is called on-demand (e.g., first attach) rather than at load time to reduce subprocess overhead during TUI startup.
Session IDs are needed in tmux environment for restart/resume operations that spawn new processes. Without this sync, R key wouldn't resume the correct session.
func (*Instance) SyncTmuxDisplayName ¶
func (i *Instance) SyncTmuxDisplayName()
SyncTmuxDisplayName updates the tmux status bar to reflect the current title.
func (*Instance) UpdateClaudeSession ¶
UpdateClaudeSession updates the Claude session ID from tmux environment. The capture-resume pattern (used in Start/Fork/Restart) sets CLAUDE_SESSION_ID in the tmux environment, making this the single authoritative source.
No file scanning fallback - we rely on the consistent capture-resume pattern.
func (*Instance) UpdateCodexSession ¶
UpdateCodexSession updates the Codex session ID. Primary source: tmux environment. Fallback: project-aware filesystem scan.
func (*Instance) UpdateGeminiSession ¶
UpdateGeminiSession updates the Gemini session ID, YOLO mode, analytics, and latest prompt. Delegates to focused helpers for each concern.
func (*Instance) UpdateHookStatus ¶
func (i *Instance) UpdateHookStatus(status *HookStatus)
UpdateHookStatus updates the instance's hook-based status fields. Called by StatusFileWatcher when a hook status file changes.
func (*Instance) UpdateStatus ¶
UpdateStatus updates the session status by checking tmux. Thread-safe: acquires write lock to protect Status, Tool, and internal cache fields.
func (*Instance) WaitForClaudeSession ¶
WaitForClaudeSession waits for the tmux environment variable to be set. The capture-resume pattern sets CLAUDE_SESSION_ID in tmux env, so we poll for that. Returns the detected session ID or empty string after timeout.
func (*Instance) WaitForClaudeSessionWithExclude ¶
func (i *Instance) WaitForClaudeSessionWithExclude(maxWait time.Duration, excludeIDs map[string]bool) string
WaitForClaudeSessionWithExclude waits for the tmux environment variable to be set. The excludeIDs parameter is kept for API compatibility but not used since tmux env is authoritative and won't return duplicate IDs.
type InstanceData ¶
type InstanceData struct {
ID string `json:"id"`
Title string `json:"title"`
ProjectPath string `json:"project_path"`
GroupPath string `json:"group_path"`
Order int `json:"order"`
ParentSessionID string `json:"parent_session_id,omitempty"` // Links to parent session (sub-session support)
Command string `json:"command"`
Wrapper string `json:"wrapper,omitempty"`
Tool string `json:"tool"`
Status Status `json:"status"`
CreatedAt time.Time `json:"created_at"`
LastAccessedAt time.Time `json:"last_accessed_at,omitempty"`
TmuxSession string `json:"tmux_session"`
// Worktree support
WorktreePath string `json:"worktree_path,omitempty"`
WorktreeRepoRoot string `json:"worktree_repo_root,omitempty"`
WorktreeBranch string `json:"worktree_branch,omitempty"`
// Claude session (persisted for resume after app restart)
ClaudeSessionID string `json:"claude_session_id,omitempty"`
ClaudeDetectedAt time.Time `json:"claude_detected_at,omitempty"`
// Gemini session (persisted for resume after app restart)
GeminiSessionID string `json:"gemini_session_id,omitempty"`
GeminiDetectedAt time.Time `json:"gemini_detected_at,omitempty"`
GeminiYoloMode *bool `json:"gemini_yolo_mode,omitempty"`
GeminiModel string `json:"gemini_model,omitempty"`
// OpenCode session (persisted for resume after app restart)
OpenCodeSessionID string `json:"opencode_session_id,omitempty"`
OpenCodeDetectedAt time.Time `json:"opencode_detected_at,omitempty"`
// Codex session (persisted for resume after app restart)
CodexSessionID string `json:"codex_session_id,omitempty"`
CodexDetectedAt time.Time `json:"codex_detected_at,omitempty"`
// Latest user input for context
LatestPrompt string `json:"latest_prompt,omitempty"`
// Tool-specific launch options (generic for all tools: claude, codex, etc.)
ToolOptionsJSON json.RawMessage `json:"tool_options,omitempty"`
// MCP tracking (persisted for sync status display)
LoadedMCPNames []string `json:"loaded_mcp_names,omitempty"`
}
InstanceData represents the serializable session data
type InstanceSettings ¶
type InstanceSettings struct {
// AllowMultiple allows running multiple hangar TUI instances for the same profile
// When true (default), multiple instances can run, but only the first (primary) manages the notification bar
// When false, only one instance can run per profile
AllowMultiple *bool `toml:"allow_multiple"`
}
InstanceSettings configures multiple hangar instance behavior
func GetInstanceSettings ¶
func GetInstanceSettings() InstanceSettings
GetInstanceSettings returns instance behavior settings
func (*InstanceSettings) GetAllowMultiple ¶
func (i *InstanceSettings) GetAllowMultiple() bool
GetAllowMultiple returns whether multiple instances are allowed, defaulting to true
type Item ¶
type Item struct {
Type ItemType
Group *Group
Session *Instance
Level int // Indentation level (0 for root groups, 1 for sessions)
Path string // Group path for this item
IsLastInGroup bool // True if this is the last session in its group (for tree rendering)
RootGroupNum int // Pre-computed root group number for hotkey display (1-9, 0 if not a root group)
IsSubSession bool // True if this session has a parent session
IsLastSubSession bool // True if this is the last sub-session of its parent (for tree rendering)
ParentIsLastInGroup bool // True if parent session is last top-level item (for tree line rendering)
}
Item represents a single item in the flattened group tree view
type LocalMCP ¶
type LocalMCP struct {
Name string // MCP name
SourcePath string // Directory containing the .mcp.json file
}
LocalMCP represents an MCP defined in a local .mcp.json file
type LogSettings ¶
type LogSettings struct {
// MaxSizeMB is the maximum size in MB before a log file is truncated
// When a log exceeds this size, it keeps only the last MaxLines lines
// Default: 10 (10MB)
MaxSizeMB int `toml:"max_size_mb"`
// MaxLines is the number of lines to keep when truncating
// Default: 10000
MaxLines int `toml:"max_lines"`
// RemoveOrphans removes log files for sessions that no longer exist
// Default: true
RemoveOrphans bool `toml:"remove_orphans"`
// DebugLevel sets the minimum log level: "debug", "info", "warn", "error"
// Default: "info"
DebugLevel string `toml:"debug_level"`
// DebugFormat sets the log format: "json" (default) or "text"
DebugFormat string `toml:"debug_format"`
// DebugMaxMB is the max size in MB for debug.log before rotation
// Default: 10
DebugMaxMB int `toml:"debug_max_mb"`
// DebugBackups is the number of rotated debug.log files to keep
// Default: 5
DebugBackups int `toml:"debug_backups"`
// DebugRetentionDays is the number of days to keep rotated debug logs
// Default: 10
DebugRetentionDays int `toml:"debug_retention_days"`
// DebugCompress enables gzip compression for rotated debug logs
// Default: true
DebugCompress bool `toml:"debug_compress"`
// RingBufferMB is the in-memory ring buffer size in MB for crash dumps
// Default: 10
RingBufferMB int `toml:"ring_buffer_mb"`
// PprofEnabled starts a pprof server on localhost:6060 when debug mode is active
// Default: false
PprofEnabled bool `toml:"pprof_enabled"`
// AggregateIntervalS is the event aggregation flush interval in seconds
// Default: 30
AggregateIntervalS int `toml:"aggregate_interval_secs"`
}
LogSettings defines log file management configuration
func GetLogSettings ¶
func GetLogSettings() LogSettings
GetLogSettings returns log management settings with defaults applied
type MCPDef ¶
type MCPDef struct {
// Command is the executable to run (e.g., "npx", "docker", "node")
// Required for stdio MCPs, optional for HTTP/SSE MCPs
Command string `toml:"command"`
// Args are command-line arguments
Args []string `toml:"args"`
// Env is optional environment variables
Env map[string]string `toml:"env"`
// Description is optional help text shown in the MCP Manager
Description string `toml:"description"`
// URL is the endpoint for HTTP/SSE MCPs (e.g., "http://localhost:8000/mcp")
// If set, this MCP uses HTTP or SSE transport instead of stdio
URL string `toml:"url"`
// Transport specifies the MCP transport type: "stdio" (default), "http", or "sse"
// Only needed when URL is set; defaults to "http" if URL is present
Transport string `toml:"transport"`
// Headers is optional HTTP headers for HTTP/SSE MCPs (e.g., for authentication)
// Example: { Authorization = "Bearer token123" }
Headers map[string]string `toml:"headers"`
// Server defines how to auto-start an HTTP MCP server process
// When set, hangar will start the server before connecting via HTTP
// This is optional - you can also connect to externally managed servers
Server *HTTPServerConfig `toml:"server"`
}
MCPDef defines an MCP server configuration for the MCP Manager
func (*MCPDef) GetTransport ¶
GetTransport returns the transport type, defaulting to "http" if URL is set
func (*MCPDef) HasAutoStartServer ¶
HasAutoStartServer returns true if this HTTP MCP has server auto-start configured
type MCPInfo ¶
type MCPInfo struct {
Global []string // From CLAUDE_CONFIG_DIR/.claude.json mcpServers
Project []string // From CLAUDE_CONFIG_DIR/.claude.json projects[path].mcpServers
LocalMCPs []LocalMCP // From .mcp.json files (walks up parent directories)
}
MCPInfo contains MCP server information for a session
func GetGeminiMCPInfo ¶
GetGeminiMCPInfo reads MCP configuration from settings.json Returns MCPInfo with Global MCPs only (Gemini has no project-level MCPs) VERIFIED: settings.json structure is simple {"mcpServers": {...}}
func GetMCPInfo ¶
GetMCPInfo retrieves MCP server information for a project path (cached) It reads from three sources: 1. Global MCPs: CLAUDE_CONFIG_DIR/.claude.json → mcpServers 2. Project MCPs: CLAUDE_CONFIG_DIR/.claude.json → projects[projectPath].mcpServers 3. Local MCPs: {projectPath}/.mcp.json → mcpServers
func (*MCPInfo) AllNames ¶
AllNames returns a deduplicated, sorted list of all MCP names across all sources Used for capturing loaded MCPs at session start for sync tracking
type MCPMode ¶
type MCPMode int
MCPMode indicates how MCP enabling/disabling is configured
func GetMCPMode ¶
GetMCPMode determines the MCP configuration mode for a project
type MCPServer ¶
MCPServer represents an MCP with its enabled state
func GetLocalMCPState ¶
GetLocalMCPState returns Local MCPs with their enabled state
type MCPServerConfig ¶
type MCPServerConfig struct {
Type string `json:"type,omitempty"`
Command string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
Env map[string]string `json:"env,omitempty"`
URL string `json:"url,omitempty"` // For HTTP transport
Headers map[string]string `json:"headers,omitempty"` // For HTTP transport (e.g., Authorization)
}
MCPServerConfig represents an MCP server configuration (Claude's format)
type MaintenanceResult ¶
type MaintenanceResult struct {
PrunedLogs int
PrunedBackups int
ArchivedSessions int
Duration time.Duration
}
MaintenanceResult holds the outcome of a maintenance run.
func RunMaintenance ¶
func RunMaintenance() MaintenanceResult
RunMaintenance executes all maintenance tasks and returns the result.
type MaintenanceSettings ¶
type MaintenanceSettings struct {
// Enabled enables the maintenance worker (default: false)
// Prunes Gemini logs, cleans old backups, archives bloated sessions
Enabled bool `toml:"enabled"`
}
MaintenanceSettings controls the automatic maintenance worker
func GetMaintenanceSettings ¶
func GetMaintenanceSettings() MaintenanceSettings
GetMaintenanceSettings returns maintenance settings from config
type MatchRange ¶
MatchRange represents a match position in content
type MigrationResult ¶
type MigrationResult struct {
Migrated bool // True if migration was performed
ProfilePath string // Path to the migrated profile
SessionCount int // Number of sessions migrated
Message string // Human-readable message
}
MigrationResult contains information about the migration outcome
func MigrateToProfiles ¶
func MigrateToProfiles() (*MigrationResult, error)
MigrateToProfiles migrates from the old single-file layout to the profiles layout. This is safe to call multiple times - it will only migrate once.
Old layout:
~/.hangar/sessions.json ~/.hangar/sessions.json.bak ~/.hangar/sessions.json.bak.1 ~/.hangar/sessions.json.bak.2
New layout:
~/.hangar/config.json ~/.hangar/profiles/default/sessions.json ~/.hangar/profiles/default/sessions.json.bak ~/.hangar/profiles/default/sessions.json.bak.1 ~/.hangar/profiles/default/sessions.json.bak.2 ~/.hangar/logs/ (unchanged)
type ModelPricing ¶
ModelPricing holds pricing per million tokens for a model
type NotificationEntry ¶
type NotificationEntry struct {
SessionID string
TmuxName string
Title string
AssignedKey string
WaitingSince time.Time
Status Status // For icon rendering when show_all enabled
}
NotificationEntry represents a waiting session in the notification bar
type NotificationManager ¶
type NotificationManager struct {
// contains filtered or unexported fields
}
NotificationManager tracks waiting sessions for the notification bar
func NewNotificationManager ¶
func NewNotificationManager(maxShown int, showAll, minimal bool) *NotificationManager
NewNotificationManager creates a new notification manager
func (*NotificationManager) Add ¶
func (nm *NotificationManager) Add(inst *Instance) error
Add registers a session as waiting (newest goes to position [0])
func (*NotificationManager) Clear ¶
func (nm *NotificationManager) Clear()
Clear removes all notifications
func (*NotificationManager) Count ¶
func (nm *NotificationManager) Count() int
Count returns number of notifications
func (*NotificationManager) FormatBar ¶
func (nm *NotificationManager) FormatBar() string
FormatBar returns the formatted status bar text
func (*NotificationManager) GetEntries ¶
func (nm *NotificationManager) GetEntries() []*NotificationEntry
GetEntries returns a copy of current entries (newest first)
func (*NotificationManager) GetSessionByKey ¶
func (nm *NotificationManager) GetSessionByKey(key string) *NotificationEntry
GetSessionByKey returns the entry for a given key (1-6)
func (*NotificationManager) Has ¶
func (nm *NotificationManager) Has(sessionID string) bool
Has checks if a session is in notifications
func (*NotificationManager) IsMinimal ¶
func (nm *NotificationManager) IsMinimal() bool
IsMinimal reports whether this manager is in minimal (icon+count) mode. home.go uses this to skip key binding updates when minimal=true.
func (*NotificationManager) Remove ¶
func (nm *NotificationManager) Remove(sessionID string)
Remove removes a session from notifications
func (*NotificationManager) SyncFromInstances ¶
func (nm *NotificationManager) SyncFromInstances(instances []*Instance, currentSessionID string) (added, removed []string)
SyncFromInstances updates notifications based on current instance states Call this periodically to sync with actual session statuses
type NotificationsConfig ¶
type NotificationsConfig struct {
// Enabled shows notification bar in tmux status (default: true)
Enabled bool `toml:"enabled"`
// MaxShown is the maximum number of sessions shown in the bar (default: 6)
MaxShown int `toml:"max_shown"`
// ShowAll displays all sessions (with status icons) instead of only waiting sessions (default: false)
ShowAll bool `toml:"show_all"`
// Minimal shows a compact icon+count summary instead of session names: ● 2 │ ◐ 3 │ ○ 1
// When true, key bindings (Ctrl+b 1-6) are disabled. ShowAll is ignored. (default: false)
Minimal bool `toml:"minimal"`
}
NotificationsConfig configures the waiting session notification bar
func GetNotificationsSettings ¶
func GetNotificationsSettings() NotificationsConfig
GetNotificationsSettings returns notification bar settings with defaults applied
type OpenCodeOptions ¶
type OpenCodeOptions struct {
// SessionMode: "new" (default), "continue" (-c), or "resume" (-s)
SessionMode string `json:"session_mode,omitempty"`
// ResumeSessionID is the session ID for -s flag (only when SessionMode="resume")
ResumeSessionID string `json:"resume_session_id,omitempty"`
// Model overrides the model (e.g., "anthropic/claude-sonnet-4-5-20250929")
Model string `json:"model,omitempty"`
// Agent overrides the agent to use
Agent string `json:"agent,omitempty"`
}
OpenCodeOptions holds launch options for OpenCode CLI sessions
func NewOpenCodeOptions ¶
func NewOpenCodeOptions(config *UserConfig) *OpenCodeOptions
NewOpenCodeOptions creates OpenCodeOptions with defaults from config
func UnmarshalOpenCodeOptions ¶
func UnmarshalOpenCodeOptions(data json.RawMessage) (*OpenCodeOptions, error)
UnmarshalOpenCodeOptions deserializes OpenCodeOptions from JSON wrapper
func (*OpenCodeOptions) ToArgs ¶
func (o *OpenCodeOptions) ToArgs() []string
ToArgs returns command-line arguments based on options
func (*OpenCodeOptions) ToArgsForFork ¶
func (o *OpenCodeOptions) ToArgsForFork() []string
ToArgsForFork returns arguments suitable for fork resume command. Fork uses -s internally, so session mode flags are excluded.
func (*OpenCodeOptions) ToolName ¶
func (o *OpenCodeOptions) ToolName() string
ToolName returns "opencode"
type OpenCodeSettings ¶
type OpenCodeSettings struct {
// DefaultModel is the model to use for new OpenCode sessions
// Format: "provider/model" (e.g., "anthropic/claude-sonnet-4-5-20250929")
// If empty, OpenCode uses its own default
DefaultModel string `toml:"default_model"`
// DefaultAgent is the agent to use for new OpenCode sessions
// If empty, OpenCode uses its own default
DefaultAgent string `toml:"default_agent"`
// EnvFile is a .env file specific to OpenCode sessions
// Sourced AFTER global [shell].env_files
// Path can be absolute, ~ for home, $HOME/${VAR} for env vars, or relative to session working directory
EnvFile string `toml:"env_file"`
}
OpenCodeSettings defines OpenCode CLI configuration
type PreviewSettings ¶
type PreviewSettings struct {
// ShowOutput shows terminal output in preview pane (including launch animation)
// Default: true (pointer to distinguish "not set" from "explicitly false")
ShowOutput *bool `toml:"show_output"`
// ShowAnalytics shows session analytics panel for Claude sessions
// Default: false (pointer to distinguish "not set" from "explicitly false")
ShowAnalytics *bool `toml:"show_analytics"`
// Analytics configures which sections to show in the analytics panel
Analytics AnalyticsDisplaySettings `toml:"analytics"`
}
PreviewSettings defines preview pane configuration
func GetPreviewSettings ¶
func GetPreviewSettings() PreviewSettings
GetPreviewSettings returns preview settings with defaults applied
func (*PreviewSettings) GetAnalyticsSettings ¶
func (p *PreviewSettings) GetAnalyticsSettings() AnalyticsDisplaySettings
GetAnalyticsSettings returns the analytics display settings with defaults applied
func (*PreviewSettings) GetShowAnalytics ¶
func (p *PreviewSettings) GetShowAnalytics() bool
GetShowAnalytics returns whether to show analytics, defaulting to false
func (*PreviewSettings) GetShowOutput ¶
func (p *PreviewSettings) GetShowOutput() bool
GetShowOutput returns whether to show terminal output, defaulting to true
type ProfileClaudeSettings ¶
type ProfileClaudeSettings struct {
// ConfigDir overrides [claude].config_dir for this profile only.
ConfigDir string `toml:"config_dir"`
}
ProfileClaudeSettings defines profile-specific Claude overrides.
type ProfileSettings ¶
type ProfileSettings struct {
// Claude defines Claude Code overrides for a specific profile.
Claude ProfileClaudeSettings `toml:"claude"`
}
ProfileSettings defines per-profile configuration overrides.
type Project ¶
type Project struct {
Name string `toml:"name"`
BaseDir string `toml:"base_dir"`
BaseBranch string `toml:"base_branch"`
Order int `toml:"order,omitempty"`
}
Project represents a git repository that hangar manages sessions for.
func GetProject ¶
GetProject returns a project by name.
func ListProjects ¶
ListProjects returns projects sorted by Order then Name.
func LoadProjects ¶
LoadProjects reads ~/.hangar/projects.toml Returns empty list if file doesn't exist.
type ProjectMCPSettings ¶
type ProjectMCPSettings struct {
EnableAllProjectMcpServers bool `json:"enableAllProjectMcpServers,omitempty"`
EnabledMcpjsonServers []string `json:"enabledMcpjsonServers,omitempty"`
DisabledMcpjsonServers []string `json:"disabledMcpjsonServers,omitempty"`
}
ProjectMCPSettings represents .claude/settings.local.json
type ProjectsManager ¶
type ProjectsManager struct {
// contains filtered or unexported fields
}
ProjectsManager manages the projects list.
type ResponseOutput ¶
type ResponseOutput struct {
Tool string `json:"tool"` // Tool type (claude, gemini, etc.)
Role string `json:"role"` // Always "assistant" for now
Content string `json:"content"` // The actual response text
Timestamp string `json:"timestamp,omitempty"` // When the response was generated (Claude only)
SessionID string `json:"session_id,omitempty"` // Claude session ID (if available)
}
ResponseOutput represents a parsed response from an agent session
type SearchEntry ¶
type SearchEntry struct {
SessionID string // Claude session UUID
FilePath string // Path to .jsonl file
CWD string // Project working directory
Summary string // First user message or summary
ModTime time.Time // File modification time
FileSize int64 // File size in bytes
// contains filtered or unexported fields
}
SearchEntry represents a searchable Claude session
func (*SearchEntry) ContentPreview ¶
func (e *SearchEntry) ContentPreview(max int) string
func (*SearchEntry) ContentString ¶
func (e *SearchEntry) ContentString() string
func (*SearchEntry) GetSnippet ¶
func (e *SearchEntry) GetSnippet(query string, windowSize int) string
GetSnippet extracts a context window around the first match Uses rune-based indexing to safely handle UTF-8 content Optimized: Single rune conversion instead of triple conversion
func (*SearchEntry) Match ¶
func (e *SearchEntry) Match(query string) []MatchRange
Match searches for query in entry content (case-insensitive) Returns match positions for highlighting
func (*SearchEntry) MatchCount ¶
func (e *SearchEntry) MatchCount(query string) int
type SearchResult ¶
type SearchResult struct {
Entry *SearchEntry
Matches []MatchRange
Score int
Snippet string
}
SearchResult represents a search result with match info
type SearchTier ¶
type SearchTier int
SearchTier represents the search strategy tier
const ( TierInstant SearchTier = iota // < 100MB, full in-memory TierBalanced // 100MB-500MB, on-demand scan to cap memory )
func DetectTier ¶
func DetectTier(totalSize int64) SearchTier
DetectTier determines the appropriate search tier based on data size
type SessionAnalytics ¶
type SessionAnalytics struct {
// Token usage (cumulative across all turns)
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
CacheReadTokens int `json:"cache_read_input_tokens"`
CacheWriteTokens int `json:"cache_creation_input_tokens"`
// Current context size (last turn's input + cache read tokens)
// This represents the actual context window usage, not cumulative totals
CurrentContextTokens int `json:"current_context_tokens"`
// Session metrics
TotalTurns int `json:"total_turns"`
Duration time.Duration `json:"duration"`
StartTime time.Time `json:"start_time"`
LastActive time.Time `json:"last_active"`
// Tool usage
ToolCalls []ToolCall `json:"tool_calls"`
// Subagents
Subagents []SubagentInfo `json:"subagents"`
// Cost estimation
EstimatedCost float64 `json:"estimated_cost"`
// 5-hour billing blocks
BillingBlocks []BillingBlock `json:"billing_blocks"`
}
SessionAnalytics holds parsed session metrics from Claude JSONL files
func ParseSessionJSONL ¶
func ParseSessionJSONL(path string) (*SessionAnalytics, error)
ParseSessionJSONL parses a Claude session JSONL file and returns analytics
func (*SessionAnalytics) CalculateCost ¶
func (a *SessionAnalytics) CalculateCost(model string) float64
CalculateCost estimates session cost based on token usage and model pricing
func (*SessionAnalytics) ContextPercent ¶
func (a *SessionAnalytics) ContextPercent(modelLimit int) float64
ContextPercent returns the percentage of context window used Uses CurrentContextTokens (last turn's input + cache) for accurate context usage modelLimit is the model's context window size (defaults to 200000 for Claude)
func (*SessionAnalytics) TotalTokens ¶
func (a *SessionAnalytics) TotalTokens() int
TotalTokens returns the sum of all token types
type ShellSettings ¶
type ShellSettings struct {
// EnvFiles is a list of .env files to source for ALL sessions
// Paths can be absolute, ~ for home, $HOME/${VAR} for env vars, or relative to session working directory
// Files are sourced in order; later files override earlier ones
EnvFiles []string `toml:"env_files"`
// InitScript is an optional shell script or command to run before each session
// Useful for direnv, nvm, pyenv, etc.
// Can be a file path (e.g., "~/.hangar/init.sh") or inline command
// (e.g., 'eval "$(direnv hook bash)"')
InitScript string `toml:"init_script"`
// IgnoreMissingEnvFiles silently ignores missing .env files (default: true)
// When false, sessions will error if an env_file doesn't exist
IgnoreMissingEnvFiles *bool `toml:"ignore_missing_env_files"`
}
ShellSettings defines shell environment configuration for sessions
func (*ShellSettings) GetIgnoreMissingEnvFiles ¶
func (s *ShellSettings) GetIgnoreMissingEnvFiles() bool
GetIgnoreMissingEnvFiles returns whether to ignore missing env files, defaulting to true
type SlackSettings ¶
SlackSettings defines Slack bot configuration (stub; conductor is removed).
type StatusEvent ¶
type StatusEvent struct {
InstanceID string `json:"instance_id"`
Title string `json:"title"`
Tool string `json:"tool"`
Status string `json:"status"`
PrevStatus string `json:"prev_status"`
Timestamp int64 `json:"ts"`
}
StatusEvent represents a session status change event. Written atomically to ~/.hangar/events/ by both the hook handler (Claude) and the TUI's background status poller (all tools).
type StatusEventWatcher ¶
type StatusEventWatcher struct {
// contains filtered or unexported fields
}
StatusEventWatcher watches ~/.hangar/events/ for new status events using fsnotify. Delivers parsed StatusEvent structs via a channel.
func NewStatusEventWatcher ¶
func NewStatusEventWatcher(filterInstanceID string) (*StatusEventWatcher, error)
NewStatusEventWatcher creates a watcher for the events directory. If filterInstanceID is non-empty, only events for that instance are delivered. Call Start() in a goroutine, then read from EventCh().
func (*StatusEventWatcher) EventCh ¶
func (w *StatusEventWatcher) EventCh() <-chan StatusEvent
EventCh returns the channel that delivers parsed status events.
func (*StatusEventWatcher) Start ¶
func (w *StatusEventWatcher) Start()
Start begins watching the events directory. Must be called in a goroutine. Blocks until Stop() is called or the context is cancelled.
func (*StatusEventWatcher) Stop ¶
func (w *StatusEventWatcher) Stop()
Stop shuts down the watcher and closes the event channel.
func (*StatusEventWatcher) WaitForStatus ¶
func (w *StatusEventWatcher) WaitForStatus(statuses []string, timeout time.Duration) (StatusEvent, error)
WaitForStatus blocks until an event with one of the given statuses is received, or the timeout expires.
type StatusFileWatcher ¶
type StatusFileWatcher struct {
// contains filtered or unexported fields
}
StatusFileWatcher watches ~/.hangar/hooks/ for status file changes and updates instance hook status in real time.
func NewStatusFileWatcher ¶
func NewStatusFileWatcher(onChange func()) (*StatusFileWatcher, error)
NewStatusFileWatcher creates a new watcher for the hooks directory. Call Start() to begin watching.
func (*StatusFileWatcher) GetHookStatus ¶
func (w *StatusFileWatcher) GetHookStatus(instanceID string) *HookStatus
GetHookStatus returns the hook status for an instance, or nil if not available.
func (*StatusFileWatcher) Start ¶
func (w *StatusFileWatcher) Start()
Start begins watching the hooks directory. Must be called in a goroutine.
type StatusSettings ¶
type StatusSettings struct {
}
func GetStatusSettings ¶
func GetStatusSettings() StatusSettings
GetStatusSettings returns status detection settings with defaults applied.
type Storage ¶
type Storage struct {
// contains filtered or unexported fields
}
Storage handles persistence of session data via SQLite. Thread-safe with mutex protection for concurrent access within a single process. Multiple processes share data via SQLite WAL mode.
func NewStorageWithProfile ¶
NewStorageWithProfile creates a storage instance for a specific profile. If profile is empty, uses the effective profile (from env var or config). Automatically runs migration from old layout if needed, then opens SQLite. If sessions.json exists and state.db is empty, auto-migrates data.
func (*Storage) DeleteInstance ¶
DeleteInstance removes a single instance from the database by ID. This ensures the row is immediately removed, preventing resurrection on reload.
func (*Storage) DeleteTodo ¶
DeleteTodo removes a todo by ID.
func (*Storage) DeleteTodosForSession ¶
DeleteTodosForSession removes the todo linked to the given session. The todo-session relationship is 1:1 by design; at most one todo will be affected.
func (*Storage) FindTodoBySessionID ¶
FindTodoBySessionID returns the todo linked to the given session, or nil.
func (*Storage) GetDB ¶
GetDB returns the underlying StateDB for direct access (status writes, heartbeat, etc.)
func (*Storage) GetFileMtime ¶
GetFileMtime returns the filesystem modification time of the database file. This is useful for detecting external changes when polling.
func (*Storage) GetUpdatedAt ¶
GetUpdatedAt returns the last modification timestamp from SQLite metadata.
func (*Storage) LoadLite ¶
func (s *Storage) LoadLite() ([]*InstanceData, []*GroupData, error)
LoadLite reads session data from SQLite without tmux reconnection. This is a fast path for operations that only need to read session metadata (e.g., finding current session by tmux name) without initializing full Instance objects. Returns raw InstanceData and GroupData without any subprocess calls.
func (*Storage) LoadWithGroups ¶
LoadWithGroups reads instances and groups from SQLite, reconnects tmux sessions.
func (*Storage) OrphanTodosForSession ¶
OrphanTodosForSession sets status to "orphaned" for the todo linked to the given session. The todo-session relationship is 1:1 by design; at most one todo will be affected.
func (*Storage) Save ¶
Save persists instances to SQLite DEPRECATED: Use SaveWithGroups to ensure groups are not lost
func (*Storage) SaveGroupsOnly ¶
SaveGroupsOnly persists only the groups table to SQLite. This is a lightweight save for visual state like group expanded/collapsed. It does NOT call Touch() to avoid triggering StorageWatcher reloads on other instances.
func (*Storage) SaveTodo ¶
SaveTodo inserts or updates a todo. Updates todo.UpdatedAt to the current time before saving.
func (*Storage) SaveWithGroups ¶
SaveWithGroups persists instances and groups to SQLite. Converts Instance objects to database rows, then batch-inserts in a transaction.
func (*Storage) UpdateTodoStatus ¶
func (s *Storage) UpdateTodoStatus(id string, status TodoStatus, sessionID string) error
UpdateTodoStatus updates a todo's status and linked session ID.
type StorageData ¶
type StorageData struct {
Instances []*InstanceData `json:"instances"`
Groups []*GroupData `json:"groups,omitempty"` // Persist empty groups
UpdatedAt time.Time `json:"updated_at"`
}
StorageData represents the JSON structure for persistence (kept for migration/compat)
type SubagentInfo ¶
type SubagentInfo struct {
ID string `json:"id"`
StartTime time.Time `json:"start_time"`
Turns int `json:"turns"`
}
SubagentInfo holds metadata about a subagent spawned during a session
type TelegramSettings ¶
TelegramSettings defines Telegram bot configuration (stub; conductor is removed).
type TmuxSettings ¶
type TmuxSettings struct {
// InjectStatusLine controls whether hangar injects a custom status line
// into new tmux sessions. When false, the tmux status bar is not modified,
// allowing users to use their own tmux status line configuration.
// Default: true (nil = use default true)
InjectStatusLine *bool `toml:"inject_status_line"`
// Options is a map of tmux option names to values.
// These are passed to `tmux set-option -t <session>` after defaults.
Options map[string]string `toml:"options"`
// MouseMode controls whether tmux mouse mode is enabled when attaching sessions.
// When true, clicking the tmux status bar switches windows and scroll works in panes.
// Default: false (opt-in). Enable with mouse_mode = true in [tmux] config section.
MouseMode *bool `toml:"mouse_mode"`
}
TmuxSettings allows users to override tmux options applied to every session. Options are applied AFTER hangar's defaults, so they take precedence.
Example config.toml:
[tmux]
inject_status_line = false
mouse_mode = true
options = { "allow-passthrough" = "all", "history-limit" = "50000" }
func GetTmuxSettings ¶
func GetTmuxSettings() TmuxSettings
GetTmuxSettings returns tmux option overrides from config
func (TmuxSettings) GetInjectStatusLine ¶
func (t TmuxSettings) GetInjectStatusLine() bool
GetInjectStatusLine returns whether to inject status line, defaulting to true
func (TmuxSettings) GetMouseMode ¶
func (t TmuxSettings) GetMouseMode() bool
GetMouseMode returns whether tmux mouse mode should be enabled on attach. Defaults to false if not set.
type Todo ¶
type Todo struct {
ID string
ProjectPath string
Title string
Description string
Prompt string // optional; sent to session on creation
Status TodoStatus
SessionID string // empty = unlinked
Order int
CreatedAt time.Time
UpdatedAt time.Time
}
Todo represents a work item tied to a project.
type TodoStatus ¶
type TodoStatus string
TodoStatus represents the lifecycle state of a todo item.
const ( TodoStatusTodo TodoStatus = "todo" TodoStatusInProgress TodoStatus = "in_progress" TodoStatusInReview TodoStatus = "in_review" TodoStatusDone TodoStatus = "done" TodoStatusOrphaned TodoStatus = "orphaned" )
type ToolDef ¶
type ToolDef struct {
// Command is the shell command to run
Command string `toml:"command"`
// Wrapper is an optional command that wraps the tool command.
// Use {command} placeholder to include the tool command, or omit it to replace the command.
// Example: wrapper = "nvim +'terminal {command}' +'startinsert'"
Wrapper string `toml:"wrapper"`
// Icon is the emoji/symbol to display
Icon string `toml:"icon"`
// BusyPatterns are strings that indicate the tool is busy
BusyPatterns []string `toml:"busy_patterns"`
// PromptPatterns are strings that indicate the tool is waiting for input
PromptPatterns []string `toml:"prompt_patterns"`
// DetectPatterns are regex patterns to auto-detect this tool from terminal content
DetectPatterns []string `toml:"detect_patterns"`
// ResumeFlag is the CLI flag to resume a session (e.g., "--resume")
ResumeFlag string `toml:"resume_flag"`
// SessionIDEnv is the tmux environment variable name storing the session ID
SessionIDEnv string `toml:"session_id_env"`
// DangerousMode enables dangerous mode flag for this tool
DangerousMode bool `toml:"dangerous_mode"`
// DangerousFlag is the CLI flag for dangerous mode (e.g., "--dangerously-skip-permissions")
DangerousFlag string `toml:"dangerous_flag"`
// OutputFormatFlag is the CLI flag for JSON output format (e.g., "--output-format json")
OutputFormatFlag string `toml:"output_format_flag"`
// SessionIDJsonPath is the jq path to extract session ID from JSON output
SessionIDJsonPath string `toml:"session_id_json_path"`
// EnvFile is a .env file specific to this tool
// Sourced AFTER global [shell].env_files
// Path can be absolute, ~ for home, $HOME/${VAR} for env vars, or relative to session working directory
EnvFile string `toml:"env_file"`
// Env is inline environment variables for this tool
// These are exported AFTER env_file (highest priority)
// Example: env = { ANTHROPIC_BASE_URL = "https://...", API_KEY = "token" }
Env map[string]string `toml:"env"`
// BusyPatternsExtra appends additional busy patterns to the built-in defaults
BusyPatternsExtra []string `toml:"busy_patterns_extra"`
// PromptPatternsExtra appends additional prompt patterns to the built-in defaults
PromptPatternsExtra []string `toml:"prompt_patterns_extra"`
// SpinnerChars replaces the default spinner characters entirely (use with caution)
SpinnerChars []string `toml:"spinner_chars"`
// SpinnerCharsExtra appends additional spinner characters to the built-in defaults
SpinnerCharsExtra []string `toml:"spinner_chars_extra"`
}
ToolDef defines a custom AI tool
func GetToolDef ¶
GetToolDef returns a tool definition from user config Returns nil if tool is not defined
type ToolOptions ¶
type ToolOptions interface {
// ToolName returns the name of the tool (e.g., "claude", "codex")
ToolName() string
// ToArgs returns command-line arguments for the tool
ToArgs() []string
}
ToolOptions is the interface for tool-specific launch options Each AI tool (claude, codex, gemini, etc.) can have its own options struct that implements this interface
type ToolOptionsWrapper ¶
type ToolOptionsWrapper struct {
Tool string `json:"tool"`
Options json.RawMessage `json:"options"`
}
ToolOptionsWrapper wraps tool options for JSON serialization JSON structure: {"tool": "claude", "options": {...}}
type TransitionDaemon ¶
type TransitionDaemon struct {
// contains filtered or unexported fields
}
func NewTransitionDaemon ¶
func NewTransitionDaemon() *TransitionDaemon
type TransitionNotificationEvent ¶
type TransitionNotificationEvent struct {
ChildSessionID string `json:"child_session_id"`
ChildTitle string `json:"child_title"`
Profile string `json:"profile"`
FromStatus string `json:"from_status"`
ToStatus string `json:"to_status"`
Timestamp time.Time `json:"timestamp"`
TargetSessionID string `json:"target_session_id,omitempty"`
TargetKind string `json:"target_kind,omitempty"` // parent | conductor
DeliveryResult string `json:"delivery_result,omitempty"`
}
type TransitionNotifier ¶
type TransitionNotifier struct {
// contains filtered or unexported fields
}
func NewTransitionNotifier ¶
func NewTransitionNotifier() *TransitionNotifier
func (*TransitionNotifier) NotifyTransition ¶
func (n *TransitionNotifier) NotifyTransition(event TransitionNotificationEvent) TransitionNotificationEvent
type UpdateSettings ¶
type UpdateSettings struct {
// AutoUpdate automatically installs updates without prompting
// Default: false
AutoUpdate bool `toml:"auto_update"`
// CheckEnabled enables automatic update checks on startup
// Default: true
CheckEnabled bool `toml:"check_enabled"`
// CheckIntervalHours is how often to check for updates (in hours)
// Default: 24
CheckIntervalHours int `toml:"check_interval_hours"`
// NotifyInCLI shows update notification in CLI commands (not just TUI)
// Default: true
NotifyInCLI bool `toml:"notify_in_cli"`
}
UpdateSettings defines auto-update configuration
func GetUpdateSettings ¶
func GetUpdateSettings() UpdateSettings
GetUpdateSettings returns update settings with defaults applied
type UserConfig ¶
type UserConfig struct {
// DefaultTool is the pre-selected AI tool when creating new sessions
// Valid values: "claude", "gemini", "opencode", "codex", or any custom tool name
// If empty or invalid, defaults to "shell" (no pre-selection)
DefaultTool string `toml:"default_tool"`
// Theme sets the color scheme: "dark" (default), "light", or "system"
Theme string `toml:"theme"`
// Tools defines custom AI tool configurations
Tools map[string]ToolDef `toml:"tools"`
// MCPDefaultScope sets the default scope for MCP operations
// Valid values: "local" (default), "global", "user"
MCPDefaultScope string `toml:"mcp_default_scope"`
// ManageMCPJson controls whether hangar writes to .mcp.json in project directories.
// Set to false to prevent hangar from touching any .mcp.json files, which is useful
// when you manage that file manually or via another tool.
// Default: true (nil = true)
ManageMCPJson *bool `toml:"manage_mcp_json"`
// MCPs defines available MCP servers for the MCP Manager
// These can be attached/detached per-project via the MCP Manager (M key)
MCPs map[string]MCPDef `toml:"mcps"`
// Claude defines Claude Code integration settings
Claude ClaudeSettings `toml:"claude"`
// Profiles defines optional per-profile overrides.
// Example:
// [profiles.work.claude]
// config_dir = "~/.claude-work"
Profiles map[string]ProfileSettings `toml:"profiles"`
// Gemini defines Gemini CLI integration settings
Gemini GeminiSettings `toml:"gemini"`
// OpenCode defines OpenCode CLI integration settings
OpenCode OpenCodeSettings `toml:"opencode"`
// Codex defines Codex CLI integration settings
Codex CodexSettings `toml:"codex"`
// Worktree defines git worktree preferences
Worktree WorktreeSettings `toml:"worktree"`
// GlobalSearch defines global conversation search settings
GlobalSearch GlobalSearchSettings `toml:"global_search"`
// Logs defines session log management settings
Logs LogSettings `toml:"logs"`
// Updates defines auto-update settings
Updates UpdateSettings `toml:"updates"`
// Preview defines preview pane display settings
Preview PreviewSettings `toml:"preview"`
// Experiments defines experiment folder settings for 'try' command
Experiments ExperimentsSettings `toml:"experiments"`
// Notifications defines waiting session notification bar settings
Notifications NotificationsConfig `toml:"notifications"`
// Instances defines multiple instance behavior settings
Instances InstanceSettings `toml:"instances"`
// Shell defines global shell environment settings for sessions
Shell ShellSettings `toml:"shell"`
// Maintenance defines automatic maintenance worker settings
Maintenance MaintenanceSettings `toml:"maintenance"`
// Status defines session status detection settings
Status StatusSettings `toml:"status"`
// Conductor defines conductor (meta-agent orchestration) settings
Conductor ConductorSettings `toml:"conductor"`
// RemoteLabels maps git remote hostnames to short display labels shown in
// the preview pane. github.com → "github" is always built-in.
// Example: "ghe.mycompany.com" = "github"
RemoteLabels map[string]string `toml:"remote_labels"`
// Tmux defines tmux option overrides applied to every session
Tmux TmuxSettings `toml:"tmux"`
}
UserConfig represents user-facing configuration in TOML format
func LoadUserConfig ¶
func LoadUserConfig() (*UserConfig, error)
LoadUserConfig loads the user configuration from TOML file Returns cached config after first load
func ReloadUserConfig ¶
func ReloadUserConfig() (*UserConfig, error)
ReloadUserConfig forces a reload of the user config
func (*UserConfig) GetProfileClaudeConfigDir ¶
func (c *UserConfig) GetProfileClaudeConfigDir(profile string) string
GetProfileClaudeConfigDir returns the profile-specific Claude config directory, if configured.
func (*UserConfig) GetShowAnalytics ¶
func (c *UserConfig) GetShowAnalytics() bool
GetShowAnalytics returns whether to show analytics panel, defaulting to false
func (*UserConfig) GetShowOutput ¶
func (c *UserConfig) GetShowOutput() bool
GetShowOutput returns whether to show terminal output in preview
type WorktreeSettings ¶
type WorktreeSettings struct {
// AutoCleanup: remove worktree when session is deleted
AutoCleanup bool `toml:"auto_cleanup"`
// DefaultLocation: "sibling" (next to repo), "subdirectory" (inside .worktrees/),
// or a custom path (e.g., "~/worktrees") creating <path>/<repo_name>/<branch>
DefaultLocation string `toml:"default_location"`
// PathTemplate: custom path template for worktree location.
// Variables: {repo-name}, {repo-root}, {branch}, {session-id}
// Unknown variables like {foo} are left as-is in the path.
// If set, overrides DefaultLocation.
PathTemplate *string `toml:"path_template"`
// AutoUpdateBase: when true, fast-forward pull the base branch before creating
// a new worktree so sessions start from the latest upstream code.
// Default: true
AutoUpdateBase bool `toml:"auto_update_base"`
}
WorktreeSettings contains git worktree preferences.
func GetWorktreeSettings ¶
func GetWorktreeSettings() WorktreeSettings
GetWorktreeSettings returns worktree settings with defaults applied
func (*WorktreeSettings) Template ¶
func (w *WorktreeSettings) Template() string
Template returns the path template if set, or empty string if nil.
Source Files
¶
- analytics.go
- claude.go
- claude_hooks.go
- config.go
- discovery.go
- env.go
- event_watcher.go
- event_writer.go
- gemini.go
- gemini_analytics.go
- gemini_mcp.go
- global_search.go
- groups.go
- hook_watcher.go
- instance.go
- maintenance.go
- mcp_catalog.go
- migration.go
- namegen.go
- notifications.go
- projects.go
- send_helper.go
- storage.go
- todo.go
- tooloptions.go
- transition_daemon.go
- transition_notifier.go
- userconfig.go
- utils.go