Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
uv run is uv's killer feature: it runs a command inside the project's virtualenv without you needing to activate first. Combined with [project.scripts] entry points in pyproject.toml, you can ship a CLI as pip install myapp && myapp --help — no python -m myapp.cli, no shebang, no PATH hassles. This is how every modern Python tool ships (ruff, uv itself, pip-tools).
An entry point declared in [project.scripts] is a thin shim script that pip install writes into the environment's bin/ directory: when you run myapp, it calls from myapp.cli import main; sys.exit(main()) under the hood. Editable install (pip install -e .) links directly to your source tree, so every code change is immediately visible without reinstalling — essential for iterative CLI development. uv run goes one step further: it finds the project venv automatically and runs any command inside it without requiring source .venv/bin/activate.
uv pip install -e . then run myapp. Note it's a real binary on your PATH.uv run myapp — same effect, but doesn't need the env activated.which myapp. It's a tiny shim in .venv/bin/ that imports your function.myapp-admin = "myapp.admin:main". Re-install. Now you have two CLIs.Use these three in order. Each builds on the one before.
In one paragraph, explain what `[project.scripts]` does — when I run `myapp` in my shell, what's the file chain that ends in my Python function?
How does `uv run` find the right environment? Walk me through the resolution: pwd, .venv, project root, fallback.
I want a single Python tool that ships as a single binary (no Python required on the user machine). Compare PyInstaller, shiv, and pex — which is right for a 50 MB CLI in 2026?
# src/myapp/cli.py
import sys
def main() -> int:
print(f"hello from myapp! args: {sys.argv[1:]}")
return 0
if __name__ == "__main__":
sys.exit(main())
# pyproject.toml
[project]
name = "myapp"
version = "0.1.0"
[project.scripts]
myapp = "myapp.cli:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
# install editable + use:
$ uv pip install -e .
Installed 1 package in 4ms
$ myapp hello world
hello from myapp! args: ['hello', 'world']
# uv run is even shorter — no install required:
$ uv run python -c "from myapp import cli; cli.main()"python3 main.py