Anthony Cardinale 030a40aa4b add btime fallback, app-library exclusion, --year, --include-untagged
Behavior changes (all opt-in or safety-first):
- prep refuses to operate inside .photoslibrary, .lrlibrary, .aplibrary,
  .fcpbundle, .band, .logicx, .app, etc. unless --allow-app-libraries
- --year YYYY restricts to files whose embedded ts (or btime) starts with YYYY
- --include-untagged accepts hand-named image files (no CleanShot/Screenshot
  prefix) and dates them via stat btime → mtime fallback. Gated on the folder
  containing ≥10 tagged matches to prevent sweeping ~/Pictures or similar
- prep pre-pass auto-normalizes the missing-space typo
  ('foo barCleanShot 2026-...' → 'foo bar CleanShot 2026-...') by os.rename
- plan now iterates the desc-tsv contents instead of the full src dir, with
  alt-extension fallback for Haiku's occasional .jpg-instead-of-.png echo
- build_new_name supports app=None (untagged) — emits
  '<keywords> - <Description> - YYYY-MM-DD.ext'

SKILL.md: gotchas #14-17 documenting each new guard, run-order updated
with the new flags, common-mistakes table extended.

Verified by smoke test with seeded files: --year filter, --include-untagged
threshold gate, app-library refusal, and typo normalization all behave.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 11:14:55 -04:00

screenshot-rename

A Claude Code skill that turns a folder of timestamp-named screenshots
into a folder of human-readable, searchable filenames —
using parallel Haiku vision agents.

Homepage  ·  SKILL.md  ·  pipeline.py  ·  MIT


Before / after — five real renames, including U+202F handling and user-keyword preservation

CleanShot 2026-04-15 at 09.14.07.png
   ↓
CleanShot - Shamel Studio Affiliate Referral Code Modal - 2026-04-15 at 09.14.07.png

Built for CleanShot-style screenshot folders, but works on any directory of .png / .gif / .mp4 / .pdf files named only by timestamp. Recognizes both CleanShot ... and Apple Screenshot ... filenames in the same pass, preserves any leading user-typed keyword prefix, and is safe to re-run on a folder that's already partially renamed.

Highlights

  • Parallel — describes ~200 files in 3 minutes using 10 concurrent Haiku subagents.
  • Safe — pre-builds the full rename plan in memory, validates uniqueness and target collisions, then renames atomically with a file-count audit. Designed after losing 4 files to a mv overwrite during prototyping.
  • Multi-prefix — same pipeline handles CleanShot ..., Apple Screenshot ..., and files with hand-typed leading keywords (e.g. jojo travel CleanShot ...).
  • Idempotent — re-running on a folder skips files already in the renamed App - Description - timestamp.ext form. No description-stacking.
  • Handles video / PDF — extracts the first frame so vision agents can describe them.
  • Resizes for the vision tool — Retina screenshots exceed Read's image cap; pipeline downsamples to 1568 px max.

A session, end to end

Claude Code session — user prompt, parallel Haiku fan-out across ten batches, receipt of the run

The skill activates when you ask Claude conversationally. Behind the scenes it preps the folder, fans out ten Haiku agents in a single round-trip, validates the resulting plan, then applies the renames in a single Python pass with a file-count audit at the end.

Installation

This is a Claude Code skill. Drop the repo into ~/.claude/skills/:

git clone https://gitea.tojo.team/cardinale/screenshot-rename.git \
  ~/.claude/skills/screenshot-rename

In your next Claude Code session, ask:

rename all the cleanshot files in ~/Documents/Screenshots/ based on their content

The skill will activate automatically from its description.

Usage from the command line

You can also drive the pipeline directly:

# 1. Prep — extract frames, resize, build batches
python3 pipeline.py prep --src "/path/to/folder" --batch-size 19

# 2. (In a Claude Code session, dispatch one Haiku subagent per
#     /tmp/screenshot-rename/full-batch-NN file using the prompt template
#     in SKILL.md.)

# 3. Plan — aggregate descriptions, validate, build rename map
python3 pipeline.py plan --src "/path/to/folder"

# 4. Execute — apply the plan, audit file count
python3 pipeline.py execute --src "/path/to/folder"

The dispatch step (#2) currently requires a Claude Code session.

What the parser accepts

Form Recognized Becomes
CleanShot 2026-MM-DD at HH.MM.SS.png yes CleanShot - <description> - 2026-MM-DD at HH.MM.SS.png
Screenshot 2026-MM-DD at H.MM.SS PM.png (with U+202F) yes — U+202F normalized to ASCII space Screenshot - <description> - 2026-MM-DD at H.MM.SS PM.png
<keywords> CleanShot 2026-MM-DD at HH.MM.SS.png yes — keywords title-cased and prepended to the AI description CleanShot - <Keywords + description> - 2026-MM-DD at HH.MM.SS.png
App - <description> - 2026-MM-DD at HH.MM.SS.png already renamed → skipped (unchanged)

The gotchas this skill encodes

This skill exists because every one of these caused real damage during development:

  1. The Read tool has an image-size cap. Resize first.
  2. Vision can't read .mp4 or multi-page .pdf directly. Extract a frame.
  3. Bash regex [[ =~ ]] does NOT populate BASH_REMATCH in zsh. Targets become empty. Loops collide on the same filename. Files vanish. Use Python for any filename mutation.
  4. mv silently overwrites. Use mv -n or os.rename with explicit pre-existence check.
  5. Pre-build the entire rename plan in memory and validate uniqueness before any mv.
  6. Audit len(os.listdir(DEST)) before and after. Equal count == proof no overwrites.
  7. iCloud-synced files in Time Machine local snapshots are file-provider stubs, not bytes. External backups (Backblaze, Time Machine to physical disk) are the real recovery source.
  8. Bash run_in_background may exit early on while read loops. Run renames foreground via Python.
  9. Haiku occasionally returns the resized .jpg filename instead of the original .png. Validator must try alt extensions.
  10. Always preserve the original .mp4 / .pdf extension — describe via the extracted frame, rename the source.
  11. macOS Screenshot filenames contain U+202F (NARROW NO-BREAK SPACE) before AM/PM. Haiku echoes it as ASCII space, so a verbatim filename lookup misses every Screenshot file. Normalize on both sides of the lookup; emit ASCII space in the new name.
  12. Re-running is only safe if the parser skips already-renamed files. Detect ^App - .+ - timestamp.ext$ and exclude.
  13. Leading user-typed keyword prefix is signal, not noise. Title-case the keywords and prepend them to the AI description before assembling the new name.

The full discussion is in SKILL.md.

Real-world impact

Run Files What happened
1 196 CleanShot Lost 4 to the bash-regex-in-zsh gotcha (#3).
2 196 CleanShot Rebuilt with Python and mv -n — 189 renamed cleanly, zero loss.
3 20 mixed (CleanShot + Apple Screenshot + one user-prefixed) First plan attempt dropped every Screenshot file with a misleading NO_DESC error. Diagnosed the U+202F gotcha (#11) via repr() of the live filename. After adding U+202F normalization, multi-prefix support, and keyword preservation — all 20 renamed in one pass.

Roadmap

  • Direct Anthropic API mode (no Claude Code session required) — needs ANTHROPIC_API_KEY
  • Custom prompt templates per-folder
  • Optional preservation of dots in technical strings (v2.1 currently becomes V21)
  • Dry-run flag on execute

License

MIT — see LICENSE.

S
Description
Rename a folder of screenshots with AI-generated descriptive names. Claude Code skill.
https://pages.tojo.team/cardinale/screenshot-rename/
Readme MIT 1.2 MiB
Languages
HTML 51.7%
Python 48.3%