feat: Allow remove and restart to take multiple crew names
This commit is contained in:
@@ -107,17 +107,19 @@ Examples:
|
|||||||
}
|
}
|
||||||
|
|
||||||
var crewRemoveCmd = &cobra.Command{
|
var crewRemoveCmd = &cobra.Command{
|
||||||
Use: "remove <name>",
|
Use: "remove <name...>",
|
||||||
Short: "Remove a crew workspace",
|
Short: "Remove crew workspace(s)",
|
||||||
Long: `Remove a crew workspace from the rig.
|
Long: `Remove one or more crew workspaces from the rig.
|
||||||
|
|
||||||
Checks for uncommitted changes and running sessions before removing.
|
Checks for uncommitted changes and running sessions before removing.
|
||||||
Use --force to skip checks and remove anyway.
|
Use --force to skip checks and remove anyway.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
gt crew remove dave # Remove with safety checks
|
gt crew remove dave # Remove with safety checks
|
||||||
|
gt crew remove dave emma fred # Remove multiple
|
||||||
|
gt crew remove beads/grip beads/fang # Remove from specific rig
|
||||||
gt crew remove dave --force # Force remove`,
|
gt crew remove dave --force # Force remove`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
RunE: runCrewRemove,
|
RunE: runCrewRemove,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,9 +154,9 @@ 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(s)",
|
||||||
Long: `Kill the tmux session and restart fresh with Claude.
|
Long: `Kill the tmux session and restart fresh with Claude.
|
||||||
|
|
||||||
Useful when a crew member gets confused or needs a clean slate.
|
Useful when a crew member gets confused or needs a clean slate.
|
||||||
@@ -169,6 +171,8 @@ 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 restart dave emma fred # Restart multiple
|
||||||
|
gt crew restart beads/grip beads/fang # Restart from specific rig
|
||||||
gt crew rs emma # Same, using alias
|
gt crew rs emma # Same, using alias
|
||||||
gt crew restart --all # Restart all running crew sessions
|
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 --rig beads # Restart all crew in beads rig
|
||||||
@@ -180,8 +184,8 @@ Examples:
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if len(args) != 1 {
|
if len(args) < 1 {
|
||||||
return fmt.Errorf("requires exactly 1 argument (or --all)")
|
return fmt.Errorf("requires at least 1 argument (or --all)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,18 +17,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func runCrewRemove(cmd *cobra.Command, args []string) error {
|
func runCrewRemove(cmd *cobra.Command, args []string) error {
|
||||||
name := args[0]
|
var lastErr error
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
name := arg
|
||||||
|
rigOverride := crewRig
|
||||||
|
|
||||||
// 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 {
|
||||||
if crewRig == "" {
|
if rigOverride == "" {
|
||||||
crewRig = rig
|
rigOverride = rig
|
||||||
}
|
}
|
||||||
name = crewName
|
name = crewName
|
||||||
}
|
}
|
||||||
|
|
||||||
crewMgr, r, err := getCrewManager(crewRig)
|
crewMgr, r, err := getCrewManager(rigOverride)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
fmt.Printf("Error removing %s: %v\n", arg, err)
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for running session (unless forced)
|
// Check for running session (unless forced)
|
||||||
@@ -37,7 +44,9 @@ func runCrewRemove(cmd *cobra.Command, args []string) error {
|
|||||||
sessionID := crewSessionName(r.Name, name)
|
sessionID := crewSessionName(r.Name, name)
|
||||||
hasSession, _ := t.HasSession(sessionID)
|
hasSession, _ := t.HasSession(sessionID)
|
||||||
if hasSession {
|
if hasSession {
|
||||||
return fmt.Errorf("session '%s' is running (use --force to kill and remove)", sessionID)
|
fmt.Printf("Error removing %s: session '%s' is running (use --force to kill and remove)\n", arg, sessionID)
|
||||||
|
lastErr = fmt.Errorf("session running")
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +55,9 @@ func runCrewRemove(cmd *cobra.Command, args []string) error {
|
|||||||
sessionID := crewSessionName(r.Name, name)
|
sessionID := crewSessionName(r.Name, name)
|
||||||
if hasSession, _ := t.HasSession(sessionID); hasSession {
|
if hasSession, _ := t.HasSession(sessionID); hasSession {
|
||||||
if err := t.KillSession(sessionID); err != nil {
|
if err := t.KillSession(sessionID); err != nil {
|
||||||
return fmt.Errorf("killing session: %w", err)
|
fmt.Printf("Error killing session for %s: %v\n", arg, err)
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
fmt.Printf("Killed session %s\n", sessionID)
|
fmt.Printf("Killed session %s\n", sessionID)
|
||||||
}
|
}
|
||||||
@@ -54,12 +65,14 @@ func runCrewRemove(cmd *cobra.Command, args []string) error {
|
|||||||
// Remove the crew workspace
|
// Remove the crew workspace
|
||||||
if err := crewMgr.Remove(name, crewForce); err != nil {
|
if err := crewMgr.Remove(name, crewForce); err != nil {
|
||||||
if err == crew.ErrCrewNotFound {
|
if err == crew.ErrCrewNotFound {
|
||||||
return fmt.Errorf("crew workspace '%s' not found", name)
|
fmt.Printf("Error removing %s: crew workspace not found\n", arg)
|
||||||
|
} else if err == crew.ErrHasChanges {
|
||||||
|
fmt.Printf("Error removing %s: uncommitted changes (use --force)\n", arg)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Error removing %s: %v\n", arg, err)
|
||||||
}
|
}
|
||||||
if err == crew.ErrHasChanges {
|
lastErr = err
|
||||||
return fmt.Errorf("crew workspace has uncommitted changes (use --force to remove anyway)")
|
continue
|
||||||
}
|
|
||||||
return fmt.Errorf("removing crew workspace: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s Removed crew workspace: %s/%s\n",
|
fmt.Printf("%s Removed crew workspace: %s/%s\n",
|
||||||
@@ -79,8 +92,9 @@ func runCrewRemove(cmd *cobra.Command, args []string) error {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Printf("Closed agent bead: %s\n", agentBeadID)
|
fmt.Printf("Closed agent bead: %s\n", agentBeadID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCrewRefresh(cmd *cobra.Command, args []string) error {
|
func runCrewRefresh(cmd *cobra.Command, args []string) error {
|
||||||
@@ -225,18 +239,25 @@ func runCrewRestart(cmd *cobra.Command, args []string) error {
|
|||||||
return runCrewRestartAll()
|
return runCrewRestartAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
name := args[0]
|
var lastErr error
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
name := arg
|
||||||
|
rigOverride := crewRig
|
||||||
|
|
||||||
// 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 {
|
||||||
if crewRig == "" {
|
if rigOverride == "" {
|
||||||
crewRig = rig
|
rigOverride = rig
|
||||||
}
|
}
|
||||||
name = crewName
|
name = crewName
|
||||||
}
|
}
|
||||||
|
|
||||||
crewMgr, r, err := getCrewManager(crewRig)
|
crewMgr, r, err := getCrewManager(rigOverride)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
fmt.Printf("Error restarting %s: %v\n", arg, err)
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the crew worker, create if not exists (idempotent)
|
// Get the crew worker, create if not exists (idempotent)
|
||||||
@@ -245,11 +266,15 @@ func runCrewRestart(cmd *cobra.Command, args []string) error {
|
|||||||
fmt.Printf("Creating crew workspace %s in %s...\n", name, r.Name)
|
fmt.Printf("Creating crew workspace %s in %s...\n", name, r.Name)
|
||||||
worker, err = crewMgr.Add(name, false) // No feature branch for crew
|
worker, err = crewMgr.Add(name, false) // No feature branch for crew
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating crew workspace: %w", err)
|
fmt.Printf("Error creating %s: %v\n", arg, err)
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
fmt.Printf("Created crew workspace: %s/%s\n", r.Name, name)
|
fmt.Printf("Created crew workspace: %s/%s\n", r.Name, name)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("getting crew worker: %w", err)
|
fmt.Printf("Error getting %s: %v\n", arg, err)
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
t := tmux.NewTmux()
|
t := tmux.NewTmux()
|
||||||
@@ -258,14 +283,18 @@ func runCrewRestart(cmd *cobra.Command, args []string) error {
|
|||||||
// Kill existing session if running
|
// Kill existing session if running
|
||||||
if hasSession, _ := t.HasSession(sessionID); hasSession {
|
if hasSession, _ := t.HasSession(sessionID); hasSession {
|
||||||
if err := t.KillSession(sessionID); err != nil {
|
if err := t.KillSession(sessionID); err != nil {
|
||||||
return fmt.Errorf("killing old session: %w", err)
|
fmt.Printf("Error killing session for %s: %v\n", arg, err)
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
fmt.Printf("Killed session %s\n", sessionID)
|
fmt.Printf("Killed session %s\n", sessionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start new session
|
// Start new session
|
||||||
if err := t.NewSession(sessionID, worker.ClonePath); err != nil {
|
if err := t.NewSession(sessionID, worker.ClonePath); err != nil {
|
||||||
return fmt.Errorf("creating session: %w", err)
|
fmt.Printf("Error creating session for %s: %v\n", arg, err)
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set environment
|
// Set environment
|
||||||
@@ -279,7 +308,9 @@ func runCrewRestart(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
// Wait for shell to be ready
|
// Wait for shell to be ready
|
||||||
if err := t.WaitForShellReady(sessionID, constants.ShellReadyTimeout); err != nil {
|
if err := t.WaitForShellReady(sessionID, constants.ShellReadyTimeout); err != nil {
|
||||||
return fmt.Errorf("waiting for shell: %w", err)
|
fmt.Printf("Error waiting for shell for %s: %v\n", arg, err)
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start claude with skip permissions (crew workers are trusted)
|
// Start claude with skip permissions (crew workers are trusted)
|
||||||
@@ -287,19 +318,21 @@ func runCrewRestart(cmd *cobra.Command, args []string) error {
|
|||||||
bdActor := fmt.Sprintf("%s/crew/%s", r.Name, name)
|
bdActor := fmt.Sprintf("%s/crew/%s", r.Name, name)
|
||||||
claudeCmd := fmt.Sprintf("export GT_ROLE=crew GT_RIG=%s GT_CREW=%s BD_ACTOR=%s && claude --dangerously-skip-permissions", r.Name, name, bdActor)
|
claudeCmd := fmt.Sprintf("export GT_ROLE=crew GT_RIG=%s GT_CREW=%s BD_ACTOR=%s && claude --dangerously-skip-permissions", r.Name, name, bdActor)
|
||||||
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
|
if err := t.SendKeys(sessionID, claudeCmd); err != nil {
|
||||||
return fmt.Errorf("starting claude: %w", err)
|
fmt.Printf("Error starting claude for %s: %v\n", arg, err)
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for Claude to start, then prime it
|
// Wait for Claude to start, then prime it
|
||||||
shells := constants.SupportedShells
|
shells := constants.SupportedShells
|
||||||
if err := t.WaitForCommand(sessionID, shells, constants.ClaudeStartTimeout); err != nil {
|
if err := t.WaitForCommand(sessionID, shells, constants.ClaudeStartTimeout); err != nil {
|
||||||
style.PrintWarning("Timeout waiting for Claude to start: %v", err)
|
style.PrintWarning("Timeout waiting for Claude to start for %s: %v", arg, err)
|
||||||
}
|
}
|
||||||
// Give Claude time to initialize after process starts
|
// Give Claude time to initialize after process starts
|
||||||
time.Sleep(constants.ShutdownNotifyDelay)
|
time.Sleep(constants.ShutdownNotifyDelay)
|
||||||
if err := t.SendKeys(sessionID, "gt prime"); err != nil {
|
if err := t.SendKeys(sessionID, "gt prime"); err != nil {
|
||||||
// Non-fatal: Claude started but priming failed
|
// Non-fatal: Claude started but priming failed
|
||||||
style.PrintWarning("Could not send prime command: %v", err)
|
style.PrintWarning("Could not send prime command to %s: %v", arg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send crew resume prompt after prime completes
|
// Send crew resume prompt after prime completes
|
||||||
@@ -308,14 +341,15 @@ func runCrewRestart(cmd *cobra.Command, args []string) error {
|
|||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
crewPrompt := "Read your mail, act on anything urgent, else await instructions."
|
crewPrompt := "Read your mail, act on anything urgent, else await instructions."
|
||||||
if err := t.NudgeSession(sessionID, crewPrompt); err != nil {
|
if err := t.NudgeSession(sessionID, crewPrompt); err != nil {
|
||||||
style.PrintWarning("Could not send resume prompt: %v", err)
|
style.PrintWarning("Could not send resume prompt to %s: %v", arg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s Restarted crew workspace: %s/%s\n",
|
fmt.Printf("%s Restarted crew workspace: %s/%s\n",
|
||||||
style.Bold.Render("✓"), r.Name, name)
|
style.Bold.Render("✓"), r.Name, name)
|
||||||
fmt.Printf("Attach with: %s\n", style.Dim.Render(fmt.Sprintf("gt crew at %s", name)))
|
fmt.Printf("Attach with: %s\n", style.Dim.Render(fmt.Sprintf("gt crew at %s", name)))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// runCrewRestartAll restarts all running crew sessions.
|
// runCrewRestartAll restarts all running crew sessions.
|
||||||
|
|||||||
Reference in New Issue
Block a user