Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 WayThe scoop Way
.venv scattered across projects~/.scoop/virtualenvs/ centralized
Manual source .venv/bin/activateAuto-activate on directory entry
pyenv-virtualenv is slowuv-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 friendlyscoop use --link creates .venv symlink for IDE discovery
  • Health checksscoop doctor diagnoses your setup

Getting Started

Ready to scoop? Head to the Installation guide to get started.

Installation

Prerequisites

DependencyVersionInstall Command
uvLatestcurl -LsSf https://astral.sh/uv/install.sh | sh
Rust1.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:

  1. Creates .scoop-version file in the current directory
  2. 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

TaskCommand
List environmentsscoop list
List Python versionsscoop list --pythons
Show environment infoscoop info myproject
Remove environmentscoop remove myproject
Check installationscoop 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

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):

PrioritySourceSet by
1SCOOP_VERSION env varscoop shell
2.scoop-version filescoop use (walks parent directories)
3~/.scoop/version filescoop 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

VariableDescriptionDefault
SCOOP_HOMEBase directory~/.scoop
SCOOP_VERSIONOverride version (highest priority)(unset)
SCOOP_NO_AUTODisable auto-activation(unset)
SCOOP_ACTIVECurrently active environment(set by scoop)
SCOOP_RESOLVE_MAX_DEPTHLimit 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

ShellStatus
ZshFull support (auto-activation, completion)
BashFull support (auto-activation, completion)
FishFull support (auto-activation, completion)
PowerShellFull 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:

  1. uv-managed installations in ~/.local/share/uv/python/ (installed via scoop install or uv python install)
  2. System Python on PATH — executables named python, python3, or python3.x
  3. 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

# 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:

  1. Direct path (recommended): scoop create <env> --python-path /path/to/python
  2. PATH-based discovery: add your Python to PATH, then run scoop create <env> <version>

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:

  1. Discovers environments from pyenv (~/.pyenv/versions/), conda (conda info --envs), or virtualenvwrapper ($WORKON_HOME)
  2. Creates a new scoop environment with the same Python version
  3. Reinstalls packages using uv for improved performance
  4. Preserves originals by default (use --delete-source to 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

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

ScenarioWhat to do
Standard Python versionscoop 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 executablescoop create myenv --python-path /path/to/python
Custom Python in non-standard pathAdd to PATH, then scoop create myenv <version>
PyPy or alternative interpreterscoop create myenv --python-path /opt/pypy/bin/pypy3
Existing pyenv/conda environmentsscoop migrate all
Shared Python installationsSet UV_PYTHON_INSTALL_DIR
Force system-only PythonSet UV_PYTHON_PREFERENCE=only-system
Uninstall Python + cleanup envsscoop uninstall 3.12 --cascade (or manual workflow)
Find envs using a Python versionscoop list --python-version 3.12
Fix broken environmentsscoop 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:

  • --global stores an environment name in ~/.scoop/version, not a raw version like 3.11.0.
  • This global default is applied in new shells and directories without a local .scoop-version.
  • Priority is: SCOOP_VERSION env 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-version changes from scoop 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 --json for machine-readable output.
  • Use --bare for 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_path field.

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:

ShellConfig FileLine to Remove
Bash~/.bashrceval "$(scoop init bash)"
Zsh~/.zshrceval "$(scoop init zsh)"
Fish~/.config/fish/config.fisheval (scoop init fish)
PowerShell$PROFILEInvoke-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:

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

CommandAliasesDescription
scoop listlsList virtualenvs or Python versions
scoop create-Create virtualenv
scoop use-Set + activate environment
scoop removerm, deleteRemove 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:

OptionDescription
-q, --quietSuppress all output
--no-colorDisable colored output
-h, --helpShow help message
-V, --versionShow version

Environment Variables

VariableDescriptionDefault
SCOOP_HOMEBase directory for scoop~/.scoop
SCOOP_NO_AUTODisable auto-activation(unset)
SCOOP_LANGDisplay language (en, ko, ja, pt-BR)System locale
NO_COLORDisable colored output(unset)

Directory Layout

LocationPurpose
~/.scoop/virtualenvs/Virtual environments storage
~/.scoop/versionGlobal default environment
.scoop-versionLocal environment preference
.venvSymlink to active environment (with --link)

list

List all virtual environments or installed Python versions.

Aliases: ls

Usage

scoop list [options]

Options

OptionDescription
--pythonsShow Python versions instead of virtualenvs
--python-version <VERSION>Filter environments by Python version (e.g., 3.12)
--bareOutput names only (for scripting)
--jsonOutput 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-version cannot be combined with --pythons (which lists Python installations, not environments).

Empty Results

  • If no Python versions are installed, scoop list --pythons shows no entries.
  • If no environments exist, scoop list shows 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

ArgumentRequiredDefaultDescription
nameYes-Name for the new virtualenv
python-versionNo3 (latest)Python version (e.g., 3.12, 3.11.8)

Options

OptionDescription
--force, -fOverwrite 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:

  1. uv-managed Python installations
  2. System Python on PATH (Homebrew, apt, pyenv, etc.)
  3. 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:

  1. Validate the path (exists, is a file, is executable)
  2. Auto-detect the Python version from the binary
  3. 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

ArgumentRequiredDescription
nameNoName of the virtualenv, or system for system Python

Options

OptionDescription
--unsetRemove version file (local or global)
--global, -gSet as global default
--linkCreate .venv symlink for IDE compatibility
--no-linkDo not create .venv symlink (default)

Behavior

  • Creates .scoop-version file 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

ArgumentRequiredDescription
nameYesName of the virtualenv to remove

Options

OptionDescription
--force, -fSkip 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

ArgumentRequiredDefaultDescription
versionNolatestPython version (e.g., 3.12, 3.11.8)

Options

OptionDescription
--latestInstall latest stable Python (default)
--stableInstall 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 patch
  • 3.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:

  1. uv-managed — installed via scoop install or uv python install
  2. System PATH — Homebrew, apt, pyenv, or any Python on your PATH
  3. 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

ArgumentRequiredDescription
versionYesPython version to remove

Options

OptionDescription
--cascadeAlso remove all virtual environments using this Python version
--force, -fSkip 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 --cascade to 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

OptionDescription
-v, --verboseShow more details (can repeat: -vv)
--jsonOutput diagnostics as JSON
--fixAuto-fix issues where possible

Checks Performed

CheckWhat it verifies
uv installationuv is installed and accessible
Shell integrationShell hook is properly configured
Environment integrityPython 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 python binary 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 doctor periodically 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

ArgumentRequiredDescription
nameYesName 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

SubcommandDescription
listList environments available for migration
@env <name>Migrate a single environment by name
allMigrate all discovered environments

Supported Sources

SourceDetection
pyenv-virtualenv~/.pyenv/versions/ (non-system virtualenvs)
virtualenvwrapper$WORKON_HOME or ~/.virtualenvs/
condaconda info --envs

Options

OptionDescription
--jsonOutput as JSON
--quietSuppress output
--no-colorDisable 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

  1. Discovery: Scans configured source paths for virtual environments
  2. Extraction: Identifies Python version and installed packages
  3. Recreation: Creates new scoop environment with same Python version
  4. Package Install: Reinstalls packages using uv pip install
  5. Cleanup: Originals are preserved by default; --delete-source removes them after successful migration

Notes

  • Original environments are preserved by default; use --delete-source to remove sources after migration
  • Package versions are preserved where possible
  • Migration creates fresh environments using uv for 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

ArgumentDescription
<code>Language code to set (e.g., en, ko)

Options

OptionDescription
--listList all supported languages
--resetReset to system default language
--jsonOutput as JSON

Supported Languages

CodeLanguage
enEnglish (default)
ko한국어 (Korean)
ja日本語 (Japanese)
pt-BRPortuguês (Brazilian Portuguese)

Language Detection Priority

  1. SCOOP_LANG environment variable
  2. ~/.scoop/config.json setting
  3. System locale (via sys-locale)
  4. 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

ArgumentRequiredDescription
shellYesShell 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), the eval is automatic:

scoop shell myenv    # Works directly

Arguments

ArgumentRequiredDescription
nameNoEnvironment name or system

Options

OptionDescription
--unsetClear shell-specific environment
--shell <SHELL>Target shell type (auto-detected if not specified)

Behavior

  • Sets SCOOP_VERSION environment variable
  • If name is an environment: also outputs activation script
  • If name is system: also outputs deactivation script
  • --unset: outputs unset 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

ArgumentRequiredDescription
shellYesShell 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 init includes completions.

Contributing

Guide for contributing to scoop development.

Prerequisites

  • Rust 1.85+ (Edition 2024)
  • uv - Python package manager (install)
  • prek - Pre-commit hooks (install)

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:

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-version files
  • 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 uv CLI commands
  • Python version management

Shell Integration

Shell scripts are embedded in Rust code:

  • src/shell/bash.rs - Bash init script
  • src/shell/zsh.rs - Zsh init script

Key components:

  1. Wrapper function - Intercepts use/activate/deactivate
  2. Hook function - Auto-activation on directory change
  3. Completion function - Tab completion

Adding a New Command

  1. Define command in src/cli/mod.rs:
#![allow(unused)]
fn main() {
#[derive(Subcommand)]
pub enum Commands {
    // ...
    MyCommand {
        #[arg(short, long)]
        option: bool,
    },
}
}
  1. Create handler in src/cli/commands/my_command.rs:
#![allow(unused)]
fn main() {
pub fn execute(output: &Output, option: bool) -> Result<()> {
    // Implementation
    Ok(())
}
}
  1. Export in src/cli/commands/mod.rs

  2. Wire up in src/main.rs

  3. 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:

  1. Create PR with changes
  2. Merge to main
  3. release-plz creates release PR
  4. Merge release PR to publish to crates.io

Internal Documentation

See .docs/ for internal technical references:

  • TECHNICAL_REFERENCE.md - Implementation details
  • SHELL_GOTCHAS.md - Shell integration pitfalls
  • IMPLEMENTATION_PLAN.md - Development roadmap
  • brand/brand.md - Brand guidelines

Code Style

  • Follow Rust conventions
  • Run cargo fmt before committing
  • Keep functions small and focused
  • Document public APIs with /// comments
  • Use thiserror for 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:


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

  1. Concise: CLI messages should be short and clear
  2. Natural: Use natural phrasing, not word-for-word translation
  3. Casual: Friendly, approachable tone — like talking to a colleague
  4. 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

TypeEnglish ExampleGuidance
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:

  1. Check your community — What do Python developers in your language use?
  2. Consistency — Pick one term and stick with it throughout
  3. 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:

TermReason
scoopBrand name
uvTool name
pyenvTool name
condaTool name
virtualenvTechnical term
virtualenvwrapperTool name
PythonLanguage name
shellTechnical term (bash, zsh, fish)
JSONFormat name
PATHEnvironment variable
pipTool 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:

EnglishWhat to look for
environmentYour language’s term for “environment”
createCommon verb for “make/create”
remove/deleteCommon verb for “delete/remove”
installStandard software installation term
uninstallStandard software removal term
activateTerm for “enable/turn on”
deactivateTerm for “disable/turn off”
migrateIT term for migration (often kept as loanword)
versionYour language’s term for “version”
pathYour language’s term for file path
errorYour language’s term for “error”
successYour 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:

TermMeaningGuidance
scoopThe toolAlways keep as “scoop”
flavorvirtualenvTranslate if the metaphor works in your language
freezer~/.scoop/ directoryTranslate 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 build succeeds
  • cargo test passes
  • SCOOP_LANG={code} scoop lang shows 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
  • Cli struct defines global options
  • Commands enum defines subcommands
  • Each command has an execute function in commands/

Core Layer (core/)

ModulePurpose
doctorHealth check system with Check trait
metadataJSON metadata for virtualenvs
versionVersion file discovery and parsing
virtualenvVirtualenv entity and operations

Shell Layer (shell/)

Generates shell scripts for integration:

  • init_script() - Returns shell initialization code
  • Wrapper function for scoop command
  • Auto-activation hooks
  • Tab completion definitions

Output Layer (output/)

Handles terminal output formatting:

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() - Returns SCOOP_HOME or ~/.scoop
  • virtualenvs_dir() - Returns virtualenvs directory
  • global_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-version is 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

CratePurpose
clapArgument parsing & completion
clap_completeShell completion generation
serdeJSON serialization
serde_jsonMetadata persistence
thiserrorError type definitions
owo-colorsTerminal colors
indicatifProgress bars & spinners
dialoguerInteractive prompts
dirsHome directory resolution
whichBinary lookup (uv, python)
regexVersion parsing & validation
walkdirDirectory traversal
rust-i18nInternationalization (en, ko, ja, pt-BR)
sys-localeSystem locale detection
chronoTimestamp generation

Extension Points

Adding a New Shell

  1. 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.

  1. Add to ShellType enum in cli/mod.rs:
#![allow(unused)]
fn main() {
pub enum ShellType {
    // ... existing
    MyShell,
}
}
  1. Add detection to shell::detect_shell() in src/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

  1. 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
    }
}
}
  1. 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

OperationTime ComplexityNotes
List envsO(n)n = number of virtualenvs
Create envO(1)**Depends on uv performance
Delete envO(1)Simple directory removal
Version resolutionO(d)d = directory depth (walks parents)
Doctor checksO(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

  1. Path Traversal: All user-provided names are validated via regex before use in filesystem operations.
  2. Command Injection: uv commands are constructed using typed arguments, not string concatenation.
  3. Symlink Safety: Doctor checks detect and warn about broken symlinks.
  4. Metadata Integrity: JSON parsing errors are gracefully handled without panics.

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 directory
  • python_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_at is DateTime<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 installed
  • HomeCheck - Checks SCOOP_HOME directory
  • VirtualenvCheck - Validates virtualenvs directory
  • SymlinkCheck - Checks for broken virtualenv Python symlinks (e.g., <env>/bin/python)
  • ShellCheck - Verifies shell integration
  • VersionCheck - 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:

  1. FISH_VERSION → Fish
  2. PSModulePath → PowerShell
  3. ZSH_VERSION → Zsh
  4. 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:

  1. SCOOP_VERSION environment variable (checked at shell hook level, not in VersionService)
  2. .scoop-version in current directory
  3. .scoop-version in parent directories (walks up)
  4. ~/.scoop/version (global default)

Note: .python-version is 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

  1. Define command in cli/mod.rs:
#![allow(unused)]
fn main() {
#[derive(Subcommand)]
pub enum Commands {
    // ... existing commands
    MyCommand {
        #[arg(help = "Argument description")]
        arg: String,
    },
}
}
  1. 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(())
}
}
  1. 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),
            ],
        }
    }
}
}


For AI/LLM Tools

When analyzing or modifying this codebase:

  1. Use symbolic tools for precise navigation:

    • find_symbol to locate specific functions/types
    • find_referencing_symbols to understand usage
    • get_symbols_overview for module structure
  2. Follow existing patterns:

    • Error handling: Always return Result<T> with ScoopError
    • 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
  3. 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.rs utilities
    • Shell detection via shell::detect_shell() function
  4. Documentation requirements:

    • All pub fn must have doc comments
    • Examples in doctests where applicable
    • Error conditions documented
    • Shell integration changes require cross-shell testing

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:

ModuleTestsCoverage
error::tests54Error types, codes, suggestions
output::json::tests35JSON serialization, edge cases
validate::tests30Name/version validation
core::version::tests18Version file resolution
core::virtualenv::tests12Virtualenv service
paths::tests16Path utilities
shell::*::tests14Shell 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

  1. Test naming: test_<function>_<scenario>_<expected>
  2. Arrange-Act-Assert: Clear test structure
  3. One assertion per test: When practical
  4. Test edge cases: Empty, unicode, special chars, boundaries
  5. No test interdependencies: Each test should be isolated
  6. 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

LintSeverityDescription
clippy::unwrap_usedWarnUse ? or expect() instead
clippy::panicWarnAvoid panic in library code
clippy::todoWarnRemove before release
clippy::dbg_macroWarnRemove debug macros
clippy::print_stdoutWarnUse 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

HookDescriptionWhen
cargo-fmtCode formattingPre-commit
cargo-clippyLintingPre-commit
cargo-checkType checkingPre-commit
trailing-whitespaceRemove trailing spacesPre-commit
end-of-file-fixerEnsure newline at EOFPre-commit
check-tomlValidate TOML filesPre-commit
check-yamlValidate YAML filesPre-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

ItemConventionExample
Modulessnake_caseversion_file
Functionssnake_caseget_version()
TypesPascalCaseVirtualenvService
ConstantsSCREAMING_SNAKEMAX_NAME_LENGTH
Lifetimesshort 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 unsafe unless 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!() or dbg!() 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) and llms-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

CommandDescription
scoop listList virtualenvs (aliases: ls)
scoop list --pythonsList installed Python versions
scoop create <name> [version]Create virtualenv (default: latest Python)
scoop use <name>Set + activate environment
scoop use <name> --globalSet as global default
scoop use <name> --linkAlso create .venv symlink for IDE
scoop use systemDeactivate, use system Python
scoop use --unsetRemove 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 doctorHealth check
scoop doctor --fixAuto-fix issues
scoop shell <name>Set shell-specific env (temporary)
scoop shell --unsetClear 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 listList migratable envs (pyenv, conda, virtualenvwrapper)
scoop migrate @env <name>Migrate single environment
scoop migrate allMigrate 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):

  1. SCOOP_VERSION env var (shell session override, set by scoop shell)
  2. .scoop-version in current directory (local, walks parent directories)
  3. ~/.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.

ExampleDescription
Basic WorkflowCreate, use, and remove environments
Migration from pyenvImport pyenv-virtualenv environments
Multi-Project SetupManage multiple project environments
GitHub Actions CIUse scoop in CI pipelines