Add gt crew restart --all for batch crew restarts
- Add --all flag to restart all running crew sessions - Add --dry-run flag to preview without restarting - Add --rig filter to target specific rig - Extract restartCrewSession helper for reuse 🤝 Filed gt-1kljv for adding tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,6 +16,8 @@ var (
|
|||||||
crewDetached bool
|
crewDetached bool
|
||||||
crewMessage string
|
crewMessage string
|
||||||
crewAccount string
|
crewAccount string
|
||||||
|
crewAll bool
|
||||||
|
crewDryRun bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var crewCmd = &cobra.Command{
|
var crewCmd = &cobra.Command{
|
||||||
@@ -148,7 +152,7 @@ Examples:
|
|||||||
}
|
}
|
||||||
|
|
||||||
var crewRestartCmd = &cobra.Command{
|
var crewRestartCmd = &cobra.Command{
|
||||||
Use: "restart <name>",
|
Use: "restart [name]",
|
||||||
Aliases: []string{"rs"},
|
Aliases: []string{"rs"},
|
||||||
Short: "Kill and restart crew workspace session",
|
Short: "Kill and restart crew workspace session",
|
||||||
Long: `Kill the tmux session and restart fresh with Claude.
|
Long: `Kill the tmux session and restart fresh with Claude.
|
||||||
@@ -161,10 +165,26 @@ The command will:
|
|||||||
2. Start fresh session with Claude
|
2. Start fresh session with Claude
|
||||||
3. Run gt prime to reinitialize context
|
3. Run gt prime to reinitialize context
|
||||||
|
|
||||||
|
Use --all to restart all running crew sessions across all rigs.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
gt crew restart dave # Restart dave's session
|
gt crew restart dave # Restart dave's session
|
||||||
gt crew rs emma # Same, using alias`,
|
gt crew rs emma # Same, using alias
|
||||||
Args: cobra.ExactArgs(1),
|
gt crew restart --all # Restart all running crew sessions
|
||||||
|
gt crew restart --all --rig beads # Restart all crew in beads rig
|
||||||
|
gt crew restart --all --dry-run # Preview what would be restarted`,
|
||||||
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if crewAll {
|
||||||
|
if len(args) > 0 {
|
||||||
|
return fmt.Errorf("cannot specify both --all and a name")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(args) != 1 {
|
||||||
|
return fmt.Errorf("requires exactly 1 argument (or --all)")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
RunE: runCrewRestart,
|
RunE: runCrewRestart,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +278,9 @@ func init() {
|
|||||||
crewPristineCmd.Flags().StringVar(&crewRig, "rig", "", "Filter by rig name")
|
crewPristineCmd.Flags().StringVar(&crewRig, "rig", "", "Filter by rig name")
|
||||||
crewPristineCmd.Flags().BoolVar(&crewJSON, "json", false, "Output as JSON")
|
crewPristineCmd.Flags().BoolVar(&crewJSON, "json", false, "Output as JSON")
|
||||||
|
|
||||||
crewRestartCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use")
|
crewRestartCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use (filter when using --all)")
|
||||||
|
crewRestartCmd.Flags().BoolVar(&crewAll, "all", false, "Restart all running crew sessions")
|
||||||
|
crewRestartCmd.Flags().BoolVar(&crewDryRun, "dry-run", false, "Show what would be restarted without restarting")
|
||||||
|
|
||||||
crewStartCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use")
|
crewStartCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use")
|
||||||
crewStartCmd.Flags().StringVar(&crewAccount, "account", "", "Claude Code account handle to use")
|
crewStartCmd.Flags().StringVar(&crewAccount, "account", "", "Claude Code account handle to use")
|
||||||
|
|||||||
@@ -177,6 +177,11 @@ func runCrewStart(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runCrewRestart(cmd *cobra.Command, args []string) error {
|
func runCrewRestart(cmd *cobra.Command, args []string) error {
|
||||||
|
// Handle --all flag
|
||||||
|
if crewAll {
|
||||||
|
return runCrewRestartAll()
|
||||||
|
}
|
||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
// Parse rig/name format (e.g., "beads/emma" -> rig=beads, name=emma)
|
// Parse rig/name format (e.g., "beads/emma" -> rig=beads, name=emma)
|
||||||
if rig, crewName, ok := parseRigSlashName(name); ok {
|
if rig, crewName, ok := parseRigSlashName(name); ok {
|
||||||
@@ -269,3 +274,158 @@ func runCrewRestart(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runCrewRestartAll restarts all running crew sessions.
|
||||||
|
// If crewRig is set, only restarts crew in that rig.
|
||||||
|
func runCrewRestartAll() error {
|
||||||
|
// Get all agent sessions (including polecats to find crew)
|
||||||
|
agents, err := getAgentSessions(true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listing sessions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter to crew agents only
|
||||||
|
var targets []*AgentSession
|
||||||
|
for _, agent := range agents {
|
||||||
|
if agent.Type != AgentCrew {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Filter by rig if specified
|
||||||
|
if crewRig != "" && agent.Rig != crewRig {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
targets = append(targets, agent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(targets) == 0 {
|
||||||
|
fmt.Println("No running crew sessions to restart.")
|
||||||
|
if crewRig != "" {
|
||||||
|
fmt.Printf(" (filtered by rig: %s)\n", crewRig)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dry run - just show what would be restarted
|
||||||
|
if crewDryRun {
|
||||||
|
fmt.Printf("Would restart %d crew session(s):\n\n", len(targets))
|
||||||
|
for _, agent := range targets {
|
||||||
|
fmt.Printf(" %s %s/crew/%s\n", AgentTypeIcons[AgentCrew], agent.Rig, agent.AgentName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Restarting %d crew session(s)...\n\n", len(targets))
|
||||||
|
|
||||||
|
var succeeded, failed int
|
||||||
|
var failures []string
|
||||||
|
|
||||||
|
for _, agent := range targets {
|
||||||
|
agentName := fmt.Sprintf("%s/crew/%s", agent.Rig, agent.AgentName)
|
||||||
|
|
||||||
|
// Use crewRig temporarily to get the right crew manager
|
||||||
|
savedRig := crewRig
|
||||||
|
crewRig = agent.Rig
|
||||||
|
|
||||||
|
crewMgr, r, err := getCrewManager(crewRig)
|
||||||
|
if err != nil {
|
||||||
|
failed++
|
||||||
|
failures = append(failures, fmt.Sprintf("%s: %v", agentName, err))
|
||||||
|
fmt.Printf(" %s %s\n", style.ErrorPrefix, agentName)
|
||||||
|
crewRig = savedRig
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
worker, err := crewMgr.Get(agent.AgentName)
|
||||||
|
if err != nil {
|
||||||
|
failed++
|
||||||
|
failures = append(failures, fmt.Sprintf("%s: %v", agentName, err))
|
||||||
|
fmt.Printf(" %s %s\n", style.ErrorPrefix, agentName)
|
||||||
|
crewRig = savedRig
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart the session
|
||||||
|
if err := restartCrewSession(r.Name, agent.AgentName, worker.ClonePath); err != nil {
|
||||||
|
failed++
|
||||||
|
failures = append(failures, fmt.Sprintf("%s: %v", agentName, err))
|
||||||
|
fmt.Printf(" %s %s\n", style.ErrorPrefix, agentName)
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
fmt.Printf(" %s %s\n", style.SuccessPrefix, agentName)
|
||||||
|
}
|
||||||
|
|
||||||
|
crewRig = savedRig
|
||||||
|
|
||||||
|
// Small delay between restarts to avoid overwhelming the system
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
if failed > 0 {
|
||||||
|
fmt.Printf("%s Restart complete: %d succeeded, %d failed\n",
|
||||||
|
style.WarningPrefix, succeeded, failed)
|
||||||
|
for _, f := range failures {
|
||||||
|
fmt.Printf(" %s\n", style.Dim.Render(f))
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%d restart(s) failed", failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s Restart complete: %d crew session(s) restarted\n", style.SuccessPrefix, succeeded)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// restartCrewSession handles the core restart logic for a single crew session.
|
||||||
|
func restartCrewSession(rigName, crewName, clonePath string) error {
|
||||||
|
t := tmux.NewTmux()
|
||||||
|
sessionID := crewSessionName(rigName, crewName)
|
||||||
|
|
||||||
|
// Kill existing session if running
|
||||||
|
if hasSession, _ := t.HasSession(sessionID); hasSession {
|
||||||
|
if err := t.KillSession(sessionID); err != nil {
|
||||||
|
return fmt.Errorf("killing old session: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start new session
|
||||||
|
if err := t.NewSession(sessionID, clonePath); err != nil {
|
||||||
|
return fmt.Errorf("creating session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set environment
|
||||||
|
t.SetEnvironment(sessionID, "GT_ROLE", "crew")
|
||||||
|
t.SetEnvironment(sessionID, "GT_RIG", rigName)
|
||||||
|
t.SetEnvironment(sessionID, "GT_CREW", crewName)
|
||||||
|
|
||||||
|
// Apply rig-based theming
|
||||||
|
theme := getThemeForRig(rigName)
|
||||||
|
_ = t.ConfigureGasTownSession(sessionID, theme, rigName, crewName, "crew")
|
||||||
|
|
||||||
|
// Wait for shell to be ready
|
||||||
|
if err := t.WaitForShellReady(sessionID, 5*time.Second); err != nil {
|
||||||
|
return fmt.Errorf("waiting for shell: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start claude with skip permissions
|
||||||
|
bdActor := fmt.Sprintf("%s/crew/%s", rigName, crewName)
|
||||||
|
claudeCmd := fmt.Sprintf("export GT_ROLE=crew GT_RIG=%s GT_CREW=%s BD_ACTOR=%s && claude --dangerously-skip-permissions", rigName, crewName, bdActor)
|
||||||
|
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
|
||||||
|
return fmt.Errorf("starting claude: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Claude to start, then prime it
|
||||||
|
shells := []string{"bash", "zsh", "sh", "fish", "tcsh", "ksh"}
|
||||||
|
if err := t.WaitForCommand(sessionID, shells, 15*time.Second); err != nil {
|
||||||
|
// Non-fatal warning
|
||||||
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
if err := t.SendKeys(sessionID, "gt prime"); err != nil {
|
||||||
|
// Non-fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send crew resume prompt after prime completes
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
crewPrompt := "Read your mail, act on anything urgent, else await instructions."
|
||||||
|
_ = t.NudgeSession(sessionID, crewPrompt)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user