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
This commit is contained in:
@@ -47,19 +47,20 @@ Commands:
|
||||
var crewAddCmd = &cobra.Command{
|
||||
Use: "add <name>",
|
||||
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 <rig>/crew/<name>/ with:
|
||||
Each workspace is created at <rig>/crew/<name>/ with:
|
||||
- A full git clone of the project repository
|
||||
- Mail directory for message delivery
|
||||
- CLAUDE.md with crew worker prompting
|
||||
- Optional feature branch (crew/<name>)
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user