Help us improve
Share bugs, ideas, or general feedback.
From immich-photo-manager
Searches an Immich photo library by natural language, GPS, dates, people, cameras, and CLIP visual search. Activates on photo-finding requests.
npx claudepluginhub drolosoft/immich-photo-manager --plugin immich-photo-managerHow this skill is triggered — by the user, by Claude, or both
Slash command
/immich-photo-manager:photo-searchThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Before doing ANYTHING else in this skill, call `ping` on the Immich MCP server.**
Creates, curates, and publishes Immich albums organized by geography, theme, or custom criteria. Automates album creation from user prompts like 'create an album from my trip to Italy'.
Automates Google Photos tasks (upload media, manage albums, search photos, batch add items) via Rube MCP (Composio). Always searches tools first for current schemas.
Batch downloads original photos from PhotoPlus album links (photoplus.cn/live/). Extracts album ID, confirms with user, and runs a Python script with multi-threading and skip support.
Share bugs, ideas, or general feedback.
Before doing ANYTHING else in this skill, call ping on the Immich MCP server.
ping succeeds -> proceed with the skill normally.ping fails or the MCP tools are not available -> STOP. Do not continue. Tell the user:Immich is not connected. This plugin needs a running Immich MCP server to work.
Run /setup-immich-photo-manager to configure your Immich connection. You'll need:
- Your Immich server URL (e.g.,
http://192.168.1.100:2283)- An Immich API key (how to create one)
- The MCP server configured (see /setup-immich-photo-manager)
Do NOT skip this check. Do NOT try to run any other tool first. Always ping, always block if it fails.
This is the most important rule of this skill. NEVER create albums as part of a search workflow.
The user has a curated library of real albums. Creating temporary albums pollutes their library with junk. Instead:
get_thumbnails_batchcreate_album from this skill. Not for "temp" albums, not for "search results", not for any reason.Identify what the user is looking for. Determine which search dimensions apply.
Use search_metadata and/or search_smart to find matching assets.
IMPORTANT — Immich EXIF location quirks:
search_smart with the place name as a CLIP query.Search strategy priority:
search_metadata(city=...) — fastest, most precise if the city name matchessearch_metadata(state=...) or search_metadata(country=...) — broader, catches municipalitiessearch_smart(query="...") — AI/CLIP semantic search, catches things without GPSCall list_albums() and fuzzy-match album names/descriptions against the user's query.
Examples:
Matching rules:
Two paths depending on whether a real album was found:
Use get_album(album_id) to get the full asset list. Extract id, originalFileName, and fileCreatedAt from each asset.
This is the best path because the user curated these albums intentionally.
Use the search results directly — they already contain id, originalFileName, and fileCreatedAt for each asset.
Do NOT create an album. Just show the photos.
Fetch thumbnails as base64 using get_thumbnails_batch(asset_ids=[...], size="thumbnail", limit=50). The Cowork viewer runs in an about: sandbox that blocks ALL external network requests, so thumbnails MUST be embedded as base64 data: URIs.
assets/viewer-template.html from the plugin root{{PLACEHOLDERS}} with actual datacomputer:// linkFor Related Albums ({{ALBUMS_JSON}}):
[]| Dimension | MCP Tool / Parameter | Example |
|---|---|---|
| Visual/semantic | search_smart(query=...) | "sunset at the beach", "birthday cake" |
| Location (text) | search_metadata(city=..., state=..., country=...) | city="Barcelona" |
| Date range | search_metadata(taken_after=..., taken_before=...) | 2023-06-01 to 2023-06-30 |
| Camera/device | search_metadata(make=..., model=...) | make="Apple", model="iPhone 14 Pro" |
| File type | search_metadata(asset_type=...) | "IMAGE" or "VIDEO" |
| Favorites | search_metadata(is_favorite=true) | true |
| User says | Search strategy |
|---|---|
| "photos from my Italy trip" | search_metadata(country="Italy") + list_albums() to find Italy albums |
| "sunset photos" | search_smart(query="sunset") |
| "photos from last Christmas" | search_metadata(taken_after="2025-12-20", taken_before="2025-12-31") |
| "my best photos" | search_metadata(is_favorite=true) |
| "photos taken with iPhone" | search_metadata(make="Apple") |
| "videos from Barcelona" | search_metadata(city="Barcelona", asset_type="VIDEO") |
| "show me Tikal" | search_metadata(state="Petén") + search_smart(query="Tikal") + match album "Tikal & Petén" |
Use the canonical template at assets/viewer-template.html. Read the template file, replace {{PLACEHOLDERS}} with actual data, and write the result.
{{PAGE_SIZE}}, {{PHOTO_COUNT}}, {{ALBUM_TOTAL}}: Should be plain integers (e.g. 20). The template uses parseInt() with fallbacks, so non-numeric values degrade gracefully (PAGE_SIZE defaults to 6, others to 0).{{ALBUM_NAME}}: Can contain any characters including apostrophes (e.g. "L'Hospitalet"). Safe in HTML contexts. The JS alt-text reads from document.title instead of re-injecting this placeholder, so apostrophes won't break JS.{{SEARCH_QUERY}}, {{IMMICH_URL}}: Can be any string.{{PHOTO_ENTRIES}}: Must be valid JS object literals, comma-separated.{{ALBUMS_JSON}}: JSON album objects. The template wraps them in [...].flat(), so you can pass any of these formats:
{"id":"abc","name":"X","total":50},{"id":"def","name":"Y","total":30}[{"id":"abc","name":"X","total":50}][].flat() → [] and hides the sectionThere are three strategies for delivering thumbnails to the gallery viewer, with different trade-offs. The strategy used depends on the user's Immich setup and the viewing context.
The Cowork viewer runs in an about: protocol sandbox that blocks ALL external network requests (fetch, <img src>, etc.). Therefore, the default and always-safe strategy is to embed thumbnails as base64 data: URIs directly in the HTML.
Each photo entry in {{PHOTO_ENTRIES}} includes the full thumbnail data:
{src:'data:image/jpeg;base64,/9j/4AAQ...',id:'<asset-id>',name:'<filename>',date:'<ISO-date>'}
src: Base64 data URI of the thumbnail (from get_thumbnails_batch, size=thumbnail, ~250px, ~15-25KB each)id: The Immich asset ID (for linking to Immich web UI)name: Original filename (displayed as label)date: ISO date string from the asset metadataAlways use size="thumbnail" (250px) — never preview (1440px). Thumbnails average ~18KB each, so 50 photos ≈ 0.9MB HTML file.
How to get thumbnails: Call get_thumbnails_batch(asset_ids=[...], size="thumbnail", limit=50). If more than 50 photos, call in batches of 50.
Limitations: HTML file size grows linearly with photo count (~18KB per photo). Not ideal for albums with hundreds of photos. Maximum practical limit is ~50 thumbnails per gallery file.
If the user has configured CORS headers on their Immich reverse proxy, the gallery HTML can use JavaScript fetch() with the x-api-key header to load thumbnails on demand, converting responses to blob URLs. This enables full URL-based delivery for any photos, not just albums.
This requires the user to configure their reverse proxy (Nginx, Caddy, Traefik, etc.) to return CORS headers. See the Post-Install: CORS Configuration section in /setup-immich-photo-manager for instructions.
Advantages: Works for any photos (albums or search results), tiny HTML file, true on-demand pagination, no shared links needed.
Limitations: Requires CORS configuration on the server side. Not available out of the box.
Always use Base64 Embedded (Strategy 1) as the default:
├─ ≤50 photos → Embed all thumbnails as base64
└─ >50 photos → Embed first 50 in batches, warn user about file size and total count
Note: CORS-enabled direct URLs (Strategy 3) are an opt-in enhancement for users who open galleries in a regular browser. They do NOT work inside the Cowork sandbox. Never mention strategy numbers or internal labels in user-facing output — just generate the gallery silently using the correct approach.
The first page (PAGE_SIZE photos) loads immediately. Subsequent pages use IntersectionObserver to set src from dataset.src only when scrolled into view. Pagination is manual via "Load more" button (no infinite scroll). This works with both base64 and URL-based strategies.
{{ALBUMS_JSON}} — a JSON array of REAL albums:
{"id":"abc123","name":"Tikal & Petén","total":169},{"id":"xyz789","name":"Guatemala","total":392}
Comma-separated JSON objects — NO outer array brackets (the template adds [...]). If no real albums match, use empty string.
User: "show me photos of Tikal"
1. ping() -> OK
2. search_metadata(state="Petén", country="Guatemala") -> found 200+ assets
3. list_albums() -> scan names -> found "Tikal & Petén" (id: d6dd63d0, 169 photos), "Guatemala" (id: 8dde4bb1, 392 photos)
4. get_album(album_id="d6dd63d0") -> get asset list with IDs, names, dates
5. get_thumbnails_batch(asset_ids=[...], size="thumbnail", limit=50) -> base64 JPEG data for first 50 photos
6. Read assets/viewer-template.html
7. Replace placeholders:
- {{ALBUM_NAME}} -> "Tikal & Petén"
- {{ALBUM_TOTAL}} -> 169
- {{SEARCH_QUERY}} -> "Tikal"
- {{IMMICH_URL}} -> "https://your-immich-server.com"
- {{PAGE_SIZE}} -> 20
- {{PHOTO_COUNT}} -> 50 (limited to 50 for file size)
- {{PHOTO_ENTRIES}} -> {src:'data:image/jpeg;base64,...',id:"abc",name:"IMG_001",date:"2023-06-15"}, ...
- {{ALBUMS_JSON}} -> {"id":"d6dd63d0","name":"Tikal & Petén","total":169},{"id":"8dde4bb1","name":"Guatemala","total":392}
8. Write tikal.html to outputs (~0.9MB with 50 base64 thumbnails, total album has 169 photos)
9. Present computer:// link
User: "show me sunset photos"
1. ping() -> OK
2. search_smart(query="sunset") -> found 35 assets (each has id, originalFileName, fileCreatedAt)
3. list_albums() -> no album name matches "sunset"
4. Read template
5. Replace placeholders:
- {{ALBUM_NAME}} -> "Sunset Photos"
- {{ALBUM_TOTAL}} -> 35
- {{PHOTO_COUNT}} -> 35
- {{PHOTO_ENTRIES}} -> entries built from get_thumbnails_batch (base64 src + id + name + date)
- {{ALBUMS_JSON}} -> <-- empty string, no real albums match
6. Write sunset-photos.html to outputs (~0.7MB with base64 thumbnails)
7. Present computer:// link
When showing search results:
Immich API returns paginated results. For large result sets:
No GPS data + screen-resolution dimensions + no lens/focal length EXIF.
Same date range across import sources. Compare by exact hash, timestamp + dimensions, or CLIP similarity.