Sort the top-level files and folders of one directory into category subdirectories with Jeff — small local models that score each option you describe. One decision per entry, about 30 ms, nothing uploaded. This is a thin CLI over Jeff's HTTP API, so it needs a running Jeff server.
$ ./jeff_sort.py ~/Downloads --dry-run # print every decision, move nothing
Holiday Photos 2024/ -> images (96%)
Podcast episodes/ -> audio (97%)
$ ./jeff_sort.py ~/Downloads # move themThe server, from a checkout of github.com/firelex/jeff (JEFF_BACKEND=mlx is the
fast Apple-silicon path; drop it, and --extra mac, for NVIDIA or CPU):
git clone https://github.com/firelex/jeff && cd jeff
uv sync --extra mac
uv run hf download mstrasser/Jeff-Qwen3.5-0.8B --local-dir checkpoints/jeff-0.8b
JEFF_BACKEND=mlx JEFF_CHECKPOINT=checkpoints/jeff-0.8b PORT=8765 uv run jeff-serveThen, with uv — the script is standard library only, nothing to install:
./jeff_sort.py DIRECTORY [--dry-run] [--url URL] [--selftest]--dry-run first; it is the only way to see the confidence before anything moves. --url defaults to
http://127.0.0.1:8765.
audio, video, images, installers and archives, ebooks, personal documents, other — the CATEGORIES map
at the top of the script holds both these names and the wording Jeff actually reads, so edit it there.
- A file is judged on its name, MIME type and size, never its contents. A folder gets a one-level peek inside it and then moves whole.
- Highest probability wins. The printed confidence is how you spot the shaky calls; there is no confidence floor.
- Top level only, nothing recurses. Dotfiles and entries already named after a category are skipped, which makes
re-runs no-ops rather than nesting
audio/insideaudio/. - Nothing is ever overwritten: a taken destination name becomes
name (2).ext.