package cmd import ( "fmt" "os/exec" "sort" "github.com/spf13/cobra" ) // townCycleSession is the --session flag for town next/prev commands. // When run via tmux key binding (run-shell), the session context may not be // correct, so we pass the session name explicitly via #{session_name} expansion. var townCycleSession string // Town-level sessions that participate in cycling (mayor, deacon). // These are the session names without the "gt-" prefix. var townLevelSessions = []string{"gt-mayor", "gt-deacon"} func init() { rootCmd.AddCommand(townCmd) townCmd.AddCommand(townNextCmd) townCmd.AddCommand(townPrevCmd) townNextCmd.Flags().StringVar(&townCycleSession, "session", "", "Override current session (used by tmux binding)") townPrevCmd.Flags().StringVar(&townCycleSession, "session", "", "Override current session (used by tmux binding)") } var townCmd = &cobra.Command{ Use: "town", Short: "Town-level operations", Long: `Commands for town-level operations including session cycling.`, } var townNextCmd = &cobra.Command{ Use: "next", Short: "Switch to next town session (mayor/deacon)", Long: `Switch to the next town-level session in the cycle order. Town sessions cycle between Mayor and Deacon. This command is typically invoked via the C-b n keybinding when in a town-level session (Mayor or Deacon).`, RunE: func(cmd *cobra.Command, args []string) error { return cycleTownSession(1, townCycleSession) }, } var townPrevCmd = &cobra.Command{ Use: "prev", Short: "Switch to previous town session (mayor/deacon)", Long: `Switch to the previous town-level session in the cycle order. Town sessions cycle between Mayor and Deacon. This command is typically invoked via the C-b p keybinding when in a town-level session (Mayor or Deacon).`, RunE: func(cmd *cobra.Command, args []string) error { return cycleTownSession(-1, townCycleSession) }, } // cycleTownSession switches to the next or previous town-level session. // direction: 1 for next, -1 for previous // sessionOverride: if non-empty, use this instead of detecting current session func cycleTownSession(direction int, sessionOverride string) error { var currentSession string var err error if sessionOverride != "" { currentSession = sessionOverride } else { currentSession, err = getCurrentTmuxSession() if err != nil { return fmt.Errorf("not in a tmux session: %w", err) } if currentSession == "" { return fmt.Errorf("not in a tmux session") } } // Check if current session is a town-level session isTownSession := false for _, s := range townLevelSessions { if s == currentSession { isTownSession = true break } } if !isTownSession { // Not a town session - no cycling, just stay put return nil } // Find running town sessions sessions, err := findRunningTownSessions() if err != nil { return fmt.Errorf("listing sessions: %w", err) } if len(sessions) == 0 { return fmt.Errorf("no town sessions found") } // Sort for consistent ordering sort.Strings(sessions) // Find current position currentIdx := -1 for i, s := range sessions { if s == currentSession { currentIdx = i break } } if currentIdx == -1 { // Current session not in list (shouldn't happen) return fmt.Errorf("current session not found in town session list") } // Calculate target index (with wrapping) targetIdx := (currentIdx + direction + len(sessions)) % len(sessions) if targetIdx == currentIdx { // Only one session, nothing to switch to return nil } targetSession := sessions[targetIdx] // Switch to target session cmd := exec.Command("tmux", "switch-client", "-t", targetSession) if err := cmd.Run(); err != nil { return fmt.Errorf("switching to %s: %w", targetSession, err) } return nil } // findRunningTownSessions returns a list of currently running town-level sessions. func findRunningTownSessions() ([]string, error) { // Get all tmux sessions out, err := exec.Command("tmux", "list-sessions", "-F", "#{session_name}").Output() if err != nil { return nil, fmt.Errorf("listing tmux sessions: %w", err) } var running []string for _, line := range splitLines(string(out)) { if line == "" { continue } // Check if this is a town-level session for _, townSession := range townLevelSessions { if line == townSession { running = append(running, line) break } } } return running, nil }