# Meal Add

> Use when adding a new recipe to the Meal OS system. Triggered by "/meal-add" or "add a recipe".

- Skill: `scurry/meal-add` (Agent Skill)
- Install (CLI): `npx skillmds@latest add scurry/meal-add`
- Raw SKILL.md: https://api.skillmd.com/api/skills/scurry/meal-add/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: scurry (https://skillmd.com/u/scurry)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/scurry/meal-add

---


# /meal-add — Recipe Intake

> **Naming note:** Skills in this family use `meal-*` (noun-first) rather than the verb-first convention. This is intentional: the `meal-` prefix groups all Meal OS slash commands together in discovery and matches how users type them (`/meal-add`, `/meal-flyer`, etc.).

Add a recipe to the Meal OS rotation by pasting recipe text from any website.

**Announce at start:** "Adding recipe to Meal OS..."

## Step 1: Get Recipe Text

The user should paste recipe text along with the `/meal-add` command. If no recipe text is provided, ask:

"Please paste the recipe text (copy from any recipe website). Include the URL if you have it."

## Step 2: Load References

Read these files in parallel:

### 2a. Household Config
Read `data/config.yaml` to get the user's diet, excluded ingredients, and thresholds.

**If the file does not exist:** Stop and tell the user:
"data/config.yaml is missing. Run /meal-setup first to initialize the project, then re-run /meal-add."

### 2b. Canonical Ingredients
Read `data/canonical-ingredients.yaml` from the project root. This is a **read-only** reference of core rotation ingredients — do NOT add new entries to this file. It defines the stable set of ingredients the family regularly uses.

**If the file does not exist:** Stop and tell the user:
"data/canonical-ingredients.yaml is missing. Run /meal-setup first to initialize the project, then re-run /meal-add."

## Step 3: Parse the Recipe

Extract from the pasted text:

- **Recipe name** — the title of the recipe
- **Source URL** — if present in the pasted text, extract it. If not, leave blank.
- **Servings** — number of servings (default to 4 if not stated)
- **Prep time, cook time, total time** — extract if present. Format as "X min" or "X hr Y min".
- **Ingredients list** — each ingredient with quantity, unit, and description
- **Instructions** — numbered steps, simplified and cleaned

**Minimal recipe handling:**
If the pasted text contains only an ingredient list with no instructions (or instructions that are a single vague sentence like "cook and serve"):
- Save what's there — do not discard the recipe
- Set `instructions: minimal` in the frontmatter
- Leave the `## Instructions` section with whatever was provided (even if just "See original source")
- Note in the Step 9 confirm output: "Instructions were minimal — fill in steps after you make it."

**Cleaning rules:**
- Strip HTML tags, ad copy, newsletter signup text, "Jump to Recipe" buttons
- Remove duplicate lines
- Normalize fractions (½ → 1/2)
- Remove "Notes:" sections that are editorial (keep cooking tips if useful)

## Step 4: Normalize Ingredients

For each ingredient in the recipe:

1. **Match against canonical-ingredients.yaml** — look for the closest canonical ID. Use the ingredient name, not the quantity/modifier. Examples:
   - "1 lb boneless skinless chicken thighs" → `chicken_thigh`
   - "2 tbsp extra-virgin olive oil" → `olive_oil`
   - "1 can (15 oz) chickpeas, drained" → `chickpea`
   - "3 cloves garlic, minced" → `garlic`
   - "1 cup baby spinach" → `spinach`

2. **Non-canonical ingredients** — if an ingredient is not in canonical-ingredients.yaml:
   - **Do NOT modify canonical-ingredients.yaml** — it is a read-only reference of core rotation ingredients.
   - Instead, list these as `extra_ingredients` in the recipe frontmatter. Use a human-readable name (not a snake_case ID). Include the quantity from the recipe.
   - These will flow through to shopping lists directly from the recipe's `## Ingredients` section.
   - Skip trivial items (water, salt, pepper, cooking spray) — don't list these as extras.

3. **Determine the primary protein** — set the `protein` frontmatter field to the main protein's canonical ID (e.g., `chicken_thigh`, `salmon`, `chickpea`). If the protein isn't in the canonical list, use the protein name in plain text. If no clear protein, set to `none`.

## Step 5: Validate

Run these checks and report findings:

### Excluded Ingredient Check
For each ingredient in `excluded_ingredients` from `data/config.yaml`:
- If any recipe ingredient matches (in any form — e.g., for "tuna": canned tuna, tuna steak, ahi tuna):
- **Stop and ask the user:** "This recipe contains [ingredient], which is excluded in your config. Add anyway, or discard?"
- If user says discard, stop. If user says add, continue with a `warnings: [contains-[ingredient]]` field in frontmatter.

### Diet Check
Read the `diet` field from `data/config.yaml`. If a diet is configured (not `none`), check whether the recipe contains ingredients that clearly conflict with that diet style. For example, for `mediterranean`: heavy cream, pork, bacon, butter as primary fat, non-olive cooking oils in large quantities.
- Add a `warnings: [non-[diet]]` field in frontmatter
- Note in output: "This recipe may not fit the [diet] diet — contains [ingredient]. Added with a warning tag."
- **Still save the recipe** — the user decides.

### Ingredient Count Check
Read `ingredient_thresholds.target` from `data/config.yaml`. Count the number of unique canonical ingredients. If the count exceeds the target:
- Warn: "This recipe has N unique ingredients (target is ≤[target]). It may be complex for the weekly rotation."
- **Still save the recipe.**

## Step 6: Preview and Confirm

Show the user a preview before writing anything:

```
Ready to save recipe:

  Name: [Recipe Name]
  Protein: [protein canonical ID]
  Canonical ingredients ([N]): [list of IDs]
  Extra ingredients ([N]): [list of non-canonical items needed for shopping]
  Tags: [tag list]
  Warnings: [any warnings or "none"]

Save? (yes / no / edit ingredient mappings)
```

- If **no**: stop, do not write any files.
- If **edit ingredient mappings**: ask the user to specify corrections ("change 'lemon_zest' to 'lemon'"), apply them, then re-show the preview.
- If **yes**: continue to Step 7.

## Step 7: Generate Slug and Check for Collisions

Generate a filename slug from the recipe name:
- Lowercase, kebab-case
- Remove special characters
- Example: "Lemon-Garlic Chicken with Green Beans" → `lemon-garlic-chicken-with-green-beans`

Check if `data/recipes/<slug>.md` already exists:
- If it exists, **warn the user**: "A recipe with this name already exists at data/recipes/<slug>.md. Overwrite (will lose any existing ratings), rename, or cancel?"
- **Never silently overwrite** — ratings data in the existing file could be lost.

## Step 8: Write the Recipe File

Ensure the directory exists: run `mkdir -p data/recipes` via Bash before writing.

Write to `data/recipes/<slug>.md` using this exact format:

```markdown
---
name: [Recipe Name]
source: [URL or blank]
servings: [number]
prep_time: [X min]
cook_time: [X min]
total_time: [X min]
protein: [canonical_id]
canonical_ingredients:
  - [canonical_id_1]
  - [canonical_id_2]
  - [canonical_id_3]
extra_ingredients:
  - [non-canonical item 1, e.g., "whole-wheat pasta"]
  - [non-canonical item 2, e.g., "Parmesan cheese"]
tags: [[diet from config], relevant-tags]
rating:
date_added: [YYYY-MM-DD]
---

## Ingredients

- [original quantity and ingredient text, one per line]

## Instructions

1. [Step 1]
2. [Step 2]
3. [Step 3]

## Ratings

- **Score:** /5
- **Would make again:**
- **Actual cook time:**
- **Estimated cost:**
- **Notes:**
```

### Frontmatter field rules:
- `canonical_ingredients` — list only IDs that exist in canonical-ingredients.yaml, sorted alphabetically
- `extra_ingredients` — list non-canonical ingredients by human-readable name (these flow to the shopping list from the Ingredients section). Omit trivial pantry basics (water, salt, pepper, cooking spray). If empty, omit the field entirely.
- `tags` — always include the `diet` value from `data/config.yaml` (e.g., `mediterranean`). Add relevant tags: `one-pan`, `sheet-pan`, `weeknight`, `salad`, `soup`, `bowl`, `batch-prep`, `quick` (≤30 min total), `slow` (>60 min total)
- `rating` — leave blank (user fills in manually after cooking)
- `date_added` — today's date in YYYY-MM-DD format

## Step 9: Confirm

Output a summary:

```
Recipe saved: data/recipes/<slug>.md

  Name: [Recipe Name]
  Protein: [protein display name]
  Canonical ingredients: [N] ([list of IDs])
  Extra ingredients: [N] ([list of non-canonical items, or "none"])
  Tags: [tag list]
  Warnings: [any warnings, or "none"]

Review the file to verify ingredient mappings are correct.
```

