Introduction
scoop is a centralized Python virtual environment manager powered by uv.
One scoop, endless envs — pyenv-style workflow with uv’s blazing speed.
What is scoop?
Think of it like running an ice cream parlor:
- The Freezer (
~/.scoop/) keeps all your flavors fresh - Flavors are your virtualenvs — mix once, serve anywhere
- One scoop is all you need to get the right env
| The Old Way | The scoop Way |
|---|---|
.venv scattered across projects | ~/.scoop/virtualenvs/ centralized |
Manual source .venv/bin/activate | Auto-activate on directory entry |
| pyenv-virtualenv is slow | uv-powered, 100x+ faster |
| Which Python? Which venv? Chaos. | scoop doctor checks everything |
Quick Example
# Install Python
scoop install 3.12
# Create a virtualenv
scoop create myproject 3.12
# Use it (auto-activates!)
scoop use myproject
(myproject) $ pip install -r requirements.txt
# Check what's available
scoop list
Features
- Fast — Powered by uv, virtualenv creation is nearly instant
- Centralized — All environments live in
~/.scoop/virtualenvs/ - Auto-activation — Enter a directory, environment activates automatically
- Shell integration — Works with bash, zsh, fish, and PowerShell
- IDE friendly —
scoop use --linkcreates.venvsymlink for IDE discovery - Health checks —
scoop doctordiagnoses your setup
Getting Started
Ready to scoop? Head to the Installation guide to get started.
Links
- GitHub Repository
- API Reference (docs.rs)
- Crates.io
- llms.txt — AI/LLM-friendly project reference
- llms-full.txt — Full API reference for AI tools
Installation
Prerequisites
| Dependency | Version | Install Command |
|---|---|---|
| uv | Latest | curl -LsSf https://astral.sh/uv/install.sh | sh |
| Rust | 1.85+ | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
Install via Cargo
cargo install scoop-uv
The binary is installed to ~/.cargo/bin/scoop.
Upgrade
To upgrade scoop to the latest version:
cargo install scoop-uv
This overwrites the existing binary in ~/.cargo/bin/scoop. Your virtual environments in ~/.scoop/ are preserved.
Verify the upgrade:
scoop --version
Verify Installation
scoop --version
# scoop 0.8.0
Troubleshooting
scoop: command not found
Ensure ~/.cargo/bin is in your PATH:
# Add to ~/.zshrc or ~/.bashrc
export PATH="$HOME/.cargo/bin:$PATH"
Then restart your terminal or run:
source ~/.zshrc # or ~/.bashrc
uv not found
scoop requires uv to be installed and available in PATH. Verify:
uv --version
If not installed, run:
curl -LsSf https://astral.sh/uv/install.sh | sh
Next Steps
After installation, set up Shell Integration to enable auto-activation and tab completion.
Quick Start
This guide walks you through the basic scoop workflow.
1. Set Up Shell Integration
Zsh (macOS default):
echo 'eval "$(scoop init zsh)"' >> ~/.zshrc
source ~/.zshrc
Bash:
echo 'eval "$(scoop init bash)"' >> ~/.bashrc
source ~/.bashrc
Fish:
echo 'eval (scoop init fish)' >> ~/.config/fish/config.fish
source ~/.config/fish/config.fish
PowerShell:
Add-Content -Path $PROFILE -Value 'Invoke-Expression (& scoop init powershell)'
. $PROFILE
2. Install Python
# Install latest Python
scoop install 3.12
# Verify installation
scoop list --pythons
3. Create a Virtual Environment
scoop create myproject 3.12
This creates a virtual environment at ~/.scoop/virtualenvs/myproject/.
4. Use the Environment
cd ~/projects/myproject
scoop use myproject
This:
- Creates
.scoop-versionfile in the current directory - Activates the environment (prompt shows
(myproject))
5. Work With Your Environment
(myproject) $ pip install -r requirements.txt
# If the file is in a different location:
(myproject) $ pip install -r path/to/requirements.txt
# Verify installed packages
(myproject) $ pip list
6. Auto-Activation
Once configured, entering a directory with .scoop-version automatically activates the environment:
cd ~/projects/myproject
# (myproject) appears in prompt automatically
Common Commands
| Task | Command |
|---|---|
| List environments | scoop list |
| List Python versions | scoop list --pythons |
| Show environment info | scoop info myproject |
| Remove environment | scoop remove myproject |
| Check installation | scoop doctor |
IDE Integration
Create a .venv symlink for IDE compatibility:
scoop use myproject --link
This creates .venv pointing to the scoop environment, recognized by VS Code, PyCharm, etc.
Next Steps
- See Shell Integration for advanced configuration
- See Commands for full command reference
Shell Integration
scoop uses a shell wrapper pattern (like pyenv) where the CLI outputs shell code that gets evaluated by the shell.
How It Works
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ User runs │ --> │ CLI outputs │ --> │ Shell evals │
│ scoop use │ │ export ... │ │ the output │
└─────────────┘ └─────────────┘ └─────────────┘
The scoop shell function wraps the CLI binary:
scoop() {
case "$1" in
use)
command scoop "$@"
local name=""
shift
for arg in "$@"; do
case "$arg" in
-*) ;;
*) name="$arg"; break ;;
esac
done
if [[ -n "$name" ]]; then
eval "$(command scoop activate "$name")"
fi
;;
activate|deactivate|shell)
eval "$(command scoop "$@")"
;;
*)
command scoop "$@"
;;
esac
}
Setup
Zsh
echo 'eval "$(scoop init zsh)"' >> ~/.zshrc
source ~/.zshrc
Bash
echo 'eval "$(scoop init bash)"' >> ~/.bashrc
source ~/.bashrc
Fish
echo 'eval (scoop init fish)' >> ~/.config/fish/config.fish
source ~/.config/fish/config.fish
PowerShell
# Add to $PROFILE
Add-Content $PROFILE 'Invoke-Expression (& scoop init powershell)'
# Restart PowerShell
Auto-Activation
When enabled, scoop automatically activates environments based on version files.
Zsh: Uses chpwd hook (runs on directory change)
autoload -Uz add-zsh-hook
add-zsh-hook chpwd _scoop_hook
Bash: Uses PROMPT_COMMAND
PROMPT_COMMAND="_scoop_hook;$PROMPT_COMMAND"
Fish: Uses --on-variable PWD event handler
function _scoop_hook --on-variable PWD
# Check for version file and activate/deactivate
end
The hook checks for version files and activates/deactivates accordingly.
Version Resolution Priority
scoop checks these sources in order (first match wins):
| Priority | Source | Set by |
|---|---|---|
| 1 | SCOOP_VERSION env var | scoop shell |
| 2 | .scoop-version file | scoop use (walks parent directories) |
| 3 | ~/.scoop/version file | scoop use --global |
The “system” Value
When any source contains the value system, scoop deactivates the current virtual environment and uses the system Python.
scoop use system # Write "system" to .scoop-version
scoop shell system # Set SCOOP_VERSION=system (this terminal only)
Environment Variables
| Variable | Description | Default |
|---|---|---|
SCOOP_HOME | Base directory | ~/.scoop |
SCOOP_VERSION | Override version (highest priority) | (unset) |
SCOOP_NO_AUTO | Disable auto-activation | (unset) |
SCOOP_ACTIVE | Currently active environment | (set by scoop) |
SCOOP_RESOLVE_MAX_DEPTH | Limit parent directory traversal | (unlimited) |
Disable Auto-Activation
export SCOOP_NO_AUTO=1
Temporary and Project-Scoped Control
Disable only in the current shell session (does not affect global settings):
export SCOOP_NO_AUTO=1
# ...work without auto-activation...
unset SCOOP_NO_AUTO
For one project directory, use local version files instead of global settings:
cd ~/project
# Keep auto-activation, but force system Python in this project only
scoop use system
# Or pin a specific environment for this project only
scoop use myproject
For temporary per-terminal overrides without changing files:
scoop shell system # this terminal only
# ...test...
scoop shell --unset # return to file-based behavior
Custom Home Directory
export SCOOP_HOME=/custom/path
Network Filesystem Optimization
For slow network filesystems (NFS, SSHFS), limit directory traversal depth:
# Only check current directory and up to 3 parents
export SCOOP_RESOLVE_MAX_DEPTH=3
# Only check current directory (fastest)
export SCOOP_RESOLVE_MAX_DEPTH=0
Using with pyenv
Add scoop after pyenv in your shell config:
# ~/.zshrc
eval "$(pyenv init -)" # 1. pyenv first
eval "$(scoop init zsh)" # 2. scoop second (takes precedence)
Tab Completion
Shell integration includes completion for:
- Commands and subcommands
- Environment names
- Python versions
- Command options
Completion is automatically enabled by scoop init.
Supported Shells
| Shell | Status |
|---|---|
| Zsh | Full support (auto-activation, completion) |
| Bash | Full support (auto-activation, completion) |
| Fish | Full support (auto-activation, completion) |
| PowerShell | Full support (auto-activation, completion) |
Python Management
scoop delegates all Python installation and discovery to uv. This page explains how Python versions are found, installed, and used with scoop.
How Python Discovery Works
When you run scoop create myenv 3.12, scoop asks uv to create a virtual environment with Python 3.12. uv searches for a matching Python in this order:
- uv-managed installations in
~/.local/share/uv/python/(installed viascoop installoruv python install) - System Python on PATH — executables named
python,python3, orpython3.x - Platform-specific locations — Windows registry, Microsoft Store (Windows only)
Key behavior: For managed Pythons, uv prefers the newest matching version. For system Pythons, uv uses the first compatible version found on PATH.
Installing Python Versions
Via scoop (recommended)
# Install latest Python
scoop install
# Install specific minor version (latest patch)
scoop install 3.12
# Install exact version
scoop install 3.12.3
# List installed versions
scoop list --pythons
Behind the scenes
scoop install 3.12 runs uv python install 3.12 internally. uv downloads a standalone Python build from the python-build-standalone project and stores it in ~/.local/share/uv/python/.
Using System Python
scoop can use Python versions already installed on your system (via Homebrew, apt, the OS, etc.) — no scoop install needed.
# Check what Python versions uv can find on your system
uv python list
# Example output:
# cpython-3.13.1 /opt/homebrew/bin/python3.13 (system)
# cpython-3.12.8 ~/.local/share/uv/python/... (managed)
# cpython-3.12.0 /usr/bin/python3 (system)
# cpython-3.11.5 /usr/bin/python3.11 (system)
# Create environment using system Python 3.13
# (uv finds it automatically — no scoop install needed)
scoop create myenv 3.13
If the version you request matches a system Python, uv will use it. You only need scoop install if the version is not already available on your system.
Using Custom Python Installations
If you have a custom-built Python or an alternative interpreter (PyPy, GraalPy) in a non-standard location, you can point scoop directly to the executable.
When the required version is not in default sources
If scoop install <version> and normal uv discovery do not provide the interpreter you need,
integrate your own Python using one of these patterns:
- Direct path (recommended):
scoop create <env> --python-path /path/to/python - PATH-based discovery: add your Python to
PATH, then runscoop create <env> <version>
Use –python-path (recommended)
The simplest approach is to pass the Python executable path directly:
# Custom Python built from source
scoop create debug-env --python-path /opt/python-debug/bin/python3
# PyPy interpreter
scoop create pypy-env --python-path /opt/pypy/bin/pypy3
# GraalPy
scoop create graal-env --python-path /opt/graalpy/bin/graalpy
scoop validates the path, auto-detects the version, and stores the custom path in metadata.
# Verify what was integrated
scoop info debug-env
# Name: debug-env
# Python: 3.13.0
# Python Path: /opt/python-debug/bin/python3
Metadata is stored in ~/.scoop/virtualenvs/<name>/.scoop-metadata.json (python_path field).
See create command for details.
Alternative: Add custom Python to PATH
You can also add the Python to your PATH so uv discovers it automatically:
# Example: custom Python built from source in /opt/python-debug/
export PATH="/opt/python-debug/bin:$PATH"
# Verify uv can find it
uv python list | grep python
# cpython-3.13.0 /opt/python-debug/bin/python3.13
# Now scoop can use it
scoop create debug-env 3.13
Use UV_PYTHON_INSTALL_DIR
For managed Python installations in a custom location:
# Store uv-managed Pythons in a custom directory
export UV_PYTHON_INSTALL_DIR=/opt/shared-pythons
# Install Python to the custom location
scoop install 3.12
# All team members can share the same Python installations
Python preference settings
Control whether uv prefers managed or system Python:
# Use only uv-managed Python (ignore system Python)
UV_PYTHON_PREFERENCE=only-managed scoop create myenv 3.12
# Use only system Python (ignore uv-managed)
UV_PYTHON_PREFERENCE=only-system scoop create myenv 3.12
# Prefer system Python over managed (default: managed first)
UV_PYTHON_PREFERENCE=system scoop create myenv 3.12
Migrating from Other Tools
If you have existing virtual environments in pyenv, conda, or virtualenvwrapper, scoop can migrate them:
# See what can be migrated
scoop migrate list
# Example output:
# pyenv-virtualenv:
# myproject (Python 3.12.0)
# webapp (Python 3.11.8)
# conda:
# ml-env (Python 3.10.4)
# Migrate a specific environment
scoop migrate @env myproject
# Migrate everything at once
scoop migrate all
The migration process:
- Discovers environments from pyenv (
~/.pyenv/versions/), conda (conda info --envs), or virtualenvwrapper ($WORKON_HOME) - Creates a new scoop environment with the same Python version
- Reinstalls packages using uv for improved performance
- Preserves originals by default (use
--delete-sourceto remove source envs after success)
See migrate command for details.
Troubleshooting
Python version not found
$ scoop create myenv 3.14
# Error: Python 3.14 not found
# Solution 1: Install it via scoop
scoop install 3.14
# Solution 2: Check what's available
uv python list
scoop list --pythons
Invalid custom Python path
# Example custom path flow
scoop create myenv --python-path /opt/custom/python3
# Verify the binary exists and runs
/opt/custom/python3 --version
If the path is invalid or not executable, provide a valid Python binary path and retry.
Verify custom integration end-to-end
# 1) Confirm uv can see your interpreter (PATH-based flow)
uv python list
# 2) Confirm scoop recorded the interpreter path
scoop info myenv
# 3) Diagnose broken links or metadata issues
scoop doctor -v
Using a different Python than expected
# Check which Python uv would select for a version
uv python find 3.12
# /opt/homebrew/bin/python3.12
# Check all available 3.12 installations
uv python list | grep 3.12
# cpython-3.12.8 /opt/homebrew/bin/python3.12 (system)
# cpython-3.12.7 ~/.local/share/uv/python/... (managed)
Verify environment’s Python
# Check what Python an environment uses
scoop info myenv
# Name: myenv
# Python: 3.12.8
# Path: ~/.scoop/virtualenvs/myenv
Removing Python Versions
Quick: Cascade removal (recommended)
Use --cascade to automatically remove all environments using a Python version:
# Remove Python 3.12 and all environments using it
scoop uninstall 3.12 --cascade
# Skip confirmation prompt
scoop uninstall 3.12 --cascade --force
Preview affected environments
Before uninstalling, you can check which environments would be affected:
# Filter environments by Python version
scoop list --python-version 3.12
# myproject 3.12.1
# webapp 3.12.0
Manual workflow
If you prefer manual control (without --cascade):
# 1. Identify environments using the target Python version
scoop list --python-version 3.12
# myproject 3.12.1
# webapp 3.12.1
# 2. Remove or recreate affected environments
scoop remove myproject --force
scoop remove webapp --force
# Or recreate with a different version:
# scoop remove myproject --force && scoop create myproject 3.13
# 3. Uninstall the Python version
scoop uninstall 3.12
# 4. Verify everything is clean
scoop list --pythons # Confirm Python removed
scoop doctor # Check for broken environments
Recovery from accidental uninstall
If you uninstalled Python without cleaning up environments:
# Detect broken environments
scoop doctor -v
# ⚠ Environment 'myproject': Python symlink broken
# Fix by reinstalling the Python version
scoop install 3.12
scoop doctor --fix
# Or remove the broken environments and start fresh
scoop remove myproject --force
See uninstall command and doctor command for details.
Summary
| Scenario | What to do |
|---|---|
| Standard Python version | scoop install 3.12 then scoop create myenv 3.12 |
| System Python (Homebrew, apt) | Just scoop create myenv 3.12 — uv finds it automatically |
| Custom Python executable | scoop create myenv --python-path /path/to/python |
| Custom Python in non-standard path | Add to PATH, then scoop create myenv <version> |
| PyPy or alternative interpreter | scoop create myenv --python-path /opt/pypy/bin/pypy3 |
| Existing pyenv/conda environments | scoop migrate all |
| Shared Python installations | Set UV_PYTHON_INSTALL_DIR |
| Force system-only Python | Set UV_PYTHON_PREFERENCE=only-system |
| Uninstall Python + cleanup envs | scoop uninstall 3.12 --cascade (or manual workflow) |
| Find envs using a Python version | scoop list --python-version 3.12 |
| Fix broken environments | scoop doctor --fix (after reinstalling the Python version) |
Frequently Asked Questions
What’s the difference between scoop and pyenv?
While both tools help you manage Python, they focus on different parts of the workflow:
pyenv is primarily a version manager. It focuses on:
- Installing multiple versions of the Python interpreter (e.g., 3.9.0, 3.12.1)
- Switching between them globally or per folder
scoop is an environment and workflow manager powered by uv. It focuses on:
- Creating and managing isolated virtual environments
- Fast project-specific environment workflows
Summary: You might use pyenv to install Python 3.11 on your machine, but you use scoop to actually build and run your application within a lightning-fast virtual environment using that Python version.
How do I set Python 3.11.0 as the global default for all new shells and environments?
Use this workflow:
# 1) Install Python 3.11.0 (skip if already available on your system)
scoop install 3.11.0
# 2) Create an environment that uses 3.11.0
scoop create py311 3.11.0
# 3) Make that environment the global default
scoop use py311 --global
Important details:
--globalstores an environment name in~/.scoop/version, not a raw version like3.11.0.- This global default is applied in new shells and directories without a local
.scoop-version. - Priority is:
SCOOP_VERSIONenv var > local.scoop-version> global~/.scoop/version.
To remove the global default later:
scoop use --unset --global
How do I create a new virtual environment for a project, explicitly specifying Python 3.9.5?
Use this end-to-end workflow:
# 1) Install Python 3.9.5 (skip if already available on your system)
scoop install 3.9.5
# 2) Create a new project environment with that exact version
scoop create myproject 3.9.5
# 3) Verify which Python the environment uses
scoop info myproject
If creation fails because 3.9.5 is not found, run:
uv python list
scoop list --pythons
Then install the exact version and retry:
scoop install 3.9.5
scoop create myproject 3.9.5
How do I uninstall a specific Python version and all its associated virtual environments managed by scoop?
Use --cascade to remove both the Python version and every environment that depends on it:
# 1) Optional: preview affected environments
scoop list --python-version 3.12
# 2) Remove Python 3.12 and all associated environments
scoop uninstall 3.12 --cascade
# 3) Verify cleanup
scoop list --pythons
scoop doctor
Useful variants:
- Non-interactive mode:
scoop uninstall 3.12 --cascade --force - JSON output for automation:
scoop uninstall 3.12 --cascade --json
Important detail:
- Without
--cascade, environments are not removed and can become broken.
Given Scoop-uv’s auto-activation feature, how would a developer temporarily disable or customize its behavior for a specific project or directory without affecting global settings?
Use one of these local or temporary patterns:
# Option 1) Disable auto-activation only in the current shell session
export SCOOP_NO_AUTO=1
# ...work here...
unset SCOOP_NO_AUTO
# Option 2) For one project directory, force system Python locally
cd ~/project
scoop use system
# Option 3) For one project directory, pin a specific environment locally
scoop use myproject
# Option 4) Temporary override in this terminal only (no file changes)
scoop shell system
# ...test...
scoop shell --unset
Notes:
- These approaches avoid
--global, so global defaults are unchanged. .scoop-versionchanges fromscoop use ...are local to the project directory (and inherited by subdirectories).scoop shell ...affects only the current terminal session.
Once a Scoop-uv environment is active, how would you install project dependencies from a requirements.txt file into it?
Run pip inside the active environment:
# Prompt shows active environment, e.g. (myproject)
pip install -r requirements.txt
Useful variants:
- Different file location:
pip install -r path/to/requirements.txt - Verify installed dependencies:
pip list
If requirements.txt is in the project root, run the command from that directory.
How can a developer list all Python versions and their associated virtual environments currently managed by Scoop-uv?
Use this sequence:
# 1) Show all managed Python versions
scoop list --pythons
# 2) Show all environments and their Python versions
scoop list
# 3) Show environments for one specific Python version
scoop list --python-version 3.12
For automation:
- Use
--jsonfor machine-readable output. - Use
--barefor name-only output in shell scripts.
Example script to iterate each Python version and print associated environments:
for v in $(scoop list --pythons --bare); do
echo "== Python $v =="
scoop list --python-version "$v" --bare
done
If no versions or environments exist yet, these commands simply return empty results.
If a project requires a Python version not directly available through Scoop-uv’s default sources, how could a developer integrate a custom or pre-existing Python installation into Scoop-uv’s management system?
Use one of these two approaches:
# Option 1) Recommended: point directly to a Python executable
scoop create myenv --python-path /opt/python-debug/bin/python3
# Option 2) Add custom Python to PATH, then use normal version selection
export PATH="/opt/python-debug/bin:$PATH"
scoop create myenv 3.13
Validation and diagnostics:
uv python list # confirm interpreter discovery
scoop info myenv # confirm selected Python + Python Path
scoop doctor -v # detect broken links/metadata issues
Where scoop stores this integration:
- Environment metadata file:
~/.scoop/virtualenvs/myenv/.scoop-metadata.json - Custom interpreter path is recorded in the
python_pathfield.
Can I use scoop with conda environments?
Not directly. They serve different purposes and operate independently:
conda is a package and environment manager. It handles:
- Its own binaries and non-Python dependencies
- Heavy data science libraries (MKL, CUDA, cuDNN, etc.)
scoop is a lightweight environment manager powered by uv. It:
- Leverages your existing Python installations
- Creates fast, portable virtual environments
When to use what: For heavy data science requiring non-Python libraries → conda. For almost everything else → scoop (significantly faster and more portable).
How do I uninstall scoop completely?
To remove scoop from your system:
1. Delete the data folder
rm -rf ~/.scoop
2. Remove the shell hook
Edit your shell config file and remove the scoop init line:
| Shell | Config File | Line to Remove |
|---|---|---|
| Bash | ~/.bashrc | eval "$(scoop init bash)" |
| Zsh | ~/.zshrc | eval "$(scoop init zsh)" |
| Fish | ~/.config/fish/config.fish | eval (scoop init fish) |
| PowerShell | $PROFILE | Invoke-Expression (& scoop init powershell) |
3. (Optional) Remove config
rm -f ~/.scoop/config.json
4. Restart your terminal
Does scoop work on Windows?
scoop supports PowerShell on Windows (both PowerShell Core 7.x+ and Windows PowerShell 5.1+). Shell integration including auto-activation and tab completion works fully.
# Add to $PROFILE
Invoke-Expression (& scoop init powershell)
Note: Command Prompt (cmd.exe) is not supported. Use PowerShell for the full scoop experience.
Can I use a custom or pre-existing Python with scoop?
Yes, in two ways:
Option 1: Use –python-path (recommended for custom builds)
Point directly to any Python executable:
# Custom-built Python
scoop create debug-env --python-path /opt/python-debug/bin/python3
# PyPy interpreter
scoop create pypy-env --python-path /opt/pypy/bin/pypy3
# GraalPy
scoop create graal-env --python-path /opt/graalpy/bin/graalpy
scoop validates the path, auto-detects the version, and stores it in metadata.
Option 2: System Python via uv discovery
scoop uses uv for Python discovery, which automatically finds Python installations on your system:
# Check what Python versions uv can discover
uv python list
# Example output:
# cpython-3.13.1 /opt/homebrew/bin/python3.13 (system)
# cpython-3.12.8 ~/.local/share/uv/python/... (managed)
# cpython-3.11.5 /usr/bin/python3.11 (system)
# Use a system-installed Python directly (no scoop install needed)
scoop create myenv 3.13
For a custom Python in a non-standard location, add it to your PATH:
export PATH="/opt/python-debug/bin:$PATH"
scoop create debug-env 3.13
See also: Python Management for the full guide on Python discovery, system Python, custom interpreters, and environment variables.
Can I migrate environments from pyenv or conda?
Yes. scoop can discover and migrate existing environments from pyenv-virtualenv, conda, and virtualenvwrapper:
# See what can be migrated
scoop migrate list
# pyenv-virtualenv:
# myproject (Python 3.12.0)
# conda:
# ml-env (Python 3.10.4)
# Migrate a specific environment
scoop migrate @env myproject
# Migrate everything at once
scoop migrate all
The original environments are preserved by default. Use --delete-source to remove source envs after successful migration. See migrate command for details.
Command Reference
Complete reference for all scoop commands.
Commands Overview
| Command | Aliases | Description |
|---|---|---|
scoop list | ls | List virtualenvs or Python versions |
scoop create | - | Create virtualenv |
scoop use | - | Set + activate environment |
scoop remove | rm, delete | Remove virtualenv |
scoop install | - | Install Python version |
scoop uninstall | - | Uninstall Python version |
scoop doctor | - | Diagnose installation |
scoop info | - | Show virtualenv details |
scoop migrate | - | Migrate from pyenv/conda/venvwrapper |
scoop lang | - | Get/set display language |
scoop shell | - | Set shell-specific env (temporary) |
scoop init | - | Shell init script |
scoop completions | - | Completion script |
Global Options
Available for all commands:
| Option | Description |
|---|---|
-q, --quiet | Suppress all output |
--no-color | Disable colored output |
-h, --help | Show help message |
-V, --version | Show version |
Environment Variables
| Variable | Description | Default |
|---|---|---|
SCOOP_HOME | Base directory for scoop | ~/.scoop |
SCOOP_NO_AUTO | Disable auto-activation | (unset) |
SCOOP_LANG | Display language (en, ko, ja, pt-BR) | System locale |
NO_COLOR | Disable colored output | (unset) |
Directory Layout
| Location | Purpose |
|---|---|
~/.scoop/virtualenvs/ | Virtual environments storage |
~/.scoop/version | Global default environment |
.scoop-version | Local environment preference |
.venv | Symlink to active environment (with --link) |
list
List all virtual environments or installed Python versions.
Aliases: ls
Usage
scoop list [options]
Options
| Option | Description |
|---|---|
--pythons | Show Python versions instead of virtualenvs |
--python-version <VERSION> | Filter environments by Python version (e.g., 3.12) |
--bare | Output names only (for scripting) |
--json | Output as JSON |
Examples
scoop list # List all virtualenvs
scoop list --pythons # List installed Python versions
scoop list --bare # Names only, one per line
scoop list --json # JSON output
# Filter by Python version
scoop list --python-version 3.12 # Show only 3.12.x environments
scoop list --python-version 3 # Show all Python 3.x environments
scoop list --python-version 3.12.1 # Exact version match
List Python Versions with Associated Environments
Use this workflow to see both sides of the mapping:
# 1) List installed Python versions managed by scoop/uv
scoop list --pythons
# 2) List all virtual environments with their Python versions
scoop list
# 3) Show environments associated with a specific Python version
scoop list --python-version 3.12
For scripting, combine --bare with per-version filtering:
for v in $(scoop list --pythons --bare); do
echo "== Python $v =="
scoop list --python-version "$v" --bare
done
You can also use --json for machine-readable output:
scoop list --pythons --json
scoop list --json
Version Filtering
The --python-version option uses prefix matching to filter environments:
scoop list --python-version 3.12
# Output:
# myproject 3.12.1
# webapp 3.12.0
# (environments using 3.11 or 3.13 are not shown)
This is useful for identifying environments before uninstalling a Python version:
# See which environments will be affected
scoop list --python-version 3.12
# Then uninstall with cascade
scoop uninstall 3.12 --cascade
Note:
--python-versioncannot be combined with--pythons(which lists Python installations, not environments).
Empty Results
- If no Python versions are installed,
scoop list --pythonsshows no entries. - If no environments exist,
scoop listshows no entries. - If no environments match a filter,
scoop list --python-version <VERSION>shows no entries.
create
Create a new virtual environment.
Usage
scoop create <name> [python-version]
Arguments
| Argument | Required | Default | Description |
|---|---|---|---|
name | Yes | - | Name for the new virtualenv |
python-version | No | 3 (latest) | Python version (e.g., 3.12, 3.11.8) |
Options
| Option | Description |
|---|---|
--force, -f | Overwrite existing virtualenv |
--python-path <PATH> | Use a specific Python executable instead of version discovery |
Examples
scoop create myproject 3.12 # Create with Python 3.12
scoop create webapp # Create with latest Python
scoop create myenv 3.11 --force # Overwrite if exists
# Use a specific Python executable
scoop create myenv --python-path /opt/python-debug/bin/python3
scoop create graal --python-path /opt/graalpy/bin/graalpy
Create a Project Environment with Python 3.9.5
# Install exact Python version (skip if already available)
scoop install 3.9.5
# Create a new project environment using that exact version
scoop create myproject 3.9.5
# Verify the environment uses Python 3.9.5
scoop info myproject
If 3.9.5 is not available, install it first with scoop install 3.9.5, then check discovery with
uv python list and scoop list --pythons.
Python Version Resolution
scoop delegates Python discovery to uv. The python-version argument is passed to uv venv --python, which searches for a match in:
- uv-managed Python installations
- System Python on
PATH(Homebrew, apt, pyenv, etc.) - Platform-specific locations (Windows only)
# Uses uv-managed Python 3.12 (if installed via scoop install)
scoop create myenv 3.12
# Also works with system Python — no scoop install needed
# (e.g., if Homebrew has python@3.13)
scoop create myenv 3.13
# Check what Python versions are available
uv python list
scoop list --pythons
Tip: If the version isn’t found, install it first with
scoop install 3.12. See Python Management for custom Python paths.
Custom Python Executable
Use --python-path to create a virtualenv with a specific Python binary. This is useful for:
- Custom-built Python (debug builds, optimized builds)
- Alternative interpreters (PyPy, GraalPy)
- Python installations in non-standard locations
# Debug build from source
scoop create debug-env --python-path /opt/python-debug/bin/python3
# PyPy interpreter
scoop create pypy-env --python-path /opt/pypy/bin/pypy3
# GraalPy
scoop create graal-env --python-path /opt/graalpy/bin/graalpy
The path must point to a valid, executable Python binary. scoop will:
- Validate the path (exists, is a file, is executable)
- Auto-detect the Python version from the binary
- Store the custom path in the environment’s metadata
You can verify the custom path with scoop info:
scoop info debug-env
# Name: debug-env
# Python: 3.13.0
# Python Path: /opt/python-debug/bin/python3
# Path: ~/.scoop/virtualenvs/debug-env
use
Set a virtual environment for the current directory and activate it.
Usage
scoop use <name> [options]
scoop use system [options]
scoop use --unset [options]
Arguments
| Argument | Required | Description |
|---|---|---|
name | No | Name of the virtualenv, or system for system Python |
Options
| Option | Description |
|---|---|
--unset | Remove version file (local or global) |
--global, -g | Set as global default |
--link | Create .venv symlink for IDE compatibility |
--no-link | Do not create .venv symlink (default) |
Behavior
- Creates
.scoop-versionfile in current directory - Immediately activates the environment (if shell hook installed)
- With
--global: writes to~/.scoop/version - With
--link: creates.venv -> ~/.scoop/virtualenvs/<name>
Special Value: system
Using system as the name tells scoop to use the system Python:
scoop use system # Use system Python in this directory
scoop use system --global # Use system Python as global default
This writes the literal string system to the version file, which the shell hook interprets as “deactivate any virtual environment.”
The --unset Flag
Removes the version file entirely:
scoop use --unset # Delete .scoop-version in current directory
scoop use --unset --global # Delete ~/.scoop/version
After unsetting, scoop falls back to the next priority level in version resolution.
Examples
# Use a virtual environment in this directory
scoop use myproject
# Also create .venv symlink (for IDE support)
scoop use myproject --link
# Set global default environment
scoop use myproject --global
# Use system Python in this directory
scoop use system
# Use system Python globally
scoop use system --global
# Remove local version setting
scoop use --unset
# Remove global version setting
scoop use --unset --global
Set Python 3.11.0 as Global Default
--global stores an environment name, not a raw Python version string.
Create an environment with Python 3.11.0, then set that environment globally:
scoop install 3.11.0
scoop create py311 3.11.0
scoop use py311 --global
This writes py311 to ~/.scoop/version, which is used in new shell sessions and
directories that do not have a local .scoop-version.
If a local .scoop-version file or SCOOP_VERSION environment variable is present,
it takes precedence over the global setting.
Version File Format
The .scoop-version file contains a single line with either:
- An environment name (e.g.,
myproject) - The literal string
system
$ cat .scoop-version
myproject
remove
Remove a virtual environment.
Aliases: rm, delete
Usage
scoop remove <name> [options]
Arguments
| Argument | Required | Description |
|---|---|---|
name | Yes | Name of the virtualenv to remove |
Options
| Option | Description |
|---|---|
--force, -f | Skip confirmation prompt |
Examples
scoop remove myproject # Remove with confirmation
scoop remove myproject --force # Remove without asking
scoop rm old-env -f # Using alias
Check Before Removing
To see details about an environment before removing it:
# Show environment details (Python version, path, packages)
scoop info myproject
# Output:
# Name: myproject
# Python: 3.12.1
# Path: ~/.scoop/virtualenvs/myproject
Removing All Environments for a Python Version
To remove all environments that use a specific Python version:
# List environments to identify which use Python 3.12
scoop list
# myproject 3.12.1
# webapp 3.12.1
# ml-env 3.11.8
# Remove each one
scoop remove myproject --force
scoop remove webapp --force
# Then optionally uninstall the Python version itself
scoop uninstall 3.12
See also: uninstall command for the complete workflow to uninstall a Python version and clean up associated environments.
install
Install a Python version.
Usage
scoop install [version] [options]
Arguments
| Argument | Required | Default | Description |
|---|---|---|---|
version | No | latest | Python version (e.g., 3.12, 3.11.8) |
Options
| Option | Description |
|---|---|
--latest | Install latest stable Python (default) |
--stable | Install oldest fully-supported Python (3.10) |
Version Resolution
- No argument or
--latest: installs latest Python 3.x --stable: installs Python 3.10 (oldest with active security support)3.12: installs latest 3.12.x patch3.12.3: installs exact version
Examples
scoop install # Install latest
scoop install --latest # Same as above
scoop install --stable # Install Python 3.10
scoop install 3.12 # Install latest 3.12.x
scoop install 3.12.3 # Install exact 3.12.3
Note: Python versions are managed by uv.
Python Discovery
You don’t always need scoop install. When you run scoop create, uv searches for a matching Python in this order:
- uv-managed — installed via
scoop installoruv python install - System PATH — Homebrew, apt, pyenv, or any Python on your
PATH - Platform-specific — Windows registry, Microsoft Store
# See all Python versions uv can find
uv python list
# Example output:
# cpython-3.13.1 /opt/homebrew/bin/python3.13 (system)
# cpython-3.12.8 ~/.local/share/uv/python/... (managed)
# cpython-3.11.5 /usr/bin/python3.11 (system)
# Use system Python directly — no scoop install needed
scoop create myenv 3.13
If the requested version isn’t found anywhere, scoop create will fail with an error. Use scoop install <version> to download it first.
See also: Python Management for custom Python paths, environment variables, and migration.
uninstall
Remove an installed Python version.
Usage
scoop uninstall <version>
Arguments
| Argument | Required | Description |
|---|---|---|
version | Yes | Python version to remove |
Options
| Option | Description |
|---|---|
--cascade | Also remove all virtual environments using this Python version |
--force, -f | Skip confirmation for cascade removal (requires --cascade) |
Examples
scoop uninstall 3.12 # Remove Python 3.12
scoop uninstall 3.11.8 # Remove specific version
# Remove Python and all environments using it
scoop uninstall 3.12 --cascade
# Remove without confirmation prompt
scoop uninstall 3.12 --cascade --force
Uninstall a Python Version and All Associated Environments
Recommended workflow for a full cleanup:
# 1) Optional: preview which environments would be removed
scoop list --python-version 3.12
# 2) Remove Python 3.12 and all environments using it
scoop uninstall 3.12 --cascade
# 3) Verify cleanup
scoop list --pythons
scoop doctor
For non-interactive scripts, skip the confirmation prompt:
scoop uninstall 3.12 --cascade --force
If the target version is not installed, check available versions first:
scoop list --pythons
Cascade Removal
The --cascade flag automatically removes all virtual environments that use the target Python version before uninstalling it. This replaces the manual multi-step workflow.
scoop uninstall 3.12 --cascade
# Finding environments using Python 3.12...
# Found 2 environments using Python 3.12:
# - myproject
# - webapp
# Remove these environments and uninstall Python 3.12? [y/N]
# Removing myproject...
# Removing webapp...
# Uninstalling Python 3.12...
# ✓ Python 3.12 uninstalled
With --force, the confirmation prompt is skipped:
scoop uninstall 3.12 --cascade --force
With --json, the output includes the list of removed environments:
scoop uninstall 3.12 --cascade --json
# {
# "status": "success",
# "command": "uninstall",
# "data": {
# "version": "3.12",
# "removed_envs": ["myproject", "webapp"]
# }
# }
Note: Without
--cascade, uninstalling a Python version does not remove virtual environments that were created with it. Those environments will become broken. Use--cascadeto handle this automatically, or follow the manual workflow below.
Manual Uninstall Workflow
If you prefer manual control (without --cascade):
Step 1: Identify affected environments
# List environments filtered by Python version
scoop list --python-version 3.12
# Output:
# myproject 3.12.1
# webapp 3.12.1
# Or use JSON for scripting
scoop list --json
Step 2: Handle affected environments
# Option A: Remove the environment entirely
scoop remove myproject --force
# Option B: Recreate with a different Python version
scoop remove myproject --force
scoop create myproject 3.13
# Option C: Keep it (will be broken until you reinstall that Python)
# Do nothing — scoop doctor can detect and help fix it later
Step 3: Uninstall the Python version
scoop uninstall 3.12
Step 4: Verify
# Confirm Python is removed
scoop list --pythons
# Check for broken environments
scoop doctor
# If any issues found:
scoop doctor --fix
Recovery
If you uninstalled a Python version without cleaning up environments first:
# Detect broken environments
scoop doctor -v
# Output:
# ⚠ Environment 'myproject': Python symlink broken
# Option 1: Reinstall the Python version
scoop install 3.12
scoop doctor --fix
# Option 2: Recreate affected environments with a new version
scoop remove myproject --force
scoop create myproject 3.13
doctor
Check scoop installation health and diagnose issues.
Usage
scoop doctor [options]
Options
| Option | Description |
|---|---|
-v, --verbose | Show more details (can repeat: -vv) |
--json | Output diagnostics as JSON |
--fix | Auto-fix issues where possible |
Checks Performed
| Check | What it verifies |
|---|---|
| uv installation | uv is installed and accessible |
| Shell integration | Shell hook is properly configured |
| Environment integrity | Python symlinks are valid, pyvenv.cfg exists |
| Path configuration | ~/.scoop/ directory structure is correct |
| Version file validity | .scoop-version files reference existing environments |
Examples
scoop doctor # Quick health check
scoop doctor -v # Verbose diagnostics
scoop doctor --fix # Fix what can be fixed
scoop doctor --json # JSON output for scripting
Environment Integrity
The doctor checks each virtual environment for:
- Python symlink — Does the
pythonbinary in the environment point to a valid Python installation? - pyvenv.cfg — Does the environment’s configuration file exist and reference a valid Python?
Environments can become broken when their underlying Python version is uninstalled. Use scoop doctor to detect these issues:
# After accidentally uninstalling Python 3.12:
scoop doctor -v
# Output:
# ✓ uv: installed (0.5.x)
# ✓ Shell: zsh integration active
# ⚠ Environment 'myproject': Python symlink broken
# ⚠ Environment 'webapp': Python symlink broken
# Auto-fix by recreating symlinks (requires Python to be reinstalled)
scoop install 3.12
scoop doctor --fix
# Output:
# ✓ Fixed 'myproject': Python symlink restored
# ✓ Fixed 'webapp': Python symlink restored
Tip: Run
scoop doctorperiodically or after uninstalling Python versions to catch broken environments early. See uninstall command for the safe uninstall workflow.
info
Show detailed information about a virtual environment.
Usage
scoop info <name>
Arguments
| Argument | Required | Description |
|---|---|---|
name | Yes | Name of the virtualenv |
Examples
scoop info myproject # Show details for myproject
migrate
Migrate virtual environments from other tools (pyenv-virtualenv, virtualenvwrapper, conda).
Usage
# List migratable environments
scoop migrate list
# Migrate a single environment
scoop migrate @env <name>
# Migrate all environments
scoop migrate all
Subcommands
| Subcommand | Description |
|---|---|
list | List environments available for migration |
@env <name> | Migrate a single environment by name |
all | Migrate all discovered environments |
Supported Sources
| Source | Detection |
|---|---|
| pyenv-virtualenv | ~/.pyenv/versions/ (non-system virtualenvs) |
| virtualenvwrapper | $WORKON_HOME or ~/.virtualenvs/ |
| conda | conda info --envs |
Options
| Option | Description |
|---|---|
--json | Output as JSON |
--quiet | Suppress output |
--no-color | Disable colored output |
Examples
List Migratable Environments
$ scoop migrate list
📦 Migratable Environments
pyenv-virtualenv:
• myproject (Python 3.12.0)
• webapp (Python 3.11.8)
conda:
• ml-env (Python 3.10.4)
Migrate Single Environment
$ scoop migrate @env myproject
✓ Migrated 'myproject' from pyenv-virtualenv
Source: ~/.pyenv/versions/myproject
Target: ~/.scoop/virtualenvs/myproject
Migrate All
$ scoop migrate all
✓ Migrated 3 environments
• myproject (pyenv-virtualenv)
• webapp (pyenv-virtualenv)
• ml-env (conda)
JSON Output
$ scoop migrate list --json
{
"status": "success",
"data": {
"environments": [
{
"name": "myproject",
"source": "pyenv",
"python_version": "3.12.0"
}
]
}
}
Migration Process
- Discovery: Scans configured source paths for virtual environments
- Extraction: Identifies Python version and installed packages
- Recreation: Creates new scoop environment with same Python version
- Package Install: Reinstalls packages using
uv pip install - Cleanup: Originals are preserved by default;
--delete-sourceremoves them after successful migration
Notes
- Original environments are preserved by default; use
--delete-sourceto remove sources after migration - Package versions are preserved where possible
- Migration creates fresh environments using
uvfor improved performance
lang
Get or set the display language for scoop CLI messages.
Usage
# Show current language
scoop lang
# Set language
scoop lang <code>
# List supported languages
scoop lang --list
# Reset to system default
scoop lang --reset
Arguments
| Argument | Description |
|---|---|
<code> | Language code to set (e.g., en, ko) |
Options
| Option | Description |
|---|---|
--list | List all supported languages |
--reset | Reset to system default language |
--json | Output as JSON |
Supported Languages
| Code | Language |
|---|---|
en | English (default) |
ko | 한국어 (Korean) |
ja | 日本語 (Japanese) |
pt-BR | Português (Brazilian Portuguese) |
Language Detection Priority
SCOOP_LANGenvironment variable~/.scoop/config.jsonsetting- System locale (via
sys-locale) - Default:
en
Examples
Show Current Language
$ scoop lang
Current language: en (English)
Set Korean
$ scoop lang ko
✓ Language set to Korean (한국어)
List Languages
$ scoop lang --list
Supported languages:
en - English
ko - 한국어 (Korean)
ja - 日本語 (Japanese)
pt-BR - Português (Brazilian Portuguese)
Reset to System Default
$ scoop lang --reset
✓ Language reset to system default
JSON Output
$ scoop lang --json
{
"status": "success",
"data": {
"current": "ko",
"name": "한국어",
"source": "config"
}
}
Configuration
Language preference is stored in:
~/.scoop/config.json
{"lang": "ko"}
Environment Variable Override
# Temporarily use English regardless of config
SCOOP_LANG=en scoop list
# Set for current session
export SCOOP_LANG=ko
Notes
- CLI help text (
--help) remains in English (industry standard) - JSON output keys remain in English (machine-readable)
- Error messages, success messages, and prompts are translated
init
Output shell initialization script.
Usage
scoop init <shell>
Arguments
| Argument | Required | Description |
|---|---|---|
shell | Yes | Shell type: bash, zsh, fish, powershell |
Setup
Add to your shell configuration:
# Bash (~/.bashrc)
eval "$(scoop init bash)"
# Zsh (~/.zshrc)
eval "$(scoop init zsh)"
# Fish (~/.config/fish/config.fish)
eval (scoop init fish)
# PowerShell ($PROFILE)
Invoke-Expression (& scoop init powershell)
Features Enabled
- Auto-activation when entering directories with
.scoop-version - Tab completion for commands, environments, and options
- Wrapper function for
activate/deactivate/use
Examples
scoop init bash # Output bash init script
scoop init zsh # Output zsh init script
scoop init fish # Output fish init script
scoop init powershell # Output PowerShell init script
shell
Set shell-specific environment (current shell session only).
Unlike scoop use which writes to a file, scoop shell sets the SCOOP_VERSION environment variable for the current shell session only.
Usage
eval "$(scoop shell <name>)" # Bash/Zsh
eval (scoop shell <name>) # Fish
Note: If you have shell integration set up (
scoop init), theevalis automatic:scoop shell myenv # Works directly
Arguments
| Argument | Required | Description |
|---|---|---|
name | No | Environment name or system |
Options
| Option | Description |
|---|---|
--unset | Clear shell-specific environment |
--shell <SHELL> | Target shell type (auto-detected if not specified) |
Behavior
- Sets
SCOOP_VERSIONenvironment variable - If
nameis an environment: also outputs activation script - If
nameissystem: also outputs deactivation script --unset: outputsunset SCOOP_VERSION
Priority
SCOOP_VERSION has the highest priority in version resolution:
1. SCOOP_VERSION env var <- scoop shell (highest)
2. .scoop-version file <- scoop use
3. ~/.scoop/version <- scoop use --global
This means scoop shell overrides any file-based settings until:
- You run
scoop shell --unset - You close the terminal
Examples
# Use a specific environment in this terminal
scoop shell myproject
# Use system Python in this terminal
scoop shell system
# Clear the shell setting (return to file-based resolution)
scoop shell --unset
# Explicit shell type
scoop shell --shell fish myenv
Use Cases
Temporary Testing
# Currently using myproject
scoop shell testenv # Switch to testenv temporarily
python test.py # Test something
scoop shell myproject # Switch back
Override Project Settings
cd ~/project # Has .scoop-version = projectenv
scoop shell system # Use system Python anyway
python --version # System Python
scoop shell --unset # Back to projectenv
completions
Generate shell completion script.
Usage
scoop completions <shell>
Arguments
| Argument | Required | Description |
|---|---|---|
shell | Yes | Shell type: bash, zsh, fish, powershell |
Examples
scoop completions bash # Output bash completions
scoop completions zsh # Output zsh completions
scoop completions fish # Output fish completions
Tip: Usually you don’t need this separately -
scoop initincludes completions.
Contributing
Guide for contributing to scoop development.
Prerequisites
Setup
# Clone the repository
git clone https://github.com/ai-screams/scoop-uv.git
cd scoop-uv
# Install prek (Rust-native pre-commit alternative)
uv tool install prek
# or: cargo install prek
# Install git hooks
prek install
# Build
cargo build
# Run tests
cargo test
Project Structure
src/
├── main.rs # Entry point
├── lib.rs # Library root
├── error.rs # Error types (ScoopError)
├── paths.rs # Path utilities
├── validate.rs # Name/version validation
├── uv/ # uv client wrapper
│ ├── mod.rs
│ └── client.rs
├── core/ # Business logic
│ ├── mod.rs
│ ├── virtualenv.rs # VirtualenvService
│ ├── version.rs # VersionService
│ ├── metadata.rs # Metadata structs
│ └── doctor.rs # Health diagnostics
├── cli/ # CLI layer
│ ├── mod.rs # Cli struct, Commands enum
│ └── commands/ # Command handlers
│ ├── mod.rs
│ ├── list.rs
│ ├── create.rs
│ ├── use_env/ # Use command (normal, system, unset, symlink)
│ ├── remove.rs
│ ├── install.rs
│ ├── doctor.rs
│ ├── migrate/ # Migration subcommands
│ └── ...
├── shell/ # Shell integration
│ ├── mod.rs
│ ├── common.rs # Shared utilities (version check macros)
│ ├── bash.rs
│ ├── zsh.rs
│ ├── fish.rs
│ └── powershell.rs
└── output/ # Output formatting
├── mod.rs
└── spinner.rs
docs/ # Public documentation
.docs/ # Internal technical docs
tests/ # Integration tests
Common Commands
Build and Run
cargo build # Debug build
cargo build --release # Release build (optimized)
cargo run -- --help # Show help
cargo run -- list # Run commands
cargo run -- doctor # Check setup health
Quick Quality Check
# All checks at once (recommended before commit)
cargo fmt --check && cargo clippy --all-targets --all-features -- -D warnings && cargo test
For detailed guides, see:
- Testing - Comprehensive testing guide
- Code Quality - Formatting, linting, pre-commit hooks
Architecture
Key Services
VirtualenvService (src/core/virtualenv.rs)
- Manages virtualenvs in
~/.scoop/virtualenvs/ - Wraps uv commands for venv creation
VersionService (src/core/version.rs)
- Manages
.scoop-versionfiles - Resolves current directory to active environment
Doctor (src/core/doctor.rs)
- Health diagnostics for scoop setup
- Checks uv, shell integration, paths, environments
UvClient (src/uv/client.rs)
- Wrapper for
uvCLI commands - Python version management
Shell Integration
Shell scripts are embedded in Rust code:
src/shell/bash.rs- Bash init scriptsrc/shell/zsh.rs- Zsh init script
Key components:
- Wrapper function - Intercepts
use/activate/deactivate - Hook function - Auto-activation on directory change
- Completion function - Tab completion
Adding a New Command
- Define command in
src/cli/mod.rs:
#![allow(unused)] fn main() { #[derive(Subcommand)] pub enum Commands { // ... MyCommand { #[arg(short, long)] option: bool, }, } }
- Create handler in
src/cli/commands/my_command.rs:
#![allow(unused)] fn main() { pub fn execute(output: &Output, option: bool) -> Result<()> { // Implementation Ok(()) } }
-
Export in
src/cli/commands/mod.rs -
Wire up in
src/main.rs -
Add shell completion in
src/shell/{bash,zsh}.rs
Testing
Unit Tests
Located within source files:
#![allow(unused)] fn main() { #[cfg(test)] mod tests { use super::*; #[test] fn test_something() { // ... } } }
Integration Tests
Located in tests/:
#![allow(unused)] fn main() { use assert_cmd::Command; #[test] fn test_cli_command() { Command::cargo_bin("scoop") .unwrap() .args(["list"]) .assert() .success(); } }
Release Process
Releases are automated via release-plz:
- Create PR with changes
- Merge to main
- release-plz creates release PR
- Merge release PR to publish to crates.io
Internal Documentation
See .docs/ for internal technical references:
TECHNICAL_REFERENCE.md- Implementation detailsSHELL_GOTCHAS.md- Shell integration pitfallsIMPLEMENTATION_PLAN.md- Development roadmapbrand/brand.md- Brand guidelines
Code Style
- Follow Rust conventions
- Run
cargo fmtbefore committing - Keep functions small and focused
- Document public APIs with
///comments - Use
thiserrorfor error types - Translated error messages with solutions (en, ko, ja, pt-BR)
Translation Guide
This document provides guidelines for contributing translations to scoop.
Current Status
For the latest translation status, see:
- Issue #42: i18n Translation Tracking
- Run
scoop lang --listto see currently supported languages
Contribution Process
Step 1: Fork and Clone
git clone https://github.com/YOUR_USERNAME/scoop-uv.git
cd scoop-uv
Step 2: Add Translations
Edit locales/app.yml and add your language to every key:
create.success:
en: "Created '%{name}' environment"
ko: "'%{name}' 환경 생성됨"
pt-BR: "Ambiente '%{name}' criado"
{ lang }: "Your translation here" # Add your language code and translation
Important:
- Add translations to all ~115 keys
- Keep placeholder syntax exactly:
%{name},%{version}, etc. - Preserve special characters:
→, quotes, backticks
Step 3: Register Language
Edit src/i18n.rs and add your language to SUPPORTED_LANGS:
#![allow(unused)] fn main() { pub const SUPPORTED_LANGS: &[(&str, &str)] = &[ ("en", "English"), ("ko", "한국어"), ("pt-BR", "Português (Brasil)"), ("{lang}", "Your Language Name"), // Add your language ]; }
Language Code Format:
- Use BCP 47 format
- Simple languages:
ja,fr,es,de,it - Regional variants:
pt-BR,zh-CN,zh-TW,es-MX
Step 4: Test Locally
# Build and test
cargo build
cargo test
# Test your language (replace {lang} with your language code)
SCOOP_LANG={lang} ./target/debug/scoop --help
SCOOP_LANG={lang} ./target/debug/scoop lang
Step 5: Create Pull Request
Required files in PR:
-
locales/app.yml- All 115 keys translated -
src/i18n.rs- Language registered in SUPPORTED_LANGS
PR Title Format:
docs(i18n): add {Language Name} translation
Style Guidelines
Philosophy: Your Language, Your Style
We trust translators. You know your language and community best.
- Word choice is yours — Pick terms that feel natural to native speakers
- Creativity welcome — Witty expressions are fine if they’re clear and widely understood
- Casual over formal — scoop is a friendly CLI tool, not enterprise software
General Principles
- Concise: CLI messages should be short and clear
- Natural: Use natural phrasing, not word-for-word translation
- Casual: Friendly, approachable tone — like talking to a colleague
- Clear: Wit is great, but clarity comes first
Tone Examples
# Too formal (avoid)
"The environment has been successfully created."
# Too robotic (avoid)
"Environment creation: complete."
# Good - casual and clear
"Created 'myenv' — ready to go!"
"'myenv' is ready"
Message Types
| Type | English Example | Guidance |
|---|---|---|
| Progress | “Installing…” | Use progressive/ongoing form |
| Success | “Created ‘myenv’” | Completion — feel free to add flair |
| Error | “Can’t find ‘myenv’” | Clear and actionable |
| Hint | “→ Create: scoop create…” | Helpful, not lecturing |
Translator’s Discretion
These decisions are up to you:
- Vocabulary: Choose words that resonate with your community
- Idioms: Use local expressions if they fit naturally
- Humor: Light wit is welcome (e.g., ice cream puns if appropriate)
- Formality level: Lean casual, but match your culture’s CLI norms
Only requirement: The meaning must be clear to users.
Technical Terms
For technical vocabulary:
- Check your community — What do Python developers in your language use?
- Consistency — Pick one term and stick with it throughout
- Loanwords OK — If your community uses English terms (e.g., “install”), that’s fine
Tip: Study existing translations in locales/app.yml for reference, but don’t feel bound by them.
Glossary
Do NOT Translate
These terms should remain in English in all languages:
| Term | Reason |
|---|---|
scoop | Brand name |
uv | Tool name |
pyenv | Tool name |
conda | Tool name |
virtualenv | Technical term |
virtualenvwrapper | Tool name |
Python | Language name |
shell | Technical term (bash, zsh, fish) |
JSON | Format name |
PATH | Environment variable |
pip | Tool name |
Commands - Never Translate
All commands and code examples must stay in English:
# WRONG - Command translated
hint: "→ Create: {translated_command} myenv 3.12"
# CORRECT - Only description translated
hint: "→ {translated_word}: scoop create myenv 3.12"
Common Terms to Translate
These are core concepts you’ll need to translate. Reference existing translations for consistency:
| English | What to look for |
|---|---|
| environment | Your language’s term for “environment” |
| create | Common verb for “make/create” |
| remove/delete | Common verb for “delete/remove” |
| install | Standard software installation term |
| uninstall | Standard software removal term |
| activate | Term for “enable/turn on” |
| deactivate | Term for “disable/turn off” |
| migrate | IT term for migration (often kept as loanword) |
| version | Your language’s term for “version” |
| path | Your language’s term for file path |
| error | Your language’s term for “error” |
| success | Your language’s term for “success” |
Tip: Check how these terms are translated in existing translations for reference.
Ice Cream Metaphor (README only)
scoop uses ice cream metaphors in documentation:
| Term | Meaning | Guidance |
|---|---|---|
| scoop | The tool | Always keep as “scoop” |
| flavor | virtualenv | Translate if the metaphor works in your language |
| freezer | ~/.scoop/ directory | Translate if the metaphor works |
Note: The metaphor is mainly in README.md, not in CLI messages (locales/app.yml).
File Structure
locales/app.yml
# Categories in order:
# 1. lang.* - Language command messages
# 2. create.* - Create command messages
# 3. remove.* - Remove command messages
# 4. list.* - List command messages
# 5. use.* - Use command messages
# 6. install.* - Install command messages
# 7. uninstall.* - Uninstall command messages
# 8. migrate.* - Migrate command messages
# 9. error.* - Error messages
# 10. suggestion.* - Suggestion/hint messages
src/i18n.rs
#![allow(unused)] fn main() { // Language detection priority: // 1. SCOOP_LANG environment variable // 2. Config file (~/.scoop/config.json) // 3. System locale // 4. Default: "en" pub const SUPPORTED_LANGS: &[(&str, &str)] = &[ ("en", "English"), // ... existing languages // Add new languages here ]; }
Common Mistakes
1. Missing SUPPORTED_LANGS Registration
Symptom: Translation exists but scoop lang {code} doesn’t work
Fix: Add language to src/i18n.rs SUPPORTED_LANGS
2. Broken Placeholders
# WRONG - Missing placeholder
error: "Cannot find environment"
# CORRECT - Placeholder preserved
error: "Cannot find '%{name}' environment"
3. Translating Commands
# WRONG - Command translated
hint: "→ List: {translated} list"
# CORRECT - Only label translated
hint: "→ {Translated Label}: scoop list"
4. Inconsistent Key Coverage
All languages must have ALL keys. Missing keys fall back to English.
Testing Checklist
Before submitting PR:
- All 115 keys translated
-
All placeholders preserved (
%{name},%{version}, etc.) - Language registered in SUPPORTED_LANGS
-
cargo buildsucceeds -
cargo testpasses -
SCOOP_LANG={code} scoop langshows your language - Messages display correctly in terminal
Questions?
- Open an issue: GitHub Issues
- See existing translations for reference:
locales/app.yml
Architecture
scoop is built in Rust using a modular architecture.
Module Structure
src/
├── cli/ # Command-line interface
│ ├── mod.rs # Cli struct, Commands enum, ShellType
│ └── commands/ # Individual command handlers
├── core/ # Domain logic
│ ├── metadata.rs # Virtualenv metadata (JSON)
│ ├── version.rs # Version file resolution
│ ├── virtualenv.rs # Virtualenv entity
│ ├── doctor.rs # Health check system
│ └── migrate/ # Migration from pyenv/conda/virtualenvwrapper
│ ├── mod.rs
│ ├── discovery.rs # Source detection
│ ├── migrator.rs # Migration orchestrator
│ └── ...
├── shell/ # Shell integration
│ ├── mod.rs # Shell module exports & detection
│ ├── common.rs # Shared shell utilities & macros
│ ├── bash.rs # Bash init script
│ ├── zsh.rs # Zsh init script
│ ├── fish.rs # Fish init script
│ └── powershell.rs # PowerShell init script
├── output/ # Terminal UI and JSON output
├── uv/ # uv CLI wrapper
├── error.rs # ScoopError enum
├── paths.rs # Path utilities
├── validate.rs # Input validation
├── i18n.rs # Internationalization
└── config.rs # Configuration management
Module Dependency Graph
graph TB
CLI[cli/] --> Core[core/]
CLI --> Shell[shell/]
CLI --> Output[output/]
CLI --> I18N[i18n]
CLI --> Config[config]
Core --> UV[uv/]
Core --> Paths[paths]
Core --> Error[error]
Core --> Validate[validate]
Core --> Config
Shell --> Paths
Output --> Error
Output --> I18N
UV --> Error
Config --> Paths
Config --> Error
style CLI fill:#e1f5ff
style Core fill:#fff3e0
style Shell fill:#f3e5f5
style Output fill:#e8f5e9
Key Components
CLI Layer (cli/)
- Uses clap for argument parsing
Clistruct defines global optionsCommandsenum defines subcommands- Each command has an
executefunction incommands/
Core Layer (core/)
| Module | Purpose |
|---|---|
doctor | Health check system with Check trait |
metadata | JSON metadata for virtualenvs |
version | Version file discovery and parsing |
virtualenv | Virtualenv entity and operations |
Shell Layer (shell/)
Generates shell scripts for integration:
init_script()- Returns shell initialization code- Wrapper function for
scoopcommand - Auto-activation hooks
- Tab completion definitions
Output Layer (output/)
Handles terminal output formatting:
- Colored output using owo-colors
- JSON output for scripting
- Progress indicators with indicatif
UV Layer (uv/)
Wraps the uv CLI for Python/virtualenv operations:
- Python installation
- Virtualenv creation
- Version listing
Design Patterns
Shell Eval Pattern
The CLI outputs shell code to stdout, which the shell evaluates:
# User runs
scoop activate myenv
# CLI outputs
export VIRTUAL_ENV="/Users/x/.scoop/virtualenvs/myenv"
export PATH="/Users/x/.scoop/virtualenvs/myenv/bin:$PATH"
export SCOOP_ACTIVE="myenv"
# Shell wrapper evaluates this output
eval "$(command scoop activate myenv)"
This pattern is used by pyenv, rbenv, and other version managers.
Error Handling
Uses thiserror for error types:
#![allow(unused)] fn main() { // Uses thiserror for Error trait derive // Display impl is manual (not #[error] attributes) for i18n support #[derive(Debug, Error)] pub enum ScoopError { VirtualenvNotFound { name: String }, VirtualenvExists { name: String }, // ... } // Manual Display implementation using rust-i18n impl std::fmt::Display for ScoopError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Self::VirtualenvNotFound { name } => { write!(f, "{}", t!("error.virtualenv_not_found", name = name)) } // ... } } } }
Path Management
Centralizes path logic in paths.rs:
scoop_home()- ReturnsSCOOP_HOMEor~/.scoopvirtualenvs_dir()- Returns virtualenvs directoryglobal_version_file()- Returns global version file path (~/.scoop/version)local_version_file(dir)- Returns local version file path in directory
Data Flow
Command Execution Flow
sequenceDiagram
participant User
participant CLI as CLI Parser
participant Cmd as Command Handler
participant Core as Core Logic
participant UV as UV Wrapper
participant Output as Output Formatter
User->>CLI: scoop create myenv 3.12
CLI->>Cmd: parse & dispatch
Cmd->>Core: VirtualenvService::create()
Core->>UV: uv venv create
UV-->>Core: success/error
Core-->>Cmd: Result<()>
Cmd->>Output: format response
Output-->>User: stdout/stderr
Shell Integration Flow
sequenceDiagram
participant User
participant Shell as Shell Wrapper
participant CLI as scoop CLI
participant Core as Core Logic
User->>Shell: scoop use myenv
Shell->>CLI: command scoop use myenv
CLI->>Core: resolve version & path
Core-->>CLI: env vars to export
CLI-->>Shell: echo shell script
Shell->>Shell: eval output
Shell-->>User: (myenv) $
Note over User: Environment activated
Version Resolution Flow
graph LR
Start([User runs command]) --> Env{SCOOP_VERSION<br/>env set?<br/><small>shell hook</small>}
Env -->|Yes| Use[Use env value]
Env -->|No| Local{.scoop-version<br/>in current/parent<br/>dirs?}
Local -->|Yes| Use
Local -->|No| Global{~/.scoop/version<br/>exists?}
Global -->|Yes| Use
Global -->|No| None[No version<br/>system Python]
style Use fill:#c8e6c9
style None fill:#fff9c4
Note:
.python-versionis not supported. Version resolution walks up parent directories to find.scoop-version.
Health Check Flow
flowchart TD
Start([scoop doctor]) --> Init[Initialize Doctor]
Init --> Run[Run all checks]
Run --> UV{UV Check}
UV -->|Pass| Home{Home Check}
UV -->|Fail| Fix1[Suggest: install uv]
Home -->|Pass| Venv{Venv Check}
Home -->|Fail| Fix2[Auto-fix: mkdir]
Venv -->|Pass| Link{Symlink Check}
Venv -->|Warn| Warn1[Warn: corrupted env]
Link -->|Pass| Shell{Shell Check}
Link -->|Fail| Fix3[Auto-fix: remove broken links]
Shell -->|Pass| Ver{Version Check}
Shell -->|Warn| Warn2[Warn: not initialized]
Ver -->|Pass| Done([All checks passed])
Ver -->|Warn| Warn3[Warn: invalid version file]
Fix1 --> Report[Generate Report]
Fix2 --> Report
Fix3 --> Report
Warn1 --> Report
Warn2 --> Report
Warn3 --> Report
Done --> Report
style Done fill:#c8e6c9
style Fix1 fill:#ffcdd2
style Fix2 fill:#ffcdd2
style Fix3 fill:#ffcdd2
style Warn1 fill:#fff9c4
style Warn2 fill:#fff9c4
style Warn3 fill:#fff9c4
Migration Architecture
scoop supports migrating environments from pyenv-virtualenv, virtualenvwrapper, and conda.
graph TD
Start([scoop migrate]) --> Detect[Detect Sources]
Detect --> Pyenv{pyenv-virtualenv}
Detect --> Venv{virtualenvwrapper}
Detect --> Conda{conda}
Pyenv -->|Found| P1[List ~/.pyenv/versions]
Venv -->|Found| V1[List $WORKON_HOME]
Conda -->|Found| C1[conda env list]
P1 --> Parse[Parse metadata]
V1 --> Parse
C1 --> Parse
Parse --> Create[Create in scoop]
Create --> Copy[Copy packages]
Copy --> Meta[Write metadata]
Meta --> Done([Migration complete])
Pyenv -->|Not found| Skip1[Skip]
Venv -->|Not found| Skip2[Skip]
Conda -->|Not found| Skip3[Skip]
Skip1 --> Check{Any source found?}
Skip2 --> Check
Skip3 --> Check
Check -->|Yes| Done
Check -->|No| Error[Error: No sources]
style Done fill:#c8e6c9
style Error fill:#ffcdd2
Dependencies
| Crate | Purpose |
|---|---|
| clap | Argument parsing & completion |
| clap_complete | Shell completion generation |
| serde | JSON serialization |
| serde_json | Metadata persistence |
| thiserror | Error type definitions |
| owo-colors | Terminal colors |
| indicatif | Progress bars & spinners |
| dialoguer | Interactive prompts |
| dirs | Home directory resolution |
| which | Binary lookup (uv, python) |
| regex | Version parsing & validation |
| walkdir | Directory traversal |
| rust-i18n | Internationalization (en, ko, ja, pt-BR) |
| sys-locale | System locale detection |
| chrono | Timestamp generation |
Extension Points
Adding a New Shell
- Create
shell/myshell.rs:
#![allow(unused)] fn main() { pub fn init_script() -> &'static str { // Return shell-specific initialization code as static string r#" Shell initialization code here scoop() { ... } "# } }
Note: Completions are generated by clap via
clap_complete, not per-shell functions.
- Add to
ShellTypeenum incli/mod.rs:
#![allow(unused)] fn main() { pub enum ShellType { // ... existing MyShell, } }
- Add detection to
shell::detect_shell()insrc/shell/mod.rs:
#![allow(unused)] fn main() { pub fn detect_shell() -> ShellType { if env::var("MYSHELL_VERSION").is_ok() { return ShellType::MyShell; } // ... existing checks } }
Adding a New Migration Source
- Create
core/migrate/mysource.rs:
#![allow(unused)] fn main() { pub struct MySource; impl MySource { pub fn detect() -> bool { // Check if source is available } pub fn list_envs() -> Result<Vec<MigrationCandidate>> { // Return list of environments } pub fn migrate_env(name: &str) -> Result<()> { // Perform migration } } }
- Register in
cli/commands/migrate.rs:
#![allow(unused)] fn main() { let sources = vec![ // ... existing Box::new(MySource), ]; }
Adding a New Doctor Check
See API Reference - Adding a New Health Check for details.
Performance Characteristics
| Operation | Time Complexity | Notes |
|---|---|---|
| List envs | O(n) | n = number of virtualenvs |
| Create env | O(1)* | *Depends on uv performance |
| Delete env | O(1) | Simple directory removal |
| Version resolution | O(d) | d = directory depth (walks parents) |
| Doctor checks | O(n) | n = number of checks (fixed) |
Thread Safety
scoop is a single-threaded CLI application. No concurrent operations are performed.
File locking: Not implemented. Assumes single user on single machine. Concurrent operations (e.g., two terminals creating the same env) may result in race conditions.
Security Considerations
- Path Traversal: All user-provided names are validated via regex before use in filesystem operations.
- Command Injection: uv commands are constructed using typed arguments, not string concatenation.
- Symlink Safety: Doctor checks detect and warn about broken symlinks.
- Metadata Integrity: JSON parsing errors are gracefully handled without panics.
Related Documentation
- API Reference - Detailed API documentation
- Testing - Testing strategies
- Contributing - Development guide
API Reference
This document provides a reference for scoop’s public API, primarily intended for:
- AI/LLM tools analyzing or modifying the codebase
- Contributors extending scoop’s functionality
- Advanced users integrating scoop into custom tooling
Note: This is an internal API reference. For CLI usage, see Commands.
Core Types
VirtualEnv Module (core/virtualenv.rs)
VirtualenvInfo
Represents basic information about a virtual environment.
#![allow(unused)] fn main() { pub struct VirtualenvInfo { pub name: String, pub path: PathBuf, pub python_version: Option<String>, } }
Fields:
name- Environment name (e.g.,"myproject")path- Absolute path to virtualenv directorypython_version- Python version string if metadata exists (e.g.,Some("3.12.1"))
Example:
#![allow(unused)] fn main() { let info = VirtualenvInfo { name: "webapp".to_string(), path: PathBuf::from("/Users/x/.scoop/virtualenvs/webapp"), python_version: Some("3.12.1".to_string()), }; }
VirtualenvService
Primary service for virtualenv operations.
#![allow(unused)] fn main() { pub struct VirtualenvService { uv: UvClient, } impl VirtualenvService { /// Creates a new service with custom uv wrapper pub fn new(uv: UvClient) -> Self /// Creates a service using system's uv installation pub fn auto() -> Result<Self> /// Lists all virtualenvs pub fn list(&self) -> Result<Vec<VirtualenvInfo>> /// Creates a new virtualenv pub fn create(&self, name: &str, python_version: &str) -> Result<PathBuf> /// Creates a new virtualenv with a specific Python executable pub fn create_with_python_path(&self, name: &str, python_version: &str, python_path: &Path) -> Result<PathBuf> /// Deletes a virtualenv pub fn delete(&self, name: &str) -> Result<()> /// Checks if a virtualenv exists pub fn exists(&self, name: &str) -> Result<bool> /// Gets the path to a virtualenv pub fn get_path(&self, name: &str) -> Result<PathBuf> /// Reads metadata for a virtualenv (internal use) pub fn read_metadata(&self, path: &Path) -> Option<Metadata> } }
Common Usage Pattern:
#![allow(unused)] fn main() { // Initialize service let service = VirtualenvService::auto()?; // Check if environment exists if !service.exists("myenv")? { // Create with Python 3.12 service.create("myenv", "3.12")?; } // Get environment path let path = service.get_path("myenv")?; println!("Environment at: {}", path.display()); }
Metadata Module (core/metadata.rs)
Metadata
Stores JSON metadata for each virtualenv.
#![allow(unused)] fn main() { use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub struct Metadata { pub name: String, pub python_version: String, pub created_at: DateTime<Utc>, // Timestamp (ISO 8601 when serialized) pub created_by: String, // "scoop X.Y.Z" format pub uv_version: Option<String>, // uv version used pub python_path: Option<String>, // Custom Python executable path (if --python-path was used) } impl Metadata { /// Creates new metadata pub fn new(name: String, python_version: String, uv_version: Option<String>) -> Self } }
Storage Location: ~/.scoop/virtualenvs/<name>/.scoop-metadata.json
Example JSON:
{
"name": "myproject",
"python_version": "3.12.1",
"created_at": "2024-01-15T10:30:00Z",
"created_by": "scoop <version>",
"uv_version": "0.1.0"
}
Note:
created_atisDateTime<Utc>in Rust but serializes to ISO 8601 string in JSON.
Doctor Module (core/doctor.rs)
Check Trait
Interface for health checks.
#![allow(unused)] fn main() { pub trait Check: Send + Sync { fn id(&self) -> &'static str; fn name(&self) -> &'static str; fn run(&self) -> Vec<CheckResult>; // Returns Vec - a check can produce multiple results } }
Implementations:
UvCheck- Verifies uv is installedHomeCheck- ChecksSCOOP_HOMEdirectoryVirtualenvCheck- Validates virtualenvs directorySymlinkCheck- Checks for broken virtualenv Python symlinks (e.g.,<env>/bin/python)ShellCheck- Verifies shell integrationVersionCheck- Validates version files
CheckStatus
Result status for health checks.
#![allow(unused)] fn main() { pub enum CheckStatus { Ok, // ✅ Check passed Warning(String), // ⚠️ Issue found, but not critical Error(String), // ❌ Critical issue } }
CheckResult
Detailed result from a health check.
#![allow(unused)] fn main() { pub struct CheckResult { pub id: &'static str, pub name: &'static str, pub status: CheckStatus, pub suggestion: Option<String>, pub details: Option<String>, } impl CheckResult { /// Creates an OK result pub fn ok(id: &'static str, name: &'static str) -> Self /// Creates a warning result pub fn warn(id: &'static str, name: &'static str, message: impl Into<String>) -> Self /// Creates an error result pub fn error(id: &'static str, name: &'static str, message: impl Into<String>) -> Self /// Adds a suggestion for fixing the issue pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self /// Adds detailed information pub fn with_details(mut self, details: impl Into<String>) -> Self // Status checks pub fn is_ok(&self) -> bool pub fn is_warning(&self) -> bool pub fn is_error(&self) -> bool } }
Example - Implementing a Custom Check:
#![allow(unused)] fn main() { struct MyCustomCheck; impl Check for MyCustomCheck { fn id(&self) -> &'static str { "my_check" } fn name(&self) -> &'static str { "My Custom Check" } fn run(&self) -> Vec<CheckResult> { if some_condition() { vec![CheckResult::ok(self.id(), self.name())] } else { vec![CheckResult::error(self.id(), self.name(), "Error message here") .with_suggestion("Run: scoop fix-it") .with_details("Expected X, found Y")] } } } }
Doctor
Orchestrates all health checks.
#![allow(unused)] fn main() { pub struct Doctor { checks: Vec<Box<dyn Check>>, } impl Doctor { /// Creates a doctor with default checks pub fn new() -> Self /// Runs all checks without fixing pub fn run_all(&self) -> Vec<CheckResult> /// Runs checks and attempts to fix issues pub fn run_and_fix(&self, output: &crate::output::Output) -> Vec<CheckResult> } impl Default for Doctor { fn default() -> Self { Self::new() } } }
Usage:
#![allow(unused)] fn main() { let doctor = Doctor::new(); // Run diagnostics let results = doctor.run_all(); for result in results { match &result.status { CheckStatus::Error(msg) => eprintln!("❌ {}: {}", result.name, msg), CheckStatus::Warning(msg) => println!("⚠️ {}: {}", result.name, msg), CheckStatus::Ok => println!("✅ {}", result.name), } } // Auto-fix issues (requires Output for progress display) use scoop::output::Output; let output = Output::new(0, false, false, false); let fixed_results = doctor.run_and_fix(&output); }
Error Handling
ScoopError (error.rs)
Primary error type for all scoop operations.
#![allow(unused)] fn main() { #[derive(Error, Debug)] pub enum ScoopError { // Virtualenv errors VirtualenvNotFound { name: String }, VirtualenvExists { name: String }, InvalidEnvName { name: String, reason: String }, // Python errors PythonNotInstalled { version: String }, PythonInstallFailed { version: String, message: String }, PythonUninstallFailed { version: String, message: String }, InvalidPythonVersion { version: String }, NoPythonVersions { pattern: String }, // uv errors UvNotFound, UvCommandFailed { command: String, message: String }, // Path/IO errors PathError(String), // Tuple variant HomeNotFound, Io(#[from] std::io::Error), // Tuple variant with From Json(#[from] serde_json::Error), // Tuple variant with From // Config errors VersionFileNotFound { path: PathBuf }, UnsupportedShell { shell: String }, // CLI errors InvalidArgument { message: String }, // Migration errors PyenvNotFound, PyenvEnvNotFound { name: String }, VenvWrapperEnvNotFound { name: String }, CondaEnvNotFound { name: String }, CorruptedEnvironment { name: String, reason: String }, PackageExtractionFailed { reason: String }, MigrationFailed { reason: String }, MigrationNameConflict { name: String, existing: PathBuf }, // Python path errors InvalidPythonPath { path: PathBuf, reason: String }, // Cascade errors CascadeAborted, } impl ScoopError { /// Returns error code string (e.g., "ENV_NOT_FOUND", "UV_COMMAND_FAILED") pub fn code(&self) -> &'static str /// Returns user-friendly suggestion (if available) pub fn suggestion(&self) -> Option<String> /// Returns migration-specific exit code pub fn migration_exit_code(&self) -> MigrationExitCode } }
Error Code String Prefixes:
ENV_*- Environment errors (e.g.,ENV_NOT_FOUND,ENV_ALREADY_EXISTS)PYTHON_*- Python version errors (e.g.,PYTHON_NOT_INSTALLED)UV_*- uv errors (e.g.,UV_NOT_INSTALLED,UV_COMMAND_FAILED)IO_*- Path/IO errors (e.g.,IO_ERROR,PATH_ERROR)CONFIG_*- Config errors (e.g.,CONFIG_VERSION_FILE_NOT_FOUND)SHELL_*- Shell errors (e.g.,SHELL_NOT_SUPPORTED)ARG_*- CLI argument errors (e.g.,ARG_INVALID)SOURCE_*- Migration source errors (e.g.,SOURCE_PYENV_NOT_FOUND)MIGRATE_*- Migration process errors (e.g.,MIGRATE_FAILED)UNINSTALL_*- Uninstall errors (e.g.,UNINSTALL_CASCADE_ABORTED)
Example Error Handling:
#![allow(unused)] fn main() { use crate::error::{ScoopError, Result}; fn my_function(name: &str) -> Result<()> { if !validate_name(name) { return Err(ScoopError::InvalidEnvName { name: name.to_string(), reason: "Must start with a letter".to_string(), }); } // ... operation Ok(()) } // Usage match my_function("123invalid") { Ok(_) => println!("Success"), Err(e) => { eprintln!("Error: {}", e); if let Some(suggestion) = e.suggestion() { eprintln!("Suggestion: {}", suggestion); } eprintln!("Error code: {}", e.code()); std::process::exit(1); // Non-zero exit for error } } }
Shell Integration
Shell Types (cli/mod.rs)
#![allow(unused)] fn main() { pub enum ShellType { Bash, Zsh, Fish, Powershell, } // Note: ShellType is a plain enum without methods // Shell operations are handled by module-level functions: // - shell::detect_shell() -> ShellType (in shell/mod.rs) // - shell::bash::init_script() -> &'static str // - shell::zsh::init_script() -> &'static str // - shell::fish::init_script() -> &'static str // - shell::powershell::init_script() -> &'static str }
Auto-detection Priority:
FISH_VERSION→ FishPSModulePath→ PowerShellZSH_VERSION→ Zsh- Default → Bash
Path Utilities (paths.rs)
#![allow(unused)] fn main() { /// Returns scoop home directory (SCOOP_HOME or ~/.scoop) pub fn scoop_home() -> Result<PathBuf> /// Returns virtualenvs directory pub fn virtualenvs_dir() -> Result<PathBuf> /// Returns global version file path (~/.scoop/version) pub fn global_version_file() -> Result<PathBuf> /// Returns local version file path in the given directory pub fn local_version_file(dir: &std::path::Path) -> PathBuf }
Version Resolution (core/version.rs)
VersionService
Service for managing version files.
#![allow(unused)] fn main() { pub struct VersionService; impl VersionService { /// Set the local version for a directory pub fn set_local(dir: &Path, env_name: &str) -> Result<()> /// Set the global version pub fn set_global(env_name: &str) -> Result<()> /// Get the local version for a directory pub fn get_local(dir: &Path) -> Option<String> /// Get the global version pub fn get_global() -> Option<String> /// Resolve the version for a directory (local -> parent walk -> global) pub fn resolve(dir: &Path) -> Option<String> /// Resolve from current directory pub fn resolve_current() -> Option<String> /// Unset local version (removes .scoop-version) pub fn unset_local(dir: &Path) -> Result<()> /// Unset global version (removes ~/.scoop/version) pub fn unset_global() -> Result<()> } }
CLI Equivalent (User Workflow):
VersionService::set_global() is exposed by the scoop use <name> --global command.
To set Python 3.11.0 as the global default in practice:
scoop install 3.11.0
scoop create py311 3.11.0
scoop use py311 --global
This writes py311 to ~/.scoop/version. The global value is used when no local
.scoop-version or SCOOP_VERSION override is present.
Resolution Priority Order:
SCOOP_VERSIONenvironment variable (checked at shell hook level, not in VersionService).scoop-versionin current directory.scoop-versionin parent directories (walks up)~/.scoop/version(global default)
Note:
.python-versionis not supported.
Testing Patterns
Property-Based Testing
scoop uses proptest for property-based testing of critical logic:
#![allow(unused)] fn main() { #[cfg(test)] mod tests { use proptest::prelude::*; proptest! { #[test] fn env_name_validation_is_consistent(name in "[a-zA-Z][a-zA-Z0-9_-]*") { assert!(validate_name(&name).is_ok()); } } } }
Integration Testing
Test files follow the pattern:
#![allow(unused)] fn main() { #[cfg(test)] mod tests { use super::*; use tempfile::TempDir; fn setup_test_env() -> TempDir { // Test setup } #[test] fn test_something() { let temp = setup_test_env(); // Test logic } } }
Extending scoop
Adding a New Command
- Define command in
cli/mod.rs:
#![allow(unused)] fn main() { #[derive(Subcommand)] pub enum Commands { // ... existing commands MyCommand { #[arg(help = "Argument description")] arg: String, }, } }
- Create handler in
cli/commands/:
#![allow(unused)] fn main() { // cli/commands/my_command.rs use crate::error::Result; pub fn execute(arg: &str) -> Result<()> { // Implementation Ok(()) } }
- Wire up in
main.rs:
#![allow(unused)] fn main() { Commands::MyCommand { arg } => { commands::my_command::execute(&arg)? } }
Adding a New Health Check
#![allow(unused)] fn main() { // In core/doctor.rs struct MyCheck; impl Check for MyCheck { fn id(&self) -> &'static str { "my_check" } fn name(&self) -> &'static str { "My Custom Check" } fn run(&self) -> Vec<CheckResult> { // Check logic vec![CheckResult::ok(self.id(), self.name())] } } // Register in Doctor::new() impl Doctor { pub fn new() -> Self { Self { checks: vec![ // ... existing checks Box::new(MyCheck), ], } } } }
Related Documentation
- Architecture - System design and patterns
- Commands - CLI reference
- Contributing - Development guide
- Testing - Testing strategies
For AI/LLM Tools
When analyzing or modifying this codebase:
-
Use symbolic tools for precise navigation:
find_symbolto locate specific functions/typesfind_referencing_symbolsto understand usageget_symbols_overviewfor module structure
-
Follow existing patterns:
- Error handling: Always return
Result<T>withScoopError - Shell output: CLI outputs shell code, wrapper evals it
- Testing: Unit tests + integration tests + property tests
- i18n: Use
t!()macro for all user-facing strings
- Error handling: Always return
-
Preserve conventions:
- Error codes are string constants (e.g., “ENV_NOT_FOUND”, “UV_COMMAND_FAILED”)
- Process exit codes: 0 = success, 1 = failure
- Path handling via
paths.rsutilities - Shell detection via
shell::detect_shell()function
-
Documentation requirements:
- All
pub fnmust have doc comments - Examples in doctests where applicable
- Error conditions documented
- Shell integration changes require cross-shell testing
- All
Last Updated: 2026-02-15 scoop Version: 0.7.0
Testing
Comprehensive guide for testing scoop.
Quick Reference
cargo test # Run all tests
cargo test json # Run tests containing "json"
cargo test -- --nocapture # Show println! output
cargo clippy -- -D warnings # Lint check
Test Structure
tests/
└── cli.rs # CLI integration tests
src/
├── error.rs # Unit tests for error types
├── validate.rs # Unit tests for validation
├── paths.rs # Unit tests for path utilities
├── output/
│ └── json.rs # Unit tests for JSON output
├── core/
│ ├── virtualenv.rs # Unit tests for virtualenv service
│ ├── version.rs # Unit tests for version service
│ ├── metadata.rs # Unit tests for metadata
│ └── doctor.rs # Unit tests for doctor
├── shell/
│ ├── bash.rs # Shell script tests
│ └── zsh.rs # Shell script tests
└── uv/
└── client.rs # Unit tests for uv client
Running Tests
All Tests
# Run all tests
cargo test
# Run with all features enabled
cargo test --all-features
# Run in release mode (faster execution)
cargo test --release
Filtered Tests
# By name pattern
cargo test json # Tests containing "json"
cargo test error # Tests containing "error"
cargo test virtualenv # Tests containing "virtualenv"
# By module path
cargo test output::json # Tests in output/json.rs
cargo test error::tests # Tests in error.rs
cargo test core::version # Tests in core/version.rs
cargo test cli::commands # Tests in cli/commands/
# Single test
cargo test test_json_response_success_creates_correct_status
Test Output
# Show stdout/stderr (println!, dbg!, etc.)
cargo test -- --nocapture
# Show test names as they run
cargo test -- --nocapture --test-threads=1
# Only show failed tests
cargo test -- --quiet
Debugging
# Run single-threaded (easier to debug)
cargo test -- --test-threads=1
# Run ignored tests
cargo test -- --ignored
# Run specific test with output
cargo test test_name -- --nocapture --test-threads=1
Test Categories
Unit Tests (520 tests)
Located within source files using #[cfg(test)]:
#![allow(unused)] fn main() { #[cfg(test)] mod tests { use super::*; #[test] fn test_something() { assert_eq!(1 + 1, 2); } } }
Key test modules:
| Module | Tests | Coverage |
|---|---|---|
error::tests | 54 | Error types, codes, suggestions |
output::json::tests | 35 | JSON serialization, edge cases |
validate::tests | 30 | Name/version validation |
core::version::tests | 18 | Version file resolution |
core::virtualenv::tests | 12 | Virtualenv service |
paths::tests | 16 | Path utilities |
shell::*::tests | 14 | Shell scripts (shellcheck) |
Integration Tests (41 tests)
Located in tests/cli.rs:
# Run only integration tests
cargo test --test cli
Categories:
- Error cases - Invalid inputs, missing arguments
- Output format - Help, version, JSON output
- Command behavior - list, create, use, remove
Some tests are marked #[ignore] because they require uv installed:
# Run ignored tests (requires uv)
cargo test -- --ignored
Doc Tests (6 tests)
Examples in documentation comments:
#![allow(unused)] fn main() { /// Validates environment name. /// /// # Examples /// /// ``` /// use scoop_uv::validate::is_valid_env_name; /// assert!(is_valid_env_name("myenv")); /// assert!(!is_valid_env_name("123bad")); /// ``` pub fn is_valid_env_name(name: &str) -> bool { ... } }
# Run only doc tests
cargo test --doc
Property Tests
Using proptest for randomized testing:
#![allow(unused)] fn main() { use proptest::prelude::*; proptest! { #[test] fn prop_valid_names_accepted(name in "[a-zA-Z][a-zA-Z0-9_-]{0,49}") { assert!(is_valid_env_name(&name)); } } }
Located in src/validate.rs.
Writing Tests
Unit Test Template
#![allow(unused)] fn main() { #[cfg(test)] mod tests { use super::*; // ======================================== // Test Group Name // ======================================== #[test] fn test_function_name_expected_behavior() { // Arrange let input = "test input"; // Act let result = function_under_test(input); // Assert assert_eq!(result, expected_value); } #[test] fn test_function_name_edge_case() { let result = function_under_test(""); assert!(result.is_err()); } } }
Integration Test Template
#![allow(unused)] fn main() { // tests/cli.rs use assert_cmd::Command; use predicates::prelude::*; #[test] fn test_command_success() { Command::cargo_bin("scoop") .unwrap() .args(["list"]) .assert() .success() .stdout(predicate::str::contains("expected output")); } #[test] fn test_command_failure() { Command::cargo_bin("scoop") .unwrap() .args(["use", "nonexistent"]) .assert() .failure() .stderr(predicate::str::contains("not found")); } }
JSON Output Testing
#![allow(unused)] fn main() { #[test] fn test_json_serialization() { let data = MyData { field: "value".into() }; let json = serde_json::to_string(&data).unwrap(); // Check JSON structure let parsed: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(parsed["field"], "value"); } #[test] fn test_optional_field_omitted() { let data = MyData { optional: None, .. }; let json = serde_json::to_string(&data).unwrap(); // skip_serializing_if = "Option::is_none" assert!(!json.contains("optional")); } }
Test Utilities
Located in src/test_utils.rs:
#![allow(unused)] fn main() { use scoop_uv::test_utils::*; #[test] fn test_with_temp_environment() { with_temp_scoop_home(|temp_dir| { // SCOOP_HOME is set to temp_dir // Cleanup happens automatically }); } #[test] fn test_with_mock_venv() { with_temp_scoop_home(|temp_dir| { create_mock_venv("myenv", Some("3.12")); // Virtual environment created at temp_dir/virtualenvs/myenv }); } }
Coverage
Using cargo-tarpaulin
# Install
cargo install cargo-tarpaulin
# Run with HTML report
cargo tarpaulin --out Html --output-dir coverage
# Run with specific target
cargo tarpaulin --out Html --output-dir coverage --packages scoop-uv
# View report
open coverage/tarpaulin-report.html
Using cargo-llvm-cov
# Install
cargo install cargo-llvm-cov
# Run with HTML report
cargo llvm-cov --html
# View report
open target/llvm-cov/html/index.html
CI/CD Testing
Tests run automatically on:
- Every push to any branch
- Every pull request
GitHub Actions workflow (.github/workflows/ci.yml):
- name: Run tests
run: cargo test --all-features
- name: Run clippy
run: cargo clippy --all-targets -- -D warnings
Troubleshooting
Test Hangs
# Run single-threaded to identify hanging test
cargo test -- --test-threads=1
Flaky Tests
# Run specific test multiple times
for i in {1..10}; do cargo test test_name || break; done
Environment Issues
# Clear test artifacts
cargo clean
# Rebuild and test
cargo test
Shell Tests Fail
ShellCheck must be installed for shell script tests:
# macOS
brew install shellcheck
# Linux
apt install shellcheck
Best Practices
- Test naming:
test_<function>_<scenario>_<expected> - Arrange-Act-Assert: Clear test structure
- One assertion per test: When practical
- Test edge cases: Empty, unicode, special chars, boundaries
- No test interdependencies: Each test should be isolated
- Fast tests: Mock external dependencies
Code Quality
Comprehensive guide for maintaining code quality in scoop.
Quick Reference
# Format code
cargo fmt
# Lint check
cargo clippy --all-targets --all-features -- -D warnings
# All checks (pre-commit style)
cargo fmt --check && cargo clippy --all-targets --all-features -- -D warnings && cargo test
# Pre-commit hooks
prek run --all-files
Formatting (rustfmt)
Configuration
Located in rustfmt.toml:
edition = "2024"
max_width = 100
tab_spaces = 4
use_field_init_shorthand = true
use_try_shorthand = true
Commands
# Auto-format all files
cargo fmt
# Check formatting (CI mode, no changes)
cargo fmt --check
# Format specific file
rustfmt src/main.rs
# Show diff instead of applying
cargo fmt -- --check --diff
IDE Integration
VS Code (rust-analyzer):
{
"[rust]": {
"editor.formatOnSave": true
}
}
JetBrains (RustRover/CLion):
- Settings → Languages → Rust → Rustfmt → Run on save
Linting (Clippy)
Basic Usage
# Standard lint check
cargo clippy
# Treat warnings as errors (CI mode)
cargo clippy -- -D warnings
# All targets (including tests, examples)
cargo clippy --all-targets
# All features enabled
cargo clippy --all-features
# Full CI check
cargo clippy --all-targets --all-features -- -D warnings
Lint Categories
# Enable specific lint category
cargo clippy -- -W clippy::pedantic
# Deny specific lint
cargo clippy -- -D clippy::unwrap_used
# Allow specific lint
cargo clippy -- -A clippy::too_many_arguments
Common Lints
| Lint | Severity | Description |
|---|---|---|
clippy::unwrap_used | Warn | Use ? or expect() instead |
clippy::panic | Warn | Avoid panic in library code |
clippy::todo | Warn | Remove before release |
clippy::dbg_macro | Warn | Remove debug macros |
clippy::print_stdout | Warn | Use logging instead |
Fixing Lints
# Auto-fix where possible
cargo clippy --fix
# Allow fixes that change behavior
cargo clippy --fix --allow-dirty --allow-staged
Suppressing Lints
#![allow(unused)] fn main() { // Single line #[allow(clippy::too_many_arguments)] fn complex_function(...) {} // Entire module #![allow(clippy::module_inception)] // With explanation #[allow(clippy::unwrap_used)] // Safe: validated in parse() fn get_value() {} }
Pre-commit Hooks (prek)
Setup
# Install prek
cargo install prek
# or
uv tool install prek
# Install hooks in repository
prek install
Configuration
Located in .pre-commit-config.yaml:
repos:
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --all --
language: system
types: [ rust ]
pass_filenames: false
- id: cargo-clippy
name: cargo clippy
entry: cargo clippy --all-targets --all-features -- -D warnings
language: system
types: [ rust ]
pass_filenames: false
- id: cargo-check
name: cargo check
entry: cargo check --all-targets
language: system
types: [ rust ]
pass_filenames: false
Usage
# Run all hooks on staged files
prek run
# Run all hooks on all files
prek run --all-files
# Run specific hook
prek run cargo-fmt
prek run cargo-clippy
# Run multiple specific hooks
prek run cargo-fmt cargo-clippy
# Skip hooks (emergency only!)
git commit --no-verify
Available Hooks
| Hook | Description | When |
|---|---|---|
cargo-fmt | Code formatting | Pre-commit |
cargo-clippy | Linting | Pre-commit |
cargo-check | Type checking | Pre-commit |
trailing-whitespace | Remove trailing spaces | Pre-commit |
end-of-file-fixer | Ensure newline at EOF | Pre-commit |
check-toml | Validate TOML files | Pre-commit |
check-yaml | Validate YAML files | Pre-commit |
CI Pipeline
GitHub Actions
Located in .github/workflows/ci.yml:
name: CI
on: [ push, pull_request ]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-action@stable
with:
components: rustfmt, clippy
- name: Format check
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Test
run: cargo test --all-features
Local CI Simulation
# Run exactly what CI runs
cargo fmt --check && \
cargo clippy --all-targets --all-features -- -D warnings && \
cargo test --all-features
Code Style Guidelines
Naming Conventions
| Item | Convention | Example |
|---|---|---|
| Modules | snake_case | version_file |
| Functions | snake_case | get_version() |
| Types | PascalCase | VirtualenvService |
| Constants | SCREAMING_SNAKE | MAX_NAME_LENGTH |
| Lifetimes | short lowercase | 'a, 'src |
Documentation
/// Creates a new virtual environment. /// /// # Arguments /// /// * `name` - Environment name (must be valid) /// * `python` - Python version (e.g., "3.12") /// /// # Returns /// /// Path to the created environment. /// /// # Errors /// /// Returns [`ScoopError::InvalidEnvName`] if name is invalid. /// /// # Examples /// /// ``` /// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// let path = create_env("myenv", "3.12")?; /// # Ok(()) /// # } /// ``` pub fn create_env(name: &str, python: &str) -> Result<PathBuf> { // ... }
Error Handling
#![allow(unused)] fn main() { // Good: Use ? operator fn process() -> Result<()> { let file = File::open(path)?; let data = read_data(&file)?; Ok(()) } // Good: Contextual errors fn process() -> Result<()> { let file = File::open(path) .map_err(|e| ScoopError::Io(e))?; Ok(()) } // Avoid: unwrap() in library code fn bad() { let file = File::open(path).unwrap(); // Bad } // OK: expect() with explanation fn acceptable() { let home = dirs::home_dir() .expect("home directory must exist"); } }
Import Organization
#![allow(unused)] fn main() { // 1. Standard library use std::collections::HashMap; use std::path::PathBuf; // 2. External crates use clap::Parser; use serde::Serialize; use thiserror::Error; // 3. Local modules use crate::error::Result; use crate::paths; }
Security Considerations
Dependency Auditing
# Install cargo-audit
cargo install cargo-audit
# Run audit
cargo audit
# Fix vulnerabilities
cargo audit fix
MSRV (Minimum Supported Rust Version)
- Current MSRV: 1.85
- Defined in
Cargo.toml:[package] rust-version = "1.85"
Unsafe Code
- Avoid
unsafeunless absolutely necessary - Document safety invariants
- Use
#![forbid(unsafe_code)]in library crates
Performance
Profiling
# Build with debug info for release
cargo build --release
# Use flamegraph
cargo install flamegraph
cargo flamegraph --bin scoop -- list
Benchmarks
# Run benchmarks (if defined)
cargo bench
# Using criterion
cargo bench --bench my_benchmark
Continuous Improvement
Regular Checks
# Weekly dependency update check
cargo outdated
# Security audit
cargo audit
# MSRV check
cargo msrv verify
Upgrade Dependencies
# Update Cargo.lock
cargo update
# Upgrade to latest compatible versions
cargo upgrade # requires cargo-edit
Troubleshooting
Clippy False Positives
#![allow(unused)] fn main() { // Silence with explanation #[allow(clippy::needless_return)] fn explicit_return() -> i32 { return 42; // Intentional for readability } }
Format Conflicts
#![allow(unused)] fn main() { // Skip formatting for specific block #[rustfmt::skip] const MATRIX: [[i32; 3]; 3] = [ [1, 0, 0], [0, 1, 0], [0, 0, 1], ]; }
CI vs Local Differences
# Ensure same toolchain as CI
rustup update stable
rustup default stable
# Check Rust version
rustc --version
Summary Checklist
Before committing:
-
cargo fmt- Code formatted -
cargo clippy -- -D warnings- No lint warnings -
cargo test- All tests pass -
cargo doc- Documentation builds -
No
todo!()ordbg!()left in code - Public APIs documented
- Error messages are helpful
LLM Reference
This page provides a concise reference for AI/LLM tools working with scoop.
Tip: The raw text versions are available at
llms.txt(concise) andllms-full.txt(full API reference).
Overview
scoop is a centralized Python virtual environment manager — pyenv-style workflow powered by uv. Written in Rust.
All virtualenvs are stored in ~/.scoop/virtualenvs/. Override with SCOOP_HOME env var.
Commands
| Command | Description |
|---|---|
scoop list | List virtualenvs (aliases: ls) |
scoop list --pythons | List installed Python versions |
scoop create <name> [version] | Create virtualenv (default: latest Python) |
scoop use <name> | Set + activate environment |
scoop use <name> --global | Set as global default |
scoop use <name> --link | Also create .venv symlink for IDE |
scoop use system | Deactivate, use system Python |
scoop use --unset | Remove version file |
scoop remove <name> | Delete virtualenv (aliases: rm, delete) |
scoop install [version] | Install Python version |
scoop uninstall <version> | Remove Python version |
scoop info <name> | Show virtualenv details |
scoop doctor | Health check |
scoop doctor --fix | Auto-fix issues |
scoop shell <name> | Set shell-specific env (temporary) |
scoop shell --unset | Clear shell-specific setting |
scoop init <shell> | Output shell init script |
scoop completions <shell> | Generate completion script |
scoop lang [code] | Get/set language (en, ko, ja, pt-BR) |
scoop migrate list | List migratable envs (pyenv, conda, virtualenvwrapper) |
scoop migrate @env <name> | Migrate single environment |
scoop migrate all | Migrate all environments |
Most commands support --json for machine-readable output.
Global options: --quiet, --no-color
Key Concepts
Set a Specific Python Version as Global Default
To set Python 3.11.0 as the global default for new shells:
scoop install 3.11.0
scoop create py311 3.11.0
scoop use py311 --global
Important: --global stores an environment name (py311) in ~/.scoop/version,
not the raw Python version string. Local .scoop-version and SCOOP_VERSION
override the global default.
Create a Project Environment with Python 3.9.5
scoop install 3.9.5
scoop create myproject 3.9.5
scoop info myproject
If 3.9.5 is not found, check discovery with uv python list and
scoop list --pythons, then install and retry.
Uninstall Python and Associated Environments
# Optional preview
scoop list --python-version 3.12
# Remove Python 3.12 and all environments that use it
scoop uninstall 3.12 --cascade
# Verify cleanup
scoop list --pythons
scoop doctor
For automation, use scoop uninstall 3.12 --cascade --force.
Without --cascade, dependent environments are not removed and may become broken.
Temporarily Disable or Customize Auto-Activation (Project-Scoped)
# Current shell only (temporary disable)
export SCOOP_NO_AUTO=1
unset SCOOP_NO_AUTO
# Project-local behavior (writes .scoop-version in current dir)
scoop use system
scoop use myproject
# Terminal-only override (no file changes)
scoop shell system
scoop shell --unset
Use these without --global to avoid changing global settings.
Install Dependencies from requirements.txt in Active Environment
# environment already active (prompt shows: (myproject))
pip install -r requirements.txt
Use pip install -r path/to/requirements.txt for non-root files.
Verify with pip list.
List Python Versions and Associated Environments
scoop list --pythons
scoop list
scoop list --python-version 3.12
Use --json for automation and --bare for script-friendly output.
For full mapping in shell scripts, iterate versions from scoop list --pythons --bare
and query each with scoop list --python-version <VERSION> --bare.
Integrate Custom or Pre-Existing Python
# Preferred: explicit interpreter path
scoop create myenv --python-path /opt/python-debug/bin/python3
# Alternative: make interpreter discoverable via PATH
export PATH="/opt/python-debug/bin:$PATH"
scoop create myenv 3.13
Verify with uv python list, scoop info myenv, and scoop doctor -v.
Custom interpreter path is stored in ~/.scoop/virtualenvs/<name>/.scoop-metadata.json
(python_path field).
Version Files
Priority (first match wins):
SCOOP_VERSIONenv var (shell session override, set byscoop shell).scoop-versionin current directory (local, walks parent directories)~/.scoop/version(global default)
Shell Integration
scoop outputs shell code to stdout; the shell wrapper evals it (pyenv pattern).
Auto-activation triggers on directory change when .scoop-version is present.
Supported shells: bash, zsh, fish, PowerShell (Core 7.x+ and Windows PowerShell 5.1+)
Disable auto-activation: export SCOOP_NO_AUTO=1
Environment Name Rules
- Pattern:
^[a-zA-Z][a-zA-Z0-9_-]*$(max 64 chars) - Must start with a letter
- Reserved words: activate, base, completions, create, deactivate, default, delete, global, help, init, install, list, local, remove, resolve, root, system, uninstall, use, version, versions
Migration Sources
Import environments from pyenv-virtualenv, virtualenvwrapper, and conda.
Internationalization
Supported languages: English (en), Korean (ko), Japanese (ja), Portuguese-BR (pt-BR)
Priority: SCOOP_LANG env > ~/.scoop/config.json > system locale > en
Configuration
- Config file:
~/.scoop/config.json - Home directory:
~/.scoop/(override:SCOOP_HOME) - Metadata:
~/.scoop/virtualenvs/<name>/.scoop-metadata.json
Examples
Explore real-world usage examples in the examples/ directory on GitHub.
| Example | Description |
|---|---|
| Basic Workflow | Create, use, and remove environments |
| Migration from pyenv | Import pyenv-virtualenv environments |
| Multi-Project Setup | Manage multiple project environments |
| GitHub Actions CI | Use scoop in CI pipelines |