This is a submission for the Hacktoberfest Open-Source AI Challenge: Week 3, Slow and Steady.
What I built
Slothshot sorts the screenshots piling up on your computer while you are not using it. Overnight, while the laptop is idle and plugged in, entirely offline, on Windows, macOS and Linux.
You have hundreds of files called Screenshot 2026-08-04 at 6.00.00 p.m..png. You know one of them has the booking number. Finding it means opening them one at a time.
An open-weight vision model reads each one on your own machine, gives it a name you can recognise, files it by what it is, and leaves a Review pile for the duplicates and the things that have expired.
I was not first to this idea β sloth-sorter by ghalmeidadev does it in about sixty lines of Python, and SlothSort by amit_rawal_93de441d25a95a tidies a folder while you nap. Both are lovely, and both are the right size for what they do.
I went a different way, because of what is actually in my screenshots.
The part that made this hard
When I labelled my own screenshot folder to build a test set, I found a payment-processor console showing a customer's email address next to card BIN and last-four fields. Also a password manager, a bank balance, and an SMS with a verification code.
That is not a privacy footnote. That is the product.
Sending those to an API to be described would be the worst thing this app could do, so a local model is not a cheaper way to build it β it is the only honest way. But it also raises the bar on everything else. An app you point at a folder like that has to be trustworthy in ways a sixty-line script does not have to be.
So most of the work was not "make the model describe a screenshot". It was:
It never deletes a file. Not duplicates, not expired items, not anything. They move to Review, where you decide. This is enforced by structure, not by care: the module that moves files contains exactly two filesystem operations β create a directory, move a file. No delete, no overwrite, no truncate anywhere in it. The only removal in the whole codebase is a single rmdir, in undo, on a folder Slothshot created, and only when it is already empty.
"Never delete a user's file" is the kind of promise that erodes through one reasonable-looking special case. If the module has no way to express deleting, it cannot be broken by accident.
Every change is written down before it happens. A journal row is committed, then the file moves, then the row is marked applied. A crash in between leaves a record undo can check against the disk, rather than a move nobody recorded.
Undo is all-or-nothing. One file, one run, or everything. Every change is verified against the filesystem first, and if any one of them cannot be reversed, nothing moves and you are told so. When it says "Nothing was changed", that is literally true.
Sensitive screenshots are sorted but never indexed. If a screenshot shows a password, a card number, bank details or a one-time code, its text is not stored at all. There is no "exclude sensitive" filter at query time β a filter is a line of code someone can forget, reorder, or route around with a new query path. Absence is not.
Searching for 482913, the verification code visible in one of my test screenshots, returns zero results. The model read it correctly. It is simply not in the index to be found.
Demo
28 screenshots in one folder, sorted in 101 seconds. This is the complete output, including the files it got wrong:
Slothshot/
βββ code/
β βββ 2026-08-22-hashing-code.png
β βββ 2026-09-30-uv-test-results.png
βββ document/
β βββ 2026-08-07-operating-summary.png
β βββ 2026-08-10-payment-details-credit-card.png
β βββ 2026-10-09-invoice-investment.png
βββ error message/
β βββ 2026-08-06-error-message-disk-full.png
β βββ 2026-10-08-404-page-not-found.png
βββ receipt/
β βββ 2026-09-06-northgate-market-grocery-receipt.png
β βββ 2026-09-11-casa-miranda-receipt.png
βββ ticket or booking/
β βββ 2026-09-21-the-pale-fountains-ticket.png
βββ Review/
βββ 2026-08-08-boarding-pass.png β the flight already departed
βββ 2026-09-28-hashing-code.png β a resized copy
βββ 2026-09-28-northgate-market-grocery-receipt.png β a byte-identical copy
The credit-card form landed in document rather than other. That is the 75% accuracy showing, and I left it in the README rather than cropping it out. It was still correctly flagged sensitive, which is the part that matters.
Then the morning card says: 28 sorted. 3 items waiting in Review. Deleting them yourself would free 65 KB. Slothshot never deletes anything, so that last line is information, not an offer.
Undoing the whole run:
restored 28 files
all back at the top level : True
same bytes, nothing lost : True
Slothshot folder gone : True
example restored name : Screenshot 2026-08-04 at 6.00.00 p.m..png
Original names come back exactly β down to the narrow no-break space (U+202F) macOS puts before p.m., which broke my first attempt at reading the folder and now has a test of its own.
The repo
RZEROSTERN
/
hacktoberfest-week4
Just another prototype for Hacktoberfest
Slothshot
Sorts the screenshots piling up on your computer while you are not using it. Entirely offline, on Windows, macOS and Linux.
You have hundreds of files called Screenshot 2026-08-04 at 6.00.00 p.m..png. You know one of them has the booking number. Finding it means opening them one at a time.
Slothshot reads each screenshot with an open-weight vision model running on your own machine, gives it a name you can recognise, files it by what it is, and leaves a Review pile for the duplicates and the things that have expired. It works overnight, while the laptop is idle and plugged in, and it gets out of the way the moment you come back.
Screenshots hold private chats, bank details and passwords. That is the whole reason this runs locally: nothing is uploaded, ever. The only time Slothshot touches the network is to download the model youβ¦
Installers for all three platforms, built and smoke-tested on their own CI runners: macOS .dmg 303 MB, Windows .exe 165 MB, Linux AppImage 204 MB and .deb 165 MB. All far under the 1.5 GB ceiling I set myself.
Choosing the model, and publishing the bad news
I benchmarked three vision models over 28 fixtures covering all ten categories:
| Model | Download | Peak RAM | Per screenshot | Accuracy | Sensitive missed |
|---|---|---|---|---|---|
qwen3-vl:2b-instruct |
1.9 GB | 2.86 GB | 3.6 s | 75% | 0 |
gemma4:e4b |
6.6 GB | 4.78 GB | 5.2 s | 89% | 0 |
gemma4:e2b |
4.6 GB | 4.50 GB | 3.0 s | 71% | 1 |
No model met every target. Exactly one fits the 3 GB memory budget. Exactly one clears the 85% accuracy gate. They are not the same model.
So Slothshot ships a choice instead of a verdict: qwen3-vl:2b-instruct is the default because it keeps the resource promise true, and Gemma 4 E4B is offered for machines with memory to spare, where it sorts noticeably better.
gemma4:e2b is not offered at all. It missed a screenshot containing bank details. That is a safety property, not a quality score, so a model that fails it is not a choice at any size. There is a test asserting it stays out of the catalog.
All 168 measured inferences returned valid JSON β zero retries β because the model is handed a JSON schema rather than asked politely for one.
Three numbers that were lies
The memory measurement said 0.03 GB. For a 1.9 GB model. Ollama's own process holds almost nothing β the model lives in a child process named llama-server, and "llama-server" does not contain the substring "ollama". My matcher only had the latter. Corrected: 2.71 GB, falling to 0.02 GB the moment a run ends.
Peak memory read lower at 1024px than at 768px, which is impossible. With a 30-second keep-alive, each model's first run overlapped the previous model still unloading. The harness now forces an unload and polls until the engine is idle.
A test passed on my Mac and failed on CI. It asserted a filename's date using astimezone() with no argument β which reads the machine's zone. The same instant is the 24th at UTC-6 and the 25th at UTC. This app is built on one machine and ships to three platforms in unknown timezones, so a test that silently depends on my locale is a bug that ships.
A number that looks like a triumph and is actually a bug is the worst kind, so all three are written up in the repo rather than quietly fixed.
The compatibility check I am proudest of
The wizard lets you name any model in the Ollama library, not just the recommended ones. A text-only model asked to describe a picture returns a confident, perfectly-shaped, entirely invented answer. Constrained JSON does not catch that β the shape is right and the content is fiction.
So the check does not ask the model whether it can see. It renders a token into a test image and demands the token back:
SLOTHSHOT
7314
A model that cannot read the image has no way to produce 7314. Both recommended models pass, in 6.4 s and 14.9 s.
The lazy payoff
Sorting 28 screenshots by hand β opening each one, deciding what it is, typing a name β is maybe 15 seconds each if you are quick and know what you are looking at. Call it 7 minutes.
Slothshot does it in 101 seconds, while you are asleep, and you do not have to be the one deciding.
The real payoff is not those 7 minutes. It is the folder you have been not-dealing-with for two years, which is 4,000 files and therefore about 17 hours you were never going to spend. That is the thing a tool that works while you are away is actually for.
Why open innovation matters here
Because of what is in a screenshot. Mine had a customer's email next to card fields. Sending that to an API to be described would be the single worst thing this app could do. A local open-weight model is not the cheap option; it is the only honest one.
Because you can check. "Nothing is uploaded" is worth exactly as much as your ability to verify it. Both local services bind to loopback, the environment handed to them is scrubbed so your own OLLAMA_* settings cannot leak in, cloud models are refused twice β by name, then again after install when Ollama reveals remote_host, at which point the model is deleted. You can read all of it.
Because it keeps working. No account, no subscription, no key to expire, no pricing change, no shutdown notice. It runs with the Wi-Fi off.
Because the model is yours to swap. The tag is read from local settings and never hardcoded. A better small vision model next month is a one-line change β and the benchmark that chose this one is in the repo, so you can run it on your own machine and disagree with me.
Because the honest answer was available. No model met every target. A closed service would have shipped the number that flattered it. Here the measurements, the model rejected for missing bank details, and the gap between 75% and my 85% goal are all written down.
What is not done
-
75% category accuracy, below my 85% goal. The misses cluster on
documentversus the more specific categories, and there are about 6 seconds per screenshot of unused headroom to spend on a better prompt. - The Windows and Linux installers have been built and smoke-tested in CI, but never installed on real hardware. That is why the 1.0.0 release is still a draft.
Built with Claude Code over one weekend, in eleven steps, twelve pull requests, GitFlow throughout. The full session transcript, including every wrong turn above, is here: Building Slothshot: from spike to signed-off release.
Top comments (0)