A Go CLI tool that keeps your macOS development environment up to date.
- 11 providers covering Homebrew, npm, Composer, pip, Rust, editor extensions, Oh My Zsh, Vim, Vagrant, and the Mac App Store — see Providers
- Scan → Confirm → Execute → Report pipeline with pterm TUI output
- Parallel execution with configurable parallelism and dependency ordering (brew-cask waits for brew)
- Batched upgrades: package managers that accept multiple packages (Homebrew, npm, pip) upgrade everything in a single command — faster, and the only way to parallelize Homebrew, whose global lock blocks concurrent processes. A failing batch automatically re-runs each package individually so failures stay isolated. Editor extensions update concurrently across editors.
- Auth-required cask partitioning: detects which casks need admin auth via dry-run probe + heuristic fallback; defers them to a separate script
- Resumability: JSON state file tracks last-run results;
--retry-failedre-runs only failed providers - Deferred cask script:
--run-deferredexecutes the generated script for auth-required casks - YAML config with per-provider skip lists, auth overrides, and strategy settings — editable via interactive TUI or by hand
- macOS notifications via
terminal-notifier(falls back toosascript)
| Name | Updates | Requires |
|---|---|---|
brew |
Homebrew formulae | Homebrew |
brew-cask |
Homebrew casks | Homebrew |
npm |
Globally-installed npm packages | npm |
composer |
Globally-installed Composer packages | composer |
pip |
pip3 + pipx packages | pip3 and/or pipx |
rust |
Rust toolchains (rustup) and cargo-installed binaries | rustup, cargo-update |
editor |
Installed extensions for VS Code, Cursor, Kiro, Windsurf, and other VS Code–compatible editors | the editor's CLI (code, cursor, etc.) |
mas |
Apps installed from the Mac App Store | mas (brew install mas) — every update needs admin auth; upkeep caches sudo credentials once per run rather than prompting per app |
omz |
Oh My Zsh itself | Oh My Zsh installed |
vim |
Vim plugins (vim-plug or pathogen) | Vim + a supported plugin manager |
vagrant |
Vagrant boxes | vagrant |
Run upkeep --list to see which providers are currently registered, or upkeep <name> [<name> ...] to update specific ones (e.g. upkeep brew npm). A provider that's missing its required tool is reported as unavailable and skipped rather than erroring.
Download the latest archive from the releases page:
# macOS Apple Silicon (arm64)
curl -sL https://github.com/teknikqa/upkeep/releases/latest/download/upkeep_$(curl -s https://api.github.com/repos/teknikqa/upkeep/releases/latest | grep tag_name | cut -d '"' -f4 | tr -d v)_darwin_arm64.tar.gz | tar xz
# macOS Intel (amd64)
curl -sL https://github.com/teknikqa/upkeep/releases/latest/download/upkeep_$(curl -s https://api.github.com/repos/teknikqa/upkeep/releases/latest | grep tag_name | cut -d '"' -f4 | tr -d v)_darwin_amd64.tar.gz | tar xz
# Move to a directory in your PATH
sudo mv upkeep /usr/local/bin/Requires Go 1.25+.
# Install with go install
go install github.com/teknikqa/upkeep@latest
# Or build from source
make build
# Build and install to ~/bin/upkeep
make install# Update all available providers
upkeep
# Scan only — show what would be updated
upkeep --dry-run
# Update without confirmation prompt
upkeep --yes
# Update specific providers
upkeep brew npm
# Re-run only providers that failed last time
upkeep --retry-failed
# Execute deferred auth-required cask updates
upkeep --run-deferred
# Show full subprocess output on console
upkeep --verbose
# List all available providers
upkeep --list
# Use a custom config file
upkeep --config ~/.config/upkeep/config.yaml# Launch interactive config editor (TUI)
upkeep config edit
# Print current effective configuration as YAML
upkeep config show
# Print config file path
upkeep config path
# Reset configuration to defaults
upkeep config resetConfig file location: ~/.config/upkeep/config.yaml (auto-created with defaults on first run).
Use upkeep config edit to modify settings interactively, or edit the file directly:
parallelism: 4
providers:
brew:
enabled: true
skip: [] # packages to skip
brew_cask:
enabled: true
greedy: true
auth_strategy: defer # defer | skip | force-interactive
auth_overrides:
docker: false # never requires auth
rebuild_open_with: true
npm:
enabled: true
skip: []
# ... other providers follow the same pattern
notifications:
enabled: true
tool: terminal-notifier # terminal-notifier | osascript
logging:
dir: ~/Library/Logs
level: infoCasks that require admin authentication are handled per the auth_strategy config:
| Strategy | Behaviour |
|---|---|
defer (default) |
Writes ~/.local/state/upkeep/deferred-cask.sh; sends macOS notification; run later with --run-deferred |
skip |
Skips auth-required casks entirely |
force-interactive |
Runs brew interactively (prompts for password) |
Auth detection priority: config override > dry-run probe (NONINTERACTIVE=1 brew upgrade --cask <name> --dry-run) > heuristic (inspects brew info for .pkg, installer, launchctl, etc.)
State is written to ~/.local/state/upkeep/last-run.json after each run. It records:
- Per-provider status (
success/partial/failed) - Lists of updated / failed / deferred / skipped packages
- Deferred cask script path
- Run timestamp and duration
# Run all tests
make test
# Run tests with coverage report
make coverage
# Lint (go vet + golangci-lint)
make lint
# Format code
make fmt
# Run full CI pipeline locally (fmt, lint, test, build)
make ci
# GoReleaser dry-run
make release-dry-run
# Tidy dependencies
make tidy