From food-planner
Use when user runs /weekplan or asks to "plan the week", "make the meal plan", "do the weekly grocery plan". Generates a 7-day Cookidoo meal plan + per-store shopping list using current Marktguru offers + the user's diet profile from ~/.weekplan/profile.json. Geocodes the user's address to find nearby supermarkets, balances offer-savings vs travel-distance, and writes the plan directly to Cookidoo's "Meine Woche" + shopping list. Outputs to ~/.weekplan/plans/<YYYY-MM-DD>/. Supports unattended/auto mode for scheduled runs.
How this skill is triggered — by the user, by Claude, or both
Slash command
/food-planner:weekplanThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
End-to-end pipeline: load profile → geocode → current supermarket offers → Cookidoo recipe selection → diet-constrained 7-day plan → per-store shopping list grouped by aisle + walking-distance route. Writes recipes directly to Cookidoo "Meine Woche" + shopping list via cookidoo-mcp write tools. Persists every weekly plan to disk under `~/.weekplan/plans/<YYYY-MM-DD>/`.
End-to-end pipeline: load profile → geocode → current supermarket offers → Cookidoo recipe selection → diet-constrained 7-day plan → per-store shopping list grouped by aisle + walking-distance route. Writes recipes directly to Cookidoo "Meine Woche" + shopping list via cookidoo-mcp write tools. Persists every weekly plan to disk under ~/.weekplan/plans/<YYYY-MM-DD>/.
--auto OR env WEEKPLAN_AUTO=1. Substring matches on prompt text MUST NOT activate auto mode (false-positive risk would destroy an existing curated plan without confirmation). In auto mode: NEVER call AskUserQuestion. Use defaults from profile.json. REPLACE existing plan via snapshot-then-replace (see "Snapshot before destructive ops" below). NEVER call clear_week — it is not part of the replace flow under any mode. Auto mode must complete with no user-facing prompts.This block applies to REPLACE mode ONLY (auto, or interactive replace). EXTEND mode never enters it — it has no snapshot and no removal step, so its writes are purely additive and a partial add_to_week failure is non-fatal (record in Notes, continue).
In REPLACE mode, before any bulk remove_from_week, ALWAYS:
mcp__cookidoo__get_week_plan and serialize the full result to ~/.weekplan/snapshots/<YYYY-MM-DD>-pre-replace.json with mtime, mode (auto|interactive), and the planned new entries.add_to_week for the NEW recipes first.add_to_week writes succeed: compute the set difference on the composite key {recipeId, dayKey} — to_remove = snapshot_entries \ new_entries, where each entry is the pair (recipeId, dayKey). Compare recipeIds as the exact normalized string the tool returns (the rNNN form). Perform remove_from_week({recipeId, dayKey}) for each pair in to_remove. A recipe that stays on the SAME day in both old and new plan is retained (never removed). A recipe that MOVES to a different day appears as an old (recipeId, oldDay) pair in to_remove (removed) plus a new (recipeId, newDay) pair added in step 2 — so the stale day is correctly cleared. Recipes the user added manually outside the snapshot are left untouched.This replaces the previous "clear_week first" pattern, which had no rollback path. clear_week is never used for replacement.
~/.weekplan/profile.jsonSingle source of truth. Schema: profile.schema.json in this skill folder. Example: profile.example.json.
Key fields:
household.size, household.portionsPerMeallocation.address (preferred) or location.zipCode; location.shoppingRadiusKmlocation.nominatimContact — REQUIRED. Your email or URL. Sent as User-Agent contact to Nominatim/Overpass (their usage policy). Without it, the supermarkets-mcp server refuses geocoding requests.diet.type (omnivore/pescetarian/vegetarian/vegan), diet.lowHistamine, diet.preferVeganDairyAlternativesdiet.forbidden[] — case-insensitive substring matches in ingredient textdiet.flaggedButAllowed[] — surface in Notes section but do not droppreferences.cadenceDefault — dinner-only or lunch-and-dinnerpreferences.preferredStores[] — Marktguru retailer slugspreferences.maxRecipesPerWeek, preferences.maxSameProteinPerWeekRead every run via Read tool on ~/.weekplan/profile.json. If missing or invalid JSON, abort with copy-paste instructions to create from profile.example.json.
Interactive: confirm preferences.cadenceDefault is right for this week ("Diese Woche: nur Abendessen, oder Mittag + Abend?"). Default = profile setting. Auto: use profile setting silently.
Call mcp__cookidoo__get_week_plan first. Defaults to Monday of current local week. Interactive: if non-empty, ask whether to extend or replace (replace = snapshot-then-set-diff, see step 8 / "Snapshot before destructive ops"). Auto: replace silently via the same snapshot-then-set-diff flow. NEVER call clear_week as the replace mechanism.
~/.weekplan/plans/<YYYY-MM-DD>/<YYYY-MM-DD> = Monday of the target week (ISO date). Files written each run:
| File | Contents |
|---|---|
plan.md | Markdown plan: header + daily table + Cookidoo confirmation |
shopping-list.md | Per-store shopping list, aisle-grouped, with walking-route hint |
offers.json | Snapshot of the Marktguru offers used (frozen, for later audit) |
recipes.json | Snapshot of recipe IDs + titles + chosen day keys |
Write order: build full content in memory → write all 4 files atomically near the end of the pipeline. Auto mode also prints plan.md to chat.
~/.weekplan/profile.json. Abort cleanly if missing.location.address set: call mcp__supermarkets-mcp__geocode_address(address) to get lat/lon + zip. Use that zip for offer queries.location.zipCode directly.mcp__supermarkets-mcp__find_stores_nearby(address, radiusKm=location.shoppingRadiusKm). Cache result for use in step 8.dinner-only: 7 dinners. Lunch = previous-night leftovers (low-histamine: leftovers ≤24h, eaten next day cold or warmed once).lunch-and-dinner: 14 meals; design overlap explicitly (e.g., big-batch curry → 4 meals across 2 days).Call mcp__supermarkets-mcp__get_weekly_offers with address (or zipCode) from profile, stores=preferences.preferredStores. Default basket OR override based on user signals (e.g. "lust auf Asia" → asian terms).
Drop offers whose description or title contain any string from profile.diet.forbidden[] (case-insensitive).
Special rules when diet.lowHistamine == true:
forbidden[] already cover the histamine triggers — no extra logic.forbidden[] is empty but lowHistamine == true, fall back to the built-in low-histamine list (see profile.example.json for the default set).Vegan dairy alternatives PREFERRED when preferVeganDairyAlternatives == true: prioritize oat/almond/coconut milk; if no vegan alt in offers, lactose-free dairy acceptable — surface in Notes.
For top ~15 offer ingredients (post-filter), call mcp__cookidoo__search_recipes per ingredient. Aggregate ~50-80 candidates.
If profile is missing or returns no usable offers, fall back to mcp__cookidoo__random_recipe (seeded with category from profile or default basket) to fill gaps — flag in Notes.
For each candidate, call mcp__cookidoo__get_recipe for full ingredient list. Parallelize via Promise.all in a single tool batch.
Drop recipes whose ingredients contain any diet.forbidden[] string. Flag (don't drop) items in diet.flaggedButAllowed[].
Build ingredient frequency map. Greedy assembly: pick highest-rated recipe whose ingredient set maximizes overlap with already-chosen meals AND uses ≥1 offer ingredient. Diversity guard: at most preferences.maxSameProteinPerWeek of the same primary protein in 7 days.
For each chosen recipe + dayKey (YYYY-MM-DD, Mon-Sun of target week):
mcp__cookidoo__add_to_week({recipeIds: [id], dayKey})mcp__cookidoo__add_to_shopping_list({recipeIds: [id]}) (covers all ingredients)Write order (snapshot-then-replace, clear_week is NEVER used):
mcp__cookidoo__get_week_plan({startDate, span: 7}) and write the result + the planned new entries to ~/.weekplan/snapshots/<YYYY-MM-DD>-pre-replace.json BEFORE any write.add_to_week ALL new recipes first.add_to_week writes succeed: compute to_remove = snapshot_entries \ new_entries on the composite key {recipeId, dayKey} (recipeId compared as the exact normalized rNNN string), and remove_from_week({recipeId, dayKey}) only those pairs. A recipe on the same day in both plans is retained; a day-moved recipe has its old (recipeId, oldDay) pair removed here and its new (recipeId, newDay) pair added in step 2; manually-added recipes outside the snapshot are left untouched.add_to_week new recipes alongside existing.add_to_shopping_list for the new chosen recipes (regardless of mode).REPLACE mode only — if any add_to_week fails: STOP. Do not call remove_from_week. Report failure with snapshot path so the user can resume manually. The original plan must remain intact when our writes haven't fully succeeded. (EXTEND mode has no removal step, so a partial add_to_week failure is safe — see Failure modes.)
For each ingredient across the 7 plans:
find_stores_nearby result → distance.preferences.preferredStores from the nearby list.household.portionsPerMeal.Output a walking route hint at the top of shopping-list.md: stores ordered by distance from origin.
Markdown plan structure (plan.md):
add_to_week write failures, consolidation decisionsshopping-list.md:
Auto mode: also print plan.md to chat. Final line: WEEKPLAN_AUTO_DONE <output-file-path> for scheduler detection.
mcp__supermarkets-mcp__list_stores({zipCode?, address?}) — discover retailersmcp__supermarkets-mcp__search_offers({query, zipCode?, address?, stores?, limit?}) — keyword searchmcp__supermarkets-mcp__get_weekly_offers({zipCode?, address?, stores?, terms?, perTermLimit?}) — fan-out basket searchmcp__supermarkets-mcp__geocode_address({address}) — address → lat/lon/zipmcp__supermarkets-mcp__find_stores_nearby({address, radiusKm?}) — OSM supermarkets near origin w/ distance + retailer slugRead:
mcp__cookidoo__search_recipes({query, hitsPerPage?}) — Algolia search (empty query rejected; use random_recipe)mcp__cookidoo__random_recipe({category?}) — random recipe (optionally seeded)mcp__cookidoo__get_recipe({id}) — full detail (ingredients, instructions)mcp__cookidoo__get_week_plan({startDate?, span?}) — read existing planWrite (cookidoo-mcp, ToS-risk override accepted 2026-05-06):
mcp__cookidoo__add_to_week({recipeIds, dayKey}) — add 1+ recipes to a daymcp__cookidoo__remove_from_week({recipeId, dayKey})mcp__cookidoo__clear_week({startDate?, span?}) — wipe plan span (composite, iterates removes)mcp__cookidoo__add_to_shopping_list({recipeIds}) — add ingredientsmcp__cookidoo__mark_owned({ingredientIds}) — mark pantry-ownedmcp__cookidoo__unmark_owned({ingredientId})mcp__cookidoo__bookmark_recipe({recipeId}) — Merkenmcp__cookidoo__unbookmark_recipe({recipeId})mcp__cookidoo__rate_recipe({recipeId, rating}) — 1-5 stars--auto or WEEKPLAN_AUTO=1 only; no substring sniffing)~/.weekplan/snapshots/<YYYY-MM-DD>-pre-replace.json (clear_week is never called)add_to_week per chosen recipe + dayKey — in REPLACE mode, STOP entire pipeline if any write fails (do not run step 14); in EXTEND mode, a failed write is non-fatal (no removal follows) — record it in Notes and continueadd_to_week writes succeed AND mode is replace: remove_from_week({recipeId, dayKey}) each pair in to_remove (= snapshot {recipeId,dayKey} pairs minus new-plan pairs; same-day retained recipes stay, day-moved recipes get their old day cleared)add_to_shopping_list for all chosen recipes~/.weekplan/plans/<YYYY-MM-DD>/profile.example.json → ~/.weekplan/profile.json and edit. Exit cleanly.location.zipCode if set. Skip nearby-stores step; cost-vs-distance degrades to cost-only. Note in output.add_to_week fails in EXTEND mode (no removal step) → continue pipeline, list failed (recipe, dayKey, error) in Notes section so user can manually add via URL fallback. Safe because nothing gets removed.add_to_week fails in REPLACE mode → STOP. Do NOT run remove_from_week. Surface the snapshot path so the user can resume manually. The partial new writes simply append to the existing plan (nothing destroyed). Note it in output.Interactive mode: provide 3 numbered next steps:
~/.weekplan/plans/<YYYY-MM-DD>/shopping-list.md on phonemcp__cookidoo__rate_recipe so memory tracks preferencesAuto mode: skip next-steps section. Final line of output: WEEKPLAN_AUTO_DONE <output-file-path> for scheduler detection.
claude -p "/weekplan --auto" --dangerously-skip-permissions
WEEKPLAN_AUTO=1 claude -p "/weekplan"
Cron (Sunday 18:00 local):
0 18 * * 0 cd /Users/marten/Code/ade_anima/food-planner && /usr/local/bin/claude -p "/weekplan --auto" --dangerously-skip-permissions >> ~/.weekplan/cron.log 2>&1
Cookies must be present at ~/.cookidoo-mcp/cookies.txt. Refresh manually via playwright-cli login + import-state when expired.
npx claudepluginhub adeanima/food-plannerCreates bite-sized, testable implementation plans from specs or requirements, with file structure and task decomposition. Activates before coding multi-step tasks.