Skip to content

Commit 14bf13d

Browse files
committed
feat: add tabbed package selector UI with progress bar
- Reorganize packages into 13 categories (Essential, Git, Development, etc.) - Add interactive tabbed multi-select UI with Tab navigation - Add real-time progress bar during installation - Allow customization of packages after preset selection
1 parent 3438cb4 commit 14bf13d

6 files changed

Lines changed: 674 additions & 34 deletions

File tree

internal/brew/brew.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,57 @@ func InstallCask(packages []string, dryRun bool) error {
5252
return cmd.Run()
5353
}
5454

55+
func InstallWithProgress(cliPkgs, caskPkgs []string, dryRun bool) error {
56+
total := len(cliPkgs) + len(caskPkgs)
57+
if total == 0 {
58+
return nil
59+
}
60+
61+
if dryRun {
62+
ui.Info("Would install packages:")
63+
for _, p := range cliPkgs {
64+
fmt.Printf(" brew install %s\n", p)
65+
}
66+
for _, p := range caskPkgs {
67+
fmt.Printf(" brew install --cask %s\n", p)
68+
}
69+
return nil
70+
}
71+
72+
progress := ui.NewProgressTracker(total)
73+
var failed []string
74+
75+
for _, pkg := range cliPkgs {
76+
progress.SetCurrent(pkg)
77+
cmd := exec.Command("brew", "install", pkg)
78+
cmd.Stdout = nil
79+
cmd.Stderr = nil
80+
if err := cmd.Run(); err != nil {
81+
failed = append(failed, pkg)
82+
}
83+
progress.Complete(pkg)
84+
}
85+
86+
for _, pkg := range caskPkgs {
87+
progress.SetCurrent(pkg)
88+
cmd := exec.Command("brew", "install", "--cask", pkg)
89+
cmd.Stdout = nil
90+
cmd.Stderr = nil
91+
if err := cmd.Run(); err != nil {
92+
failed = append(failed, pkg)
93+
}
94+
progress.Complete(pkg)
95+
}
96+
97+
progress.Finish()
98+
99+
if len(failed) > 0 {
100+
ui.Muted(fmt.Sprintf("Note: %d packages failed to install: %v", len(failed), failed))
101+
}
102+
103+
return nil
104+
}
105+
55106
func Update(dryRun bool) error {
56107
if dryRun {
57108
ui.Info("Would run: brew update && brew upgrade")

internal/config/config.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
package config
22

33
type Config struct {
4-
Preset string
5-
Silent bool
6-
DryRun bool
7-
Update bool
8-
Rollback bool
9-
Resume bool
10-
Shell string
11-
Macos string
12-
Dotfiles string
13-
GitName string
14-
GitEmail string
4+
Preset string
5+
Silent bool
6+
DryRun bool
7+
Update bool
8+
Rollback bool
9+
Resume bool
10+
Shell string
11+
Macos string
12+
Dotfiles string
13+
GitName string
14+
GitEmail string
15+
SelectedPkgs map[string]bool
1516
}
1617

1718
type Preset struct {

internal/config/packages.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package config
2+
3+
type Package struct {
4+
Name string
5+
Description string
6+
IsCask bool
7+
}
8+
9+
type Category struct {
10+
Name string
11+
Icon string
12+
Packages []Package
13+
}
14+
15+
var Categories = []Category{
16+
{
17+
Name: "Essential",
18+
Icon: "⚡",
19+
Packages: []Package{
20+
{Name: "curl", Description: "Transfer data with URLs"},
21+
{Name: "wget", Description: "Network downloader"},
22+
{Name: "jq", Description: "JSON processor"},
23+
{Name: "yq", Description: "YAML processor"},
24+
{Name: "ripgrep", Description: "Fast grep alternative"},
25+
{Name: "fd", Description: "Fast find alternative"},
26+
{Name: "bat", Description: "Cat with syntax highlighting"},
27+
{Name: "eza", Description: "Modern ls replacement"},
28+
{Name: "fzf", Description: "Fuzzy finder"},
29+
{Name: "zoxide", Description: "Smarter cd command"},
30+
{Name: "htop", Description: "Interactive process viewer"},
31+
{Name: "btop", Description: "Resource monitor"},
32+
{Name: "tree", Description: "Directory tree viewer"},
33+
{Name: "tldr", Description: "Simplified man pages"},
34+
},
35+
},
36+
{
37+
Name: "Git & GitHub",
38+
Icon: "🔀",
39+
Packages: []Package{
40+
{Name: "gh", Description: "GitHub CLI"},
41+
{Name: "git-delta", Description: "Better git diff"},
42+
{Name: "lazygit", Description: "Terminal UI for git"},
43+
{Name: "stow", Description: "Symlink farm manager"},
44+
},
45+
},
46+
{
47+
Name: "Development",
48+
Icon: "🛠",
49+
Packages: []Package{
50+
{Name: "node", Description: "JavaScript runtime"},
51+
{Name: "go", Description: "Go programming language"},
52+
{Name: "rustup", Description: "Rust toolchain"},
53+
{Name: "python", Description: "Python 3"},
54+
{Name: "uv", Description: "Fast Python package manager"},
55+
{Name: "deno", Description: "Secure JS/TS runtime"},
56+
{Name: "bun", Description: "Fast JS runtime & bundler"},
57+
{Name: "pnpm", Description: "Fast npm alternative"},
58+
{Name: "tmux", Description: "Terminal multiplexer"},
59+
{Name: "neovim", Description: "Modern Vim"},
60+
},
61+
},
62+
{
63+
Name: "DevOps",
64+
Icon: "☁️",
65+
Packages: []Package{
66+
{Name: "docker", Description: "Container runtime"},
67+
{Name: "docker-compose", Description: "Multi-container Docker"},
68+
{Name: "kubectl", Description: "Kubernetes CLI"},
69+
{Name: "helm", Description: "Kubernetes package manager"},
70+
{Name: "k9s", Description: "Kubernetes TUI"},
71+
{Name: "terraform", Description: "Infrastructure as code"},
72+
{Name: "awscli", Description: "AWS CLI"},
73+
{Name: "argocd", Description: "GitOps for Kubernetes"},
74+
},
75+
},
76+
{
77+
Name: "Database",
78+
Icon: "🗄",
79+
Packages: []Package{
80+
{Name: "sqlite", Description: "Embedded SQL database"},
81+
{Name: "postgresql", Description: "PostgreSQL client"},
82+
{Name: "redis", Description: "Redis CLI"},
83+
{Name: "duckdb", Description: "Analytical SQL database"},
84+
{Name: "mysql", Description: "MySQL client"},
85+
},
86+
},
87+
{
88+
Name: "AI & ML",
89+
Icon: "🤖",
90+
Packages: []Package{
91+
{Name: "ollama", Description: "Run LLMs locally"},
92+
{Name: "llm", Description: "CLI for LLMs"},
93+
},
94+
},
95+
{
96+
Name: "Editors",
97+
Icon: "📝",
98+
Packages: []Package{
99+
{Name: "visual-studio-code", Description: "VS Code", IsCask: true},
100+
{Name: "cursor", Description: "AI-powered editor", IsCask: true},
101+
{Name: "zed", Description: "High-performance editor", IsCask: true},
102+
},
103+
},
104+
{
105+
Name: "Browsers",
106+
Icon: "🌐",
107+
Packages: []Package{
108+
{Name: "google-chrome", Description: "Chrome browser", IsCask: true},
109+
{Name: "arc", Description: "Arc browser", IsCask: true},
110+
{Name: "firefox", Description: "Firefox browser", IsCask: true},
111+
{Name: "microsoft-edge", Description: "Edge browser", IsCask: true},
112+
},
113+
},
114+
{
115+
Name: "Terminals",
116+
Icon: "💻",
117+
Packages: []Package{
118+
{Name: "warp", Description: "Modern terminal", IsCask: true},
119+
{Name: "iterm2", Description: "Terminal emulator", IsCask: true},
120+
{Name: "alacritty", Description: "GPU-accelerated terminal", IsCask: true},
121+
},
122+
},
123+
{
124+
Name: "Productivity",
125+
Icon: "📋",
126+
Packages: []Package{
127+
{Name: "raycast", Description: "Launcher & productivity", IsCask: true},
128+
{Name: "maccy", Description: "Clipboard manager", IsCask: true},
129+
{Name: "notion", Description: "Notes & docs", IsCask: true},
130+
{Name: "obsidian", Description: "Knowledge base", IsCask: true},
131+
{Name: "slack", Description: "Team communication", IsCask: true},
132+
{Name: "discord", Description: "Community chat", IsCask: true},
133+
{Name: "telegram", Description: "Messaging", IsCask: true},
134+
},
135+
},
136+
{
137+
Name: "Utilities",
138+
Icon: "🔧",
139+
Packages: []Package{
140+
{Name: "stats", Description: "System monitor in menubar", IsCask: true},
141+
{Name: "scroll-reverser", Description: "Reverse scroll direction", IsCask: true},
142+
{Name: "rectangle", Description: "Window management", IsCask: true},
143+
{Name: "aldente", Description: "Battery charge limiter", IsCask: true},
144+
{Name: "keka", Description: "File archiver", IsCask: true},
145+
{Name: "iina", Description: "Modern media player", IsCask: true},
146+
},
147+
},
148+
{
149+
Name: "Design",
150+
Icon: "🎨",
151+
Packages: []Package{
152+
{Name: "figma", Description: "Design tool", IsCask: true},
153+
{Name: "sketch", Description: "Vector design", IsCask: true},
154+
{Name: "imageoptim", Description: "Image compression", IsCask: true},
155+
},
156+
},
157+
{
158+
Name: "API & Debug",
159+
Icon: "🔍",
160+
Packages: []Package{
161+
{Name: "httpie", Description: "HTTP client"},
162+
{Name: "postman", Description: "API platform", IsCask: true},
163+
{Name: "proxyman", Description: "HTTP debugging", IsCask: true},
164+
{Name: "orbstack", Description: "Docker & Linux VMs", IsCask: true},
165+
},
166+
},
167+
}
168+
169+
func GetPackagesForPreset(presetName string) map[string]bool {
170+
selected := make(map[string]bool)
171+
172+
preset, ok := Presets[presetName]
173+
if !ok {
174+
return selected
175+
}
176+
177+
for _, pkg := range preset.CLI {
178+
selected[pkg] = true
179+
}
180+
for _, pkg := range preset.Cask {
181+
selected[pkg] = true
182+
}
183+
184+
return selected
185+
}
186+
187+
func GetAllPackageNames() []string {
188+
var names []string
189+
for _, cat := range Categories {
190+
for _, pkg := range cat.Packages {
191+
names = append(names, pkg.Name)
192+
}
193+
}
194+
return names
195+
}

0 commit comments

Comments
 (0)