Files
gastown/internal/cmd/namepool.go
Steve Yegge 97e0535bfe Implement three-tier config architecture (gt-k1lr tasks 1-5)
**Architecture changes:**
- Renamed `.gastown/` → `.runtime/` for runtime state (gitignored)
- Added `settings/` directory for rig behavioral config (git-tracked)
- Added `mayor/config.json` for town-level config (MayorConfig type)
- Separated RigConfig (identity) from RigSettings (behavioral)

**File location changes:**
- Town runtime: `~/.gastown/*` → `~/.runtime/*`
- Rig runtime: `<rig>/.gastown/*` → `<rig>/.runtime/*`
- Rig config: `<rig>/.gastown/config.json` → `<rig>/settings/config.json`
- Namepool state: `namepool.json` → `namepool-state.json`

**New types:**
- MayorConfig: town-level behavioral config
- RigSettings: rig behavioral config (merge_queue, theme, namepool)
- RigConfig now identity-only (name, git_url, beads, created_at)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-22 01:22:43 -08:00

303 lines
7.5 KiB
Go

package cmd
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/polecat"
"github.com/steveyegge/gastown/internal/workspace"
)
var (
namepoolListFlag bool
namepoolThemeFlag string
)
var namepoolCmd = &cobra.Command{
Use: "namepool",
Short: "Manage polecat name pools",
Long: `Manage themed name pools for polecats in Gas Town.
By default, polecats get themed names from the Mad Max universe
(furiosa, nux, slit, etc.). You can change the theme or add custom names.
Examples:
gt namepool # Show current pool status
gt namepool --list # List available themes
gt namepool themes # Show theme names
gt namepool set minerals # Set theme to 'minerals'
gt namepool add ember # Add custom name to pool
gt namepool reset # Reset pool state`,
RunE: runNamepool,
}
var namepoolThemesCmd = &cobra.Command{
Use: "themes [theme]",
Short: "List available themes and their names",
RunE: runNamepoolThemes,
}
var namepoolSetCmd = &cobra.Command{
Use: "set <theme>",
Short: "Set the namepool theme for this rig",
Args: cobra.ExactArgs(1),
RunE: runNamepoolSet,
}
var namepoolAddCmd = &cobra.Command{
Use: "add <name>",
Short: "Add a custom name to the pool",
Args: cobra.ExactArgs(1),
RunE: runNamepoolAdd,
}
var namepoolResetCmd = &cobra.Command{
Use: "reset",
Short: "Reset the pool state (release all names)",
RunE: runNamepoolReset,
}
func init() {
rootCmd.AddCommand(namepoolCmd)
namepoolCmd.AddCommand(namepoolThemesCmd)
namepoolCmd.AddCommand(namepoolSetCmd)
namepoolCmd.AddCommand(namepoolAddCmd)
namepoolCmd.AddCommand(namepoolResetCmd)
namepoolCmd.Flags().BoolVarP(&namepoolListFlag, "list", "l", false, "List available themes")
}
func runNamepool(cmd *cobra.Command, args []string) error {
// List themes mode
if namepoolListFlag {
return runNamepoolThemes(cmd, nil)
}
// Show current pool status
rigName, rigPath := detectCurrentRigWithPath()
if rigName == "" {
return fmt.Errorf("not in a rig directory")
}
// Load pool
pool := polecat.NewNamePool(rigPath, rigName)
if err := pool.Load(); err != nil {
// Pool doesn't exist yet, show defaults
fmt.Printf("Rig: %s\n", rigName)
fmt.Printf("Theme: %s (default)\n", polecat.DefaultTheme)
fmt.Printf("Active polecats: 0\n")
fmt.Printf("Max pool size: %d\n", polecat.DefaultPoolSize)
return nil
}
// Show pool status
fmt.Printf("Rig: %s\n", rigName)
fmt.Printf("Theme: %s\n", pool.GetTheme())
fmt.Printf("Active polecats: %d\n", pool.ActiveCount())
activeNames := pool.ActiveNames()
if len(activeNames) > 0 {
fmt.Printf("In use: %s\n", strings.Join(activeNames, ", "))
}
// Check if configured
settingsPath := filepath.Join(rigPath, "settings", "config.json")
if settings, err := config.LoadRigSettings(settingsPath); err == nil && settings.Namepool != nil {
fmt.Printf("(configured in settings/config.json)\n")
}
return nil
}
func runNamepoolThemes(cmd *cobra.Command, args []string) error {
themes := polecat.ListThemes()
if len(args) == 0 {
// List all themes
fmt.Println("Available themes:")
for _, theme := range themes {
names, _ := polecat.GetThemeNames(theme)
fmt.Printf("\n %s (%d names):\n", theme, len(names))
// Show first 10 names
preview := names
if len(preview) > 10 {
preview = preview[:10]
}
fmt.Printf(" %s...\n", strings.Join(preview, ", "))
}
return nil
}
// Show specific theme names
theme := args[0]
names, err := polecat.GetThemeNames(theme)
if err != nil {
return fmt.Errorf("unknown theme: %s (available: %s)", theme, strings.Join(themes, ", "))
}
fmt.Printf("Theme: %s (%d names)\n\n", theme, len(names))
for i, name := range names {
if i > 0 && i%5 == 0 {
fmt.Println()
}
fmt.Printf(" %-12s", name)
}
fmt.Println()
return nil
}
func runNamepoolSet(cmd *cobra.Command, args []string) error {
theme := args[0]
// Validate theme
themes := polecat.ListThemes()
valid := false
for _, t := range themes {
if t == theme {
valid = true
break
}
}
if !valid {
return fmt.Errorf("unknown theme: %s (available: %s)", theme, strings.Join(themes, ", "))
}
// Get rig
rigName, rigPath := detectCurrentRigWithPath()
if rigName == "" {
return fmt.Errorf("not in a rig directory")
}
// Update pool
pool := polecat.NewNamePool(rigPath, rigName)
if err := pool.Load(); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("loading pool: %w", err)
}
if err := pool.SetTheme(theme); err != nil {
return err
}
if err := pool.Save(); err != nil {
return fmt.Errorf("saving pool: %w", err)
}
// Also save to rig config
if err := saveRigNamepoolConfig(rigPath, theme, nil); err != nil {
return fmt.Errorf("saving config: %w", err)
}
fmt.Printf("Theme '%s' set for rig '%s'\n", theme, rigName)
fmt.Printf("New polecats will use names from this theme.\n")
return nil
}
func runNamepoolAdd(cmd *cobra.Command, args []string) error {
name := args[0]
rigName, rigPath := detectCurrentRigWithPath()
if rigName == "" {
return fmt.Errorf("not in a rig directory")
}
// Load pool
pool := polecat.NewNamePool(rigPath, rigName)
if err := pool.Load(); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("loading pool: %w", err)
}
pool.AddCustomName(name)
if err := pool.Save(); err != nil {
return fmt.Errorf("saving pool: %w", err)
}
fmt.Printf("Added '%s' to the name pool\n", name)
return nil
}
func runNamepoolReset(cmd *cobra.Command, args []string) error {
rigName, rigPath := detectCurrentRigWithPath()
if rigName == "" {
return fmt.Errorf("not in a rig directory")
}
// Load pool
pool := polecat.NewNamePool(rigPath, rigName)
if err := pool.Load(); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("loading pool: %w", err)
}
pool.Reset()
if err := pool.Save(); err != nil {
return fmt.Errorf("saving pool: %w", err)
}
fmt.Printf("Pool reset for rig '%s'\n", rigName)
fmt.Printf("All names released and available for reuse.\n")
return nil
}
// detectCurrentRigWithPath determines the rig name and path from cwd.
func detectCurrentRigWithPath() (string, string) {
cwd, err := os.Getwd()
if err != nil {
return "", ""
}
townRoot, err := workspace.FindFromCwd()
if err != nil || townRoot == "" {
return "", ""
}
// Get path relative to town root
rel, err := filepath.Rel(townRoot, cwd)
if err != nil {
return "", ""
}
// Extract first path component (rig name)
parts := strings.Split(rel, string(filepath.Separator))
if len(parts) > 0 && parts[0] != "." && parts[0] != "mayor" && parts[0] != "deacon" {
return parts[0], filepath.Join(townRoot, parts[0])
}
return "", ""
}
// saveRigNamepoolConfig saves the namepool config to rig settings.
func saveRigNamepoolConfig(rigPath, theme string, customNames []string) error {
settingsPath := filepath.Join(rigPath, "settings", "config.json")
// Load existing settings or create new
var settings *config.RigSettings
settings, err := config.LoadRigSettings(settingsPath)
if err != nil {
// Create new settings if not found
if os.IsNotExist(err) || strings.Contains(err.Error(), "not found") {
settings = config.NewRigSettings()
} else {
return fmt.Errorf("loading settings: %w", err)
}
}
// Set namepool
settings.Namepool = &config.NamepoolConfig{
Style: theme,
Names: customNames,
}
// Save (creates directory if needed)
if err := config.SaveRigSettings(settingsPath, settings); err != nil {
return fmt.Errorf("saving settings: %w", err)
}
return nil
}