Files
gastown/internal/cmd/start.go
Steve Yegge 3705b2b5ac Add gt shutdown command to stop all agents
Stops agents in the correct order:
1. Deacon first (so it doesn't restart others)
2. All polecats, witnesses, refineries, crew (gt-* sessions)
3. Mayor last

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 19:36:32 -08:00

158 lines
4.6 KiB
Go

package cmd
import (
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/tmux"
"github.com/steveyegge/gastown/internal/workspace"
)
var startCmd = &cobra.Command{
Use: "start",
Short: "Start Gas Town",
Long: `Start Gas Town by launching the Deacon and Mayor.
The Deacon is the health-check orchestrator that monitors Mayor and Witnesses.
The Mayor is the global coordinator that dispatches work.
Other agents (Witnesses, Refineries, Polecats) are started lazily as needed.
To stop Gas Town, use 'gt shutdown'.`,
RunE: runStart,
}
var shutdownCmd = &cobra.Command{
Use: "shutdown",
Short: "Shutdown Gas Town",
Long: `Shutdown Gas Town by stopping all agents.
Stops agents in the correct order:
1. Deacon (health monitor) - so it doesn't restart others
2. All polecats, witnesses, refineries, crew
3. Mayor (global coordinator)
This is a graceful shutdown that kills all Gas Town tmux sessions.`,
RunE: runShutdown,
}
func init() {
rootCmd.AddCommand(startCmd)
rootCmd.AddCommand(shutdownCmd)
}
func runStart(cmd *cobra.Command, args []string) error {
// Verify we're in a Gas Town workspace
townRoot, err := workspace.FindFromCwdOrError()
if err != nil {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}
t := tmux.NewTmux()
fmt.Printf("Starting Gas Town from %s\n\n", style.Dim.Render(townRoot))
// Start Mayor first (so Deacon sees it as up)
mayorRunning, _ := t.HasSession(MayorSessionName)
if mayorRunning {
fmt.Printf(" %s Mayor already running\n", style.Dim.Render("○"))
} else {
fmt.Printf(" %s Starting Mayor...\n", style.Bold.Render("→"))
if err := startMayorSession(t); err != nil {
return fmt.Errorf("starting Mayor: %w", err)
}
fmt.Printf(" %s Mayor started\n", style.Bold.Render("✓"))
}
// Start Deacon (health monitor)
deaconRunning, _ := t.HasSession(DeaconSessionName)
if deaconRunning {
fmt.Printf(" %s Deacon already running\n", style.Dim.Render("○"))
} else {
fmt.Printf(" %s Starting Deacon...\n", style.Bold.Render("→"))
if err := startDeaconSession(t); err != nil {
return fmt.Errorf("starting Deacon: %w", err)
}
fmt.Printf(" %s Deacon started\n", style.Bold.Render("✓"))
}
fmt.Println()
fmt.Printf("%s Gas Town is running\n", style.Bold.Render("✓"))
fmt.Println()
fmt.Printf(" Attach to Mayor: %s\n", style.Dim.Render("gt mayor attach"))
fmt.Printf(" Attach to Deacon: %s\n", style.Dim.Render("gt deacon attach"))
fmt.Printf(" Check status: %s\n", style.Dim.Render("gt status"))
return nil
}
func runShutdown(cmd *cobra.Command, args []string) error {
t := tmux.NewTmux()
fmt.Println("Shutting down Gas Town...\n")
stopped := 0
// 1. Stop Deacon first (so it doesn't try to restart others)
deaconRunning, _ := t.HasSession(DeaconSessionName)
if deaconRunning {
fmt.Printf(" %s Stopping Deacon...\n", style.Bold.Render("→"))
if err := t.KillSession(DeaconSessionName); err != nil {
fmt.Printf(" %s Failed to stop Deacon: %v\n", style.Dim.Render("!"), err)
} else {
fmt.Printf(" %s Deacon stopped\n", style.Bold.Render("✓"))
stopped++
}
} else {
fmt.Printf(" %s Deacon not running\n", style.Dim.Render("○"))
}
// 2. Stop all other gt-* sessions (polecats, witnesses, refineries, crew)
sessions, err := t.ListSessions()
if err == nil {
for _, sess := range sessions {
// Skip Mayor (we'll stop it last) and Deacon (already stopped)
if sess == MayorSessionName || sess == DeaconSessionName {
continue
}
// Only kill gt-* sessions
if !strings.HasPrefix(sess, "gt-") {
continue
}
fmt.Printf(" %s Stopping %s...\n", style.Bold.Render("→"), sess)
if err := t.KillSession(sess); err != nil {
fmt.Printf(" %s Failed to stop %s: %v\n", style.Dim.Render("!"), sess, err)
} else {
fmt.Printf(" %s %s stopped\n", style.Bold.Render("✓"), sess)
stopped++
}
}
}
// 3. Stop Mayor last
mayorRunning, _ := t.HasSession(MayorSessionName)
if mayorRunning {
fmt.Printf(" %s Stopping Mayor...\n", style.Bold.Render("→"))
if err := t.KillSession(MayorSessionName); err != nil {
fmt.Printf(" %s Failed to stop Mayor: %v\n", style.Dim.Render("!"), err)
} else {
fmt.Printf(" %s Mayor stopped\n", style.Bold.Render("✓"))
stopped++
}
} else {
fmt.Printf(" %s Mayor not running\n", style.Dim.Render("○"))
}
fmt.Println()
if stopped > 0 {
fmt.Printf("%s Gas Town shutdown complete (%d sessions stopped)\n", style.Bold.Render("✓"), stopped)
} else {
fmt.Printf("%s Gas Town was not running\n", style.Dim.Render("○"))
}
return nil
}