Releases: fexxdev/Mystral
Release list
v1.1.1
Quieter sleep (#2)
Fans no longer keep spinning at the last forced speed while the Mac is asleep. On system sleep the helper hands fan control back to macOS auto mode, so the firmware idles the fans during sleep; on wake your fan curve re-engages automatically.
No action needed — the in-app updater will offer this automatically.
Closes #2.
v1.1.0
⚡ Power monitoring
Mystral now reads and visualizes power draw — no extra permissions, no root.
Menu bar
- New display modes: Icon + Power and Power Only (total system watts).
Live dropdown
- Total system power plus a CPU / GPU breakdown.
Dashboard
- New Power section between the temperature cards and the chart:
- Total system watts with a stacked CPU / GPU / Other bar.
- A Power — Last 5 Minutes chart (Total / CPU / GPU).
Total power comes from the SMC (PSTR); CPU/GPU come from the IOReport
"Energy Model". Other = total − CPU − GPU (RAM, display, SSD, etc.), since
the two sources measure different things.
Install: open the DMG, drag Mystral.app to Applications. First launch:
right-click → Open to bypass Gatekeeper (unsigned build).
v1.0.15
The fan helper now never stops — and only asks for your password once
Previously the background SMC helper was periodically terminated by macOS (launchd housekeeping reaped it every ~20–40 minutes), causing it to relaunch and prompt for your password again each time.
What changed
- The helper is now installed as a launchd LaunchDaemon (
KeepAlive). macOS keeps it alive indefinitely, it survives reboot, and you are asked for your password only once — at first install, never again. - Manual restarts and post-update binary reloads are now prompt-free.
Install
- Drag Mystral.app to Applications.
- Right-click → Open the first time (unsigned build, bypasses Gatekeeper).
- Enter your password once to install the fan helper. That's it.
Uninstall the helper
sudo launchctl bootout system/com.fexxdev.Mystral.helper
sudo rm /Library/LaunchDaemons/com.fexxdev.Mystral.helper.plist
Verified: the helper ran for a full hour on the same process with zero deaths and zero password prompts.
v1.0.14
Fixes
- Fixed helper restart getting permanently stuck: Aligned staleness detection thresholds between the health check and restart logic (was 60s vs 120s, causing a dead zone where the app knew the helper was down but wouldn't restart it)
- Added 12-hour automatic retry: After exhausting 3 restart attempts, the app now retries every 12 hours instead of giving up permanently
- Added "Restart Helper" button: The error banner now includes a manual restart button instead of the misleading "Attempting to restart..." text
v1.0.13
What's Changed
Bug Fixes
- Fix helper crash on macOS Tahoe: The SMC helper was crashing before reaching Swift code because the LLVM profiling runtime tried to write
default.profrawto/(read-only root cwd). Fixed by settingLLVM_PROFILE_FILE=/dev/nulland changing cwd to/tmpin the launch script. - Fix missing Sensors empty state icon:
thermometer.slashdoesn't exist as an SF Symbol — replaced withexclamationmark.triangle.
Improvements
- Unified empty state styling: Dashboard, Sensors, and Fans views now use consistent vertical centering with
ContentUnavailableView. - Better helper launch diagnostics: Launch script now logs binary path, UID, cwd, PID, and alive/dead status to
/tmp/mystral-helper-launch.log.
Full Changelog: v1.0.12...v1.0.13
v1.0.12
In-App Auto-Updater
Updates can now be downloaded and installed directly from within the app — no more opening GitHub in the browser.
How it works
- Go to Settings → Updates → Check Now
- If an update is available, click Install Update
- The app downloads the DMG with a progress bar, installs, and relaunches automatically
Details
- Real-time download progress with cancel support
- Automatic DMG mount, version validation, and app replacement
- Rollback if installation fails
- Elevates via admin prompt only if the install directory requires it
- Removes macOS quarantine attributes for seamless launch
v1.0.11
Fix: Prevent unnecessary password prompts during sleep/wake
The SMC helper was being killed and relaunched (triggering a password prompt) whenever its data file became temporarily stale — e.g. during display sleep, power nap, or system wake transitions.
Changes
- Raise
checkHelperstaleness threshold from 10s to 120s for alive processes with matching version - Add PID liveness check in
SMCProxyServicebefore declaring helper dead - Tolerate up to 60s of stale data if helper process is still alive
- Ignore
SIGHUPin helper process to prevent unexpected termination - Launch helper with
nohupfor additional signal isolation - Observe
screensDidWakeNotificationfor display wake grace periods
Mystral 1.0.10
What's New
Accurate Chip Detection
- Fixed wrong chip identification — Mystral now reads the chip name directly from the OS (
machdep.cpu.brand_stringsysctl) instead of guessing from SMC sensor counts. All Apple Silicon chips (M1, M1 Pro, M1 Max, M2, M3, M4, etc.) are now correctly identified. - Robust CPU core counting — CPU core sensor detection is now prefix-based, so new or unmapped sensor keys are always counted correctly.
- Improved diagnostic export — SMC export now includes
hw.modelidentifier and sysctl chip name, making it easier to debug hardware-specific issues.
Full Changelog
Mystral 1.0.9
What's New
Helper Reliability & Wake Safety
- Activity assertion — the SMC helper now holds a
latencyCriticalactivity assertion so macOS won't throttle or kill it during display sleep. - Timer leeway tightened — helper GCD timer leeway reduced to 100ms (was unlimited), preventing stale data during idle.
- 15-second wake grace period — proxy and fan controller now ignore stale-data alerts for 15s after system wake, eliminating false "helper down" banners.
- Launch verification — helper launch now polls for the PID file to confirm the process actually started, instead of trusting NSAppleScript blindly.
- Capped retries — max 3 consecutive launch failures before giving up, stopping infinite admin password prompt loops.
Cache & State Fixes
- Wake-safe caches —
notifyWake()now clears Ftst unlock state and SMCKit key-info cache after system sleep, so stale firmware state doesn't persist. - FanController wake handler — invalidates caches for both proxy and direct SMC modes on wake.
- Power source monitor — uses
.commonModesso profile auto-switching reacts to AC/battery changes even during modal dialogs.
Cleanup
- Removed dead
reUnlockAfterWakecode path, replaced with cleanernotifyWake()flow.
Full Changelog
Mystral 1.0.7
What's New
Helper Reliability
- Fixed helper crash — the root-privileged SMC helper could crash on Debug/coverage builds due to LLVM profiling writing to a read-only filesystem (
/). The helper now sets its working directory to/tmp. - Staleness detection — the app now detects when the helper stops responding (stale data file >10 seconds) and reports it immediately instead of silently serving cached readings.
- Auto-restart — after 3 consecutive helper failures, the app automatically re-launches the helper with a 30-second cooldown between attempts.
- Dashboard warning banner — a visible red banner appears when the helper is unresponsive, so you know fan control is paused.
Hardening & Safety
- Command input validation — the helper now validates all incoming commands (fan index 0–7, percentage 0–100, mode 0–1) and rejects out-of-range values with a log entry.
- CurvePoint clamping — temperature (0–120°C) and fan percentage (0–100%) are clamped on init, decode, and edit to prevent invalid curve data.
- Profile name sanitization — empty or whitespace-only profile names are replaced with "Untitled Profile".
- Division-by-zero guard — fan curve interpolation now handles overlapping temperature points safely.
- Write gating — the app stops queuing SMC commands when the helper is known to be dead, avoiding stale command buildup.
Performance & Correctness
- SMC key info cache —
readKeyInforesults are cached, reducing repeated IOKit calls during each polling tick. - Refresh cooldown —
SMCProxyServiceskips redundant file I/O within 0.5 seconds, avoiding double-reads when sensors and fans are fetched in the same tick. - Stable chart IDs — history chart data points use deterministic IDs instead of random UUIDs, eliminating SwiftUI diff thrashing.
- Fan slider debounce — manual fan override slider now debounces at 200ms to avoid flooding the helper with rapid commands.
- Menu bar timer sync — the menu bar update timer now uses the configured polling interval instead of a hardcoded 2 seconds.
Fixes
- Memory leak fix —
ProfileAutoSwitchernow properly balancespassRetained/releasefor the IOKit power source callback. - Helper kill moved off main thread — stale helper termination + relaunch now runs in a
Task.detachedinstead of blocking the main thread withusleep. - Monotonic restart cooldown — helper restart timing uses
ProcessInfo.systemUptimeinstead of wall-clockDate, immune to clock changes. - Fan-stuck detection — threshold changed from
== 0 RPMto< 100 RPMfor more reliable stuck-fan alerts. - Sensor registry consistency —
AlertManagerandStressTesternow useSensorRegistry.cpuCoreSensorsinstead of hardcodedhasPrefix("Tp"). - Settings reset — "Reset All Settings" now resets all keys (smoothing, deadband, aggressive override) and syncs in-memory state.
- Pre-release version parsing — update checker handles version strings like
1.0.0-betagracefully. - Profile loading errors — corrupt profile JSON now logs the error instead of silently failing.
- Release build optimizations — Release config now uses
-O, whole-module compilation, and dSYM generation.