uvm keeps the familiar create / activate / deactivate / list / delete workflow, while delegating Python, virtual-environment, and package operations to uv. Version 1.2.3 makes uvm update safe to run (no more false failure after a successful update), adds uvm config set envs-dir and a Python download mirror, and parses the config file instead of executing it. 1.2.2 wrote mirror configuration where uv actually reads it and keeps CI tracking the latest uv release, on top of the 1.2.1 hardening: trusted local activation, real uv pip package transfer, valid mirror configuration, stable rename semantics, and release-grade integration tests.
- Conda-style commands with a small Bash footprint
- Shared environments under
UVM_ENVS_DIRand tracked custom--pathenvironments - Explicitly trusted upward auto-activation for local
.venv, plus managed.uvmrcactivation - Safer metadata storage under
envs.d/instead of fragile JSON string assembly run, metadata-onlyrename,clone,export,import, and self-update workflows- Managed shell and PyPI mirror updates with stable start/end markers
- Diagnostics and recovery commands:
uvm doctor,uvm repair - Linux, macOS, and Windows Git Bash support
- Bash or Zsh
uv- Linux, macOS, or Windows with Git Bash
Notes:
- PowerShell and CMD are not supported in this release.
- On Windows, install
uvin PowerShell first, then useuvmfrom Git Bash.
Interactive installation needs a real script file, so download it first instead of piping it directly into bash.
Linux / macOS:
curl -fsSL https://raw.githubusercontent.com/Tendo33/uvm/main/install.sh -o install.sh
bash install.sh
rm install.shWindows (Git Bash):
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
curl -fsSL https://raw.githubusercontent.com/Tendo33/uvm/main/install.sh -o install.sh
bash install.sh
rm install.shThe installer:
- installs
uvmto~/.local/bin/uvm - installs libraries to
~/.local/lib/uvm - initializes
UVM_HOMEat${UVM_HOME:-~/.config/uvm} - initializes
UVM_ENVS_DIRat~/uv_envsunless overridden - registers existing managed environments in the default environment directory
- writes managed PATH and shell-hook blocks instead of appending loose lines
- optionally updates the
uvPyPI index configuration through a validated managed block - when
install.shis fetched from a tagged release, remote file downloads stay pinned to that same tag by default
bash install.sh -yImportant:
- In non-interactive mode, missing
uvis a hard error. - Use this mode for CI, automation, or repeatable local setup.
- Existing
UVM_HOMEandUVM_ENVS_DIRconfiguration is preserved during non-interactive reinstall/update.
bash install.sh --envs-dir /path/to/envsThis writes the selected directory into $(uvm config show) through UVM_HOME/config.
When you download a release-scoped install.sh, the installer downloads bin/, lib/, and completions/ from the matching v<version> ref by default.
If you intentionally want another ref, override it explicitly:
UVM_DOWNLOAD_REF=main bash install.sh -ygit clone https://github.com/Tendo33/uvm.git
cd uvm
bash install.shuvm create myenv --python 3.11
source ~/.bashrc # or ~/.zshrc after install
uvm activate myenv
uvm list
uvm deactivate
uvm delete myenvuvm create myenv
uvm create myenv --python 3.12
uvm create myenv --path /work/envs/myenvBehavior:
- environment names are validated before any filesystem write
--pathcreates the environment at a custom location- custom-path environments are still tracked by metadata and show up in
uvm list
uvm activate myenvactivate must run inside shell integration because it needs to source the target environment into the current shell.
If it says shell integration is required, add this to your shell rc file:
eval "$(uvm shell-hook)"The installer can manage that block for you automatically.
uvm deactivateLike activate, this works through shell integration.
uvm list
uvm list --allBehavior:
- lists managed records from
envs.d/ - also discovers valid environments under the default
UVM_ENVS_DIR - marks the current active environment with
* --alladds the source column so you can see whether an entry ismanagedordiscovered
uvm delete myenv
uvm delete myenv --forceSafety rules:
- refuses invalid environment names
- refuses to delete the currently active environment
- refuses to delete unmanaged or out-of-scope paths
- removes both the directory and the corresponding metadata record
uvm run myenv python -V
uvm rename myenv renamed
uvm clone renamed copied
uvm export renamed > requirements.txt
uvm import restored --from requirements.txtrunexecutes in a subshell and does not modify the calling shell.renamechanges the managed name only; it does not move a non-relocatable venv.clone,export, andimportdelegate package operations touv pip --python, so seededpipis not required.- failed package import/clone operations return a failure instead of reporting partial success.
cd ~/project
uvm trust # review first; defaults to ./.venv
uvm trust list
uvm untrustLocal .venv activation scripts are executable shell code. uvm therefore refuses to source them automatically until their canonical path is explicitly trusted. Managed .uvmrc environments do not need this local-path trust step.
uvm update # latest release
uvm update --check # only report installed vs. available version
uvm update v1.2.3 # a specific release; also reinstalls the current onelatest resolves through GitHub Releases, refuses version downgrade, and preserves the configured environment directory. When you are already on the latest release it says so instead of reinstalling. If you installed without auto-activation, the update keeps it off. Restart your shell afterwards (exec "$SHELL") so the current session loads the new version.
Upgrading from a release older than 1.2.0 (uvm version shows 1.0.x or 1.1.x, and uvm update reports Unknown command): reinstall once, then uvm update is available from then on. Existing environments and the configured environment directory are kept.
curl -fsSL https://github.com/Tendo33/uvm/releases/latest/download/install.sh -o install.sh
bash install.sh -y
rm install.sh
exec "$SHELL"Older installers appended a bare eval "$(uvm shell-hook)" line to your shell rc file. The new installer writes its own block between # >>> uvm shell >>> markers, so delete the old bare line to avoid loading the hook twice.
uvm scan
uvm scan /path/to/envsScans a directory and registers valid environments found there.
uvm initInitializes UVM_HOME, ensures the default environment directory exists, and scans it. Mirror configuration remains opt-in.
uvm doctorReports:
- platform and shell
- resolved shell rc file
- shell hook status
UVM_HOMEUVM_ENVS_DIR- metadata record count
- whether
~/.local/binis inPATH - detected
uvversion - mirror block status
- current active environment
- whether the current environment came from auto-activation
Use this first when activate, list, or auto-activation does not behave as expected.
uvm repairRepairs safe, recoverable state by:
- pruning invalid metadata records
- rescanning the default
UVM_ENVS_DIR - rewriting the managed shell-hook block
- preserving any existing managed mirror block without inventing a replacement URL
It does not delete valid environments automatically.
uvm config show # every effective setting, including both mirrors
uvm config get envs-dir
uvm config set envs-dir ~/my-envs # directory for new environments
uvm config mirror set https://pypi.tuna.tsinghua.edu.cn/simple
uvm config mirror show
uvm config mirror remove
uvm config python-mirror set https://mirror.nju.edu.cn/github-release/astral-sh/python-build-standalone
uvm config python-mirror show
uvm config python-mirror removeconfig set envs-dircreates the directory, saves it, and registers any environments already inside it. Existing environments elsewhere stay where they are and remain registered.config mirrormanages the PyPI package index ([[index]]) used forpip install.config python-mirrormanagespython-install-mirror, which uv uses to download Python interpreters (uvm create --python 3.12on a machine without that version).- Both are written to uv's user config file (see Managed mirror block). If an unmanaged setting of the same kind already exists,
uvmleaves the file unchanged.
eval "$(uvm shell-hook)"This command emits the shell runtime needed for:
uvm activateuvm deactivate- prompt-based auto-activation checks
The shell hook no longer overrides cd. It uses prompt/chpwd hooks instead.
uvm checks for activation targets upward from the current directory on every prompt.
Priority order:
- nearest explicitly trusted parent
.venv - nearest parent
.uvmrc
That means:
- entering a project subdirectory still keeps the project environment active
- leaving the project tree deactivates auto-activated environments
.venvwins over.uvmrcwhen both exist in scope
An untrusted .venv is detected but never sourced. uvm doctor reports the pending path; inspect it and run uvm trust only when you accept its activation script.
cd ~/project
uv venv
uvm trust
cd ~/project/src/moduleAfter trust is granted, ~/project/.venv is auto-activated even from src/module.
uvm create shared-311 --python 3.11
echo "shared-311" > .uvmrcAny directory inside that project tree inherits the nearest parent .uvmrc.
UVM_HOME: defaults to~/.config/uvmUVM_ENVS_DIR: defaults to~/uv_envs; change it withuvm config set envs-dir <dir>- config file:
~/.config/uvm/config, plainKEY="value"lines that uvm parses but never executes - metadata directory:
~/.config/uvm/envs.d - the installer respects an already-exported
UVM_HOME
uvm now stores one record per environment:
~/.config/uvm/envs.d/
myenv.env
py311.env
Benefits:
- no manual JSON string assembly
- atomic record writes through temporary files + move
- stable add/update/remove behavior in pure Bash
- lock directory reserved for concurrent metadata writes
Legacy envs.json is only used for one-time migration when record files do not exist yet.
The installer and repair flow write stable markers such as:
# >>> uvm path >>>
export PATH="${HOME}/.local/bin:$PATH"
# <<< uvm path <<<
# >>> uvm shell >>>
eval "$(uvm shell-hook)"
# <<< uvm shell <<<These markers make install, repair, reinstall, and uninstall idempotent.
uvm config mirror set <url> updates only the managed PyPI index block inside uv's user-level uv.toml, at the same location uv reads:
- Linux / macOS:
$XDG_CONFIG_HOME/uv/uv.tomlwhenXDG_CONFIG_HOMEis an absolute path, otherwise~/.config/uv/uv.toml - Windows Git Bash:
%APPDATA%\uv\uv.toml
uvm doctor prints the resolved path. A managed block left in ~/.config/uv/uv.toml by uvm 1.2.1 or earlier, where uv does not read it, is reported by uvm doctor and removed by the next config mirror set or config mirror remove.
# >>> uvm mirror >>>
[[index]]
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
default = true
# <<< uvm mirror <<<uvm config python-mirror set <url> writes its own block at the top of the same file, because python-install-mirror is a top-level key and must come before any TOML table:
# >>> uvm python-mirror >>>
python-install-mirror = "https://mirror.nju.edu.cn/github-release/astral-sh/python-build-standalone"
# <<< uvm python-mirror <<<If uv.toml already exists, uvm keeps a one-time backup as uv.toml.backup.
An unmanaged [[index]] blocks config mirror set, and an unmanaged python-install-mirror blocks config python-mirror set; in both cases the file is left unchanged. The two mirrors are independent and neither is inferred from the other. The UV_DEFAULT_INDEX / UV_INDEX_URL and UV_PYTHON_INSTALL_MIRROR environment variables override them, and uvm config show points this out when they are set.
Run:
source ~/.bashrc # or ~/.zshrc
uvm doctorIf PATH ~/.local/bin shows missing, add:
export PATH="${HOME}/.local/bin:$PATH"or rerun:
bash install.sh -yRun:
uvm repair
source ~/.bashrc # or ~/.zshrcThen verify with:
uvm doctorChecklist:
- if it was created with
uvm create --path, confirm the path still exists - run
uvm repairto prune stale records and rescan the default directory - run
uvm list --allto inspect source labels
Checklist:
- the shell rc file contains the managed
uvm shellblock - the shell has been reloaded
.venvis a validuvenvironment with an activation script.uvmrccontains a valid environment name- the referenced shared environment still exists
Use:
uvm doctor
uvm repairuvm does not bundle uv.
- interactive install can offer installation on Linux/macOS
- non-interactive install fails if
uvis missing - Windows users should install
uvin PowerShell first
bash uninstall.sh
bash uninstall.sh --force
bash uninstall.sh --keep-shell-configUninstall removes:
~/.local/bin/uvm~/.local/lib/uvm- the effective
UVM_HOMEdirectory - managed shell blocks, unless
--keep-shell-configis used
Uninstall keeps:
- your virtual environments
uv- uv's
uv.toml
If you installed with a custom UVM_HOME, export the same value before uninstalling:
UVM_HOME=/custom/uvm-home bash uninstall.sh --forceDetails: project_document/UNINSTALL.md
- Linux: supported
- macOS: supported
- Windows Git Bash: supported
- PowerShell / CMD: not supported in this release
This repository currently includes:
- BATS tests for metadata, name validation, managed blocks,
doctor,repair,--path, and upward.uvmrcactivation - CI jobs for syntax checks, BATS, and Windows Git Bash smoke coverage
- environment export / import
- shell completion
- richer environment descriptions
- future shell adapters beyond Bash/Zsh after the current Bash core remains stable
MIT. See LICENSE.