<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Erik Dallaqyan</title>
    <description>The latest articles on DEV Community by Erik Dallaqyan (@erdallaqyan).</description>
    <link>https://dev.to/erdallaqyan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4187158%2F67c4006e-b2c1-4ee8-81b2-09b99b9121cc.jpg</url>
      <title>DEV Community: Erik Dallaqyan</title>
      <link>https://dev.to/erdallaqyan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/erdallaqyan"/>
    <language>en</language>
    <item>
      <title>Sloth Sort: a local Gemma 3 that names your screenshots so you don't have to</title>
      <dc:creator>Erik Dallaqyan</dc:creator>
      <pubDate>Fri, 23 Oct 2026 15:53:16 +0000</pubDate>
      <link>https://dev.to/erdallaqyan/sloth-sort-a-local-gemma-3-that-names-your-screenshots-so-you-dont-have-to-4fla</link>
      <guid>https://dev.to/erdallaqyan/sloth-sort-a-local-gemma-3-that-names-your-screenshots-so-you-dont-have-to-4fla</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Open anyone's Desktop and you'll find something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Screenshot 2026-10-14 at 9.41.22 AM.png
Screenshot 2026-10-15 at 2.03.10 PM.png
download.pdf
document (1).pdf
Scan 2026-10-12.pdf
image (3).png
8f14e45fceea167a5a36dedd4bea2543.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every one of those was important enough to save, and not important enough to name. "I'll rename them later" is one of the great lies of computing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sloth Sort&lt;/strong&gt; does "later" for you. It watches &lt;code&gt;~/Desktop&lt;/code&gt; and &lt;code&gt;~/Downloads&lt;/code&gt;. When a file with a throwaway name shows up, &lt;strong&gt;Gemma 3 running on your own laptop&lt;/strong&gt; looks at it, works out what it is, and renames it the way a tidy person would have:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Screenshot 2026-10-14 at 9.41.22 AM.png  →  2026-10-24-skyline-air-boarding-pass-sfo-to-jfk.png
Screenshot 2026-10-15 at 2.03.10 PM.png  →  2026-10-15-python-billing-customer-id-error.png
download.pdf                             →  2026-10-02-acme-cloud-invoice-rivers-design-llc.pdf
Scan 2026-10-12.pdf                      →  2026-10-12-corner-hardware-oakland-paint-supplies-receipt.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Who gets to be lazier? Anyone who has ever typed "boarding pass" into Finder search and gotten nothing back, because the boarding pass is called &lt;code&gt;Screenshot 2026-10-14 at 9.41.22 AM.png&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The whole point is that you do nothing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It only touches throwaway names.&lt;/strong&gt; &lt;code&gt;Screenshot …&lt;/code&gt;, &lt;code&gt;IMG_4821&lt;/code&gt;, &lt;code&gt;download (2).pdf&lt;/code&gt;, hashes and UUIDs get renamed. &lt;code&gt;taxes 2025 notes.pdf&lt;/code&gt; is left exactly as you named it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dates go first&lt;/strong&gt;, so files sort by when things happened. That's the invoice date or flight date if one is printed on the file, otherwise when the file was saved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every rename can be undone.&lt;/strong&gt; A small review page on &lt;code&gt;127.0.0.1&lt;/code&gt; lists every change with one-click Undo and a box to retype the name. A file you undo is never touched again. Spotted junk while you're there? &lt;strong&gt;Delete&lt;/strong&gt; sends it to Sloth Sort's trash, and &lt;strong&gt;Restore&lt;/strong&gt; brings it back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It moves at a sloth's pace.&lt;/strong&gt; One file at a time, about 4 seconds each. The model unloads itself after 5 idle minutes, so it costs nothing while you aren't making clutter.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live page:&lt;/strong&gt; &lt;a href="https://erikdallaqyan.github.io/hacktoberfest-project/" rel="noopener noreferrer"&gt;https://erikdallaqyan.github.io/hacktoberfest-project/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press &lt;strong&gt;"Let the sloth do it"&lt;/strong&gt; to watch a messy Desktop get renamed one file at a time. The names are real Gemma 3 output for 12 made-up files, replayed in your browser.&lt;/p&gt;

&lt;p&gt;The review page, after tidying that Desktop:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc7hdgjlizicg408zocdt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc7hdgjlizicg408zocdt.jpg" alt="Sloth Sort review page: each row shows the old name crossed out, the new name, a one-line description, a category, and Undo and Rename buttons" width="800" height="785"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the red &lt;strong&gt;"shows a password or key"&lt;/strong&gt; flag on the API keys screenshot. Gemma noticed the keys and the page warns you, so that screenshot doesn't sit on your Desktop forever.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ErikDallaqyan" rel="noopener noreferrer"&gt;
        ErikDallaqyan
      &lt;/a&gt; / &lt;a href="https://github.com/ErikDallaqyan/hacktoberfest-project" rel="noopener noreferrer"&gt;
        hacktoberfest-project
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🦥 Sloth Sort&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Names your files so you don't have to.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your Desktop is full of &lt;code&gt;Screenshot 2026-10-14 at 9.41.22 AM.png&lt;/code&gt;. Your Downloads folder has &lt;code&gt;download.pdf&lt;/code&gt;, &lt;code&gt;document (1).pdf&lt;/code&gt; and &lt;code&gt;image (3).png&lt;/code&gt;. You'll rename them later. You won't.&lt;/p&gt;

&lt;p&gt;Sloth Sort watches those folders. When a file with a throwaway name lands there, an open-weight model running on your own computer (Google's &lt;strong&gt;Gemma 3&lt;/strong&gt;, through &lt;strong&gt;Ollama&lt;/strong&gt;) looks at it, figures out what it is, and gives it a name a tidy person would have picked:&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;Screenshot 2026-10-14 at 9.41.22 AM.png  →  2026-10-24-skyline-air-boarding-pass-sfo-to-jfk.png
Screenshot 2026-10-15 at 2.03.10 PM.png  →  2026-10-15-python-billing-customer-id-error.png
download.pdf                             →  2026-10-02-acme-cloud-invoice-rivers-design-llc.pdf
Scan 2026-10-12.pdf                      →  2026-10-12-corner-hardware-oakland-paint-supplies-receipt.pdf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nothing leaves your computer. Screenshots are full of bank balances, private chats and API keys, so the model runs locally, and the review page is only reachable from your own machine.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ErikDallaqyan/hacktoberfest-project/docs/img/review.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FErikDallaqyan%2Fhacktoberfest-project%2FHEAD%2Fdocs%2Fimg%2Freview.jpg" alt="Sloth Sort review page"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Watches&lt;/strong&gt; &lt;code&gt;~/Desktop&lt;/code&gt; and &lt;code&gt;~/Downloads&lt;/code&gt; (or any folders…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ErikDallaqyan/hacktoberfest-project" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Running it takes three commands:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull gemma3:4b
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python &lt;span class="nt"&gt;-m&lt;/span&gt; slothsort        &lt;span class="c"&gt;# then open http://127.0.0.1:8765&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Not ready to let it loose? &lt;code&gt;python -m slothsort scan ~/Downloads&lt;/code&gt; previews every name without touching a file.&lt;/p&gt;
&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The open pieces:&lt;/strong&gt; Google's open-weight &lt;strong&gt;Gemma 3&lt;/strong&gt; (4B) does the looking, served locally by &lt;strong&gt;Ollama&lt;/strong&gt;. Everything around it is plain Python: FastAPI for the review page, &lt;code&gt;watchdog&lt;/code&gt; for folder events, &lt;code&gt;pypdfium2&lt;/code&gt; for PDFs, Pillow for images, and SQLite for the undo journal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pipeline for each file:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Watch.&lt;/strong&gt; &lt;code&gt;watchdog&lt;/code&gt; reports new files in the watched folders. macOS writes screenshots as a hidden temp file and then renames them, so "moved" events count too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter.&lt;/strong&gt; About a dozen regexes decide whether a name is a throwaway (&lt;code&gt;Screenshot …&lt;/code&gt;, &lt;code&gt;IMG_1234&lt;/code&gt;, &lt;code&gt;download (2)&lt;/code&gt;, hashes). Anything else is left alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wait.&lt;/strong&gt; A file that is still downloading keeps growing. Sloth Sort waits until the size stops changing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look.&lt;/strong&gt; Images are shrunk to 896 px, which is the resolution Gemma 3 sees at anyway. For PDFs, Gemma gets the first page as an image &lt;em&gt;and&lt;/em&gt; the text layer. Phone scans have no text at all, so the image does the work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask.&lt;/strong&gt; One call to Gemma, constrained by a JSON schema, returns a sentence describing the file, a 3–7 word title, a category, the date printed on the file, and whether a secret is visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide in code.&lt;/strong&gt; Plain Python builds the filename (more on this below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Journal, then rename.&lt;/strong&gt; The rename is written to SQLite &lt;em&gt;before&lt;/em&gt; the file moves, so Undo always knows where everything went.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  The model suggests, code decides
&lt;/h3&gt;

&lt;p&gt;This was the most important design choice. Gemma is great at reading a boarding pass. I don't want it in charge of my file system. So Gemma only suggests &lt;em&gt;words&lt;/em&gt;, and &lt;a href="https://github.com/ErikDallaqyan/hacktoberfest-project/blob/main/slothsort/naming.py" rel="noopener noreferrer"&gt;&lt;code&gt;naming.py&lt;/code&gt;&lt;/a&gt; decides what a filename is allowed to contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No account numbers, ever.&lt;/strong&gt; Any run of 5+ digits, any email and any phone number is scrubbed from every name the model suggests. Filenames end up in Spotlight, iCloud sync logs and email attachments, so &lt;code&gt;chase-statement-7730-2219-5541.pdf&lt;/code&gt; should never exist, whatever the model says.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dates only if they're really printed there.&lt;/strong&gt; Small models fill gaps confidently. A shopping cart that said "Free delivery Thu, Oct 29" came back as &lt;code&gt;2023-10-29&lt;/code&gt;. A chart titled "Q3 2026" came back as July 1. Now the model also has to copy the date &lt;em&gt;exactly as printed&lt;/em&gt;, and code accepts it only if that text has a day, a month and a year. Otherwise the file's own date is used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never overwrite.&lt;/strong&gt; A name clash becomes &lt;code&gt;-2&lt;/code&gt;, &lt;code&gt;-3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lowercase ASCII, 60 characters max, and the date isn't repeated&lt;/strong&gt; inside the title. &lt;code&gt;invoice-october-2-2026&lt;/code&gt; after a &lt;code&gt;2026-10-02-&lt;/code&gt; prefix was a real thing Gemma did.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are 57 tests for these rules and for the full rename → undo → retype → delete → restore flow, with the model stubbed out, so they run in CI without Ollama.&lt;/p&gt;

&lt;h3&gt;
  
  
  How good are the names?
&lt;/h3&gt;

&lt;p&gt;I generated 11 made-up files with headless Chrome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a boarding pass, a Python traceback and a group chat&lt;/li&gt;
&lt;li&gt;a rental listing, an API keys page and a shopping cart&lt;/li&gt;
&lt;li&gt;an invoice PDF, an electric bill and an image-only scan of a hardware store receipt&lt;/li&gt;
&lt;li&gt;a chart and a meme&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/ErikDallaqyan/hacktoberfest-project/blob/main/scripts/eval.py" rel="noopener noreferrer"&gt;&lt;code&gt;eval.py&lt;/code&gt;&lt;/a&gt; checks each suggested name for the right words, category, date and secret flag.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Gemma 3 4B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Passed every check&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9 / 11&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dates right&lt;/td&gt;
&lt;td&gt;11 / 11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret flag right&lt;/td&gt;
&lt;td&gt;11 / 11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median time per file&lt;/td&gt;
&lt;td&gt;4.1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two misses are honest ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The meme&lt;/strong&gt; became &lt;code&gt;lazy-desktop-collection&lt;/code&gt; (no "sloth" or "meme" in the name).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The signups chart&lt;/strong&gt; got the category "other" instead of "work".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both names are still something you'd find in a search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Look at what's actually in a Downloads folder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bank statements&lt;/li&gt;
&lt;li&gt;utility bills with your home address&lt;/li&gt;
&lt;li&gt;screenshots of private group chats&lt;/li&gt;
&lt;li&gt;a settings page with your API keys still on it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A cloud vision API would need &lt;strong&gt;every one of those uploaded&lt;/strong&gt; just to suggest a filename. That's a terrible trade for a tool whose whole job is to run on everything, all the time, without you looking. A local open-weight model makes the trade disappear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your files never leave your laptop.&lt;/strong&gt; Gemma runs through Ollama on &lt;code&gt;localhost&lt;/code&gt;, and the review page only listens on &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs $0 to run, forever.&lt;/strong&gt; "Set it up once and never think about it again" is the lazy dream, and that only works if there isn't an API bill growing in the background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works offline.&lt;/strong&gt; On a plane, which is exactly when you screenshot your boarding pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could pick the model size myself.&lt;/strong&gt; This one mattered most this week. I started with Gemma 3 &lt;strong&gt;12B&lt;/strong&gt;. It names slightly better, but on my 16 GB laptop it took about 10 seconds per file and made the whole machine lag. That's unacceptable for something that's supposed to run quietly in the background. Switching to &lt;strong&gt;Gemma 3 4B&lt;/strong&gt; was a single setting: &lt;code&gt;SLOTH_MODEL=gemma3:4b&lt;/code&gt;. It's about twice as fast, uses about 3 GB of memory instead of 8, and still gets every date right. With a closed API you get whatever model they serve, at whatever price they set.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Lazy Payoff
&lt;/h2&gt;

&lt;p&gt;Renaming a screenshot by hand means opening it, figuring out what it is, picking a name, typing it and fixing the date. Call it 20–30 seconds, if you ever actually do it. Sloth Sort does it in about 4 seconds of &lt;em&gt;machine&lt;/em&gt; time, and zero seconds of yours.&lt;/p&gt;

&lt;p&gt;The bigger payoff is search. Spotlight can finally find "boarding pass", "invoice" or "keyerror", because those words are now in the filenames.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma 3 is the whole engine: it reads screenshots, photos and image-only PDF scans locally through Ollama. Its vision support is what made a single model enough for every file type. Its range of sizes is what let me drop from 12B to 4B the moment my laptop complained.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
