A small, modular Bash CLI to update common macOS and Linux tooling (Homebrew, global npm packages, global Python packages, etc.).
This script can be disruptive (it updates global environments). Use --dry-run and scope with --only / --skip.
See SPEC.md for the full CLI/module contract, exit codes, and release invariants.
Using the Makefile:
make install
# Recommended (user-writable; enables self-update without sudo):
# make install PREFIX=$HOME/.local
# or: make install PREFIX=/opt/homebrewManual install:
chmod +x ./updates
sudo mkdir -p /usr/local/bin
sudo install -m 0755 ./updates /usr/local/bin/updatesupdates
updates --dry-run
updates --only brew,node --brew-mode formula
updates --only linux -n
updates --full
updates --skip python --log-file ./updates.log
updates --json -n --no-self-update --log-level warnExample output (trimmed):
Starting updates...
==> brew START
Homebrew 🍺
==> brew END (OK) (12s)
==> SUMMARY ok=1 skip=0 fail=0 total=12s
Done in 12s. 🎉
List available modules:
updates --list-modulesShow help:
updates --helpModules are auto-detected: if the underlying command isn’t installed, the module is skipped (unless you used --only, in which case it’s an error).
brew: update/upgrade Homebrew formulae (+ casks when enabled via--brew-mode casks/--brew-mode greedy/--full)shell: update Oh My Zsh and custom git plugins/themes (auto-detected)linux: upgrade Linux system packages (auto-detectsapt-get/dnf/yum/pacman/zypper/apk)node: upgrade global npm packages viancu+npmpython: upgrade global Python packages viapython3 -m pipuv: update uv and uv-managed tools (uv self update,uv tool upgrade --all)mas: upgrade Mac App Store apps viamas(disabled by default; enable with--mas-upgradeor--full)pipx: upgrade pipx-managed apps viapipx upgrade-allrustup: update Rust toolchains viarustup updateclaude: update Claude Code CLI viaclaude updatemise: update mise and upgrade installed tools (mise self-update,mise upgrade)go: update Go binaries fromGO_BINARIESin~/.updatesrc(entries default to@latest)macos: list available macOS software updates viasoftwareupdate -l(disabled by default; enable with--macos-updatesor--full)
updates optionally reads ~/.updatesrc for defaults (CLI flags override; pass --no-config to ignore).
Example:
# ~/.updatesrc
SKIP_MODULES=mas,macos
BREW_MODE=formula
BREW_CLEANUP=1
LOG_LEVEL=info
GO_BINARIES="golang.org/x/tools/gopls,github.com/go-delve/delve/cmd/dlv"For GO_BINARIES, entries may be module or module@version. If @version is omitted, it defaults to @latest.
Install what you actually use:
brew(Homebrew)git(for theshellmodule)ncu(npm-check-updates):npm install -g npm-check-updatesuv: https://github.com/astral-sh/uvmas:brew install masmise: https://mise.jdx.devpipx:brew install pipxrustup: from https://rustup.rsclaude(Claude Code CLI) for theclaudemodulego(for thegomodule)- On Linux: a supported system package manager (
apt-get,dnf,yum,pacman,zypper, orapk) andsudo(if not running as root)
./scripts/lint.sh
./scripts/test.sh- This script updates global environments (
npm -g,pip), which can be disruptive. - Use
--dry-runfirst, and consider--only/--skipto control scope. updatescan self-update from GitHub Releases; disable with--no-self-updateorUPDATES_SELF_UPDATE=0. Self-update works best when installed to a user-writable location (e.g.PREFIX=$HOME/.local).- On macOS, Homebrew casks are disabled by default; enable with
--brew-mode casksor--brew-mode greedy(or--full). On macOS 26+, cask upgrades may be blocked unless your terminal app is allowed under Privacy & Security → App Management (e.g. Ghostty). If you see a system notification like “<Terminal App> tried modifying your system…”, enable App Management or rerun with--brew-mode formula. - On WSL, updates apply to the Linux distro (not Windows itself).
- Output uses ANSI colors when run in a TTY; disable with
--no-colororNO_COLOR=1. When--log-fileis used, colors are disabled to keep logs clean. - If Python is externally-managed (PEP 668),
updatesupgrades user-site packages by default; use--pip-forceto override (dangerous).
See CONTRIBUTING.md.
MIT — see LICENSE.