This guide helps you move to vex from nvm, asdf, or pyenv without guessing about behavior that has not been verified.
It focuses on:
- current
vexbehavior in this repository - official upstream documentation for
nvm,asdf,pyenv, andpyenv-virtualenv - migration steps that are safe to validate locally on macOS
If your current setup depends on custom plugins, shell wrappers, or private toolchains, treat this guide as the baseline and test those extras separately.
| Manager | Scope | Common version file | Activation model | Main migration concern |
|---|---|---|---|---|
nvm |
Node.js only | .nvmrc |
shell initialization plus nvm use |
remove old shell init after verifying vex owns PATH |
asdf |
Multi-language via plugins | .tool-versions |
shims plus plugin runtime | normalize .tool-versions for vex semantics and supported tools |
pyenv |
Python only | .python-version |
shims plus pyenv init |
replace shim-based Python selection and virtualenv plugin workflows |
vex |
Built-in support for Node.js, Go, Java, Rust, Python | .tool-versions plus selected language-specific files |
direct symlink switching, vex exec, vex run, shell hook |
macOS only, built-in tools only |
Before migrating, these are the vex rules worth anchoring on:
vexsupports the built-in tool namesnode,go,java,rust, andpython.vexreads these project files:.tool-versions,.node-version,.nvmrc,.go-version,.java-version,.rust-toolchain, and.python-version.- File priority is:
.tool-versionsfirst, then language-specific files. - Project lookup walks up parent directories, so nested directories can inherit a root
.tool-versionsfile. - A child
.tool-versionsfile overrides matching tools from a parent directory while leaving unrelated parent entries in place. - Global defaults live in
~/.vex/tool-versions, not~/.tool-versions. - Legacy global files and supported language home/cache directories can be audited and migrated explicitly with
vex repair migrate-home. vex execandvex runactivate the resolved environment for a single process without changing global symlinks.- With the shell hook installed,
vexalso auto-activates a project-local.venvwhen youcdinto that project.
Use this checklist no matter which manager you are migrating from:
- Install
vexand initialize shell integration. - Decide which manager will own
PATHduring the migration window. Do not leave both active long term. - Normalize your project version files into a format
vexactually understands. - Install the required versions with
vex. - Verify with
vex current,<tool> --version, andwhich <tool>. - Clear your shell command cache with
hash -rifwhichstill shows the old path. - Only after verification, remove the old manager's shell initialization.
Recommended verification commands:
vex --version
vex doctor
vex current
which node
which python
echo "$PATH"The biggest change when moving to vex is not syntax. It is ownership.
vexis not plugin-driven today. It has first-party support for a fixed set of tools.vexkeeps version selection in version files and keeps tasks, env vars, and network settings in.vex.toml.vexprefers direct symlink switching over shim dispatch.vex useupdates the active symlinks under~/.vexfor the current user, so it is broader than a shell-only override such aspyenv shell.vex execandvex runare the safest replacements for many one-off shell overrides and ad-hoc subshell workflows.
If your existing setup mixes version selection, shell aliases, task runners, and virtualenv activation in one place, split those responsibilities during migration instead of copying them verbatim.
According to the official nvm documentation, nvm installs under ~/.nvm, is initialized from your shell profile, and uses .nvmrc as its project version file. Its documented auto-use behavior is a shell recipe, not a universal built-in for every shell session.
.nvmrccan stay in place temporarily becausevexreads it for Node.js resolution.nvm usemaps conceptually to either:vex use node@<version>for explicit switchingvexshell auto-switching when a version file is present
nvm execstyle one-off commands map better tovex exec -- <command>.nvm alias defaultmaps conceptually tovex global node@<version>.
vexis multi-language, so.tool-versionsbecomes the better long-term file if the repo needs more than Node.js.vexdoes not store its state under~/.nvm.vexshell ownership should replacenvmshell ownership once migration is complete.
-
Install
vexand run:vex init --shell auto vex doctor
-
If the repository is Node-only and already has a correct
.nvmrc, you can keep that file for the first validation pass. -
If the repository will use more than Node.js, create or update
.tool-versions:node 20.11.0 -
Install the required Node.js version:
vex install node@20.11.0
-
Verify:
vex current node --version which node
-
Remove
nvminitialization lines from your shell config only after verification.
- Do not keep both
nvminit lines andvexshell hook active indefinitely. - If a repo already has both
.tool-versionsand.nvmrc,vexwill prefer.tool-versions. - If your old shell still resolves
nodefrom annvmpath after switching, runhash -ror restart the shell.
According to the official asdf docs, asdf is a plugin-based version manager that uses shims and a .tool-versions file. Its version file format can express multiple fallback versions, system, ref:, and path: values, and global defaults live in ~/.tool-versions.
That means asdf is the closest conceptual match to vex, but it is also the migration with the most hidden format differences.
- The idea of a checked-in
.tool-versionsfile carries over well. - Root-level shared defaults plus child-directory overrides also carry over well.
- The idea of a global default version file also carries over, but
vexstores it at~/.vex/tool-versions.
vexdoes not use plugin names. It only recognizesnode,go,java,rust, andpython.vexdoes not implement the broaderasdfversion expression model. Rewrite any line that relies on:- multiple fallback versions
systemref:...path:...- unsupported tool names
vexseparates project tasks and env vars into.vex.toml; they should not be encoded into version-file conventions.vexdoes not require installing plugins before installing tool versions.
Good vex input:
node 20.11.0
go 1.24.0
python 3.12.8
Examples that should be rewritten before relying on vex:
nodejs 20.11.0
python 3.12.8 system
node ref:main
terraform 1.7.5
The examples above are common in plugin-based asdf setups, but they are not valid vex migration targets as-is.
-
Audit every entry in your current
.tool-versionsfile. -
Rewrite it so that:
- each line uses a
vextool name - each tool has one version or supported alias
- unsupported tools are removed or managed outside
vex
- each line uses a
-
Preview legacy home-state cleanup and migrate the safe paths into
~/.vexexplicitly:vex repair migrate-home
-
Install and verify:
vex install vex current
-
If you need project tasks or repo-local env vars, add them to
.vex.tomlinstead of stretching.tool-versions. -
Remove
asdfinitialization from your shell only afterwhich <tool>resolves into~/.vex/bin.
- If your current
asdfsetup depends on plugins for tools outsidevex's built-in set, migrate only the supported tools first. - If your repo uses
asdfplugin-specific conventions, keep those repos onasdfuntil you have a clear replacement plan. - If you benchmark
asdfagainstvex, make sure you compare equivalent workflows and note that one is shim-based and one is symlink-based.
According to the official pyenv docs, pyenv uses shims, resolves versions from PYENV_VERSION, .python-version, parent directories, and a global version file, and supports local, global, and shell version selection. The official pyenv-virtualenv plugin adds virtualenv and auto-activation workflows on top of that.
.python-versioncan stay in place temporarily becausevexreads it.pyenv localmaps conceptually to a project version file.pyenv globalmaps conceptually tovex global python@<version>; user-level Python CLIs belong invex python base.pyenv-virtualenvstyle "activate a project environment when I enter the directory" maps conceptually tovexshell hook plus a local.venv.
vexstores global defaults in~/.vex/tool-versions, not inpyenv's global file location.vexdoes not expose a direct equivalent ofpyenv shellorPYENV_VERSION.- For one-off commands,
vex execis the cleaner replacement. - For global Python CLIs installed with pip, use the per-version base environment:
vex python base pip install kagglevex python base freezevex python base sync
- For Python environments,
vexuses project-local.venvplus:vex python initvex python freezevex python sync
- The base environment is hidden while a project
.venvis active, so base-installed tools do not leak into project dependency resolution. - If your
.python-versionfile contains multiple entries or otherpyenv-specific patterns, rewrite it to a single version string before treating it as avexsource of truth.
-
Install the Python you want
vexto manage:vex install python@3.12 vex global python@3.12
-
For Python-only repos, you can keep
.python-versionduring the initial migration. -
Move global pip CLIs into the active Python base environment:
vex python base pip install kaggle vex python base freeze
-
For mixed-language repos, convert to
.tool-versions:python 3.12.8 node 20.11.0 -
Replace virtualenv plugin workflows with
vexcommands:vex python init vex python freeze vex python sync
-
Verify:
vex current python --version which python
-
Remove
pyenv initandpyenv virtualenv-initlines after the environment behaves correctly undervex.
- If you depended on shell-scoped version overrides, replace them with explicit project files or
vex exec. vexwill auto-activate a.venvin the current project when the shell hook is installed, but it does not try to mirror everypyenv-virtualenvfeature.- Keep
.venvuncommitted and commit the files that describe it:.tool-versionsandrequirements.lock.
Use this table when deciding what to keep and what to rewrite.
| File | vex reads it |
Best long-term use with vex |
|---|---|---|
.tool-versions |
Yes | preferred for checked-in multi-tool projects |
.nvmrc |
Yes, for Node.js | acceptable for temporary migration or Node-only repos |
.node-version |
Yes, for Node.js | acceptable when already present |
.python-version |
Yes, for Python | acceptable for temporary migration or Python-only repos |
~/.tool-versions |
auto-migrated to ~/.vex/tool-versions when possible |
move to ~/.vex/tool-versions |
The recommended end state for most active vex repositories is:
.tool-versionsfor project version pins.tool-versions.lockfor reproducible installs.vex.tomlfor project tasks, env vars, and repo-local configuration
Do not uninstall the old manager until these checks pass:
vex doctor
vex current
node --version
python --version
which node
which pythonYou should see active binaries resolving from ~/.vex/bin or from a vex-managed toolchain path, not from ~/.nvm, ~/.asdf, or ~/.pyenv.
These are the upstream references used when writing this guide: