内容
# 节奏
一款优先考虑本地化的健康情境引擎。它将WHOOP和Apple Health数据拉取到一个本地存储中,并为Claude提供真实、当前的身体状况信息——睡眠、恢复、应激、锻炼等,而不是盲目工作。

## 诚实的隐私故事
与本投资组合中的其他项目([lantern](https://github.com/rajanshxrma/lantern)、[stacks](https://github.com/rajanshxrma/stacks)、[orchard](https://github.com/rajanshxrma/orchard))不同,cadence无法声称“没有任何数据离开设备”——WHOOP数据必然来自WHOOP自己的云API,通过OAuth;对此无可避免。真正且真实的是:**一旦拉取,您的健康数据仅存储在本地设备上**(位于`~/.cadence/cadence.db`的本地SQLite文件,权限为`0600`,仅所有者可访问),您的OAuth令牌存储在macOS钥匙串中(从不以明文存储在磁盘上,从不在此代码仓库中),cadence从不重新上传或分享任何数据给第三方。Apple Health数据根本不会离开设备——它是一个本地文件导入,无需网络调用。Claude通过MCP本地读取您的数据;对此没有任何网络传输。
## 安装
```bash
pip install -e .
```
**Apple Health(无需设置,完全本地):**
```bash
cadence import-apple-health ~/Downloads/export.zip
cadence brief
```
(从iPhone导出:健康应用 -> 个人资料图标 -> 导出所有健康数据。)
**WHOOP(一次性OAuth设置,见下文):**
```bash
cadence auth # 打开浏览器一次,存储令牌在钥匙串中
cadence sync # 拉取最近30天的恢复/睡眠/锻炼数据
cadence brief
```
**MCP服务器**(让Claude可以实时查询而不是仅读取简报):
```bash
cadence mcp
```
将您的Claude客户端指向它(`claude mcp add cadence -- cadence mcp`,或适用于您的客户端的等效stdio服务器配置)。
## WHOOP设置(一次性)
1. 在[developer.whoop.com](https://developer.whoop.com/)注册一个个人应用(使用您的普通WHOOP账户登录,创建团队如果需要,然后创建应用)。填写:
- **隐私政策**:需要一个真实的链接——`https://github.com/<your-fork>/cadence#the-honest-privacy-story`(此部分)是一个合理的选择用于个人应用。
- **重定向URL**:必须与`cadence.auth.REDIRECT_URI`完全匹配(默认为`https://github.com/rajanshxrma/cadence`)。WHOOP自己的表单仅接受`https://`或自定义URL方案——纯`http://localhost`重定向被拒绝,这就是为什么`cadence auth`使用复制粘贴流程而不是本地回调服务器(参见`auth.py`模块文档字符串)。
- **范围**:选中所有六个数据范围(`read:recovery`、`read:cycles`、`read:sleep`、`read:workout`、`read:profile`、`read:body_measurement`)。表单上没有`offline`复选框——该范围在实际授权请求中动态请求,而不是每个应用的设置。
- **Webhooks**:留空——cadence是基于拉取的(`cadence sync`),无需Webhook接收器。
- 应用立即生效,最多可用于10个WHOOP账户,处于开发状态——个人使用无需批准。
2. 导出您的凭证(在应用页面创建后显示一次——立即复制两者):
```bash
export CADENCE_WHOOP_CLIENT_ID="..."
export CADENCE_WHOOP_CLIENT_SECRET="..."
```
3. 运行`cadence auth`。它会打开您的浏览器;您批准后,WHOOP会重定向到那里,并带有URL中的代码——复制完整的地址栏URL,然后在终端中粘贴回来,当提示时。令牌存储在钥匙串中。之后,`cadence sync`直接生效——刷新自动发生(WHOOP每用一次都会旋转刷新令牌;cadence每次都会保留新的令牌,按照他们的API要求)。
客户端ID/密钥仅从环境变量读取——从不写入磁盘,从不提交。`keyring`(用于令牌存储的库)需要一个OS凭证存储;在macOS上,这是钥匙串,无需额外设置。
## 健康感知的AI会话(v0.2.0)
使这个项目不仅仅是一个数据查看器的部分:`cadence hook`发出一个[Claude Code SessionStart钩子](https://docs.anthropic.com)的有效载体,因此**每天的第一个Claude会话都会打开,已经知道您的生理状态**——昨晚的睡眠,今天的恢复,最近的应激——以及一个一行自适应提示,当数字需要时(低恢复:“倾向于更轻松的工作而不是马拉松式构建”;睡眠不足:标记;数据过期:重新同步的提示而不是沉默死亡)。第二天的会话:沉默,按照设计——一个每次会话都说话的钩子会产生噪音。
```bash
cadence hook # 打印SessionStart JSON(或无 - 沉默是一种功能)
cadence sync --quiet # 无声模式适用于launchd/cron作业;如果需要重新授权,macOS会通知
cadence auth --store-client # 一次性:将客户端凭证持久化到钥匙串,以便无人值守的同步不需要环境变量
```
连接方式:一个15行的shim脚本在您的钩子配置文件中调用`cadence hook`;一个launchd作业运行`cadence sync --quiet`每天两次。钩子从不阻塞会话——无需网络调用(仅本地SQLite读取),每个失败路径都选择沉默而不是破坏会话启动。
## 个人基线和异常检测(v0.3.0)
以下是个人化的部分:cadence为每个指标构建一个真实的30天滚动平均值/标准偏差(恢复分数,HRV,静息心率,呼吸率,睡眠时长/效率),并仅当今天的值与您的正常值有真正的统计偏差(|z| >= 1.5)时标记。 **最重要的诚实约束:** 仅当有14天以上的真实历史记录时,才会将指标默默省略基线(不会用小n显示错误)——基于3个数据点构建的异常值是噪音伪装成洞察力——这个项目宁愿不说任何话也不愿自信地给出错误信息。
```bash
cadence baseline # 打印当前基线 + 今天的任何偏差
```
异常出现三种方式,每种方式都根据紧急程度校准:
- **在`brief`/会话钩子中** - 内联,每次存在时。
- **在一天中聚焦重新提醒** - 如果在一天的第一个会话已经发生后出现一个新的严重偏差(|z| >= 2.5)(例如,晚上8点的同步发现了上午8点的同步没有的数据),钩子会再次触发仅针对该偏差 - 真实的新信号优先于每日配额。已经看到的偏差永远不会在同一天再次触发。
- **来自无人值守的同步作业的macOS通知** - 仅针对严重级别 - 这是值得打断沉默的事情。
**每周摘要:** `cadence digest`将一个有日期的摘要写入`~/ai-brain/digests/cadence/`并提交+推送它(ai-brain是私有的,因此这是自动推送的,无需提示,按照与此项目的其他摘要例程相同的约定)。电子邮件投递故意**不**启用 - 请参见`digest.py`模块文档字符串的原因(一个平行的例程已经标记了摘要电子邮件疲劳;添加第7个邮件投递功能在进行此对话之前是不正确的)。
## 镜像:身体、代码和AI使用在一个地方(v0.4.0)
这是其他地方都没有的东西。少数公开的WHOOP-MCP服务器存在;仅凭健康工具并不新鲜。实际上,新的东西是cadence是第一个将您的**git提交历史**、**Claude使用情况**和**生理数据**放在一个本地关联层中的地方——这三个时间序列已经位于同一台机器上,但从未连接。
```bash
cadence config add-repo ~/Downloads/your-project # 指向您自己的仓库 - 完全通用,不硬编码到我的
cadence mirror # 提交-小时节奏 + 观察相关性
```
**使之成为现实的诚实纪律:** 每个相关性都是一个简单的两组平均值比较**在您自己的历史记录上** - 没有回归,没有显著性测试伪装成更多东西 - 而且它**拒绝报告任何少于14天真实重叠数据每组**,一刀切。每个打印的结果都带有明确的方法论说明(确切地说明了两个变量如何按日期连接,明确说明为“观察性,非因果性”)。沉默是诚实的答案,低于该阈值,从不自信地发出由噪音构建的数字。
**它实际发现的内容,第一天(2026-07-10):** 在过去90天中,**11个真实仓库中56%的提交发生在凌晨6点到凌晨12点之间** - 一个真实的,可立即获得的节奏信号,无需清除阈值即可进行纯直方图。更深层次的相关性(深夜编码与次晨恢复,睡眠不足与提交输出,重度Claude使用与次日恢复)正确返回**无** - 不是错误,验证:实际的提交活动仅跨越7个不同的日历日在此窗口内,并且`usage-history.jsonl`刚刚开始记录。诚实约束做了它该做的事情:拒绝根据2-3个数据点制造“发现”。随着日志积累超过14天阈值,相同的命令将开始报告真实数字 - 正是这个项目围绕的纪律在演示,而不仅仅是在文档字符串中声明,而不是在自己的实际运行中。
MCP工具:`get_developer_rhythm()`,`get_body_code_correlations()`。
## 设备叙述(v0.5.0)- 投资组合循环关闭
`cadence digest --narrate`将结构化的每周摘要变成2-3个普通的段落,完全在设备上通过[langchain-apple-foundation-models](https://github.com/rajanshxrma/langchain-apple-foundation-models)生成 - 整个存储库中的第一个存储库,现在是最后一个见解引擎的洞察引擎。零云,零成本,`pip install cadence[narrate]`(可选 - cadence从不依赖Apple Intelligence工作)。
```bash
cadence digest --narrate --no-commit # 尝试它而不影响ai-brain
```
**一个真实的幻觉,找到并修复,实时,不被掩盖:** 第一个工作提示生成了一个真实的运行,其中摘要的基线部分清楚地显示了真实数字(恢复分数72.7±21.3%,n=23),而模型仍然写道“没有足够的数据将您的恢复分数与30天基线进行比较” - 直接与自己的输入相矛盾。 与[lantern](https://github.com/rajanshxrma/lantern)的README中记录的错误类别相同。 通过使指令明确说明“没有足够的数据”被允许意味着什么(仅当输入中出现该确切短语时);在修复后,在3次连续实际生成中验证了零幻觉声明,每个数字逐字复制。 不是证明它永远不会再次发生 - 单纯的提示永远不能消除幻觉 - 这就是为什么叙述插入**在**确定性结构化摘要之上,从不取代它:真实数字始终坐在那里进行交叉检查。
## 它是如何工作的
| | WHOOP | Apple Health |
|---|---|---|
| 数据来源 | 云API,OAuth 2.0 | 本地`export.zip`(手动从健康应用导出) |
| 独特之处 | 恢复分数,HRV(RMSSD),应激,睡眠性能/效率 | 步数,有/无能量消耗,运动分钟数,距离,HRV(SDNN),锻炼GPS相关细节 |
| 网络调用 | 是的,拉取数据(`cadence sync`) | 无 - 纯本地文件解析 |
| 同步模型 | 基于拉取,运行`cadence sync` | 一次性导入每个导出(重新导入以刷新) |
两个来源都标准化为一个本地SQLite模式(`sleep`、`recovery`、`workouts`、`daily_metrics`),每个行都标记其`source`。当两个来源涵盖同一个现实事件(例如同一个晚上的睡眠)时,cadence不会默默地将它们合并成一个伪“规范”记录 - 它会明确选择并说明:WHOOP在睡眠分期中获胜,并且是恢复/应激的**唯一**来源(Apple Health没有等效的计算分数);Apple Health是步数和其他日常活动WHOOP的API未公开暴露的来源。
## 真实的工程挑战(找到并处理,而不是掩盖)
1. **Apple Health导出可以是200MB-1GB+的XML.** `apple_health.py`使用`xml.etree.ElementTree.iterparse`流式处理它,并积极地从解析树中删除每个处理的元素 - 仅`elem.clear()`是不够的(一个众所周知的stdlib陷阱:元素仍然位于其父节点的子列表中)。 验证,而不是声称:一个30万记录/48MB的合成文件通过完整的清除减少了~19MB的峰值RSS,而仅使用`elem.clear()`减少了~27MB - 一个真实的可测量差异,在实际导出规模(数百万记录)上变得更加明显。
2. **Apple Health以数十个小的每个阶段的记录发出睡眠,而不是每晚一行。** `_group_sleep_sessions()`通过连续时间减少它们(> 2小时的间隙开始一个新的晚上),对每个阶段的秒求和 - 与间隙分割案例和同夜合并案例进行测试。
3. **WHOOP每次使用都会旋转刷新令牌** - 旧令牌在新令牌颁发时立即失效。 `auth.py`的刷新路径立即保留新的刷新令牌,然后返回;跳过此步骤会破坏同步下一次令牌轮换后。
4. **趋势计算中的真实越界错误**,被其自身的测试捕获:比较“本周”与“上周”时,朴素的包含日期边界会将共享的边界日放入**两个**窗口中,从而膨胀最近的平均值(测量:8625而不是正确的9000,在9000 vs 6000的合成场景中)。 通过使两个窗口正确分离。
5. **真实的测试隔离错误,被其自身的测试捕获:** `store.connect()`的默认参数原来在函数**定义**时绑定了`DEFAULT_DB_PATH`,因此在测试中重新分配`store.DEFAULT_DB_PATH`(以隔离它与真实的`~/.cadence/cadence.db`)默默地没有效果 - 早期测试运行实际上在捕获和修复此问题之前将真实的合成测试行写入了真实的本地存储器上的开发机器。 `tests/test_tools.py::test_tools_never_touch_real_default_db_path`现在防止回归。
6. **趋势数学中的部分日偏差,通过第一个运行对抗真实数据发现的那种错误类型 - 合成测试无法捕获。** 周过周趋势最初包括**今日**在最近的窗口中; `cadence brief`在早晨运行会计算当天的几百步作为完整的一天,而第一个实时报告声称步数下降了56% - 部分是命令发生时期的伪影,而不是真实的行为。 两个比较窗口现在仅覆盖完整的日期,仅在昨天结束,并带有回归测试固定今天的排除。
7. **WHOOP的`start`/`end`参数在裸日期上静默404.** 发现实时:`?start=2026-06-10`返回一个纯404(不是验证错误),而相同的请求使用相同的请求与`?start=2026-06-10T00:00:00.000Z`返回200并带有数据。 完整的ISO8601日期时间 - 现在由`_iso_datetime()`及其自身的回归测试强制执行。
8. **两个模块中出现相同的词典日期比较错误,两次在不同的模块中.** `sleep`/`workouts`行存储完整的ISO时间戳(“2026-07-10T03:00:00Z”),这些时间戳在排序后排在裸日期字符串(“2026-07-10”)**之后** - 裸`date.today().isoformat()`上限会默默地排除今天的记录。 这已经在`apple_health.py`的测试中发现并修复了一次;它在`digest.py`中再次重现(真正的错误,而不是第一次的回归),因为修复模式(`brief.py`的`+1 day`缓冲区)默认情况下不会应用于新模块。 现在在构建查询边界时在每个模块中一致。
9. **真实的相关性错误,被其自身的测试捕获。** `late_night_cost()`最初遍历提交日以构建其两个比较组 - 这意味着零提交(确切的“没有深夜提交”组它需要)被默默地排除整个,因此它们永远不会出现在提交数据中。 通过将缺少的提交数据默认为零,相同的模式`sleep_to_output()`已经正确使用。 由一个真实的端到端测试(合成存储库+合成恢复数据)捕获之前,此版本运行对抗真实账户 - 如果按照编写的内容发布,相关结果将被默默地从有偏见的仅提交的子集计算,而不是真正的比较。