| name | media-import |
| description | Unified inbox ingestion skill for carteakey.dev. Scans inbox/ subdirectories, diffs against already-imported files, shows a preview of what will be imported and handles 4 distinct content targets: photography (photos.yaml), vibes (src/static/img/vibes/), ai-memes folio (folio/ai-memes/index.html), and loose root inbox files that need classification first.
|
Media Import Skill
You are helping import media from the inbox/ staging area into the site.
There are 4 distinct content targets, each with its own ingestion pattern.
Always diff first, confirm with the user, then execute.
Step 0 - Consult Manifest
Before any import, check inbox/manifest.yaml.
- Missing Entries: If a file is not in the manifest, run the
manifest-update skill to classify it.
- Ignores: Files in
inbox/_ignore/ are ignored by all skills and not tracked in the manifest.
- Descriptions: Use the
description field from the manifest for all imported media (captions, alt text, etc.).
- Targets: Respect the
suggested_target.
Step 1 - Scan and Diff
Run the following to understand what's in the inbox vs already imported:
find inbox/ -type f ! -name ".DS_Store" | sort
grep "^ path:" src/_data/photos.yaml | sed 's|.*path: ||' | sort
ls src/static/img/vibes/
grep 'src="/img/folio/ai-memes/' src/folio/ai-memes/index.html | grep -o 'src="[^"]*"' | sed 's/src="//;s/"//' | sort
Build a table of unimported files per target:
| File | Inbox path | Target | Status |
|---|
| ... | inbox/photography/IMG_xxx.jpg | photography | ⏳ pending |
| ... | inbox/vibes/meme.webp | vibes | ⏳ pending |
| ... | inbox/ai-memes/funny.webp | ai-memes folio | ⏳ pending |
| ... | inbox/something.jpg | ❓ unclassified | needs routing |
Manifest Pre-check:
Always cross-reference the find output with inbox/manifest.yaml. If a file is not in the manifest, you must describe it and add it to the manifest first.
Hash-based dedup (required for photography):
Do NOT rely on filename matching for photos - the same image may be deployed under a different name.
Run MD5 hashes against all deployed photos and cross-check before importing:
md5 inbox/photography/*.{jpg,jpeg,png,JPG} 2>/dev/null
md5 src/static/img/photography/real/*.{jpg,jpeg,png,JPG} 2>/dev/null | sort -k4
If a hash match is found, update the existing YAML entry with the correct title, path rename, and real EXIF data - do NOT add a new entry. Rename the deployed file to the human title slug.
For vibes and ai-memes, filename-based dedup is sufficient (filenames are stable Reddit/Twitter hashes).
Show the user this table and ask: "Which of these should I import, and to which target?" before proceeding.
Step 2 - Content Targets
Target A: Photography (inbox/photography/)
Destination: src/static/img/photography/{real|virtual}/ + src/_data/photos.yaml
Rules:
- Use the existing script:
node utils/add-photo.mjs (interactive) for one-off imports.
- For bulk AI-agent imports, replicate its logic manually:
- Read EXIF data (device, aperture, ISO, shutter, date, GPS).
- Reverse-geocode GPS if available (Nominatim API).
- Generate a short, human title (2–5 words, lowercase vibes, no AI slop). Examples:
"Still waters", "Golden hour on the lake", "Dam son". Do NOT generate verbose sentences or use words like "serene", "tranquil", "vibrant", "evocative".
- Do NOT generate descriptions - leave
description field absent unless truly necessary.
- Classify as
real (photos) or virtual (game screenshots).
- Slugify the title for the filename. Copy file to appropriate subdirectory.
- Prepend the YAML entry to
src/_data/photos.yaml.
YAML entry format (real photo):
- title: Short human title
category: real
path: /img/photography/real/slugified-title.jpg
device: iPhone 16
make: Apple
lens: ...
focalLength: ...
aperture: ...
iso: ...
shutterSpeed: ...
date: 'YYYY-MM-DD'
width: 1234
height: 5678
location: City, Country
YAML entry format (virtual / game screenshot):
- title: Short title
category: virtual
path: /img/photography/virtual/slugified-title.png
date: 'YYYY-MM-DD'
width: 1234
height: 5678
game: Elden Ring
platform: PC
Target B: Vibes (inbox/vibes/)
Destination: src/static/img/vibes/
Rules:
- Just copy the file.
vibes.js auto-discovers everything in that directory.
- Supported formats:
.jpg, .jpeg, .png, .gif, .webp, .avif.
- Rename to a slugified lowercase filename if the original is a messy Reddit/Twitter URL hash.
- No YAML or HTML edits needed.
cp "inbox/vibes/some-meme.webp" "src/static/img/vibes/some-meme.webp"
Target C: AI Memes Folio (inbox/ai-memes/)
Destination: src/_data/memes.yaml and src/static/img/folio/ai-memes/
Rules:
- Copy the file to
src/static/img/folio/ai-memes/.
- Prepend a new YAML entry to
src/_data/memes.yaml (newest first).
- Increment the
id from the previous highest card (e.g., if the highest is "025", the new one is "026").
YAML entry format:
- id: "026"
image: "/img/folio/ai-memes/FILENAME.webp"
caption: "Short punchy caption"
tags:
- "TAG1"
- "TAG2"
vibes: 0
submitter: "anonymous intern"
time: "just now"
source_name: "the feed"
source_url: "#"
Caption / tags guidance:
- Caption: 1 punchy sentence. Dry wit preferred. No AI-sounding descriptions.
- Tags: pick from existing set (
xkcd-style, agents, vibe-coding, existential, relatable, burnout, prompt-engineering, hallucinated, shipped-to-prod, my-agent-did-this) or invent short kebab-case tags.
Target D: Unclassified root inbox/*.{jpg,png,...}
Files sitting in inbox/ root (not in a subfolder) need to be classified first.
Ask the user: "I found [N] unclassified files in inbox/. Where should each go?"
Show a thumbnail description and let them route to A/B/C or skip.
Step 3 - Execute
For each approved file:
- Copy the file to its destination.
- Make the appropriate data or HTML edit.
- By default, plan to delete successfully imported files from the
inbox/ directory at the end of the run to keep it clean.
After all imports, run a quick sanity check:
node -e "import('js-yaml').then(m => { m.default.load(require('fs').readFileSync('src/_data/photos.yaml','utf8')); console.log('photos.yaml OK'); })"
ls src/static/img/vibes/ | wc -l
grep -c 'class="meme-card"' src/folio/ai-memes/index.html
Step 4 - Commit
git add src/_data/photos.yaml src/static/img/photography/ src/static/img/vibes/ src/static/img/folio/ai-memes/ src/folio/ai-memes/index.html
git commit -m "feat(inbox): import [N] photos / [N] vibes / [N] ai-memes"
git push origin main
Step 5 - Inbox Cleanup
After successfully committing and verifying the media is live in the destination folders, automatically delete the original files from the inbox/ subdirectories to keep the staging area clean.
rm inbox/photography/IMG_xxx.jpg
Conventions & Anti-Patterns
| ✅ Do | ❌ Don't |
|---|
| Short 2–5 word titles for photos | Generate verbose AI descriptions |
| Use words like "Dam son", "Still waters" | Use "serene", "vibrant", "evocative", "tranquil" |
| Leave description absent if not needed | Copy or paraphrase what the script generates |
| Preserve EXIF data accurately | Make up EXIF values |
| Insert ai-memes newest-first | Append to end of meme grid |
| Renumber all card indices after insert | Leave index numbers out of sync |
| Ask before routing unclassified files | Guess the target |