Using Ebb
Install it once, then mostly forget it: Ebb scans every day and tells you when there's something worth clearing. This page covers the app, the ebbctl command, and every setting.
Install
# macOS 14 or later, Apple silicon or Intel
curl -fsSL https://ebb.serveka.com/install.sh | sh
The script downloads the latest GitHub release, checks its SHA-256 checksum and code signature, and then:
- puts Ebb.app in
/Applications(or~/Applicationsif that isn't writable); - links the
ebbctlandebbcommands into/usr/local/bin(or~/.local/bin); - opens Ebb.
ebb. The installer never replaces it, and ebbctl is always Ebb. Everywhere below, ebb and ebbctl are the same program.Without the script
Download Ebb.zip from the latest release and drag Ebb.app to Applications. Ebb isn't notarized yet, so the first time, right-click Ebb.app and choose Open. To build from source, see the README.
First run
- Welcome. What Ebb does and what it promises not to do.
- Full Disk Access. macOS hides parts of
~/Libraryand some tool folders from apps. Click Open System Settings, find Ebb under Privacy & Security › Full Disk Access, and switch it on. Ebb notices and carries on by itself. You can also choose Scan without it; Ebb then skips what it can't see. - First scan. About a minute, read-only. At the end, switch on Scan once a day to get the daily notification, and allow notifications when macOS asks.
Update and uninstall
Run the install command again to update; it replaces the app and keeps your settings.
# remove everything: app, commands, daily agent, settings, history, permissions curl -fsSL https://ebb.serveka.com/uninstall.sh | sh # remove the app but keep settings, rules and history for later curl -fsSL https://ebb.serveka.com/uninstall.sh | sh -s -- --keep-data
Nothing Ebb moved to the Trash is touched by uninstalling.
The daily notification
With Scan once a day on, Ebb checks every hour whether today's scan is due. It scans once a day:
- after a start time picked at random between 10:00 and 18:00 when you turn the agent on;
- only once the Mac has been idle for two minutes;
- on power, or on battery above 30% (you can change the limit);
- and it catches up after the Mac slept through the start time.
It notifies you only when at least 2 GB can be cleared, the amount grew since the last notification (or you cleaned since), and it hasn't notified you today. Click the notification to open the review screen. The scan never cleans anything.
The window
| Pane | What it's for |
|---|---|
| Overview | Free space, safe to clear, worth a look, freed this month, and the top recommendations. |
| Recommendations | Every item with a checkbox, its path, why it's safe and whether it regenerates. Filter by Regenerates or Needs a look. The side panel shows the selected total and the Move to Trash button. |
| Active projects | Every project Ebb is protecting, with the signals behind it, and Always keep. |
| History | Every clean, with Put back, and how much from Ebb cleans is still in the Trash. |
| Settings | See Settings. |
Pre-selected items regenerate on their own (reinstall, rebuild, refill). Items that can't come back, like old AI chats or duplicates, are listed but never pre-selected. Items cleared by a tool's own command, like the pnpm store, show the command; Ebb doesn't run it.
Always keep
On the Active projects pane, click Always keep on a project. In Recommendations, right-click an item and choose Always keep. A kept project counts as active forever; a kept path is never suggested, nor anything inside it. Unpin it from Settings › Never touch.
History, put back and emptying
Before moving anything, Ebb checks each item again: still there, not open in an app, not changed in the last 24 hours, not in a project that became active. Then it moves it to the Trash and writes it to History. From History, Put back restores one item or a whole clean, and never overwrites something new at the old path.
The space comes back once the Trash is emptied. Empty Ebb's items (in History, or in the menu bar panel when something is waiting) removes for good only what Ebb moved there; anything you put in the Trash yourself stays. It asks first, and emptied items can't be put back. From the terminal: ebbctl empty-trash.
Command line
ebbctl uses the same engine and the same settings as the app. Commands that look for projects share these options:
| Option | What it does |
|---|---|
--in <dir> | Folder to search for projects. Repeatable. Default: your home folder (or your Settings). |
--active-days <days> | Activity this recent makes a project active. Default: your setting, 14. |
--library-caches | Also look at app caches in ~/Library/Caches. |
--all-volumes | Follow folders onto external disks and network volumes. |
-q, --quiet | Don't show the progress line. |
Progress goes to stderr and only on a terminal, so --json output stays clean in pipes.
ebbctl scan
What's safe to clear, by category. Read-only; this is also what ebbctl runs with no command.
ebbctl scan ebbctl scan --details # every item with its reason, and everything skipped ebbctl scan --json > scan.json # the full result, for scripts ebbctl scan --in ~/code --active-days 30
"Safe to clear" is the total of pre-selected items. "Worth a look" is everything else. ~ before a size means it's an estimate, usually because files are shared with something else (pnpm does this).
ebbctl clean
Scans, shows what would go, asks, then moves items to the Trash and logs them.
ebbctl clean --dry-run # show, move nothing ebbctl clean # the pre-selected items, after asking ebbctl clean --select node-leftovers,python-venvs ebbctl clean --select old-installers --yes # no question (scripts)
--select takes category ids from ebbctl scan and includes items that aren't pre-selected. Without a terminal, clean refuses unless you pass --yes. Items that need a tool's own command are listed with it instead of being moved.
ebbctl active
Every project Ebb considers active, and the signals that made it so.
ebbctl active ebbctl active --json
ebbctl history · undo · empty-trash
history lists every clean with its id, size and what's still in the Trash. undo puts a clean back; without an id it's the latest one. empty-trash removes Ebb's items from the Trash for good, after asking (-y to skip the question).
ebbctl history ebbctl undo # the latest clean ebbctl undo 20261026-082535-760b # a specific clean ebbctl undo 20261026-082535-760b --path ~/code/site/node_modules ebbctl empty-trash # only Ebb's items; asks first
ebbctl rule
Add, list and remove custom rules.
ebbctl rule add '\.mov$' --in ~/Movies --older 90d --larger 500MB --name "Old recordings" ebbctl rule add '^client-' --in ~/code --never-touch ebbctl rule list ebbctl rule remove d940aee9
| Option | What it does |
|---|---|
<regex> | The pattern (ICU syntax, like NSRegularExpression). Matches anywhere unless anchored with ^ and $. |
--in <dir> | Folder to look in. Repeatable. Required unless --never-touch. |
--older <age> | Only files unchanged this long: 14d, 2w, 3m, 1y. |
--larger <size> | Only files at least this big: 50MB, 1.5GB. |
--never-touch | Protect matches from every category and rule, instead of suggesting them. |
--full-path | Match the whole path, not just the file name. |
--name <name> | A name to show. Default: the pattern. |
--comes-back <label> | no (default), redownload, or a how-to like "re-export from Final Cut". |
Settings
Open Ebb › Settings. Changes apply from the next scan, and the command line uses the same settings.
| Setting | Default | What it does |
|---|---|---|
| Scan once a day | off until first run | Installs the daily agent and sends the notification. |
| Notify when at least | 2 GB | Below this, no notification. 1, 2, 5, 10 or 20 GB. |
| On battery, only above | 30% | On battery, the agent waits until the charge is above this. |
| Show the size in the menu bar | on | The number next to the wave icon. |
| Open Ebb at login | off | Starts the menu bar app when you log in. |
| Active means used in the last | 14 days | 7, 14 or 30 days. |
| Signals | all on | Turn off any of: file edits, git, Claude Code, Codex, running processes, editors. |
| Look for projects in | ~ | Add or remove folders. |
| Never look in | none | Folders to skip completely. |
| External disks | off | Include external disks and network volumes. |
| App caches | off | Include ~/Library/Caches in temp files. |
| Categories | all on | Turn any category off. |
Custom rules
Rules add your own patterns. Two kinds:
- Suggest rules list matching files under "worth a look". They're never pre-selected, and every built-in check still applies: the never-touch list, active projects, changed in the last 24 hours, open files.
- Never-touch rules protect matches from every category and every other rule. A match anywhere inside a folder protects the whole folder.
Add them with ebbctl rule add, or edit the file directly:
{
"version": 1,
"rules": [
{
"id": "3f2a9c1e",
"name": "Old screen recordings",
"pattern": "^Screen Recording .*\\.mov$",
"matchOn": "fileName",
"roots": ["~/Desktop", "~/Movies"],
"olderThanDays": 14,
"largerThanMB": 50,
"action": "suggest",
"comesBack": { "kind": "no" }
}
]
}
Invalid patterns are refused when added, and skipped with a note if edited in by hand. Each rule gets 15 seconds per folder, so a bad pattern can't hang a scan. Never-touch rules count as matching if they can't decide in time.
Files Ebb keeps
Everything lives in ~/Library/Application Support/Ebb/, on your Mac only.
| File | What's in it |
|---|---|
settings.json | Your settings, including Always keep. |
rules.json | Custom rules. Copy it to another Mac to bring your rules along. |
history.json | Every clean: what moved, from where, to where in the Trash. |
last-scan.json | The latest scan, so the menu bar has a number right away. |
agent-state.json | When the agent last scanned and notified. |
The daily agent is ~/Library/LaunchAgents/com.serveka.ebb.agent.plist; its log is ~/Library/Logs/Ebb/agent.log.
How Ebb decides
A project is the outermost folder with a marker: .git, package.json, pyproject.toml, requirements.txt, Cargo.toml, Package.swift, go.mod, build.gradle or an .xcodeproj. It's active, and skipped completely, if within activeDays:
| Signal | What Ebb looks at |
|---|---|
| File edits | A file changed outside build folders like node_modules and dist. |
| Git | .git/HEAD, index or logs/HEAD changed. |
| Claude Code | A session in ~/.claude/projects for the folder. |
| Codex | A session in ~/.codex/sessions whose working folder is inside it. |
| Running | A process has its working folder inside it right now. |
| Editor | A recent workspace in VS Code, Cursor, Windsurf, Kiro, Antigravity or Xcode. |
| Pinned | You chose Always keep. |
ebbctl active shows which signals fired for each project.
Categories
| Id | What | Rule |
|---|---|---|
node-leftovers | node_modules, .next, .turbo, .parcel-cache, dist, build | Project inactive |
python-venvs | .venv, venv, env with pyvenv.cfg | Project inactive |
build-outputs | Rust target, Swift .build, Gradle | Project inactive |
package-caches | npm, pnpm, Yarn, Bun, pip, Homebrew, CocoaPods, Go | Always eligible |
xcode | DerivedData, old device support, unavailable simulators | Project inactive or gone |
docker | Untagged images, unused build cache | Not used by a container |
temp-files | $TMPDIR, optionally ~/Library/Caches | 3 days untouched |
old-installers | .dmg, .pkg, downloaded .zip in Downloads | Older than 30 days |
claude-code-history | Old Claude Code chats | Older than 30 days* |
agent-history | Codex, Gemini CLI, Cursor | Older than 30 days* |
local-models | Ollama, LM Studio, Hugging Face | Not used in 30 days* |
xcode-archives | .xcarchive | Older than 90 days* |
duplicates | Identical files in Downloads, Desktop, Documents | Oldest copy kept* |
* Never pre-selected. Contributors can add categories in categories.json.
Never touched
No category or rule can override this list: .git, .env*, .credentials*, .netrc, .npmrc, key files, keychains, CLAUDE.md, AGENTS.md, GEMINI.md, ~/.claude/settings*.json, Claude Code memory, ~/.codex/config* and auth.json, ~/.ssh, ~/.gnupg, ~/.aws, ~/.kube, the Trash, /System, /usr, and other users' home folders. Ebb also skips anything changed in the last 24 hours, anything open in an app, symlinks, other disks, and folders it can't fully read.
Troubleshooting
I can't see Ebb in the menu bar
On a MacBook with a notch, macOS hides menu bar items that don't fit, and Ebb's can end up behind the notch. Open Ebb from Applications or Spotlight to get its window any time. To bring the item back, quit a few menu bar apps or hold ⌘ and drag Ebb's icon to the right; turning off Show the size in the menu bar in Settings also makes it narrower.
"ebb" runs something else
That's MacTeX's ebb. Use ebbctl.
No notifications
Check that Scan once a day is on, and that Ebb is allowed in System Settings › Notifications. The agent only notifies when at least your threshold can be cleared and it grew since last time. To try a run now: /Applications/Ebb.app/Contents/MacOS/Ebb --agent --now.
Something I expected isn't listed
Run ebbctl scan --details. The skipped list says why each item was left out, and ebbctl active shows which project signals fired.
Something I need was moved
Open History and click Put back, or run ebbctl undo. Please also report it privately so it can't happen again.