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>
This commit is contained in:
186
internal/cmd/rig_quick_add.go
Normal file
186
internal/cmd/rig_quick_add.go
Normal file
@@ -0,0 +1,186 @@
|
||||
// ABOUTME: Quick-add command for adding a repo to Gas Town with minimal friction.
|
||||
// ABOUTME: Used by shell hook for automatic "add to Gas Town?" prompts.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/style"
|
||||
"github.com/steveyegge/gastown/internal/workspace"
|
||||
)
|
||||
|
||||
var (
|
||||
quickAddUser string
|
||||
quickAddYes bool
|
||||
quickAddQuiet bool
|
||||
)
|
||||
|
||||
var rigQuickAddCmd = &cobra.Command{
|
||||
Use: "quick-add [path]",
|
||||
Short: "Quickly add current repo to Gas Town",
|
||||
Hidden: true,
|
||||
Long: `Quickly add a git repository to Gas Town with minimal interaction.
|
||||
|
||||
This command is designed for the shell hook's "Add to Gas Town?" prompt.
|
||||
It infers the rig name from the directory and git URL from the remote.
|
||||
|
||||
Examples:
|
||||
gt rig quick-add # Add current directory
|
||||
gt rig quick-add ~/Repos/myproject # Add specific path
|
||||
gt rig quick-add --yes # Non-interactive`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: runRigQuickAdd,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rigCmd.AddCommand(rigQuickAddCmd)
|
||||
rigQuickAddCmd.Flags().StringVar(&quickAddUser, "user", "", "Crew workspace name (default: $USER)")
|
||||
rigQuickAddCmd.Flags().BoolVar(&quickAddYes, "yes", false, "Non-interactive, assume yes")
|
||||
rigQuickAddCmd.Flags().BoolVar(&quickAddQuiet, "quiet", false, "Minimal output")
|
||||
}
|
||||
|
||||
func runRigQuickAdd(cmd *cobra.Command, args []string) error {
|
||||
targetPath := "."
|
||||
if len(args) > 0 {
|
||||
targetPath = args[0]
|
||||
}
|
||||
|
||||
absPath, err := filepath.Abs(targetPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving path: %w", err)
|
||||
}
|
||||
|
||||
if townRoot, err := workspace.Find(absPath); err == nil && townRoot != "" {
|
||||
return fmt.Errorf("already part of a Gas Town workspace: %s", townRoot)
|
||||
}
|
||||
|
||||
gitRoot, err := findGitRoot(absPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("not a git repository: %w", err)
|
||||
}
|
||||
|
||||
gitURL, err := findGitRemoteURL(gitRoot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("no git remote found: %w", err)
|
||||
}
|
||||
|
||||
rigName := sanitizeRigName(filepath.Base(gitRoot))
|
||||
|
||||
townRoot, err := findOrCreateTown()
|
||||
if err != nil {
|
||||
return fmt.Errorf("finding Gas Town: %w", err)
|
||||
}
|
||||
|
||||
rigPath := filepath.Join(townRoot, rigName)
|
||||
if _, err := os.Stat(rigPath); err == nil {
|
||||
return fmt.Errorf("rig %q already exists in %s", rigName, townRoot)
|
||||
}
|
||||
|
||||
originalName := filepath.Base(gitRoot)
|
||||
if rigName != originalName && !quickAddQuiet {
|
||||
fmt.Printf("Note: Using %q as rig name (sanitized from %q)\n", rigName, originalName)
|
||||
}
|
||||
|
||||
if !quickAddQuiet {
|
||||
fmt.Printf("Adding %s to Gas Town...\n", style.Bold.Render(rigName))
|
||||
fmt.Printf(" Repository: %s\n", gitURL)
|
||||
fmt.Printf(" Town: %s\n", townRoot)
|
||||
}
|
||||
|
||||
addArgs := []string{"rig", "add", rigName, gitURL}
|
||||
addCmd := exec.Command("gt", addArgs...)
|
||||
addCmd.Dir = townRoot
|
||||
addCmd.Stdout = os.Stdout
|
||||
addCmd.Stderr = os.Stderr
|
||||
if err := addCmd.Run(); err != nil {
|
||||
fmt.Printf("\n%s Failed to add rig. You can try manually:\n", style.Warning.Render("⚠"))
|
||||
fmt.Printf(" cd %s && gt rig add %s %s\n", townRoot, rigName, gitURL)
|
||||
return fmt.Errorf("gt rig add failed: %w", err)
|
||||
}
|
||||
|
||||
user := quickAddUser
|
||||
if user == "" {
|
||||
user = os.Getenv("USER")
|
||||
}
|
||||
if user == "" {
|
||||
user = "default"
|
||||
}
|
||||
|
||||
if !quickAddQuiet {
|
||||
fmt.Printf("\nCreating crew workspace for %s...\n", user)
|
||||
}
|
||||
|
||||
crewArgs := []string{"crew", "add", user, "--rig", rigName}
|
||||
crewCmd := exec.Command("gt", crewArgs...)
|
||||
crewCmd.Dir = filepath.Join(townRoot, rigName)
|
||||
crewCmd.Stdout = os.Stdout
|
||||
crewCmd.Stderr = os.Stderr
|
||||
if err := crewCmd.Run(); err != nil {
|
||||
fmt.Printf(" %s Could not create crew workspace: %v\n", style.Dim.Render("⚠"), err)
|
||||
fmt.Printf(" Run manually: cd %s && gt crew add %s --rig %s\n", filepath.Join(townRoot, rigName), user, rigName)
|
||||
}
|
||||
|
||||
crewPath := filepath.Join(townRoot, rigName, "crew", user)
|
||||
if !quickAddQuiet {
|
||||
fmt.Printf("\n%s Added to Gas Town!\n", style.Success.Render("✓"))
|
||||
fmt.Printf("\nYour workspace: %s\n", style.Bold.Render(crewPath))
|
||||
}
|
||||
|
||||
fmt.Printf("GT_CREW_PATH=%s\n", crewPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findGitRoot(path string) (string, error) {
|
||||
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
|
||||
cmd.Dir = path
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
}
|
||||
|
||||
func findGitRemoteURL(gitRoot string) (string, error) {
|
||||
cmd := exec.Command("git", "remote", "get-url", "origin")
|
||||
cmd.Dir = gitRoot
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
}
|
||||
|
||||
func sanitizeRigName(name string) string {
|
||||
name = strings.ReplaceAll(name, "-", "_")
|
||||
name = strings.ReplaceAll(name, ".", "_")
|
||||
name = strings.ReplaceAll(name, " ", "_")
|
||||
return name
|
||||
}
|
||||
|
||||
func findOrCreateTown() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
candidates := []string{
|
||||
filepath.Join(home, "gt"),
|
||||
filepath.Join(home, "gastown"),
|
||||
}
|
||||
|
||||
for _, path := range candidates {
|
||||
mayorDir := filepath.Join(path, "mayor")
|
||||
if _, err := os.Stat(mayorDir); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no Gas Town found - run 'gt install ~/gt' first")
|
||||
}
|
||||
Reference in New Issue
Block a user