- SKILL.md: add egg-limit hard rule (max 4/meal, one egg-meal/day), cuisine-rotation section - scripts/pick_cuisine.py: seeded weekly world-cuisine picker (stable per week, no recent repeats) - scripts/verify_eggs.py: regression guard for the egg cap - README.md: full project documentation - .gitignore: exclude cuisine-rotation.json + python caches (live data stays local)
20 KiB
name, description, version, author, license
| name | description | version | author | license |
|---|---|---|---|---|
| meal-suggestion | Kitchen inventory + high-protein meal suggestions. Tracks ingredients, suggests 2 daily meals from stock, and a weekly £30 shopping list. Emails jp@txt3.com from hpm6@txt3.com. For a recovering burns victim on a high-protein diet avoiding red meat. | 1.0.0 | agent | MIT |
Meal Suggestion
Context
User jp is a recovering burns victim who must eat a high-protein diet and wants to avoid red meat. This skill keeps a running kitchen inventory and produces:
-
2 daily meal suggestions (from what's in stock) — emailed every day at 09:00.
-
A weekly shopping list on a £30 budget — emailed Mondays.
-
Sender:
hpm6@txt3.com(the agent's Gmail, via smtp.gmail.com:587). -
Recipient:
jp@txt3.com.
Inventory
File: /home/jp/.hermes/skills/meal-suggestion/inventory.json
Schema:
{
"updated": "YYYY-MM-DD",
"diet": { "goal": "high-protein", "avoid": ["red meat"] },
"items": [
{ "name": "Eggs", "qty": 12, "unit": "count", "category": "protein", "notes": "" }
]
}
categoryis one of:protein,veg,fruit,dairy,grain,tinned,frozen,condiment,other.- Keep
qtyas a number with aunit(count,g,ml,pack,can,tbsp, etc.).
Updating the inventory (you own this)
- When the user says they used/consumed ingredients, decrement or remove the
matching item(s) with the
patchtool, then bumpupdatedto today. - When the user bought items, add or increase them.
- Do NOT auto-consume on a daily suggestion — the user tells you what was actually eaten. The daily email only proposes meals.
Meal suggestion rules
- Suggest two meals per day (e.g. lunch + dinner) using only items currently in the inventory.
- Maximize protein per meal; lead with a protein source.
- EGG LIMIT (hard rule): Eggs are capped at 4 per meal and only ONE of the two daily meals may contain eggs — the other meal MUST be egg-free. Never suggest more than 4 eggs in a single meal, and never put eggs in both meals. If a meal needs more protein than 4 eggs supply, close the gap with other proteins (lentils, beans, halloumi, cheese, tinned fish, whey) or a whey shake — do NOT add a 5th+ egg. This overrides the "lead with eggs" preference below.
- Avoid red meat for PURCHASES (beef, lamb, pork, venison, bacon, gammon) — the weekly shopping list must NEVER suggest red meat to buy.
- If red meat is already in the inventory, it stays usable in daily meal suggestions so it isn't wasted. Deprioritize it behind the high-protein non-red-meat options, but do NOT exclude it just because it's red meat.
- Preferred proteins (lead with these): eggs, chicken, turkey, fish, prawns, tofu, tempeh, lentils, beans, chickpeas, Greek yogurt, cottage cheese, quark, skim milk, whey, tinned tuna/salmon, edamame, halloumi. (Eggs are subject to the EGG LIMIT hard rule above: max 4 per meal, only one meal/day may include them.)
- Protein target: ~190–200 g/day (user ~95–100 kg × 2 g/kg — burns recovery + preserving lean mass while losing ~15 kg fat). Aim each meal at ~90–100 g protein and STATE the per-meal and daily protein totals in the email. If 2 meals can't reach ~190 g with current stock, say so and suggest a third hit (whey shake, extra eggs, Greek yogurt) to close the gap.
- Weight-loss framing: keep meals calorie-moderate — bulk with veg, go easy on oven chips, white bread, and oil; lead with lean protein. Fat loss comes from a mild calorie deficit, NOT from cutting protein.
- Note: this is general guidance, not medical advice — defer to the user's clinical/dietitian team and keep hydration up on a high-protein intake.
- Each meal block: name, the ingredients + approx amounts used, a short 2–3 step method, and the protein total (g).
- Make meals EASY & TASTY, not just high-protein. When generating each day's
suggestions, do a quick web search (1–2 queries) for easy, tasty, high-protein,
budget meals using the proteins you have in stock (e.g. "easy tasty high protein
meal eggs chicken tuna lentils no cook"), and use real recipes as inspiration so the
two meals are genuinely appetising and varied day to day — not the same plate repeated.
Prefer no-cook / one-pan / 10–15 min methods. Draw on
references/food-health.md(tuna-lentil salad, tofu stir-fry, veggie chilli, Mexican chicken stew, chickpea curry jacket potato, egg & spinach scramble, lentil soup, mackerel bowl, etc.). - Healing + health micronutrients: fold in wound-healing nutrients (Vitamin C,
Zinc, Vitamin A, Copper, Iron+VitC pairing, Vitamin K) and 46-year-old health picks
(Vitamin D, B12, Magnesium, Omega-3) where the stock allows — see
references/food-health.md. Also favour foods that support mental acuity (leafy greens, eggs/choline, berries, walnuts, olive oil, whole grains, hydration) and visual acuity (lutein/zeaxanthin from greens + egg yolk, omega-3 DHA, Vit C/E/zinc) — these overlap heavily with the above, so a few smart picks (oily fish, eggs with yolk, greens, nuts/seeds) cover all three goals at once. Don't overcomplicate; note them as gentle "health boost" lines, not medical advice. - STRICT inventory rule: both daily meals MUST be built ONLY from items the user
ACTUALLY HAS in
inventory.jsonat the time. Never invent, assume, or add an ingredient that isn't listed there. Recipe/web inspiration is fine, but only use in-stock items — substitute with an in-stock item or drop the idea. Before sending, cross-check every ingredient against inventory.json and remove anything not present. If stock is too sparse for a real high-protein meal, say so and list the top 2–3 things to buy — do NOT fabricate meals from items you don't have.
Email format
Send via the helper (it reads SMTP creds from ~/.hermes/.env):
python3 /home/jp/.hermes/scripts/meal/send_meal_email.py \
--subject "Your Meals for Today — <date>" \
--html /tmp/meal_today.html \
--to jp@txt3.com
HTML must be self-contained (inline CSS), mobile-friendly, dark-on-light, with a
header, the two meal cards, and a footer note
("Reply to tell me what you used and I'll update the inventory").
Use the scaffold in references/email-template.md.
Weekly cuisine rotation
To keep shops and meals varied over time, each week's list is themed around a
randomly chosen world cuisine. This is driven by
scripts/pick_cuisine.py (same skill dir).
- Run
python3 scripts/pick_cuisine.pyat the start of the weekly job. It picks a cuisine seeded by the week's Monday, so it is stable within a week (re-runs/retries never flip the cuisine mid-week) but varies week to week and avoids the last 4 cuisines (no back-to-back repeats). It prints JSON withcuisineandtheme_items, and persists the choice tocuisine-rotation.json. - The chosen cuisine must theme the entire weekly basket: lead the protein,
veg, and 1–2 condiment/flavour buys with that cuisine's
theme_items, so the shop introduces varied ingredients. Cuisines are all Lidl-achievable on ~£30, high-protein, and red-meat-free by design. - Do NOT abandon the core rules for the theme: still hit the protein target, still cover healing/brain/eyes/46yo micronutrients where possible, still default to Lidl, still never buy red meat, still keep the £30 budget. Theme items that are just flavour (spices, sauces, lime, herbs) are the optional "interest" buys — keep them low-price and behind protein + veg + staples.
- Persist the active cuisine so the daily meal job can read
cuisine-rotation.jsonand on-theme meals from the same week's buys (e.g. build a Korean bowl, a Mexican bowl, etc., from stock). The daily job should still respect the STRICT inventory rule — only cook with what's actually in inventory.json. - The rotation file keeps a short history; no manual tracking needed.
Weekly shopping list (Mondays)
- Budget £30 total. Use realistic UK supermarket prices (Tesco/Asda/Sainsbury's).
- Pick this week's cuisine first (see "Weekly cuisine rotation" above) and theme the basket around it — varied ingredients keep meals interesting.
- Prioritize protein, then veg/fruit, then staples. Show item, est. price, and a running total that lands in range.
- APPLY the research (
references/food-health.md): build the basket so it covers all health goals at once — favour overlap picks: oily fish (mackerel/salmon/tuna → omega-3 DHA for healing + brain + eyes), eggs with yolk (choline + lutein), leafy greens/broccoli/peas (Vit C/K/lutein), nuts/seeds (Mg/Zn/Cu + ALA), berries (flavonoids), sweet potato/carrot (Vit A), Greek yogurt/cottage cheese/lentils/tofu (protein). Add a short "health coverage" line in the email showing how the basket hits healing + brain + eyes + 46yo needs. - Email subject:
Weekly Shopping List — <date>. - This does not modify the inventory.
- Micronutrients & vitamins (user is 46): you MAY include 1–2 low-price health-support items on the weekly shop when budget allows under £30, to support overall health alongside the high-protein diet. Sensible, evidence-aware picks for a 46-year-old on high protein with limited red meat: vitamin D (low sun exposure risk), omega-3 (fish-oil or flax/chia), magnesium (recovery/sleep), and a B12 source if relying on eggs/dairy over meat. Prefer food-first (oily fish, leafy greens, seeds); only add a supplement line (e.g. "Vitamin D3 1000–2000 IU", "Omega-3 caps") when budget permits and label it clearly as a general-health suggestion, NOT medical advice. Keep each low-cost and behind protein + veg + staples in priority.
- Condiments & flavourings: you MAY include a small item or two from the spice/sauce/condiment aisle (e.g. fresh herbs, lemon, soy, spices, stock cubes, passata, a hot sauce) so meals are more interesting — keep each low-price and only if budget allows under £30. These are optional "interest" buys; protein + veg + staples come first.
Where to shop (Eastbourne) — single-shop, per-store totals
User is in Eastbourne. Local options: Lidl, Sainsbury's, Co-op, Tesco Express, and a local Londis (good deal on milk: 2L £1.40). Memberships: Tesco Clubcard, Co-op member.
User preference (confirmed): Lidl is the PREFERRED shop. It's a weekly trip so the extra distance is acceptable; Lidl gives the best value AND leaves budget for essential micronutrients and the occasional treat. The other stores (Tesco Express, Sainsbury's, Co-op, Londis) are shown ONLY for comparison, and as the fallback for days he doesn't want to make the longer journey.
Grounded in Which? 2026 monthly price index (93-item basket; Lidl £160.70 baseline):
- Lidl ≈ 1.00x — CHEAPEST, preferred (whey, eggs, chicken, lentils, frozen veg, milk, tuna, bananas, plus fruit/veg for micronutrients + a treat)
- Tesco superstore w/ Clubcard ≈ 1.17x; Tesco Express adds convenience premium → ~1.22x WITH Clubcard
- Sainsbury's w/ Nectar ≈ 1.17x
- Co-op ≈ 1.30x nominal — membership = annual dividend, not instant discount
- Londis ≈ 1.25–1.35x indicative; milk cheap but full basket pricier → top-up only
Weekly email MUST:
- Build ONE single-shop basket available at Lidl (the default recommended shop).
- Show a "Same basket — where else?" comparison table (Lidl / Tesco Express Clubcard / Sainsbury's Nectar / Co-op member / Londis) with estimated totals + delta vs Lidl. Label as ESTIMATES from Which? 2026 ratios + known local prices (Londis milk £1.40), NOT live scans. Do NOT run a live web search.
- Recommend Lidl as the primary shop — note it's a weekly trip so distance is fine and it best fits the budget incl. micronutrients/treats. List the others as fallback only ("if you'd rather not make the trip: Tesco Express is closest").
- Single-shop the list — don't split across stores UNLESS a specific item's local price is definitely known to be lower elsewhere (e.g. Londis milk 2L at £1.40 is a confirmed cheaper line). Estimated basket ratios alone are NOT grounds to split.
- You MAY suggest ordering certain items via Amazon Prime when it's clearly better value (e.g. whey protein 1 kg is often cheaper per gram on Prime than in-store). If used, show the Prime item separately and note it's a delivery, not a store trip.
- Robustness: the comparison table must be clearly visible. ALSO attach a plain-text alternative (send_meal_email.py --text) so the comparison survives even if the mail client strips HTML tables.
Split-store & Amazon Prime rules
- Default: single shop at Lidl. Do NOT split the basket across stores based only on the estimated Which? 2026 ratios — those are approximations. Splitting is allowed ONLY when a specific item's local price is definitely known to be lower elsewhere (e.g. Londis milk 2L at £1.40 is a confirmed cheaper line, so milk could be a Londis top-up). State the known price when you do this.
- Amazon Prime: you MAY suggest ordering specific items via Amazon Prime, but ONLY when it is genuinely better value or more convenient — do not assume Prime is automatically cheaper. Reality check (2026): Lidl whey is ~£9.99/500g (≈£20/kg); Prime branded whey (Bulk/Myprotein) is typically £18–£27/kg, so it's roughly comparable and Lidl often wins on a single trip. Suggest Prime for whey ONLY when (a) a confirmed deal drops it below Lidl's per-kg price, or (b) the user prefers delivery over the trip. When suggesting, show both prices side by side and state which is cheaper. Show any Prime item as a separate "delivered via Prime" line, distinct from the in-store Lidl list.
- When neither applies, keep everything in the one Lidl list.
7-day meal history (for inventory removal)
A rolling log lives at meal-history.json (same skill dir). The daily job UPSERTS
its two meal entries each run (replaces any same date+meal, so re-runs don't
duplicate) and prunes entries older than 7 days. Each entry:
{ date, weekday, meal: "lunch"|"dinner", name, items: [{name, qty, unit}] }.
Only substantive food items are logged — pure condiments (oil, salt, pepper, spices,
garlic, soy, etc.) and the optional "third hit" snack are NOT logged. Units match
inventory.json where possible.
On-demand removal (user says e.g. "remove Wednesday's dinner from inventory"):
- Resolve the weekday/date to the matching entry (weekday is relative to today; if
ambiguous, ask). 2) For each
itemin that entry, find the matching inventory entry by name (case-insensitive). 3) Subtractqty; if the result is ~0 or negative, remove the inventory entry; else update it. 4) Skip any item NOT present in inventory (e.g. a suggested-but-never-bought item) and say so. 5) Bumpupdatedto today. - Confirm what was removed. The user can also say "remove all of " to drop both meals, or name a specific item to override.
Item-level overrides & partial consumption (apply BEFORE step 3 above):
- If the user corrects a quantity for one item (e.g. "only used 100g halloumi, not 200g"), use their stated qty instead of the logged qty for that item.
- If the user says they only used PART of an item and the rest remains (e.g. "still
got half a lettuce", "there's half a head of broccoli left"), do NOT remove the whole
inventory entry — keep/restore the remaining portion (re-add at e.g. 0.5 count, or
leave prior-qty minus what was used). Never zero out an item the user says they still
have. Log the restored amount back into
inventory.jsonand re-sort.
Reconcile other meal-history entries AFTER removal (prevents stale future plans):
The daily job only writes TODAY's two meals at 09:00 — it never pre-plans future days.
But meal-history.json may contain future-dated entries (pre-seeded, or written by a
re-run). After any removal that deletes an inventory item (hits ~0), scan EVERY entry
in meal-history.json — including future-dated ones inside the 7-day window — for
items that reference the now-absent inventory name. For each such entry:
- Flag it to the user: " ('') still lists but it's no longer in inventory."
- Offer to rewrite that entry using ONLY in-stock items (substitute the missing item
for an in-stock alternative, or drop it). Do NOT silently leave a plan that can't be
cooked.
This is the only guard against "Thursday's plan used chicken, but I ate the chicken
today" — the design is correct; the stale future row is the anomaly, so fix the row,
not the design. See
references/reconcile-history.mdfor the scan snippet.
On-demand (chat)
If the user lists ingredients in chat, add them to the inventory and optionally give an immediate 2-meal suggestion. If they ask "what should I eat today?", read the inventory and suggest now (and offer to email it).
Pitfalls (learned the hard way)
- "Regenerate / resend today's meals" means GENERATE FRESH, not replay. When the
user asks to regenerate a day's meals or re-send the email, do NOT pull the two meals
back out of
meal-history.jsonand re-format them. Re-readinventory.json, build two new meals from current stock (honouring the egg limit, STRICT inventory rule, etc.), then update today'smeal-history.jsonentries to match what you actually send. The history is a log; it is not the source of truth for a regeneration request. - Editing a cron job's prompt: edit jobs.json AND push via the
cronjob updateaction. The scheduler may hold jobs in memory, so a bare file edit is not enough — the running daemon won't see it. After writing the new prompt string, callcronjob update job_id=<id> prompt="..."so the in-memory job reloads. Verify withcronjob listthat the preview reflects the change. (Pushing to a non-running daemon is harmless; not pushing is the silent-failure trap.) - The egg rule is a HARD cap, not a preference. The daily job leaned on eggs as the
"preferred protein" and produced 8 eggs across two meals (6 + 4). Cured by (a) the
EGG LIMIT prose rule above, (b)
diet.egg_rulesin inventory.json, and (c) runningscripts/verify_eggs.pyafter any history/inventory change or before a resend. Always run that probe before sending a regenerated day.
Automation
Two cron jobs (created via the cronjob tool), both loading this skill:
- Daily 09:00 —
0 9 * * *— 2 meal suggestions. - Monday 08:00 —
0 8 * * 1— weekly £30 shopping list.
Delivery is local because the email itself is the deliverable; check
cronjob action=list / logs if a send ever seems missing.
Scripts (in this skill dir)
scripts/pick_cuisine.py— picks the week's random world cuisine (stable per week, varies weekly, no recent repeats); persists tocuisine-rotation.json. Used by the Monday weekly job (see "Weekly cuisine rotation").scripts/verify_eggs.py— regression probe for the EGG LIMIT. Run after any change tomeal-history.json,inventory.json, or the daily job, and before sending a regenerated day's email. Exits non-zero on violation. See "Pitfalls".
References
references/protein-sources.md— allowed proteins + example pairings.references/email-template.md— HTML email scaffold.references/eastbourne-shops.md— shop price table.references/meal-history.md— 7-day logger spec.references/protein-targets.md— protein math.references/food-health.md— research-grounded food & health knowledge base (healing nutrition, wound-healing micronutrients, men's health at 46, easy & tasty budget meals, flavour-without-cost). Read this when generating meals.