Home-cooked Utility Scripts

I write a lot of utility scripts for work and personal projects. Ideally, anything repetitive I have to do more than a few times has a script to automate it. With the advent of LLMs, the time needed to generate something useful has gone down dramatically. There was a recent discussion on the excellent Software Defined Talk podcast about the kind of CLI-lifestyle this kind of development enables.

I’ve heard this kind of personal dev work compared to home-cooking , an analogy I like. Here’s my current recipe for quick utility scripts.

My Recipe

  1. Each script is a single executable file with no file extension. This way, they can be in different programming languages, and I can rewrite them if needed without changing the executable name.
  2. Write most scripts in Python over Bash. It’s easier for me to proofread than Bash, and LLMs have a better hit-rate in Python since it’s such a big part of their training data.
  3. Use uv to manage script dependencies inline. You can add dependencies in a comment at the top of the file, and uv will automatically create a standalone virtual environment for the script with the correct dependencies when it’s first run. This keeps the scripts as single-files even if they rely on external libraries.
  4. Store the prompt requirements in a docstring at the top of the script. That way, when I’m iterating on the script over time, I have a record of previous requirements the script still has to meet. This helps prevent the LLM breaking feature A when I ask it to add feature B. It also eases the transition between a web-based chat UI and a local AI coding agent if one of my scripts grows into a larger project later.

I have a re-usable style guide for this that gets passed to the LLM automatically with every “write me a script” style prompt. I’ve included it below:

## Script structure

specify: `#!/usr/bin/env -S uv run --script` as first line

store any script dependencies in a comment in this format:
```py
# /// script
# requires-python = ">=3.12"
# dependencies = [
#   "requests",
#   "python-dotenv"
# ]
# ///
```

Summarize the requirements of the script as articulated in this chat as a series of bullet point user stories in a docstring at the top of the script.

Here is an example of the first part of a script following this format:

```py
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = [
# ]
# ///
"""
Script Requirements:
- As a user, I want to calculate the current date in 'yymmdd' format.
- As a user, I want to construct a file path for a daily note, formatted as '~/code/notes/dn_DATE.md'.
- As a user, I want to execute a command to open this file with the 'code' executable located at '$HOME/.nix-profile/bin/code'.
- As a user, I want the script to run silently, printing no output on success.
"""

Here is an example of a script I’ve written since I started following this convention. You’ll find some others if you poke around my scripts repo as well.