Skill v1.0.1
currentAutomated scan100/100+1 new
name: optimize-images description: Optimize the images in a project with Iminify and write the smaller files back in place. Compresses by default; converts to WebP, AVIF, JPG or PNG, resizes, fits each file under a size in KB, or adds alt text along the way, when asked. Use when the user asks to compress, optimize, shrink, convert or resize the images in a project, a folder or a set of files, or to get them under a file size. Not for alt text or file names alone (the image-seo skill) or for how a page loads its images (the fix-core-web-vitals skill). metadata: version: "1.0.1"
Optimize images
Compress a project's images with Iminify and put the smaller files back where they were. Iminify does the encoding on the user's own account, through the iminify MCP server this plugin connects. This skill is the procedure around it: which files, which settings, and what gets replaced.
It changes files in the user's project, so it confirms before it starts and never overwrites anything that can't be restored.
0. Check the connection and the allowance
The Iminify tools are get_usage, create_upload_link, compress_images and get_image, plus write_alt_text for step 7. If they're missing, or a call says the server needs authentication, stop and tell the user how to sign in:
- Claude Code: run
/mcp, pickplugin:iminify:iminify, and press Approve on the Iminify page that opens. - Cursor: open Cursor Settings, then MCP, and press Connect next to
iminify.
Signing in needs an Iminify account with a verified email address. The free plan works.
Then call get_usage and note:
usage.images.remaining: images left today (nullmeans the plan has no daily cap);limits.max_file_size: the largest file the plan takes, in bytes;limits.compressions_per_minute: how many compressions a minute the plan allows;usage.alt_texts.remaining: AI alt texts left today, when the user wants alt text (nullmeans no daily cap).
1. Find the images
Work on exactly what the user named: a file, a folder, a pattern. When they said "the project" or "the site", look through the project. In a git repository, list tracked files with git ls-files, so ignored and generated files are left out. Otherwise, search the folder and skip dependency, build and cache folders: node_modules, vendor, .git, dist, build, public/build, .next, .nuxt, .output, coverage, storage, tmp.
Iminify reads .jpg, .jpeg, .png, .webp, .gif, .heic, .heif, .tif and .tiff. Leave everything else alone, SVG, ICO and AVIF included. Leave out files larger than max_file_size too, and name them in the report.
Show the user what you found before uploading anything: the number of files, their total size, the largest few, and the settings you'll use (step 3). Wait for a yes, unless the user already named the exact files and settings. When there are more files than remaining allows today, say so and offer to start with the largest ones.
2. Make sure the originals can come back
- In a git repository, a file can be restored after it's replaced only when git tracks it and it has no uncommitted changes:
git ls-files --error-unmatch <file>succeeds andgit status --porcelain <file>prints nothing. An ignored file prints nothing too, so the first check is what tells them apart. Name every file that fails either check (untracked, ignored, or changed) and ask before touching it. - Outside git, say that the originals stay in the user's Iminify account (Iminify keeps every upload until the user deletes it, and
get_imagegives a freshoriginal.download_urlat any time), and ask whether to go on.
3. Choose the settings
Send no settings unless the user asked for something. The defaults suit a website: level smart (the lowest quality that still looks like the original), the same format, and the metadata stripped (a photo's orientation is applied to its pixels first, so nothing comes back sideways).
| The user asks for | Send | |
|---|---|---|
| no visible loss at all, "lossless" | level: "lossless" | |
| "as small as possible", "aggressive" | level: "ultra" | |
| WebP, AVIF, JPG or PNG | format: "webp" (or "avif", "jpg", "png"), and read step 6 first | |
| "whatever format is smallest" | format: "auto", and read step 6 first | |
| no wider than N pixels | width: N, only for the images wider than N (below) | |
| under N KB each, for a form or a page with a file size limit | size_limit: N, and read the size limit paragraph below | |
| keep camera data, dates or location | keep_metadata: true | |
| alt text, image descriptions, accessibility | nothing extra (alt text is on by default), alt_text_language for another language, and read step 7 | |
| only alt text or file names, nothing else | stop here and use the image-seo skill instead |
Unless the user asked for alt text, send alt_text: false. Iminify writes an AI alt text for every image by default, from a small copy it sends to Anthropic, and each one counts against the day's AI alt texts. This procedure writes files back and uses no alt text, so there is no reason to send the copies or spend the allowance.
A width or height is exact: Iminify enlarges an image that is smaller than it. For "no wider than 1600", read each image's width first and send width only in a call for the wider images. The rest go in a call without it. To read a width: file prints it for PNG and JPEG, as do ImageMagick's identify and, on macOS, sips -g pixelWidth. In Windows PowerShell, for JPG, PNG, GIF and TIFF (not WebP or HEIC): Add-Type -AssemblyName System.Drawing; ([System.Drawing.Image]::FromFile((Resolve-Path 'path\to\hero.png'))).Width. When a file's width can't be read, don't resize it: leave width out for it and say so in the report. Settings apply to every image in a call, so images that need different settings go in separate calls.
A size_limit is the most each optimized file may weigh, in KB of 1,000 bytes. Iminify lowers the quality until the file fits, and when even the lowest quality is over, makes the image smaller in pixels until it fits. The limit comes before the level: with level: "lossless", a file over the limit is encoded again with loss, so tell the user when they asked for both. Two things can change beyond the bytes, so say both when you show the list in step 1: a PNG or GIF over the limit comes back as JPG, WebP or AVIF, which is a conversion (ask whether to convert those and follow step 6, or leave them out of the call), and a photo can come back smaller in pixels. format: "png" with a limit is refused, and an animation is never fitted. Each finished image's size_limit_status says how it came out: under, resized (made smaller in pixels to fit) or over (it couldn't get under).
An animated WebP or PNG can't be resized: Iminify refuses the resize with animation_not_resizable, because only its first frame would come back (step 4 says what to do then). An animated GIF can be resized, frame by frame. Sent without a resize and without a format change, an animated WebP or PNG comes back with every frame, only its metadata taken off (kept with keep_metadata: true).
HEIC and TIFF always come back in another format, because Iminify doesn't write either. Treat them as conversions (step 6).
4. Upload and compress, one batch at a time
One compress_images call takes a limited number of images (its own description gives the number, and a call over it is refused whole). Split the files into batches of at most that many, and finish each batch before you start the next:
- For each file in the batch, one at a time:
- Call
create_upload_linkwith the file's name. The answer has anupload_id, anupload_urland a readycurlcommand. - Send the file straight away, because the link expires within minutes and works once: run the answer's
curlcommand with the file's real path in place of its name. In Windows PowerShell, typecurl.exe: plaincurlthere is an alias forInvoke-WebRequest, which takes different arguments. - Keep the
upload_idwith the file's path. - Call
compress_imageswith the batch'supload_idsand the settings.
Iminify counts every file sent but not yet compressed, and stops handing out upload links once that count reaches the images the plan can still compress today (a fixed number on a plan with no daily cap). Compressing each batch before uploading the next keeps the count low. When create_upload_link is refused, handle it by its code:
uploads_waiting: uploads not yet compressed are holding every place. When the message names upload ids, those files were already sent: send them tocompress_imagesin calls of at most the number its description gives, then ask for the link again. An id you didn't keep, left over from an earlier run that stopped, still gets compressed so it stops counting, but don't write its result over any file. When the message names no ids, the places are held by open links whose file never arrived, and they lapse within the minutes it gives: wait that long and ask again, or stop and report.daily_limit_reached: stop. Report what's done, what's left, and the message as it came.
The answer from compress_images lists its images in the order of the upload_ids you sent, less the ones turned down, and each has an id: keep a map from that id to the local path. Anything turned down is in refused, with its source (the upload id), code and message. When nothing at all could be queued, the call comes back as an error that carries the same refused list. Handle each refusal by its code:
rate_limited: the minute's allowance is spent. Waitretry_afterseconds and send those upload ids again. An upload refused this way can still be used.daily_limit_reached: stop. Report what's done, what's left, and the message as it came.animation_not_resizable: the file is an animated WebP or PNG and the call asked for a resize. The upload is kept: send the same upload id again in a call with nowidth,height,scaleorformat, and every frame stays. Say in the report that it wasn't resized.- anything else: leave that file alone and give the reason in the report.
An uploaded file keeps for about a day, so a batch interrupted between its uploads and its compress_images call can still be finished with the ids you kept.
5. Wait, then write the results back
Poll get_image for each id, a few seconds apart, until its status is completed, already-optimized, failed or cancelled. Deal with each image as soon as it finishes, because download addresses expire after an hour. Ask get_image again for a fresh one if an address has expired.
completed, the same format as the file, andoptimized.sizesmaller than the file on disk: saveoptimized.download_urlto a temporary file in the same folder, such as.hero.png.iminifybesidehero.png, following redirects and failing on an HTTP error rather than saving the error page. Check that its size in bytes equalsoptimized.size, then move it over the original. If it doesn't, delete the temporary file, askget_imagefor a fresh address and download again.completedin another format: it's a conversion. Follow step 6.- With a
size_limit, readsize_limit_statustoo.resized:optimized.widthandoptimized.heightare smaller than the file's own; write it back the way its format's bullet says (the first bullet, or step 6 for a conversion), and name it in the report with its new size.over: it couldn't get under the limit; write it back only when it is still smaller than the file on disk, and say in the report that it missed, with its size. content_credentials.statusisremoved: the file carries Content Credentials, a signed record of where it came from (every Gemini image has one), and the optimized copy doesn't, because any change to a file breaks their signature. Write it back as its format's bullet says, and name it in the report: the project's file loses the record once it's written over (git still has the old one, if the project uses it), while the upload Iminify keeps atoriginal.download_urlstill carries it.already-optimized: Iminify couldn't make the image smaller with these settings. Leave the file as it is and say so plainly, with no saving reported.completedbut not smaller: leave the file as it is.failedorcancelled: leave the file as it is, and pass on thehintif there is one.
6. Conversions change file names
A PNG converted to WebP becomes hero.webp, and everything that loads hero.png still points at the old file. Convert only when the user asked for it. Before writing anything:
- Search the project for each file's name: code, templates, stylesheets, Markdown, config.
- Tell the user what will change: the new files, the references you'll update, and that the old files stay unless they want them removed.
- Check that nothing already sits at the new name. A site often ships
hero.webpbesidehero.pngalready: never overwrite it, and don't convert that original. Name those files in the report instead. - Save each result next to its original under the new extension, checking its size against
optimized.sizeas in step 5, then update the references.
When a file's name appears nowhere in the project, the site may build its path at runtime or load it from a CMS. List those files for the user rather than guessing, and never delete their originals.
A conversion keeps one frame of an animation unless it is an animated GIF converted to WebP: an animated WebP or PNG converted to any format, or a GIF converted to PNG, JPG or AVIF, comes back as a still of its first frame. Tell the user before converting one.
7. Alt text, when asked
The images already carry their alt text, so don't send them again. Follow the image-seo skill in this plugin (load it if it isn't loaded): its step 1 to find and sort the places that show these files, then from its step 3 on with these images' ids. It waits for the alt text, puts it in the markup with the user's approval, and renames files only when asked.
8. Report
Finish with a table: each file, its size before and after, and what it saved in bytes and percent, then a line for the total. Under it, list the files left alone and why: already optimized, too large, refused, failed, or skipped at the user's request. With a size limit, also list the files made smaller in pixels to fit, with their new sizes, and the ones that came out over it. List the files that lost their Content Credentials too, saying when the record named generative AI (content_credentials.generative_ai). Call get_usage again and say how many images today's allowance has left.
Leave committing to the user. The uploads and their results also stay in the user's Iminify account, where the website lists them. Only delete them (delete_image, which can't be undone) when the user asks.