I propose that we standardize file paths in wheels to be understood as completion scripts (for interactive use of Python CLIs in the terminal) and as manual pages (to be displayed with man your-python-cli and other equivalent tooling).
pipx already supports manpages, uv is considering it, and I’ve opened issues in both pipx and uv to support shell completion scripts too.
I have drafted the following PEP with LLM assistance, let me know what you think of it (is it worth pursuing, can you think of big blockers I didn’t see, etc.).
Abstract
This PEP defines standard, reserved paths in wheel distributions for:
- shell completion scripts, and
- manual pages.
The goal is to let installers (for example, environment and tool installers) discover these assets without project-specific commands, then expose them through installer-managed central locations.
Motivation
Command-line Python applications commonly provide shell completions and manpages, but installation is inconsistent:
- each project invents its own CLI flags (
--install-completions,completions, etc.), - users must read per-project docs,
- users must often rerun installation commands after upgrades,
- CLIs gain maintenance-only subcommands.
Implementation experience in real projects confirms the cost of the current model. In duty’s PR for Zsh completions (https://github.com/pawamoy/duty/pull/34), maintainers and contributors repeatedly hit shell- and platform-specific complexity when adding and maintaining --completion / --install-completion flows:
- shell auto-detection is fragile (
$SHELLcan describe the login shell, not the currently running shell), - installation directories differ across shells and operating systems, with permission differences (for example, system Zsh directories on Linux often requiring
sudo), - there is no universally accepted user-level default for some shells, leading to extra documentation and shell-configuration guidance,
- end-to-end validation of completion install behavior tends to require expensive integration testing across OS/shell environments.
This PEP’s packaging-level approach shifts installation and exposure complexity away from individual Python CLIs and into installer tooling that is better positioned to handle platform-specific behavior once.
A standard asset layout in wheels enables a better model:
- authors ship assets once in a predictable place,
- installers discover assets automatically,
- installers link/copy assets into managed locations,
- users configure shell/man search paths once.
For example, with uv today, users can work around manpage discovery with shell code such as:
MANPATH="$(find "${XDG_DATA_HOME:-${HOME}/.local/share}/uv/tools" -maxdepth 3 -type d -name man -printf '%p:')"
This is brittle (it matches any directory named man) and requires custom shell logic.
With standardized wheel paths and installer-managed exposure, installers can offer a stable directory command instead. For example, for manual pages:
MANPATH="$(uv man-dir):$(manpath)"
For completion scripts, depending on the shell in use:
# bash
for f in "$(uv completion-dir --shell=bash)"/*; do
source "$f"
done
# zsh
fpath=($(uv completion-dir --shell=zsh) $fpath)
# autoload, compinit, etc.
# fish
for f in (uv completion-dir --shell=fish)/*
source $f
end
# powershell
Get-ChildItem (uv completion-dir --shell=powershell) -File | ForEach-Object { . $_.FullName }
Rationale
Why path conventions?
Path conventions are simple, backward-compatible, and work with existing wheel data layout. They avoid introducing new metadata fields for a problem that is primarily file-placement and installer policy.
Why installer-managed exposure?
Global and user-level destination paths are platform- and installer-specific. This PEP standardizes artifact-internal locations, not system-wide install paths.
Experience from real-world completion work (for example, Enable Zsh completions by j-g00da · Pull Request #34 · pawamoy/duty · GitHub) shows this installer-facing boundary reduces repeated shell- and platform-specific install logic in each CLI project.
Downstream distribution packagers
This standardization also benefits downstream packagers for operating-system distributions (for example, Gentoo, Arch Linux, Debian, and similar ecosystems). When completion scripts and manpages are located in predictable wheel paths, distro packaging tooling can extract those files deterministically and repackage them into OS-specific locations and package splits. This reduces per-project patching and custom heuristics in downstream packaging recipes.
Prior art
pipx already supports exposing manual pages from installed applications in a way consistent with this PEP’s manpage path model; see: How pipx works - pipx.
In practice, users can load pipx-exposed manpages by deriving the configured manpage directory and prepending it to MANPATH, for example:
export MANPATH="$(pipx environment --value PIPX_MAN_DIR):$(manpath)"
Specification
1. Reserved distribution-internal directories
1.1 In wheels
A wheel MAY include completion scripts and manpages under the wheel data scheme in these directories:
{dist}-{version}.data/data/share/man/{dist}-{version}.data/data/share/shell-completions/
Subdirectory layouts:
- Manpages:
{dist}-{version}.data/data/share/man/man<section>/<name>.<section>[.<compress-ext>]sectionis typically1–9.
- Completions:
{dist}-{version}.data/data/share/shell-completions/<shell>/<filename>
<shell> is an opaque directory name provided by the package. This PEP does not define or restrict allowed shell names.
1.2 In sdists and source trees
Source layout is out of scope for this PEP.
Project authors and maintainers MAY place completion scripts and manpages anywhere in their source tree and configure their build backend however they choose.
This PEP only standardizes the resulting wheel-internal paths.
2. File naming conventions
2.1 Manpages
- Filename SHOULD match the command name.
- Section SHOULD be
1for user executables. - Compressed pages (
.gz,.xz, etc.) are allowed.
Examples:
share/man/man1/mytool.1share/man/man1/mytool-subcommand.1.gz
2.2 Completions
Completion filename conventions are shell-specific. Common examples:
- bash:
mytool - zsh:
_mytool - fish:
mytool.fish - powershell:
mytool.ps1
No universal filename normalization is required by this PEP; installers SHOULD treat filenames as opaque.
3. Installer behavior
For installers that install Python CLI applications, support is OPTIONAL but RECOMMENDED.
If supported, installer behavior SHOULD be:
- discover assets in the standardized wheel paths,
- expose them in installer-managed directories (via symlink or copy),
- group completion scripts by the wheel-provided
<shell>directory name (one installer directory per provided name), - not enforce, rewrite, or require recognition of specific shell names,
- update/remove exposed assets on upgrade/uninstall,
- avoid exposing assets for commands not installed by that installer environment.
Install destination paths are explicitly out of scope; installers define them.
4. Conflict handling
When multiple installed tools provide the same target filename in the same shell/man namespace:
- installer MUST define deterministic precedence (e.g., last-installed wins), and
- installer SHOULD warn the user.
5. Security considerations
Installers SHOULD:
- not execute discovered completion/manpage files at install time,
- treat files as untrusted package content,
- validate that archive paths are normalized and do not escape extraction roots,
- ensure symlink targets remain within installer-managed locations where practical.
This mechanism adds attack surface: auto-loaded completion scripts can contain malicious shell code that executes with the user’s privileges when loaded by the shell.
Installers SHOULD provide user-facing safety controls to reduce exposure risk, such as:
- an option to disable completion-script exposure entirely,
- explicit opt-in (or a clear policy toggle) for automatic completion exposure,
- per-package allow/deny controls,
- clear visibility into which completion scripts are currently exposed.
6. Backward compatibility
- Existing packages remain valid.
- Existing installers can ignore these paths with no breakage.
- Existing sdist/source-tree layouts remain fully unconstrained.
- Packages can continue offering runtime-generated completions; this PEP adds a static-distribution path, not a replacement requirement.
Example
Wheel contents excerpt:
mypkg-1.2.0.data/data/share/man/man1/mypkg.1mypkg-1.2.0.data/data/share/shell-completions/zsh/_mypkgmypkg-1.2.0.data/data/share/shell-completions/fish/mypkg.fishmypkg-1.2.0.data/data/share/shell-completions/bash/mypkg
User workflow with supporting installer:
- configure shell/man lookup once for installer-managed dirs,
- run
tool install mypkg, - completions/manpages become available automatically.
Reference implementation sketch
A minimal implementation in an installer would:
- inspect wheel entries for:
*.data/data/share/man/***.data/data/share/shell-completions/**
- map discovered files to installer-managed
manandcompletionsroots, - create/remove links on install/upgrade/uninstall,
- warn on collisions.
Rejected alternatives
- Per-project CLI install commands only: poor UX, non-standard, repeated user effort.
- New core metadata fields listing assets: adds metadata complexity without removing need for file placement.
- Mandating global OS-specific install paths: too platform-specific and conflicts with isolated tool environments.
Open issues
- Whether to define validation guidance for malformed completion directory layouts.
- Whether PyPA should mirror this as a packaging-spec document alongside PEP text.
- Installer support tracking:
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.