Files
gastown/internal/cmd/crew_add.go
Sohail Mohammad 81bfe48ed3 feat: Set and Forget - Seamless Gas Town Integration (#255)
Adds shell integration for automatic Gas Town context detection.

Features:
- `gt enable` / `gt disable` - Global on/off switch
- `gt shell install|remove|status` - Shell integration management
- `gt rig quick-add [path]` - One-command project setup
- `gt uninstall` - Clean removal with options
- Shell hook auto-sets GT_TOWN_ROOT/GT_RIG on cd

Implementation:
- XDG-compliant state storage (~/.local/state/gastown/)
- Safe RC file manipulation with block markers
- Environment overrides (GASTOWN_DISABLED/ENABLED)
- Doctor check for global state validation

Co-authored-by: Sohail Mohammad <sohailm25@gmail.com>
2026-01-08 20:25:01 -08:00

144 lines
4.1 KiB
Go

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"
)
func runCrewAdd(cmd *cobra.Command, args []string) error {
// 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)
}
// Load rigs config
rigsConfigPath := filepath.Join(townRoot, "mayor", "rigs.json")
rigsConfig, err := config.LoadRigsConfig(rigsConfigPath)
if err != nil {
rigsConfig = &config.RigsConfig{Rigs: make(map[string]config.RigEntry)}
}
// 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
baseRig, err = inferRigFromCwd(townRoot)
if err != nil {
return fmt.Errorf("could not determine rig (use --rig flag): %w", err)
}
}
// Get rig
g := git.NewGit(townRoot)
rigMgr := rig.NewManager(townRoot, rigsConfig, g)
r, err := rigMgr.GetRig(baseRig)
if err != nil {
return fmt.Errorf("rig '%s' not found", baseRig)
}
// Create crew manager
crewGit := git.NewGit(r.Path)
crewMgr := crew.NewManager(r, crewGit)
bd := beads.New(beads.ResolveBeadsDir(r.Path))
// 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
}
// 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
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",
RoleBead: beads.RoleBeadIDTown("crew"),
}
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()
}
// 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
}