A native macOS menu bar app that monitors your Mac's system health in real time — CPU, memory, thermals, battery, fans, disk, and processes — with automatic anomaly detection, a composite health score, and a Claude companion plugin that can diagnose problems and recommend fixes.
macOS hides the data you actually need. Activity Monitor shows you CPU usage but not thermal throttling state. System Information shows battery cycle count but not drain rate. The menu bar extras built into macOS show a single CPU bar with no history, no context, and no help interpreting what you're seeing.
MacWatch fills that gap. It's a Swift app that shells out to vm_stat, ioreg, pmset, ps, and smctemp — the same UNIX tools you'd run manually — and presents everything as a live dashboard with a composite health score and automatic anomaly logging. The Claude companion plugin connects that data directly to Claude so you can ask questions in plain English and get specific answers.
Typical use cases:
- Your Mac is running hot and fans are loud — MacWatch shows you exactly which process is responsible and whether the CPU is thermally throttling
- Something is consuming your battery faster than normal — MacWatch surfaces drain rate, battery temperature, and the top CPU offenders simultaneously
- Your Mac feels sluggish — MacWatch correlates memory pressure, swap usage, and compressed memory so you can tell whether you need more RAM or just need to kill something
- You want a persistent health canary in your menu bar without paying for a commercial tool
Reads CPU, GPU, battery, and ambient temperatures using smctemp -l — a single pass that parses all SMC temperature keys at once. MacWatch maps the raw SMC key names to human-readable sensors:
- CPU die temp —
TC0D/TC1D(Intel) orTp*cluster dies (Apple Silicon). This is the temperature the CPU actually runs at, not the case temp. At 85 °C the CPU begins reducing clock speed to protect itself; at 95 °C throttling is severe and sustained. - GPU die temp —
TG0D/TG0P. GPU thermals are usually less critical than CPU on Macs, but sustained GPU temps above 90 °C on a fanless machine (MBA) indicate it's working harder than the chassis can dissipate. - Battery temp —
TB1T/TB0T. A warm battery (> 35 °C) charges slower than a cool one. A hot battery (> 45 °C) while plugged in is a sign the adapter is working hard — normal under load, worth watching if it's idle. - Ambient —
TA0P. The temperature inside the chassis near the logic board. Useful for distinguishing "hot room" from "hot chip."
Throttling detection reads sysctl machdep.xcpm.cpu_thermal_level. A value of 0 means the CPU is running at full speed. Values 1–127 indicate active thermal throttling — the CPU is reducing its clock speed below its rated maximum to prevent damage. A value above 50 sustained for more than a few minutes means your Mac cannot keep up with its thermal load at this workload.
Reads vm_stat and sysctl to build a full picture of how macOS is using your RAM:
- Wired — memory the kernel has locked in physical RAM and cannot page out. Drivers, kernel extensions, and some system services contribute to this. High wired memory (> 4 GB on a 16 GB Mac) is not inherently a problem but reduces how much is available to apps.
- Active — pages currently in use by running apps. This is what you think of as "used RAM."
- Inactive — pages that were recently used but are available if an app needs them. macOS caches aggressively; high inactive is healthy.
- Compressed — pages the memory compressor has compressed in place rather than writing to disk. Apple Silicon Macs have extremely fast memory compression. Some compressed memory is normal; a lot means you're running close to your physical limit.
- Free — genuinely unused pages. On a healthy macOS system, free RAM is low — the OS fills it with disk cache and inactive pages because idle RAM is wasted RAM.
What actually matters for performance is not "free RAM" but memory pressure: the ratio of compressed + inactive to total. When pressure is high, macOS starts writing memory to the swap file on disk. Even on an NVMe SSD, swap I/O is orders of magnitude slower than RAM access, and you'll feel it.
- Swap in/out — bytes read from and written to the swap file since boot. Any swap-in is meaningful: it means macOS needed pages it had to fetch from disk. Heavy swap-in (> 1 GB/session) on a machine with 16+ GB RAM points to a specific process leaking memory rather than simply needing more RAM.
On Intel Macs, machdep.xcpm.cpu_thermal_level directly controls how many performance states the CPU is allowed to occupy. On Apple Silicon, the equivalent signal is the P-state distribution visible in powermetrics. When a Mac throttles:
- Clock speed drops — usually to 50–80% of rated speed first, then lower if temps keep rising.
- Work takes longer — a task that normally takes 2 seconds might take 4.
- The CPU is doing less work per watt, not safer work. Thermal throttling is the CPU protecting itself from a cooling bottleneck, not from overuse.
The fix is almost always one of: (a) a process you don't know is running is burning CPU, (b) your vents are blocked or fans aren't spinning, (c) your Mac's thermal paste is degraded (> 5 years old).
Reads ioreg -r -c AppleSmartBattery and pmset -g batt to surface the metrics Apple doesn't show you by default:
- Battery health % —
NominalChargeCapacity / DesignCapacity × 100. This is the same number Apple's Battery Health indicator shows, computed the same way. Below 80% Apple considers the battery degraded. Below 70% you'll notice a meaningful reduction in unplugged runtime. - Cycle count — one cycle = 100% of the battery's capacity discharged, not one charge session. Charging from 50% to 100% twice = one cycle. Apple's rated cycle counts are 500–1000 depending on model. Beyond that, health degrades faster.
- Battery temperature — read from the
Temperaturekey in ioreg (centidegrees → °C). Lithium batteries age faster when hot. Charging at > 40 °C accelerates capacity loss compared to charging at room temperature. - Charge state —
pmset -g battreports whether you're on AC or battery, current charge %, and the estimated time remaining (macOS computes this from rolling drain rate). - pmset event log — the last 15 lines of
pmset -g logcapture thermal events, sleep/wake transitions, and battery fault notices. Useful for spotting patterns like "Mac sleeps during video calls" (thermal event during peak GPU load).
Reads ps -axo pid,ppid,pcpu,pmem,rss,etime,comm,command — the full process table, no sudo required. Per-process data:
- CPU % — instantaneous CPU usage. MacWatch keeps a 3-sample rolling buffer per PID to detect sustained high CPU (> 25% across 3 consecutive polls = 45 seconds). A process that spikes to 90% and returns to 0% is doing real work. A process sitting at 40% for 10 minutes while your fans spin is a runaway.
- Memory (RSS) — resident set size: how much physical RAM the process is actually occupying right now (not virtual address space, which is always huge). A process whose RSS grows monotonically over hours without ever shrinking is leaking memory.
- Elapsed time — how long the process has been running. A short-lived process (< 1 min) that keeps reappearing is being respawned — usually by a LaunchAgent.
Process archaeology links each process back to the LaunchAgent plist that launched it, if one exists. This answers "where did this process come from and what will restart it if I kill it?" — information ps alone can't give you.
MacWatch computes a single 0–100 health score updated every poll cycle:
Start at 100
− 15 for each critical metric (CPU temp > 95 °C, battery health < 70%, etc.)
− 5 for each warning metric (CPU temp > 85 °C, high swap, etc.)
− 10 if an entire sensor is in critical state
− 4 if an entire sensor is in warning state
− 3 per anomaly event in the last hour (capped at −15 from this source)
Floor: 0
The score is graded: Excellent (90–100), Good (70–89), Fair (50–69), Poor (< 50). MacWatch persists the score history to SQLite so you can see how your Mac's health has trended over time — useful for spotting gradual degradation that's invisible day-to-day.
AnomalyDetector watches each sensor snapshot as it arrives and fires an AnomalyEvent whenever a metric crosses its severity threshold. Events are persisted to SQLite and exposed as a time-ordered list in the dashboard. The most recent 200 events are kept in memory for fast access.
This answers the question "what happened to my Mac last Thursday at 2pm?" without requiring you to have been watching.
A MenuBarExtra shows a color-coded indicator driven by the composite health score:
- Green — score ≥ 70, all sensors healthy
- Yellow — score 50–69, at least one sensor in warning state
- Red — score < 50, critical metric or active anomaly
The popover shows live stats without opening a window — a persistent canary that costs essentially zero performance to run.
This repo contains source code only — there is no pre-built binary. You build it yourself in about 30–60 seconds using the script below.
- macOS 14 Sonoma or later
- Xcode Command Line Tools (free, ~2 GB). If you don't have them:
A dialog will appear — click Install and wait. Skip this step if you already have Xcode installed.
xcode-select --install
You do not need a paid Apple Developer account. You do not need full Xcode.
Step 1 — Open Terminal.
Press ⌘ Space, type Terminal, and press Return.
Step 2 — Download MacWatch:
git clone https://github.com/lswingrover/MacWatch.git ~/Developer/MacWatchStep 3 — Build and install:
bash ~/Developer/MacWatch/scripts/install.shThe script compiles MacWatch from source, assembles the app bundle, and installs it to ~/Applications/MacWatch.app.
Step 4 — Launch MacWatch:
open ~/Applications/MacWatch.appMacWatch appears in your menu bar.
Because MacWatch is ad-hoc signed (not notarized by Apple), macOS may block the first launch:
"MacWatch cannot be opened because it is from an unidentified developer."
Fix — Option A (GUI): In Finder, navigate to ~/Applications, right-click MacWatch.app → Open → click Open in the confirmation dialog. You only need to do this once.
Fix — Option B (Terminal):
xattr -dr com.apple.quarantine ~/Applications/MacWatch.app
open ~/Applications/MacWatch.appWhy is it safe? The ad-hoc signature proves the binary hasn't been tampered with since it was built on your machine. It just lacks Apple's notarization stamp, which is only required for distributing software to other people's machines.
Thermal and fan readings require smctemp:
brew tap narugit/smctemp
brew install narugit/smctemp/smctempWithout smctemp, the Thermal and Fan sensors degrade gracefully — they show "unavailable" rather than crashing. Everything else (memory, battery, processes, disk) works without it.
cd ~/Developer/MacWatch
git pull
bash scripts/install.shThe script replaces the existing ~/Applications/MacWatch.app automatically.
| Metric | Good | Watch | Bad |
|---|---|---|---|
| CPU temp (Apple Silicon) | < 75 °C | 75–90 °C | > 90 °C |
| CPU temp (Intel) | < 80 °C | 80–90 °C | > 95 °C |
| Thermal throttle level | 0 | 1–20 | > 50 |
| Battery health | > 85% | 70–85% | < 70% |
| Battery cycle count (M-series) | < 500 | 500–900 | > 1000 |
| Battery temp | < 35 °C | 35–42 °C | > 45 °C |
| Memory pressure | Low | Moderate | High |
| Swap in (per session) | 0 | < 500 MB | > 1 GB |
| Sustained CPU per process | < 15% | 15–25% | > 25% for > 45s |
| Health score | 90–100 | 70–89 | < 70 |
A high health score with a single red metric usually means one runaway process rather than a systemic problem. A low health score with many yellow metrics usually means the Mac is being pushed to its thermal or memory limit — the fix is reducing workload or upgrading RAM.
MacWatch includes a Claude plugin that connects your live sensor data directly to Claude. Once installed, you can ask questions like:
- "Why is my Mac hot right now?"
- "What's using all my memory?"
- "Give me a prioritized action plan for my Mac's health."
- "Kill the process that's eating my CPU."
Claude reads the MacWatch SQLite snapshots directly, so it's working with the same data you see in the dashboard — not guessing.
Step 1 — Open the Claude desktop app.
Step 2 — Go to Settings. Click the gear icon, then choose Capabilities → Customize.
Step 3 — Add the MacWatch plugin. Under Plugins, click the + button. Navigate to:
~/Developer/MacWatch/companion-plugin/macwatch-companion.plugin
Select it and click Open.
Step 4 — Restart Claude if prompted.
The plugin is active. In any Claude session, describe a problem and Claude will pull the relevant MacWatch data automatically.
| Skill | Trigger phrase | What Claude does |
|---|---|---|
| macwatch-analyze | "Analyze my Mac" or "What does MacWatch show?" | Reads live sensor snapshots and gives a structured health summary with severity ratings across all sensors |
| macwatch-diagnose | "Why is my Mac slow?" or "Diagnose my CPU" | Deep-dives on a specific sensor or issue — pulls raw metrics, process lists, trend history from the SQLite store |
| macwatch-recommend | "What should I do?" or "Give me recommendations" | Generates a prioritized action plan ranked by impact, based on current health score and recent anomalies |
| macwatch-act | "Kill that process" or "Free up disk space" | Takes remedial action on your Mac — kills runaway processes, clears caches, disables suspect LaunchAgents. Always confirms before anything destructive. |
MacWatchApp.swift @main — MenuBarExtra + Settings scenes
Engine/
HealthScoreEngine.swift Composite 0–100 health score; persists history to SQLite
AnomalyDetector.swift Threshold monitor — fires AnomalyEvents, persists to SQLite
ProcessArchaeologyEngine.swift Links processes to their originating LaunchAgent plist
HealthScoreStore.swift SQLite store for health score history
Sensors/
SensorManager.swift 15-second poll orchestrator; fan-out to all connectors
SensorRegistry.swift Singleton factory — register + instantiate sensors by id
SnapshotStore.swift SQLite store for sensor snapshots
ThermalConnector.swift smctemp -l → CPU/GPU/battery/ambient temps + throttle state
MemoryConnector.swift vm_stat + sysctl → wired/active/inactive/compressed/free/swap
PowerConnector.swift ioreg + pmset → battery health, cycle count, temp, charge state
ProcessConnector.swift ps → top CPU/memory consumers, runaway detection, archaeology
FanConnector.swift smctemp -f → fan speeds and RPM
DiskConnector.swift iostat → disk read/write throughput and I/O wait
NetworkConnector.swift netstat → interface throughput, errors, TCP session count
LaunchAgentConnector.swift launchctl → LaunchAgent inventory and status
PowermetricsConnector.swift powermetrics → CPU power draw, efficiency core vs performance core usage
ConnectionMonitorConnector.swift Connectivity canary (pings gateway + 1.1.1.1)
MenuBar/ NSStatusItem, popover, color-coded health indicator
Views/ SwiftUI dashboard views (per-sensor detail, health timeline)
Notifications/ UserNotifications for threshold breaches
Resources/ App icon, entitlements
companion-plugin/
macwatch-companion.plugin Install this in Claude → Settings → Capabilities → Customize
skills/ Skill definitions (analyze, diagnose, recommend, act)
scripts/
install.sh One-shot build + assemble + sign + install script
make_icon.py Generates AppIcon.icns programmatically (no Xcode required)
BUILD.md Developer notes: build options, MCP server, architecture
Why shell out to vm_stat, ioreg, and ps instead of using private frameworks? Apple's private performance frameworks (IOKit, energyd, cornered) require entitlements that either mandate App Store distribution or expose the app to notarization scrutiny for every release. The UNIX tools are public API, stable across macOS releases, and produce the same data. The tradeoff is a ~15–30 ms process-launch overhead per tool per poll cycle — acceptable at a 15-second interval, not viable for sub-second monitoring.
Why 15-second poll intervals? CPU temperature sensors thermally lag the actual die temperature by 2–4 seconds. Memory pressure changes on a seconds-to-minutes timescale. Process CPU accumulation is a 45-second signal (3 samples) by design. Polling faster adds overhead without adding useful signal. The one exception is the connection monitor (pings gateway every 5 seconds) because network failures are instantaneous.
Why a composite health score instead of per-sensor alerts? Per-sensor alerts produce alert fatigue. A Mac under heavy video export might show CPU temp 88 °C, fans at max, and memory pressure moderate — all individually alarming, all contextually expected. The health score aggregates the signals and lets you set one threshold ("tell me when score drops below 70") rather than tuning six separate thresholds for a workload you already understand.
Why SQLite for snapshot history? In-memory state is fast and simple but disappears when the app relaunches. SQLite gives you persistent trend data — "how has my battery health trended over the past 3 months?" — without the overhead of a full database server. The schema is append-only: each poll writes one row per sensor. Queries are simple range selects. No ORM, no migration headaches.
- macOS 14 Sonoma or later
- Xcode Command Line Tools (for building from source)
smctemp(optional, for thermal + fan data):brew install narugit/smctemp/smctemp
- Thermal history charts — per-sensor RTT-style sparklines showing temperature over time
- Auto-start on login —
SMAppServicetoggle in Preferences - Process allowlist — suppress known-good high-CPU processes (e.g., Spotlight indexing, Time Machine) from anomaly scoring
- Battery discharge curve — per-session drain rate chart to identify which app or workload is the battery killer
- Notification profiles — separate thresholds per sensor (e.g., tighter tolerance for battery health than for CPU temp)
MIT — see LICENSE.
These apps are built by the same author and follow the same install pattern — build from source, no App Store, optional Claude companion plugin:
| App | What it does |
|---|---|
| NetWatch | Network monitoring — ping latency, DNS health, Wi-Fi metrics, automatic incident bundling and ISP escalation drafts |
| ClipWatch | Clipboard manager — searchable history, sensitive clip detection, Touch ID, hotkey panel |