内容
# ☕ Niacin
**[niacin.dort.zone](https://niacin.dort.zone/) · 一款 macOS 状态栏工具,保持计算机唤醒 — 由您或您的 AI 代理驱动。**
Niacin 按需阻止计算机休眠,对保持唤醒状态有精细控制:系统、显示屏或两者。每项设置都可以通过 JAMF(或任何 MDM)管理的首选项 plist 进行锁定和强制执行。支持 Model Context Protocol 的 AI 代理可以直接通过仅限本地的 MCP 端点驱动 Niacin。
一个沙盒构建,以两种方式分发:作为此存储库的 GitHub Releases 中的 `.dmg` / `.pkg` 文件,以及通过 Mac 应用商店。相同的二进制文件,相同的捆绑 ID,相同的功能集。
---
## 功能
- **状态栏优先** — 位于状态栏中,无 Dock 图标,无杂乱
- **灵活的睡眠控制** — 在完全唤醒、仅系统唤醒或按激活锁定之间选择
- **定时激活** — 激活 5 分钟至无限时间,或通过策略设置硬上限
- **AI 代理原生** — 本地 MCP 服务器允许 Claude Desktop、Claude Code、Cursor 等 MCP 客户端直接通过 `keep_awake` 工具请求保持唤醒声明,并使用 bearer-token 认证
- **可通过 MDM 管理** — 每项设置都可以通过 JAMF(或任何 MDM)管理的首选项 plist 进行锁定和强制执行
- **锁定指示器** — IT 控制的设置显示 🔒 图标;用户无法覆盖它们
- **托管策略部分** — 设置窗口显示活动 IT 约束
- **沙盒化** — 应用沙盒开启,强化运行时开启,除了 MCP 监听器(仅限环回)之外,默认情况下没有其他权限
---
## 工作原理
Niacin 直接持有 IOKit 电源声明(`IOPMAssertionCreateWithName`)— 没有生成的 `caffeinate` 子进程,没有内核扩展,没有后台守护进程。如果进程死亡,声明会自动释放,因此崩溃或强制退出不会导致“保持唤醒”状态卡住。
| 模式 | 声明类型 | 效果 |
|---|---|---|
| 完全唤醒 | `PreventUserIdleSystemSleep` + `PreventUserIdleDisplaySleep` | 系统和显示屏保持开启 |
| 仅系统唤醒 | `PreventUserIdleSystemSleep` | 系统保持唤醒;显示屏可以睡眠和锁定 |
| 定时激活 | (以上,加上内部定时器) | Niacin 在截止日期释放声明 |
MCP 驱动的会话与用户发起的声明分开,因此用户结束的会话不会取消正在进行的代理的保持唤醒状态,反之亦然。
---
## 要求
- macOS 14 Sonoma 或更高版本
- 无额外依赖
---
## 安装
### 从源代码构建
1. 克隆存储库
2. 在 Xcode 中打开 `niacin.xcodeproj`
3. 在 **Signing & Capabilities** 中设置您的开发团队
4. 构建并运行(`⌘R`)
> **注意:** 要禁止 Dock 图标,请在 Xcode 的 Info.plist 属性中为目标添加 `Application is agent (UIElement)` = `YES`。
---
## MDM / JAMF 配置
Niacin 从以下位置自动读取托管首选项:
/Library/Managed Preferences/com.oldsalt.niacin.plist
托管域中存在的任何键都覆盖用户的偏好并锁定相应的 UI 控制。缺失的键保持完全用户控制。
### 所有托管键
| 键 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| `enabled` | Bool | `true` | 主开关 — 设置为 `false` 以完全禁用应用 |
| `activateOnLaunch` | Bool | `false` | 每次应用启动时强制激活 |
| `allowIndefinite` | Bool | `true` | 允许无限(无超时)激活 |
| `allowUserToDisable` | Bool | `true` | 用户是否可以手动停用 |
| `disableQuit` | Bool | `false` | 从状态栏菜单中移除 Quit |
| `allowDisplaySleep` | Bool | *(用户)* | 锁定“允许屏幕睡眠”切换 |
| `preventDeviceLock` | Bool | *(用户)* | 锁定“防止设备锁定”切换 |
| `deactivateOnUserSwitch` | Bool | *(用户)* | 在快速用户切换时自动停用 |
| `maxDurationSeconds` | Integer | *(无)* | 任何单个激活的硬上限(秒) |
| `allowedDurations` | Integer 数组 | *(默认值)* | 覆盖可用时长列表 |
| `mcpServerEnabled` | Bool | `false` | 启用本地 MCP(Model Context Protocol)服务器。启用时:Niacin 在 `127.0.0.1` 上绑定一个 HTTP 监听器(默认为端口 11473),以便配对的 AI 代理(如 Claude Desktop、Claude Code、Cursor)可以通过 bearer-token 认证的 JSON-RPC 调用 `keep_awake`、`release_awake` 和 `status` 工具。令牌由用户在设置中生成并存储在 Keychain 中。无外部流量。 |
### 示例 plist
典型的企业部署,保持系统唤醒,但强制执行屏幕锁定策略,最大会话时间为 4 小时:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- 应用已启用 -->
<key>enabled</key>
<true/>
<!-- 用户可以关闭,但应用在启动时自动激活 -->
<key>activateOnLaunch</key>
<true/>
<key>allowUserToDisable</key>
<true/>
<!-- 屏幕必须能够睡眠 — 遵循公司锁屏策略 -->
<key>allowDisplaySleep</key>
<true/>
<!-- 不允许设备锁定预防 -->
<key>preventDeviceLock</key>
<false/>
<!-- 用户切换账户时始终停用 -->
<key>deactivateOnUserSwitch</key>
<true/>
<!-- 最大单次激活:4 小时 -->
<key>maxDurationSeconds</key>
<integer>14400</integer>
<!-- 可用时长:15 分钟、30 分钟、1 小时、2 小时、4 小时 -->
<key>allowedDurations</key>
<array>
<integer>900</integer>
<integer>1800</integer>
<integer>3600</integer>
<integer>7200</integer>
<integer>14400</integer>
</array>
<!-- 不允许无限激活 -->
<key>allowIndefinite</key>
<false/>
</dict>
</plist>
```
### 展厅 / 显示模式示例
锁定设备完全唤醒,无用户控制:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>enabled</key>
<true/>
<key>activateOnLaunch</key>
<true/>
<key>allowUserToDisable</key>
<false/>
<key>disableQuit</key>
<true/>
<key>allowDisplaySleep</key>
<false/>
<key>preventDeviceLock</key>
<true/>
<key>allowIndefinite</key>
<true/>
<key>allowedDurations</key>
<array>
<integer>0</integer>
</array>
</dict>
</plist>
```
### 使用 JAMF 部署
1. 在 JAMF Pro 中,导航到 **Computers → Configuration Profiles**
2. 添加新的配置文件并选择 **Application & Custom Settings**
3. 将首选项域设置为 `com.oldsalt.niacin`
4. 上传您的 plist 或手动输入键
5. 将配置文件范围限定到目标计算机或组
macOS 将 plist 写入 `/Library/Managed Preferences/`,Niacin 将在下次启动时读取,无需重启。
---
## IT 管理员
对于使用 Niacin 管理的 IT 团队,相关的信号是:
- 每个行为都由单个托管首选项 plist 控制(参见上面的表格)。配置文件推送在 ~200 毫秒内生效 — 无需注销循环。
- 每次激活事件的审计日志,可通过 `log show` 检索。
- `mcpServerEnabled` 允许代理显式驱动保持唤醒。如果您需要“在夜间部署期间保持唤醒”,请从 MDM 设置 `pmset schedule wake`,并将其与 `activateOnLaunch=true` 和 `maxDurationSeconds` 上限结合;Niacin 不再扫描 `sysctl` 以部署守护进程(沙盒禁止,`pmset` 是 macOS 公认的机制)。
### 示例配置配置文件
一个可自定义的 `.mobileconfig` 文件位于 [`examples/niacin.mobileconfig`](examples/niacin.mobileconfig)。它包含所有强制活动键以及标准企业锁定(`maxDurationSeconds`、`allowIndefinite=false` 等)。在推送之前:
1. 将三个 `PayloadUUID` 值替换为 `uuidgen` 的新值
2. 将 `YOUR-ORG` 替换为您的组织名称
3. 使用 MDM 的签名证书签名配置文件(`security cms -S` 或通过 JAMF/Kandji/Mosyle 的签名 UI)
4. 上传到您的 MDM 并范围限定到适当的计算机组
### 审计
每次强制活动事件都记录在统一日志中,位于 `subsystem=="com.oldsalt.niacin" category=="audit"` 下。从托管设备:
```sh
sudo log show --predicate 'subsystem=="com.oldsalt.niacin" AND category=="audit"' --info --last 24h
```
期望类似 `force-active begin: reason=deploy matches=["jamf"]` 和 `force-active end: reason=deploy` 的行。SIEM 代理可以直接摄取统一日志(CrowdStrike Falcon、Splunk 等)— 参见 [保留和 SIEM 集成](#保留和-siem-集成)。
### 即时策略重新加载
配置文件推送在 ~200 毫秒内生效,无需重启应用 — Niacin 通过 kqueue 监视 `/Library/Managed Preferences/`。您不需要 `launchctl kickstart` 或注销循环来应用 JAMF 配置文件推送。
---
## 自动化
Niacin 注册了一个 `niacin://` URL 方案。任何可以 `open` URL 的工具(shell、Shortcuts、Calendar reminders、Stream Deck、webhooks、Hammerspoon)都可以在不针对菜单 UI 进行脚本编写的情况下驱动激活和停用。
### URL 方案
```sh
# 无限期激活
open "niacin://activate"
# 激活 30 分钟(1800 秒)
open "niacin://activate?duration=1800"
# 无限期激活(显式)
open "niacin://activate?duration=indefinite"
# 停用当前会话
open "niacin://deactivate"
```
URL 驱动的激活遵循每个托管首选项防护(`enabled`、`allowIndefinite`、`maxDurationSeconds` 等)— IT 托管的安装可以锁定 URL 方案调用者允许执行的操作,与菜单 UI 相同。
### `niacin run -- <command>`
在命令执行期间激活 Niacin,并在命令退出时停用 — 清洁、错误或 Ctrl+C:
```sh
niacin run -- make build
niacin run -- xcodebuild test -scheme MyApp
niacin run -- bash -c 'sleep 3600 && say "done"'
```
将包装器安装到您的 `$PATH` 中:
```sh
curl -fsSL https://raw.githubusercontent.com/just-an-oldsalt/niacin/main/scripts/niacin -o /usr/local/bin/niacin
chmod +x /usr/local/bin/niacin
```
包装器当前仅支持 `run` 子命令。完整的 Swift CLI(`niacin status`、`niacin activate 30m`、`niacin watch <pid>`)计划在 v2.1 中实现;`run` 形状将保持稳定,因此现有脚本继续有效。
---
## 日志记录和诊断
Niacin 通过 Swift 的 `os.Logger` 将所有状态更改事件和诊断信息记录到 macOS 的统一日志系统中,位于 `com.oldsalt.niacin` 子系统下。IT 管理员可以提取、过滤和分析这些日志以进行故障排除、合规审计和舰队监控。
### 快速提取
要为支持票导出过去一小时的 Niacin 日志:
```
log show --predicate 'subsystem == "com.oldsalt.niacin"' --info --last 1h
```
添加 `--debug` 以捕获调试级别的跟踪(通常被抑制):
```
log show --predicate 'subsystem == "com.oldsalt.niacin"' --info --debug --last 1h
```
### 实时跟踪
对于实时故障排除,请实时监视日志:
```
log stream --predicate 'subsystem == "com.oldsalt.niacin"' --info
```
### 按类别过滤
每个日志消息都属于一个类别。要查看过去 24 小时内的仅策略事件:
```
log show --predicate 'subsystem == "com.oldsalt.niacin" && category == "policy"' --info --last 24h
```
### 舰队收集和存档
使用 `log collect` 捕获一个带有时间戳的存档,可以传输进行集中分析:
```
log collect --last 7d --output /tmp/niacin-diag.logarchive
```
然后在另一台机器上用存档打开:
```
log show --archive /tmp/niacin-diag.logarchive --predicate 'subsystem == "com.oldsalt.niacin"'
```
### 日志类别
| 类别 | 包含 | 有用信息 |
|---|---|---|
| `policy` | 激活/停用事件、策略强制决策、策略阻止操作、会话终止原因 | 审计跟踪、验证策略强制执行 |
| `policy-watcher` | 托管 plist 路径的文件系统事件、实时重新加载通知 | 确认配置文件推送是否到达设备 |
| `managed-prefs` | 托管首选项键读取、解析错误 | 诊断格式错误的 `.mobileconfig` 或意外键值 |
| `sleep-preventer` | IOKit 声明生命周期(获取/释放)、IOReturn 错误代码 | 确认睡眠预防引擎正在运行 |
### 保留和 SIEM 集成
macOS 默认保留统一日志输出约 7 天。对于更长时间的保留,使用 `log collect` 创建持久存档,或配置 SIEM 代理(CrowdStrike Falcon、Splunk 等)直接摄取统一日志。SIEM 转发不受 Niacin 本身管理。
---
## 更新
**Niacin Enterprise** 没有自我更新。IT 通过 MDM 以 `.pkg` 文件推送新版本。构建不会联系更新服务器进行更新检查。
**Niacin** (Mac 应用商店)通过应用商店的标准机制更新。Niacin 中的代码不会联系更新服务器。
---
## AI 代理集成(MCP)
在 **设置 → AI 代理集成** 中启用 MCP 服务器。Niacin 将:
1. 绑定一个仅限本地的 HTTP 监听器(默认端口 `11473`,如果默认端口被占用,则回退到 `11479`)。
2. 生成一个 bearer token,创建后显示一次,然后存储在 Keychain 中。
3. 显示一个 **复制配置代码段** 按钮,生成一个可粘贴的 JSON 代码块,供 MCP 客户端使用。
代码段具有 MCP-over-HTTP 客户端期望的格式:
```json
{
"mcpServers": {
"niacin": {
"url": "http://127.0.0.1:11473",
"headers": { "Authorization": "Bearer <token>" }
}
}
}
```
### 暴露的工具
| 工具 | 参数 | 效果 |
|---|---|---|
| `keep_awake` | `duration_seconds?` (int),`reason` (string),`allow_display_sleep?` (bool),`client?` (string) | 持有电源声明。返回 `session_id` 和 `expires_at`。 |
| `release_awake` | `session_id?` (string) | 释放一个会话,或省略时释放所有 MCP 持有的会话。 |
| `status` | — | 报告当前保持唤醒状态和所有持有声明的源标签。 |
每次调用都记录在 `subsystem="com.oldsalt.niacin" category="audit"` 下,并带有调用客户端的自我报告名称(如果提供)和原因文本。会话在声明的持续时间内通过挂钟任务自释放。
---
## 设置窗口
设置窗口反映当前的策略状态:
- IT 控制下的切换显示 **锁定图标**,且无法更改
- **由组织管理** 部分在激活任何策略时自动出现,列出所有强制约束
---
## 致谢
灵感来自 [KeepingYouAwake](https://github.com/newmarcel/KeepingYouAwake) by Marcel Dierkes。
---
## 许可证
MIT