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