Amphetamine
A Rust CLI that frees memory and reduces CPU contention on Apple Silicon, so your editor gets the machine's attention.
It closes only the apps you have named, clears only caches that belong to installed applications, and refuses to make any change it cannot undo.
What it actually does, and what it can't
Being straight about this matters more than the pitch:
There is no overclocking. Apple Silicon exposes no user-space clock control
— no cpufreq, no multiplier, nothing, SIP enabled or not. The only performance
selector is the Energy Mode in System Settings, and amph status will tell you
if you are not already on High Power. If nothing is thermally throttling you,
there is no clock speed to win back, and any tool claiming otherwise is lying.
What is real is contention and memory pressure:
| Lever | Mechanism | Honest effect |
|---|---|---|
| Closing apps | Quit request, as if you pressed Cmd-Q | Frees RAM and, critically, drains swap |
| Clearing caches | Age-gated file deletion | Frees disk, reduces cache-warming I/O |
| Deprioritising | nice |
Rival processes get a weaker claim on the CPU |
| Idle-sleep hold | IOKit power assertion | Long builds are not interrupted |
Swap is usually the real culprit. Free RAM is a poor health signal; swap is
a better one. Pages pushed to swap during past pressure stay there, and every
touch of one is a disk read. Swap only drains as the processes owning those
pages exit — no amount of free RAM, and no purge, brings it back. That is why
closing apps helps even on a 128 GB machine.
nice is not efficiency-core parking. PRIO_DARWIN_PROCESS, the mechanism
behind taskpolicy -b, silently does nothing when aimed at another process on
current macOS: it reports success and changes no state. Amphetamine uses nice,
which measurably works, and does not claim to do more.
Install
Requires macOS on Apple Silicon and a recent stable Rust.
Use
The first run writes ~/.config/amphetamine/config.toml with empty lists and
changes nothing. That is the design, not an oversight: Amphetamine closes an app
only after you name it.
Choosing what to close
You never have to open the config by hand.
amph pick opens a checklist of running apps pre-ticked to match your current
config, so it reads as an editor rather than a one-way import. Apps that are
protected are never offered, and entries for apps that aren't running are left
untouched rather than silently dropped.
Names match a bundle ID, an app name, or the last component of a bundle ID,
case-insensitively, and are stored using the app's own spelling — amph add slack records Slack. Adding something the built-in denylist protects is
refused with the reason rather than accepted as a line that could never fire:
$ amph add Cursor Docker
close list
! Cursor not added — protected (Cursor) — it would never be touched
! Docker not added — protected (Docker) — it would never be touched
Edits are made in place with toml_edit, so the config's explanatory comments
survive; each result is parsed back and validated before it replaces the file,
and the swap is atomic, so a failed edit leaves the previous config whole.
For hand-editing, amph config edit opens $EDITOR and amph config raw
prints the file as-is.
[]
= ["Slack", "Spotify", "Messages"]
[]
= 7
[]
= ["Spotify", "Dropbox"]
= 10
Permissions
Closing apps, clearing your caches, and reading every statistic here need no privileges at all, and there is no daemon.
Focus mode needs exactly one grant, because of a macOS asymmetry: any user may raise a process's nice value, but only root may lower it again. Without help, deprioritising an app would be a one-way door — so Amphetamine refuses to demote anything until it can prove it can undo it.
amph setup asks for your password once and installs:
you ALL=(root) NOPASSWD: /usr/bin/renice 0 -p [0-9]*
The 0 is literal. The rule can only ever return a process to normal
priority: it cannot deprioritise anything, cannot run any other command, and
cannot open a shell. It is validated with visudo -c before installation —
a malformed file in /etc/sudoers.d can lock you out of sudo entirely.
Inspect it with amph setup --print, remove it with amph setup --remove.
How it stays safe
A hard-coded denylist that config cannot override. The OS and your session (WindowServer, Finder, Dock, loginwindow), your work surface (Cursor, every common terminal), and everything holding mutable state (all VMs, Docker, Parallels, UTM) are refused even if you name them explicitly. Killing a VM abruptly can corrupt its disk image, so Amphetamine reports those and stops.
Apps are asked, never killed. Closing goes through
NSRunningApplication::terminate, the same quit request as Cmd-Q. The app runs
its normal shutdown and is free to put up an unsaved-work sheet. If it does not
exit within the timeout it is simply left running — a stalled app is usually one
asking you to save something. SIGKILL is never sent; --force exists and is
never implied.
Caches must prove they are caches. ~/Library/Caches is not one kind of
thing: alongside app caches it holds compiler and package-manager state that is
ruinously expensive to rebuild. On the machine this was developed against,
go-build alone was 91 GB and a dead-code analyser's cache was 39 GB. So the
rule is a positive test rather than a denylist that could never keep pace: a
bucket is eligible only if an application with that bundle identifier is
actually installed, per LaunchServices. Everything unrecognised is left
alone, which makes tools you install next year safe by default.
Then four more brakes: buckets holding live state (CloudKit, container manager)
are permanently excluded; IDEs and browsers are skipped by default; a cache
whose app is currently running is never touched, including its .ShipIt
updater; and only files untouched — by both modification and access time — for
min_age_days are eligible.
Deletion is narrow by construction. Only ~/Library/Caches and
~/Library/Logs, never configurable. Symlinks are never followed, and a
symlinked bucket is skipped whole. Every path is re-canonicalised and re-vetted
against its root immediately before removal, so nothing can escape through a
link swapped in mid-run. There is no recursive delete anywhere: files are
removed individually and directories only via remove_dir, which fails unless
already empty.
Nothing irreversible. Focus mode verifies its restore path before touching
a process, only demotes processes already at nice 0 (so restoring to 0 is exact
rather than a guess), and restores on exit. If a session is killed outright,
amph restore is the safety net.
Tests
44 tests, run against the live machine rather than mocks. The ones that matter assert refusals: that the built-in denylist beats an explicit close request, that a symlink cannot lead a delete outside its root, that build-tool caches are never touched, that nothing is demoted without a proven way back, and that lowering a nice value without privileges still fails — the last one is a canary; if it ever passes, the sudoers grant is no longer needed.