A worked example based on the sls (see-latest-screenshot) command. Written for an LLM agent that needs to do something similar.
What We Built
A command sls (alias see-latest-screenshot) that: - In a terminal: runs hermes -z with a prompt containing the latest screenshot path - In a Hermes TUI session: the agent recognizes “sls” as a trigger phrase and runs the same logic directly (find latest file, copy to tmp, call vision_analyze)
Step 1: Investigate Before Building
Always check what already exists before writing anything.
which sls # is it already in PATH?
ls ~/av/bin/ | grep sls # check the workspace bin dir
ls ~/.local/bin/ | grep sls # check local bin
hermes skills list | grep <topic> # is there a skill stub?
If a skill exists, load it:
skill_view(name="screenshot_tool")
A stub skill (no real steps, broken frontmatter) is worse than nothing – it creates false confidence. Check whether the skill actually works before trusting it.
Step 2: Understand the Environment
Before writing any script, collect these facts:
echo "SCREENSHOTDIR=${SCREENSHOTDIR:-not set}"
echo $PATH | tr : '\n' | grep av/bin # is ~/av/bin in PATH?
which hermes # confirm hermes location
In this workspace: - SCREENSHOTDIR is set in the shell environment: /Users/crasch/av/ast/img/screenshots - ~/av/bin/ is in PATH (workspace convention) - hermes is at ~/.local/bin/hermes - hermes -z “prompt text” runs a one-shot (non-interactive) agent call
Step 3: Write the Shell Script
Put scripts in ~/av/bin/ (not ~/.local/bin/ directly). Symlink aliases there too.
Key decisions for this script:
- Respect SCREENSHOTDIR, fall back to ~/Pictures/Screenshots
- Use find + xargs + ls -t to get the newest file (handles filenames with spaces safely when combined with -print0 and xargs -0)
- Copy to a space-free /tmp path before passing to vision_analyze (see Pitfall #1)
- Use exec hermes -z “$PROMPT” to hand off without a subprocess layer
Final script at ~/av/bin/sls:
#!/usr/bin/env bash
SCREENSHOTDIR="${SCREENSHOTDIR:-$HOME/Pictures/Screenshots}"
LATEST=$(find "$SCREENSHOTDIR" -maxdepth 1 \
\( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" \) \
-print0 2>/dev/null \
| xargs -0 ls -t 2>/dev/null | head -1)
if [[ -z "$LATEST" ]]; then
echo "sls: no screenshots found in $SCREENSHOTDIR" >&2
exit 1
fi
TMPFILE="/tmp/sls_latest.png"
cp "$LATEST" "$TMPFILE"
QUESTION="${1:-Describe everything you see in this screenshot in detail.}"
echo "sls: loading $LATEST" >&2
PROMPT="Use vision_analyze to look at the image at this path: ${TMPFILE}
Question: ${QUESTION}"
exec hermes -z "$PROMPT"
Make it executable and create the alias:
chmod +x ~/av/bin/sls
ln -sf ~/av/bin/sls ~/av/bin/see-latest-screenshot
Verify syntax:
bash -n ~/av/bin/sls && echo "syntax OK"
Step 4: Test the Core Tool (vision_analyze) in Isolation
Before wiring everything together, prove the underlying tool works. Find a screenshot without spaces in the name and test directly:
vision_analyze(
image_url="/Users/crasch/av/ast/img/screenshots/fb-post-verified.png",
question="Describe what you see."
)
If that works, the tool is fine. If the only screenshots have spaces in their names, copy to /tmp first:
cp "/path/with spaces/file.png" /tmp/test.png
vision_analyze(image_url="/tmp/test.png", question="Describe what you see.")
Do NOT assume vision_analyze handles all path formats. Test first, then encode the workaround into the script and skill.
Step 5: Write a Real Skill (not a stub)
A skill must have: 1. Valid YAML frontmatter (no bare - as a value – it parses as a list) 2. Trigger conditions (what phrases should load this skill) 3. Numbered steps the agent can follow mechanically 4. A pitfalls section with known failure modes
Bad frontmatter that breaks skill_manage: homepage: - # WRONG – bare - is a YAML sequence marker
Good frontmatter: — name: screenshot_tool description: “One sentence summary.” tags: [tools, vision, screenshots] —
The skill body for screenshot_tool:
## Trigger conditions
- User says "sls", "see latest screenshot", "show latest screenshot"
## Steps
1. Run: find "${SCREENSHOTDIR:-$HOME/Pictures/Screenshots}" -maxdepth 1
\( -name "*.png" -o -name "*.jpg" \) -print0 | xargs -0 ls -t | head -1
Capture path as LATEST.
2. Copy: cp "$LATEST" /tmp/sls_latest.png
3. Call: vision_analyze(image_url="/tmp/sls_latest.png", question=<user question>)
4. Report what the image shows.
## Pitfalls
- vision_analyze BREAKS on paths with spaces. Always copy to /tmp first.
- vision_analyze rejects file:// URIs. Pass raw filesystem paths only.
- If SCREENSHOTDIR is wrong, check: echo $SCREENSHOTDIR
Write the skill with skill_manage(action=‘edit’, …) or action=‘create’ if new.
Pitfall Reference
Pitfall 1: vision_analyze breaks on paths with spaces
macOS generates screenshot names like: Screenshot 2026-06-07 at 1.29.59 PM.png
Passing this path directly to vision_analyze fails with: “Invalid image source. Provide an HTTP/HTTPS URL or a valid local file path.”
The fix is always to copy to a space-free path first: cp “$LATEST” /tmp/sls_latest.png vision_analyze(image_url=“/tmp/sls_latest.png”, …)
Also does NOT work: file:///path/to/file.png (file:// URI – rejected) /path/to/Screenshot%202026.png (URL-encoding – rejected)
Pitfall 2: Broken YAML frontmatter in skills
homepage: - # breaks skill_manage with "sequence entries not allowed"
Use an empty string or omit the field entirely if you have no value.
Pitfall 3: Stub skills create false confidence
If a skill exists but is hollow, the next agent will load it, see instructions, and assume they work. Always verify a skill actually does what it says. If it is a stub, rewrite it completely with skill_manage(action=‘edit’).
Pitfall 4: sls is a shell command, not a TUI command
sls as a shell script calls hermes -z – this only makes sense at a terminal prompt. Inside a Hermes TUI session, the user types “sls” as a message and the agent picks it up via the skill’s trigger conditions. The agent then runs the find + cp + vision_analyze flow directly, without spawning a subprocess.
Verification Checklist
After completing the task, confirm all of these:
Want to stay in touch?
- Signal (announcements): https://signal.group/#CjQKIGLn7xDB0uOXMMlbKlsKEG0CmkmL9gk3U0SeIX0KlKRZEhDoqIluCXo84TrBz-2tMJD7
- Signal (discussion): https://signal.group/#CjQKIDA0v6tUciWe-3jRArkbYttju8xfuoczTOfMrGuvhmEZEhCrOnPk-IWFmFmipdI1EHxv
- Signal: archerships.43 (https://signal.me/#eu/9JUc8x9c-QA0_-QR9qQd0HUmjsnAG1BeOJM2nDo5DopjIPq5bThAJYr99lsh0cPP)
- Mailing list: https://archerships.substack.com/subscribe
- Email: [email protected]
- Website: https://archerships.com
- Substack: https://substack.com/@archerships
- Twitter: https://x.com/archerships
- Facebook: https://www.facebook.com/archerships
- Yahihonne: https://yakihonne.com/profile/nprofile1qqsgr0xn6vvr8su9ptzj4n50j8vzmczzayed0wcl5rdnvh0tc6xhqncy6jrjw
- Nostr-npub:
npub1sx7d85ccx0pc2zk99t8glywc9hsy96fj67a3lgxmxew7h35dwp8shak49e - Odysee: https://odysee.com/@archerships:6
- TikTok: https://www.tiktok.com/@archertships
Support my work
- Donations (crypto): https://trocador.app/anonpay/?ticker_to=xmr&network_to=Mainnet&address=85e4n5bgLTWiAWZbkjbbF5MLrwyiU8kjxHWHL9t6vDE5MyNUCPzBuZUNDcvbCisC5iW5PPBP9ETRQUWQQjMuvAhHRFaYCeM&donation=True&simple_mode=True&name=Archerships&[email protected]&ticker_from=xmr&network_from=Mainnet&bgcolor=000000ff
- Donations (fiat): https://ko-fi.com/archerships
- Consulting: privacy / crypto / censorship consulting – email or Signal