updates to add AI packages and an opt out of AI flag #17

Merged
JMR-dev merged 1 commits from feat-add-AI-cli-packages into main 2026-05-25 17:11:32 +00:00
5 changed files with 88 additions and 2 deletions
+2 -1
View File
@@ -4,7 +4,7 @@ A Go-based bootstrap tool that installs a development environment across
macOS, Debian/Ubuntu, RHEL/Fedora, and Arch Linux on `x86_64` and `aarch64`.
It installs system packages, optional Flatpak GUI apps, and a set of custom
third-party tools (Go, Neovim, Zig, NVM, pyenv, oh-my-zsh, Firecracker on
Linux).
Linux, agy, claude, codex, and copilot).
## Install
@@ -40,5 +40,6 @@ Flags:
headless: both are skipped.
- `--no-vm` — macOS only: skip provisioning the Fedora-on-QEMU/VirtualBox VM
that backs the `firecracker()` zsh wrapper.
- `--no-ai` — skip installation of AI/LLM CLI tools (agy, claude, codex, copilot).
Package lists live in `packages.go`. Edit and rebuild.
+49 -1
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
@@ -54,6 +55,7 @@ var defaultInstallPaths = map[string]string{
"pyenv": "~/.pyenv",
"neovim": "/usr/local/bin/nvim",
"oh-my-zsh": "~/.oh-my-zsh",
"agy": "~/.local/bin/agy",
}
func expandHome(p string) string {
@@ -84,10 +86,42 @@ func pipInstalled() bool {
// isCustomPkgInstalled returns (installed, checkPath). pip ships inside the
// Python distribution rather than at a fixed path, so it's detected with
// `python3 -m pip --version`.
func npmInstalled(cmdName string) (bool, string) {
if hasCmd(cmdName) {
if p, err := exec.LookPath(cmdName); err == nil {
return true, p
}
return true, ""
}
home, err := os.UserHomeDir()
if err != nil {
return false, ""
}
matches, _ := filepath.Glob(filepath.Join(home, ".local/share/pnpm/bin", cmdName))
if len(matches) > 0 {
return true, matches[0]
}
matches, _ = filepath.Glob(filepath.Join(home, ".nvm/versions/node/*/bin", cmdName))
if len(matches) > 0 {
return true, matches[0]
}
return false, ""
}
func isCustomPkgInstalled(pkg *CustomPackage) (bool, string) {
if strings.ToLower(pkg.Name) == "pip" {
name := strings.ToLower(pkg.Name)
if name == "pip" {
return pipInstalled(), ""
}
if name == "claude" {
return npmInstalled("claude")
}
if name == "codex" {
return npmInstalled("codex")
}
if name == "copilot" {
return npmInstalled("copilot")
}
raw := pkg.InstallPath
if raw == "" {
raw = defaultInstallPath(pkg)
@@ -428,6 +462,7 @@ func resolveLatest(pkg *CustomPackage) {
func installCustomPackages(toInstall []*CustomPackage) {
fmt.Println("\n=== Custom Packages ===")
ensureNodeLTS()
for _, pkg := range toInstall {
name := strings.ToLower(pkg.Name)
_, checkPath := isCustomPkgInstalled(pkg)
@@ -448,6 +483,7 @@ func installCustomPackages(toInstall []*CustomPackage) {
switch name {
case "nvm":
installNVM()
ensureNodeLTS()
continue
case "pyenv":
installPyenv()
@@ -467,6 +503,18 @@ func installCustomPackages(toInstall []*CustomPackage) {
installNeovim(pkg, tmp)
os.RemoveAll(tmp)
continue
case "agy":
installAgy()
continue
case "claude":
installNpmPackage("@anthropic-ai/claude-code")
continue
case "codex":
installNpmPackage("@openai/codex")
continue
case "copilot":
installNpmPackage("@github/copilot")
continue
}
resolveLatest(pkg)
+7
View File
@@ -33,6 +33,7 @@ func main() {
only := flag.String("only", "", "Install only the named section (system|flatpak|custom)")
gui := flag.Bool("gui", false, "Include GUI applications (headed environments).")
noVM := flag.Bool("no-vm", false, "macOS only: skip provisioning the Fedora-on-QEMU VM that backs the firecracker() zsh wrapper.")
noAI := flag.Bool("no-ai", false, "Skip installation of LLM/AI CLI tools (agy, claude, codex, copilot).")
flag.Parse()
switch *only {
@@ -54,6 +55,12 @@ func main() {
if isMacOS && strings.ToLower(custom[i].Name) == "firecracker" {
continue
}
if *noAI {
name := strings.ToLower(custom[i].Name)
if name == "agy" || name == "claude" || name == "codex" || name == "copilot" {
continue
}
}
customPtrs = append(customPtrs, &custom[i])
}
+4
View File
@@ -136,5 +136,9 @@ func customPackages() []CustomPackage {
{Name: "pyenv"},
{Name: "pip"},
{Name: "oh-my-zsh"},
{Name: "agy"},
{Name: "claude"},
{Name: "codex"},
{Name: "copilot"},
}
}
+26
View File
@@ -243,6 +243,11 @@ func ensureNodeLTS() {
if !runShell(`bash -c "source ~/.nvm/nvm.sh && nvm alias default 'lts/*' && nvm use --lts"`, CmdOpts{}).OK() {
errLog("Setting nvm default to LTS failed")
}
fmt.Println("[NVM] Enabling corepack and installing latest pnpm ...")
if !runShell(`bash -c 'source ~/.nvm/nvm.sh && corepack enable && corepack prepare pnpm@latest --activate && pnpm config set global-bin-dir "$HOME/.local/share/pnpm/bin"'`, CmdOpts{}).OK() {
errLog("Failed to enable corepack and prepare pnpm")
}
}
// ── oh-my-zsh ───────────────────────────────────────────────────────────
@@ -455,3 +460,24 @@ func askYN(prompt string) bool {
answer := strings.ToLower(strings.TrimSpace(string(buf[:n])))
return answer == "y"
}
func installAgy() {
fmt.Println(" Installing agy via curl ...")
if !runShell("curl -fsSL https://antigravity.google/cli/install.sh | bash", CmdOpts{}).OK() {
errLog("agy installation failed")
}
}
func installNpmPackage(pkgName string) {
home, _ := os.UserHomeDir()
if _, err := os.Stat(filepath.Join(home, ".nvm")); err != nil {
errLog("NVM is not installed — cannot install " + pkgName)
return
}
ensureNodeLTS()
fmt.Printf(" Installing %s via pnpm ...\n", pkgName)
if !runShell(fmt.Sprintf(`bash -c "source ~/.nvm/nvm.sh && pnpm add -g %s"`, pkgName), CmdOpts{}).OK() {
errLog(fmt.Sprintf("%s installation failed", pkgName))
}
}