Metadata-Version: 2.3
Name: uvlink
Version: 0.9.0
Summary: CLI helper for keeping .venv, node_modules, and other heavy folders outside synced project folders
Author: Cory Chu
Author-email: Cory Chu <cory@gwlab.page>
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Requires-Dist: questionary>=2.1
Requires-Dist: rich>=14.2
Requires-Dist: typer>=0.20
Requires-Python: >=3.12
Project-URL: Changelog, https://c0rychu.github.io/uvlink/CHANGELOG/
Project-URL: Documentation, https://c0rychu.github.io/uvlink/
Project-URL: Homepage, https://github.com/c0rychu/uvlink
Project-URL: Issues, https://github.com/c0rychu/uvlink/issues
Project-URL: Repository, https://github.com/c0rychu/uvlink.git
Description-Content-Type: text/markdown

# uvlink
[![PyPI - Version](https://img.shields.io/pypi/v/uvlink)](https://pypi.org/project/uvlink/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/uvlink)](https://pypi.org/project/uvlink/)
[![GitHub Actions - CI Tests](https://img.shields.io/github/actions/workflow/status/c0rychu/uvlink/ci-tests.yml?branch=main&label=CI%20Tests)](https://github.com/c0rychu/uvlink/actions/workflows/ci-tests.yml)
[![GitHub - License](https://img.shields.io/github/license/c0rychu/uvlink)](https://github.com/c0rychu/uvlink/blob/main/LICENSE)

`uvlink` is a CLI tool that moves heavy folders like Python's `.venv` and Node's `node_modules` out of your project into a local cache, and symlinks them back. It works for any folder you name. Perfect for developers who sync code to Dropbox, Google Drive, or iCloud. Only your source code syncs, not gigabytes of dependencies.

- [Documentation](https://c0rychu.github.io/uvlink/)
- [Changelog](docs/CHANGELOG.md)

> [!CAUTION]
> Since `v0.6.0`, the cache directory includes the venv type in its folder name and stores the environment under a matching subdirectory (e.g. `~/.local/share/uvlink/cache/<project-name>-<hash>-.venv/.venv`). Delete caches created with older versions and rerun `uvlink link` to migrate.


## Requirements

- Python 3.12+
- macOS, Linux, and Windows(beta support)

> [!NOTE]
> Windows support is considered beta as of `v0.8.0`. It uses directory junctions as a fallback when symlinks are not available, which does not require admin privileges or Developer Mode. Please report any issues you encounter on Windows!



## Install

### Using `uv tool` (recommended)

```bash
$ uv tool install uvlink
```

This installs the CLI into your `~/.local/bin` (or platform equivalent) with isolated dependencies handled by [Astral's uv](https://docs.astral.sh/uv/).

### Using `pip`

```bash
$ pip install uvlink
```


## Quick Start

Navigate to any Python project (which may be created by `uv init` for instance) and run `uvlink link`:

```bash
$ cd /path/to/your/project
$ uvlink link
```

The `link` command asks which directory to link:

```
? Which directory to link? (↑/↓, Enter)
 ❯ .venv
   node_modules
   type your own
```

Use the arrow keys to move and Enter to pick. Pressing Enter right away picks `.venv`, which is where `uv sync` installs by default. uvlink then creates a `.venv` symlink in your project pointing to a cached environment. When not run in a terminal (e.g. in a script), it uses `.venv` without asking.

You can also skip the menu by giving the name directly, for example:

```bash
$ uvlink link myenv
```

This creates a `myenv` symlink instead. 

The cached environment is stored under `$XDG_DATA_HOME/uvlink/cache/<project-name>-<hash>-<venv_name>/<venv_name>` (or `~/.local/share/uvlink/cache/...` if `XDG_DATA_HOME` is not set).

After linking, you can use the virtual environment just like you would with `uv` whithin the project directory:

```bash
$ source .venv/bin/activate  # Activate the environment
$ uv sync                    # Install dependencies into the cached .venv
$ uv run python script.py    # Run commands in the environment
```

Since `.venv` exists as a symlink in your project directory, all standard activation methods work. Your cloud service will ignore the heavy virtual environment files since they're stored in the cache.

**List all cached environments:**

```bash
$ uvlink ls
```

Shows all projects with cached environments and their link status. Projects where the symlink has been removed (e.g., via `rm .venv`) will show as "Not Linked" (❌). You can relink them by running `uvlink link` again in that project directory.

**Clean up unlinked caches:**

```bash
$ uvlink gc
```

Removes cached environments for projects that no longer have working symlinks (marked as "Not Linked" in `uvlink ls`), freeing up disk space.


## Demo
![](docs/fig/uvlink-demo@2x.gif)

## Advanced Usage

`uvlink` ships with a [Typer](https://typer.tiangolo.com/) CLI. Run `uvlink --help` for all available options and commands.

**Specify a project directory:**

```bash
$ uvlink --project-dir /path/to/project link
```

Works from any location without needing to `cd` into the project directory first.

**Swap the .venv folder name:**

```bash
$ uvlink --project-dir /path/to/project link [VENV_TYPE]
```

Custom `[VENV_TYPE]` are helpful when sharing a cache across tooling expectations (e.g., `.venv-prod`, `.venv-dev`).

**Custom Cache Location:**

The default cache location is `$XDG_DATA_HOME/uvlink/cache` if `XDG_DATA_HOME` is set, otherwise it falls back to `~/.local/share/uvlink/cache`.

To override the cache location, you can either:

1. Set the `XDG_DATA_HOME` environment variable:
   ```bash
   $ export XDG_DATA_HOME=/my/custom/path
   $ uvlink link
   ```
> [!WARNING]
> This will affect other software using `XDG_DATA_HOME` environment variable as well!

2. Use the `--cache-root` option (must be used consistently across all commands):
   ```bash
   $ uvlink --cache-root /path/to/cache link
   $ uvlink --cache-root /path/to/cache ls
   $ uvlink --cache-root /path/to/cache gc
   ```

   Note: When using `--cache-root`, you must specify it for every command (`link`, `ls`, `gc`). Consider setting up a shell alias to avoid repetition:
   ```bash
   $ alias uvlink='uvlink --cache-root /my/cache/root'
   ```



## How It Works

`uvlink` stores environments under `$XDG_DATA_HOME/uvlink/cache/<project-name>-<hash>-<venv_name>/<venv_name>` (or `~/.local/share/uvlink/cache/...` if `XDG_DATA_HOME` is not set) and creates a symlink `<venv_name>` in your project directory pointing to that cached location.

Each project receives a stable hash based on its absolute path, so repeated runs target the same cache location. The symlink behaves like a regular `.venv` directory for most purposes. You can activate it with `source .venv/bin/activate` or use `uv run` commands as usual.

**What `uvlink` does:**
- Creates a symlink `.venv` (or your custom name) in your project directory
- Stores the actual virtual environment in a centralized cache location

**What `uvlink` does NOT do:**
- It does not install packages (use `uv sync`, `uv add`, `uv pip install`, etc.)
- It does not remove symlinks (use `rm .venv` if you want to unlink and run `uvlink gc` after unlink to clean up the unlinked caches.)
- It does not manage or activate environments (use standard `uv` commands)



## Contributing

Issues and pull requests are welcome. Please keep docstrings and comments in the Google style already used throughout the codebase and run the included linters/formatters before submitting. A pre-commit configuration is provided that runs Ruff; install it with `pre-commit install` to match formatting.

Also, feel free to make comments/discussion at

- [Reddit Post](https://www.reddit.com/r/Python/comments/1p662t0/uvlink_a_cli_to_keep_venv_in_a_centralized_cache/)
- [Hacker News Post](https://news.ycombinator.com/item?id=46042792)
