The missing control knob for your Mac's SMC.
smctl is an open-source, CLI-first tool for controlling the hardware your Mac normally manages for you — fan speed, battery charging, and power telemetry — straight from the terminal.
smctl 是一个开源、命令行优先的 Mac 硬件控制工具:风扇曲线、电池充电限制、温度/功耗遥测——这些 macOS 不开放的控制能力,一条命令搞定。
English | 中文文档
$ smctl battery maintain 70-80 # keep the battery between 70% and 80%
$ smctl fan profile quiet # custom fan curve, stays silent until hot
$ smctl sensors --watch # live temperatures, fan RPM, package power- Your battery ages fastest at 100%. macOS only offers an opaque, ML-driven "Optimized Charging" and a single fixed 80% option. smctl gives you an explicit, deterministic charge limit with a sailing range — set it once, sync it in your dotfiles.
- Apple Silicon Macs expose no fan control at all. No official API, no third-party CLI — smctl implements manual fan targets and declarative fan curves on Apple Silicon, with a built-in thermal safety guard.
- Headless Macs deserve first-class tooling. Running a Mac mini as a home server? Every feature works over SSH, every command has
--jsonoutput for scripting.
| Command | What it does |
|---|---|
smctl sensors [--watch] [--json] |
Temperatures by sensor group, fan RPM/mode, battery, package power |
smctl battery status |
Charge level, charging state, configured limit, time estimate |
smctl battery maintain 80 / 70-80 / stop |
Charge limit with dead-band; add --force-discharge to drain down to the band |
smctl battery charging on|off / adapter on|off |
Manual charging and adapter-power toggles |
smctl battery charge 90 / discharge 40 |
One-shot top-up or supervised discharge |
smctl fan status |
Per-fan actual/target/min/max RPM and control mode |
smctl fan set 2500 [--fan N] |
Manual fan target |
smctl fan profile quiet|full|auto|<custom> |
Declarative fan curves (TOML), hysteresis + slew-rate limited |
smctl power status [--watch] [--json] |
Thermal pressure, CPU throttling (% speed limit), system/package + input power |
smctl alert list|status|test <name> |
Temperature/event alerts → webhook, command, or log (configured in TOML) |
smctl daemon install|uninstall|status|ping|logs |
Manage the privileged helper and inspect recent daemon logs |
Policies live in /etc/smctl/config.toml — declarative, diffable, dotfiles-friendly.
$ brew install leaperone/smctl/smctl
$ sudo smctl daemon installOr tap once and use short names from then on:
$ brew tap leaperone/smctl
$ brew install smctl # later: brew upgrade smctl$ git clone https://github.com/leaperone/smctl && cd smctl
$ swift build -c release
$ sudo .build/release/smctl daemon installEach release ships a zip with smctl + smctld, Developer ID signed and notarized by Apple.
Charge limiting and fan control are applied by smctld, a root LaunchDaemon. The CLI talks to it over XPC; reading sensors needs no daemon and no root.
The Homebrew install sets up shell completions (bash/zsh/fish) and a man smctl page automatically.
Controlling fans and charging from userspace demands paranoia. smctl treats these as first-class invariants:
- Never leave a brick. Uninstalling, stopping, or killing the daemon restores system control of fans and charging — enforced by termination hooks, startup reconciliation, and launchd restart.
- Thermal safety guard. While fans are under manual control, smctl monitors all temperature sensors every second. Sustained readings above the ceiling force fans back to system control and latch out manual control until things cool down. The base ceiling defaults to 100 °C and is hard-capped at 105 °C; Apple Silicon
Tp*hot-spot sensors get a narrow allowance up to 110 °C because they can run hotter than board/skin/proximity sensors under ordinary load. The guard cannot be disabled, and losing temperature visibility counts as unsafe. - Verified writes. Every SMC write is read back and verified (with a settle window for firmware that applies writes asynchronously) — failures surface as errors, never as silent no-ops.
- Graceful degradation. If a macOS update changes SMC behavior, affected features degrade to read-only with an explicit message instead of pretending to work.
The daemon makes outbound network requests in these cases, all under your control:
- Update check — a once-a-day check of the GitHub releases API to learn the latest version, surfaced by the CLI as an upgrade hint. On by default; turn it off with
[update] check = false. - Alert webhooks — only the webhook URLs you configure in
[[alert]]rules. No alerts configured (the default) means no such requests. - Sentry crash/error reporting — only when you configure a DSN under
[sentry]. The default empty DSN disables the SDK entirely. smctl setssendDefaultPii = falseand does not enable performance tracing unless you opt in withtraces_sample_rate.
There is no product analytics. The CLI itself never makes network requests — all outbound traffic originates in the daemon, from the opt-in/opt-out cases above. To go fully offline, set:
[update]
check = false
[sentry]
dsn = ""in /etc/smctl/config.toml (then sudo smctl daemon restart) and configure no webhook alerts. You can also hard-disable Sentry for the daemon process with SMCTL_SENTRY_DISABLED=1.
The daemon already watches every temperature sensor once a second for the thermal safety guard. Alert rules hang off that same loop: when a condition holds, the daemon runs an action — a shell command, an HTTP webhook, or a log line. Useful for a headless Mac mini wired into Prometheus/Gotify/etc.
Rules are declarative TOML in /etc/smctl/config.toml:
[[alert]]
name = "cpu-hot"
on = "temp" # temp | guard | write-error
sensor = "Tp09" # a sensor key, or "any" for the hottest
above = 85 # °C
for = 30 # must hold this many seconds before firing (debounce)
cooldown = 300 # silence re-firing for this many seconds
resolve = true # also fire once when the condition clears
action = "webhook" # webhook | exec | log
url = "http://gotify.lan/message?token=..."
[[alert]]
name = "guard-tripped"
on = "guard" # the thermal safety guard forced fans back to auto
action = "exec"
command = ["/usr/local/bin/notify.sh", "{name}", "{reason}"]exec commands receive the event as both substituted argv placeholders ({name} {kind} {trigger} {reason} {value}) and SMCTL_ALERT_* environment variables. Inspect and verify rules with:
$ smctl alert list # configured rules, validity, and redacted action targets
$ smctl alert status # per-rule state + recent events
$ smctl alert test cpu-hot # fire the action now, to check your webhook/scriptalert list deliberately redacts webhook query strings and exec arguments in human and JSON output, so tokens in /etc/smctl/config.toml do not leak through the read-only status API.
Security:
execactions run as root (the daemon is root). The trust boundary is whoever can edit the root-owned/etc/smctl/config.toml— the same boundary as every other policy. Commands run via argv arrays only (no shell), so there is no command-injection surface, but treat write access to the config as equivalent to root.
- Apple Silicon Macs, macOS 14+.
- Fan control fully verified on M4 Mac mini (direct-write path). The diagnostic-unlock fallback path for machines that need it (some MacBook Pro generations) is implemented per published research but needs more real-hardware coverage — reports welcome.
- Battery charge control targets the same SMC keys used by established tools (pre-Tahoe and macOS 26 key sets, runtime-detected); broader machine coverage is in progress.
- Intel Macs: not yet (sensors are read-only there for now).
Capability detection is done at runtime — on unsupported hardware, commands tell you exactly what's unavailable rather than failing silently.
| smctl | macOS built-in | AlDente | Macs Fan Control | stats | |
|---|---|---|---|---|---|
| CLI / scriptable | ✅ | — | — | — | — |
| Battery charge limit | ✅ custom range | 80% fixed | ✅ | — | — |
| Fan control (Apple Silicon) | ✅ curves | — | — | ✅ GUI only | — |
| Declarative config | ✅ TOML | — | — | — | — |
| Open source | ✅ MIT | — | — | — | ✅ |
- Homebrew tap, then homebrew-core
- Thermal-throttling visibility (
smctl power) smctl battery calibrate— planned, pending validation on a MacBook (calibration must run inside the daemon to avoid fighting the maintain loop, and needs real battery hardware to verify)- Menu bar app (the daemon already speaks XPC; a GUI is just another client)
- 中文项目文档 — vision, research notes, and roadmap (Chinese)
- Architecture & design (Chinese)
- Field notes: M4 Mac mini — real-hardware findings, including SMC asynchronous-write behavior (Chinese)
smctl writes to your Mac's System Management Controller. The safety mechanisms above are engineered conservatively, but you use it at your own risk — especially manual fan control under sustained load.
