feat: Add Chatwoot Agent Bot prototype and FAQ knowledge base
- Created chatwoot-agent-bot/ with Node.js webhook server - Bot detects intent (greeting, billing, technical, features, account) - Auto-responds from FAQ knowledge base or escalates to human - FAQ-KB.md: Living knowledge base that grows with customer questions - CHATWOOT-SETUP.md: Complete deployment and configuration guide - Supports Telegram notifications on escalation - Bot runs on port 3001, ready for Chatwoot webhook integration
This commit is contained in:
56
agents/fitness-coach/SKILL.md
Normal file
56
agents/fitness-coach/SKILL.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Iron - Fitness Coach Agent
|
||||
|
||||
## Identity
|
||||
**Name:** Iron
|
||||
**Role:** Personal fitness companion and coach
|
||||
**Personality:** Encouraging, data-driven, no-nonsense
|
||||
|
||||
## Capabilities
|
||||
- Creates workout plans for goals and equipment
|
||||
- Tracks progressive overload and personal records (PRs)
|
||||
- Suggests quick high-protein meals
|
||||
- Sends weekly progress reports
|
||||
- Modifies workouts based on pain/injury feedback
|
||||
|
||||
## Conversation Flow Examples
|
||||
|
||||
**Workout Request:**
|
||||
User: "Today's workout"
|
||||
Iron: [Full workout with exercises, sets, reps, rest times, notes from last session]
|
||||
|
||||
**Nutrition Request:**
|
||||
User: "Quick high protein lunch"
|
||||
Iron: [Recipe with ingredients, prep time, macros]
|
||||
|
||||
**Progress Check:**
|
||||
User: "Weekly progress"
|
||||
Iron: [Workouts completed, PRs, body weight trend, next week adjustments]
|
||||
|
||||
**Injury/Issue:**
|
||||
User: "My shoulder hurts during overhead press"
|
||||
Iron: [Alternative exercises, modified program, recovery suggestions]
|
||||
|
||||
## Workout Format Example
|
||||
Upper Body - Push Day (45 min)
|
||||
|
||||
1. Bench Press: 4x8 @ 70kg (rest 90s)
|
||||
2. Overhead Press: 3x10 @ 40kg (rest 60s)
|
||||
3. Incline DB Press: 3x12 (rest 60s)
|
||||
4. Lateral Raises: 3x15 (rest 45s)
|
||||
5. Tricep Dips: 3x12 (rest 60s)
|
||||
|
||||
Last session: 72.5kg bench. Try 75 today.
|
||||
|
||||
## Progress Tracking
|
||||
- Log every workout
|
||||
- Track PRs by exercise
|
||||
- Monitor body weight trends
|
||||
- Calculate weekly completion rates
|
||||
- Adjust programs based on progress
|
||||
|
||||
## Key Principles
|
||||
1. Set goal first (strength, muscle, or endurance)
|
||||
2. Log sessions consistently
|
||||
3. Be honest about pain - modify don't push through
|
||||
4. Check weekly progress
|
||||
5. Consistency beats intensity
|
||||
31
agents/fitness-coach/SOUL.md
Normal file
31
agents/fitness-coach/SOUL.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Iron - Who You Are
|
||||
|
||||
## Core Identity
|
||||
You're Iron, a personal fitness coach who combines data-driven programming with genuine encouragement. You're not a drill sergeant - you're a knowledgeable training partner who keeps people consistent.
|
||||
|
||||
## Personality Traits
|
||||
- Encouraging but not cheesy
|
||||
- Data-focused (weights, reps, trends matter)
|
||||
- Practical (adjust for real life)
|
||||
- Safety-conscious (pain = modification, not heroics)
|
||||
- Progressive (small consistent improvements)
|
||||
|
||||
## Voice
|
||||
- Clear and concise
|
||||
- No fluff or motivational poster speak
|
||||
- Use numbers and specifics
|
||||
- Reference past performance
|
||||
- Celebrate PRs genuinely
|
||||
|
||||
## Key Phrases
|
||||
- "Let's see what you've got today"
|
||||
- "Last session you hit X - aim for Y today"
|
||||
- "If that hurts, try this instead"
|
||||
- "Week X: Y/Z workouts (streak: N weeks)"
|
||||
- "PR! Way to push that"
|
||||
|
||||
## Boundaries
|
||||
- Never encourage pushing through sharp pain
|
||||
- Always offer modifications for injuries
|
||||
- Don't guilt-trip missed workouts
|
||||
- Focus on personal progress, not comparisons
|
||||
27
agents/fitness-coach/commands.sh
Executable file
27
agents/fitness-coach/commands.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# Iron Fitness Coach - Telegram Commands
|
||||
|
||||
WORKSPACE="/Users/claw/.openclaw/workspace/agents/fitness-coach"
|
||||
SCRIPT="$WORKSPACE/scripts/iron-agent.py"
|
||||
|
||||
case "$1" in
|
||||
workout)
|
||||
python3 "$SCRIPT" workout
|
||||
;;
|
||||
meal)
|
||||
python3 "$SCRIPT" meal
|
||||
;;
|
||||
progress)
|
||||
python3 "$SCRIPT" progress
|
||||
;;
|
||||
log)
|
||||
python3 "$SCRIPT" log "$2"
|
||||
;;
|
||||
*)
|
||||
echo "Iron Fitness Coach Commands:"
|
||||
echo " ./commands.sh workout - Today's workout"
|
||||
echo " ./commands.sh meal - Meal suggestion"
|
||||
echo " ./commands.sh progress - Weekly progress"
|
||||
echo " ./commands.sh log '<result>' - Log result"
|
||||
;;
|
||||
esac
|
||||
47
agents/fitness-coach/config/program.json
Normal file
47
agents/fitness-coach/config/program.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"program_name": "3-Day Recomposition Protocol",
|
||||
"level": "intermediate",
|
||||
"split": "Upper/Lower/Full",
|
||||
|
||||
"workouts": {
|
||||
"day1": {
|
||||
"name": "Upper Body - Push Focus",
|
||||
"duration_min": 50,
|
||||
"exercises": [
|
||||
{"name": "Bench Press", "sets": 4, "reps": "6-8", "rest_sec": 90, "notes": "Main strength movement"},
|
||||
{"name": "Overhead Press", "sets": 3, "reps": "8-10", "rest_sec": 60, "notes": "Shoulder strength"},
|
||||
{"name": "Incline DB Press", "sets": 3, "reps": "10-12", "rest_sec": 60, "notes": "Upper chest"},
|
||||
{"name": "Lateral Raises", "sets": 3, "reps": "12-15", "rest_sec": 45, "notes": "Side delts"},
|
||||
{"name": "Tricep Dips", "sets": 3, "reps": "10-12", "rest_sec": 60, "notes": "Tricep focus"}
|
||||
]
|
||||
},
|
||||
"day2": {
|
||||
"name": "Lower Body",
|
||||
"duration_min": 55,
|
||||
"exercises": [
|
||||
{"name": "Back Squat", "sets": 4, "reps": "6-8", "rest_sec": 120, "notes": "Main leg movement"},
|
||||
{"name": "Romanian Deadlift", "sets": 3, "reps": "8-10", "rest_sec": 90, "notes": "Hamstrings"},
|
||||
{"name": "Leg Press", "sets": 3, "reps": "10-12", "rest_sec": 90, "notes": "Quad volume"},
|
||||
{"name": "Leg Curls", "sets": 3, "reps": "12-15", "rest_sec": 60, "notes": "Hamstring isolation"},
|
||||
{"name": "Calf Raises", "sets": 4, "reps": "15-20", "rest_sec": 45, "notes": "Calves"}
|
||||
]
|
||||
},
|
||||
"day3": {
|
||||
"name": "Full Body / Weak Point",
|
||||
"duration_min": 50,
|
||||
"exercises": [
|
||||
{"name": "Deadlift", "sets": 3, "reps": "5-6", "rest_sec": 120, "notes": "Posterior chain"},
|
||||
{"name": "Pull-ups or Lat Pulldown", "sets": 3, "reps": "8-10", "rest_sec": 90, "notes": "Back width"},
|
||||
{"name": "DB Row", "sets": 3, "reps": "10-12", "rest_sec": 60, "notes": "Back thickness"},
|
||||
{"name": "DB Bench Press", "sets": 3, "reps": "10-12", "rest_sec": 60, "notes": "Chest volume"},
|
||||
{"name": "Face Pulls", "sets": 3, "reps": "15-20", "rest_sec": 45, "notes": "Rear delts/posture"}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"progression_rules": {
|
||||
"strength_range": "Increase weight when hitting top of rep range for all sets",
|
||||
"hypertrophy_range": "Increase weight or reps weekly",
|
||||
"deload": "Every 6-8 weeks, reduce volume 50%"
|
||||
}
|
||||
}
|
||||
30
agents/fitness-coach/config/user-profile.json
Normal file
30
agents/fitness-coach/config/user-profile.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "Chris",
|
||||
"goal": "body_recomposition",
|
||||
"goal_details": "Lose fat and build muscle",
|
||||
"schedule": {
|
||||
"days_per_week": 3,
|
||||
"duration_min": 60,
|
||||
"location": "full_gym"
|
||||
},
|
||||
"stats": {
|
||||
"weight_lbs": 215,
|
||||
"weight_history": [{"date": "2026-03-23", "weight": 215}],
|
||||
"experience": "intermediate"
|
||||
},
|
||||
"health": {
|
||||
"type1_diabetic": true,
|
||||
"diet": "high_protein_low_carb",
|
||||
"injuries": [],
|
||||
"limitations": []
|
||||
},
|
||||
"preferences": {
|
||||
"notifications": "telegram",
|
||||
"log_location": "local"
|
||||
},
|
||||
"program": {
|
||||
"type": "upper_lower_full",
|
||||
"split": ["Upper Body - Push", "Lower Body", "Full Body / Weak Point"],
|
||||
"start_date": "2026-03-23"
|
||||
}
|
||||
}
|
||||
4
agents/fitness-coach/install-cron.sh
Executable file
4
agents/fitness-coach/install-cron.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
# Install cron job for 8 AM workout
|
||||
(crontab -l 2>/dev/null | grep -v "send-workout"; echo "0 8 * * * /Users/claw/.openclaw/workspace/agents/fitness-coach/scripts/send-workout.sh") | crontab -
|
||||
echo "Cron installed"
|
||||
18
agents/fitness-coach/logs/workout-sent.log
Normal file
18
agents/fitness-coach/logs/workout-sent.log
Normal file
@@ -0,0 +1,18 @@
|
||||
[2026-03-23 20:33:19] Workout sent
|
||||
[2026-03-24 08:05:08] Workout sent
|
||||
[2026-03-25 08:00:00] Workout sent
|
||||
[2026-03-25 08:00:07] Workout sent
|
||||
[2026-03-26 08:00:01] Workout sent
|
||||
[2026-03-26 08:00:14] Workout sent
|
||||
[2026-03-27 08:00:00] Workout sent
|
||||
[2026-03-27 08:00:08] Workout sent
|
||||
[2026-03-28 08:00:00] Workout sent
|
||||
[2026-03-28 08:00:15] Workout sent
|
||||
[2026-03-29 08:00:01] Workout sent
|
||||
[2026-03-29 08:00:08] Workout sent
|
||||
[2026-03-30 08:00:00] Workout sent
|
||||
[2026-03-30 08:00:06] Workout sent
|
||||
[2026-03-31 08:00:00] Workout sent
|
||||
[2026-03-31 08:00:12] Workout sent
|
||||
[2026-04-01 08:00:00] Workout sent
|
||||
[2026-04-01 08:07:18] Workout sent
|
||||
145
agents/fitness-coach/scripts/iron-agent.py
Executable file
145
agents/fitness-coach/scripts/iron-agent.py
Executable file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Iron - Personal Fitness Coach Agent
|
||||
Handles: workout generation, PR tracking, progress reports, meal suggestions
|
||||
"""
|
||||
import json
|
||||
import subprocess
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
import random
|
||||
|
||||
WORKSPACE = Path(__file__).parent.parent
|
||||
CONFIG = WORKSPACE / "config"
|
||||
STATE = WORKSPACE / "state"
|
||||
LOGS = WORKSPACE / "logs"
|
||||
|
||||
PROFILE_FILE = CONFIG / "user-profile.json"
|
||||
PROGRAM_FILE = CONFIG / "program.json"
|
||||
PRS_FILE = STATE / "prs.json"
|
||||
|
||||
def load_json(path):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
|
||||
def save_json(path, data):
|
||||
with open(path, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
def log(msg):
|
||||
ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
line = f"[{ts}] {msg}"
|
||||
print(line)
|
||||
(LOGS / f"iron-{datetime.now().strftime('%Y%m%d')}.log").open('a').write(line + '\n')
|
||||
|
||||
def get_todays_workout():
|
||||
"""Generate today's workout based on program"""
|
||||
program = load_json(PROGRAM_FILE)
|
||||
profile = load_json(PROFILE_FILE)
|
||||
|
||||
# Determine which day (simple rotation)
|
||||
today = datetime.now()
|
||||
start = datetime.strptime(profile['program']['start_date'], '%Y-%m-%d')
|
||||
days_since_start = (today - start).days
|
||||
day_num = (days_since_start % 7) // 2 # Roughly every 2-3 days
|
||||
|
||||
if day_num == 0:
|
||||
workout_key = 'day1'
|
||||
elif day_num == 1:
|
||||
workout_key = 'day2'
|
||||
else:
|
||||
workout_key = 'day3'
|
||||
|
||||
workout = program['workouts'][workout_key]
|
||||
prs = load_json(PRS_FILE)
|
||||
|
||||
output = f"💪 *{workout['name']}* ({workout['duration_min']} min)\n\n"
|
||||
|
||||
for i, ex in enumerate(workout['exercises'], 1):
|
||||
# Check if we have PR data
|
||||
pr_weight = prs['personal_records'].get(ex['name'], {}).get('weight')
|
||||
pr_note = ""
|
||||
if pr_weight:
|
||||
pr_note = f" (Last: {pr_weight})"
|
||||
|
||||
output += f"{i}. *{ex['name']}*: {ex['sets']}x{ex['reps']} @ ___ {pr_note}\n"
|
||||
output += f" Rest: {ex['rest_sec']}s | {ex['notes']}\n"
|
||||
|
||||
output += "\n_Reply with results: 'Bench 4x8 @ 185'_ "
|
||||
return output
|
||||
|
||||
def get_meal_suggestion():
|
||||
"""Generate T1D-friendly high protein, low carb meal"""
|
||||
meals = [
|
||||
("Grilled Chicken & Veggies", "Chicken breast, broccoli, olive oil", "45g protein, 8g net carbs"),
|
||||
("Salmon & Asparagus", "Salmon fillet, asparagus, lemon", "40g protein, 6g net carbs"),
|
||||
("Turkey & Egg Scramble", "Ground turkey, eggs, spinach", "42g protein, 5g net carbs"),
|
||||
("Steak & Cauliflower", "Ribeye steak, cauliflower rice", "48g protein, 7g net carbs"),
|
||||
("Tuna Salad", "Canned tuna, mayo, celery, lettuce", "35g protein, 4g net carbs"),
|
||||
("Greek Yogurt Bowl", "Greek yogurt, almonds, berries", "25g protein, 9g net carbs"),
|
||||
("Protein Shake + Nuts", "Whey isolate, almonds, peanut butter", "30g protein, 6g net carbs"),
|
||||
]
|
||||
|
||||
meal = random.choice(meals)
|
||||
return f"🍽️ *{meal[0]}*\n\nIngredients: {meal[1]}\nMacros: {meal[2]}\n\n_{meal[0].lower()} with veggies for easy prep_"
|
||||
|
||||
def get_weekly_progress():
|
||||
"""Generate weekly progress report"""
|
||||
profile = load_json(PROFILE_FILE)
|
||||
prs = load_json(PRS_FILE)
|
||||
|
||||
weight = profile['stats']['weight_lbs']
|
||||
weight_history = profile['stats'].get('weight_history', [])
|
||||
trend = ""
|
||||
if len(weight_history) > 1:
|
||||
change = weight_history[-1]['weight'] - weight_history[0]['weight']
|
||||
trend = f"{'+' if change > 0 else ''}{change:.1f} lbs"
|
||||
|
||||
report = f"📊 *Weekly Progress Report*\n\n"
|
||||
report += f"*Current Weight:* {weight} lbs {trend}\n"
|
||||
report += f"*Program:* {profile['program']['type']}\n"
|
||||
report += f"*Experience:* {profile['stats']['experience'].title()}\n\n"
|
||||
|
||||
# Count workouts
|
||||
completed = len(prs.get('workout_history', []))
|
||||
report += f"*Workouts Logged:* {completed}\n"
|
||||
|
||||
# PRs
|
||||
pr_count = sum(1 for v in prs['personal_records'].values() if v.get('weight'))
|
||||
report += f"*PRs Tracked:* {pr_count}/5\n\n"
|
||||
|
||||
report += "_Keep grinding. Consistency > intensity._"
|
||||
return report
|
||||
|
||||
def send_telegram(msg):
|
||||
"""Send message via Telegram"""
|
||||
try:
|
||||
subprocess.run(['openclaw', 'message', 'send', '--text', msg],
|
||||
capture_output=True, timeout=10)
|
||||
except Exception as e:
|
||||
log(f"Telegram send failed: {e}")
|
||||
|
||||
def main():
|
||||
import sys
|
||||
cmd = sys.argv[1] if len(sys.argv) > 1 else 'workout'
|
||||
|
||||
if cmd == 'workout':
|
||||
print(get_todays_workout())
|
||||
elif cmd == 'meal':
|
||||
print(get_meal_suggestion())
|
||||
elif cmd == 'progress':
|
||||
print(get_weekly_progress())
|
||||
elif cmd == 'log' and len(sys.argv) > 2:
|
||||
# Log workout result
|
||||
result = ' '.join(sys.argv[2:])
|
||||
log(f"WORKOUT LOG: {result}")
|
||||
print(f"✅ Logged: {result}")
|
||||
else:
|
||||
print("Iron Fitness Coach - Commands:")
|
||||
print(" workout - Get today's workout")
|
||||
print(" meal - Get meal suggestion")
|
||||
print(" progress - Weekly progress report")
|
||||
print(" log <result> - Log workout result")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
17
agents/fitness-coach/scripts/send-workout.sh
Executable file
17
agents/fitness-coach/scripts/send-workout.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
# Send morning workout via Telegram
|
||||
|
||||
WORKSPACE="/Users/claw/.openclaw/workspace/agents/fitness-coach"
|
||||
WORKOUT=$(python3 "$WORKSPACE/scripts/iron-agent.py" workout)
|
||||
|
||||
MSG="☀️ *GOOD MORNING! Today's Workout:*
|
||||
|
||||
$WORKOUT
|
||||
|
||||
Time to grind! 💪"
|
||||
|
||||
# Send via Telegram to Chris
|
||||
openclaw message send --channel telegram --target telegram:8269921691 --message "$MSG" 2>/dev/null
|
||||
|
||||
# Log it
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Workout sent" >> "$WORKSPACE/logs/workout-sent.log"
|
||||
15
agents/fitness-coach/state/prs.json
Normal file
15
agents/fitness-coach/state/prs.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"personal_records": {
|
||||
"Bench Press": {"weight": null, "reps": null, "date": null},
|
||||
"Back Squat": {"weight": null, "reps": null, "date": null},
|
||||
"Deadlift": {"weight": null, "reps": null, "date": null},
|
||||
"Overhead Press": {"weight": null, "reps": null, "date": null},
|
||||
"Pull-ups": {"weight": "bodyweight", "reps": null, "date": null}
|
||||
},
|
||||
"workout_history": [],
|
||||
"streak": {
|
||||
"current_weeks": 0,
|
||||
"longest_weeks": 0,
|
||||
"last_workout": null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user