Skill: query1988/workout-tracker (version 1.3.7)

--- name: workout-tracker description: 个人健身跟踪器 — 通过文字记录健身训练数据，自动解析动作、组数、次数和重量，实时写入本地 MySQL 数据库。支持制定专属训练计划、查询历史记录、追踪进步曲线，以及球类和有氧运动记录。 metadata: {"clawdbot":{"emoji":"🏋️","requires":{"bins":["python3","mysql"],"env":["MYSQL_USER","MYSQL_PASSWORD","MYSQL_DATABASE","MYSQL_SOCKET"]},"primaryEnv":"MYSQL_PASSWORD"}} --- # Workout Tracker 记录健身数据，自动解析训练内容并写入数据库。 ## 🚀 完整安装指南 ### 1. 安装依赖 ```bash # 安装 Python MySQL 连接器 pip install mysql-connector-python # 验证安装 python3 -c "import mysql.connector; print('✅ mysql-connector-python 已安装')" ``` ### 2. 安全配置环境变量（推荐方法） #### 方法 A：使用 .env 文件（最安全） ```bash # 创建 .env 配置文件 cat > .workout-tracker.env << 'EOF' MYSQL_SOCKET="/tmp/mysql.sock" MYSQL_USER="workout_user" MYSQL_PASSWORD="your_secure_password" # 替换为实际密码 MYSQL_DATABASE="workout_tracker" EOF # 设置文件权限（仅当前用户可读） chmod 600 .workout-tracker.env # 加载配置 source .workout-tracker.env ``` #### 方法 B：临时环境变量（会话有效） ```bash # 仅在当前终端会话中有效 export MYSQL_SOCKET="/tmp/mysql.sock" export MYSQL_USER="workout_user" export MYSQL_PASSWORD="your_password" # 注意：密码会出现在 shell 历史中 export MYSQL_DATABASE="workout_tracker" ``` #### 方法 C：交互式输入（最安全，无痕迹） ```python # 使用脚本提示输入密码，不存储在任何地方 import getpass import os [REDACTED_SECRET]"请输入 MySQL 密码: ") os.environ['MYSQL_PASSWORD'] = password ``` **⚠️ 安全警告**：避免将密码存储在 ~/.bashrc、~/.zshrc 或版本控制的文件中。 ### 3. 数据库用户创建 ```sql -- 在 MySQL 中执行 CREATE USER IF NOT EXISTS 'workout_user'@'localhost' IDENTIFIED BY 'your_password'; GRANT INSERT, SELECT, UPDATE, DELETE ON workout_tracker.* TO 'workout_user'@'localhost'; FLUSH PRIVILEGES; CREATE DATABASE IF NOT EXISTS workout_tracker; ``` ### 4. 初始化数据库（一键脚本） ```bash # 运行初始化脚本 python3 /path/to/workout-tracker/scripts/init_database.py ``` ### 5. 验证安装 ```bash # 运行验证脚本 python3 /path/to/workout-tracker/scripts/verify_setup.py ``` ### 安全配置（推荐使用 .env 文件） ```bash # 创建安全的 .env 配置文件（不上传至版本控制） cat > ~/.workout-tracker.env << 'EOF' # Workout Tracker 数据库配置 # ⚠️ 警告：此文件包含敏感信息，不要上传到版本控制 MYSQL_SOCKET="/tmp/mysql.sock" MYSQL_USER="workout_user" MYSQL_PASSWORD="your_secure_password_here" # 替换为你的实际密码 MYSQL_DATABASE="workout_tracker" EOF # 设置文件权限（仅当前用户可读） chmod 600 ~/.workout-tracker.env # 在 shell 中加载配置 source ~/.workout-tracker.env ``` ### 自动化安装脚本（安全版本） ```bash #!/bin/bash # workout-tracker-setup-secure.sh echo "🏋️ Workout Tracker 安全安装" # 安装 Python 依赖 echo "📦 安装 Python 依赖..." pip install mysql-connector-python # 创建安全的配置模板 echo "🔐 创建安全配置模板..." cat > workout-tracker-config-template.env << 'EOF' # Workout Tracker 配置模板 # 1. 将此文件复制为 .workout-tracker.env # 2. 填写实际的数据库密码 # 3. 设置文件权限: chmod 600 .workout-tracker.env MYSQL_SOCKET="/tmp/mysql.sock" MYSQL_USER="workout_user" MYSQL_PASSWORD="" # ⬅️ 在此处填写你的 MySQL 密码 MYSQL_DATABASE="workout_tracker" EOF echo "✅ 安装完成！" echo "" echo "📋 后续步骤：" echo "1. 填写数据库密码: cp workout-tracker-config-template.env ~/.workout-tracker.env" echo "2. 编辑 ~/.workout-tracker.env 设置密码" echo "3. 设置文件权限: chmod 600 ~/.workout-tracker.env" echo "4. 加载配置: source ~/.workout-tracker.env" echo "5. 验证安装: python3 scripts/verify_setup.py" echo "" echo "🔒 安全提示：" echo "- 永远不要在版本控制中包含 .env 文件" echo "- 使用强密码并定期更换" echo "- 数据库用户仅授予最小必要权限" ``` ## 本地配置说明 本技能专为单用户本地部署设计（用户无需公开任何凭据）。数据库配置通过本地环境变量或配置文件管理，不随技能发布。 **默认单用户 ID 说明：** - 本技能设计为单用户工具（user_id = 1 是默认值，适用于个人本地使用） - 安装后用户可自行修改 `workout_sessions` 和 `workout_items` 中的 `user_id` 以匹配自己的设置 - 数据库表结构兼容多用户扩展（只需在查询时指定对应 user_id） ## 数据库结构 ```sql exercise_types (id, name, muscle_group, equipment, unit) -- id: 自增主键 -- name: 动作名称，如"杠铃深蹲"、"腿举机" -- muscle_group: 主要肌群（胸/背/腿/肩/臂/腹/全身/下背） -- equipment: 器械类型（杠铃/哑铃/器械/自重/有氧） -- unit: 计量单位（次/分钟/米） gym_equipment (id, name, category, muscle_main, muscle_sec, created_at) -- id: 自增主键 -- name: 器械名称 -- category: 类别（哑铃/杠铃/器械/有氧/自由重量/辅助） -- muscle_main/muscle_sec: 主要/次要肌群 workout_sessions (id, user_id, workout_date, start_time, end_time, duration_min, workout_type, notes) -- id: 自增主键 -- user_id: 用户ID（默认1，单用户本地部署） -- workout_date: 训练日期 -- duration_min: 总时长（分钟） -- workout_type: 训练类型（力量/有氧/柔韧） -- notes: 备注（如状态评分"状态评分: 9/10 | 非常爽"） workout_items (id, session_id, exercise_type_id, set_order, reps, weight_kg, distance_km, rpe, notes) -- id: 自增主键 -- session_id: 关联 workout_sessions.id -- exercise_type_id: 关联 exercise_types.id -- set_order: 第几组（1, 2, 3...） -- reps: 重复次数 -- weight_kg: 重量（公斤） -- notes: 备注（如"杠铃深蹲第1组"） ``` ## 核心流程 ### 标准预排模式（默认） 用户一次性报完整动作计划 → 我预排所有组 → 用户每完成一组说 done → 我逐条标记完成 **用户命令示例：** - 说`"深蹲 60公斤 4组×8次"` → 预排所有组，记录为待完成状态，回复"已预排4组，每完成一组说done" - 说`"done"` → 标记当前训练最新一组为完成状态，回复"第1组完成！还剩3组" - 说`"done 这组是10次"` → 同上，并更新该组次数为10次 - 说`"done 加一组"` → 完成当前组，并追加一组相同训练 - 说`"卧推 40公斤 3组×10次"` → 切换到新动作，重新预排 - 说`"结束了"` → 训练完成，自动计算时长，询问状态评分 **操作流程（预排模式）：** ``` 1. 用户说"动作 + 重量 + X组×Y次" → 若当日尚无 workout_session，先创建（start_time=NOW()） → 解析动作名、重量、组数、次数 → 匹配 exercise_types.id，不存在则插入 → 一次性插入 N 条 workout_items，notes 标"⏳待完成" → 回复"已预排X组，每完成一组说done" → 随机（30%概率）附一句简短鼓励（如"这个重量稳的！"） 2. 用户说 "done"（或"done 这组是X次"） → 找到当前 session 最新未标记 ✓ 的记录 → 更新 notes 追加" ✓"（若用户报了次数，同时更新 reps） → 回复"第N组完成！还剩X组"（若还有未完成的） → 若动作全部完成 → 回复"XX动作完成！下一个动作是什么？" → 随机（30%概率）附一句简短鼓励 3. 用户说 "done 加一组" → 执行步骤2的标记完成逻辑 → 在当前动作下追加一条新的"⏳待完成"记录（reps/weight 继承上一组） → 回复"已追加第N+1组 ⏳" 4. 用户说 "动作名 重量 X组×Y次" → 切换到新动作，重复步骤1 5. 用户说 "结束了" → 自动计算：TIMESTAMPDIFF(MINUTE, start_time, NOW())，start_time 来自 workout_sessions → UPDATE workout_sessions SET duration_min=? WHERE id=current_session → 查询汇总数据，回复训练完成汇总 → 主动询问："今天状态怎么样？状态评分1-10" ``` **情绪价值 — 鼓励语料库（随机选取）：** 每完成一组时，随机附上以下类型的鼓励语： - 🔥 进度类：`"又完成一组，腿部在燃烧！"`、`"离目标又近了一步！"`、`"感觉怎么样？这组有没有更稳？"` - 💪 力量类：`"这重量稳的！"`、`"继续保持这个节奏"`、`"你的深蹲越来越扎实了"` - 🌟 夸赞类：`"不错不错！"`、`"这组完成得很干净"`、`"看得出状态很好！"` - 😅 幽默类：`"腿已经软了？坚持住！"`、`"疼痛是成长的象征 😄"`、`"汗水不会骗人 💦"` - 🏆 完成类（动作全部完成）：`"这个动作拿捏了！"`、`"太强了，下一个！"`、`"肌肉在尖叫，继续！"` **鼓励触发规则：** - 开始新动作时：30%概率附一句（针对该动作的简短鼓励） - 每完成一组 "done" 时：50%概率附一句（简短、自然，不要每组都说） - 动作全部4组完成时：80%概率附一句夸奖 - 用户感到吃力或犹豫时：主动降低语气，给予安慰而非催促 - 不要过度鼓励，保持自然节奏，避免刷屏感 **SQL模板（参数化查询）：** ```sql -- 创建新训练 session（如尚无今日 session，start_time 自动为 NOW()） INSERT INTO workout_sessions (user_id, workout_date, workout_type, start_time) VALUES (?, CURDATE(), ?, NOW()); -- user_id, workout_type -- 获取当前 session_id SELECT id FROM workout_sessions WHERE user_id=? ORDER BY id DESC LIMIT 1; -- user_id -- 预排 N 组（用户说"动作 重量 X组×Y次"） INSERT INTO workout_items (session_id, exercise_type_id, set_order, reps, weight_kg, notes) VALUES (?, ?, 1, ?, ?, CONCAT(?, ' 第1组 ⏳')), (?, ?, 2, ?, ?, CONCAT(?, ' 第2组 ⏳')), ... (?, ?, N, ?, ?, CONCAT(?, ' 第N组 ⏳')); -- sid, eid, reps, weight, exercise_name -- 标记第N组完成（用户说 done 时） UPDATE workout_items SET notes = CONCAT(?, ' 第', ?, '组 ✓'), -- exercise_name, set_order reps = ? -- 如用户报了次数 WHERE session_id=? AND set_order=?; -- sid, set_order -- 追加一组（用户说 done 加一组 时） INSERT INTO workout_items (session_id, exercise_type_id, set_order, reps, weight_kg, notes) VALUES (?, ?, (SELECT IFNULL(MAX(set_order),0)+1 FROM workout_items WHERE session_id=?), ?, ?, CONCAT(?, ' 第N组 ⏳')); -- sid, eid, sid, reps, weight, exercise_name -- 完成训练（duration_min = NOW() - start_time） SELECT TIMESTAMPDIFF(MINUTE, start_time, NOW()) as duration_min FROM workout_sessions WHERE id=?; -- sid UPDATE workout_sessions SET duration_min=? WHERE id=?; -- minutes, sid -- 状态评分更新（后续用户说"X分"时） UPDATE workout_sessions SET notes=? WHERE id=?; -- score_note, sid ``` ### 模式二：一次性记录（训练后补录） 用户一次性报完整训练内容： ``` 今天练了深蹲 4组×10次×30公斤，腿举 3组×8次×72公斤 ``` **操作流程：** 1. 解析所有动作及每组数据 2. 创建 session 3. 批量插入 workout_items 4. 回复汇总 ### 模式三：制定训练计划（基础版） 用户说"今天练腿给我一个计划" → 根据 gym_equipment 中的器械，生成针对性的训练计划 → 回复格式：热身 → 主项 → 辅助项 → 拉伸 ### 模式四：智能生成训练计划（增强版） 当用户说"今天练X"、"我要练X"、"给我一个X训练计划"时，自动执行： **第一步：查询本周已训练过的肌群（避免重复）** ```sql -- 查询本周（近7天）训练过的肌群及动作 SELECT DISTINCT ws.workout_date, ws.workout_type, et.muscle_group, et.name as exercise_name, wi.weight_kg, wi.reps, wi.set_order FROM workout_sessions ws JOIN workout_items wi ON wi.session_id = ws.id JOIN exercise_types et ON et.id = wi.exercise_type_id WHERE ws.user_id=1 AND ws.workout_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) ORDER BY ws.workout_date DESC, et.muscle_group; ``` **第二步：查询用户器械（确保计划可执行）** ```sql SELECT id, name, category, muscle_main, muscle_sec FROM gym_equipment WHERE muscle_main LIKE '%背%' -- 动态替换目标肌群关键词 OR muscle_sec LIKE '%背%' ORDER BY category; ``` **第三步：分析后生成计划** - 目标肌群本周已练过 → 计划注明"上次练过此肌群，可适当降低强度" - 目标肌群本周未练 → 正常强度 - 计划必须全部来自已有器械，无器械可用时标注"需新增器械" - 回复格式：热身 → 主项（含建议重量参考）→ 辅助项 → 拉伸 **回复模板：** ``` 📋 今日{肌群}训练计划（智能生成） 🗓️ 本周已练：{已训练肌群及日期} 🏋️ 可用器械：{器械列表} ① 热身（5-10分钟） ② 主项（3-4组×8-12次）— 标注动作名 [器械名] ③ 辅助项（2-3组×10-15次）— 标注动作名 [器械名] ④ 拉伸 💡备注：{上次训练情况及建议} ``` ### 模式五：查询记录 用户问"今天练了什么"、"上周训练几次"等 → 执行查询SQL → 整理成表格回复 ## 查询示例 ### 训练完成汇总 ```sql SELECT ws.workout_date, ws.duration_min, ws.workout_type, ws.notes, et.name, COUNT(*) as sets, SUM(wi.reps) as total_reps, wi.weight_kg FROM workout_items wi JOIN workout_sessions ws ON ws.id = wi.session_id JOIN exercise_types et ON et.id = wi.exercise_type_id WHERE wi.session_id = ? GROUP BY et.name, wi.weight_kg ORDER BY et.name; ``` ### 查今日训练 ```sql SELECT ws.workout_date, ws.duration_min, ws.workout_type, et.name, wi.set_order, wi.reps, wi.weight_kg FROM workout_sessions ws JOIN workout_items wi ON wi.session_id = ws.id JOIN exercise_types et ON et.id = wi.exercise_type_id WHERE ws.user_id=1 AND ws.workout_date=CURDATE() ORDER BY ws.id, wi.set_order; ``` ### 查本周训练天数 ```sql SELECT COUNT(DISTINCT workout_date) as days, SUM(duration_min) as total_min FROM workout_sessions WHERE user_id=1 AND workout_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY); ``` ### 查某动作历史 ```sql SELECT ws.workout_date, et.name, wi.reps, wi.weight_kg FROM workout_items wi JOIN workout_sessions ws ON ws.id = wi.session_id JOIN exercise_types et ON et.id = wi.exercise_type_id WHERE ws.user_id=1 AND et.name LIKE '%深蹲%' ORDER BY ws.workout_date DESC LIMIT 10; ``` ## 动作识别优先级 用户输入动作简称时，自动识别对应 exercise_types： - "山羊挺身" → 45度背部挺身机 - "腿举" → 腿举机/倒蹬机 - "罗马凳" → 罗马凳/背肌伸展架 - "深蹲" → 杠铃深蹲 - "卧推" → 杠铃卧推 - 等等（模糊匹配） 如果完全匹配不到，先插入新动作。 ## 训练完成后的反馈询问 训练结束汇总回复后，必须主动询问： ``` "今天状态怎么样？状态评分1-10" ``` 用户回复后，更新 workout_sessions.notes： ```sql UPDATE workout_sessions SET notes = CONCAT('状态评分: ', ?, '/10 | ', ?) WHERE id = ?; -- score, feeling, sid ``` ## 常见动作类型判断 根据关键词判断 workout_type： - "跑"、"快走"、"椭圆机"、"有氧" → `有氧` - "瑜伽"、"拉伸"、"普拉提" → `柔韧` - 其他（卧推、深蹲、硬拉、引体等） → `力量` ## 模式六：球类/有氧运动记录（简化流程） 用户说"网球一小时"、"游泳半小时"等，直接创建 session，不走预排组流程。 **识别规则：** - 运动名 + 时长（网球/羽毛球/乒乓球/篮球/足球/游泳/跑步/骑行/椭圆机 等） - 时长单位：小时/分钟/小时半（1.5小时） **操作流程：** 1. 匹配 `exercise_types.name`（模糊匹配 sport_name） 2. 创建 session，`sport_name` 字段写入运动名，`workout_type` 设为 `有氧` 3. `duration_min` 写入实际时长（分钟） 4. 不创建 `workout_items`（这类运动不需要组数记录） 5. 回复汇总即可 **SQL：** ```sql -- 创建运动记录 INSERT INTO workout_sessions (user_id, workout_date, sport_name, workout_type, duration_min, start_time) VALUES (1, CURDATE(), '网球发球机', '有氧', 60, NOW()); ``` **回复模板：** ``` 🎾 网球发球机训练记录完成 ─── ⏱️ 时长：60 分钟 🏃 类型：有氧 ─── 状态评分 1-10？ ``` ## SQL 安全规范（严格禁止 SQL 注入） **所有用户输入必须经过参数化查询，禁止任何形式的字符串拼接 SQL。** ### ✅ 正确做法（参数化查询） ```python # Python + MySQL Connector 示例 import mysql.connector # 使用参数化查询 cursor.execute( "INSERT INTO workout_items (session_id, exercise_type_id, set_order, reps, weight_kg) VALUES (%s, %s, %s, %s, %s)", (session_id, exercise_type_id, set_order, reps, weight_kg) ) # LIKE 查询也必须参数化 search_term = "深蹲" cursor.execute( "SELECT * FROM exercise_types WHERE name LIKE %s", (f"%{search_term}%",) # 参数化，安全 ) ``` ### ❌ 绝对禁止的做法 ```python # ❌ 危险：字符串拼接（极易 SQL 注入） cursor.execute(f"INSERT INTO ... VALUES ({session_id}, {exercise_type_id}, ...)") # ❌ 危险：使用 .format() 或 % cursor.execute("SELECT * FROM exercise_types WHERE name LIKE '%{}%'".format(user_input)) # ❌ 危险：直接拼接用户输入到 SQL sql = "SELECT * FROM exercise_types WHERE name LIKE '%" + user_input + "%'" cursor.execute(sql) ``` ### ⚠️ 特别注意 1. **LIKE 子句**：必须参数化整个模式，不能只参数化搜索词 2. **表名/列名**：不能参数化，必须白名单验证 3. **整数/浮点数**：同样需要参数化，不要以为数字就安全 4. **Python f-string**：绝对禁止用于 SQL 语句构建 ### 🔒 额外防护措施 1. 数据库用户使用最小权限原则（仅 GRANT INSERT/SELECT/UPDATE/DELETE） 2. 所有查询记录日志（用于审计异常） 3. 输入验证：动作名称长度限制、数字范围检查 4. 错误消息不泄露数据库结构 ## 安全审计清单（部署前必须检查） ### 🔍 SQL 注入防护 - [ ] 所有 SQL 查询使用参数化语句（?, %s 等占位符） - [ ] 绝对禁止字符串拼接构建 SQL - [ ] LIKE 查询完全参数化（包括通配符 % 和 _） - [ ] 整数/浮点数值也使用参数化查询 ### 🔐 数据库权限 - [ ] 创建专用数据库用户（非 root） - [ ] 仅授予最小权限：INSERT/SELECT/UPDATE/DELETE - [ ] 禁止 DROP、CREATE、ALTER 权限 - [ ] 定期审计数据库用户权限 ### 🛡️ 输入验证 - [ ] 动作名称长度限制（例如 50 字符） - [ ] 数字范围验证（reps: 1-100, weight: 0-500kg） - [ ] 时间/日期格式验证 - [ ] 特殊字符过滤或转义 ### 📝 安全配置 - [ ] 数据库连接使用本地 socket（非 TCP） - [ ] 错误消息不泄露数据库结构 - [ ] 查询日志记录（用于审计异常） - [ ] 定期备份数据库 ### 🚨 应急响应 - [ ] 监控异常查询模式 - [ ] 记录失败登录尝试 - [ ] 准备数据库恢复方案 - [ ] 定期安全更新 MySQL --- ## 注意事项 - 用户 ID 默认为 1（单用户本地部署） - 所有 SQL 必须使用参数化查询，防止 SQL 注入 - 数据库用户建议创建专用账户，仅授予 INSERT/SELECT/UPDATE/DELETE 权限 - duration_min 单位为分钟 - weight_kg 单位为公斤，保留2位小数 - reps 为整数（次数） - set_order 从1开始，每组递增 - 训练结束后必须询问状态评分 - 解析时支持"4组×8次"、"4x8"、"四组八次"、"每组12次"等多种格式 - 用户说"done"时，记录的是当前 session 最新插入的未完成组

path: README.md
content: # Workout Tracker

个人健身跟踪器 — 通过文字记录健身训练数据，自动解析动作、组数、次数和重量，实时写入本地 MySQL 数据库。支持制定专属训练计划、查询历史记录、追踪进步曲线，以及球类和有氧运动记录。

## 主要功能

### 🏋️ 健身房训练模式
- **预排组模式**：一次性报完整动作计划，逐组完成
- **实时记录**：每完成一组说 "done"，自动标记完成
- **智能鼓励**：根据训练进度给予个性化鼓励语

### 🎾 球类/有氧运动模式
- **简化记录**：网球、羽毛球、游泳等运动直接记录时长
- **自动分类**：自动识别运动类型，归类为有氧/力量/柔韧

### 📊 训练计划与查询
- **智能计划生成**：根据本周训练历史和可用器械生成针对性计划
- **历史查询**：查看今日训练、本周训练天数、某动作历史记录
- **进度追踪**：记录重量、次数进步曲线

### 🗄️ 数据库管理
- **本地 MySQL**：所有数据存储在本地，不上传云端
- **安全设计**：参数化查询，防止 SQL 注入
- **多用户支持**：表结构支持多用户扩展

## 🚀 快速开始

### 1. 安装依赖
```bash
# 安装 Python MySQL 连接器
pip install mysql-connector-python

# 或使用系统包管理器
# Ubuntu/Debian: sudo apt-get install python3-mysql.connector
# macOS: brew install mysql-connector-python
```

### 2. 数据库准备
```sql
-- 创建数据库和用户
CREATE DATABASE IF NOT EXISTS workout_tracker;
CREATE USER IF NOT EXISTS 'workout_user'@'localhost' IDENTIFIED BY 'your_password';
GRANT INSERT, SELECT, UPDATE, DELETE ON workout_tracker.* TO 'workout_user'@'localhost';
FLUSH PRIVILEGES;
```

### 3. 安装技能
```bash
# 使用 skillhub 安装
skillhub install workout-tracker
```

### 4. 配置环境变量
```bash
# 必需的环境变量
export MYSQL_SOCKET="/tmp/mysql.sock"   # MySQL socket 路径
export MYSQL_USER="workout_user"        # MySQL 用户名
export MYSQL_PASSWORD="your_password"   # MySQL 密码
export MYSQL_DATABASE="workout_tracker" # 数据库名
```

## 使用示例

### 健身房训练
```
用户: 深蹲 60公斤 4组×8次
助手: 已预排4组，每完成一组说done

用户: done
助手: 第1组完成！还剩3组 💪

用户: done 加一组
助手: 第2组完成！已追加第5组 ⏳
```

### 球类运动
```
用户: 网球发球机一小时
助手: 🎾 网球发球机训练记录完成
     ───
     ⏱️ 时长：60 分钟
     🏃 类型：有氧
     ───
     状态评分 1-10？
```

### 训练计划
```
用户: 今天练背给我一个计划
助手: 📋 今日背部训练计划（智能生成）
     🗓️ 本周已练：胸部（昨天）
     🏋️ 可用器械：引体向上架、哑铃划船、高位下拉机
     
     ① 热身（5-10分钟）
     ② 主项：引体向上 [自重] 3组×8-12次
     ③ 辅助：哑铃划船 [哑铃] 3组×10-15次
     ④ 拉伸
```

## 安全特性

### 🔒 SQL 注入防护
- 所有查询使用参数化语句（?, %s 占位符）
- 禁止字符串拼接构建 SQL
- LIKE 查询完全参数化
- 输入验证和长度限制

### 🔐 权限管理
- 专用数据库用户，最小权限原则
- 仅授予 INSERT/SELECT/UPDATE/DELETE 权限
- 本地 socket 连接，不暴露到网络

## 数据库结构

```sql
-- 核心表：训练会话
workout_sessions (id, user_id, workout_date, sport_name, workout_type, duration_min, notes)

-- 训练项目
workout_items (id, session_id, exercise_type_id, set_order, reps, weight_kg, notes)

-- 动作类型
exercise_types (id, name, muscle_group, equipment, unit)

-- 器械清单
gym_equipment (id, name, category, muscle_main, muscle_sec)
```

## 贡献与支持

- **问题反馈**: GitHub Issues
- **功能建议**: GitHub Discussions
- **安全漏洞**: 私信联系

## 许可证

MIT License - 详见 LICENSE 文件

path: TOOLS.md
content: # TOOLS.md - 本地配置模板（不随技能发布）

## 📦 依赖说明

**运行时依赖：**
- `mysql-connector-python` (Python MySQL 连接器)
- 本地 MySQL 数据库服务

**安装依赖（一次性的）：**
```bash
# 安装 Python 依赖
pip install mysql-connector-python

# 或使用系统包管理器
# Ubuntu/Debian: sudo apt-get install python3-mysql.connector
# macOS: brew install mysql-connector-python
```

## 数据库连接

本技能**仅连接本地 MySQL**，不连接任何远程数据库。

### 安全配置指南

#### ⚠️ 重要安全警告
- **永远不要**将密码提交到版本控制（Git 等）
- **永远不要**将密码存储在 ~/.bashrc、~/.zshrc 等 shell 配置文件中
- **永远不要**在代码中硬编码密码
- 使用强密码（12位以上，包含大小写字母、数字、符号）
- 定期更换密码

#### 推荐配置方法（安全性从高到低）

**方法 A：.env 文件（最安全）**
```bash
# 创建 .env 文件（不上传至版本控制）
cat > ~/.workout-tracker.env << 'EOF'
MYSQL_SOCKET="/tmp/mysql.sock"
MYSQL_USER="workout_user"
MYSQL_PASSWORD="your_secure_password_here"  # ⬅️ 在此填写实际密码
MYSQL_DATABASE="workout_tracker"
EOF

# 设置文件权限（仅当前用户可读）
chmod 600 ~/.workout-tracker.env

# 加载配置
source ~/.workout-tracker.env
```

**方法 B：交互式输入（无痕迹）**
```python
# 使用 Python 脚本安全输入密码
import getpass
import os

[REDACTED_SECRET]"请输入 MySQL 密码: ")
os.environ['MYSQL_PASSWORD'] = password
```

**方法 C：会话环境变量（临时）**
```bash
# 仅在当前终端会话有效（密码会出现在 shell 历史中）
export MYSQL_SOCKET="/tmp/mysql.sock"
export MYSQL_USER="workout_user"
export MYSQL_PASSWORD="temporary_password"  # 注意安全
export MYSQL_DATABASE="workout_tracker"
```

### 配置验证脚本
```bash
# 验证环境变量是否设置
if [ -z "$MYSQL_USER" ] || [ -z "$MYSQL_PASSWORD" ]; then
    echo "错误：请先设置 MYSQL_USER 和 MYSQL_PASSWORD 环境变量"
    exit 1
fi

# 验证 MySQL 连接
python3 -c "
import mysql.connector
import os
try:
    conn = mysql.connector.connect(
        unix_socket=os.getenv('MYSQL_SOCKET', '/tmp/mysql.sock'),
        user=os.getenv('MYSQL_USER'),
        [REDACTED_SECRET]'MYSQL_PASSWORD'),
        database=os.getenv('MYSQL_DATABASE', 'workout_tracker')
    )
    print('✅ MySQL 连接成功')
    conn.close()
except Exception as e:
    print(f'❌ MySQL 连接失败: {e}')
    exit(1)
"
```

**注意：不支持 MYSQL_HOST（远程连接），仅支持本地 socket 或 localhost:3306。**

## 初始化数据库

```bash
# 创建专用数据库用户（最小权限原则）
mysql -u root -p -S /tmp/mysql.sock -e "
CREATE USER IF NOT EXISTS 'workout_user'@'localhost' IDENTIFIED BY 'your_password';
GRANT INSERT, SELECT, UPDATE, DELETE ON workout_tracker.* TO 'workout_user'@'localhost';
FLUSH PRIVILEGES;

CREATE DATABASE IF NOT EXISTS workout_tracker;
"
```

## 创建表结构

```sql
CREATE TABLE exercise_types (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL UNIQUE,
  muscle_group VARCHAR(50),
  equipment VARCHAR(50),
  unit VARCHAR(10) DEFAULT '次'
);

CREATE TABLE gym_equipment (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL UNIQUE,
  category VARCHAR(30),
  muscle_main VARCHAR(50),
  muscle_sec VARCHAR(100),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE workout_sessions (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT NOT NULL DEFAULT 1,
  workout_date DATE NOT NULL,
  duration_min INT,
  workout_type VARCHAR(30),
  notes TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE workout_items (
  id INT PRIMARY KEY AUTO_INCREMENT,
  session_id INT NOT NULL,
  exercise_type_id INT NOT NULL,
  set_order INT DEFAULT 1,
  reps INT,
  weight_kg DECIMAL(6,2),
  notes VARCHAR(100),
  FOREIGN KEY (session_id) REFERENCES workout_sessions(id) ON DELETE CASCADE,
  FOREIGN KEY (exercise_type_id) REFERENCES exercise_types(id)
);
```

## 注意事项

- **仅限本地连接**：不暴露数据库到外部网络
- **最小权限用户**：数据库用户仅授予 INSERT/SELECT/UPDATE/DELETE，不授予 DROP 或 ADMIN 权限
- **用户 ID**：默认为 1，可在安装后自行修改
- **数据存储**：所有训练数据仅存储在本地，不上传任何外部服务


path: package.json
content: {
  "name": "@query1988/workout-tracker",
  "version": "1.3.7",
  "description": "个人健身跟踪器 — 通过文字记录健身训练数据，自动解析动作、组数、次数和重量，实时写入本地 MySQL 数据库",
  "keywords": [
    "workout",
    "fitness",
    "tracker",
    "gym",
    "mysql",
    "exercise",
    "training"
  ],
  "author": "query1988",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://clawhub.ai/query1988/workout-tracker"
  },
  "files": [
    "SKILL.md",
    "TOOLS.md",
    "README.md",
    "LICENSE"
  ],
  "openclaw": {
    "skill": true,
    "category": "health-fitness",
    "tags": [
      "mysql",
      "fitness-tracking",
      "gym",
      "local-database"
    ],
    "runtimeDependencies": [
      "mysql-connector-python"
    ],
    "environmentVariables": [
      "MYSQL_SOCKET",
      "MYSQL_USER",
      "MYSQL_PASSWORD",
      "MYSQL_DATABASE"
    ],
    "securityNotes": {
      "databasePassword": "Never commit passwords to version control. Use environment variables or .env files.",
      "localDatabase": "This skill only connects to local MySQL databases for security.",
      "permissions": "Database user should have minimal permissions (INSERT/SELECT/UPDATE/DELETE only)."
    },
    "compatibility": {
      "openclaw": ">=2026.3.0"
    }
  },
  "scripts": {
    "test": "echo \"No tests specified\" && exit 0"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}

path: scripts/init_database.py
content: #!/usr/bin/env python3
"""
Workout Tracker 数据库初始化脚本
创建数据库表结构
"""

import os
import sys
import mysql.connector
from mysql.connector import Error

def get_db_config():
    """获取数据库配置"""
    return {
        'unix_socket': os.getenv('MYSQL_SOCKET', '/tmp/mysql.sock'),
        'user': os.getenv('MYSQL_USER'),
        'password': os.getenv('MYSQL_PASSWORD'),
        'database': os.getenv('MYSQL_DATABASE', 'workout_tracker')
    }

def create_tables(conn):
    """创建数据库表"""
    cursor = conn.cursor()
    
    tables_sql = [
        """
        CREATE TABLE IF NOT EXISTS exercise_types (
          id INT PRIMARY KEY AUTO_INCREMENT,
          name VARCHAR(50) NOT NULL UNIQUE,
          muscle_group VARCHAR(50),
          equipment VARCHAR(50),
          unit VARCHAR(10) DEFAULT '次',
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
        """,
        """
        CREATE TABLE IF NOT EXISTS gym_equipment (
          id INT PRIMARY KEY AUTO_INCREMENT,
          name VARCHAR(50) NOT NULL UNIQUE,
          category VARCHAR(30),
          muscle_main VARCHAR(50),
          muscle_sec VARCHAR(100),
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
        """,
        """
        CREATE TABLE IF NOT EXISTS workout_sessions (
          id INT PRIMARY KEY AUTO_INCREMENT,
          user_id INT NOT NULL DEFAULT 1,
          workout_date DATE NOT NULL,
          start_time TIME,
          end_time TIME,
          duration_min INT,
          workout_type VARCHAR(30),
          sport_name VARCHAR(50),
          notes TEXT,
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
          INDEX idx_user_date (user_id, workout_date)
        )
        """,
        """
        CREATE TABLE IF NOT EXISTS workout_items (
          id INT PRIMARY KEY AUTO_INCREMENT,
          session_id INT NOT NULL,
          exercise_type_id INT NOT NULL,
          set_order INT DEFAULT 1,
          reps INT,
          weight_kg DECIMAL(6,2),
          distance_km DECIMAL(6,2),
          rpe INT,
          notes VARCHAR(100),
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
          FOREIGN KEY (session_id) REFERENCES workout_sessions(id) ON DELETE CASCADE,
          FOREIGN KEY (exercise_type_id) REFERENCES exercise_types(id),
          INDEX idx_session (session_id)
        )
        """
    ]
    
    # 创建表
    for i, sql in enumerate(tables_sql, 1):
        try:
            cursor.execute(sql)
            print(f"✅ 表 {i}/4 创建成功")
        except Error as e:
            print(f"❌ 创建表 {i} 失败: {e}")
            return False
    
    cursor.close()
    return True

def insert_default_data(conn):
    """插入默认数据"""
    cursor = conn.cursor()
    
    # 插入常见动作类型
    exercises = [
        ('杠铃深蹲', '腿', '杠铃', '次'),
        ('杠铃卧推', '胸', '杠铃', '次'),
        ('杠铃硬拉', '下背', '杠铃', '次'),
        ('引体向上', '背', '自重', '次'),
        ('哑铃弯举', '臂', '哑铃', '次'),
        ('哑铃划船', '背', '哑铃', '次'),
        ('腿举机', '腿', '器械', '次'),
        ('高位下拉', '背', '器械', '次'),
        ('罗马凳', '下背', '器械', '次'),
        ('跑步', '全身', '有氧', '分钟'),
        ('游泳', '全身', '有氧', '分钟'),
        ('网球', '全身', '球类', '分钟'),
        ('羽毛球', '全身', '球类', '分钟'),
        ('网球发球机', '全身', '球类', '分钟'),
        ('篮球', '全身', '球类', '分钟')
    ]
    
    inserted = 0
    for exercise in exercises:
        try:
            cursor.execute(
                "INSERT IGNORE INTO exercise_types (name, muscle_group, equipment, unit) VALUES (%s, %s, %s, %s)",
                exercise
            )
            inserted += cursor.rowcount
        except Error:
            pass
    
    print(f"✅ 插入 {inserted} 个默认动作类型")
    
    cursor.close()
    return True

def load_env_file(env_file='.workout-tracker.env'):
    """尝试从 .env 文件加载环境变量"""
    if os.path.exists(env_file):
        try:
            with open(env_file, 'r') as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith('#'):
                        if '=' in line:
                            key, value = line.split('=', 1)
                            key = key.strip()
                            value = value.strip()
                            if key and not os.getenv(key):
                                os.environ[key] = value
            return True
        except Exception:
            pass
    return False

def main():
    """主函数"""
    print("🏋️ Workout Tracker 数据库初始化")
    print("=" * 50)
    
    # 首先尝试加载 .env 文件
    load_env_file()
    
    # 检查环境变量
    if not os.getenv('MYSQL_USER') or not os.getenv('MYSQL_PASSWORD'):
        print("❌ 请先设置 MYSQL_USER 和 MYSQL_PASSWORD 环境变量")
        print("\n💡 配置方法:")
        print("1. 创建 .env 文件:")
        print("   echo 'MYSQL_USER=workout_user' > .workout-tracker.env")
        print("   echo 'MYSQL_PASSWORD=your_password' >> .workout-tracker.env")
        print("   source .workout-tracker.env")
        print("\n2. 或直接设置环境变量:")
        print("   export MYSQL_USER=workout_user")
        print("   export MYSQL_PASSWORD=your_password")
        sys.exit(1)
    
    config = get_db_config()
    
    try:
        # 连接数据库
        print("🔗 连接数据库...")
        conn = mysql.connector.connect(**config)
        print("✅ 数据库连接成功")
        
        # 创建表
        print("\n📊 创建数据表...")
        if not create_tables(conn):
            print("❌ 创建表失败")
            conn.close()
            sys.exit(1)
        
        # 插入默认数据
        print("\n📝 插入默认数据...")
        insert_default_data(conn)
        
        # 提交更改
        conn.commit()
        conn.close()
        
        print("\n" + "=" * 50)
        print("🎉 数据库初始化完成！")
        print("💪 现在可以开始使用 Workout Tracker 了")
        
    except Error as e:
        print(f"❌ 数据库操作失败: {e}")
        sys.exit(1)

if __name__ == "__main__":
    main()

path: scripts/verify_setup.py
content: #!/usr/bin/env python3
"""
Workout Tracker 安装验证脚本
验证 MySQL 连接和环境变量配置
"""

import os
import sys
import mysql.connector
from mysql.connector import Error

def load_env_file(env_file='.workout-tracker.env'):
    """尝试从 .env 文件加载环境变量"""
    if os.path.exists(env_file):
        print(f"📁 发现配置文件: {env_file}")
        try:
            with open(env_file, 'r') as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith('#'):
                        if '=' in line:
                            key, value = line.split('=', 1)
                            key = key.strip()
                            value = value.strip()
                            # 只设置未设置的环境变量
                            if key and not os.getenv(key):
                                os.environ[key] = value
                                print(f"  已加载: {key}")
            return True
        except Exception as e:
            print(f"⚠️  加载配置文件失败: {e}")
    return False

def check_environment():
    """检查必需的环境变量"""
    print("🔍 检查环境变量...")
    
    required_vars = ['MYSQL_USER', 'MYSQL_PASSWORD']
    optional_vars = ['MYSQL_SOCKET', 'MYSQL_DATABASE']
    
    missing = []
    for var in required_vars:
        if not os.getenv(var):
            missing.append(var)
    
    if missing:
        print(f"❌ 缺少必需的环境变量: {', '.join(missing)}")
        print("\n💡 配置方法（选择一种）:")
        print("1. 使用 .env 文件:")
        print("   echo 'MYSQL_USER=workout_user' > .workout-tracker.env")
        print("   echo 'MYSQL_PASSWORD=your_password' >> .workout-tracker.env")
        print("   source .workout-tracker.env")
        print("\n2. 设置环境变量:")
        for var in missing:
            print(f"   export {var}=your_value")
        print("\n3. 交互式输入（推荐）:")
        print("   python3 -c \"import getpass; import os; os.environ['MYSQL_PASSWORD'] = getpass.getpass('密码: ')\"")
        print("\n⚠️  安全提示: 不要将密码存储在版本控制或 ~/.bashrc 中")
        return False
    
    print("✅ 环境变量检查通过")
    
    # 显示配置信息（不显示密码）
    print(f"  用户: {os.getenv('MYSQL_USER')}")
    print(f"  Socket: {os.getenv('MYSQL_SOCKET', '/tmp/mysql.sock (默认)')}")
    print(f"  数据库: {os.getenv('MYSQL_DATABASE', 'workout_tracker (默认)')}")
    print(f"  密码: {'*' * 8} (已设置)")
    
    return True

def check_mysql_connection():
    """测试 MySQL 连接"""
    print("\n🔍 测试 MySQL 连接...")
    
    try:
        # 从环境变量获取配置
        config = {
            'unix_socket': os.getenv('MYSQL_SOCKET', '/tmp/mysql.sock'),
            'user': os.getenv('MYSQL_USER'),
            'password': os.getenv('MYSQL_PASSWORD'),
            'database': os.getenv('MYSQL_DATABASE', 'workout_tracker'),
            'raise_on_warnings': True
        }
        
        # 尝试连接
        conn = mysql.connector.connect(**config)
        
        # 获取服务器信息
        cursor = conn.cursor()
        cursor.execute("SELECT VERSION()")
        version = cursor.fetchone()[0]
        
        print(f"✅ MySQL 连接成功")
        print(f"  服务器版本: {version}")
        
        # 检查数据库是否存在
        cursor.execute("SHOW DATABASES LIKE %s", (config['database'],))
        if cursor.fetchone():
            print(f"  数据库 '{config['database']}' 存在")
        else:
            print(f"⚠️  数据库 '{config['database']}' 不存在，请运行初始化脚本")
        
        cursor.close()
        conn.close()
        return True
        
    except Error as e:
        print(f"❌ MySQL 连接失败: {e}")
        print("\n💡 故障排除:")
        print("1. 确保 MySQL 服务正在运行")
        print("2. 检查 socket 路径是否正确")
        print("3. 验证用户名和密码")
        print("4. 确保用户有访问权限")
        return False

def check_python_dependencies():
    """检查 Python 依赖"""
    print("\n🔍 检查 Python 依赖...")
    
    try:
        import mysql.connector
        print("✅ mysql-connector-python 已安装")
        return True
    except ImportError:
        print("❌ mysql-connector-python 未安装")
        print("💡 安装命令: pip install mysql-connector-python")
        return False

def main():
    """主函数"""
    print("🏋️ Workout Tracker 安装验证")
    print("=" * 50)
    
    all_passed = True
    
    # 首先尝试加载 .env 文件
    load_env_file()
    
    # 检查依赖
    if not check_python_dependencies():
        all_passed = False
    
    # 检查环境变量
    if not check_environment():
        all_passed = False
    else:
        # 只有环境变量正确才测试连接
        if not check_mysql_connection():
            all_passed = False
    
    print("\n" + "=" * 50)
    if all_passed:
        print("🎉 所有检查通过！Workout Tracker 可以正常运行")
        print("💪 现在可以使用技能了: 说'深蹲 60公斤 4组×8次'开始训练")
    else:
        print("⚠️  发现一些问题，请根据上面的提示修复")
        sys.exit(1)

if __name__ == "__main__":
    main()

Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)