Macotron

macotron.system

Read the state of the Mac: CPU, GPU, memory, disk, network totals, processes, battery, and fans. Switch Dark Mode and Low Power Mode, and format the time in another zone.

// CPU and memory pressure in the menu bar, every two seconds.
function render() {
  const cpu = Math.round(macotron.system.cpu().usage);
  const { pressure } = macotron.system.memory();
  macotron.menubar.status("load", {
    title: cpu + "%",
    color: pressure === "normal" ? undefined : "orange",
  });
}

render();
macotron.every(2000, render);

CPU and GPU

cpu(): { usage, performance, efficiency, performanceCores, efficiencyCores }

usage is the busy percentage, 0…100, of all cores together since the previous call. The counters are shared by all plugins, so the window runs from whichever plugin called last. Call it on a steady timer for a steady reading.

performance and efficiency are the busy percentages of each core cluster on Apple silicon. performanceCores and efficiencyCores are the logical core counts. An Intel Mac reports every core as a performance core and efficiencyCores as 0.

gpu(): { name, usage } | null

The GPU's name and its utilization in percent. usage is 0 when macOS does not report it. Returns null when the Mac has no Metal GPU.

processes(limit?: number): Promise<Array<{ name, pid, cpu }>>

The processes using the most CPU, highest first. limit defaults to 10. cpu is the percentage that ps reports, where 100 is one full core, so a busy process can go above 100. name is the executable path or name.

Memory, disk, and network

memory(): { total, used, free, active, inactive, wired, compressed, pressure }

All sizes are bytes. used is active plus wired plus compressed memory, and free is total minus used, so free includes the file cache.

pressure is "normal", "warning", or "critical": the same signal as Activity Monitor's memory pressure graph. Read pressure to warn about memory. Do not compare used to total: macOS fills free memory with cache on purpose, so a Mac that looks nearly full with normal pressure is healthy.

disk(): { total, free, used }

Bytes on the startup volume. free is the space available for important use, which counts purgeable space that macOS can clear.

network(): { bytesIn, bytesOut }

Total bytes received and sent on every interface except loopback, since the interfaces came up. Take two readings and subtract to get a rate.

The counters come from 32-bit fields, so each interface wraps at 4 GiB. A rate that goes negative means a counter wrapped; skip that sample. For per-interface counters, see macotron.network.counters().

Battery and Low Power Mode

battery(): Battery

FieldMeaning
levelCharge in percent, 0…100. -1 on a Mac with no battery.
chargingtrue while on AC power, even when the battery is full. Read charged to tell the two apart.
chargedtrue when the battery is full.
timeRemainingMinutes until empty, or -1 when unknown.
timeToFullMinutes until full, or -1 when unknown.
source"ac" or "battery".
healthMaximum capacity as a percent of the design capacity. Only when known.
cyclesCharge cycle count. Only when known.
wattsThe power adapter's wattage. Only when known.
lowPowerModeWhether Low Power Mode is on.

setLowPowerMode(enabled: boolean): Promise<{ ok, lowPowerMode, error? }>

Turns Low Power Mode on or off for both battery and power adapter, with pmset. macOS asks the user for an administrator password, and the promise resolves after the user answers. If the user cancels, ok is false, error says why, and lowPowerMode is the current setting.

Fans

Any plugin can read fan speeds. Setting a minimum speed, the fan floor, needs the Macotron background helper, because the fans are changed with root privileges. To offer the helper, put "helper" in the permissions of macotron.plugin(). The plugin's Settings page then shows an Install button.

fans(): FanState

FieldMeaning
availabletrue when this Mac has fans that can be read.
controllabletrue when a floor can be set now: the Mac has fans and the helper is installed.
floorThe minimum in percent while Macotron holds one. Absent when the fans follow macOS.
errorWhy the fans could not be read, when they could not.
fansArray<{ index, rpm, min, max }>: the current, minimum, and maximum speed of each fan in RPM.

setFanFloor(percent: number | null): Promise<FanState>

Holds the fans at or above percent of the range from each fan's minimum to its maximum speed. Values above 100 count as 100. null, or a value of 0 or less, gives control back to macOS. Resolves with the new fans() state. When the helper is not installed or cannot be reached, the state carries an error.

The floor never lowers a fan below the speed macOS wants. A floor below 100% is released when the Mac's thermal state reaches serious, so that macOS can run the fans faster.

A plugin reload keeps the floor only if a plugin sets it again while loading. If no plugin claims it, the fans go back to macOS. When Macotron quits, the helper gives the fans back too, so store the floor (for example in localStorage) and set it again at load.

const saved = Number(localStorage.getItem("fan.floor")) || null;
if (saved) macotron.system.setFanFloor(saved);

Appearance and Focus

darkMode(): boolean

Whether the system shows Dark Mode now. This is the macOS appearance, not Macotron's own Settings theme. To pick colors for the menu bar, read macotron.menubar.isDark() instead, because the menu bar follows the wallpaper.

setDarkMode(on: boolean): Promise<{ ok, darkMode, error? }>

Switches the system to Dark or Light Mode. If the direct switch is not available, Macotron asks System Events instead, and macOS can ask the user to allow that the first time.

appearance(): "light" | "dark" | "auto"

The appearance setting. "auto" means macOS switches with the time of day; it reads "auto" whichever theme shows now.

setAppearance(mode: "light" | "dark" | "auto"): Promise<{ ok, appearance, error? }>

Sets the appearance. "light" and "dark" turn off automatic switching first. Throws a TypeError for any other mode.

const next = { light: "dark", dark: "auto", auto: "light" }[macotron.system.appearance()];
macotron.system.setAppearance(next);

focus(): { focused: boolean }

Whether a Focus mode, such as Do Not Disturb, Sleep, or Work, is on. It reads false until macOS gives Macotron access to Focus status. You cannot change Focus from here.

home(): string

The path of the user's home folder, such as "/Users/alex". Every macotron.fs call already expands ~; use this when you build a path to compare against, or one for a tool that does not.

Locale and time zones

The JavaScript runtime has no Intl, so use these calls to format for the user's locale or for another zone.

locale(): { language, region, measurement, hour12 }

language is a code such as "en" and region a code such as "US"; either can be "". measurement is "metric" or "us". hour12 is true when the user's clock shows AM and PM.

timeIn(zone: string, format?: string): string

The time now in an IANA zone such as "Europe/London", or in a zone abbreviation such as "PST". format is a Unicode date pattern and defaults to "HH:mm". Returns "" for a zone macOS does not know.

macotron.system.timeIn("Asia/Tokyo");             // "02:42"
macotron.system.timeIn("Asia/Tokyo", "EEE h:mm a"); // "Fri 2:42 AM"

See also