A tiny macOS menu-bar app that keeps your Mac awake — and, when you want it to, keeps it running even with the lid closed and unplugged.
Perfect for when you've got something that needs to keep going: a build, a local server, a long download, or a coding agent working away — but you want to shut the lid and slip your laptop into a bag.
CaffeinateCat lives in your menu bar as a coffee cup ☕️ that doubles as a battery gauge: the drink inside sits at your battery's level (full, 80, 60, 40, 20, empty) — orange while it's keeping the Mac awake, grey when off — and the cup steams in lid-close mode. It gives you two levels of "stay awake":
| Mode | Screen | Idle sleep | Lid closed |
|---|---|---|---|
| Keep Screen Awake | stays on | prevented | Mac sleeps |
| Keep Awake on Lid Close | stays on | prevented | stays awake (even on battery) |
"Keep Awake on Lid Close" is a superset of the other: when the lid is open it behaves exactly the same, and when you close the lid it keeps everything running.
On launch, Keep Screen Awake turns on automatically (Indefinite), so the app just works the moment you open it.
Clicking the menu bar icon drops down a panel; right-clicking it turns keep-awake on or off in one go (using whichever mode you used last).
Rows adapt to the state they describe: before the lid-close permission is installed, that switch reads Runs lid-closed · asks for admin once, and while the admin prompt is up, Waiting for administrator approval…. Check for Updates… names the new version instead once one is waiting, and is hidden entirely in a build without Sparkle. Remove Lid-Close Permission… shows only while the rule is installed.
The two switches are mutually exclusive — turning one on turns the other off. Each remembers its own duration, across relaunches too. Picking Custom reveals hours and minutes fields with stepper arrows (up to 23 h 59 m, starting at 2 h), so any duration is reachable; typing or clicking an arrow restarts the timer straight away.
While a timer runs, the remaining time shows next to the menu bar icon as well as in the panel, with the clock time it ends at. Timers are measured on a clock that keeps running while the Mac sleeps, so a run that spans a sleep ends when it should. When it expires, the Mac goes back to sleeping normally.
| Option | What it does |
|---|---|
| Allow display to sleep | Keeps the system awake but lets the screen turn off on its normal schedule. The first switch then reads Keep Mac Awake. |
| Turn off at 20% and 10% battery | Switches keep-awake off as the battery falls through 20% and again through 10%, so a Mac in a bag can't run itself flat. On by default. |
| Keep awake when app opens | Starts Keep Screen Awake at launch (the old always-on behaviour). On by default. |
| Open at login | Installs a small LaunchAgent in ~/Library/LaunchAgents. |
| Remove Lid-Close Permission… | Deletes the sudoers rule (shown only while it's installed). |
flowchart LR
U[You flip a switch] --> M{Which mode?}
M -- Keep Screen Awake --> A[ProcessInfo.beginActivity<br/>idle and display sleep held off]
M -- Keep Awake on Lid Close --> P[pmset -a disablesleep 1<br/>through a tightly scoped sudoers rule]
P --> W[Watchdog process<br/>restores sleep if the app dies]
A --> O[Turned off or quit]
P --> O
O --> R[Normal sleep restored and checked]
- Keep Screen Awake uses
ProcessInfo.beginActivitywith idle-system and idle-display sleep assertions. No special permissions needed. - Keep Awake on Lid Close sets the
SleepDisabledflag inIOPMrootDomainviapmset -a disablesleep 1. This is the only reliable way to keep an Apple Silicon Mac awake with the lid shut on battery power.
Because pmset needs root, the app installs a small, tightly-scoped sudoers rule the first time you enable lid-closed mode (or on first launch, if you opt in). This asks for your administrator password once via a native macOS prompt (Touch ID works too), and grants passwordless access to exactly these two commands and nothing else:
pmset -a disablesleep 1
pmset -a disablesleep 0
After that, the feature works silently with no more prompts. The rule lives at /etc/sudoers.d/caffeinatecat.
Safety:
disablesleeppersists across reboots, so the app goes to some lengths never to leave it set:
- It restores
disablesleep 0when you turn a mode off or quit, and onSIGTERM/SIGINT/SIGHUP— and reads the value back frompmset -gto confirm it took.- While lid mode is on, a tiny watchdog process in its own process group waits for the app to exit and restores sleep itself, so even a crash, Force Quit or
kill -9can't strand the flag.- If the app ever dies holding the flag anyway (power loss, kernel panic), it notices on next launch and restores it. If restoring fails, it tells you the one command to run.
- Every 30 seconds, and after each wake, it re-checks the flag is still set and the watchdog is still alive.
The sudoers rule is written, validated with
visudoand installed entirely by the privileged shell, into a root-owned temp file — nothing staged in a user-writable location can be swapped in between the check and the install.
CaffeinateCat is a handful of Swift files. Its one dependency is Sparkle, used for automatic updates; build.sh downloads a pinned, checksum-verified release and embeds it. A plain swiftc build works without Sparkle and simply has no updater (the "Check for Updates…" row is hidden).
For a quick local binary, with no updater:
swiftc -O -o CaffeinateCat *.swiftTo build a proper, universal CaffeinateCat.app into build/ (optimised, with Sparkle, icon and Info.plist):
sh build.sh # ad-hoc signed
IDENTITY="Developer ID Application: …" sh build.sh # hardened runtime, ready to notarize
IDENTITY="…" NOTARY_PROFILE=myprofile sh build.sh # also notarizes, staples and zipsVERSION and BUILD can be overridden the same way. BUILD is an integer that must go up with every release, because Sparkle compares it. FEED_URL points the app at a different update feed, which is handy for testing an update locally.
NOTARY_PROFILE is a notarytool keychain profile, created once with xcrun notarytool store-credentials.
With a real IDENTITY, the script also writes build/CaffeinateCat.xcarchive and copies it into Xcode's archive folder, so you can notarize by hand from Window → Organizer → Distribute App → Direct Distribution.
As a table
| File | What's in it |
|---|---|
main.swift |
Entry point |
CaffeinateCat.swift |
App delegate: modes, timers, safety checks, menu bar item, alerts |
Power.swift |
pmset, the sudoers rule, the crash watchdog, battery monitoring |
Settings.swift |
Saved preferences and the open-at-login agent |
Updater.swift |
Sparkle: update checks and the "update available" flag |
PanelController.swift |
The popover — anchoring, sizing, dismissal |
PanelView.swift |
Panel layout |
Controls.swift |
Custom-drawn switch, segmented control, duration fields, menu and option rows |
Theme.swift |
Colours, fonts, and metrics |
build.sh |
Builds, signs and optionally notarizes the .app without Xcode; embeds Sparkle |
assets/icon.png |
The app icon, which build.sh turns into an .icns |
tools/ |
fetch-sparkle.sh (downloads the pinned Sparkle), sparkle.conf (its version, checksum and the update feed URL), sparkle_public_key.txt (the public key updates are verified with) |
- macOS 12 (Monterey) or later
- Administrator access (once) to enable the lid-closed feature
From version 1.3.0 CaffeinateCat updates itself with Sparkle. It checks https://noxdrop.com/caffeinateCat/appcast.xml, and every update is signed (EdDSA) and verified before it is installed. Check for Updates… in the panel runs a check on demand. An update restarts the app, which turns lid-closed mode off.
The app is designed to be shareable. When a friend or family member runs it for the first time, it sets up its own permission with a single native admin prompt. A notarized build (see build.sh) opens without warnings; an ad-hoc build may need right-click → Open the first time to get past Gatekeeper.
Use Options → Remove Lid-Close Permission… and untick Open at login, then delete the app. Or, by hand:
sudo rm /etc/sudoers.d/caffeinatecat
rm -f ~/Library/LaunchAgents/com.caffeinatecat.launcher.plistApache 2.0. Sparkle is used under the MIT License.
© 2026 Prem Poddar · Noxdrop Systems


