A fast, multi-language version manager for macOS
Symlink-based switching · Node.js / Go / Java / Rust / Python · .tool-versions · Auto-switch on cd
Quick Start · Commands · .tool-versions Workflow · FAQ
- Symlink-based switching — version changes take effect instantly, no shim overhead
- Multi-language — manage Node.js, Go, Java (Eclipse Temurin), Rust, and Python from one tool
- Python venv integration —
vex python init/freeze/syncfor venv and lockfile management; shell hook auto-activates.venvoncd - Shell auto-configuration —
vex init --shell autodetects and configures your shell automatically (zsh, bash, fish, nushell) - Project templates —
vex init --list-templatesandvex init --template <name>bootstrap official starters for Node, Go, Java, Rust, and Python - Safe add-only templating —
vex init --template <name> --add-onlyonly merges.tool-versionsand.gitignore, then creates missing starter files - Fuzzy version matching —
node@20resolves to latest 20.x,node@ltsto latest LTS - Version aliases —
latest,lts,lts-<codename>,stable, minor version matching - Historical Rust stable installs —
vex list-remote rustandvex install rust@1.93.1resolve against Rust's official archived stable installers for the current macOS architecture, not just the current stable release - User-defined aliases —
vex alias set/list/deletefor custom version shortcuts - TUI dashboard —
vex tuifor interactive version overview and health check - Offline mode —
--offlineflag for cache-only operations, no network required - Lockfile support —
vex lockgenerates reproducible.tool-versions.lockwith checksums - Team config sync —
vex install --from/vex sync --fromsupport local files,vex-config.toml, HTTPS team configs, and Git repositories with a safe[tools]schema - Managed npm globals — Shell hooks and
vex exec/runexportNPM_CONFIG_PREFIX=$HOME/.vex/npm/prefixand keep~/.vex/npm/prefix/binon PATH for stablenpm install -gbehavior - Auto-export env vars — Automatic
JAVA_HOME,GOROOT,CARGO_HOME, captured user-state env vars, and project.venvactivation in shell hooks - Official Rust extensions —
vex rust target/componentmanages official Rust toolchain extensions such asrust-srcand iOS std targets - Contained user-state capture — supported language homes, caches, and user bins default into
~/.vex - Explicit home repair —
vex repair migrate-homepreviews and applies safe migrations from legacy home-directory paths - One-command upgrade —
vex upgrade nodeinstalls and switches to the latest version - Managed context upgrades —
vex outdatedinspects the current project/global/active scope, andvex upgrade --allupgrades that whole managed set - Explicit relink for Node globals —
vex relink noderebuilds~/.vex/binafter npm adds new executables to the active Node toolchain - Transient execution —
vex exec -- <command>runs tools in the resolved vex environment without changing global symlinks - Project task runner —
.vex.tomlcan define project env vars and named commands forvex run <task> - Official GitHub Action —
uses: imnotnoahhh/vex@v1installsvexplus cached toolchains and managed npm globals on macOS GitHub Actions runners .tool-versionssupport — per-project pinning, auto-switch oncd, batch install- Project configuration —
.vex.tomladds project-local commands, env vars, behavior overrides, and optional network/mirror overrides - Smart version filtering —
vex list-remote node --filter ltsshows only LTS versions - Remote version cache — cached for 5 min by default, configurable via
config.toml - Concurrent install protection — file-based locking prevents parallel install corruption
- Checksum verification — Node.js uses official SHA256 verification; Go/Java/Rust follow upstream checksum metadata availability
- Parallel downloads — atomic writes with automatic cleanup, up to 3 concurrent downloads
- Parallel extraction — fast archive extraction using parallel file processing
- Security hardening — TOCTOU protection, ownership validation, path traversal protection, atomic operations
- Self-update —
vex self-updateupgrades vex itself to the latest GitHub release - Health check —
vex doctorvalidates installation, PATH, shell hooks, managed npm global bins, and active manager conflicts with actionable fixes - Disk space check — prevents installation when less than 500 MB free space available
- Machine-readable output —
--jsonforcurrent,list,list-remote, anddoctor - Homebrew support — optional official tap for brew users, while direct install remains the recommended path
- Multi-shell support — zsh, bash, fish, and nushell integration for auto-switching
- macOS native — supports both Apple Silicon and Intel macOS environments
Automatically downloads the correct prebuilt binary for your macOS architecture (arm64/x86_64), installs to ~/.local/bin/vex, and updates your shell PATH configuration:
# Latest release
curl -fsSL https://raw.githubusercontent.com/imnotnoahhh/vex/main/scripts/install-release.sh | bash
# Specific tag
curl -fsSL https://raw.githubusercontent.com/imnotnoahhh/vex/main/scripts/install-release.sh | bash -s -- --version v1.6.1For auditability, review the script before running:
curl -fsSL -o install-release.sh https://raw.githubusercontent.com/imnotnoahhh/vex/main/scripts/install-release.sh
less install-release.sh
bash install-release.sh --helpDownload the prebuilt binary for your architecture from the Releases page:
- Apple Silicon (M1/M2/M3):
vex-aarch64-apple-darwin.tar.gz - Intel:
vex-x86_64-apple-darwin.tar.gz
Extract and install:
tar -xzf vex-*.tar.gz
mkdir -p ~/.local/bin
cp vex-*/vex ~/.local/bin/vex
chmod +x ~/.local/bin/vex
# Add to PATH if not already present
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrcIf you already use Homebrew, vex can also be installed from the official tap:
brew install imnotnoahhh/homebrew-vex/vexDirect installation remains the recommended path because it keeps vex completely independent from Homebrew after setup.
git clone https://github.com/imnotnoahhh/vex.git
cd vex
cargo build --release && cp target/release/vex ~/.local/bin/vexVerify installation:
vex --versionvex init --shell auto
# Or configure shell hooks manually.
# For zsh:
echo 'eval "$(vex env zsh)"' >> ~/.zshrc
source ~/.zshrc
# For bash:
echo 'eval "$(vex env bash)"' >> ~/.bashrc
source ~/.bashrc
# For fish:
echo 'vex env fish | source' >> ~/.config/fish/config.fish
# For nushell:
vex env nu | save -f ~/.config/nushell/vex.nu
echo 'source ~/.config/nushell/vex.nu' >> ~/.config/nushell/config.nuThe generated hook keeps ~/.vex/npm/prefix/bin and ~/.vex/bin on PATH, runs vex use --auto on directory changes, and refreshes the exported activation environment via vex env <shell> --exports.
# Install a specific version (fuzzy matching)
# Note: Automatically switches to the installed version
vex install node@20 # → latest 20.x
vex install node@lts # → latest LTS
vex install node@20.11.0 # → exact version
# Install without switching (preserve current version)
vex install node@20 --no-switch
# Version aliases
vex install node@lts-iron # → specific LTS codename
vex install go@1.23 # → latest 1.23.x
vex install rust@stable # → latest stable
# Bootstrap a project starter
vex init --list-templates
vex init --template node-typescript
vex init --template python-venv --add-only
# Sync from a team-managed source
vex sync --from vex-config.toml
vex sync --from https://company.example/vex-config.toml
vex install --from git@github.com:company/vex-config.git
# Upgrade to latest
vex upgrade node
vex upgrade --all
# Show what is behind latest in the current managed context
vex outdated
# List user-defined aliases
vex alias list
# Run a command in the resolved vex-managed environment
vex exec -- node -v
# Run a named task from .vex.toml
vex run test
# Rebuild Node binary links after npm installs a new global CLI
vex relink node
# Preview or apply safe home-directory migrations into ~/.vex
vex repair migrate-home
vex repair migrate-home --apply
# Manage official Rust extensions for the active toolchain
vex rust target list
vex rust target add aarch64-apple-ios aarch64-apple-ios-sim
vex rust component add rust-src
# Switch versions
vex use node@22
# Pin version for current project
vex local node@20.11.0 # writes .tool-versions
# Install everything from .tool-versions
vex installFor the full CLI reference, including command groups and option details, see docs/guides/command-reference.md.
| Command | Description | Example |
|---|---|---|
vex init |
Initialize directory structure | vex init |
vex init --shell auto |
Initialize and auto-configure shell | vex init --shell auto |
vex init --shell zsh |
Initialize and configure specific shell | vex init --shell zsh |
vex init --list-templates |
List built-in project templates | vex init --list-templates |
vex init --template <name> |
Bootstrap a project starter | vex init --template rust-cli |
vex init --template <name> --add-only |
Safely merge missing template files into an existing repo | vex init --template python-venv --add-only |
vex install <tool@version> |
Install a specific version | vex install node@20 |
vex install <tool@version> <tool@version>... |
Install multiple specific versions | vex install node@20 go@1.24 |
vex install |
Install all from .tool-versions |
vex install |
vex install --from <source> |
Install from a version file, vex-config.toml, HTTPS URL, or Git repo |
vex install --from git@github.com:company/vex-config.git |
vex use <tool@version> |
Switch to installed version | vex use node@22 |
vex use --auto |
Auto-switch from version files | vex use --auto |
vex relink node |
Rebuild ~/.vex/bin from the active Node toolchain after new npm global executables appear |
vex relink node |
vex local <tool@version> |
Pin version in .tool-versions |
vex local node@20.11.0 |
vex global <tool@version> |
Pin version in ~/.vex/tool-versions |
vex global go@1.23 |
vex list <tool> |
List installed versions | vex list node |
vex list <tool> --json |
List installed versions as JSON | vex list node --json |
vex list-remote <tool> |
List all remote versions | vex list-remote node |
vex list-remote <tool> --json |
List remote versions as JSON | vex list-remote node --json |
vex list-remote <tool> -f lts |
List only LTS versions | vex list-remote node -f lts |
vex list-remote <tool> -f major |
List latest of each major version | vex list-remote node -f major |
vex list-remote <tool> --no-cache |
List remote versions (skip cache) | vex list-remote node --no-cache |
vex upgrade <tool> |
Upgrade to latest version | vex upgrade node |
vex upgrade --all |
Upgrade every managed tool in the current context | vex upgrade --all |
vex outdated |
Show managed tools that are behind latest | vex outdated |
vex outdated --json |
Show outdated status as JSON | vex outdated --json |
vex prune --dry-run |
Preview cache, stale-lock, and unused-toolchain cleanup | vex prune --dry-run |
vex gc |
Alias for vex prune |
vex gc --dry-run |
vex install --force |
Reinstall a version even if it already exists | vex install node@20 --force |
vex install --frozen |
Install from version files while strictly enforcing .tool-versions.lock |
vex install --frozen |
vex alias set <tool> <alias> <version> |
Set custom version alias | vex alias set node lts-current 20.11.0 |
vex alias list [tool] |
List all aliases | vex alias list node |
vex alias delete <tool> <alias> |
Delete an alias | vex alias delete node lts-current |
vex lock |
Generate lockfile from .tool-versions |
vex lock |
vex sync --from <source> |
Sync from a version file, vex-config.toml, HTTPS URL, or Git repo |
vex sync --from https://company.example/vex-config.toml |
vex sync --frozen |
Install from lockfile | vex sync --frozen |
vex sync --offline |
Sync using cached metadata and archives only | vex sync --offline |
vex tui |
Launch interactive dashboard | vex tui |
vex install --offline |
Install from cache only | vex install node@20 --offline |
vex exec -- <command> |
Run a command in the resolved vex environment without switching global state | vex exec -- node -v |
vex run <task> [args...] |
Run a named task from .vex.toml |
vex run test -- --nocapture |
vex current |
Show active versions | vex current |
vex current --json |
Show active versions as JSON | vex current --json |
vex uninstall <tool@version> |
Uninstall a version | vex uninstall node@20.11.0 |
vex doctor |
Run health check and diagnostics | vex doctor |
vex doctor --json |
Run health check and emit JSON | vex doctor --json |
vex doctor --verbose |
Show extra provenance and captured-env details | vex doctor --verbose |
vex repair migrate-home |
Preview or apply safe legacy home-directory migrations into ~/.vex |
vex repair migrate-home --apply |
vex self-update |
Update vex itself to the latest release | vex self-update |
vex env <shell> |
Output shell hook script | vex env zsh |
vex rust target <subcommand> |
Manage official Rust targets for the active Rust toolchain | vex rust target add aarch64-apple-ios |
vex rust component <subcommand> |
Manage official Rust components for the active Rust toolchain | vex rust component add rust-src |
vex python init |
Create .venv in current directory |
vex python init |
vex python freeze |
Lock environment to requirements.lock |
vex python freeze |
vex python sync |
Restore environment from requirements.lock |
vex python sync |
| Tool | Binaries | Source |
|---|---|---|
| Node.js | node, npm, npx (+ corepack in v24 and earlier) | Official binaries |
| Go | go, gofmt | Official binaries |
| Java | java, javac, jar, javadoc + 26 more JDK tools | Eclipse Temurin JDK |
| Rust | rustc, rustdoc, cargo, rustfmt, clippy, rust-analyzer + 5 more | Official stable binaries |
| Python | python3, pip3, python, pip, 2to3, idle3, pydoc3, python3-config | python-build-standalone (astral-sh) |
- User guides and troubleshooting: docs/README.md
- Full command reference: docs/guides/command-reference.md
- Migration and comparison: docs/guides/migration-comparison.md
- Benchmark methodology: docs/guides/benchmark-methodology.md
- Team and CI recommendations: docs/guides/best-practices.md
- Maintainer and contributor docs: docs/development/README.md
Global settings live in ~/.vex/config.toml. Project settings in .vex.toml can override behavior and network defaults within that repo. Environment variables still take highest precedence for CI and enterprise shells.
# ~/.vex/config.toml
cache_ttl_secs = 300
[network]
connect_timeout_secs = 30
read_timeout_secs = 300
download_retries = 3
proxy = "http://proxy.internal:8080"
[mirrors]
node = "https://mirror.example.com/nodejs"# .vex.toml
[behavior]
auto_switch = true
auto_activate_venv = true
[network]
download_retries = 5
proxy = "http://team-proxy.internal:8080"
[mirrors]
rust = "https://mirror.example.com/rust"
[env]
RUST_LOG = "debug"
[commands]
test = "cargo test"
lint = "cargo clippy --all-targets --all-features -- -D warnings"Use vex exec for one-off commands and vex run for project tasks:
vex exec -- python -m pytest
vex run testFor more detail, see docs/guides/configuration.md.
vex init now has two explicit modes:
vex init --shell ...initializes~/.vexand shell integrationvex init --template ...bootstraps the current project directory
The built-in core templates are:
node-typescriptgo-servicejava-basicrust-clipython-venv
Template defaults:
--dry-runpreviews every file without writing anything- strict mode exits without writing if any target file already exists
--add-onlyonly merges.tool-versionsand.gitignore, then creates any missing starter files
vex install --from and vex sync --from can now consume:
- a local version file such as
.tool-versions - a local
vex-config.toml - an HTTPS-hosted
vex-config.toml - an HTTPS or SSH Git repository whose root contains
vex-config.toml
Team config is intentionally narrow and safe:
version = 1
[tools]
node = "20"
go = "1.24"
python = "3.12"Rules:
- remote team config only supports
[tools] - local
.tool-versionsentries override the remote baseline for matching tools - team config is only loaded when you explicitly pass
--from - local
--fromfile paths are resolved relative to your current working directory
This repository now publishes a macOS-only composite action:
- uses: imnotnoahhh/vex@v1
with:
tools: node@20 go@1.24Or:
- uses: imnotnoahhh/vex@v1
with:
auto-install: trueThe action:
- installs the latest
vexrelease or a requested release tag - caches
~/.vex/cacheand~/.vex/toolchains - re-runs activation after cache restore so
~/.vex/binis ready inPATH
Lock your toolchain versions for reproducible environments:
# 1. Pin versions in .tool-versions
vex local node@20.11.0
vex local go@1.23.5
# 2. Generate lockfile with checksums
vex lock
# Creates .tool-versions.lock with SHA256 checksums
# 3. Commit both files
git add .tool-versions .tool-versions.lock
git commit -m "Lock toolchain versions"Teammates can restore the exact environment:
# Install with frozen lockfile (enforces exact versions)
vex sync --frozenThe lockfile includes SHA256 checksums for security and reproducibility.
Create custom version shortcuts:
# Set a global alias (default)
vex alias set node production 20.11.0
# Set a project-local alias
vex alias set --project node lts-current 20.11.0
# List all aliases
vex alias list node
# Use the alias
vex install node@lts-current
# Delete an alias
vex alias delete node lts-currentAliases are stored in:
- Project:
.vex.toml(committed to git) - Global:
~/.vex/aliases.toml(user-specific)
Project aliases override global aliases.
Version specs don't need to be exact:
vex install node@20 # latest 20.x.x
vex install node@20.11 # latest 20.11.x
vex install node@20.11.0 # exact version
vex install java@21 # exact (Java uses single numbers)| Tool | Aliases |
|---|---|
| Node.js | latest, lts, lts-<codename> (e.g. lts-iron) |
| Go | latest, <major>.<minor> (e.g. 1.23 → latest 1.23.x) |
| Java | latest, lts |
| Rust | latest, stable |
| Python | latest, stable, bugfix, security |
# Inspect remote versions before choosing a built-in alias
vex list-remote node --filter lts# Pin versions for your project
vex local node@20.11.0
vex local go@1.23.5
# .tool-versions is created automatically:
# go 1.23.5
# node 20.11.0
# Teammates clone and run:
vex install # installs everything from .tool-versions
# With shell hook enabled, versions auto-switch on cd
cd my-project # → switches to node 20.11.0, go 1.23.5Python binaries come from python-build-standalone standard install_only CPython packages — prebuilt, standalone binaries with no compilation needed. vex does not currently manage free-threaded Python variants.
# 1. Install a Python version
vex install python@3.12 # or: python@latest, python@bugfix, python@security
# 2. Activate it
vex use python@3.12
# 3. Create a project venv
# Uses ~/.vex/bin/python3 (the active vex-managed python), falls back to system python3
cd my-project
vex python init # runs: python3 -m venv .venv
# also writes python version to .tool-versions
# 4. Install packages and lock them
pip install requests flask
vex python freeze # runs: pip freeze > requirements.lock
# 5. Commit both files
git add .tool-versions requirements.lockOn a new machine or for a teammate:
vex install python@3.12
cd my-project
vex python sync # auto-creates .venv if missing + pip install -r requirements.lockThe shell hook automatically refreshes PATH, VIRTUAL_ENV, and captured tool env vars when you cd into or out of a project — no manual source .venv/bin/activate needed.
requirements.lock is generated by pip freeze and pins all packages including transitive dependencies. Commit it to git for reproducible environments.
vex uses symlinks + PATH prepending. No shims, no runtime overhead.
~/.vex/bin/node → ~/.vex/toolchains/node/20.11.0/bin/node
↓ (vex use node@22)
~/.vex/bin/node → ~/.vex/toolchains/node/22.0.0/bin/node
Switching versions just updates symlinks — instant and shell-restart-free.
| vex | nvm | fnm | asdf | mise | |
|---|---|---|---|---|---|
| Multi-language | ✅ | ❌ Node only | ❌ Node only | ✅ | ✅ |
| Python venv management | ✅ built-in | ❌ | ❌ | ❌ | ❌ |
| No shims | ✅ symlinks | ✅ | ✅ | ❌ shims | ❌ shims |
| .tool-versions | ✅ | ❌ | ❌ | ✅ | ✅ |
| Auto-switch on cd | ✅ | ❌ | ✅ | ✅ | ✅ |
| Zero home dir pollution | ✅ all in ~/.vex | ❌ | ❌ | ❌ | ❌ |
| Self-update | ✅ built-in | ❌ | ❌ | ❌ | ❌ |
| Implementation | Rust | Shell | Rust | Shell | Rust |
Why no shims matters: asdf and mise insert a shim binary in front of every command. Every time you run node, the shim wakes up, looks up the version, then execs the real binary. vex skips this entirely — ~/.vex/bin/node is a direct symlink to the real binary. Zero overhead, no startup tax.
Why zero home dir pollution matters: most version managers scatter files across ~/.nvm, ~/.cargo, ~/.cache/node, ~/.tool-versions, etc. vex keeps everything under ~/.vex/ — one directory, easy to back up, easy to nuke.
~/.vex/
├── bin/ # Symlinks (added to PATH)
├── toolchains/ # Installed versions
│ ├── node/
│ │ ├── 20.11.0/
│ │ └── 22.0.0/
│ ├── go/
│ ├── java/
│ └── rust/
├── current/ # Active version symlinks
├── cache/ # Download cache + remote version cache
├── locks/ # Install lock files (concurrent protection)
└── config.toml # Configuration (e.g. cache_ttl_secs)
Run vex doctor to perform a comprehensive health check. It validates:
- vex installation and PATH configuration
- Shell hook setup (auto-switch on cd)
- Installed tool versions and activation status
- Binary symlinks integrity
- Provides actionable suggestions for fixing issues
Why does vex list-remote go not show every historical Go release?
Go remote listings are constrained by upstream API policy and usually focus on active maintenance lines.
Why does vex list-remote rust show limited choices?
Current Rust support reads channel-rust-stable.toml and only targets the stable channel's current release.
How do I upgrade vex itself?
Run vex self-update. It fetches the latest release from GitHub, downloads the binary for your architecture, and replaces the current executable atomically.
How do I upgrade to the latest version of a tool?
Use vex upgrade <tool>. It installs the latest version and switches to it automatically.
Can it coexist with nvm/fnm? Technically yes, but not recommended — PATH conflicts are likely.
Windows/Linux support? macOS only for now.
How to uninstall vex?
# 1. Remove these lines from ~/.zshrc (or ~/.bashrc):
# eval "$(vex env zsh)"
# export PATH="$HOME/.vex/bin:$PATH"
# 2. Remove vex data and binary
rm -rf ~/.vex
rm -f ~/.local/bin/vexgit clone https://github.com/imnotnoahhh/vex.git
cd vex
cargo build
cargo test
# Optional: run network-dependent tests explicitly
cargo test --features network-tests
# CI-aligned smoke tests
VEX_BIN="$(pwd)/target/debug/vex" bash scripts/test-management-features.sh
VEX_BIN="$(pwd)/target/debug/vex" bash scripts/test-shell-hooks.sh
VEX_BIN="$(pwd)/target/debug/vex" bash scripts/test-rust-extensions-live.shGenerate comprehensive API documentation with custom styling:
# Quick command
make docs
# Or manually
RUSTDOCFLAGS="--html-in-header docs/header.html" cargo doc --no-deps
cp docs/custom.css target/doc/
open target/doc/vex/index.htmlThe documentation includes:
- Pure English documentation for all modules
- Custom theme with improved readability
- Enhanced code highlighting and styling
- Comprehensive module and function documentation
Performance benchmarks are available to measure key operations:
# Run all benchmarks
cargo bench
# Run specific benchmark
cargo bench bench_parse_tool_versions
# Generate detailed reports (saved to target/criterion/)
cargo bench -- --verboseBenchmarked operations:
- Version file parsing (
.tool-versions) - Directory traversal for version resolution
- Symlink creation and updates (version switching)
- Cache read/write operations
- Parallel vs sequential file extraction
Note: Benchmarks are not run in CI to keep build times fast. Run them locally to measure performance improvements.
For reporting guidance and fair comparison rules, see docs/guides/benchmark-methodology.md.
See CONTRIBUTING.md for details.
- Linux support — Extend to Linux distributions (Ubuntu, Debian, Fedora, Arch)
- Windows support — Windows compatibility with junction points
- Plugin system — Allow community-contributed tool adapters
- Version constraints — Support version ranges in
.tool-versions(e.g.,node >=20.0.0 <21.0.0) - Global default versions — Set default versions without
.tool-versionsfile - Parallel installations — Install multiple tools concurrently
- Update notifications — Notify when new tool versions are available
- Self-update — ✅ Done (
vex self-update)
- Additional languages: Ruby, PHP, Elixir, Zig
- Custom binary sources: Support for private registries
- Version pinning strategies: Lock files for reproducible builds
- Integration with CI/CD: GitHub Actions, GitLab CI support
See GitHub Issues for detailed feature requests and discussions.
Thanks to everyone who has contributed to vex!
- Noah Qin - Creator and maintainer
Want to contribute? Check out CONTRIBUTING.md to get started!
MIT © 2026 Noah Qin
