// Package cmd provides CLI commands for the gt tool. package cmd import ( "fmt" "os" "path/filepath" "time" "github.com/spf13/cobra" "github.com/steveyegge/gastown/internal/config" "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 rigCmd = &cobra.Command{ Use: "rig", Short: "Manage rigs in the workspace", Long: `Manage rigs (project containers) in the Gas Town workspace. A rig is a container for managing a project and its agents: - refinery/rig/ Canonical main clone (Refinery's working copy) - mayor/rig/ Mayor's working clone for this rig - crew// Human workspace(s) - witness/ Witness agent (no clone) - polecats/ Worker directories - .beads/ Rig-level issue tracking`, } var rigAddCmd = &cobra.Command{ Use: "add ", Short: "Add a new rig to the workspace", Long: `Add a new rig by cloning a repository. This creates a rig container with: - config.json Rig configuration - .beads/ Rig-level issue tracking (initialized) - refinery/rig/ Canonical main clone - mayor/rig/ Mayor's working clone - crew/main/ Default human workspace - witness/ Witness agent directory - polecats/ Worker directory (empty) Example: gt rig add gastown https://github.com/steveyegge/gastown gt rig add my-project git@github.com:user/repo.git --prefix mp`, Args: cobra.ExactArgs(2), RunE: runRigAdd, } var rigListCmd = &cobra.Command{ Use: "list", Short: "List all rigs in the workspace", RunE: runRigList, } var rigRemoveCmd = &cobra.Command{ Use: "remove ", Short: "Remove a rig from the registry (does not delete files)", Args: cobra.ExactArgs(1), RunE: runRigRemove, } // Flags var ( rigAddPrefix string rigAddCrew string ) func init() { rootCmd.AddCommand(rigCmd) rigCmd.AddCommand(rigAddCmd) rigCmd.AddCommand(rigListCmd) rigCmd.AddCommand(rigRemoveCmd) rigAddCmd.Flags().StringVar(&rigAddPrefix, "prefix", "", "Beads issue prefix (default: derived from name)") rigAddCmd.Flags().StringVar(&rigAddCrew, "crew", "main", "Default crew workspace name") } func runRigAdd(cmd *cobra.Command, args []string) error { name := args[0] gitURL := args[1] // Find workspace townRoot, err := workspace.FindFromCwdOrError() if err != nil { return fmt.Errorf("not in a Gas Town workspace: %w", err) } // Load rigs config rigsPath := filepath.Join(townRoot, "mayor", "rigs.json") rigsConfig, err := config.LoadRigsConfig(rigsPath) if err != nil { // Create new if doesn't exist rigsConfig = &config.RigsConfig{ Version: 1, Rigs: make(map[string]config.RigEntry), } } // Create rig manager g := git.NewGit(townRoot) mgr := rig.NewManager(townRoot, rigsConfig, g) fmt.Printf("Creating rig %s...\n", style.Bold.Render(name)) fmt.Printf(" Repository: %s\n", gitURL) startTime := time.Now() // Add the rig newRig, err := mgr.AddRig(rig.AddRigOptions{ Name: name, GitURL: gitURL, BeadsPrefix: rigAddPrefix, CrewName: rigAddCrew, }) if err != nil { return fmt.Errorf("adding rig: %w", err) } // Save updated rigs config if err := config.SaveRigsConfig(rigsPath, rigsConfig); err != nil { return fmt.Errorf("saving rigs config: %w", err) } elapsed := time.Since(startTime) fmt.Printf("\n%s Rig created in %.1fs\n", style.Success.Render("✓"), elapsed.Seconds()) fmt.Printf("\nStructure:\n") fmt.Printf(" %s/\n", name) fmt.Printf(" ├── config.json\n") fmt.Printf(" ├── .beads/ (prefix: %s)\n", newRig.Config.Prefix) fmt.Printf(" ├── refinery/rig/ (canonical main)\n") fmt.Printf(" ├── mayor/rig/ (mayor's clone)\n") fmt.Printf(" ├── crew/%s/ (your workspace)\n", rigAddCrew) fmt.Printf(" ├── witness/\n") fmt.Printf(" └── polecats/\n") fmt.Printf("\nNext steps:\n") fmt.Printf(" cd %s/crew/%s # Work in your clone\n", filepath.Join(townRoot, name), rigAddCrew) fmt.Printf(" bd ready # See available work\n") return nil } func runRigList(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 rigsPath := filepath.Join(townRoot, "mayor", "rigs.json") rigsConfig, err := config.LoadRigsConfig(rigsPath) if err != nil { fmt.Println("No rigs configured.") return nil } if len(rigsConfig.Rigs) == 0 { fmt.Println("No rigs configured.") fmt.Printf("\nAdd one with: %s\n", style.Dim.Render("gt rig add ")) return nil } // Create rig manager to get details g := git.NewGit(townRoot) mgr := rig.NewManager(townRoot, rigsConfig, g) fmt.Printf("Rigs in %s:\n\n", townRoot) for name := range rigsConfig.Rigs { r, err := mgr.GetRig(name) if err != nil { fmt.Printf(" %s %s\n", style.Warning.Render("!"), name) continue } summary := r.Summary() fmt.Printf(" %s\n", style.Bold.Render(name)) fmt.Printf(" Polecats: %d Crew: %d\n", summary.PolecatCount, summary.CrewCount) agents := []string{} if summary.HasRefinery { agents = append(agents, "refinery") } if summary.HasWitness { agents = append(agents, "witness") } if r.HasMayor { agents = append(agents, "mayor") } if len(agents) > 0 { fmt.Printf(" Agents: %v\n", agents) } fmt.Println() } return nil } func runRigRemove(cmd *cobra.Command, args []string) error { name := args[0] // Find workspace townRoot, err := workspace.FindFromCwdOrError() if err != nil { return fmt.Errorf("not in a Gas Town workspace: %w", err) } // Load rigs config rigsPath := filepath.Join(townRoot, "mayor", "rigs.json") rigsConfig, err := config.LoadRigsConfig(rigsPath) if err != nil { return fmt.Errorf("loading rigs config: %w", err) } // Create rig manager g := git.NewGit(townRoot) mgr := rig.NewManager(townRoot, rigsConfig, g) if err := mgr.RemoveRig(name); err != nil { return fmt.Errorf("removing rig: %w", err) } // Save updated config if err := config.SaveRigsConfig(rigsPath, rigsConfig); err != nil { return fmt.Errorf("saving rigs config: %w", err) } fmt.Printf("%s Rig %s removed from registry\n", style.Success.Render("✓"), name) fmt.Printf("\nNote: Files at %s were NOT deleted.\n", filepath.Join(townRoot, name)) fmt.Printf("To delete: %s\n", style.Dim.Render(fmt.Sprintf("rm -rf %s", filepath.Join(townRoot, name)))) return nil } // Helper to check if path exists func pathExists(path string) bool { _, err := os.Stat(path) return err == nil }