This guide is for administrators, power users, and developers who want to understand how Stats is built, how to deploy or build it, how it uses your system, and how to contribute. For day-to-day usage, see the companion User Guide.
Stats is a native macOS menu bar system monitor written in Swift for AppKit (Cocoa). It has no server component and no external runtime dependencies beyond Apple's own frameworks. Its defining architectural trait is a plugin-style module system: a shared framework (Kit) defines the contracts, and each monitor (CPU, RAM, GPU, …) is an independent module/target that plugs into it.
Table of Contents
- Executive Summary
- Architecture Overview
- Repository Structure
- The Kit Framework
- Module Lifecycle
- Data Persistence
- The SMC Subsystem & Privileged Helper
- Remote / System Stats Integration
- The Updater
- Building From Source
- Configuration Reference
- Security Analysis
- Code Patterns & Conventions
- Operations & Troubleshooting
- Potential Improvements
- Contributing
- Quick Reference
1. Executive Summary
| Aspect | Detail |
|---|---|
| Purpose | macOS menu bar system monitor (CPU, GPU, RAM, Disk, Network, Battery, Sensors, Bluetooth, Clock, Remote) |
| Language | Swift 5 (some files Swift 6) |
| UI framework | AppKit / Cocoa (programmatic, no Storyboards for the modules) |
| Minimum OS | macOS 12 (Monterey) |
| Architectures | Apple Silicon (arm64) and Intel (x86_64) |
| Distribution | Signed .dmg from GitHub Releases; Homebrew cask stats |
| External services | api.mac-stats.com, api.github.com, app.system-stats.com (optional) |
| License | MIT |
| Privileged component | Optional SMC helper (XPC, /Library/PrivilegedHelperTools) for fan control |
The codebase is organized as an Xcode workspace of many targets: the main app (Stats), a shared framework (Kit), one framework target per monitor under Modules/, an SMC helper toolset (SMC/), a WidgetKit extension (Widgets/), and a LaunchAtLogin helper.
Note
There is no package-manager manifest with third-party dependencies — Stats relies entirely on Apple frameworks (Cocoa, IOKit, CoreBluetooth, SystemConfiguration, Metal, ServiceManagement, UserNotifications, WidgetKit, WebKit, Carbon). This keeps the supply chain minimal.
2. Architecture Overview
┌───────────────────────────────────────────┐
│ Stats.app (main) │
│ AppDelegate │
│ • holds `modules: [Module]` │
│ • global key monitor, updater, support │
│ • SettingsWindow / Setup / Update / Support│
│ • CombinedView (merged menu bar item) │
└───────────────┬─────────────────────────────┘
│ imports
┌────────────────────────────────┼─────────────────────────────────┐
│ │ │
┌────▼─────┐ ┌──────────┐ ┌────────▼────────┐ ... ┌─────────────┐
│ CPU │ │ RAM │ │ Network │ │ Remote │
│ (Module) │ │ (Module) │ │ (Module) │ │ (Module) │
└────┬─────┘ └────┬─────┘ └────────┬─────────┘ └──────┬──────┘
│ subclass of │ │ │
└──────────────┴───────┬──────────┴──────────────────────────┘
▼
┌──────────────────────────┐
│ Kit (framework) │
│ Module / Reader / Widget │
│ Popup / Portal / Settings│
│ Store / DB / Notifications│
│ SMC client / SystemKit │
└─────────────┬─────────────┘
│ XPC (fan control only)
▼
┌──────────────────────────┐
│ SMC Helper (privileged) │
│ /Library/PrivilegedHelperTools │
└──────────────────────────┘
Flow: AppDelegate instantiates all modules into a global modules array, then calls mount() on each. A mounted module starts its Readers (background pollers), which read system data and fire callbacks. Each callback updates the module's menu bar widgets, its pop-up, its portal (the row shown in the combined pop-up), and its notifications. The Kit framework supplies all of these base classes; modules subclass and specialize them.
Key design properties
- Loose coupling via
NotificationCenter. Modules and views communicate through named notifications (.toggleModule,.togglePopup,.toggleSettings,.moduleRearrange,.pause,.remoteState, …) rather than direct references. This keeps modules independent. - Configuration as data. Each module ships a
config.plist(Name,State,Symbol,Widgets,Settings,Preview). Themodule_cstruct parses it at init to decide the module's default state, default widget, and available widgets. Adding/removing a widget is a plist edit, not code. - Persistence is the source of truth. All user choices live in
UserDefaultsvia theStorewrapper; historical samples live in a small embeddedDB. There is no in-memory-only state that matters across launches.
3. Repository Structure
Stats/ Main application target
AppDelegate.swift App entry, module registry, update/support scheduling
helpers.swift Argument parsing, version migration, dock/login setup
Views/ Settings UI
Settings.swift SettingsWindow (sidebar + toolbar), MainView, SidebarView
AppSettings.swift ApplicationSettings (global preferences)
Dashboard.swift Read-only device information panel
CombinedView.swift Merged single menu bar item + combined pop-up
Supporting Files/ Info.plist, assets, .lproj localizations (40+ languages)
Kit/ Shared framework (the plugin contract)
constants.swift Sizes, ModuleType enum, widget_t enum
extensions.swift UI helpers, color/format helpers, KeyboardShartcutView
helpers.swift LaunchAtLogin, Units/DiskSize, label/value views, rows
module/ Module, Reader, Widget, Popup, Portal, notifications
Widgets/ Widget implementations (Mini, LineChart, BarChart, …)
plugins/ SMC client, SystemKit, sensor plugins
Supporting Files/, lldb/
Modules/ One framework target per monitor
CPU/ GPU/ RAM/ Disk/ Net/ Battery/ Sensors/ Bluetooth/ Clock/ Remote/
main.swift Module subclass + data structs + reader wiring
readers.swift Reader subclasses (the actual data collection)
settings.swift Per-module settings UI
popup.swift Per-module pop-up UI
portal.swift Combined-pop-up row
notifications.swift Threshold notifications
widget.swift / preview.swift (where applicable)
config.plist Module manifest (name, widgets, settings, preview)
SMC/ Command-line SMC tool (`smc`) + privileged Helper
main.swift CLI: list/set/fan/fans/reset/help
Helper/ XPC privileged helper for fan control
Widgets/ WidgetKit extension (desktop / Notification Center)
LaunchAtLogin/ Tiny helper binary that relaunches the main app
Stats.xcodeproj/ Xcode project & shared schemes
Tests/ Unit tests
Note
The bulk of the file count is localization (
*.lprojfor 40+ languages) and asset catalogs (device icons). The hand-written Swift source is concentrated inKit/,Modules/*/, andStats/.
4. The Kit Framework
Kit is the contract every module obeys. Understanding these five abstractions explains the whole app.
4.1 Module (base class)
Module (in Kit/module/module.swift) is the superclass of every monitor. It owns:
- a
module_cconfig (parsed from the target'sconfig.plist), - a
MenuBarofSWidgets, - an optional
Window(settings),PopupWindow,Portal,NotificationsWrapper, andPreviewWrapper, - a list of
Reader_ppollers.
module_c reads the plist keys:
| Plist key | Meaning |
|---|---|
Name |
Module display name (required) |
State |
Default enabled state |
Symbol / AlternativeSymbol |
SF Symbol for the module icon |
Widgets |
Dictionary of available widgets, each with Default and Order |
Settings |
Settings metadata |
Preview |
Preview availability |
4.2 Reader<T> (data collection)
Reader<T: Codable> (in Kit/module/reader.swift) is a generic, thread-safe poller:
- Holds a typed
value: T?behind a serial dispatch queue. - Runs on a configurable interval (
{module}_updateInterval, default 1s) via an internalRepeater, with an optional second-boundary aligned mode for clocks. - Persists the last value to
DBand forwards it toSystemStats.shared.send(...)for remote monitoring. - Supports
start/pause/stop/lock/unlockand a sleep mode so readers stop when the Mac sleeps. popup/previewreaders run only while their UI is visible, saving energy.
Each module defines its own readers (e.g. CPU has LoadReader, ProcessReader, TemperatureReader, FrequencyReader/LimitReader, AverageLoadReader).
Note
LimitReader(Intel) vs.FrequencyReader(Apple Silicon) are selected with#if arch(x86_64)/#else. This compile-time branching for architecture-specific metrics recurs throughout the modules.
4.3 widget_t (widget catalog)
The widget_t enum enumerates all widget styles. Each maps to a concrete WidgetWrapper subclass:
widget_t |
Raw value | Class |
|---|---|---|
mini |
mini |
Mini |
lineChart |
line_chart |
LineChart |
barChart |
bar_chart |
BarChart |
pieChart |
pie_chart |
PieChart |
networkChart |
network_chart |
NetworkChart |
speed |
speed |
SpeedWidget |
battery |
battery |
BatteryWidget |
batteryDetails |
battery_details |
BatteryDetailsWidget |
stack |
sensors |
StackWidget |
memory |
memory |
MemoryWidget |
label |
label |
Label |
tachometer |
tachometer |
Tachometer |
state |
state |
DotWidget |
text |
text |
TextWidget |
widget_t.new(...) instantiates both a live widget and a preview image. A module's data callback then switches on the concrete widget type to push values — this switch is the authoritative list of which widgets a module actually feeds.
4.4 Popup, Portal, Settings
Popup_p/PopupWrapper— the detail panel. APopupWindow(borderlessNSPanel) hosts aPopupViewwith a header (HeaderView) offering Activity Monitor/Calendar/System Stats shortcuts and a settings button. Popups support a per-module keyboard shortcut (stored as{module}_popup_keyboardShortcut).Portal_p/PortalWrapper— a compact module summary row shown in the combined pop-up.Settings_v— per-module settings view, wired to the module via callbacks (callback,setInterval,setTopInterval, selection handlers, etc.).
4.5 Helpers
Kit also provides reusable UI builders (popupRow, portalRow, separatorView, switchView, selectView, sliderView, colorSelectView), formatting types (Units, DiskSize), a KeyboardShartcutView recorder, and LaunchAtLogin (using SMAppService on macOS 13+ and the legacy SMLoginItemSetEnabled below that, with a migrate() path).
5. Module Lifecycle
init() Parse config.plist → build menuBar widgets, popup, settings,
portal, notifications. Read enabled state from Store.
isAvailable() decides if the module can run at all.
│
mount() Called by AppDelegate at startup (in reverse order). If enabled,
start all readers and enable the menu bar item.
│
enable() Persist enabled=true, start readers, show widgets.
disable() Persist enabled=false, stop readers, hide widgets & popup.
│
terminate() willTerminate() hook, stop+terminate readers, disable menu bar.
isAvailable() is overridden per module — e.g. Battery checks IOPSCopyPowerSourcesList, Network checks for any SCNetworkInterface. Unavailable modules are forced off.
A module is shown in the menu bar only when enabled and has at least one active widget; AppDelegate.showSettingsIfNoActiveWidgets() opens Settings on launch when nothing is active.
Pause is global: posting .pause disables every enabled module and swaps the menu bar for a single app icon, restoring state when unpaused.
6. Data Persistence
| Store | Backing | Holds |
|---|---|---|
Store.shared |
UserDefaults |
All user preferences (module states, colors, intervals, widget choices, thresholds, clocks, etc.) |
DB.shared |
Embedded key/value DB | Last value per Reader ({Module}@{ReaderName}), optional time-series history |
| Widget defaults | App-group UserDefaults ({TeamId}.eu.exelban.Stats.widgets) |
Data shared with the WidgetKit extension |
- Preference keys follow
{ModuleName}_{option}(e.g.CPU_splitValue,Disk_<uuid>_popup,Network_publicIPRefreshInterval). Storesupportsexport(to:)/import(from:)(theStats.plistbackup) andreset().- Readers throttle DB writes (roughly every
interval × 10seconds) to limit disk churn. - On version change,
parseVersion()migrates or, if no prior version is recorded, resets the store.
Tip
The app-group suite name embeds the developer Team ID (
{TeamId}.eu.exelban.Stats.widgets). If you re-sign Stats under a different team for a custom build, the WidgetKit extension must use the matching app group or widgets won't receive data.
7. The SMC Subsystem & Privileged Helper
Stats reads hardware sensors (temperatures, voltages, power, fans) through the System Management Controller (SMC). Reading is unprivileged; changing fan speed/mode is privileged and goes through a separate helper.
7.1 The smc CLI tool
SMC/main.swift builds a small command-line tool bundled with the app:
./smc [command]
Commands:
list list keys and values
set set value to a key (-k <4-char key> -v <int>)
fan set fan speed / mode (<id> -v <speed> | -m <0|1>)
fans list fans (count, actual/min/max/target speed, mode)
reset reset Ftst — Apple Silicon only
help help menu
Flags (for list):
-t temperature sensors -v voltage sensors
-p power sensors -f fans
Fan mode 0 = automatic, 1 = manual. SMC keys are 4 characters (e.g. FNum, F0Ac, F0Mx).
7.2 The privileged Helper (fan control)
Fan writes are performed by an XPC helper installed at /Library/PrivilegedHelperTools/eu.exelban.Stats.SMC.Helper, registered as a launch daemon (/Library/LaunchDaemons/eu.exelban.Stats.SMC.Helper.plist).
Stats.app ──XPC──► SMC.Helper ──shells──► smc fan <id> -m/-v ...
│
└─ validates caller code signature on every connection
Security-relevant behavior in SMC/Helper/main.swift:
- The helper listens on a Mach service (
NSXPCListener(machServiceName: "eu.exelban.Stats.SMC.Helper")). - Every incoming XPC connection is validated with
CodesignCheck.codeSigningMatches(pid:)— the caller's signing certificates must match the helper's own. Mismatched callers are rejected. This prevents arbitrary processes from driving fan control. - The helper exposes only
setFanMode,setFanSpeed,resetFanControl,version, anduninstallover theHelperProtocol. - It self-terminates when the last connection drops, and
uninstallunloads the daemon vialaunchctland removes both the daemon plist and the helper binary.
Warning
Fan control is in legacy maintenance mode — no new features or fixes. The privileged helper is the highest-risk component in the app's threat model; the code-signature check on each connection is the primary mitigation. Review it carefully before shipping a re-signed build.
To remove the helper, the app calls SMCHelper.shared.uninstall() (surfaced as Uninstall fan helper in Settings).
8. Remote / System Stats Integration
The Remote module and SystemStats client connect Stats to the optional System Stats cloud service.
| Endpoint base | Constant | Purpose |
|---|---|---|
| API host | SystemStats.host |
REST + SSE data |
| App host | SystemStats.appHost = app.system-stats.com |
Web links for machines/hosts |
Key behaviors:
- Auth: OAuth-style device flow (
access_token/refresh_token/device_code/user_code/verification_uri_complete). Requests attachAuthorization: Bearer <accessToken>. - Outbound monitoring: when Monitoring is enabled, each
ReadercallsSystemStats.shared.send(key:value:), serializing a compactremote()representation per data type (RemoteType). - Inbound dashboard: the Remote module fetches
/remote/machine,/host,/group,/accountand renders machines/hosts grouped per the account's order. A State dot aggregates overall up/down/mixed status. - Live updates:
RemoteMachineStreamopens an SSE connection (/remote/machine/<id>/sse) with exponential-backoff reconnect (capped at 60s) and parsesdata:frames intoRemoteUpdates. - Control: disabled by default and gated behind an explicit warning dialog; enabling it allows the service to issue control actions.
Caution
All Remote functionality is opt-in and requires login. The Control capability widens the trust boundary (the cloud can act on the machine); document and default it off in any managed deployment.
9. The Updater
Stats self-updates via Updater(github: "exelban/stats", url: "https://api.mac-stats.com/release/latest").
- Check cadence is the
update-intervalpreference:silent(background install),atStart,oncePerDay,oncePerWeek,oncePerMonth(AppUpdateInterval). Background activity is scheduled withNSBackgroundActivityScheduler. - On a new version it either installs silently, posts a notification (clicking it downloads+installs), or shows the update window, depending on notification authorization.
- After installing, the new app is relaunched and the updater cleans up the temporary DMG mount and downloaded
.dmgvia the--mount-path/--dmg-patharguments (both are path-validated to temp/Downloads to avoid deleting arbitrary files). api.github.comis the fallback update source.
Note
The public-IP lookup also uses
api.mac-stats.com. The maintainer runs this server specifically to avoid third-party IP providers; both update and IP requests can be blocked with a network filter at the cost of those features.
10. Building From Source
Prerequisites
- macOS with Xcode (recent enough to target macOS 12+, Swift 5/6).
- The repository:
git clone https://github.com/exelban/stats.git. - For full builds: an Apple Developer Team ID for code signing (required for the app group, WidgetKit extension, and the privileged helper).
Build steps
git clone https://github.com/exelban/stats.git
cd stats
open Stats.xcodeproj
- Open
Stats.xcodeprojin Xcode. - Select the Stats scheme.
- Set your Team in Signing & Capabilities for each signed target (app, widgets extension, helper).
- Product → Run (⌘R) to build and launch, or Product → Archive to produce a distributable build.
Important
Several capabilities depend on correct signing: the app group (
{TeamId}.eu.exelban.Stats.widgets) that feeds macOS widgets, and the privileged helper (which validates that callers share its signing identity). Building with a personal team works for local runs, but widgets and fan control require the entitlements/app group to line up.
Code style
The repo uses SwiftLint (.swiftlint.yml). Notable limits: line_length: 200, file_length warning at 1400 / error at 1800, type_body_length 700/1000. Several rules are disabled or opt-in; many data-model type names are excluded from identifier_name.
Tip
Run
swiftlintbefore submitting a PR. The config disablesforce_cast,cyclomatic_complexity, and a few others as "todo", so don't be surprised by existing force-casts in older code.
11. Configuration Reference
All configuration is UserDefaults (via Store). There is no config file for end users — preferences are set in the UI and can be exported to Stats.plist.
Global keys
| Key | Default | Values |
|---|---|---|
update-interval |
silent |
silent, atStart, oncePerDay, oncePerWeek, oncePerMonth |
temperature_units |
system |
system, plus Celsius/Fahrenheit options |
dockIcon |
false |
bool |
keep_menubar_positions |
false |
bool |
CombinedModules |
false |
bool |
CombinedModules_spacing |
none |
spacing option |
CombinedModules_separator |
false |
bool |
CombinedModules_popup |
true |
bool |
version |
— | last-run version (migration) |
pause |
false |
global pause |
support_ts |
— | last support-window timestamp |
The macOS-widgets switch (systemWidgetsUpdates_state) lives in the app-group suite, not the main store.
Representative per-module keys
| Pattern | Example | Meaning |
|---|---|---|
{Module}_state |
CPU_state |
Module enabled |
{Module}_updateInterval |
RAM_updateInterval |
Reader interval (sec) |
{Module}_popup_keyboardShortcut |
CPU_popup_keyboardShortcut |
Pop-up hotkey |
{Module}_position |
GPU_position |
Order in combined item |
| CPU | CPU_usagePerCore, CPU_splitValue, CPU_clustersGroup, CPU_systemColor, CPU_userColor, CPU_eCoresColor, CPU_pCoresColor, CPU_sCoresColor |
CPU display options |
| RAM | RAM_splitValue, RAM_appColor, RAM_wiredColor, RAM_compressedColor, RAM_textWidgetValue |
RAM display options |
| Disk | Disk_disk, Disk_mainColor, Disk_textWidgetValue, Disk_<uuid>_popup |
Disk selection & display |
| GPU | GPU_gpu, GPU_showType |
GPU selection & label |
| Network | Network_publicIPRefreshInterval, Network_usageReset, Network_widgetActivationThreshold(State/Size), Network_textWidgetValue |
Network behavior |
| Sensors | Sensors_sensor, Sensors_fanValue |
Selected sensor / fan unit |
| Bluetooth | ble_<id>, ble_<id>_notification |
Per-device visibility/threshold |
| Clock | Clock_list, clock_<id>_popupState, clock_<id>_popupIndex |
Clock definitions |
| Remote | Remote_machine_<id>, Remote_host_<id> |
Per-machine/host visibility |
12. Security Analysis
| Area | Assessment |
|---|---|
| Privilege boundary | Fan control runs in a separate root helper. Every XPC connection is gated by CodesignCheck (caller's signing certs must equal the helper's). This is the central security control — preserve it in forks. |
| Code execution | The helper shells out to the bundled smc tool (/bin/sh -c "<smc> fan <id> ..."). Inputs are integers from internal calls, not user free-text, limiting injection surface. |
| Network | Outbound only. Update checks (api.mac-stats.com, fallback api.github.com), public IP (api.mac-stats.com), and — only if logged in — System Stats (app.system-stats.com). Remote requests use Authorization: Bearer over HTTPS. |
| Auth tokens | Remote uses OAuth device flow; access_token/refresh_token are held by the SystemStats.auth client. Treat the token store as sensitive. |
| Data exfiltration | Monitoring data leaves the machine only when the user enables Monitoring in System Stats. Control must be explicitly enabled behind a warning. |
| Argument handling | --mount-path / --dmg-path are validated to temp/Downloads and reject .. traversal before deleting anything. |
| App Transport Security | Community issue #3320 requested hardening ATS (avoiding a global NSAllowsArbitraryLoads). Verify the shipped Info.plist ATS posture when auditing. |
Caution
For managed/enterprise deployments: keep Control disabled, consider blocking
api.mac-stats.comif you don't want update/IP calls, and audit the privileged helper's signing identity. The helper is installed system-wide (/Library/...) and persists until explicitly uninstalled.
13. Code Patterns & Conventions
- Programmatic AppKit. Views are built in code (
NSStackView,NSGridView, customNSViewsubclasses). No SwiftUI in the core app (SwiftUI/WidgetKit only in the widget extension). - Notification-driven coordination. Cross-component events flow through
NotificationCenterwithNotification.Nameextensions (.toggleModule,.togglePopup,.toggleSettings,.pause,.moduleRearrange,.remoteState, etc.). - Thread-safe value holders. Shared mutable collections (e.g.
Disks,GPUs,Reader.value) are guarded by dedicated serialDispatchQueues withsyncget/set. - Codable everywhere. Data models conform to
Codablefor DB persistence and remote serialization; many implement a compactremote()string for the wire format. - Architecture branching.
#if arch(x86_64)/#if arch(arm64)selects Intel vs. Apple Silicon code paths (frequency vs. scheduler limits, fan reset). - Config-as-plist. Module capabilities (widgets, default state) are declared in
config.plist, parsed bymodule_c. - Localization-first. All user-facing strings pass through
localizedString(...); 40+.lprojbundles are maintained by the community.
14. Operations & Troubleshooting
Where things live at runtime
| Item | Location |
|---|---|
| Preferences | UserDefaults for eu.exelban.Stats |
| Widget data | App group {TeamId}.eu.exelban.Stats.widgets |
| Privileged helper | /Library/PrivilegedHelperTools/eu.exelban.Stats.SMC.Helper |
| Helper launch daemon | /Library/LaunchDaemons/eu.exelban.Stats.SMC.Helper.plist |
| Login item | Registered via SMAppService.mainApp (macOS 13+) |
Common operational tasks
# Reset all settings (clean slate)
/Applications/Stats.app/Contents/MacOS/Stats --reset
# Launch with certain modules disabled
/Applications/Stats.app/Contents/MacOS/Stats --disable sensors,bluetooth
# Remove the privileged fan helper
sudo launchctl unload /Library/LaunchDaemons/eu.exelban.Stats.SMC.Helper.plist
sudo rm /Library/LaunchDaemons/eu.exelban.Stats.SMC.Helper.plist
sudo rm /Library/PrivilegedHelperTools/eu.exelban.Stats.SMC.Helper
Tip
Prefer the in-app Uninstall fan helper button over manual
rm; it performs the samelaunchctlunload and file removal in the correct order.
Diagnostics
| Symptom | Check |
|---|---|
| Menu bar icons missing (macOS 26) | System Settings → Menu Bar → Stats = ON |
| Desktop widget empty | macOS widgets setting enabled; app group/Team ID match |
| High CPU | Disable Sensors/Bluetooth; raise update intervals |
| Network falsely DOWN | Restart Stats; update to latest |
| Fan helper install fails | Approve admin prompt; uninstall stale helper, retry |
| Update breaks app (Gatekeeper) | Reinstall from Stats.dmg |
15. Potential Improvements
These are drawn from the public issue tracker and visible code structure — candidates for contribution.
- Fan control modernization. It's explicitly legacy. A maintained, well-tested fan-control path (and clearer install UX, given issues #3330/#3331/#3332) would close a recurring pain point.
- ATS hardening (#3320) — avoid any global arbitrary-loads allowance.
- Network interface status robustness (#2545, #3293) — eliminate false "DOWN" without requiring a restart.
- Apple Silicon sensor mapping — sensor keys change per SoC; a maintained mapping (and handling for the M5 "super cores" frequency gap, #3325) would improve accuracy.
- Per-monitor multi-display windows and richer RAM data (#3327).
- CPU core frequencies on the menu bar (#3333) and usage history in an expanded window (#1194).
- Fan-read debounce on startup to avoid spurious 100% notifications (#3211).
- Code-health items the lint config flags as "todo": re-enable
cyclomatic_complexity,type_name, and reduceforce_castusage.
16. Contributing
The project has a deliberate workflow (from the repository README):
- Open an issue first to discuss the feature or bug. PRs are expected to map to a discussed issue, or they may be closed automatically.
- Exceptions that can skip discussion: localization/translation changes, and contributions from established contributors whose work aligns with the project.
- Translations are community-driven — add a new
.lprojor improve an existing one. - Follow the existing SwiftLint configuration and the programmatic-AppKit, notification-driven,
Codable-model conventions.
To add a new module: create a target under Modules/, subclass Module, add a config.plist declaring its widgets/settings, implement one or more Readers plus the popup/settings/portal/notifications views, and register an instance in the modules array in Stats/AppDelegate.swift.
To add a new widget: add a case to widget_t, implement a WidgetWrapper subclass, wire it in widget_t.new(...), and handle it in each relevant module's data callback.
17. Quick Reference
Targets
| Target | Role |
|---|---|
Stats |
Main menu bar app |
Kit |
Shared framework (Module/Reader/Widget/Popup/Portal/Settings/Store/DB/SMC client) |
Modules/* |
One framework per monitor (CPU, GPU, RAM, Disk, Net, Battery, Sensors, Bluetooth, Clock, Remote) |
SMC |
smc CLI tool |
SMC/Helper |
Privileged XPC fan-control helper |
Widgets |
WidgetKit extension |
LaunchAtLogin |
Relaunch helper |
smc commands
| Command | Purpose |
|---|---|
list [-t/-v/-p/-f] |
List keys/values (filterable) |
set -k <key> -v <int> |
Write a 4-char SMC key |
fan <id> -v <speed> / -m <0|1> |
Set fan speed / mode |
fans |
List fans and their speeds/mode |
reset |
Reset fan control (Apple Silicon) |
help |
Usage |
Launch arguments
| Argument | Effect |
|---|---|
--reset |
Reset all settings |
--disable a,b |
Disable named modules |
--mount-path <p> |
(updater) unmount temp DMG |
--dmg-path <p> |
(updater) delete downloaded DMG |
External hosts
| Host | Purpose |
|---|---|
api.mac-stats.com |
Updates + public IP |
api.github.com |
Fallback update check |
app.system-stats.com |
Remote dashboard (opt-in) |
Key notifications
.toggleModule, .togglePopup, .toggleSettings, .openModuleSettings, .togglePreview, .pause, .moduleRearrange, .toggleOneView, .combinedModulesPopup, .remoteState, .remoteAuthenticated, .fanHelperState, .refreshPublicIP, .resetTotalNetworkUsage, .clickInSettings.