feat: gt mayor at auto-starts and restarts Claude if needed
- Auto-start Mayor session if not running (no need for gt mayor start first) - Restart Claude if it has exited (detects shell in pane) - Prime with gt prime after start/restart - Refactor: extract startMayorSession helper 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+54
-13
@@ -82,12 +82,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runMayorStart(cmd *cobra.Command, args []string) error {
|
func runMayorStart(cmd *cobra.Command, args []string) error {
|
||||||
// Find workspace root
|
|
||||||
townRoot, err := workspace.FindFromCwdOrError()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("not in a Gas Town workspace: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t := tmux.NewTmux()
|
t := tmux.NewTmux()
|
||||||
|
|
||||||
// Check if session already exists
|
// Check if session already exists
|
||||||
@@ -99,6 +93,25 @@ func runMayorStart(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("Mayor session already running. Attach with: gt mayor attach")
|
return fmt.Errorf("Mayor session already running. Attach with: gt mayor attach")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := startMayorSession(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s Mayor session started. Attach with: %s\n",
|
||||||
|
style.Bold.Render("✓"),
|
||||||
|
style.Dim.Render("gt mayor attach"))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// startMayorSession creates and initializes the Mayor tmux session.
|
||||||
|
func startMayorSession(t *tmux.Tmux) error {
|
||||||
|
// Find workspace root
|
||||||
|
townRoot, err := workspace.FindFromCwdOrError()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("not in a Gas Town workspace: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create session in workspace root
|
// Create session in workspace root
|
||||||
fmt.Println("Starting Mayor session...")
|
fmt.Println("Starting Mayor session...")
|
||||||
if err := t.NewSession(MayorSessionName, townRoot); err != nil {
|
if err := t.NewSession(MayorSessionName, townRoot); err != nil {
|
||||||
@@ -109,14 +122,14 @@ func runMayorStart(cmd *cobra.Command, args []string) error {
|
|||||||
t.SetEnvironment(MayorSessionName, "GT_ROLE", "mayor")
|
t.SetEnvironment(MayorSessionName, "GT_ROLE", "mayor")
|
||||||
|
|
||||||
// Launch Claude with full permissions (Mayor is trusted)
|
// Launch Claude with full permissions (Mayor is trusted)
|
||||||
command := "claude --dangerously-skip-permissions"
|
if err := t.SendKeys(MayorSessionName, "claude --dangerously-skip-permissions"); err != nil {
|
||||||
if err := t.SendKeys(MayorSessionName, command); err != nil {
|
|
||||||
return fmt.Errorf("sending command: %w", err)
|
return fmt.Errorf("sending command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s Mayor session started. Attach with: %s\n",
|
// Prime after a delay
|
||||||
style.Bold.Render("✓"),
|
if err := t.SendKeysDelayed(MayorSessionName, "gt prime", 2000); err != nil {
|
||||||
style.Dim.Render("gt mayor attach"))
|
fmt.Printf("Warning: Could not send prime command: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -157,11 +170,28 @@ func runMayorAttach(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("checking session: %w", err)
|
return fmt.Errorf("checking session: %w", err)
|
||||||
}
|
}
|
||||||
if !running {
|
if !running {
|
||||||
return errors.New("Mayor session is not running. Start with: gt mayor start")
|
// Auto-start if not running
|
||||||
|
fmt.Println("Mayor session not running, starting...")
|
||||||
|
if err := startMayorSession(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Session exists - check if Claude is still running
|
||||||
|
paneCmd, err := t.GetPaneCommand(MayorSessionName)
|
||||||
|
if err == nil && isMayorShellCommand(paneCmd) {
|
||||||
|
// Claude has exited, restart it
|
||||||
|
fmt.Println("Claude exited, restarting...")
|
||||||
|
if err := t.SendKeys(MayorSessionName, "claude --dangerously-skip-permissions"); err != nil {
|
||||||
|
return fmt.Errorf("restarting claude: %w", err)
|
||||||
|
}
|
||||||
|
// Prime after restart
|
||||||
|
if err := t.SendKeysDelayed(MayorSessionName, "gt prime", 2000); err != nil {
|
||||||
|
fmt.Printf("Warning: Could not send prime command: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use exec to replace current process with tmux attach
|
// Use exec to replace current process with tmux attach
|
||||||
// This is the standard pattern for attaching to tmux sessions
|
|
||||||
tmuxPath, err := exec.LookPath("tmux")
|
tmuxPath, err := exec.LookPath("tmux")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("tmux not found: %w", err)
|
return fmt.Errorf("tmux not found: %w", err)
|
||||||
@@ -170,6 +200,17 @@ func runMayorAttach(cmd *cobra.Command, args []string) error {
|
|||||||
return execCommand(tmuxPath, "attach-session", "-t", MayorSessionName)
|
return execCommand(tmuxPath, "attach-session", "-t", MayorSessionName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isMayorShellCommand checks if the command is a shell (meaning Claude has exited).
|
||||||
|
func isMayorShellCommand(cmd string) bool {
|
||||||
|
shells := []string{"bash", "zsh", "sh", "fish", "tcsh", "ksh"}
|
||||||
|
for _, shell := range shells {
|
||||||
|
if cmd == shell {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// execCommand replaces the current process with the given command.
|
// execCommand replaces the current process with the given command.
|
||||||
// This is used for attaching to tmux sessions.
|
// This is used for attaching to tmux sessions.
|
||||||
func execCommand(name string, args ...string) error {
|
func execCommand(name string, args ...string) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user