Skip to content

Commit 14146ef

Browse files
committed
neovim via brew, bootstrap prereqs, deep-removal script [minor-release]
Move neovim from custom GitHub tarball installer to brew formula. Remove git/curl/zsh from brew lists (now bootstrap prereqs via apt on Linux, built-in on macOS). Add deep-removal.sh that wipes CPS, brew packages, and OMZ while preserving ~/.zsh_history and Homebrew itself. Replace migrate-v1.3.0.sh with the new script. Update README with bootstrap section and prerequisite install one-liners.
1 parent ed4258b commit 14146ef

6 files changed

Lines changed: 108 additions & 263 deletions

File tree

README.md

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,22 @@ A single Go binary (`cps`) that sets up and manages a complete CLI development e
1111

1212
## Prerequisites
1313

14-
| Requirement | Why |
14+
### Bootstrap (Linux only)
15+
16+
macOS has these out of the box (git via Xcode CLT, zsh built-in, curl built-in). On Linux:
17+
18+
```bash
19+
sudo apt install git curl zsh build-essential
20+
```
21+
22+
### Install these (both platforms)
23+
24+
| Requirement | One-line install |
1525
|---|---|
16-
| [Oh My Zsh](https://ohmyz.sh/) | Shell framework — `cps init` will not run without it |
17-
| Git | Used to clone plugins and configs |
18-
| [Homebrew](https://brew.sh/) | System package installs (both Linux and macOS) — `cps` installs all system/cloud CLI packages via brew |
26+
| [Oh My Zsh](https://ohmyz.sh/) | `sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"` |
27+
| [Homebrew](https://brew.sh/) | `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` |
28+
29+
Both are required — `cps init` won't run without them. CPS uses brew for all system and cloud CLI packages.
1930

2031
**Recommended:**
2132

@@ -144,15 +155,8 @@ CPS uses a modular fragment system instead of a monolithic `.zshrc`:
144155

145156
## Deep Removal
146157

147-
```bash
148-
# CPS directories
149-
rm -rf $HOME/shell $HOME/.tmux $HOME/.config/nvim $HOME/.config/cps
158+
Run the included script to wipe CPS, CPS-installed brew packages, and Oh My Zsh. Homebrew itself and `~/.zsh_history` are preserved so you can reinstall cleanly without rebuilding your shell history or re-bootstrapping brew.
150159

151-
# Configs
152-
rm -f $HOME/.tmux.conf $HOME/.zshrc $HOME/.aerospace.toml $HOME/.config/kitty/kitty.conf $HOME/.config/kitty/current-theme.conf
153-
rm -rf $HOME/.local/share/nvim $HOME/.oh-my-zsh
154-
rm -f $HOME/.local/bin/cps
155-
156-
# Uninstall brew-managed system and cloud packages (optional)
157-
brew uninstall awscli azure-cli gcloud-cli
160+
```bash
161+
./deep-removal.sh
158162
```

deep-removal.sh

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env bash
2+
# Deep removal of CPS + CPS-installed brew packages + Oh My Zsh.
3+
# Homebrew itself is preserved. ~/.zsh_history is preserved.
4+
#
5+
# After running this, you can start fresh by:
6+
# 1. Reinstalling Oh My Zsh (Homebrew stays)
7+
# 2. Reinstalling the cps binary
8+
# 3. Running `cps init`
9+
10+
echo "CPS deep removal"
11+
echo ""
12+
echo "Will remove:"
13+
echo " - cps binary (~/.local/bin/cps)"
14+
echo " - CPS directories (~/shell, ~/.tmux, ~/.config/nvim, ~/.config/cps, ~/.local/share/nvim)"
15+
echo " - CPS-deployed configs (.zshrc, .tmux.conf, .aerospace.toml, kitty configs)"
16+
echo " - Oh My Zsh (~/.oh-my-zsh)"
17+
echo " - Brew packages installed by CPS (neovim, cloud CLIs, core/dev/network/media tools)"
18+
echo ""
19+
echo "Will preserve:"
20+
echo " - ~/.zsh_history"
21+
echo " - Homebrew itself"
22+
echo " - System tools (git, curl, zsh from apt or macOS built-in)"
23+
echo ""
24+
read -rp "Continue? [y/N] " ans
25+
case "$ans" in
26+
[yY]|[yY][eE][sS]) ;;
27+
*) echo "aborted"; exit 0 ;;
28+
esac
29+
30+
echo ""
31+
echo "==> removing cps binary"
32+
rm -f "$HOME/.local/bin/cps"
33+
34+
echo "==> removing CPS-managed directories"
35+
rm -rf "$HOME/shell"
36+
rm -rf "$HOME/.tmux"
37+
rm -rf "$HOME/.config/nvim"
38+
rm -rf "$HOME/.config/cps"
39+
rm -rf "$HOME/.local/share/nvim"
40+
41+
echo "==> removing CPS-deployed configs"
42+
rm -f "$HOME/.tmux.conf"
43+
rm -f "$HOME/.zshrc"
44+
rm -f "$HOME/.aerospace.toml"
45+
rm -f "$HOME/.config/kitty/kitty.conf"
46+
rm -f "$HOME/.config/kitty/current-theme.conf"
47+
48+
if command -v brew >/dev/null 2>&1; then
49+
echo "==> uninstalling CPS-installed brew formulas"
50+
brew uninstall --ignore-dependencies \
51+
wget zip unzip file tmux htop neovim \
52+
cmake gcc make ninja gettext \
53+
nmap openssl ffmpeg \
54+
awscli azure-cli \
55+
2>/dev/null || true
56+
57+
echo "==> uninstalling CPS-installed brew casks"
58+
brew uninstall --cask --force gcloud-cli 2>/dev/null || true
59+
brew uninstall --cask --force nikitabobko/tap/aerospace 2>/dev/null || true
60+
else
61+
echo "==> brew not found, skipping brew package uninstall"
62+
fi
63+
64+
echo "==> removing Oh My Zsh"
65+
rm -rf "$HOME/.oh-my-zsh"
66+
67+
echo ""
68+
echo "done."
69+
echo "preserved: ~/.zsh_history, Homebrew itself"
70+
echo ""
71+
echo "to start fresh:"
72+
echo " 1. reinstall Oh My Zsh:"
73+
echo " sh -c \"\$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\""
74+
echo " 2. reinstall cps:"
75+
echo " see https://github.com/tanq16/cli-productivity-suite#install"
76+
echo " 3. cps init"

internal/installer/language_runtime.go

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ type LanguageRuntimeInstaller struct{}
2222

2323
func (l *LanguageRuntimeInstaller) Install(tool *registry.Tool, p platform.Platform, gh *github.Client, st *state.State) Result {
2424
switch tool.Name {
25-
case "neovim":
26-
return l.installNeovim(p, gh, st)
2725
case "go-sdk":
2826
return l.installGo(p, st)
2927
case "uv":
@@ -41,67 +39,6 @@ func (l *LanguageRuntimeInstaller) Install(tool *registry.Tool, p platform.Platf
4139
}
4240
}
4341

44-
func (l *LanguageRuntimeInstaller) installNeovim(p platform.Platform, gh *github.Client, st *state.State) Result {
45-
var archStr string
46-
switch p.Arch {
47-
case platform.AMD64:
48-
archStr = "x86_64"
49-
case platform.ARM64:
50-
archStr = "arm64"
51-
}
52-
53-
var osStr string
54-
switch p.OS {
55-
case platform.Darwin:
56-
osStr = "macos"
57-
default:
58-
osStr = "linux"
59-
}
60-
61-
url := fmt.Sprintf("https://github.com/neovim/neovim/releases/download/stable/nvim-%s-%s.tar.gz", osStr, archStr)
62-
63-
// Temp dir inside p.ShellDir() so os.Rename stays on the same filesystem (avoids EXDEV on Linux tmpfs /tmp).
64-
tmpDir, err := os.MkdirTemp(p.ShellDir(), "cps-neovim-*")
65-
if err != nil {
66-
return Result{Tool: "neovim", Err: err}
67-
}
68-
defer os.RemoveAll(tmpDir)
69-
70-
tarPath := filepath.Join(tmpDir, "nvim.tar.gz")
71-
if err := DownloadToFile(url, tarPath); err != nil {
72-
return Result{Tool: "neovim", Err: fmt.Errorf("download failed: %w", err)}
73-
}
74-
75-
if p.OS == platform.Darwin {
76-
xattrCmd := exec.Command("xattr", "-c", tarPath)
77-
utils.RunCmd(xattrCmd)
78-
}
79-
80-
if err := ExtractTarGz(tarPath, tmpDir); err != nil {
81-
return Result{Tool: "neovim", Err: fmt.Errorf("extract failed: %w", err)}
82-
}
83-
84-
extractedDir := filepath.Join(tmpDir, fmt.Sprintf("nvim-%s-%s", osStr, archStr))
85-
nvimDir := filepath.Join(p.ShellDir(), "nvim")
86-
os.RemoveAll(nvimDir)
87-
if err := os.Rename(extractedDir, nvimDir); err != nil {
88-
return Result{Tool: "neovim", Err: fmt.Errorf("move to %s failed: %w", nvimDir, err)}
89-
}
90-
91-
symlinkPath := filepath.Join(p.ShellExecDir(), "nvim")
92-
os.Remove(symlinkPath)
93-
if err := os.Symlink(filepath.Join(nvimDir, "bin", "nvim"), symlinkPath); err != nil {
94-
return Result{Tool: "neovim", Err: fmt.Errorf("symlink failed: %w", err)}
95-
}
96-
97-
version := "stable"
98-
if release, err := gh.LatestRelease("neovim/neovim"); err == nil {
99-
version = release.TagName
100-
}
101-
st.SetToolVersion("neovim", version)
102-
return Result{Tool: "neovim", Version: version}
103-
}
104-
10542
func (l *LanguageRuntimeInstaller) installGo(p platform.Platform, st *state.State) Result {
10643
type goDL struct {
10744
Version string `json:"version"`

internal/registry/manifest.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,18 +163,19 @@ var AllTools = []Tool{
163163
{
164164
Name: "core-utils", Kind: SystemPackage, Category: System,
165165
Description: "Core system utilities",
166-
BrewPkgs: []string{"git", "wget", "curl", "zip", "unzip", "file"},
166+
BrewPkgs: []string{"wget", "zip", "unzip", "file"},
167167
},
168168
{
169169
Name: "shell-base", Kind: SystemPackage, Category: System,
170170
Description: "Shell and terminal essentials",
171-
BrewPkgs: []string{"tmux", "zsh", "htop"},
171+
BrewPkgs: []string{"tmux", "htop"},
172172
},
173173

174-
// ========== Language Runtimes (base only) ==========
174+
// ========== Neovim (via brew) ==========
175175
{
176-
Name: "neovim", Kind: LanguageRuntime, Category: Runtime,
176+
Name: "neovim", Kind: SystemPackage, Category: System,
177177
Description: "Neovim text editor (0.11+ for NvChad)",
178+
BrewPkgs: []string{"neovim"},
178179
},
179180

180181
// ========== Config Files ==========

internal/runner/runner.go

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -84,27 +84,20 @@ func Init(ghToken string) {
8484
}
8585
st.Save()
8686

87-
// Phase 5: Neovim
88-
neovim := filterByName(reg.ByKind(registry.LanguageRuntime), "neovim")
89-
if runPhase("Phase 5: Neovim", neovim, p, gh, st) {
90-
hadErrors = true
91-
}
92-
st.Save()
93-
94-
// Phase 6: Shell plugins (base only, exclude extension tools)
87+
// Phase 5: Shell plugins (base only, exclude extension tools)
9588
disableOMZSlowPaste(p)
96-
if runPhase("Phase 6: Shell plugins", filterBaseTools(reg.ByKind(registry.ShellPlugin)), p, gh, st) {
89+
if runPhase("Phase 5: Shell plugins", filterBaseTools(reg.ByKind(registry.ShellPlugin)), p, gh, st) {
9790
hadErrors = true
9891
}
9992
st.Save()
10093

101-
// Phase 7: Config files
102-
if runPhase("Phase 7: Config files", filterPlatformTools(reg.ByKind(registry.ConfigFile), p), p, gh, st) {
94+
// Phase 6: Config files
95+
if runPhase("Phase 6: Config files", filterPlatformTools(reg.ByKind(registry.ConfigFile), p), p, gh, st) {
10396
hadErrors = true
10497
}
10598
st.Save()
10699

107-
// Phase 8: Post-install tasks
100+
// Phase 7: Post-install tasks
108101
runPostInstall(p)
109102

110103
st.LastInit = time.Now()
@@ -222,7 +215,7 @@ func runPhase(phaseName string, tools []registry.Tool, p platform.Platform, gh *
222215
}
223216

224217
func runPostInstall(p platform.Platform) {
225-
utils.PrintRunning("(Running) Phase 8: Post-install tasks")
218+
utils.PrintRunning("(Running) Phase 7: Post-install tasks")
226219
var lineCount int
227220
var errors []jobResult
228221

@@ -237,8 +230,7 @@ func runPostInstall(p platform.Platform) {
237230
}
238231
}
239232

240-
nvimBin := filepath.Join(p.ShellExecDir(), "nvim")
241-
if _, err := os.Stat(nvimBin); err == nil {
233+
if nvimBin, err := exec.LookPath("nvim"); err == nil {
242234
utils.PrintIndentedRunning("nvchad-setup: running")
243235
lineCount++
244236
nvimCmd := exec.Command(nvimBin, "--headless", "+MasonInstallAll", "+Lazy sync", "+qa")
@@ -251,12 +243,12 @@ func runPostInstall(p platform.Platform) {
251243

252244
utils.ClearLines(lineCount + 1) // sub-lines + running header
253245
if len(errors) > 0 {
254-
utils.PrintError("Phase 8: partially completed with errors", nil)
246+
utils.PrintError("Phase 7: partially completed with errors", nil)
255247
for _, e := range errors {
256248
utils.PrintIndentedError(e.name, e.err)
257249
}
258250
} else {
259-
utils.PrintInfo("Phase 8: Post-install tasks")
251+
utils.PrintInfo("Phase 7: Post-install tasks")
260252
}
261253
}
262254

@@ -363,20 +355,6 @@ func filterBaseTools(tools []registry.Tool) []registry.Tool {
363355
return result
364356
}
365357

366-
func filterByName(tools []registry.Tool, names ...string) []registry.Tool {
367-
nameSet := make(map[string]bool, len(names))
368-
for _, n := range names {
369-
nameSet[n] = true
370-
}
371-
var result []registry.Tool
372-
for _, t := range tools {
373-
if nameSet[t.Name] {
374-
result = append(result, t)
375-
}
376-
}
377-
return result
378-
}
379-
380358
func isOwnTool(repo string) bool {
381359
return strings.HasPrefix(repo, "Tanq16/")
382360
}

0 commit comments

Comments
 (0)