My Downloads folder had 847 files in it. I know this because I counted. Actually, the script counted. I just stared at the number and felt something between shame and recognition.
PDFs from 2021. Screenshots of things I don't remember. Eleven different installers for the same app. A file called final_FINAL_v3_USE_THIS.zip.
So I looked at CleanMyMac. $90 a year. I looked at a file organizer on the Mac App Store. $42 one-time.
Then I wrote a script. 85 lines. It runs in about two seconds. It's been doing its job every night for a month.
Here it is.
What the Script Does
Plainly:
- Scans your Downloads folder
- Sorts files into folders by type: Images, Documents, Archives, Audio, Video, Installers, Other
- Skips files younger than 7 days (because you probably still need them)
- Moves the rest into a dated subfolder so nothing gets lost
- Prints a one-line summary: how many files moved, and where
No config file. No daemon. One script. One command.
The Full Script
MIT licensed. Copy it. Run it. Break it. It's yours.
# tidy.py — organize your Downloads folder.
# Built by Spike Foundry. MIT license.
import os, shutil, time
from pathlib import Path
from datetime import datetime
DOWNLOADS = Path.home() / "Downloads"
AGE_DAYS = 7 # skip anything newer than this
ARCHIVE = DOWNLOADS / "_archive" / datetime.now().strftime("%Y-%m-%d")
BUCKETS = {
"Images": {".png", ".jpg", ".jpeg", ".gif", ".webp", ".heic", ".svg"},
"Documents": {".pdf", ".doc", ".docx", ".txt", ".md", ".rtf", ".pages"},
"Sheets": {".csv", ".xls", ".xlsx", ".numbers"},
"Archives": {".zip", ".tar", ".gz", ".7z", ".rar"},
"Audio": {".mp3", ".wav", ".m4a", ".flac", ".aac"},
"Video": {".mp4", ".mov", ".avi", ".mkv", ".webm"},
"Installers": {".dmg", ".pkg", ".exe", ".msi", ".deb", ".rpm"},
"Code": {".py", ".js", ".ts", ".go", ".rs", ".sh", ".json"},
}
def bucket_for(ext: str) -> str:
for name, exts in BUCKETS.items():
if ext.lower() in exts:
return name
return "Other"
def is_old_enough(p: Path) -> bool:
age = time.time() - p.stat().st_mtime
return age > AGE_DAYS * 86400
def tidy() -> None:
if not DOWNLOADS.exists():
print(f"no Downloads folder at {DOWNLOADS}")
return
moved = 0
by_bucket: dict[str, int] = {}
for item in DOWNLOADS.iterdir():
if item.name.startswith("."): continue
if item.is_dir(): continue
if item.parent.name == "_archive": continue
if not is_old_enough(item): continue
bucket = bucket_for(item.suffix)
dest_dir = ARCHIVE / bucket
dest_dir.mkdir(parents=True, exist_ok=True)
dest = dest_dir / item.name
n = 1
while dest.exists():
dest = dest_dir / f"{item.stem} ({n}){item.suffix}"
n += 1
shutil.move(str(item), str(dest))
moved += 1
by_bucket[bucket] = by_bucket.get(bucket, 0) + 1
if moved == 0:
print("nothing to tidy.")
return
print(f"moved {moved} files into {ARCHIVE}")
for name, count in sorted(by_bucket.items()):
print(f" {name:<11} {count}")
if __name__ == "__main__":
tidy()
How the Three Key Parts Work
The buckets. A plain dictionary of extensions. Easy to extend. If you want a "Fonts" bucket, add one line.
The age check. The script ignores anything touched in the last 7 days. This is the single most important line. Real people need their recent downloads. Change AGE_DAYS if you want a longer or shorter window.
The dated archive. Files don't go to a random pile. They go to _archive/2026-02-03/, grouped by type. If you ever need to find something, you know exactly when it moved.
What It Looks Like When You Run It
$ python3 tidy.py
moved 412 files into /Users/operator/Downloads/_archive/2026-02-03
Archives 38
Code 11
Documents 114
Images 203
Installers 7
Other 25
Sheets 4
Video 10
412 files. Two seconds. Downloads folder now has 435 items that actually matter. I can finally see the Desktop behind the dock badge.
Why Pay $42 for Something That Takes 85 Lines?
A paid file organizer does roughly this. Plus a dashboard. Plus an onboarding screen. Plus an update notifier.
Some of that is worth money to some people. That's fine. But I'd rather own the 85 lines. I know exactly what it does. I can change it in three minutes. It will still work in ten years because it's just Python and a folder.
The math:
- CleanMyMac: $90/year
- File organizer app: $42 one-time
- This script: free, yours, modifiable
How to Extend It
A few small variations I've shipped on top of this:
- Schedule it.
cronon Mac/Linux, Task Scheduler on Windows. Set it to 3 AM. - Log to a file. Add a
with open(...) as faround the summary so you have a history. - Auto-delete after 90 days. One more loop over
_archivethat nukes anything older than a threshold. - Notify on Mac. One line using
osascript -e 'display notification ...'.
What Is This Series
This is the first post in Scripts, Not Subscriptions. Every post ships a small, working, MIT-licensed script that replaces software people normally pay for every month.
Not because paid software is evil. Because a lot of it is charging a subscription for something you can own in 85 lines.
Run it. Break it. Change it. Send back the version you made better.
That's it. That was the script.
— next ticket up.
— The Operator