
Apple Photos
- 54 installs
- 638 repo stars
- Updated March 7, 2026
- sundial-org/awesome-openclaw-skills
Helps with ai & agent building tasks during AI-assisted development.
About
apple-photos is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- apple-photos
- AI & Agent Building
- AI-coding skill
Apple Photos by the numbers
- 54 all-time installs (skills.sh)
- +1 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #6,946 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/sundial-org/awesome-openclaw-skills --skill apple-photosAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 54 |
|---|---|
| repo stars | ★ 638 |
| Last updated | March 7, 2026 |
| Repository | sundial-org/awesome-openclaw-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Apple Photos
Access Photos.app via SQLite queries. Run scripts from: cd {baseDir}
Requirements
- Full Disk Access for terminal (System Settings → Privacy → Full Disk Access)
Commands
| Command | Usage |
|---|---|
| Library stats | scripts/photos-count.sh |
| List albums | scripts/photos-list-albums.sh |
| Recent photos | scripts/photos-recent.sh [count] |
| List people | scripts/photos-list-people.sh |
| Search by person | scripts/photos-search-person.sh <name> [limit] |
| Search by content | scripts/photos-search-content.sh <query> [limit] |
| Search by date | scripts/photos-search-date.sh <start> [end] [limit] |
| Photo info | scripts/photos-info.sh <uuid> |
| Export photo | scripts/photos-export.sh <uuid> [output_path] |
Output
- Recent/search:
Filename | Date | Type | UUID - People:
ID | Name | Photo Count - Default export:
/tmp/photo_export.jpg
Workflow: View a Photo
1. Get UUID: scripts/photos-recent.sh 1 2. Export: scripts/photos-export.sh "UUID" 3. View at /tmp/photo_export.jpg
Notes
- Date format:
YYYY-MM-DDorYYYY-MM-DD HH:MM - Content search uses ML, slower (~5-10s) than date/person (~100ms)
- HEIC auto-converts to JPEG on export
- Name search is case-insensitive, partial match
#!/bin/bash
# Get photo library statistics
# Usage: photos-count.sh
# Output: Quick stats about the library
PHOTOS_DB=~/Pictures/Photos\ Library.photoslibrary/database/Photos.sqlite
if [ ! -f "$PHOTOS_DB" ]; then
echo "Error: Photos database not found" >&2
exit 1
fi
total=$(sqlite3 "$PHOTOS_DB" "SELECT COUNT(*) FROM ZASSET WHERE ZTRASHEDSTATE = 0;" 2>/dev/null)
photos=$(sqlite3 "$PHOTOS_DB" "SELECT COUNT(*) FROM ZASSET WHERE ZTRASHEDSTATE = 0 AND ZKIND = 0;" 2>/dev/null)
videos=$(sqlite3 "$PHOTOS_DB" "SELECT COUNT(*) FROM ZASSET WHERE ZTRASHEDSTATE = 0 AND ZKIND = 1;" 2>/dev/null)
favorites=$(sqlite3 "$PHOTOS_DB" "SELECT COUNT(*) FROM ZASSET WHERE ZTRASHEDSTATE = 0 AND ZFAVORITE = 1;" 2>/dev/null)
hidden=$(sqlite3 "$PHOTOS_DB" "SELECT COUNT(*) FROM ZASSET WHERE ZTRASHEDSTATE = 0 AND ZHIDDEN = 1;" 2>/dev/null)
screenshots=$(sqlite3 "$PHOTOS_DB" "SELECT COUNT(*) FROM ZASSET WHERE ZTRASHEDSTATE = 0 AND ZKINDSUBTYPE = 2;" 2>/dev/null)
echo "Total: $total"
echo "Photos: $photos"
echo "Videos: $videos"
echo "Screenshots: $screenshots"
echo "Favorites: $favorites"
echo "Hidden: $hidden"
#!/bin/bash
# Export a photo from Photos library to a viewable format
# Usage: photos-export.sh <uuid> [output_path]
# Default output: /tmp/photo_export.jpg
# Automatically converts HEIC to JPEG
UUID="$1"
OUTPUT="${2:-/tmp/photo_export.jpg}"
if [ -z "$UUID" ]; then
echo "Usage: photos-export.sh <uuid> [output_path]" >&2
exit 1
fi
PHOTOS_LIB=~/Pictures/Photos\ Library.photoslibrary
PHOTOS_DB="$PHOTOS_LIB/database/Photos.sqlite"
if [ ! -f "$PHOTOS_DB" ]; then
echo "Error: Photos database not found" >&2
exit 1
fi
# Get filename and directory from UUID
read -r FILENAME DIRECTORY < <(sqlite3 "$PHOTOS_DB" "
SELECT ZFILENAME, ZDIRECTORY
FROM ZASSET
WHERE ZUUID = '$UUID'
AND ZTRASHEDSTATE = 0
LIMIT 1;
" 2>/dev/null | tr '|' ' ')
if [ -z "$FILENAME" ]; then
echo "Error: Photo with UUID '$UUID' not found" >&2
exit 1
fi
# Find the original file
ORIGINAL=$(find "$PHOTOS_LIB/originals" -name "$FILENAME" 2>/dev/null | head -1)
if [ -z "$ORIGINAL" ] || [ ! -f "$ORIGINAL" ]; then
echo "Error: Original file not found for $FILENAME" >&2
exit 1
fi
# Get file extension
EXT="${FILENAME##*.}"
EXT_LOWER=$(echo "$EXT" | tr '[:upper:]' '[:lower:]')
# Ensure output directory exists
mkdir -p "$(dirname "$OUTPUT")"
# Check if ImageMagick is available for proper orientation handling
HAS_MAGICK=false
if command -v magick >/dev/null 2>&1; then
HAS_MAGICK=true
elif command -v convert >/dev/null 2>&1; then
HAS_MAGICK=true
fi
# Convert HEIC/HEIF to JPEG, copy others directly
if [[ "$EXT_LOWER" == "heic" || "$EXT_LOWER" == "heif" ]]; then
if [ "$HAS_MAGICK" = true ]; then
# Use ImageMagick for HEIC conversion + auto-orient
magick "$ORIGINAL" -auto-orient "$OUTPUT" 2>/dev/null || \
convert "$ORIGINAL" -auto-orient "$OUTPUT" 2>/dev/null
else
# Fallback to sips
sips -s format jpeg "$ORIGINAL" --out "$OUTPUT" >/dev/null 2>&1
fi
if [ $? -ne 0 ]; then
echo "Error: Failed to convert HEIC to JPEG" >&2
exit 1
fi
elif [[ "$EXT_LOWER" == "png" || "$EXT_LOWER" == "jpg" || "$EXT_LOWER" == "jpeg" ]]; then
if [ "$HAS_MAGICK" = true ]; then
# Use ImageMagick to copy + auto-orient
magick "$ORIGINAL" -auto-orient "$OUTPUT" 2>/dev/null || \
convert "$ORIGINAL" -auto-orient "$OUTPUT" 2>/dev/null
else
cp "$ORIGINAL" "$OUTPUT"
fi
else
# For other formats (video, etc), just copy
cp "$ORIGINAL" "$OUTPUT"
fi
echo "$OUTPUT"
#!/bin/bash
# Get detailed info about a photo
# Usage: photos-info.sh <uuid>
# Output: Key-value pairs
UUID="$1"
if [ -z "$UUID" ]; then
echo "Usage: photos-info.sh <uuid>" >&2
exit 1
fi
PHOTOS_DB=~/Pictures/Photos\ Library.photoslibrary/database/Photos.sqlite
if [ ! -f "$PHOTOS_DB" ]; then
echo "Error: Photos database not found" >&2
exit 1
fi
sqlite3 -separator '|' "$PHOTOS_DB" "
SELECT
ZUUID,
ZFILENAME,
datetime(ZDATECREATED + 978307200, 'unixepoch', 'localtime'),
datetime(ZMODIFICATIONDATE + 978307200, 'unixepoch', 'localtime'),
datetime(ZADDEDDATE + 978307200, 'unixepoch', 'localtime'),
ZUNIFORMTYPEIDENTIFIER,
ZWIDTH,
ZHEIGHT,
CASE WHEN ZFAVORITE = 1 THEN 'Yes' ELSE 'No' END,
CASE WHEN ZHIDDEN = 1 THEN 'Yes' ELSE 'No' END,
ZLATITUDE,
ZLONGITUDE
FROM ZASSET
WHERE ZUUID = '$UUID'
AND ZTRASHEDSTATE = 0
LIMIT 1;
" 2>/dev/null | while IFS='|' read -r uuid filename created modified added type width height favorite hidden lat lon; do
if [ -z "$uuid" ]; then
echo "Error: Photo with UUID '$UUID' not found" >&2
exit 1
fi
echo "UUID: $uuid"
echo "Filename: $filename"
echo "Created: $created"
echo "Modified: $modified"
echo "Added: $added"
echo "Type: $type"
echo "Dimensions: ${width}x${height}"
echo "Favorite: $favorite"
echo "Hidden: $hidden"
if [ -n "$lat" ] && [ "$lat" != "" ] && [ "$lat" != "0" ] && [ "$lat" != "-180.0" ] && [ "$lat" != "-180" ]; then
echo "Location: $lat, $lon"
else
echo "Location: None"
fi
done
# Check if anything was output
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "Error: Could not query Photos database" >&2
exit 1
fi
#!/bin/bash
# List all albums in Photos.app
# Usage: photos-list-albums.sh
# Output: Album Name | Photo Count
osascript -e '
tell application "Photos"
set output to ""
repeat with a in albums
set albumName to name of a
set photoCount to count of media items of a
set output to output & albumName & " | " & photoCount & "\n"
end repeat
return output
end tell
' 2>/dev/null | sed '/^$/d'
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "Error: Could not access Photos.app" >&2
exit 1
fi
#!/bin/bash
# List all recognized people in Photos library
# Usage: photos-list-people.sh
# Output: ID | Name | Photo Count
PHOTOS_DB=~/Pictures/Photos\ Library.photoslibrary/database/Photos.sqlite
if [ ! -f "$PHOTOS_DB" ]; then
echo "Error: Photos database not found" >&2
exit 1
fi
sqlite3 "$PHOTOS_DB" "
SELECT
p.Z_PK,
COALESCE(p.ZDISPLAYNAME, p.ZFULLNAME, 'Unnamed'),
COUNT(DISTINCT f.ZASSETFORFACE) as photo_count
FROM ZPERSON p
LEFT JOIN ZDETECTEDFACE f ON f.ZPERSONFORFACE = p.Z_PK
WHERE (p.ZDISPLAYNAME IS NOT NULL AND p.ZDISPLAYNAME != '')
OR (p.ZFULLNAME IS NOT NULL AND p.ZFULLNAME != '')
GROUP BY p.Z_PK
HAVING photo_count > 0
ORDER BY photo_count DESC;
" 2>/dev/null | while IFS='|' read -r id name count; do
echo "$id | $name | $count photos"
done
#!/bin/bash
# List recent photos from Photos library (fast SQLite query)
# Usage: photos-recent.sh [count]
# Default: 10 most recent
# Output: Filename | Date | Type | UUID
COUNT=${1:-10}
PHOTOS_DB=~/Pictures/Photos\ Library.photoslibrary/database/Photos.sqlite
if [ ! -f "$PHOTOS_DB" ]; then
echo "Error: Photos database not found" >&2
exit 1
fi
sqlite3 "$PHOTOS_DB" "
SELECT
ZFILENAME,
datetime(ZDATECREATED + 978307200, 'unixepoch', 'localtime'),
CASE ZUNIFORMTYPEIDENTIFIER
WHEN 'public.heic' THEN 'HEIC'
WHEN 'public.jpeg' THEN 'JPEG'
WHEN 'public.png' THEN 'PNG'
WHEN 'com.apple.quicktime-movie' THEN 'VIDEO'
ELSE ZUNIFORMTYPEIDENTIFIER
END,
ZUUID
FROM ZASSET
WHERE ZTRASHEDSTATE = 0
AND ZFILENAME IS NOT NULL
ORDER BY ZDATECREATED DESC
LIMIT $COUNT;
" 2>/dev/null | while IFS='|' read -r filename date type uuid; do
echo "$filename | $date | $type | $uuid"
done
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "Error: Could not query Photos database" >&2
exit 1
fi
#!/bin/bash
# Search photos by content (objects, scenes, text) using Photos.app ML
# Usage: photos-search-content.sh <query> [limit]
# Examples: "cat", "beach", "receipt", "food", "car"
# Default limit: 10
# NOTE: Slower than other searches (~5-10 seconds) - uses AppleScript
QUERY="$1"
LIMIT="${2:-10}"
if [ -z "$QUERY" ]; then
echo "Usage: photos-search-content.sh <query> [limit]" >&2
echo " Examples: cat, beach, receipt, food, car, sunset" >&2
exit 1
fi
osascript -e "
tell application \"Photos\"
set foundPhotos to search for \"$QUERY\"
set photoCount to count of foundPhotos
set output to \"\"
set exportCount to 0
repeat with p in foundPhotos
if exportCount >= $LIMIT then exit repeat
try
set fname to filename of p
set fdate to date of p
set fid to id of p
set output to output & fname & \"|\" & (fdate as string) & \"|\" & fid & \"\n\"
set exportCount to exportCount + 1
end try
end repeat
return output & \"---\n\" & \"Total: \" & photoCount & \" results for '\" & \"$QUERY\" & \"'\"
end tell
" 2>/dev/null | while IFS='|' read -r line; do
echo "$line"
done
#!/bin/bash
# Search photos by date range
# Usage: photos-search-date.sh <start_date> [end_date] [limit]
# Date format: YYYY-MM-DD or "YYYY-MM-DD HH:MM"
# Default end_date: now, Default limit: 50
# Output: Filename | Date | Type | UUID
START_DATE="$1"
END_DATE="${2:-$(date '+%Y-%m-%d %H:%M')}"
LIMIT="${3:-50}"
if [ -z "$START_DATE" ]; then
echo "Usage: photos-search-date.sh <start_date> [end_date] [limit]" >&2
echo " Date format: YYYY-MM-DD or 'YYYY-MM-DD HH:MM'" >&2
exit 1
fi
PHOTOS_DB=~/Pictures/Photos\ Library.photoslibrary/database/Photos.sqlite
if [ ! -f "$PHOTOS_DB" ]; then
echo "Error: Photos database not found" >&2
exit 1
fi
# Convert dates to Apple epoch (seconds since 2001-01-01)
# Apple epoch = Unix epoch - 978307200
start_unix=$(date -j -f "%Y-%m-%d %H:%M" "$START_DATE 00:00" "+%s" 2>/dev/null || date -j -f "%Y-%m-%d %H:%M" "$START_DATE" "+%s" 2>/dev/null)
end_unix=$(date -j -f "%Y-%m-%d %H:%M" "$END_DATE 23:59" "+%s" 2>/dev/null || date -j -f "%Y-%m-%d %H:%M" "$END_DATE" "+%s" 2>/dev/null)
if [ -z "$start_unix" ] || [ -z "$end_unix" ]; then
echo "Error: Invalid date format. Use YYYY-MM-DD or 'YYYY-MM-DD HH:MM'" >&2
exit 1
fi
start_apple=$((start_unix - 978307200))
end_apple=$((end_unix - 978307200))
sqlite3 "$PHOTOS_DB" "
SELECT
ZFILENAME,
datetime(ZDATECREATED + 978307200, 'unixepoch', 'localtime'),
CASE ZUNIFORMTYPEIDENTIFIER
WHEN 'public.heic' THEN 'HEIC'
WHEN 'public.jpeg' THEN 'JPEG'
WHEN 'public.png' THEN 'PNG'
WHEN 'com.apple.quicktime-movie' THEN 'VIDEO'
ELSE ZUNIFORMTYPEIDENTIFIER
END,
ZUUID
FROM ZASSET
WHERE ZTRASHEDSTATE = 0
AND ZFILENAME IS NOT NULL
AND ZDATECREATED >= $start_apple
AND ZDATECREATED <= $end_apple
ORDER BY ZDATECREATED DESC
LIMIT $LIMIT;
" 2>/dev/null | while IFS='|' read -r filename date type uuid; do
echo "$filename | $date | $type | $uuid"
done
result_count=$(sqlite3 "$PHOTOS_DB" "
SELECT COUNT(*) FROM ZASSET
WHERE ZTRASHEDSTATE = 0 AND ZFILENAME IS NOT NULL
AND ZDATECREATED >= $start_apple AND ZDATECREATED <= $end_apple;
" 2>/dev/null)
if [ "$result_count" -eq 0 ]; then
echo "No photos found in date range"
fi
#!/bin/bash
# Search photos by person name
# Usage: photos-search-person.sh <name> [limit]
# Name search is case-insensitive and partial match
# Default limit: 50
# Output: Filename | Date | Type | UUID
NAME="$1"
LIMIT="${2:-50}"
if [ -z "$NAME" ]; then
echo "Usage: photos-search-person.sh <name> [limit]" >&2
exit 1
fi
PHOTOS_DB=~/Pictures/Photos\ Library.photoslibrary/database/Photos.sqlite
if [ ! -f "$PHOTOS_DB" ]; then
echo "Error: Photos database not found" >&2
exit 1
fi
# First find matching person IDs
PERSON_IDS=$(sqlite3 "$PHOTOS_DB" "
SELECT Z_PK FROM ZPERSON
WHERE LOWER(ZDISPLAYNAME) LIKE LOWER('%$NAME%')
OR LOWER(ZFULLNAME) LIKE LOWER('%$NAME%');
" 2>/dev/null | tr '\n' ',' | sed 's/,$//')
if [ -z "$PERSON_IDS" ]; then
echo "No person found matching '$NAME'"
exit 0
fi
# Get photos for those persons
sqlite3 "$PHOTOS_DB" "
SELECT DISTINCT
a.ZFILENAME,
datetime(a.ZDATECREATED + 978307200, 'unixepoch', 'localtime'),
CASE a.ZUNIFORMTYPEIDENTIFIER
WHEN 'public.heic' THEN 'HEIC'
WHEN 'public.jpeg' THEN 'JPEG'
WHEN 'public.png' THEN 'PNG'
WHEN 'com.apple.quicktime-movie' THEN 'VIDEO'
ELSE a.ZUNIFORMTYPEIDENTIFIER
END,
a.ZUUID
FROM ZASSET a
JOIN ZDETECTEDFACE f ON f.ZASSETFORFACE = a.Z_PK
WHERE f.ZPERSONFORFACE IN ($PERSON_IDS)
AND a.ZTRASHEDSTATE = 0
AND a.ZFILENAME IS NOT NULL
ORDER BY a.ZDATECREATED DESC
LIMIT $LIMIT;
" 2>/dev/null | while IFS='|' read -r filename date type uuid; do
echo "$filename | $date | $type | $uuid"
done
# Get total count
TOTAL=$(sqlite3 "$PHOTOS_DB" "
SELECT COUNT(DISTINCT a.Z_PK)
FROM ZASSET a
JOIN ZDETECTEDFACE f ON f.ZASSETFORFACE = a.Z_PK
WHERE f.ZPERSONFORFACE IN ($PERSON_IDS)
AND a.ZTRASHEDSTATE = 0;
" 2>/dev/null)
echo "---"
echo "Total: $TOTAL photos of '$NAME'"
Related skills
AI & Agent Buildingagents