package cmd import ( "fmt" "path/filepath" "github.com/spf13/cobra" "github.com/steveyegge/gastown/internal/beads" "github.com/steveyegge/gastown/internal/config" "github.com/steveyegge/gastown/internal/crew" "github.com/steveyegge/gastown/internal/git" "github.com/steveyegge/gastown/internal/rig" "github.com/steveyegge/gastown/internal/style" "github.com/steveyegge/gastown/internal/workspace" ) var crewSyncCmd = &cobra.Command{ Use: "sync", Short: "Create missing crew members from rigs.json config", Long: `Sync crew members from rigs.json configuration. Creates any crew members defined in rigs.json that don't already exist locally. This enables sharing crew configuration across machines. Configuration in mayor/rigs.json: { "rigs": { "gastown": { "crew": { "theme": "mad-max", "members": ["diesel", "chrome", "nitro"] } } } } Examples: gt crew sync # Sync crew in current rig gt crew sync --rig gastown # Sync crew in specific rig gt crew sync --dry-run # Show what would be created`, RunE: runCrewSync, } func init() { crewSyncCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to sync crew in") crewSyncCmd.Flags().BoolVar(&crewDryRun, "dry-run", false, "Show what would be created without creating") crewCmd.AddCommand(crewSyncCmd) } func runCrewSync(cmd *cobra.Command, args []string) error { // Find workspace 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 { return fmt.Errorf("loading rigs config: %w", err) } // Determine rig rigName := crewRig if rigName == "" { rigName, err = inferRigFromCwd(townRoot) if err != nil { return fmt.Errorf("could not determine rig (use --rig flag): %w", err) } } // Get rig entry from rigs.json rigEntry, ok := rigsConfig.Rigs[rigName] if !ok { return fmt.Errorf("rig '%s' not found in rigs.json", rigName) } // Check if crew config exists if rigEntry.Crew == nil || len(rigEntry.Crew.Members) == 0 { fmt.Printf("No crew members configured for rig '%s' in rigs.json\n", rigName) fmt.Printf("\nTo configure crew, add to mayor/rigs.json:\n") fmt.Printf(" \"crew\": {\n") fmt.Printf(" \"theme\": \"mad-max\",\n") fmt.Printf(" \"members\": [\"diesel\", \"chrome\", \"nitro\"]\n") fmt.Printf(" }\n") return nil } // Get rig g := git.NewGit(townRoot) rigMgr := rig.NewManager(townRoot, rigsConfig, g) r, err := rigMgr.GetRig(rigName) if err != nil { return fmt.Errorf("rig '%s' not found", rigName) } // Create crew manager crewGit := git.NewGit(r.Path) crewMgr := crew.NewManager(r, crewGit) bd := beads.New(beads.ResolveBeadsDir(r.Path)) // Get existing crew existingCrew, err := crewMgr.List() if err != nil { return fmt.Errorf("listing existing crew: %w", err) } existingNames := make(map[string]bool) for _, c := range existingCrew { existingNames[c.Name] = true } // Track results var created []string var skipped []string var failed []string // Process each configured member for _, name := range rigEntry.Crew.Members { if existingNames[name] { skipped = append(skipped, name) continue } if crewDryRun { fmt.Printf("Would create: %s/%s\n", rigName, name) created = append(created, name) continue } // Create crew workspace fmt.Printf("Creating crew workspace %s in %s...\n", name, rigName) worker, err := crewMgr.Add(name, false) // No feature branch for synced crew if err != nil { if err == crew.ErrCrewExists { skipped = append(skipped, name) 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("\u2713"), rigName, name) fmt.Printf(" Path: %s\n", worker.ClonePath) fmt.Printf(" Branch: %s\n", worker.Branch) // Create agent bead for the crew worker prefix := beads.GetPrefixForRig(townRoot, rigName) crewID := beads.CrewBeadIDWithPrefix(prefix, 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", } desc := fmt.Sprintf("Crew worker %s in %s - synced from rigs.json.", 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) fmt.Println() } // Summary if crewDryRun { fmt.Printf("\n%s Dry run complete\n", style.Bold.Render("\u2713")) if len(created) > 0 { fmt.Printf(" Would create: %v\n", created) } if len(skipped) > 0 { fmt.Printf(" Already exist: %v\n", skipped) } return nil } if len(created) > 0 { fmt.Printf("%s Created %d crew workspace(s): %v\n", style.Bold.Render("\u2713"), len(created), created) } if len(skipped) > 0 { fmt.Printf("%s Skipped %d (already exist): %v\n", style.Dim.Render("-"), len(skipped), skipped) } if len(failed) > 0 { fmt.Printf("%s Failed to create %d: %v\n", style.Warning.Render("!"), len(failed), failed) } // Show theme if configured if rigEntry.Crew.Theme != "" { fmt.Printf("\nCrew theme: %s\n", rigEntry.Crew.Theme) } return nil }