Choose the automation

Design input and output

Give an automation explicit data types, paths, exit behavior, and output so it can run from more than one trigger.

An automation that silently reads the current Finder selection is hard to test. You can’t run it from a script. You can’t write a fixture for it. It behaves differently depending on which window is frontmost.

Prefer explicit input: file paths, text, a project directory. Something you can pass in.

The same goes for output. An automation that “just does things” leaves the next step with nothing to work with. Print the paths you created on stdout. Keep messages on stderr. Exit non-zero on failure. That’s the whole interface.

Write the contract first

For a shell command, I write the contract before the implementation:

input: one existing image path
output: new JPG path on stdout
failure: non-zero exit with message on stderr
side effect: original remains unchanged

Then I implement exactly that, and nothing more:

#!/bin/zsh
if [[ ! -f "$1" ]]; then
  echo "input file not found: $1" >&2
  exit 1
fi
output="${1%.*}.jpg"
sips -s format jpeg "$1" --out "$output" >/dev/null
echo "$output"

sips is the image tool built into macOS, so this runs on any Mac. Notice the >&2 on the error message. That sends it to stderr. The only thing on stdout is the answer.

Verify both directions

Test the happy path and the failure path:

./to-jpg.sh ~/Desktop/sample.png
# /Users/flavio/Desktop/sample.jpg
echo $?
# 0

./to-jpg.sh missing.png
# input file not found: missing.png
echo $?
# 1

Because data goes to stdout and complaints go to stderr, another program can capture the result with output=$(./to-jpg.sh "$file"). It never parses error text as a path.

This is what makes the automation trigger-independent. A Shortcut, a scheduled job, and your own terminal all call the same contract and read the same answer.

The same idea in Shortcuts

Shortcuts can follow the same contract. Declare which input types the shortcut receives. Decide what happens when no input arrives. End with Stop and Output instead of relying on whatever the last action happened to return.

We build exactly that shortcut in the next module.

The common mistake

Mixing the streams. If your script echoes “processing sample.png” to stdout, the caller receives that string glued to the real path. The next step fails on a file that does not exist.

Everything that is not the answer belongs on stderr. I check this every time I write a script that another script will call.

Try this on the script from your own task card: write the four-line contract first, then make the script match it.

Lesson completed