Skip to content

treyhunner/uvrs

Repository files navigation

uvrs

A tool for managing uv scripts more easily.

Unlike uv, uvrs adds a shebang line, sets an executable bit, runs scripts with exact dependency syncing, and timestamp-pins requirements by default.

Why this exists

While uv has excellent support for inline script metadata (PEP 723), there are several rough edges when managing scripts:

  1. Non-portable shebang: The recommended shebang uses #!/usr/bin/env -S uv run --script, which relies on the non-standard -S flag that doesn't work on some systems (uv issue #11876) and uv does not currently have a single command shortcut to run a script (uv issue #16241).

  2. No executable bit: uv init --script creates files that aren't executable by default, requiring a manual chmod +x.

  3. Inconsistent syncing: uv run --script is deliberately inexact by default (uv issue #16334). It may implicitly sync some changes, but doesn't guarantee that the installed dependencies match the virtual environment consistently.

  4. No reproducibility by default: Scripts don't include exclude-newer timestamps, so running the same script weeks later may use different package versions, breaking reproducibility. There is also no easy command for adding or updating the exclude-newer timestamp in a script (uv issue #13123)

uvrs addresses all of these issues:

  • Portable shebang that works everywhere: #!/usr/bin/env uvrs
  • Executable by default for new and fixed scripts
  • Consistent syncing via uv run --exact --script which ensures the environment always matches metadata
  • Reproducible by default with automatic exclude-newer timestamps

Installation

This should be installed as a globally available tool (so the above shebang line works):

uv tool install -p 3.14 uvrs

That will install uvrs using Python 3.14 (for nicely colorized help text).

What each command does

Here's what uvrs does under the hood, compared to the equivalent uv workflow:

uvrs init <path>

This is equivalent to:

uv init --script <path>
# Add #!/usr/bin/env uvrs shebang
# Add exclude-newer timestamp to metadata
chmod +x <path>

uvrs fix <path>

This is equivalent to:

# Update shebang to #!/usr/bin/env uvrs
# Add exclude-newer timestamp if missing
uv sync --script <path> --upgrade
chmod +x <path>

uvrs add <path> <package>

This is equivalent to:

uv add --script <path> <package>

uvrs remove <path> <package>

This is equivalent to:

uv remove --script <path> <package>

uvrs stamp <path>

This is equivalent to:

# Update exclude-newer timestamp to current time
uv sync --script <path> --upgrade

uvrs python <path> [args...]

This is equivalent to:

# Find the Python executable for the script's environment
python_path=$(uv python find --script <path>)
# Run the Python executable with the provided arguments
$python_path [args...]

This allows you to run Python commands in the context of the script's virtual environment, such as:

  • uvrs python <path> to launch a Python REPL
  • uvrs python <path> -m pdb <path> to launch PDB

uvrs pip <path> [args...]

This is equivalent to:

# Find the Python executable for the script's environment
python_path=$(uv python find --script <path>)
# Run uv pip with the script's Python
uv pip [args...] --python $python_path

This allows you to use pip commands in the context of the script's virtual environment, such as:

  • uvrs pip <path> list to list installed packages
  • uvrs pip <path> show <package> to show package details
  • uvrs pip <path> freeze to output installed packages in requirements format

uvrs <path>

This is equivalent to:

uv run --exact --script <path>

Using --exact guarantees the managed virtual environment always matches the script's inline metadata before execution. This means any dependency changes, including those introduced by uvrs add or uvrs remove, are respected the next time you run the script.

Creating new uv scripts

To initialize a new uv script with a uvrs shebang line use the init command:

uvrs init ~/bin/my-script --python 3.12

This will create the file ~/bin/my-script using uv init --script ~/bin/my-script --python 3.12 and then add an appropriate shebang line to the beginning of the script.

By default, uvrs init also adds an exclude-newer timestamp to improve reproducibility:

#!/usr/bin/env uvrs
# /// script
# requires-python = ">=3.12"
# dependencies = []
#
# [tool.uv]
# exclude-newer = "2025-10-15T20:30:45Z"
# ///


def main() -> None:
    print("Hello from my-script!")


if __name__ == "__main__":
    main()

To skip adding the timestamp, use --no-stamp:

uvrs init ~/bin/my-script --no-stamp

Updating existing scripts

To update an existing Python script to use the uvrs shebang, use the fix command:

uvrs fix ~/bin/my-script

This command:

  1. Updates the shebang to #!/usr/bin/env uvrs
  2. Adds PEP 723 metadata with an exclude-newer timestamp (if not present)
  3. Runs uv sync --script --upgrade to ensure the environment is up to date

For example, a plain Python script like:

#!/usr/bin/env python
print("Hello!")

Will be transformed to:

#!/usr/bin/env uvrs
# /// script
# dependencies = []
#
# [tool.uv]
# exclude-newer = "2025-10-16T00:25:00Z"
# ///

print("Hello!")

To skip adding the timestamp and metadata, use --no-stamp:

uvrs fix ~/bin/my-script --no-stamp

Managing dependencies

To update the dependencies within inline script metadata, use uvrs add and uvrs remove.

To add a new dependency:

uvrs add ~/bin/my-script 'rich'

To remove a dependency:

uvrs remove ~/bin/my-script 'rich'

The environment will sync automatically the next time you run the script (uvrs uses uv run --exact which ensures the environment matches the metadata exactly).

Updating timestamps and upgrading dependencies

To update the exclude-newer timestamp and upgrade all dependencies to the latest versions allowed by your constraints, use the stamp command:

uvrs stamp ~/bin/my-script

This command:

  1. Updates the exclude-newer field in the script's [tool.uv] section to the current UTC timestamp
  2. Runs uv sync --script --upgrade to upgrade dependencies and rebuild the environment

The exclude-newer field limits package versions to those published before the specified timestamp, which improves reproducibility by preventing unexpected updates.

Inspecting a script's environment

To inspect what's installed in a script's environment or run Python commands in its context, use the python and pip commands.

Using uvrs python

Run Python commands or a Python REPL in the script's environment:

uvrs python ~/bin/my-script

Using uvrs pip

Inspect packages in the script's environment:

uvrs pip ~/bin/my-script list

Note: For adding or removing dependencies, use uvrs add and uvrs remove instead, as they properly update the script's inline metadata.

The goal

Eventually, I would like to see a similar tool integrated into uv.

Until that time, I plan to maintain this uvrs tool.

How uv could make uvrs largely unnecessary

Using uv scripts with inline-metadata already feels close to magical. From my viewpoint, uvrs mainly bridges three gaps that I hope uv will eventually close:

  1. Exact execution by default: Update the documentation to recommend uv run --exact --script instead of uv run --script in script shebang lines (uv issue #16334)

  2. Script bootstrapping: Update uv init --script to add a shebang and set an executable bit. The shebang would either relying on /usr/bin/env -S (uv issue #11876) or use a new dedicated command (uv issue #16241).

  3. Timestamp management: Add a command to more easily "soft pin" to the current time using exclude-newer: ideally uv timestamp --script or even a general uv config (uv issue #13123).

About

Create and run uv scripts with POSIX standardized shebang line

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published