CellKeeper is an open-source macOS menu-bar app for transparent, safety-first battery charge management on Mac laptops. It shows what your battery is doing, lets you set a charge limit with a resume threshold, and explains exactly what it would do — and whether it can actually do it.
CellKeeper is an independent open-source project and is not affiliated with Apple, AppHouseKitchen, AlDente, or their respective developers.
CellKeeper is pre-alpha. The architecture, telemetry, and charging policy engine work. By default the app runs with a simulated control backend that records decisions without changing your Mac's charging. The first real control is opt-in and experimental: CellKeeper can set macOS's own Charge Limit (80–100%) through a shortcut you create, and macOS enforces it.
| Feature | State |
|---|---|
| Battery %, power source, charging state | Real — public IOPowerSources API |
| Cycle count, voltage, current, mAh capacities | Real — read-only IORegistry properties (some undocumented) |
| Negotiated external power (Power in) | Real — public API |
| Mac thermal state and Low Power Mode | Real — public ProcessInfo API; system-wide, not battery temperature; shown only |
| Power flow: power in, battery and Mac power, with the last hour | Implemented — undocumented macOS data, updated about once a minute; shown only, never used to control charging. The diagram is at the top of the menu; its chart and the source's note are under Details › Power history, and off (Settings › General › Menu › Show power flow) the whole flow is under Details › Power flow. Tried in a preview build on the owner's Mac on 2026-11-01, the menu open and closed. A faster SMC source (about once a second, averaged over 3 s) is designed and built, with the owner's reader, off by default; it ran in that trial, and its checks on a Mac (research note 13, O3) are still pending |
| Battery temperature | Not available on macOS 27 through any public interface; shown as unavailable |
| Battery health / condition as shown by macOS | Not available to third-party apps; CellKeeper shows its own computed capacity ratio, labelled as such |
| Charge limit 80/85/90/95/100% via macOS's Charge Limit | Real, experimental, opt-in (macOS 26.4+, Apple silicon); enforced by macOS; verified on one Mac |
| Charge limit + custom resume threshold (hysteresis) | Policy real, control simulated |
| Temporary charge to 100% | Real with macOS's Charge Limit; otherwise simulated |
| Scheduled temporary charge to 100% (one schedule, one-shot, up to 7 days ahead) | Real with macOS's Charge Limit (CellKeeper requests 100% at the start); otherwise simulated. Starts only while your Mac is awake and CellKeeper is running; it cannot wake your Mac |
| Weekly temporary charge to 100% (one weekly schedule: the days, a local start time, 1–48 hours) | Real with macOS's Charge Limit, as the one-shot schedule; otherwise simulated. Times follow your Mac's time zone. Starts only while your Mac is awake and CellKeeper is running; one whose window passed is not made up later |
| Temperature protection | Policy real; cannot trigger without a temperature reading; not available with macOS's Charge Limit |
| Discharge to limit while plugged in | Policy real, control simulated; a confirmed one-shot session, never automatic; not available with macOS's Charge Limit |
| Limits at any level (including below 80%), with CellKeeper switching charging itself | Simulated — choose Simulated helper in Settings › Control to try CellKeeper's own charge control at 20–100%, run through its helper's logic inside the app; nothing on your Mac changes. While macOS's own Charge Limit is on, it withholds new restrictions and asks for the release of any of its own; Settings › Control shows how to turn macOS's limit off (100% in System Settings › Battery) to try it, but the Simulated helper provides no replacement charge limit, so your Mac may then charge to 100%. No hardware control yet; the current priority — see roadmap milestone 4 and issue #1 |
| Diagnostics view, and a report for bug reports without serial numbers or other identifiers | Real — Settings › Diagnostics shows what CellKeeper sees and does; Copy Diagnostics there, or Save Diagnostics… to a text file you choose |
| Notifications: charging held at your limit (or, with macOS's Charge Limit, macOS reporting the battery at or above that limit and not charging), a temperature pause, a temporary full charge or discharge ending, a control fault, and a return to normal charging or to your own Charge Limit that CellKeeper cannot confirm (and what resolved it later) | Real, opt-in — off by default; turn on in Settings › General, and macOS asks for permission; you can turn them off there at any time. They say only what CellKeeper observed, and say so when its control is simulated |
| Shortcuts actions: set CellKeeper's limit, start or stop a temporary full charge, turn Manage charging on or off, get its status | Implemented, not yet tried in the Shortcuts app on a Mac — the same commands as the menu, with the same checks; see Shortcuts actions |
| Guided battery calibration: CellKeeper raises macOS's Charge Limit to 100% only for its charges, the rest at the top and the wait for you to unplug, asks you to unplug, rest the Mac unplugged and plug in again, returns to your limit confirmed, and reports before and after | Implemented; first tried on a Mac on 2026-10-21, when it ended early in step 4 because of a bug since fixed; the updated calibration has not been run yet — with macOS's Charge Limit only; user-initiated; about one to two days, though its 72-hour cap allows three; with bounds on each charge and on the wait to unplug, and a 72-hour cap; see research note 12 |
| Menu bar icon: battery style (charge fill, charging bolt or plug, optional percentage) or CellKeeper's status icon | Implemented, not yet seen in a Mac's menu bar (checked in offscreen renders) — choose in Settings › General › Menu bar; the battery style, with the percentage, is the default, and keeps a warning or thermometer symbol beside the battery while CellKeeper needs your attention. If you want only one battery icon, you can hide macOS's own in System Settings › Menu Bar › Battery (Control Center › Battery on older macOS versions); CellKeeper never changes that setting |
| Scheduled limits, other recurring schedules, launch at login | Planned |
The menu always says, under the battery, which kind of control is in effect: macOS's Charge Limit with how far it is verified (Available, Experimental or Unavailable), or a line that says when CellKeeper's own control is Simulated.
Apple provides no public API to stop or limit charging. macOS 26.4 and later on Apple silicon include a built-in Charge Limit (80–100%, System Settings › Battery), which is the only supported mechanism. Older approaches used by other tools rely on undocumented hardware keys that Apple has progressively locked down. CellKeeper adds control only through verified, documented-as-far-as-possible mechanisms, with the fail-safe rules in docs/safety.md. The details are in docs/research/.
Limits at any level, including below 80%, need CellKeeper to switch
charging itself, and every known way to do that is private and needs root.
This is CellKeeper's current priority: the parts that need no hardware
writes (phase 4a) were completed on 2026-10-11
(issue #1), and real
control stays gated on a mechanism verified on a test Mac and, for
distribution, on an Apple Developer ID (milestone 4 on the
roadmap). The first
test, of Apple's private ChargeInhibit power assertion on the
maintainer's Mac, ran on 2026-10-19 and observed no effect on charging
(research note 09). The
second, of the private DisableInflow assertion, ran on 2026-10-20 and
also observed no effect
(research note 10). The
third, of the SMC adapter cut (CHIE), which the owner authorized on
2026-10-20, was a manual test that ran the same day: the owner wrote the
key by hand with an existing open-source tool, while CellKeeper's probe
only observed. The cut made the Mac run from its battery with the adapter
plugged in, and restoring the key brought adapter power and charging back
(research note 11). The owner
decided not to use it: a cut makes the Mac run from its battery, while
CellKeeper's purpose is to stop charging at a limit and run from the
adapter. The older SMC keys that stopped charging read empty on this
Mac's firmware, so macOS's own Charge Limit (80–100%) stays CellKeeper's
only real control, and limits below 80% stay simulated.
Requirements: macOS Tahoe 26.4 or later on a Mac with Apple silicon.
- In the Shortcuts app, create a shortcut named exactly CellKeeper Set Charge Limit.
- Search the actions for "charge limit" and add the one that reads Set charge limit to …. Set its value to Shortcut Input, so CellKeeper can pass a value from 80 to 100. Leave Set Until Tomorrow off.
- Shortcuts then adds Receive … from Nowhere at the top, with If there's no input: Continue. Leave it as it is: CellKeeper passes the value from the command line, which does not need the Share sheet or Quick Actions.
- In CellKeeper, open Settings… › Control, choose macOS Charge Limit (through Shortcuts), and confirm.
- Pick a limit (80, 85, 90, 95 or 100%) in the menu bar.
What happens:
- Before its first change, CellKeeper reads your current Charge Limit and records it durably. It does so only on AC power, once power has been on for about a minute after a plug-in, a wake or its own start; on battery power it waits. If such a read shows "no limit", CellKeeper first asks you to confirm that your limit is 100%.
- It restores exactly that value when you turn off Manage charging, switch backend, quit, or if anything fails, unless you have changed the limit yourself meanwhile (see below). If it cannot confirm the restore when quitting, it tells you which value to set, and it tries again the next time it starts. On battery power it waits: once your Mac has been on power for a minute, it checks macOS's Charge Limit and gives the recorded limit back, keeping a change you made meanwhile (macOS applies its Charge Limit only on power); the menu and Settings say that it is waiting.
- Every change is confirmed by reading the setting back from macOS.
The read uses
pmset -g battlimit, an undocumented, read-only report. If CellKeeper cannot read or recognise it, it sets no new limit; it still tries to give back your recorded limit, and treats that as unconfirmed until it reads it back. - Features macOS's Charge Limit cannot express are not offered: a custom resume threshold (macOS resumes after a drop of more than 5%), the temperature pause, and discharging. Settings hides them and says so in one note.
- If you change the limit in System Settings while CellKeeper manages it, CellKeeper keeps your new value as your own limit, changes nothing, and turns off Manage charging. Turn it on again to let CellKeeper manage the limit; your new value is then the one it restores. CellKeeper notices a change only on AC power, once the reading has settled (about a minute after power returns or a wake); a change made on battery power is noticed after the Mac is plugged in. It cannot tell who changed the limit, so it does the same if another tool changes it.
You can always set the limit yourself in System Settings › Battery › ⓘ next to Charging. See docs/safety.md. The research behind this backend is in docs/research/08-native-charge-limit.md.
In the Shortcuts app, CellKeeper offers Set CellKeeper Limit, Start CellKeeper Full Charge, Stop CellKeeper Full Charge or Discharge, Set CellKeeper Manage Charging and Get CellKeeper Status, and Siri and Spotlight know "Get CellKeeper status". They are the same commands as the menu, with the same checks: an action refuses, with the reason, what the menu would not offer. One exception: Set CellKeeper Limit also works while Manage charging is off, when the menu's limit control is disabled; the limit is then saved for later, and CellKeeper uses it once you turn Manage charging on. Set CellKeeper Limit is not Apple's Set Charge Limit action, nor the "CellKeeper Set Charge Limit" shortcut you may have created; it changes CellKeeper's own setting.
An action says what CellKeeper saved or was asked to do, not that your Mac's charging changed; the menu shows what happens next. If CellKeeper is not running, running an action opens it, and it starts managing charging with your saved settings, as when you open it.
- To run: macOS 14 Sonoma or later. Telemetry has been verified on an Apple silicon MacBook Pro running macOS 27; other models and versions are expected to work but are unverified. Intel Macs can show telemetry, but CellKeeper will not offer charge control on Intel.
- To build: Xcode 16.0 or later (Swift 6). No Apple Developer account and no third-party dependencies are needed.
git clone https://github.com/SaltedTan/CellKeeper.git
cd CellKeeper
open CellKeeper.xcodeproj # then choose the CellKeeper scheme and RunOr from the command line:
xcodebuild -project CellKeeper.xcodeproj -scheme CellKeeper -configuration Debug \
-destination 'platform=macOS' -derivedDataPath .build/DerivedData build
open .build/DerivedData/Build/Products/Debug/CellKeeper.appThe app appears in the menu bar (no Dock icon). Builds are ad-hoc signed
("Sign to Run Locally"). To use your own bundle identifier or signing team,
copy Config/Local.xcconfig.example to Config/Local.xcconfig (git-ignored)
and edit it. The bundle identifier is io.github.saltedtan.CellKeeper; forks
that distribute their own builds should use their own prefix.
swift test --package-path Packages/CellKeeperKitThis runs all core and telemetry tests (Swift Testing). The tests are
deterministic and need no battery; one smoke test reads the real system's
power information read-only. In Xcode, ⌘U on the CellKeeper scheme runs the
same tests.
To see CellKeeper's decisions in the unified log:
log stream --level info --predicate 'subsystem == "io.github.saltedtan.CellKeeper"'CellKeeper.app (SwiftUI menu bar, settings, Shortcuts actions)
└── CellKeeperKit macOS adapters: read-only IOKit telemetry, power
notifications, the shortcuts and pmset runners, the
record of your own Charge Limit, local notifications
(opt-in), and what Shortcuts actions check and say
└── CellKeeperCore pure Swift: settings validation, charging policy
state machine, controller, backend protocol,
simulated, read-only and native Charge Limit
backends, and what to notify
- The charging policy is a pure, deterministic function: telemetry + settings + backend capabilities → desired mode + explicit action (enable charging, disable charging, request discharge, no action, or refuse with a reason). It never touches hardware. With macOS's Charge Limit it computes the limit macOS should enforce.
- All control goes through the
ChargingBackendprotocol. Simulated backends report actions as simulated, never as applied. - Every failure path returns to macOS default charging. With macOS's Charge Limit, that means your own limit, exactly as recorded.
The helper daemon, CellKeeperHelper, and its launchd property list
(Config/LaunchDaemons/io.github.saltedtan.CellKeeper.Helper.plist) are
built and tested, but in this phase they are not installed, not embedded in
the app and not registered with launchd, and the daemon controls no
hardware. swift build --package-path Packages/CellKeeperKit --product CellKeeperHelper builds it; do not install it.
See docs/architecture.md for the full design.
Battery charging is hardware-adjacent. CellKeeper is designed to fail toward macOS's default behaviour, validates every setting, logs every change in its decisions and every control request, and never writes to hardware itself in this version. Its only real control, opt-in, is macOS's own Charge Limit, which macOS enforces. Your Mac's built-in battery protections always remain in effect. Read docs/safety.md before contributing anything that could change charging behaviour.
Turn off Manage charging or quit CellKeeper first. If you used macOS's Charge Limit through CellKeeper, follow Getting your own Charge Limit back. For CellKeeper's own charge control and its helper, follow Recovery if charging does not resume: remove the helper, turn off its background item, stop it from Terminal, restart, and check macOS's own charging settings. In this version no helper is installed, so that procedure is not needed yet.
Disclaimer: CellKeeper is experimental software provided under the Apache License 2.0, without warranty of any kind. Future control features may depend on undocumented interfaces that Apple can change at any time. Use at your own risk.
Contributions are welcome. Please read CONTRIBUTING.md, including the clean-room rules: CellKeeper must not contain code, assets, text, or implementation details taken from other battery applications. Security issues: see SECURITY.md. Everyone participating is expected to follow the Code of Conduct.
CellKeeper is licensed under the Apache License, Version 2.0. The Code of Conduct is adapted from the Contributor Covenant 3.0 and is licensed under CC BY-SA 4.0.