niacin 2.0 — for the people running models, deploys, and scripts overnight
The 1.x line was about getting the engine right: live MDM-managed policy, dropping the caffeinate subprocess for direct IOKit assertions, and learning to fail loudly instead of silently. 2.0 is a different kind of release. It’s about three audiences whose use cases didn’t fit anywhere in the 1.x story — the person running a 70B model overnight on a Mac mini, the IT admin pushing a JAMF deploy at 3 AM, and the developer who’s been wrapping every long command in caffeinate -i for years. 2.0 has flagship features for each of them.
The niacin:// URL scheme — every other automation tool becomes a niacin trigger
niacin now registers a niacin:// URL scheme. Anything that can open a URL — a shell, a Calendar reminder, Shortcuts, a Stream Deck button, a webhook, Hammerspoon — can drive activation and deactivation without touching the menu UI:
open "niacin://activate" # indefinite
open "niacin://activate?duration=1800" # 30 minutes
open "niacin://deactivate"
URL-driven activations honour every managed-preferences guard (enabled, allowIndefinite, maxDurationSeconds), because they dispatch through the same AppState.activate path as the menu UI. IT-managed installs can lock down what URL-scheme callers are allowed to do with the same plist keys that lock down the menu.
It’s a small piece of code — an NSApplicationDelegate hook on open(_:) and a URL parser — but the surface-area unlock is the largest in the release. Every automation tool you already own works with niacin now.
niacin run -- <cmd> — one wrapper to replace a thousand caffeinate -is
The companion to the URL scheme is niacin run, a shell wrapper that activates niacin for the duration of a command and deactivates when it exits — cleanly, with an error, or via Ctrl+C:
niacin run -- make build
niacin run -- xcodebuild test -scheme MyApp
niacin run -- bash -c 'sleep 3600 && say "done"'
Functionally it’s caffeinate -i ./long-job.sh, but it surfaces in the menu bar (so you can see at a glance whether the assertion is still held), it’s deactivated through the same code path as the menu (so it can’t leak across crashes), and the cleanup happens via trap EXIT INT TERM instead of relying on the kernel to reap an orphaned caffeinate subprocess. Install with curl — see the README for the one-liner. A full Swift CLI (niacin status, niacin activate 30m, niacin watch <pid>) is planned for 2.1; the run shape stays stable.
AI runtime auto-detect — the audience nobody else serves
Apple Silicon Macs have quietly become the cheap entry point into 64–192 GB unified-memory AI workstations. Mac mini orders are sold out at multiple retailers for this reason. The use case is: someone kicks off a 70B model overnight on an M-series mini and discovers at breakfast that macOS slept the box and killed the run. None of the existing menu-bar caffeine apps address this.
niacin 2.0 ships a hardcoded watchlist of known local-AI runtimes — Ollama, LM Studio, llama.cpp server, MLX, mlx-lm, ComfyUI, InvokeAI, Stable Diffusion WebUI, vLLM, and mistralrs — and force-activates while any of them is loaded. There’s a new Auto-activation section in Settings with a single toggle to opt in. It’s off by default; the AI-workstation audience flips it on once.
The implementation is a 5-second-interval ProcessWatcher matching against kinfo_proc.p_comm via sysctl(KERN_PROC). When a match appears, niacin holds its own IOKit power assertions — separate from any user-session activation — so the system stays awake whether you manually clicked Activate or a watcher matched, or both. The matches drop out of the set when the process exits, and the assertions release automatically.
Ollama active-inference detection — because “Ollama is running” isn’t the right question
The trouble with process-presence detection alone is that Ollama is commonly installed as a launchd autostart service. It sits in the background, port 11434 listening, with no model loaded into VRAM. If process-presence were the only signal, niacin would keep the box awake 24/7 for the cost of having Ollama installed.
2.0 adds an active-inference probe on top. Every 30 seconds, it polls http://127.0.0.1:11434/api/ps — the same endpoint ollama ps uses to show which models are currently resident in VRAM. When the models array has been empty for 5 minutes (which matches Ollama’s default 5-minute model keep-alive), niacin drops the Ollama-specific entry from the active set and releases force-active. The next model load triggers it back on the next 30-second tick.
The probe stays out of the way when Ollama is unreachable (don’t flap state on a transient network glitch); the process watcher owns the “Ollama is gone” transition. Non-Ollama runtimes don’t have a comparable probe yet — 2.1 will extend the pattern to LM Studio, llama.cpp, and vLLM.
Force-active for IT deploys — the JAMF chapter
niacin’s biggest existing differentiator over Amphetamine / KeepingYouAwake / Lungo has been live MDM-managed policy. 2.0 leans into the unattended-deploy use case where IT pushes a software update overnight and needs the device to stay awake without user involvement.
Two new managed keys:
forceActiveDuringDeploys— an array of process-name patterns. When any matches a running process (jamf,installd,softwareupdated,munki,IntuneMdmAgent,mdmclient,Installer), niacin force-activates silently — no menu bar flicker, no user prompt. The user wakes up to a finished deploy instead of a half-applied update.forceActiveDuringApps— same shape, for user-facing apps where sleep-during-use is unacceptable (Zoom, Teams, OBS, your custom internal tools).
Both feed into the same ProcessWatcher infrastructure as the AI runtime detection. All three watchers run independently, hold their own assertions, and macOS composes them — so a deploy can complete after the user has manually deactivated for the night.
Audit logging — for the compliance review
Every force-active event lands in the unified log under subsystem=="com.oldsalt.niacin" category=="audit":
force-active begin: reason=deploy matches=["jamf"]
force-active end: reason=deploy
force-active begin: reason=ai-runtime matches=["ollama"]
ollama-inference: idle=true
force-active end: reason=ai-runtime
From any managed device:
sudo log show --predicate 'subsystem=="com.oldsalt.niacin" AND category=="audit"' --info --last 24h
SIEM agents (CrowdStrike Falcon, Splunk, etc.) ingest the unified log directly, so fleet-wide visibility is a forwarder config change, not a niacin change. There’s a new For IT admins section in the README walking through the keys, the audit predicate, and the sample profile.
A sample .mobileconfig in the repo
A ready-to-customise Configuration Profile is now in the niacin repo at examples/niacin.mobileconfig. It covers all the v2.0 force-active keys plus the standard enterprise lockdown (disableAutoUpdate, maxDurationSeconds, allowIndefinite=false, etc.). Drop it into JAMF Pro, Kandji, Mosyle, or Intune; replace the placeholder PayloadUUID values with fresh ones from uuidgen; sign it with your MDM’s signing certificate; push.
Sparkle: daily checks, and a small telemetry change worth flagging
2.0 simplifies the Software Update section in Settings down to one button: Check Now. The auto-update toggle and the Daily/Weekly/Monthly picker are gone — niacin checks once every 24 hours, period. There’s exactly one escape hatch: the existing disableAutoUpdate managed key, for organisations whose policy is “IT pushes builds via JAMF, suppress all outbound update traffic.”
The reasoning: niacin is a 1.6 MB utility where staying current matters more than the toggle. A user who turns off auto-update and forgets silently falls off the security/compatibility track, and that’s exactly the wrong failure mode for a sleep-prevention tool that’s also an MDM endpoint. Daily checks at every install is a better default.
The other change in the same area: SUEnableSystemProfiling is now on. Every appcast fetch now includes the OS version, CPU architecture and model, RAM, and locale as query parameters. No identifiers, no per-user data, no installation IDs — this is Sparkle’s built-in profile feature, the same one MailMate and Transmit use. It gives us breakdowns like “X% of installs are on Apple Silicon with > 32 GB RAM” so we can make minimum-OS and architecture-support decisions with real data instead of guessing. Documented in the README under Update checks and telemetry; disableAutoUpdate=true suppresses these requests entirely for organisations that don’t want them leaving the device.
Gentle countdown end — the small one that’ll surprise you
Timed sessions used to end in a hard cutoff: the assertion released, the screen locked, and you’d find yourself wondering why your 2-hour render lost its display 90 seconds ago. 2.0 fixes that. The last 30 seconds of any timed session, the countdown text in the menu bar turns orange, and (optionally; opt in via Settings) a single NSSound.beep() plays. It’s a small change with a big “oh, that’s nicer” payoff.
Upgrading
On 1.3 or later from a direct download? Sparkle will offer 2.0 the next time it polls the appcast (now: daily, not on whatever schedule you used to have set), or right now via Settings → Software Update → Check Now.
On 1.0–1.2 from a GitHub release? Download the 2.0 .zip from the releases page, replace the app in /Applications, and relaunch — from there forward you’re on the auto-update track. Mac App Store users stay on the App Store channel; the new triggers and Sparkle changes are dormant in those builds.
Source, the new For IT admins guide, and the sample .mobileconfig are on GitHub at github.com/just-an-oldsalt/niacin. If 2.0 changes a behaviour in a way that breaks something for your setup, open an issue or email niacin@dort.zone.