Documentation
¶
Index ¶
- Constants
- Variables
- func AttachCmd() *cobra.Command
- func AttachToSession(sessionID, tmuxSession string, forceAttach bool) error
- func AttachToTmuxSession(tmuxSession string) int
- func CheckHooksInstalled() (installed bool, missing []string, needsRepair bool)
- func CheckTmuxInstalled() error
- func ClaudeSettingsPath() string
- func CleanupOldExitedSessions(maxAge time.Duration) error
- func Cmd() *cobra.Command
- func ConfigureTmuxKeybindings()
- func DeleteSessionState(id string) error
- func DetachSessionClients(sessionName string) (int, error)
- func EnsureHooksInstalled(autoInstall bool, stdout, stderr *os.File) bool
- func FindClaudePID() int
- func FocusCmd() *cobra.Command
- func FocusOwnWindow() bool
- func FormatDuration(d time.Duration) string
- func GenerateSessionID() string
- func GetCurrentTmuxSession() string
- func GetOwnWindowTitle() string
- func GetParentPID(pid int) int
- func GetProcessName(pid int) string
- func GetSessionCompletions(includeExited bool) []string
- func GetTmuxSessionAttachedCount(sessionName string) int
- func GitLocationOf(dir string) (worktreeRoot, branch string)
- func GotoCmd() *cobra.Command
- func GuardAgainstNestedSpawn() error
- func HookCallbackCmd() *cobra.Command
- func InstallHooks() error
- func IsProcessAlive(pid int) bool
- func IsTmuxSessionAlive(sessionName string) bool
- func IsTmuxSessionAttached(sessionName string) bool
- func IsXdotoolInstalled() bool
- func KillCmd() *cobra.Command
- func ListCmd() *cobra.Command
- func MaxUpdatedAt() (time.Time, error)
- func NewCmd() *cobra.Command
- func NotifyListenCmd() *cobra.Command
- func ParsePIDFromTmux(sessionName string) int
- func PruneCmd() *cobra.Command
- func RefreshSessionStatus(state *SessionState)
- func RegisterJoinGroupCompletion(cmd *cobra.Command)
- func ReinitHookCommand()
- func ReplayCmd() *cobra.Command
- func RunInteractive(includeAll bool, state WatchState) (AttachResult, WatchState, error)
- func RunNew(params *NewParams) error
- func RunWatchMode(includeAll bool, initialSort table.SortState, ...) error
- func SaveSessionState(state *SessionState) error
- func SessionExists(id string) (bool, error)
- func ShortID(id string) string
- func ShortenPath(path string, maxLen int) string
- func SortSessionsByKey(sessions []*SessionState, key string, dir table.SortDirection)
- func StatusCallbackCmd() *cobra.Command
- func TaskSignalPath(cwd string) string
- func TryFocusAttachedSession(tmuxSession string)
- func TryFocusAttachedSessionWithID(tmuxSession, sessionID string)
- func WorkDirFromToolUse(toolName string, toolInput json.RawMessage, cwd string) (string, bool)
- type AttachParams
- type AttachResult
- type FocusParams
- type GotoParams
- type HookCallbackInput
- type HookConfig
- type HookInput
- type HookMatcher
- type KillParams
- type ListParams
- type NewParams
- type PruneParams
- type SessionState
- type StatusCallbackParams
- type TaskSignal
- type WatchState
Constants ¶
const ( StatusRunning = "running" StatusWaitingInput = "waiting_input" StatusWaitingPermission = "waiting_permission" StatusExited = "exited" )
Status constants
const ( StatusWorking = "working" StatusMainAgentIdle = "main_agent_idle" StatusIdle = "idle" StatusAwaitingPermission = "awaiting_permission" StatusAwaitingInput = "awaiting_input" // StatusError marks a turn that ended in an API/auth/billing error // (Claude Code's StopFailure hook). It is transient: the next // normal hook event overwrites it, so a retried agent leaves the // error state on its own. See the StopFailure case in hook_callback.go. StatusError = "error" )
Valid status values for callbacks
const DefaultCleanupAge = 7 * 24 * time.Hour // 1 week
DefaultCleanupAge is the default max age for exited sessions in prune command
Variables ¶
var ClaudeAncestorCheck = func() bool { return FindClaudePID() != 0 }
ClaudeAncestorCheck reports whether the current process is running underneath a Claude Code instance — i.e. whether a `claude`/`node` process appears anywhere in this process's own ancestry, walked up from os.Getppid().
It is a package var, not a plain func, so tests (and the agentd flow tests) can substitute a deterministic implementation — "pretend we have a claude ancestor" — without literally running the test binary under Claude Code. Production points it at FindClaudePID, the same process-tree walk agentd's identity middleware uses (via convIDForPID) to tell a human caller apart from an agent.
var ErrNestedClaudeSpawn = errors.New("refusing to launch a nested Claude Code session")
ErrNestedClaudeSpawn is the sentinel wrapped by the error GuardAgainstNestedSpawn returns, so callers/tests can match it with errors.Is regardless of the human-readable explanation appended.
var HookCommand string
HookCommand is the unified callback command for all hooks (detected at startup)
var JoinGroupHandler func(*NewParams) error
JoinGroupHandler implements `--join-group`. Set by the agent package's init() to avoid a session→agent import cycle (agent already depends on session for AttachToSession). When nil, --join-group falls back to a clear error.
var RequiredHooks map[string][]HookMatcher
RequiredHooks defines the hooks tclaude needs for status tracking All hooks use the same unified callback - it reads stdin and figures out what to do
Functions ¶
func AttachToSession ¶
AttachToSession attaches to a tmux session. Sets terminal title for window focus, then replaces process with tmux attach. If forceAttach is true, detaches other clients before attaching (-d flag).
func AttachToTmuxSession ¶
AttachToTmuxSession attaches to a tmux session, replacing the current process Returns exit code (0 = success) for use by other packages
func CheckHooksInstalled ¶
CheckHooksInstalled checks if tclaude hooks are installed in Claude settings. Returns: installed (all required hooks present with current binary), missing event names, needsRepair (stale or duplicate hooks detected).
func CheckTmuxInstalled ¶
func CheckTmuxInstalled() error
CheckTmuxInstalled verifies tmux is available
func ClaudeSettingsPath ¶
func ClaudeSettingsPath() string
ClaudeSettingsPath returns the path to ~/.claude/settings.json
func CleanupOldExitedSessions ¶
CleanupOldExitedSessions removes exited session states older than maxAge.
func ConfigureTmuxKeybindings ¶ added in v0.0.45
func ConfigureTmuxKeybindings()
ConfigureTmuxKeybindings sets up keybindings on the tclaude tmux server for session navigation. Prefix-less so they work without Ctrl+b.
Only enabled on macOS with iTerm2 or Terminal.app, since the goto command uses AppleScript to focus terminal windows/tabs.
Current bindings:
- Shift+Right → goto next session
- Shift+Left → goto prev session
func DeleteSessionState ¶
DeleteSessionState removes a session from the database.
func DetachSessionClients ¶
DetachSessionClients detaches all clients from a tmux session and returns how many were detached. The tmux session — and the process running inside it — keeps running untouched; only the attached clients (the terminal windows) go away. A count of 0 means the session had no window open: a clean no-op.
func EnsureHooksInstalled ¶
EnsureHooksInstalled checks and optionally installs hooks, returning true if ready. Stale hooks (wrong/duplicate binary) are always auto-repaired since the user already opted into hook management. The autoInstall flag only controls first-time installation.
func FindClaudePID ¶
func FindClaudePID() int
FindClaudePID walks up the process tree from the current process to find a parent process named "claude" or "node" (Claude Code runs as node) Returns the PID of the Claude process, or 0 if not found
func FocusOwnWindow ¶
func FocusOwnWindow() bool
FocusOwnWindow attempts to focus the current process's terminal window.
func FormatDuration ¶
FormatDuration formats a duration in a human-readable way
func GenerateSessionID ¶
func GenerateSessionID() string
GenerateSessionID creates a short unique session ID
func GetCurrentTmuxSession ¶
func GetCurrentTmuxSession() string
GetCurrentTmuxSession returns the current tmux session name if running inside tmux Returns empty string if not in tmux
func GetOwnWindowTitle ¶
func GetOwnWindowTitle() string
GetOwnWindowTitle returns the title of the current terminal window.
func GetParentPID ¶
GetParentPID returns the parent PID of a process Returns 0 if unable to determine
func GetProcessName ¶
GetProcessName returns the name of a process Returns empty string if unable to determine
func GetSessionCompletions ¶
GetSessionCompletions returns completions for session IDs If includeExited is true, includes exited sessions (for kill command)
func GetTmuxSessionAttachedCount ¶
GetTmuxSessionAttachedCount returns the number of clients attached to a tmux session Returns 0 if session doesn't exist or on error
func GitLocationOf ¶ added in v0.0.249
GitLocationOf resolves dir to its git worktree root and the branch checked out there. It shells out to git twice; both calls are best-effort — a dir outside any git repo (or a missing git binary) yields ("", ""), which is recorded faithfully as "not in a repo" rather than treated as an error. A detached HEAD reports an empty branch (there's no branch name to show).
The PostToolUse hook calls this once per file edit so an agent's current branch is stamped into agent_workdir at edit time — read surfaces then never have to run git themselves.
func GuardAgainstNestedSpawn ¶ added in v0.0.268
func GuardAgainstNestedSpawn() error
GuardAgainstNestedSpawn refuses to start a new Claude Code session when the calling `tclaude` process is itself running underneath a Claude Code instance. This stops a runaway chain of CC instances launching each other directly via `tclaude session new` (or bare `tclaude`).
Daemon-initiated spawns are deliberately unaffected: `tclaude agent spawn` / `groups resume` make the agentd daemon fork `tclaude session new`. agentd is started by the human and is not a CC instance, so a daemon-forked `session new` has agentd (and the human's shell) in its ancestry — no `claude`/`node` — and ClaudeAncestorCheck returns false for it. Agents that genuinely need another session go through `tclaude agent spawn`, which the daemon gates on the `groups.spawn` permission.
Known limitation: if the human starts `tclaude agentd serve` from inside a Claude Code session, daemon-forked spawns would inherit that claude ancestor and be refused too. agentd is expected to be launched from a plain shell / login / tray, so this is accepted rather than papered over with a bypass flag a CC instance could trivially set.
func HookCallbackCmd ¶
func InstallHooks ¶
func InstallHooks() error
InstallHooks adds tclaude hooks to Claude settings, replacing any existing tclaude hooks
func IsProcessAlive ¶
IsProcessAlive checks if a process with the given PID is still running
func IsTmuxSessionAlive ¶
IsTmuxSessionAlive checks if a tmux session exists
func IsTmuxSessionAttached ¶
IsTmuxSessionAttached checks if a tmux session has any clients attached
func IsXdotoolInstalled ¶
func IsXdotoolInstalled() bool
IsXdotoolInstalled checks if xdotool is available.
func MaxUpdatedAt ¶ added in v0.0.38
MaxUpdatedAt returns the most recent updated_at across all sessions.
func NotifyListenCmd ¶
NotifyListenCmd returns a hidden command that sends a D-Bus notification and listens for action signals on the same connection. The notification and signal must use the same D-Bus connection because notification daemons send ActionInvoked signals only to the connection that created the notification. This runs as a detached background process spawned by sendLinuxClickable.
func ParsePIDFromTmux ¶
ParsePIDFromTmux gets the PID of the main process in a tmux session
func RefreshSessionStatus ¶
func RefreshSessionStatus(state *SessionState)
RefreshSessionStatus updates the session status based on actual state
func RegisterJoinGroupCompletion ¶ added in v0.0.187
RegisterJoinGroupCompletion wires `--join-group` to suggest existing agent group names. Reads SQLite directly — completions fire on every <tab> keystroke, so they bypass the daemon (same convention as `tclaude agent groups …` completions). Exported so the top-level `tclaude` cobra cmd in pkg/claude/claude.go can register it too.
func ReinitHookCommand ¶ added in v0.0.85
func ReinitHookCommand()
ReinitHookCommand re-evaluates the hook command path using current DetectCmd settings. Call this after changing common.SetAbsolutePaths().
func RunInteractive ¶
func RunInteractive(includeAll bool, state WatchState) (AttachResult, WatchState, error)
RunInteractive starts the interactive session viewer Returns the attach result (if any) and the final watch state
func RunWatchMode ¶
func RunWatchMode(includeAll bool, initialSort table.SortState, initialFilter, initialHide []string) error
RunWatchMode runs the interactive watch mode with attach support
func SaveSessionState ¶
func SaveSessionState(state *SessionState) error
SaveSessionState saves session state to the database.
func SessionExists ¶ added in v0.0.38
SessionExists checks whether a session with the given ID exists.
func ShortenPath ¶
ShortenPath shortens a path for display
func SortSessionsByKey ¶
func SortSessionsByKey(sessions []*SessionState, key string, dir table.SortDirection)
SortSessionsByKey sorts sessions by the given sort key and direction.
func StatusCallbackCmd ¶
func TaskSignalPath ¶ added in v0.0.64
TaskSignalPath returns a per-project path to the task signal file, allowing concurrent task runners in different projects.
func TryFocusAttachedSession ¶
func TryFocusAttachedSession(tmuxSession string)
TryFocusAttachedSession attempts to focus the terminal window that has the session attached.
func TryFocusAttachedSessionWithID ¶ added in v0.0.181
func TryFocusAttachedSessionWithID(tmuxSession, sessionID string)
TryFocusAttachedSessionWithID is like TryFocusAttachedSession but takes the tclaude session ID (label) explicitly. The session ID is needed on WSL where the focus path searches Windows windows by the "tclaude:<id>" title pattern that setTerminalTitle stamps on each pane. Existing TryFocusAttachedSession reads the ID from $TCLAUDE_SESSION_ID, which is correct when called from `tclaude session focus` (CLI sets the env first) but not from the daemon (env points at the daemon's own session, if any).
func WorkDirFromToolUse ¶ added in v0.0.234
WorkDirFromToolUse inspects a Claude Code PostToolUse payload and returns the directory the tool acted in, when it can tell.
It looks only at the file-mutating tools (see fileMutatingTools) and returns the parent directory of the file they touched. Relative paths are resolved against cwd — Claude Code's launch directory, as reported in the hook payload. Returns ("", false) when the tool isn't a recognised file-mutator or carries no usable path.
Pure function: no I/O, no globals beyond the static tool set, so the derivation is straightforward to unit-test.
Types ¶
type AttachParams ¶
type AttachResult ¶
type AttachResult struct {
TmuxSession string
SessionID string // session ID for inbox watcher
ForceAttach bool // true if we should detach other clients
CreateNew bool // true if user wants to create a new session
FocusOnly bool // true if we should just focus (session already attached)
}
AttachResult holds the result of selecting a session to attach
type FocusParams ¶
type FocusParams struct {
ID string `pos:"true" help:"Session ID to focus"`
}
type GotoParams ¶ added in v0.0.45
type GotoParams struct {
Direction string `pos:"true" help:"Direction: next or prev"`
}
type HookCallbackInput ¶
type HookCallbackInput struct {
ConvID string `json:"session_id"` // claude's session id, what we call conv_id
TranscriptPath string `json:"transcript_path"`
Cwd string `json:"cwd"`
PermissionMode string `json:"permission_mode,omitempty"`
HookEventName string `json:"hook_event_name"`
NotificationType string `json:"notification_type,omitempty"`
Reason string `json:"reason,omitempty"` // SessionEnd: clear | logout | prompt_input_exit | other
Message string `json:"message,omitempty"`
Prompt string `json:"prompt,omitempty"`
StopHookActive bool `json:"stop_hook_active,omitempty"`
ToolName string `json:"tool_name,omitempty"`
ToolInput json.RawMessage `json:"tool_input,omitempty"`
AgentType string `json:"agent_type,omitempty"`
AgentID string `json:"agent_id,omitempty"`
LastAssistantMessage string `json:"last_assistant_message,omitempty"`
// StopFailure: error_type is one of rate_limit, authentication_failed,
// oauth_org_not_allowed, billing_error, invalid_request, server_error,
// max_output_tokens, unknown; error_message is the human-readable string.
ErrorType string `json:"error_type,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
}
HookCallbackInput represents the JSON input from any Claude Code hook
type HookConfig ¶
HookConfig represents a single hook configuration
type HookInput ¶
type HookInput struct {
SessionID string `json:"session_id"`
Cwd string `json:"cwd"`
HookEventName string `json:"hook_event_name"`
NotificationType string `json:"notification_type,omitempty"`
}
HookInput represents the JSON input from Claude Code hooks
type HookMatcher ¶
type HookMatcher struct {
Matcher string `json:"matcher,omitempty"`
Hooks []HookConfig `json:"hooks"`
}
HookMatcher represents a hook matcher configuration
type KillParams ¶
type ListParams ¶
type ListParams struct {
JSON bool `long:"json" help:"Output as JSON"`
All bool `short:"a" long:"all" help:"Include exited sessions"`
Watch bool `short:"w" long:"watch" help:"Interactive watch mode with auto-refresh"`
Sort string `short:"s" long:"sort" optional:"true" help:"Sort by column" alts:"id,directory,status,age,updated"`
Asc bool `long:"asc" help:"Sort ascending (default for id/directory/status)"`
Desc bool `long:"desc" help:"Sort descending (default for updated)"`
Show []string `` /* 131-byte string literal not displayed */
Hide []string `long:"hide" optional:"true" help:"Hide these statuses" alts:"idle,working,awaiting_permission,awaiting_input,error,exited"`
}
type NewParams ¶
type NewParams struct {
Dir string `short:"C" long:"dir" optional:"true" help:"Directory to start session in (defaults to current directory)"`
Resume string `long:"resume" short:"r" optional:"true" help:"Resume an existing conversation by ID"`
Global bool `short:"g" help:"Search for conversation across all projects (with --resume)"`
Label string `long:"label" optional:"true" help:"Custom label for the session"`
Detached bool `long:"detached" short:"d" help:"Start detached (don't attach to session)"`
Compact int `long:"compact" optional:"true" help:"Auto-compact at this context usage percentage (overrides config)"`
WaitForRateLimit bool `long:"wait-for-rate-limit" short:"w" help:"Wait for rate limit (5-hour and 7-day) to reset before starting session"`
// --join-group makes the new session auto-join an existing agent group
// the moment its conv-id materialises. Routed through the daemon's
// `groups.spawn` orchestration; not compatible with --resume / --label.
JoinGroup string `` /* 144-byte string literal not displayed */
Name string `long:"name" optional:"true" help:"Name for the new agent in --join-group (e.g. 'reviewer'); becomes its conversation title"`
Role string `long:"role" optional:"true" help:"Role tag for the new member in --join-group (e.g. 'tech-lead')"`
Descr string `long:"descr" optional:"true" help:"Description of the new member's purpose in --join-group"`
}
type PruneParams ¶
type SessionState ¶
type SessionState struct {
ID string `json:"id"`
TmuxSession string `json:"tmuxSession"`
PID int `json:"pid"`
Cwd string `json:"cwd"`
ConvID string `json:"convId,omitempty"`
Status string `json:"status"`
StatusDetail string `json:"statusDetail,omitempty"`
SubagentCount int `json:"subagentCount"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
LastHook time.Time `json:"lastHook"`
Attached int `json:"-"` // Number of attached clients (runtime only, not persisted)
}
SessionState represents the state of a Claude session
func FindSessionByConvID ¶ added in v0.0.38
func FindSessionByConvID(convID string) (*SessionState, error)
FindSessionByConvID finds a session by Claude conversation ID using an indexed lookup.
func ListSessionStates ¶
func ListSessionStates() ([]*SessionState, error)
ListSessionStates returns all session states.
func LoadSessionState ¶
func LoadSessionState(id string) (*SessionState, error)
LoadSessionState loads session state from the database.
type StatusCallbackParams ¶
type StatusCallbackParams struct {
Status string `pos:"true" help:"New status (working, idle, awaiting_permission, awaiting_input, error)"`
}
type TaskSignal ¶ added in v0.0.36
type TaskSignal struct {
Report string `json:"report"`
SessionID string `json:"sessionId,omitempty"`
Event string `json:"event,omitempty"` // hook event name (e.g. "Stop", "PermissionRequest")
ToolName string `json:"toolName,omitempty"` // tool name from the hook (e.g. "ExitPlanMode")
}
TaskSignal is the JSON structure written to the task signal file.