feat(crew): add crew configuration to rigs.json for cross-machine sync
Add CrewRegistryConfig to RigEntry allowing crew members to be defined
in rigs.json and synced across machines. The new `gt crew sync` command
creates missing crew members from the configuration.
Configuration example:
"rigs": {
"gastown": {
"crew": {
"theme": "mad-max",
"members": ["diesel", "chrome", "nitro"]
}
}
}
Closes: gt-tu4
This commit is contained in:
204
internal/cmd/crew_sync.go
Normal file
204
internal/cmd/crew_sync.go
Normal file
@@ -0,0 +1,204 @@
|
||||
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
|
||||
}
|
||||
@@ -163,10 +163,11 @@ type RigsConfig struct {
|
||||
|
||||
// RigEntry represents a single rig in the registry.
|
||||
type RigEntry struct {
|
||||
GitURL string `json:"git_url"`
|
||||
LocalRepo string `json:"local_repo,omitempty"`
|
||||
AddedAt time.Time `json:"added_at"`
|
||||
BeadsConfig *BeadsConfig `json:"beads,omitempty"`
|
||||
GitURL string `json:"git_url"`
|
||||
LocalRepo string `json:"local_repo,omitempty"`
|
||||
AddedAt time.Time `json:"added_at"`
|
||||
BeadsConfig *BeadsConfig `json:"beads,omitempty"`
|
||||
Crew *CrewRegistryConfig `json:"crew,omitempty"`
|
||||
}
|
||||
|
||||
// BeadsConfig represents beads configuration for a rig.
|
||||
@@ -175,6 +176,18 @@ type BeadsConfig struct {
|
||||
Prefix string `json:"prefix"` // issue prefix
|
||||
}
|
||||
|
||||
// CrewRegistryConfig represents crew configuration for a rig in rigs.json.
|
||||
// This enables cross-machine sync of crew member definitions.
|
||||
type CrewRegistryConfig struct {
|
||||
// Theme selects the naming theme for crew members (e.g., "mad-max", "minerals").
|
||||
// Used when displaying crew member names and for consistency across machines.
|
||||
Theme string `json:"theme,omitempty"`
|
||||
|
||||
// Members lists the crew member names to create on this rig.
|
||||
// Use `gt crew sync` to create missing members from this list.
|
||||
Members []string `json:"members,omitempty"`
|
||||
}
|
||||
|
||||
// CurrentTownVersion is the current schema version for TownConfig.
|
||||
// Version 2: Added Owner and PublicName fields for federation identity.
|
||||
const CurrentTownVersion = 2
|
||||
|
||||
Reference in New Issue
Block a user