From Script to CLI Tool with uv
Turn any Python script into a globally-available CLI tool using uv tool install.
Preface: My Slow Descent into uv Addiction
Since mid 2025 I have slowly begun to work uv into being my daily driver for managing my various python projects' package requirements.
In this regard, it has been an absolute wonder.
In one instance, I was able to use uv pip install as a drop-in replacement for pip and immediately reduce a mission-critical Docker image's build time by 87%. My CI pipeline sent me a thank-you card.
This year I've decided to wade a bit deeper.
Intro my script
After my 9 to 5, I love tracking and doing some light analysis of my family's spending patterns. That's right. I'd do this stuff FOR FREE (...not really π). After Intuit axed Mint, I hopped over to Copilot and I've continued to tag transactions to help me keep track of various things. Unfortunately, each month I would eventually find myself trying to match up my Copilot tracked transactions to transactions in credit card statements to get some aggregated data by tag. This year I finally reached my wit's end and decided to script out the once manual process. The actual repo is here in case anyone is curious.
Making things globally available
In this case, I wanted to make my script a cli tool that I could call from anywhere in my terminal.
There are definitely alternative approaches that bypass making something universally available, but I really didn't want to have to possibly add extra keystrokes to type in the python script's filepath relative to my $PWD.
The uv tool command comes in handy here.
What's uv tool do, you say?
In uv's world, "tools" are Python packages that provide CLIs.
When you run uv tool install, uv creates an isolated virtual environment in ~/.local/share/uv/tools/ and symlinks the executable to ~/.local/bin/βwhich should already be on your $PATH.
One Prerequisite
Before you can use uv tool install, your project needs a build backend.
You can always modify your pyproject.toml to add this section in, or you can ensure that your uv project is initialized with this already populated by utilizing the --build-backend option.
Usage: uv init [OPTIONS] [PATH]
Arguments:
[PATH] The path to use for the project/script
Options:
...
--build-backend <BUILD_BACKEND> Initialize a build-backend of choice for the project [env: UV_INIT_BUILD_BACKEND=] [possible values: uv,
hatch, flit, pdm, poetry, setuptools, maturin, scikit]
...
Let's do it! In the snippet below we'll install my script tool so that I can call it from anywhere. No python virtualenv activations. No bash aliases.
β― uv tool install .
Resolved 9 packages in 245ms
Audited 9 packages in 0.72ms
Installed 1 executable: cc-calc
β― cc-calc
usage: cc-calc [-h] [--fuzzy-threshold FUZZY_THRESHOLD] [-i] [-l] CHASE_CSV COPILOT_CSV
cc-calc: error: the following arguments are required: CHASE_CSV, COPILOT_CSV
β― ll ~/.local/bin/
Permissions Size User Date Modified Name
lrwxr-xr-x - agonzalez 19 Jan 19:38 ο cc-calc -> /Users/agonzalez/.local/share/uv/tools/cc-calc/bin/cc-calc*
β― cat /Users/agonzalez/.local/share/uv/tools/cc-calc/bin/cc-calc
ββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β File: /Users/agonzalez/.local/share/uv/tools/cc-calc/bin/cc-calc
ββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1 β #!/Users/agonzalez/.local/share/uv/tools/cc-calc/bin/python3
2 β # -*- coding: utf-8 -*-
3 β import sys
4 β from cc_calc.main import main
5 β if __name__ == "__main__":
6 β if sys.argv[0].endswith("-script.pyw"):
7 β sys.argv[0] = sys.argv[0][:-11]
8 β elif sys.argv[0].endswith(".exe"):
9 β sys.argv[0] = sys.argv[0][:-4]
10 β sys.exit(main())
ββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββConclusion
I love keeping up to date with modern tools and workflows.
uv is one of these newer tools that hasn't disappointed me as of yet.
When it comes to uv tool install, I am still not sure how much mileage I'll get out of it. But hey, if it shaves 87% off my monthly reconciliation frustration too, even better.