Name the folder.
Whisk keeps it clean.
A rule-driven tidying utility for the macOS menu bar. Downloads, Desktop, an inbox — give each one a few rules, and files get moved, renamed, aged into the Trash, or handed to your scripts. You stop doing it by hand.
- IMG_4231.heicmoved → Pictures/2026-08
- Screenshot 2026-08-25….pngscreenshot-2026-08-25.pngrenamed
- OldApp.dmgtrashed · 32 days old
- archive.zipran my-unpack
- invoice (3).pdfmoved → Documents/Invoices
- Whisk-universal.zipfresh — left alone
macOS 14+ · universal binary · unsigned for now — one xattr clears the first-launch block
Notice. Snapshot. Plan. Act.
Watching a folder is easy; trusting what you watched is not. Whisk treats every wake-up — a file event, an age timer, the safety-net rescan — as a reason to look again, never as the truth itself.
The folder is the truth
FSEvents coalesces, drops, and lies by omission. Acting on events directly means acting on a stale picture — so Whisk only ever acts on a fresh listing.
One function decides everything
Snapshot + rules in, planned actions out — no I/O inside. CI holds the planner at 100% region coverage, so every branch of the matching and budget logic is exercised before it ships.
Ordering is a feature
Rules run top to bottom; a file consumed by move or trash is skipped by later rules in the same sweep. Without that, two overlapping rules quietly fight over the same file.
Rules, not chores
One JSON5 file is the whole configuration — comments and trailing commas welcome, hot-reloaded on every save, yours to sync and version. A built-in editor covers the common cases — and saving from it preserves your comments — while a Settings window (⌘,) holds everything else.
// ~/.config/whisk/rules.json — reloaded whenever it changes { version: 1, targets: [{ path: "~/Downloads", rules: [ { id: "file-images", match: { name: { glob: "*.{png,jpg,heic}" } }, actions: [{ move: { to: "~/Pictures/Inbox/{date.modified:yyyy-MM}" } }] }, { id: "trash-old-installers", match: { all: [ { extension: ["dmg", "pkg"] }, { age: { olderThan: "7d" } }, ]}, actions: [{ trash: {} }] }, ], }], }
Compose, to any depth
Name globs and regexes, extensions, file/folder kind, size, and age
(created, modified, or added) — nested under all /
any / not.
Destinations that sort themselves
Move targets and rename templates take {name},
{ext}, and {date.modified:yyyy-MM}-style
tokens, with Finder-style conflict renaming built in.
Broken saves don't break Whisk
A rules file mid-edit is often invalid. On a parse error Whisk keeps the last good ruleset running, shows the error, and picks up your next save.
Built to refuse to make a mess
A tool that moves your files unattended has one job before all others: never surprise you. Everything Whisk does is previewable, budgeted, and reversible.
It can't chase its own tail
A cleanup tool's classic failure is reacting to its own writes forever. Whisk keeps a ledger of what it just wrote, cools down per file-and-rule, and refuses moves into the source's own path at plan time.
Your scripts, once approved
A rules file syncs between machines — a command you didn't write could arrive with it. New or changed commands are held until you approve that exact command from the menu, once.
Only the folders you list
No sandbox theater, no blanket disk access: macOS asks for each folder your rules target, and a denied folder shows a "No access" badge instead of failing silently.
A URL scheme and a log file
Whisk is a good citizen of your own automation: drive it from Shortcuts, Raycast, or a script, and read back exactly what it did.
| URL | Effect |
|---|---|
| whisk://sweep | sweep every target now |
| whisk://pause?minutes=60 | pause — for a while, or indefinitely |
| whisk://resume | resume sweeping |
| whisk://dry-run?enabled=true | toggle preview mode |
| Path | Holds |
|---|---|
| ~/.config/whisk/rules.json | your rules — the only file that's yours to edit |
| …/Whisk/activity.jsonl | every action, one JSON object per line |
| …/Whisk/approvals.json | the shell commands you've approved |
Two commands to a clean folder
macOS 14 (Sonoma) or later, Apple Silicon or Intel — one universal binary.
brew tap alexnodeland/tap
Newer Homebrew asks for a one-time brew trust alexnodeland/tap — it will tell you if yours does.
brew install --cask whisk
Builds are unsigned for now — if macOS blocks the first launch: xattr -dr com.apple.quarantine /Applications/Whisk.app
open -a Whisk
First launch seeds ~/.config/whisk/rules.json with a commented starter file, and macOS asks for access to the folders your rules touch.
Direct download
Grab Whisk-universal.zip from the
latest release,
drag to Applications, and clear quarantine once:
xattr -dr com.apple.quarantine /Applications/Whisk.app
It keeps itself fresh
Whisk checks for new releases daily and offers them in the menu — flip Install updates automatically and it handles the rest.
Build from source
Pure swiftc — no Xcode project to open.
just setup && just dev builds and launches;
just check runs the full CI gate, 100% coverage included.
A note on what it touches
Whisk acts only on the folders your rules list, asks macOS for consent to each one, and prefers reversible operations — the Trash over deletion, previews over surprises. It's a personal tool, shared as-is.