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>
172 lines
4.9 KiB
Go
172 lines
4.9 KiB
Go
// ABOUTME: Command to completely uninstall Gas Town from the system.
|
|
// ABOUTME: Removes shell integration, wrappers, state, and optionally workspace.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/steveyegge/gastown/internal/shell"
|
|
"github.com/steveyegge/gastown/internal/state"
|
|
"github.com/steveyegge/gastown/internal/style"
|
|
"github.com/steveyegge/gastown/internal/wrappers"
|
|
)
|
|
|
|
var (
|
|
uninstallWorkspace bool
|
|
uninstallForce bool
|
|
)
|
|
|
|
var uninstallCmd = &cobra.Command{
|
|
Use: "uninstall",
|
|
GroupID: GroupConfig,
|
|
Short: "Remove Gas Town from the system",
|
|
Long: `Completely remove Gas Town from the system.
|
|
|
|
By default, removes:
|
|
- Shell integration (~/.zshrc or ~/.bashrc)
|
|
- Wrapper scripts (~/bin/gt-codex, ~/bin/gt-opencode)
|
|
- State directory (~/.local/state/gastown/)
|
|
- Config directory (~/.config/gastown/)
|
|
- Cache directory (~/.cache/gastown/)
|
|
|
|
The workspace (e.g., ~/gt) is NOT removed unless --workspace is specified.
|
|
|
|
Use --force to skip confirmation prompts.
|
|
|
|
Examples:
|
|
gt uninstall # Remove Gas Town, keep workspace
|
|
gt uninstall --workspace # Also remove workspace directory
|
|
gt uninstall --force # Skip confirmation`,
|
|
RunE: runUninstall,
|
|
}
|
|
|
|
func init() {
|
|
uninstallCmd.Flags().BoolVar(&uninstallWorkspace, "workspace", false,
|
|
"Also remove the workspace directory (DESTRUCTIVE)")
|
|
uninstallCmd.Flags().BoolVarP(&uninstallForce, "force", "f", false,
|
|
"Skip confirmation prompts")
|
|
rootCmd.AddCommand(uninstallCmd)
|
|
}
|
|
|
|
func runUninstall(cmd *cobra.Command, args []string) error {
|
|
if !uninstallForce {
|
|
fmt.Println("This will remove Gas Town from your system.")
|
|
fmt.Println()
|
|
fmt.Println("The following will be removed:")
|
|
fmt.Printf(" • Shell integration (%s)\n", shell.RCFilePath(shell.DetectShell()))
|
|
fmt.Printf(" • Wrapper scripts (%s)\n", wrappers.BinDir())
|
|
fmt.Printf(" • State directory (%s)\n", state.StateDir())
|
|
fmt.Printf(" • Config directory (%s)\n", state.ConfigDir())
|
|
fmt.Printf(" • Cache directory (%s)\n", state.CacheDir())
|
|
|
|
if uninstallWorkspace {
|
|
fmt.Println()
|
|
fmt.Printf(" %s WORKSPACE WILL BE DELETED\n", style.Warning.Render("⚠"))
|
|
fmt.Println(" This cannot be undone!")
|
|
}
|
|
|
|
fmt.Println()
|
|
fmt.Print("Continue? [y/N] ")
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
response, _ := reader.ReadString('\n')
|
|
response = strings.TrimSpace(strings.ToLower(response))
|
|
|
|
if response != "y" && response != "yes" {
|
|
fmt.Println("Aborted.")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var errors []string
|
|
|
|
fmt.Println()
|
|
fmt.Println("Removing Gas Town...")
|
|
|
|
if err := shell.Remove(); err != nil {
|
|
errors = append(errors, fmt.Sprintf("shell integration: %v", err))
|
|
} else {
|
|
fmt.Printf(" %s Removed shell integration\n", style.Success.Render("✓"))
|
|
}
|
|
|
|
if err := wrappers.Remove(); err != nil {
|
|
errors = append(errors, fmt.Sprintf("wrapper scripts: %v", err))
|
|
} else {
|
|
fmt.Printf(" %s Removed wrapper scripts\n", style.Success.Render("✓"))
|
|
}
|
|
|
|
if err := os.RemoveAll(state.StateDir()); err != nil && !os.IsNotExist(err) {
|
|
errors = append(errors, fmt.Sprintf("state directory: %v", err))
|
|
} else {
|
|
fmt.Printf(" %s Removed state directory\n", style.Success.Render("✓"))
|
|
}
|
|
|
|
if err := os.RemoveAll(state.ConfigDir()); err != nil && !os.IsNotExist(err) {
|
|
errors = append(errors, fmt.Sprintf("config directory: %v", err))
|
|
} else {
|
|
fmt.Printf(" %s Removed config directory\n", style.Success.Render("✓"))
|
|
}
|
|
|
|
if err := os.RemoveAll(state.CacheDir()); err != nil && !os.IsNotExist(err) {
|
|
errors = append(errors, fmt.Sprintf("cache directory: %v", err))
|
|
} else {
|
|
fmt.Printf(" %s Removed cache directory\n", style.Success.Render("✓"))
|
|
}
|
|
|
|
if uninstallWorkspace {
|
|
workspaceDir := findWorkspaceForUninstall()
|
|
if workspaceDir != "" {
|
|
if err := os.RemoveAll(workspaceDir); err != nil {
|
|
errors = append(errors, fmt.Sprintf("workspace: %v", err))
|
|
} else {
|
|
fmt.Printf(" %s Removed workspace: %s\n", style.Success.Render("✓"), workspaceDir)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(errors) > 0 {
|
|
fmt.Println()
|
|
fmt.Printf("%s Some components could not be removed:\n", style.Warning.Render("⚠"))
|
|
for _, e := range errors {
|
|
fmt.Printf(" • %s\n", e)
|
|
}
|
|
return fmt.Errorf("uninstall incomplete")
|
|
}
|
|
|
|
fmt.Println()
|
|
fmt.Printf("%s Gas Town has been uninstalled\n", style.Success.Render("✓"))
|
|
fmt.Println()
|
|
fmt.Println("To reinstall, run:")
|
|
fmt.Printf(" %s\n", style.Dim.Render("go install github.com/steveyegge/gastown/cmd/gt@latest"))
|
|
fmt.Printf(" %s\n", style.Dim.Render("gt install ~/gt --shell"))
|
|
|
|
return nil
|
|
}
|
|
|
|
func findWorkspaceForUninstall() string {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|