DEV Community

Cover image for I Taught a Local Gemma to Clean My Downloads Folder, Offline
Naeem Ahmed
Naeem Ahmed

Posted on

I Taught a Local Gemma to Clean My Downloads Folder, Offline

What I Built

My Downloads folder is a landfill. IMG_4821.heic, final_v3.docx, a LICENSE with no context, installers from months ago. Every few weeks I promise myself I'll sort it, and every few weeks I don't.

So I built lazy-tidy: a tiny Python script that sorts a messy folder into Documents, Images, Videos, Audio, Code, Archives, Installers and Other. It previews by default and only moves files when I pass --apply.

The "slow and steady" part: it uses the cheapest decision-maker first. Known extensions are sorted instantly with a lookup table. Only files the table can't place (Makefile, README, data.bin, photo.raw) are handed to a local open-weight model, Gemma 3 4B running through Ollama on my laptop.

Code: https://github.com/naeemahmedpnl/lazy-tidy

Demo

Before, a folder with 18 mixed files:

Preview first, and every line shows who made the decision, [extension] or [model]:

$ python tidy.py ~/tidy-demo
IMG_4821.heic  ->  Images/      [extension]
LICENSE        ->  Documents/   [model]
Makefile       ->  Code/        [model]
README         ->  Documents/   [model]
data.bin       ->  Archives/    [model]
photo.raw      ->  Images/      [model]
...
Done: 12 by extension, 6 by local model.
Preview only. Re-run with --apply to move files.
Enter fullscreen mode Exit fullscreen mode

After --apply:

How It Works

  1. Extension first. A dictionary maps common extensions to categories. It is instant, free, and never wrong. In my demo it handled 12 of 18 files without touching a model.
  2. Local model for the leftovers. Anything unknown goes to Gemma 3 4B via Ollama with a prompt that asks for JSON only:
r = ollama.chat(
    model="gemma3:4b",
    messages=[{"role": "user", "content": prompt}],
    format="json",
)
cat = json.loads(r["message"]["content"]).get("category", "Other")
return cat if cat in CATEGORIES else "Other"
Enter fullscreen mode Exit fullscreen mode
  1. Guardrails. If the model returns something invalid or the call fails, the file goes to Other. If a name already exists in the destination, the file gets a _dup suffix instead of being overwritten. Nothing moves unless I pass --apply.

Why Open Innovation Mattered Here

It works with no internet. I turned Wi-Fi off and proved it with a failing ping before running the script:

$ ping -c 2 google.com
ping: cannot resolve google.com: Unknown host
$ time python tidy.py ~/tidy-offline
...
Done: 6 by extension, 6 by local model.
python tidy.py ~/tidy-offline  4.067 total
Enter fullscreen mode Exit fullscreen mode

Twelve files, six of them classified by the model, in about 4 seconds on an M1 Pro, fully offline.

My file names stay on my machine. Downloads contains invoices, resumes and personal photos. Even with just file names, that is information I would rather not send to a server I don't control, just to decide that LICENSE is a document.

It costs nothing. No API key, no per-call billing, no rate limits. I can run it on a schedule forever.

It is swappable. The model is one line, MODEL = "gemma3:4b". Changing to another open model is a one-line edit, and I can compare how different models handle the awkward files.

A closed API would have worked for this task too. But for something that touches the contents of my personal folders and should run quietly in the background, running it locally was the better fit, not just a nice-to-have.

Honest Limitations

  • The model only sees file names, never contents. My test files were empty touch files, so this was purely name-based.
  • It makes mistakes. data.bin went to Archives, and Other would have been the better answer. It made the same choice in both runs.
  • The small model is a guess on ambiguous names. README and notes_2026 landing in Documents is reasonable, but it is a guess.
  • No undo. That is exactly why preview is the default. Only top-level files are handled, not subfolders.

What's Next

Read a short text snippet for ambiguous files, add an undo log, and run it on a schedule so the folder never gets bad again.

The Lazy Payoff

One command, a few seconds, no internet, and a folder I had ignored for months is tidy. Do the work once, then let the sloth handle it.

Top comments (0)