Get Started

Ship your own command-line app that anyone installs with one line

Build tidyup, a small Python tool that sorts a messy Downloads folder, shows you the plan first with --dry-run, and can undo itself. Then install it like a real program with one uv command and share it from GitHub in one line. Publishing to PyPI is an optional last step.

105 minIntermediateTools: Free. GitHub, TestPyPI and PyPI accounts are free too, and only the optional last two steps need them.Step 1 free
A working tool, tidyup, with proper --help pages, a --dry-run mode, a confirm prompt and a one-command undo
A real Python packagepyproject.toml, a src layout, an entry point and 14 passing tests
The tool installed on your PC so tidyup works in any terminal, in any folder
  • The one-line command that installs it on anyone else's computer straight from GitHub
  • The safe route to PyPI: TestPyPI first, scoped tokens, then trusted publishing from GitHub with no password stored anywhere
Before you start
  • Windows, Mac or Linux, with about 60MB free. uv downloads Python 3.12 if you don't have it, plus two small libraries (Typer and Rich).
  • uv, ideally 0.12 or newer. Step 1 installs or updates it. If you've never used it, Turn a Python script into a web app is a gentler first go.
  • Basic terminal skills: cd, running a command, opening a file in an editor. If that's new, do the Terminal survival kit first.
  • A code editor. VS Code ↗ is free. Notepad or TextEdit work too.
  • For step 6 only: Git and a free GitHub account. Install anything from GitHub shows you how to install Git, and Use Git without memorising commands sets up your Git name and email.
  • For step 7 only (optional): free accounts on TestPyPI and PyPI. Both insist on two-factor authentication, so have an authenticator app on your phone.
01/07Make the package skeleton with uv0 done
STEP 01 of 07~10 min

Make the package skeleton with uv

A loose .py file only works on your PC. One uv command gives you the shape of a proper installable package, so you start right instead of fixing it later.

  1. To make something other people can install with one command, Python needs a package: your code in a folder, plus a pyproject.toml file that says its name, its version, what it depends on, and which command it creates. uv ↗ makes that skeleton for you, then builds, tests and installs it.

  2. Check you have uv and which version:

    Terminal
    uv --version
  3. You want 0.12 or newer. To update: uv self update if you used uv's own installer, winget upgrade --id astral-sh.uv -e if you used winget, brew upgrade uv on a Mac with Homebrew. No uv at all? Install it, then close the terminal and open a new one:

    Windows
    winget install --id=astral-sh.uv -e
    Mac / Linux
    curl -LsSf https://astral.sh/uv/install.sh | sh
  4. Now make the project. The --package flag is the important part: it gives you a src folder, a build system and a command-line entry point, all the things a plain script project doesn't have.

    Windows
    cd $HOME\Documents
    uv init --package tidyup
    cd tidyup
    Mac / Linux
    cd ~/Documents
    uv init --package tidyup
    cd tidyup
    Output
    Initialized project `tidyup` at `...\tidyup`
  5. You get this layout. uv also made the folder a Git repository, with a .gitignore that keeps the .venv folder and build files out of it:

    Project layout
    tidyup/
    ├── .gitignore
    ├── .python-version
    ├── README.md
    ├── pyproject.toml
    └── src/
        └── tidyup/
            └── __init__.py
  6. Open pyproject.toml in your editor. This is what uv wrote:

    pyproject.toml
    [project]
    name = "tidyup"
    version = "0.1.0"
    description = "Add your description here"
    readme = "README.md"
    requires-python = ">=3.12"
    dependencies = []
    
    [project.scripts]
    tidyup = "tidyup:main"
    
    [build-system]
    requires = ["uv_build>=0.12.24,<0.13.0"]
    build-backend = "uv_build"
    • [project.scripts] is the entry point. The left side is the command people will type: tidyup. The right side says where Python finds the code to run: the main function in the tidyup package. When the package is installed, a small launcher called tidyup (tidyup.exe on Windows) is made that runs it.
    • [build-system] names the tool that turns your folder into an installable file. uv uses its own, uv_build. The upper limit (<0.13.0) stops a future uv release from quietly changing how your package builds.
    • requires-python comes from the Python uv found on your PC. Yours may say a different version. Anything from 3.10 up works with this mission.
  7. Change the description to something real, like "Tidy a messy folder (like Downloads) into subfolders by file type.". It shows on PyPI if you publish. Then run the command uv already wired up:

    Terminal
    uv run tidyup
    Output
    Using CPython 3.12.13
    Creating virtual environment at: .venv
       Building tidyup @ file:///.../tidyup
          Built tidyup @ file:///.../tidyup
    Installed 1 package in 31ms
    Hello from tidyup!
  8. uv run made a private environment (.venv) inside the project, installed your package into it, and ran the tidyup command from there. Nothing has been installed for the rest of your PC yet. That comes in step 5.

    TipThe finished project is in the starter kit: tidyup-starter.zip (13KB). Use it to check your work or to skip the typing. The steps below build the same files one at a time, so you understand each piece.
✓
Checkpointuv run tidyup prints Hello from tidyup!
If it breaksuv made a main.py and no src folder + 2 more
uv made a main.py and no src folder
You left out --package. Without it, uv makes a plain script project with no entry point. Delete the folder and run uv init --package tidyup again.
It says it failed to spawn tidyup (program not found)
You're not inside the project folder, or the [project.scripts] section is missing or misspelt. Run cd tidyup and check pyproject.toml matches the one above.
'uv' is not recognised
Close every terminal window and open a new one, so it picks up the newly installed program. If it still fails, see the PATH fix in the Terminal survival kit.
Still stuck? Ask the community
Next mission · Build things with codeBuild your first Chrome extension
→