Organize 43 commands into 7 logical groups using cobra's built-in AddGroup/GroupID feature: - Work Management: spawn, sling, hook, handoff, done, mol, mq, etc. - Agent Management: mayor, witness, refinery, deacon, polecat, etc. - Communication: mail, nudge, broadcast, peek - Services: daemon, start, stop, up, down, shutdown - Workspace: rig, crew, init, install, git-init, namepool - Configuration: account, theme, hooks, issue, completion - Diagnostics: status, doctor, prime, version, help Also renamed molecule to mol as the primary command name (molecule is now an alias). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
180 lines
4.2 KiB
Go
180 lines
4.2 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/steveyegge/gastown/internal/config"
|
|
"github.com/steveyegge/gastown/internal/git"
|
|
"github.com/steveyegge/gastown/internal/rig"
|
|
"github.com/steveyegge/gastown/internal/session"
|
|
"github.com/steveyegge/gastown/internal/style"
|
|
"github.com/steveyegge/gastown/internal/tmux"
|
|
"github.com/steveyegge/gastown/internal/workspace"
|
|
)
|
|
|
|
var (
|
|
stopAll bool
|
|
stopRig string
|
|
stopGraceful bool
|
|
)
|
|
|
|
var stopCmd = &cobra.Command{
|
|
Use: "stop",
|
|
GroupID: GroupServices,
|
|
Short: "Emergency stop for sessions",
|
|
Long: `Emergency stop command for Gas Town sessions.
|
|
|
|
Stops all running polecat sessions across rigs. Use for emergency shutdown
|
|
when you need to halt all agent activity immediately.
|
|
|
|
Examples:
|
|
gt stop --all # Kill ALL sessions across all rigs
|
|
gt stop --rig wyvern # Kill all sessions in the wyvern rig
|
|
gt stop --all --graceful # Try graceful shutdown first`,
|
|
RunE: runStop,
|
|
}
|
|
|
|
func init() {
|
|
stopCmd.Flags().BoolVar(&stopAll, "all", false, "Stop all sessions across all rigs")
|
|
stopCmd.Flags().StringVar(&stopRig, "rig", "", "Stop all sessions in a specific rig")
|
|
stopCmd.Flags().BoolVar(&stopGraceful, "graceful", false, "Try graceful shutdown before force kill")
|
|
rootCmd.AddCommand(stopCmd)
|
|
}
|
|
|
|
// StopResult tracks what was stopped.
|
|
type StopResult struct {
|
|
Rig string
|
|
Polecat string
|
|
SessionID string
|
|
Success bool
|
|
Error string
|
|
}
|
|
|
|
func runStop(cmd *cobra.Command, args []string) error {
|
|
if !stopAll && stopRig == "" {
|
|
return fmt.Errorf("must specify --all or --rig <name>")
|
|
}
|
|
|
|
// Find town root
|
|
townRoot, err := workspace.FindFromCwdOrError()
|
|
if err != nil {
|
|
return fmt.Errorf("not in a Gas Town workspace: %w", err)
|
|
}
|
|
|
|
// Load rigs config
|
|
rigsConfigPath := filepath.Join(townRoot, "mayor", "rigs.json")
|
|
rigsConfig, err := config.LoadRigsConfig(rigsConfigPath)
|
|
if err != nil {
|
|
rigsConfig = &config.RigsConfig{Rigs: make(map[string]config.RigEntry)}
|
|
}
|
|
|
|
// Get rigs to stop
|
|
g := git.NewGit(townRoot)
|
|
rigMgr := rig.NewManager(townRoot, rigsConfig, g)
|
|
rigs, err := rigMgr.DiscoverRigs()
|
|
if err != nil {
|
|
return fmt.Errorf("discovering rigs: %w", err)
|
|
}
|
|
|
|
// Filter by rig if specified
|
|
if stopRig != "" {
|
|
var filtered []*rig.Rig
|
|
for _, r := range rigs {
|
|
if r.Name == stopRig {
|
|
filtered = append(filtered, r)
|
|
}
|
|
}
|
|
if len(filtered) == 0 {
|
|
return fmt.Errorf("rig '%s' not found", stopRig)
|
|
}
|
|
rigs = filtered
|
|
}
|
|
|
|
// Determine force mode
|
|
force := !stopGraceful
|
|
|
|
if stopAll {
|
|
fmt.Printf("%s Stopping ALL Gas Town sessions...\n\n",
|
|
style.Bold.Render("🛑"))
|
|
} else {
|
|
fmt.Printf("%s Stopping sessions in rig '%s'...\n\n",
|
|
style.Bold.Render("🛑"), stopRig)
|
|
}
|
|
|
|
// Stop sessions in each rig
|
|
t := tmux.NewTmux()
|
|
var results []StopResult
|
|
stopped := 0
|
|
|
|
for _, r := range rigs {
|
|
mgr := session.NewManager(t, r)
|
|
infos, err := mgr.List()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for _, info := range infos {
|
|
result := StopResult{
|
|
Rig: r.Name,
|
|
Polecat: info.Polecat,
|
|
SessionID: info.SessionID,
|
|
}
|
|
|
|
// Capture output before stopping (best effort)
|
|
output, _ := mgr.Capture(info.Polecat, 50)
|
|
|
|
// Stop the session
|
|
err := mgr.Stop(info.Polecat, force)
|
|
if err != nil {
|
|
result.Success = false
|
|
result.Error = err.Error()
|
|
fmt.Printf(" %s [%s] %s: %s\n",
|
|
style.Dim.Render("✗"),
|
|
r.Name, info.Polecat,
|
|
style.Dim.Render(err.Error()))
|
|
} else {
|
|
result.Success = true
|
|
stopped++
|
|
fmt.Printf(" %s [%s] %s: stopped\n",
|
|
style.Bold.Render("✓"),
|
|
r.Name, info.Polecat)
|
|
|
|
// Log captured output (truncated)
|
|
if len(output) > 200 {
|
|
output = output[len(output)-200:]
|
|
}
|
|
if output != "" {
|
|
fmt.Printf(" %s\n", style.Dim.Render("(output captured)"))
|
|
}
|
|
}
|
|
|
|
results = append(results, result)
|
|
}
|
|
}
|
|
|
|
// Summary
|
|
fmt.Println()
|
|
if stopped == 0 {
|
|
fmt.Println("No active sessions to stop.")
|
|
} else {
|
|
fmt.Printf("%s %d session(s) stopped.\n",
|
|
style.Bold.Render("✓"), stopped)
|
|
}
|
|
|
|
// Report failures
|
|
failures := 0
|
|
for _, r := range results {
|
|
if !r.Success {
|
|
failures++
|
|
}
|
|
}
|
|
if failures > 0 {
|
|
fmt.Printf("%s %d session(s) failed to stop.\n",
|
|
style.Dim.Render("⚠"), failures)
|
|
}
|
|
|
|
return nil
|
|
}
|