From db07394dbe5177d6144546f80e13ea585a5ee5b6 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Tue, 30 Dec 2025 19:43:09 -0800 Subject: [PATCH] Support multiple names in gt crew add Examples: gt crew add murgen croaker goblin # Create all three gt crew add dave # Still works for single - Continues on failure (warns but doesn't abort) - Shows summary at end - Existing workspaces skipped with warning --- internal/cmd/crew.go | 11 +-- internal/cmd/crew_add.go | 143 ++++++++++++++++++++++++--------------- 2 files changed, 96 insertions(+), 58 deletions(-) diff --git a/internal/cmd/crew.go b/internal/cmd/crew.go index 0fafe600..1fac5491 100644 --- a/internal/cmd/crew.go +++ b/internal/cmd/crew.go @@ -47,19 +47,20 @@ Commands: var crewAddCmd = &cobra.Command{ Use: "add ", Short: "Create a new crew workspace", - Long: `Create a new crew workspace with a clone of the rig repository. + Long: `Create new crew workspace(s) with a clone of the rig repository. -The workspace is created at /crew// with: +Each workspace is created at /crew// with: - A full git clone of the project repository - Mail directory for message delivery - CLAUDE.md with crew worker prompting - Optional feature branch (crew/) Examples: - gt crew add dave # Create in current rig + gt crew add dave # Create single workspace + gt crew add murgen croaker goblin # Create multiple at once gt crew add emma --rig greenplace # Create in specific rig - gt crew add fred --branch # Create with feature branch`, - Args: cobra.ExactArgs(1), + gt crew add fred --branch # Create with feature branch`, + Args: cobra.MinimumNArgs(1), RunE: runCrewAdd, } diff --git a/internal/cmd/crew_add.go b/internal/cmd/crew_add.go index dee5787d..754a7b59 100644 --- a/internal/cmd/crew_add.go +++ b/internal/cmd/crew_add.go @@ -15,19 +15,7 @@ import ( ) func runCrewAdd(cmd *cobra.Command, args []string) error { - name := args[0] - - // Parse rig/name format (e.g., "beads/emma" -> rig=beads, name=emma) - // This prevents creating nested directories like crew/beads/emma - rigName := crewRig - if parsedRig, crewName, ok := parseRigSlashName(name); ok { - if rigName == "" { - rigName = parsedRig - } - name = crewName - } - - // Find workspace + // Find workspace first (needed for all names) townRoot, err := workspace.FindFromCwdOrError() if err != nil { return fmt.Errorf("not in a Gas Town workspace: %w", err) @@ -40,10 +28,17 @@ func runCrewAdd(cmd *cobra.Command, args []string) error { rigsConfig = &config.RigsConfig{Rigs: make(map[string]config.RigEntry)} } - // Determine rig (if not already set from slash format or --rig flag) - if rigName == "" { + // Determine base rig from --rig flag or first name's rig/name format + baseRig := crewRig + if baseRig == "" { + // Check if first arg has rig/name format + if parsedRig, _, ok := parseRigSlashName(args[0]); ok { + baseRig = parsedRig + } + } + if baseRig == "" { // Try to infer from cwd - rigName, err = inferRigFromCwd(townRoot) + baseRig, err = inferRigFromCwd(townRoot) if err != nil { return fmt.Errorf("could not determine rig (use --rig flag): %w", err) } @@ -52,56 +47,98 @@ func runCrewAdd(cmd *cobra.Command, args []string) error { // Get rig g := git.NewGit(townRoot) rigMgr := rig.NewManager(townRoot, rigsConfig, g) - r, err := rigMgr.GetRig(rigName) + r, err := rigMgr.GetRig(baseRig) if err != nil { - return fmt.Errorf("rig '%s' not found", rigName) + return fmt.Errorf("rig '%s' not found", baseRig) } // Create crew manager crewGit := git.NewGit(r.Path) crewMgr := crew.NewManager(r, crewGit) - // Create crew workspace - fmt.Printf("Creating crew workspace %s in %s...\n", name, rigName) - - worker, err := crewMgr.Add(name, crewBranch) - if err != nil { - if err == crew.ErrCrewExists { - return fmt.Errorf("crew workspace '%s' already exists", name) - } - return fmt.Errorf("creating crew workspace: %w", err) - } - - fmt.Printf("%s Created crew workspace: %s/%s\n", - style.Bold.Render("✓"), rigName, name) - fmt.Printf(" Path: %s\n", worker.ClonePath) - fmt.Printf(" Branch: %s\n", worker.Branch) - fmt.Printf(" Mail: %s/mail/\n", worker.ClonePath) - - // Create agent bead for the crew worker + // Beads for agent bead creation rigBeadsPath := filepath.Join(r.Path, "mayor", "rig") bd := beads.New(rigBeadsPath) - // Agent beads always use "gt-" prefix (required by beads validation) - // Only issue beads use rig-specific prefixes - crewID := beads.CrewBeadID(rigName, name) - if _, err := bd.Show(crewID); err != nil { - // Agent bead doesn't exist, create it - fields := &beads.AgentFields{ - RoleType: "crew", - Rig: rigName, - AgentState: "idle", - RoleBead: "gt-crew-role", + + // Track results + var created []string + var failed []string + var lastWorker *crew.CrewWorker + + // Process each name + for _, arg := range args { + name := arg + rigName := baseRig + + // Parse rig/name format (e.g., "beads/emma" -> rig=beads, name=emma) + if parsedRig, crewName, ok := parseRigSlashName(arg); ok { + // For rig/name format, use that rig (but warn if different from base) + if parsedRig != baseRig { + style.PrintWarning("%s: different rig '%s' ignored (use --rig to change)", arg, parsedRig) + } + name = crewName } - desc := fmt.Sprintf("Crew worker %s in %s - human-managed persistent workspace.", name, rigName) - if _, err := bd.CreateAgentBead(crewID, desc, fields); err != nil { - // Non-fatal: warn but don't fail the add - style.PrintWarning("could not create agent bead: %v", err) - } else { - fmt.Printf(" Agent bead: %s\n", crewID) + + // Create crew workspace + fmt.Printf("Creating crew workspace %s in %s...\n", name, rigName) + + worker, err := crewMgr.Add(name, crewBranch) + if err != nil { + if err == crew.ErrCrewExists { + style.PrintWarning("crew workspace '%s' already exists, skipping", name) + failed = append(failed, name+" (exists)") + continue + } + style.PrintWarning("creating crew workspace '%s': %v", name, err) + failed = append(failed, name) + continue } + + fmt.Printf("%s Created crew workspace: %s/%s\n", + style.Bold.Render("✓"), rigName, name) + fmt.Printf(" Path: %s\n", worker.ClonePath) + fmt.Printf(" Branch: %s\n", worker.Branch) + + // Create agent bead for the crew worker + crewID := beads.CrewBeadID(rigName, name) + if _, err := bd.Show(crewID); err != nil { + // Agent bead doesn't exist, create it + fields := &beads.AgentFields{ + RoleType: "crew", + Rig: rigName, + AgentState: "idle", + RoleBead: "gt-crew-role", + } + desc := fmt.Sprintf("Crew worker %s in %s - human-managed persistent workspace.", name, rigName) + if _, err := bd.CreateAgentBead(crewID, desc, fields); err != nil { + style.PrintWarning("could not create agent bead for %s: %v", name, err) + } else { + fmt.Printf(" Agent bead: %s\n", crewID) + } + } + + created = append(created, name) + lastWorker = worker + fmt.Println() } - fmt.Printf("\n%s\n", style.Dim.Render("Start working with: cd "+worker.ClonePath)) + // Summary + if len(created) > 0 { + fmt.Printf("%s Created %d crew workspace(s): %v\n", + style.Bold.Render("✓"), len(created), created) + if lastWorker != nil && len(created) == 1 { + fmt.Printf("\n%s\n", style.Dim.Render("Start working with: cd "+lastWorker.ClonePath)) + } + } + if len(failed) > 0 { + fmt.Printf("%s Failed to create %d workspace(s): %v\n", + style.Warning.Render("!"), len(failed), failed) + } + + // Return error if all failed + if len(created) == 0 && len(failed) > 0 { + return fmt.Errorf("failed to create any crew workspaces") + } return nil }