Metadata-Version: 2.3
Name: uv-launch
Version: 0.2.0
Summary: Immediate Python command startup with atomic background updates through uv
Requires-Dist: packaging>=24
Requires-Python: >=3.11
Project-URL: Repository, https://github.com/nimashoghi/uv-launch
Description-Content-Type: text/markdown

# uv-launch

Install Python commands that start immediately and update in the background. The foreground shell shim executes an already installed environment. It does not invoke uv or wait for network access.

Requires macOS or Linux, Python 3.11+, and [uv](https://docs.astral.sh/uv/). Install this tool into a stable environment with `uv tool install uv-launch` from [PyPI](https://pypi.org/project/uv-launch/). `uvx uv-launch --help` also runs the CLI without a tool installation; use the stable tool installation for long-lived managed commands because their background updater uses its Python environment. Then install commands:

```sh
uv-launch install ruff ruff
uv-launch install my-command 'package-name @ git+https://github.com/owner/project.git@main' \
  --entrypoint actual-console-script --with another-package --python 3.13
my-command --help
uv-launch update my-command
uv-launch status my-command
```

`--bin-dir` defaults to `~/.local/bin`. Use a separate directory for evaluation. The installer refuses to overwrite commands it does not own. `UV_LAUNCH_HOME` selects the installation-state directory; its default is `~/.local/share/uv-launch`.

Each invocation captures the current immutable generation and starts a detached update check. Concurrent checks for the same tool coalesce under a process lock. A check resolves all requested packages together, pinning Git dependencies to concrete revisions. If the resolved installation is unchanged, it keeps the existing environment. Otherwise it creates a new environment at its final path, checks dependencies and loads the Python entrypoint (or verifies ownership of a wheel-provided executable), then atomically switches the generation used by future launches. Virtual environments are never moved after creation, because their scripts contain absolute paths.

The launched command retains its arguments, working directory, environment and exit status. Updates never mutate its environment. An offline or failed update leaves the installed version usable. `status` reports the current generation and the last completed update result; background diagnostics are in `<UV_LAUNCH_HOME>/<command>/update.log`. Checks use the existing uv/Git authentication configuration and do not change it.

An initial installation requires dependency access. Automatic updates have a 600-second timeout per subprocess by default; configure it with `install --timeout`. Validation checks installation and entrypoint integrity, not application-specific behavior. Keep old generations while processes may still use them; this release deliberately does not automatically delete them. Reinstalling this launcher itself is explicit, using `uv tool upgrade uv-launch`.

## Service startup and companion commands

A service can require the latest successfully resolved dependencies at startup:

```sh
uv-launch run --refresh my-command -- serve --port 8080
```

Unlike the ordinary background-updating shim, `run --refresh` waits for a successful update and fails without executing stale code when that update fails. It then replaces itself with the selected command. Arguments, environment, working directory and exit status are preserved. SIGTERM during refresh terminates and reaps the resolver/build process group; after execution the application receives signals directly. Old generations remain available for workers that outlive their launcher.

Run a companion executable from the same installation with `uv-launch run --command python my-command -- -c 'print("probe")'`. Options to `run` precede the installation name. For several operations that must use exactly the same generation, capture once and pass the returned opaque `generation` value:

```sh
uv-launch select --refresh my-command
uv-launch run --generation GENERATION --command python my-command -- -c 'print("probe")'
uv-launch run --generation GENERATION my-command -- serve
```

The Python equivalents are `select_installation(name, refresh_dependencies=True)` and `execute_installation(installation, arguments, command=None)` from `uv_launch`. `execute_installation` uses `exec`, so successful execution does not return. Consumers should use these interfaces instead of reading installation symlinks or reconstructing generation paths.

## Script runtimes and Python imports

`uv_launch.dependencies` provides shared preparation for tools such as toolfuncs:

- `prepare_environment(requirements, python=..., background=True)` returns a ready immutable virtual environment. Warm calls reuse a generation without foreground resolution; first use and changed requirements wait for preparation. It does not snapshot the caller's script.
- `resolve_dependencies(requirements, python=..., constraints=(), background=True)` returns an immutable pinned requirements file. Resolutions are cached by requirements, interpreter/platform and constraints.
- `install_dependencies(requirements)` adds missing packages to the **current interpreter's environment**. It reuses satisfying installed dependencies, constrains resolution to preserve existing distributions, checks the selected graph before installation, and raises `DependencyConflictError` if preservation is impossible. It never synchronizes the whole environment or replaces existing distributions. Background resolution refresh does not install into the active interpreter or reload modules.
- `managed_target(path)` statically resolves a generated uv-launch command to its installed executable. It validates the shim against its recorded installation without executing shell code, importing the application, or requesting an update. This lets discovery tools inspect package metadata behind managed commands.

The shared resolver accepts floating Git branches and records exact commit IDs in each cached resolution. No separate script registration or checked-in lockfile is required. A process already using an installed package retains that package; a new cached resolution is not a hot reload. Use a compatible environment when requirements conflict. Each installed source distribution is identified by its recorded origin or package version via `distribution_requirement(distribution)`.

## Codex-router example

All five packages are resolved in one operation, so a moving Git ref produces one consistent suite revision:

```sh
uv-launch install codexr \
  'codex-router @ git+https://github.com/nimashoghi/codex-router.git@main#subdirectory=packages/codex-router' \
  --with 'codex-router-sdk @ git+https://github.com/nimashoghi/codex-router.git@main#subdirectory=packages/codex-router-sdk' \
  --with 'codex-monitor @ git+https://github.com/nimashoghi/codex-router.git@main#subdirectory=packages/codex-monitor' \
  --with 'codex-recovery @ git+https://github.com/nimashoghi/codex-router.git@main#subdirectory=packages/codex-recovery' \
  --with 'codex-accounts @ git+https://github.com/nimashoghi/codex-router.git@main#subdirectory=packages/codex-accounts' \
  --bin-dir ~/.local/share/codexr-evaluation/bin
```

Use that isolated command before choosing to replace an existing launcher. Updating the local codexr suite and preparing that exact suite on an SSH host remain separate responsibilities; uv-launch contains no Codex or SSH runtime logic.

## Development

```sh
uv sync
uv run pytest -q
uv run ruff check .
uv run ruff format --check .
```

Integration tests create local Git packages and exercise installation, immutable revisions, background publication, failed updates, concurrent checks, offline launches, and preservation of unrelated commands.
