feat(skills): add fitness-nutrition skill to optional-skills

Cherry-picked from PR #9177 by @haileymarshall.

Adds a fitness and nutrition skill for gym-goers and health-conscious users:
- Exercise search via wger API (690+ exercises, free, no auth)
- Nutrition lookup via USDA FoodData Central (380K+ foods, DEMO_KEY fallback)
- Offline body composition calculators (BMI, TDEE, 1RM, macros, body fat %)
- Pure stdlib Python, no pip dependencies

Changes from original PR:
- Moved from skills/ to optional-skills/health/ (correct location)
- Fixed BMR formula in FORMULAS.md (removed confusing -5+10, now just +5)
- Fixed author attribution to match PR submitter
- Marked USDA_API_KEY as optional (DEMO_KEY works without signup)

Also adds optional env var support to the skill readiness checker:
- New 'optional: true' field in required_environment_variables entries
- Optional vars are preserved in metadata but don't block skill readiness
- Optional vars skip the CLI capture prompt flow
- Skills with only optional missing vars show as 'available' not 'setup_needed'
This commit is contained in:
haileymarshall 2026-04-13 20:31:18 -07:00 committed by Teknium
parent 62fb6b2cd8
commit f0b353bade
5 changed files with 658 additions and 1 deletions

View file

@ -0,0 +1,255 @@
---
name: fitness-nutrition
description: >
Gym workout planner and nutrition tracker. Search 690+ exercises by muscle,
equipment, or category via wger. Look up macros and calories for 380,000+
foods via USDA FoodData Central. Compute BMI, TDEE, one-rep max, macro
splits, and body fat — pure Python, no pip installs. Built for anyone
chasing gains, cutting weight, or just trying to eat better.
version: 1.0.0
authors:
- haileymarshall
license: MIT
metadata:
hermes:
tags: [health, fitness, nutrition, gym, workout, diet, exercise]
category: health
prerequisites:
commands: [curl, python3]
required_environment_variables:
- name: USDA_API_KEY
prompt: "USDA FoodData Central API key (free)"
help: "Get one free at https://fdc.nal.usda.gov/api-key-signup/ — or skip to use DEMO_KEY with lower rate limits"
required_for: "higher rate limits on food/nutrition lookups (DEMO_KEY works without signup)"
optional: true
---
# Fitness & Nutrition
Expert fitness coach and sports nutritionist skill. Two data sources
plus offline calculators — everything a gym-goer needs in one place.
**Data sources (all free, no pip dependencies):**
- **wger** (https://wger.de/api/v2/) — open exercise database, 690+ exercises with muscles, equipment, images. Public endpoints need zero authentication.
- **USDA FoodData Central** (https://api.nal.usda.gov/fdc/v1/) — US government nutrition database, 380,000+ foods. `DEMO_KEY` works instantly; free signup for higher limits.
**Offline calculators (pure stdlib Python):**
- BMI, TDEE (Mifflin-St Jeor), one-rep max (Epley/Brzycki/Lombardi), macro splits, body fat % (US Navy method)
---
## When to Use
Trigger this skill when the user asks about:
- Exercises, workouts, gym routines, muscle groups, workout splits
- Food macros, calories, protein content, meal planning, calorie counting
- Body composition: BMI, body fat, TDEE, caloric surplus/deficit
- One-rep max estimates, training percentages, progressive overload
- Macro ratios for cutting, bulking, or maintenance
---
## Procedure
### Exercise Lookup (wger API)
All wger public endpoints return JSON and require no auth. Always add
`format=json` and `language=2` (English) to exercise queries.
**Step 1 — Identify what the user wants:**
- By muscle → use `/api/v2/exercise/?muscles={id}&language=2&status=2&format=json`
- By category → use `/api/v2/exercise/?category={id}&language=2&status=2&format=json`
- By equipment → use `/api/v2/exercise/?equipment={id}&language=2&status=2&format=json`
- By name → use `/api/v2/exercise/search/?term={query}&language=english&format=json`
- Full details → use `/api/v2/exerciseinfo/{exercise_id}/?format=json`
**Step 2 — Reference IDs (so you don't need extra API calls):**
Exercise categories:
| ID | Category |
|----|-------------|
| 8 | Arms |
| 9 | Legs |
| 10 | Abs |
| 11 | Chest |
| 12 | Back |
| 13 | Shoulders |
| 14 | Calves |
| 15 | Cardio |
Muscles:
| ID | Muscle | ID | Muscle |
|----|---------------------------|----|-------------------------|
| 1 | Biceps brachii | 2 | Anterior deltoid |
| 3 | Serratus anterior | 4 | Pectoralis major |
| 5 | Obliquus externus | 6 | Gastrocnemius |
| 7 | Rectus abdominis | 8 | Gluteus maximus |
| 9 | Trapezius | 10 | Quadriceps femoris |
| 11 | Biceps femoris | 12 | Latissimus dorsi |
| 13 | Brachialis | 14 | Triceps brachii |
| 15 | Soleus | | |
Equipment:
| ID | Equipment |
|----|----------------|
| 1 | Barbell |
| 3 | Dumbbell |
| 4 | Gym mat |
| 5 | Swiss Ball |
| 6 | Pull-up bar |
| 7 | none (bodyweight) |
| 8 | Bench |
| 9 | Incline bench |
| 10 | Kettlebell |
**Step 3 — Fetch and present results:**
```bash
# Search exercises by name
QUERY="$1"
ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$QUERY")
curl -s "https://wger.de/api/v2/exercise/search/?term=${ENCODED}&language=english&format=json" \
| python3 -c "
import json,sys
data=json.load(sys.stdin)
for s in data.get('suggestions',[])[:10]:
d=s.get('data',{})
print(f\" ID {d.get('id','?'):>4} | {d.get('name','N/A'):<35} | Category: {d.get('category','N/A')}\")
"
```
```bash
# Get full details for a specific exercise
EXERCISE_ID="$1"
curl -s "https://wger.de/api/v2/exerciseinfo/${EXERCISE_ID}/?format=json" \
| python3 -c "
import json,sys,html,re
data=json.load(sys.stdin)
trans=[t for t in data.get('translations',[]) if t.get('language')==2]
t=trans[0] if trans else data.get('translations',[{}])[0]
desc=re.sub('<[^>]+>','',html.unescape(t.get('description','N/A')))
print(f\"Exercise : {t.get('name','N/A')}\")
print(f\"Category : {data.get('category',{}).get('name','N/A')}\")
print(f\"Primary : {', '.join(m.get('name_en','') for m in data.get('muscles',[])) or 'N/A'}\")
print(f\"Secondary : {', '.join(m.get('name_en','') for m in data.get('muscles_secondary',[])) or 'none'}\")
print(f\"Equipment : {', '.join(e.get('name','') for e in data.get('equipment',[])) or 'bodyweight'}\")
print(f\"How to : {desc[:500]}\")
imgs=data.get('images',[])
if imgs: print(f\"Image : {imgs[0].get('image','')}\")
"
```
```bash
# List exercises filtering by muscle, category, or equipment
# Combine filters as needed: ?muscles=4&equipment=1&language=2&status=2
FILTER="$1" # e.g. "muscles=4" or "category=11" or "equipment=3"
curl -s "https://wger.de/api/v2/exercise/?${FILTER}&language=2&status=2&limit=20&format=json" \
| python3 -c "
import json,sys
data=json.load(sys.stdin)
print(f'Found {data.get(\"count\",0)} exercises.')
for ex in data.get('results',[]):
print(f\" ID {ex['id']:>4} | muscles: {ex.get('muscles',[])} | equipment: {ex.get('equipment',[])}\")
"
```
### Nutrition Lookup (USDA FoodData Central)
Uses `USDA_API_KEY` env var if set, otherwise falls back to `DEMO_KEY`.
DEMO_KEY = 30 requests/hour. Free signup key = 1,000 requests/hour.
```bash
# Search foods by name
FOOD="$1"
API_KEY="${USDA_API_KEY:-DEMO_KEY}"
ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$FOOD")
curl -s "https://api.nal.usda.gov/fdc/v1/foods/search?api_key=${API_KEY}&query=${ENCODED}&pageSize=5&dataType=Foundation,SR%20Legacy" \
| python3 -c "
import json,sys
data=json.load(sys.stdin)
foods=data.get('foods',[])
if not foods: print('No foods found.'); sys.exit()
for f in foods:
n={x['nutrientName']:x.get('value','?') for x in f.get('foodNutrients',[])}
cal=n.get('Energy','?'); prot=n.get('Protein','?')
fat=n.get('Total lipid (fat)','?'); carb=n.get('Carbohydrate, by difference','?')
print(f\"{f.get('description','N/A')}\")
print(f\" Per 100g: {cal} kcal | {prot}g protein | {fat}g fat | {carb}g carbs\")
print(f\" FDC ID: {f.get('fdcId','N/A')}\")
print()
"
```
```bash
# Detailed nutrient profile by FDC ID
FDC_ID="$1"
API_KEY="${USDA_API_KEY:-DEMO_KEY}"
curl -s "https://api.nal.usda.gov/fdc/v1/food/${FDC_ID}?api_key=${API_KEY}" \
| python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f\"Food: {d.get('description','N/A')}\")
print(f\"{'Nutrient':<40} {'Amount':>8} {'Unit'}\")
print('-'*56)
for x in sorted(d.get('foodNutrients',[]),key=lambda x:x.get('nutrient',{}).get('rank',9999)):
nut=x.get('nutrient',{}); amt=x.get('amount',0)
if amt and float(amt)>0:
print(f\" {nut.get('name',''):<38} {amt:>8} {nut.get('unitName','')}\")
"
```
### Offline Calculators
Use the helper scripts in `scripts/` for batch operations,
or run inline for single calculations:
- `python3 scripts/body_calc.py bmi <weight_kg> <height_cm>`
- `python3 scripts/body_calc.py tdee <weight_kg> <height_cm> <age> <M|F> <activity 1-5>`
- `python3 scripts/body_calc.py 1rm <weight> <reps>`
- `python3 scripts/body_calc.py macros <tdee_kcal> <cut|maintain|bulk>`
- `python3 scripts/body_calc.py bodyfat <M|F> <neck_cm> <waist_cm> [hip_cm] <height_cm>`
See `references/FORMULAS.md` for the science behind each formula.
---
## Pitfalls
- wger exercise endpoint returns **all languages by default** — always add `language=2` for English
- wger includes **unverified user submissions** — add `status=2` to only get approved exercises
- USDA `DEMO_KEY` has **30 req/hour** — add `sleep 2` between batch requests or get a free key
- USDA data is **per 100g** — remind users to scale to their actual portion size
- BMI does not distinguish muscle from fat — high BMI in muscular people is not necessarily unhealthy
- Body fat formulas are **estimates** (±3-5%) — recommend DEXA scans for precision
- 1RM formulas lose accuracy above 10 reps — use sets of 3-5 for best estimates
- wger's `exercise/search` endpoint uses `term` not `query` as the parameter name
---
## Verification
After running exercise search: confirm results include exercise names, muscle groups, and equipment.
After nutrition lookup: confirm per-100g macros are returned with kcal, protein, fat, carbs.
After calculators: sanity-check outputs (e.g. TDEE should be 1500-3500 for most adults).
---
## Quick Reference
| Task | Source | Endpoint |
|------|--------|----------|
| Search exercises by name | wger | `GET /api/v2/exercise/search/?term=&language=english` |
| Exercise details | wger | `GET /api/v2/exerciseinfo/{id}/` |
| Filter by muscle | wger | `GET /api/v2/exercise/?muscles={id}&language=2&status=2` |
| Filter by equipment | wger | `GET /api/v2/exercise/?equipment={id}&language=2&status=2` |
| List categories | wger | `GET /api/v2/exercisecategory/` |
| List muscles | wger | `GET /api/v2/muscle/` |
| Search foods | USDA | `GET /fdc/v1/foods/search?query=&dataType=Foundation,SR Legacy` |
| Food details | USDA | `GET /fdc/v1/food/{fdcId}` |
| BMI / TDEE / 1RM / macros | offline | `python3 scripts/body_calc.py` |

View file

@ -0,0 +1,100 @@
# Formulas Reference
Scientific references for all calculators used in the fitness-nutrition skill.
## BMI (Body Mass Index)
**Formula:** BMI = weight (kg) / height (m)²
| Category | BMI Range |
|-------------|------------|
| Underweight | < 18.5 |
| Normal | 18.5 24.9 |
| Overweight | 25.0 29.9 |
| Obese | 30.0+ |
**Limitation:** BMI does not distinguish muscle from fat. A muscular person
can have a high BMI while being lean. Use body fat % for a better picture.
Reference: Quetelet, A. (1832). Keys et al., Int J Obes (1972).
## TDEE (Total Daily Energy Expenditure)
Uses the **Mifflin-St Jeor equation** — the most accurate BMR predictor for
the general population according to the ADA (2005).
**BMR formulas:**
- Male: BMR = 10 × weight(kg) + 6.25 × height(cm) 5 × age + 5
- Female: BMR = 10 × weight(kg) + 6.25 × height(cm) 5 × age 161
**Activity multipliers:**
| Level | Description | Multiplier |
|-------|--------------------------------|------------|
| 1 | Sedentary (desk job) | 1.200 |
| 2 | Lightly active (1-3 days/wk) | 1.375 |
| 3 | Moderately active (3-5 days) | 1.550 |
| 4 | Very active (6-7 days) | 1.725 |
| 5 | Extremely active (2x/day) | 1.900 |
Reference: Mifflin et al., Am J Clin Nutr 51, 241-247 (1990).
## One-Rep Max (1RM)
Three validated formulas. Average of all three is most reliable.
- **Epley:** 1RM = w × (1 + r/30)
- **Brzycki:** 1RM = w × 36 / (37 r)
- **Lombardi:** 1RM = w × r^0.1
All formulas are most accurate for r ≤ 10. Above 10 reps, error increases.
Reference: LeSuer et al., J Strength Cond Res 11(4), 211-213 (1997).
## Macro Splits
Recommended splits based on goal:
| Goal | Protein | Fat | Carbs | Calorie Offset |
|-------------|---------|------|-------|----------------|
| Fat loss | 40% | 30% | 30% | 500 kcal |
| Maintenance | 30% | 30% | 40% | 0 |
| Lean bulk | 30% | 25% | 45% | +400 kcal |
Protein targets for muscle growth: 1.62.2 g/kg body weight per day.
Minimum fat intake: 0.5 g/kg to support hormone production.
Conversion: Protein = 4 kcal/g, Fat = 9 kcal/g, Carbs = 4 kcal/g.
Reference: Morton et al., Br J Sports Med 52, 376384 (2018).
## Body Fat % (US Navy Method)
**Male:**
BF% = 86.010 × log₁₀(waist neck) 70.041 × log₁₀(height) + 36.76
**Female:**
BF% = 163.205 × log₁₀(waist + hip neck) 97.684 × log₁₀(height) 78.387
All measurements in centimeters.
| Category | Male | Female |
|--------------|--------|--------|
| Essential | 2-5% | 10-13% |
| Athletic | 6-13% | 14-20% |
| Fitness | 14-17% | 21-24% |
| Average | 18-24% | 25-31% |
| Obese | 25%+ | 32%+ |
Accuracy: ±3-5% compared to DEXA. Measure at the navel (waist),
at the Adam's apple (neck), and widest point (hip, females only).
Reference: Hodgdon & Beckett, Naval Health Research Center (1984).
## APIs
- wger: https://wger.de/api/v2/ — AGPL-3.0, exercise data is CC-BY-SA 3.0
- USDA FoodData Central: https://api.nal.usda.gov/fdc/v1/ — public domain (CC0 1.0)

View file

@ -0,0 +1,210 @@
#!/usr/bin/env python3
"""
body_calc.py All-in-one fitness calculator.
Subcommands:
bmi <weight_kg> <height_cm>
tdee <weight_kg> <height_cm> <age> <M|F> <activity 1-5>
1rm <weight> <reps>
macros <tdee_kcal> <cut|maintain|bulk>
bodyfat <M|F> <neck_cm> <waist_cm> [hip_cm] <height_cm>
No external dependencies stdlib only.
"""
import sys
import math
def bmi(weight_kg, height_cm):
h = height_cm / 100
val = weight_kg / (h * h)
if val < 18.5:
cat = "Underweight"
elif val < 25:
cat = "Normal weight"
elif val < 30:
cat = "Overweight"
else:
cat = "Obese"
print(f"BMI: {val:.1f}{cat}")
print()
print("Ranges:")
print(f" Underweight : < 18.5")
print(f" Normal : 18.5 24.9")
print(f" Overweight : 25.0 29.9")
print(f" Obese : 30.0+")
def tdee(weight_kg, height_cm, age, sex, activity):
if sex.upper() == "M":
bmr = 10 * weight_kg + 6.25 * height_cm - 5 * age + 5
else:
bmr = 10 * weight_kg + 6.25 * height_cm - 5 * age - 161
multipliers = {
1: ("Sedentary (desk job, no exercise)", 1.2),
2: ("Lightly active (1-3 days/week)", 1.375),
3: ("Moderately active (3-5 days/week)", 1.55),
4: ("Very active (6-7 days/week)", 1.725),
5: ("Extremely active (athlete + physical job)", 1.9),
}
label, mult = multipliers.get(activity, ("Moderate", 1.55))
total = bmr * mult
print(f"BMR (Mifflin-St Jeor): {bmr:.0f} kcal/day")
print(f"Activity: {label} (x{mult})")
print(f"TDEE: {total:.0f} kcal/day")
print()
print("Calorie targets:")
print(f" Aggressive cut (-750): {total - 750:.0f} kcal/day")
print(f" Fat loss (-500): {total - 500:.0f} kcal/day")
print(f" Mild cut (-250): {total - 250:.0f} kcal/day")
print(f" Maintenance : {total:.0f} kcal/day")
print(f" Lean bulk (+250): {total + 250:.0f} kcal/day")
print(f" Bulk (+500): {total + 500:.0f} kcal/day")
def one_rep_max(weight, reps):
if reps < 1:
print("Error: reps must be at least 1.")
sys.exit(1)
if reps == 1:
print(f"1RM = {weight:.1f} (actual single)")
return
epley = weight * (1 + reps / 30)
brzycki = weight * (36 / (37 - reps)) if reps < 37 else 0
lombardi = weight * (reps ** 0.1)
avg = (epley + brzycki + lombardi) / 3
print(f"Estimated 1RM ({weight} x {reps} reps):")
print(f" Epley : {epley:.1f}")
print(f" Brzycki : {brzycki:.1f}")
print(f" Lombardi : {lombardi:.1f}")
print(f" Average : {avg:.1f}")
print()
print("Training percentages off average 1RM:")
for pct, rep_range in [
(100, "1"), (95, "1-2"), (90, "3-4"), (85, "4-6"),
(80, "6-8"), (75, "8-10"), (70, "10-12"),
(65, "12-15"), (60, "15-20"),
]:
print(f" {pct:>3}% = {avg * pct / 100:>7.1f} (~{rep_range} reps)")
def macros(tdee_kcal, goal):
goal = goal.lower()
if goal in ("cut", "lose", "deficit"):
cals = tdee_kcal - 500
p, f, c = 0.40, 0.30, 0.30
label = "Fat Loss (-500 kcal)"
elif goal in ("bulk", "gain", "surplus"):
cals = tdee_kcal + 400
p, f, c = 0.30, 0.25, 0.45
label = "Lean Bulk (+400 kcal)"
else:
cals = tdee_kcal
p, f, c = 0.30, 0.30, 0.40
label = "Maintenance"
prot_g = cals * p / 4
fat_g = cals * f / 9
carb_g = cals * c / 4
print(f"Goal: {label}")
print(f"Daily calories: {cals:.0f} kcal")
print()
print(f" Protein : {prot_g:>6.0f}g ({p * 100:.0f}%) = {prot_g * 4:.0f} kcal")
print(f" Fat : {fat_g:>6.0f}g ({f * 100:.0f}%) = {fat_g * 9:.0f} kcal")
print(f" Carbs : {carb_g:>6.0f}g ({c * 100:.0f}%) = {carb_g * 4:.0f} kcal")
print()
print(f"Per meal (3 meals): P {prot_g / 3:.0f}g | F {fat_g / 3:.0f}g | C {carb_g / 3:.0f}g")
print(f"Per meal (4 meals): P {prot_g / 4:.0f}g | F {fat_g / 4:.0f}g | C {carb_g / 4:.0f}g")
def bodyfat(sex, neck_cm, waist_cm, hip_cm, height_cm):
sex = sex.upper()
if sex == "M":
if waist_cm <= neck_cm:
print("Error: waist must be larger than neck."); sys.exit(1)
bf = 86.010 * math.log10(waist_cm - neck_cm) - 70.041 * math.log10(height_cm) + 36.76
else:
if (waist_cm + hip_cm) <= neck_cm:
print("Error: waist + hip must be larger than neck."); sys.exit(1)
bf = 163.205 * math.log10(waist_cm + hip_cm - neck_cm) - 97.684 * math.log10(height_cm) - 78.387
print(f"Estimated body fat: {bf:.1f}%")
if sex == "M":
ranges = [
(6, "Essential fat (2-5%)"),
(14, "Athletic (6-13%)"),
(18, "Fitness (14-17%)"),
(25, "Average (18-24%)"),
]
default = "Obese (25%+)"
else:
ranges = [
(14, "Essential fat (10-13%)"),
(21, "Athletic (14-20%)"),
(25, "Fitness (21-24%)"),
(32, "Average (25-31%)"),
]
default = "Obese (32%+)"
cat = default
for threshold, label in ranges:
if bf < threshold:
cat = label
break
print(f"Category: {cat}")
print(f"Method: US Navy circumference formula")
def usage():
print(__doc__)
sys.exit(1)
def main():
if len(sys.argv) < 2:
usage()
cmd = sys.argv[1].lower()
try:
if cmd == "bmi":
bmi(float(sys.argv[2]), float(sys.argv[3]))
elif cmd == "tdee":
tdee(
float(sys.argv[2]), float(sys.argv[3]),
int(sys.argv[4]), sys.argv[5], int(sys.argv[6]),
)
elif cmd in ("1rm", "orm"):
one_rep_max(float(sys.argv[2]), int(sys.argv[3]))
elif cmd == "macros":
macros(float(sys.argv[2]), sys.argv[3])
elif cmd == "bodyfat":
sex = sys.argv[2]
if sex.upper() == "M":
bodyfat(sex, float(sys.argv[3]), float(sys.argv[4]), 0, float(sys.argv[5]))
else:
bodyfat(sex, float(sys.argv[3]), float(sys.argv[4]), float(sys.argv[5]), float(sys.argv[6]))
else:
print(f"Unknown command: {cmd}")
usage()
except (IndexError, ValueError) as e:
print(f"Error: {e}")
usage()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""
nutrition_search.py Search USDA FoodData Central for nutrition info.
Usage:
python3 nutrition_search.py "chicken breast"
python3 nutrition_search.py "rice" "eggs" "broccoli"
echo -e "oats\\nbanana\\nwhey protein" | python3 nutrition_search.py -
Reads USDA_API_KEY from environment, falls back to DEMO_KEY.
No external dependencies.
"""
import sys
import os
import json
import time
import urllib.request
import urllib.parse
import urllib.error
API_KEY = os.environ.get("USDA_API_KEY", "DEMO_KEY")
BASE = "https://api.nal.usda.gov/fdc/v1"
def search(query, max_results=3):
encoded = urllib.parse.quote(query)
url = (
f"{BASE}/foods/search?api_key={API_KEY}"
f"&query={encoded}&pageSize={max_results}"
f"&dataType=Foundation,SR%20Legacy"
)
try:
req = urllib.request.Request(url, headers={"Accept": "application/json"})
with urllib.request.urlopen(req, timeout=15) as r:
return json.loads(r.read())
except Exception as e:
print(f" API error: {e}", file=sys.stderr)
return None
def display(food):
nutrients = {n["nutrientName"]: n.get("value", "?") for n in food.get("foodNutrients", [])}
cal = nutrients.get("Energy", "?")
prot = nutrients.get("Protein", "?")
fat = nutrients.get("Total lipid (fat)", "?")
carb = nutrients.get("Carbohydrate, by difference", "?")
fib = nutrients.get("Fiber, total dietary", "?")
sug = nutrients.get("Sugars, total including NLEA", "?")
print(f" {food.get('description', 'N/A')}")
print(f" Calories : {cal} kcal")
print(f" Protein : {prot}g")
print(f" Fat : {fat}g")
print(f" Carbs : {carb}g (fiber: {fib}g, sugar: {sug}g)")
print(f" FDC ID : {food.get('fdcId', 'N/A')}")
def main():
if len(sys.argv) < 2:
print(__doc__)
sys.exit(1)
if sys.argv[1] == "-":
queries = [line.strip() for line in sys.stdin if line.strip()]
else:
queries = sys.argv[1:]
for query in queries:
print(f"\n--- {query.upper()} (per 100g) ---")
data = search(query, max_results=2)
if not data or not data.get("foods"):
print(" No results found.")
else:
for food in data["foods"]:
display(food)
print()
if len(queries) > 1:
time.sleep(1) # respect rate limits
if API_KEY == "DEMO_KEY":
print("\nTip: using DEMO_KEY (30 req/hr). Set USDA_API_KEY for 1000 req/hr.")
print("Free signup: https://fdc.nal.usda.gov/api-key-signup/")
if __name__ == "__main__":
main()

View file

@ -245,6 +245,9 @@ def _get_required_environment_variables(
if isinstance(required_for, str) and required_for.strip(): if isinstance(required_for, str) and required_for.strip():
normalized["required_for"] = required_for.strip() normalized["required_for"] = required_for.strip()
if entry.get("optional"):
normalized["optional"] = True
seen.add(env_name) seen.add(env_name)
required.append(normalized) required.append(normalized)
@ -378,6 +381,8 @@ def _remaining_required_environment_names(
remaining = [] remaining = []
for entry in required_env_vars: for entry in required_env_vars:
name = entry["name"] name = entry["name"]
if entry.get("optional"):
continue
if name in missing_names or not _is_env_var_persisted(name, env_snapshot): if name in missing_names or not _is_env_var_persisted(name, env_snapshot):
remaining.append(name) remaining.append(name)
return remaining return remaining
@ -1042,7 +1047,8 @@ def skill_view(name: str, file_path: str = None, task_id: str = None) -> str:
missing_required_env_vars = [ missing_required_env_vars = [
e e
for e in required_env_vars for e in required_env_vars
if not _is_env_var_persisted(e["name"], env_snapshot) if not e.get("optional")
and not _is_env_var_persisted(e["name"], env_snapshot)
] ]
capture_result = _capture_required_environment_variables( capture_result = _capture_required_environment_variables(
skill_name, skill_name,