diff --git a/internal/cmd/rig_park.go b/internal/cmd/rig_park.go new file mode 100644 index 00000000..e1052a7a --- /dev/null +++ b/internal/cmd/rig_park.go @@ -0,0 +1,149 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/steveyegge/gastown/internal/refinery" + "github.com/steveyegge/gastown/internal/style" + "github.com/steveyegge/gastown/internal/tmux" + "github.com/steveyegge/gastown/internal/wisp" + "github.com/steveyegge/gastown/internal/witness" +) + +// RigStatusKey is the wisp config key for rig operational status. +const RigStatusKey = "status" + +// RigStatusParked is the value indicating a rig is parked. +const RigStatusParked = "parked" + +var rigParkCmd = &cobra.Command{ + Use: "park ", + Short: "Park a rig (stops agents, daemon won't auto-restart)", + Long: `Park a rig to temporarily disable it. + +Parking a rig: + - Stops the witness if running + - Stops the refinery if running + - Sets status=parked in the wisp layer (local/ephemeral) + - The daemon respects this status and won't auto-restart agents + +This is a Level 1 (local/ephemeral) operation: + - Only affects this town + - Disappears on wisp cleanup + - Use 'gt rig unpark' to resume normal operation + +Examples: + gt rig park gastown + gt rig park beads`, + Args: cobra.ExactArgs(1), + RunE: runRigPark, +} + +var rigUnparkCmd = &cobra.Command{ + Use: "unpark ", + Short: "Unpark a rig (allow daemon to auto-restart agents)", + Long: `Unpark a rig to resume normal operation. + +Unparking a rig: + - Removes the parked status from the wisp layer + - Allows the daemon to auto-restart agents + - Does NOT automatically start agents (use 'gt rig start' for that) + +Examples: + gt rig unpark gastown + gt rig unpark beads`, + Args: cobra.ExactArgs(1), + RunE: runRigUnpark, +} + +func init() { + rigCmd.AddCommand(rigParkCmd) + rigCmd.AddCommand(rigUnparkCmd) +} + +func runRigPark(cmd *cobra.Command, args []string) error { + rigName := args[0] + + // Get rig and town root + townRoot, r, err := getRig(rigName) + if err != nil { + return err + } + + fmt.Printf("Parking rig %s...\n", style.Bold.Render(rigName)) + + var stoppedAgents []string + + t := tmux.NewTmux() + + // Stop witness if running + witnessSession := fmt.Sprintf("gt-%s-witness", rigName) + witnessRunning, _ := t.HasSession(witnessSession) + if witnessRunning { + fmt.Printf(" Stopping witness...\n") + witMgr := witness.NewManager(r) + if err := witMgr.Stop(); err != nil { + fmt.Printf(" %s Failed to stop witness: %v\n", style.Warning.Render("!"), err) + } else { + stoppedAgents = append(stoppedAgents, "Witness stopped") + } + } + + // Stop refinery if running + refinerySession := fmt.Sprintf("gt-%s-refinery", rigName) + refineryRunning, _ := t.HasSession(refinerySession) + if refineryRunning { + fmt.Printf(" Stopping refinery...\n") + refMgr := refinery.NewManager(r) + if err := refMgr.Stop(); err != nil { + fmt.Printf(" %s Failed to stop refinery: %v\n", style.Warning.Render("!"), err) + } else { + stoppedAgents = append(stoppedAgents, "Refinery stopped") + } + } + + // Set parked status in wisp layer + wispCfg := wisp.NewConfig(townRoot, rigName) + if err := wispCfg.Set(RigStatusKey, RigStatusParked); err != nil { + return fmt.Errorf("setting parked status: %w", err) + } + + // Output + fmt.Printf("%s Rig %s parked (local only)\n", style.Success.Render("✓"), rigName) + for _, msg := range stoppedAgents { + fmt.Printf(" %s\n", msg) + } + fmt.Printf(" Daemon will not auto-restart\n") + + return nil +} + +func runRigUnpark(cmd *cobra.Command, args []string) error { + rigName := args[0] + + // Get rig and town root + townRoot, _, err := getRig(rigName) + if err != nil { + return err + } + + // Remove parked status from wisp layer + wispCfg := wisp.NewConfig(townRoot, rigName) + if err := wispCfg.Unset(RigStatusKey); err != nil { + return fmt.Errorf("clearing parked status: %w", err) + } + + fmt.Printf("%s Rig %s unparked\n", style.Success.Render("✓"), rigName) + fmt.Printf(" Daemon can now auto-restart agents\n") + fmt.Printf(" Use '%s' to start agents immediately\n", style.Dim.Render("gt rig start "+rigName)) + + return nil +} + +// IsRigParked checks if a rig is parked in the wisp layer. +// This function is exported for use by the daemon. +func IsRigParked(townRoot, rigName string) bool { + wispCfg := wisp.NewConfig(townRoot, rigName) + return wispCfg.GetString(RigStatusKey) == RigStatusParked +}