Standard path in wheels for completion scripts and manpages

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 ($SHELL can 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:

  1. authors ship assets once in a predictable place,
  2. installers discover assets automatically,
  3. installers link/copy assets into managed locations,
  4. 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>]
    • section is typically 19.
  • 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 1 for user executables.
  • Compressed pages (.gz, .xz, etc.) are allowed.

Examples:

  • share/man/man1/mytool.1
  • share/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.1
  • mypkg-1.2.0.data/data/share/shell-completions/zsh/_mypkg
  • mypkg-1.2.0.data/data/share/shell-completions/fish/mypkg.fish
  • mypkg-1.2.0.data/data/share/shell-completions/bash/mypkg

User workflow with supporting installer:

  1. configure shell/man lookup once for installer-managed dirs,
  2. run tool install mypkg,
  3. 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 man and completions roots,
  • 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

Copyright

This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.

I got half way through the “Motivation” section before getting bogged down in the typical LLM verbosity. I’ll go back to the rest of the proposal later, but I’d like to start by asking some basic questions and making some high level points.

  1. Wheels were fundamentally designed as a library distribution format. With console entry points they very quickly became popular for distributing simple CLI apps, but there’s a lot of functionality that an application installer has that’s missing. This proposal covers one particular part, but ignores the rest. Wouldn’t it be better to look for a more general solution that handles application installs as a whole, rather than just adding extensions piecemeal as someone thinks of them? Personally, for example, I have little interest in completion scripts or manpages, but I would be interested in standardised config file handling and logging.
  2. How does this proposal work on Windows? Manpages aren’t useful on Windows, and making them “the standard” tends to cause projects primarily developed on Unix to supply manpages and forget that by doing so they are making their docs inaccessible to Windows users.
  3. Do completion scripts have standard system locations? I don’t think that there’s a standard location for Powershell completion scripts, so how would that work? Would each tool have its own standard location? Expecting users to remember which installer they used and code uv completion-dir, pipx completion-dir or hatch completion-dir as needed seems like a pretty bad UX.

Also, the handling of files in (for example) {dist}-{version}.data/data/share/man/ is well-defined under the current spec. So as far as I can see, everything in the specification you give is present already (except maybe that not all tools implement the “optional” features you list - but that’s OK as they are optional…) And in fact, I’ve seen wheels with man sections like you describe here. If you install Jupyter, there’s an <env>/share/man directory, for example. Typically they are useless on Windows (which is the platform I use) and so tend to get ignored. What about this new proposal will change that?

1 Like

Thank you @pf_moore for the quick response!

Application installs as a whole becomes so vast that I’m unsure what to even think about them, and I see even less what a proposal could try to address or standardize.

Ha, guilty of not doing my research well enough. Could you share a link to that spec? I cannot find anything related to manpages in Binary distribution format - Python Packaging User Guide.

I will argue that if the spec defines what to do with specific data directories like share/man, why wouldn’t we consider defining what to do with other data directories like share/completions? IMO completions are a fundamental aspect of CLIs, so would deserve some consideration for standardization in how we distribute those :slight_smile:

Yeah, valid point. Manual pages are quite a Unix thing. I’m not sure if and what Powershell exposes to users to check out the documentation of commands. Maybe we could just consider Windows out of scope for manpages here (though as you mentioned, they’re already in the spec, so we’re already in a weird intermediate state?).

This is addressed in the draft (which I didn’t find that verbose, except for too many adjectives in sentences maybe?). We explicitly exclude standard system locations from the proposal. The location where these assets (completion scripts, manpages, whatever) are copied/symlinked is a directory that is internal to the installer tool (.local/share/uv, .local/share/pipx, etc.).

The reason, as you mention, is that there aren’t always standard system locations. And when there are, writing in these locations sometimes need elevated privileges. Just like installing a tool with pipx or uv doesn’t install it system-wide, the completion scripts or manpages shouldn’t be installed system-wide.

Now, if XDG defines some paths for these specific types of assets, we could consider using these paths instead. Just like ~/.local/bin is often used to install user executables, we could install manpages in ~/.local/share/man and completions in ~/.local/share/completions/<shell>/.

I agree it might not be the best UX, but it’s not uncommon either. When you use go or cargo to install tools, you have to make sure you add the right directories to your PATH. So configuring man or your shell to look for man pages or completion scripts in a uv-managed directory when you use uv doesn’t seem too surprising to me :slightly_smiling_face:

The part you linked has a relevant snippet, step b of spread:

b. Move each subtree of distribution-1.0.data/ onto its destination path. Each subdirectory of distribution-1.0.data/ is a key into a dict of destination directories, such as distribution-1.0.data/(purelib|platlib|headers|scripts|data). These subdirectories are installation paths defined by sysconfig.

So a complainant installer would move {dist}-{version}.data/data/share/man/ to the path defined in sysconfig. I think this is what Paul is referring to, and not some special cased manpage handling. So from a wheel format perspective, we can already include these files in the format described. The “what to do with it” parts of the proposal appear to be optional tool features.

1 Like

A small point: you should not format ideas with PEP front matter until they rise to the level of a PEP. It can be misleading to readers, and clutters an already long topic.

It’s good to use the PEP structure as a guideline for the kinds of details you need to provide, but you don’t need “PEP-XXXX” and so on at the top.

1 Like

I see, thanks, I’ll remove the PEP header :slight_smile:

Thanks!

So, if I understand correctly, the wheel format just allows for any folder under data, and all files under it, to be written to the “userbase” folder (usually the root of a virtual environment). In this sense, data/share/man is not more supported than data/share/completions.

So to answer the earlier question of “what does this proposal bring”: it brings standardization of these paths so that installers know what additional steps to run on them, i.e. copying/symlinking files in a central location.

Obviously if these additional steps aren’t taken, the whole proposal doesn’t bring anything, as anyone can already scan the folder where uv or pipx install tools, to find the corresponding venvs and the installed assets (manpages/completions), then load them. That’s what the example in the Motivation section shows. Copying/symlinking in a central location (and upgrading/removing assets when upgrading/uninstalling) is the central feature here, and is what would make it much easier for users to load completions or access manpages.

It makes me think now that, even without centralization, installers could provide a command to list these folders (more reliably than with find commands and similar).

$ uv tool data share/man
~/.local/share/uv/tools/copier/share/man
~/.local/share/uv/tools/hatch/share/man
~/.local/share/uv/tools/toad/share/man

(one could replace new lines with : for re-export of the MANPATH env var)

$ uv tool data share/completions
~/.local/share/uv/tools/copier/share/completions
~/.local/share/uv/tools/hatch/share/completions
~/.local/share/uv/tools/toad/share/completions

Users would then run code like this in their profile:

while read -r directory; do
    for completion_file in "${directory}"/*; do
        source "${completion_file}"
    done
done <<<"$(uv tool data share/completions/bash)"

I will suggest that to pipx and uv in the respective feature requests.

Then the exact share/man and share/completions/<shell> subpaths can just emerge as conventions rather than standards. A bit less convenient, but working.

I’d look at it the other way round. The handling of these paths is already defined, as a consequence of how everything under /data is handled. Any change to this would be breaking behaviour.

Requiring additional steps after the “spread” step of unpacking a wheel is possible, but you’re getting very close to mandating a particular tool UI in a standard. You need to remember that a tool like pip could install multiple copies of a tool, into numerous different locations (for example, into several project venvs). There’s no way to make a single tool-managed central location work correctly with multiple venvs like this. That’s not a problem for a tool like pipx, which manages both the venv and the tool, but a standard has to define behaviour for all tools, so it can’t assume a certain implementation.

IMO, this sort of feature is better served as a tool-specific “quality of life” feature. If uv tool supports completions and/or documentation in a more user-friendly way than (say) pipx does, let that be a selling point for uv tool, and people can then choose the UI they prefer.

If various tools converge on a common approach, and the tool maintainers feel that there would be benefit in standardising some aspects of what they do, then that’s a much more compelling case for a PEP, rather than trying to design something in the abstract and requiring tools to follow that design.

3 Likes

I was exactly focusing on pipx and uv tool, which are used to install Python CLIs in isolated venvs :slightly_smiling_face: But I guess that’s true that we should at least mention any other installer such as pip, installer and how the standard would apply.

Tools want standards, standards want tool implementations, heh :grin:

I won’t push further here, and see if Astral folks want to work towards manpages/completions support, or just data file listing generically :slightly_smiling_face:

Thanks everyone for the thoughtful discussion!