Telegram 低交互场景的个人运动追踪教练。把"像发消息一样记录运动"作为核心目标。
3秒记录原则:用户在任何对话轮次里,发来一条训练或状态消息,agent 应当能在最少回复轮次内完成记录。
data/
├── plan.json # 当前训练计划(格式不变)
├── history.json # 统一历史:训练记录+状态记录+快照引用(升级格式,见下)
├── body_status.json # 身体基本信息+体重/体脂历史
└── .current_workout.json # 训练中途进度(临时)
{
"records": [
{
"date": "2026-04-20",
"type": "workout",
"workout": {
"summary": "练了背",
"exercises": [
{"name": "坐姿划船", "weight_kg": 25, "sets": [{"reps": 8}, {"reps": 8}, {"reps": 8}]},
{"name": "高位下拉", "weight_kg": 25, "sets": [{"reps": 8}, {"reps": 8}]}
],
"duration_min": 35,
"note": "状态不错"
}
},
{
"date": "2026-04-20",
"type": "status",
"status": "累",
"detail": "昨晚没睡好"
},
{
"date": "2026-04-20",
"type": "snapshot",
"snapshot": {
"week_of": "2026-04-14",
"workout_count": 4,
"status_summary": {"累": 2, "正常": 5},
"plan_version": "v2"
}
}
]
}
当用户发送消息时,按以下顺序判断意图:
关键词/模式:跑了、练了、深蹲、卧推、走了多少步、拉伸、骑车、上半身、今天训练了、做了.组、Xkg.X次、开始练、练完了、第.*组
处理流程:
summary 自由文字保存workout.py log 写入 history.json格式灵活接受:
今天跑了40分钟 → {summary:"跑步40分钟"}深蹲60kg 5x5,卧推30kg 5x5 → 两个动作分别记录晚上练了腿 → {summary:"练腿"}走了8000步 → {summary:"步行8000步"}今天拉伸了15分钟 → {summary:"拉伸15分钟"}上半身训练 → {summary:"上半身训练"}实时记录工作流(低摩擦版):
workout.py start-session(记录开始时间到 data/.session.json)workout.py log "" '' 实时更新记录(duration_min 先写 null)workout.py finish-workout(从 .session.json 读取开始时间,计算实际时长,更新历史记录中对应记录的 duration_min)log 每次调用会覆盖当天训练记录,所以每组完成后要重新传入完整的 exercises 列表关键词/模式:累、没睡好、不想练.、这周只能.次、出差、经期、恢复、不舒服、膝盖.*、状态
处理流程:
workout.py status-log 写入 history.json状态类型映射:
| 用户词 | 存储值 |
|---|---|
| -------- | -------- |
| 累、很累、疲惫 | tired |
| 没睡好、睡眠差 | poor_sleep |
| 出差 | travel |
| 经期、生理期 | period |
| 不想练X、讨厌练X | avoiding:{部位} |
| 这周只能练X次 | frequency_limit:{N} |
| 恢复、休息一下 | recovery |
| 膝盖不舒服、肩膀疼 | injury:{部位} |
| 状态不错 | good |
| 正常 | normal |
关键词/模式:今天练了什么、这周练了几次、报告、周报、月报、这周为什么执行.、最近计划改了.、练什么
处理流程:
workout.py plan view,内部已根据 date.today() 自动确定真实日期对应的星期workout.py plan view 输出中已包含今日训练内容(标注了当天)今天练了什么 → 查 history.json 中今日 type=workout 记录这周练了几次 → 统计本周 type=workout 数量这周状态 → 查本周 type=status 记录日报 → 调用 workout.py report today周报 → 调用 workout.py report week月报 → 调用 workout.py report month阶段总结 → 调用 workout.py report summary关键词/模式:新计划、改训练计划、更新计划、训练计划.*调整
处理流程:
workout.py plan set 保存 plan.jsonworkout.py plan version 追加版本记录到 history关键词/模式:体重.kg、体脂.%、身高.*cm
处理流程:
调用 workout.py body update 或 workout.py body log
以下状态变化后,agent 应主动或被动更新训练建议:
出差、经期、恢复模式、这周只能练X次 → 降频建议不想练腿、讨厌练背 → 调整部位偏好tired → 建议降强度poor_sleep → 建议推迟主训练日当触发调整时,输出格式:
📋 训练建议已调整(v{N})
原因:{触发原因}
建议:{具体建议}
每次调整训练建议,调用 workout.py plan version 记录到 history.json 的 plan_versions 数组。
提供以下快捷操作选项:
[🏋️ 练什么] [📝 记录训练] [📊 查看报告]
[😫 状态记录] [⚖️ 体重记录] [📋 训练计划]
| 脚本命令 | 功能 |
|---|---|
| ---------- | ------ |
workout.py start-session | 开始训练计时(记录开始时间到 .session.json) |
workout.py log " | 记录/更新一条训练(实时逐组更新) |
workout.py finish-workout | 结束训练(从 session 自动算时长并更新记录) |
workout.py status-log " | 记录一条状态 |
workout.py plan version | 记录方案版本变更 |
workout.py plan view | 查看当前计划(自动标注今天) |
workout.py report today | 日报 |
workout.py report week [date] | 周报(默认本周) |
workout.py report month [date] | 月报(默认本月) |
workout.py report summary | 阶段总结 |
workout.py body update key=value... | 更新身体数据 |
workout.py body log key=value... | 记录当天体重体脂 |
workout.py body view | 查看身体数据 |
workout.py body history [days] | 体重体脂历史 |
workout.py history [days] | 训练历史(仅训练记录) |
workout.py reset-today | 重置今日 |
workout.py reset-date | 重置指定日 |
{summary:"练腿"}📅 2026-04-20 日报
━━━━━━━━━━━━━━━
🏋️ 训练:坐姿划船 25kg×3组 / 高位下拉 25kg×3组
⏱️ 约 35 分钟
😫 状态:累(昨晚没睡好)
📝 备注:—
━━━━━━━━━━━━━━━
📅 周报(4/14 - 4/20)
━━━━━━━━━━━━━━━
🏋️ 训练次数:4次
😫 状态:累×2 / 正常×3 / 出差×1
📉 执行率:4/5 计划日(80%)
⚠️ 问题:周二因出差未训练
📋 方案变更:v2(出差降频)
━━━━━━━━━━━━━━━
📅 月报(4月)
━━━━━━━━━━━━━━━
🏋️ 训练次数:18次
⏱️ 总时长:约 9 小时
😫 状态趋势:上半月累×6 / 下半月正常×8
📉 执行率:18/22(82%)
💪 进步:卧推+5kg,深蹲+10kg
📋 方案变更:3次
━━━━━━━━━━━━━━━
plan.json 格式完全不变,继续使用history.json 追加兼容:原有训练记录结构在 records[].workout 中,原有格式可直接读取body_status.json 继续使用,不变scripts/workout.py 扩展新命令,不删除原有功能✅ 以下是本 skill 第一版实际交付的能力集:
输入处理:
数据层:
报告输出:
训练建议:
Quick Actions:
workout.py 脚本中的日期/时间计算禁止使用 Python 内置的 date.today() 或 datetime.now(),因为系统时钟可能与实际用户时区不一致。
正确做法: 脚本从 HERMES_SESSION_START 环境变量解析 session 开始的 UTC 时间戳,再转换为 UTC+8(用户所在时区)。
from datetime import datetime, timezone, timedelta
def _get_session_dt():
raw = os.environ.get("HERMES_SESSION_START", "")
if raw:
try:
return datetime.fromisoformat(raw.replace("Z", "+00:00"))
except Exception:
pass
return datetime.now(timezone.utc) # fallback
def _get_session_dt_local():
dt = _get_session_dt()
utc8 = timezone(timedelta(hours=8))
return dt.astimezone(utc8)
def _get_session_date():
return _get_session_dt_local().date()
然后将所有 date.today() 替换为 _get_session_date(),所有 datetime.now() 替换为 _get_session_dt_local()。
__pycache__/
*.py[cod]
backend/.pybase/
backend/.vendor/
backend/.venv/
.pytest_cache/
.env
.data/
*.log
*.lock
dist/
❌ 以下为第一版不做项(留待后续迭代):
共 1 个版本