- macOS 13 or later.
- Apple Silicon is the primary tested target.
- Xcode Command Line Tools:
xcode-select --install. - Codex and/or Claude Code installed if you want first-class hooks.
swift build -c releaseThe release artifacts are in .build/release/:
powernappowernapdpowernap-hookpowernap-menupowernap-watchdog
The easiest install path is:
./scripts/install.shThe script builds release artifacts, copies all five binaries to /usr/local/bin, adds protected codex and claude aliases to ~/.zshrc, and runs:
powernap installpowernap install writes and bootstraps these per-user LaunchAgents:
~/Library/LaunchAgents/dev.powernap.daemon.plist~/Library/LaunchAgents/dev.powernap.watchdog.plist~/Library/LaunchAgents/dev.powernap.menu.plist
The daemon, watchdog, and menu bar companion run as the current user, not root.
Install Codex hooks:
powernap hooks installThis writes a PowerNAP-managed command hook to ~/.codex/hooks.json and enables:
[features]
hooks = trueThe hook is inert outside a PowerNAP-wrapped run. It exits immediately unless POWERNAP_RUN_ID, POWERNAP_HOOK_TOKEN, and POWERNAP_SOCKET are present.
Claude Code does not need a global install. powernap claude creates a temporary per-run settings overlay and passes it via claude --settings <overlay>. Do not pass --bare to Claude Code; Claude documents and local help confirms that --bare skips hooks.
Run the non-invasive checks:
powernap doctorIf doctor is being used as a fast smoke test, bound subprocess checks:
powernap doctor --check-timeout-seconds 2Before closing the lid for real QA, run the explicit hardware spike:
powernap doctor --hardware-spikeThe hardware spike briefly enables and immediately clears the clamshell override. Do not start closed-lid testing until this passes and the watchdog LaunchAgent is loaded.
Useful checks:
powernap status
powernap hooks status
launchctl print gui/$(id -u)/dev.powernap.daemon
launchctl print gui/$(id -u)/dev.powernap.watchdog
launchctl print gui/$(id -u)/dev.powernap.menuThe menu bar icon uses a bolt while PowerNAP is blocking sleep, a Zzz glyph when normal sleep is allowed, and a question mark when daemon status is unavailable. The number beside the icon is the count of active protected agent threads keeping the Mac awake.
Codex:
codexAfter install, open a new shell or load the aliases into the current shell:
source ~/.zshrcClaude Code:
claudeGeneric command:
powernap run -- ./long-running-agentCodex and Claude wrappers acquire protection immediately, then agent hooks refine the state when a turn waits, idles, finishes, or exits. Codex startup protection expires after a short grace period if no prompt starts, then the matching local session transcript reacquires protection on task_started and releases it on task_complete or interruption. Generic mode treats the process lifetime as active because it has no agent-native waiting hooks.
If anything looks wrong:
powernap restorerestore asks the daemon to release PowerNAP-owned leases and clear the clamshell override. If the daemon is unavailable, the CLI performs a local safety restore from the state database.
PowerNAP config lives at:
~/Library/Application Support/PowerNAP/config.toml
Important defaults:
[power]
closed_lid_enabled = true
idle_sleep_assertion = true
max_closed_lid_minutes = 720
release_when_waiting = true
prearm_clamshell_on_active = true
[safety]
min_battery_percent = 20
critical_battery_percent = 10
allow_on_battery = true
allow_thermal_serious = false
watchdog_heartbeat_seconds = 60
watchdog_release_after_seconds = 180
active_lease_ttl_seconds = 43200
waiting_grace_seconds = 20Runtime paths can be overridden for tests:
POWERNAP_RUNTIME_DIRPOWERNAP_APP_SUPPORT_DIRPOWERNAP_LOGS_DIR