name: uv
description: |
uv + uvx — Astral's fast Python package/project manager. Installs as a
direct-download binary (no pixi env, no Python dep). Pulled via the
download: verb with strip_components: 1 to handle the upstream
tarball's arch-prefixed top-level directory.
Use when working with the uv candy or when deciding whether to install a
CLI tool via pixi vs. direct binary download.
uv -- Python package / project manager (astral-sh/uv)
Candy Properties
| Property | Value |
|---|---|
| Kind | Direct-download binary candy |
| Install files | charly.yml (no pixi.toml, no dependencies) |
| Depends | (none) — uv is a self-contained Rust binary |
| Binaries | /usr/local/bin/uv, /usr/local/bin/uvx |
Why direct download (not pixi)
uv is a completely self-contained Rust binary needing no Python runtime, so
installing it via pixi (pixi.toml with uv = "*" + require: python,
landing it in $HOME/.pixi/envs/default/bin/ inside a conda-forge Python
environment) would be the wrong fit. Pixi would bring three problems uv
doesn't warrant:
- Image bloat — the pixi env drags in ~500 MB of Python stdlib + numpy transitively, nothing of which uv actually uses.
- HOME-dependency — the binary path would vary per-image's
$HOME, and HOME-relative PATH entries in child images go subtly wrong when thecharlyauto-intermediate machinery bakes uid=1000 paths into boxes deployed at uid=0 (handled incharly/intermediates.go— see/charly-internals:generate-source"UID-keyed sibling grouping"). - Confusion — "uv is installed but
which uvsays not found" for anyone who isn't in a pixi shell.
So uv installs the way /charly-coder:typst and /charly-languages:pixi itself do
— fetch the upstream binary, unpack to /usr/local/bin, done. System-wide
reachable, always on PATH, no HOME gymnastics.
Install plan
# a child step node under the uv candy entity
uv-download:
run: download and unpack the uv binary to /usr/local/bin
download: "https://github.com/astral-sh/uv/releases/latest/download/uv-${BUILD_ARCH}-unknown-linux-gnu.tar.gz"
extract: tar.gz
strip_components: 1
to: /usr/local/bin
run_as: root
${BUILD_ARCH} expands to x86_64 / aarch64 at build time. Upstream
ships per-arch tarballs under stable latest-download URLs, nested one
level deep (uv-x86_64-unknown-linux-gnu/uv, …/uvx). The
strip_components: 1 modifier (see /charly-image:layer "Download verb")
collapses that wrapper so both binaries land directly at
/usr/local/bin/uv and /usr/local/bin/uvx.
Tests
Three build-scope tests ship with the candy:
| Test | Purpose |
|---|---|
uv-binary |
/usr/local/bin/uv exists |
uv-version |
uv --version exits 0 |
uvx-binary |
/usr/local/bin/uvx exists (the per-run tool runner) |
Used In Boxes
/charly-coder:fedora-coder— the canonical kitchen-sink dev box.- Any box composing
hermes-fullor directly includinguv.
Related Candies
/charly-languages:pixi— direct-download pattern this candy now mirrors. Pixi remains a legitimate multi-binary Python env manager for boxes that genuinely need one (jupyter, whisper, openwebui); uv is too small to justify the overhead./charly-coder:typst— sibling direct-download binary pattern./charly-languages:python— the pixi-python meta-layer this candy does not depend on./charly-coder:rust— system Rust toolchain, if you want to build uv from source instead (cargo install uv). Usually not worth it — the Astral binary is pre-optimized.
Related Commands
/charly-image:layer— authoring reference (coversdownload:verb +strip_components:modifier)/charly-core:shell— runuvinside a container
When to Use This Skill
MUST be invoked when:
- Adding
uvto a box's candy list. - Authoring a sibling candy for a small Rust/Go CLI tool — this is the canonical pattern (direct download > pixi > anything else).
- Debugging why
uv --versionfails: it's a$PATHor binary-landing- at-wrong-path issue, not a Python env problem. - Understanding why "uv has no dependencies" is correct even though it manages Python packages.
Related
/charly-check:check— declarative testing (check:block,charly check box,charly check live)