Metadata-Version: 2.3
Name: uvrs
Version: 0.9.0
Summary: Create and run uv scripts with POSIX standardized shebang line
License: MIT License
         
         Copyright (c) 2025 Trey Hunner
         
         Permission is hereby granted, free of charge, to any person obtaining a copy
         of this software and associated documentation files (the "Software"), to deal
         in the Software without restriction, including without limitation the rights
         to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         copies of the Software, and to permit persons to whom the Software is
         furnished to do so, subject to the following conditions:
         
         The above copyright notice and this permission notice shall be included in
         all copies or substantial portions of the Software.
         
         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
         THE SOFTWARE.
Requires-Dist: rich>=13.9.0
Requires-Dist: tomlkit>=0.13.3
Requires-Python: >=3.10
Project-URL: homepage, https://github.com/treyhunner/uvrs
Project-URL: issues, https://github.com/treyhunner/uvrs/issues
Project-URL: repository, https://github.com/treyhunner/uvrs
Description-Content-Type: text/markdown

# 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)][PEP 723], there are several rough edges when managing scripts:

1. **Non-portable shebang**:
   The [recommended shebang][uv 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][11876]) and uv does not currently have a single command shortcut to run a script ([uv issue #16241][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][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][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):

```console
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:

```bash
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:

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

```bash
uv add --script <path> <package>
```

### `uvrs remove <path> <package>`

This is equivalent to:

```bash
uv remove --script <path> <package>
```

### `uvrs stamp <path>`

This is equivalent to:

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

### `uvrs python <path> [args...]`

This is equivalent to:

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

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

```bash
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:

```console
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:

```python
#!/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`:

```console
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:

```console
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:

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

Will be transformed to:

```python
#!/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`:

```console
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:

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

To remove a dependency:

```console
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:

```console
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](https://docs.astral.sh/uv/guides/scripts/#improving-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:

```console
uvrs python ~/bin/my-script
```

### Using `uvrs pip`

Inspect packages in the script's environment:

```console
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][16241].

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][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][11876]) or use a new dedicated command ([uv issue #16241][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][13123]).


[PEP 723]: https://peps.python.org/pep-0723/
[uv shebang]: https://docs.astral.sh/uv/guides/scripts/#using-a-shebang-to-create-an-executable-file
[11876]: https://github.com/astral-sh/uv/issues/11876
[16334]: https://github.com/astral-sh/uv/issues/16334
[16241]: https://github.com/astral-sh/uv/issues/16241
[13123]: https://github.com/astral-sh/uv/issues/13123
