skills/cooking/SKILL.md
Find recipes online (NYT Cooking, web) and order ingredients via Instacart. Use when asked about recipes, meal planning, grocery shopping, or ordering ingredients.
npx skillsauth add svenflow/dispatch cookingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Find recipes and order ingredients through Instacart.
NYT Cooking requires a subscription. Use Chrome profile 1 (owner's account — see config.local.yaml) which has access.
# Open NYT Cooking
~/.claude/skills/chrome-control/scripts/chrome -p 1 open "https://cooking.nytimes.com"
# Search for a recipe
~/.claude/skills/chrome-control/scripts/chrome -p 1 navigate <tab_id> "https://cooking.nytimes.com/search?q=<search_term>"
Workflow:
Tips:
chrome read <tab_id> to find clickable elementschrome text <tab_id> to extract page contentDO NOT add these items to cart automatically - most kitchens already have them.
| Item | Variations | |------|------------| | Salt | Table salt, kosher salt, sea salt | | Black pepper | Ground black pepper, peppercorns | | Sugar | White/granulated sugar | | Flour | All-purpose flour |
| Item | Notes | |------|-------| | Vegetable/canola oil | Basic cooking oils | | Olive oil | Basic olive oil (not specialty/finishing oils) | | Butter | Unsalted or salted | | Common spices | Garam masala, turmeric, cumin, paprika, cayenne, chili powder, cinnamon, oregano, basil, thyme |
| Item | Why ask | |------|---------| | Fresh aromatics | Onion, garlic, ginger, shallots - often in kitchen but may be out | | Lemons/limes | Common but perishable | | Eggs | Staple but people run out | | Milk/cream | May or may not have |
IMPORTANT: Always clearly communicate what you skipped!
Workflow:
Example message (ALWAYS include this):
Added 8 items to cart at Roche Bros ($52.30):
✓ Ground beef, American cheese, hamburger buns...
SKIPPED (assuming you have):
• Kosher salt
• Black pepper
• Sugar
• Olive oil
Need any of these? Just ask and I'll add them.
Instacart account: owner's email (use Chrome profile 1, see config.local.yaml)
Login Flow:
# Navigate to login
~/.claude/skills/chrome-control/scripts/chrome -p 1 navigate <tab_id> "https://www.instacart.com/login"
# Find and enter email (use owner.email from config.local.yaml)
~/.claude/skills/chrome-control/scripts/chrome -p 1 read <tab_id> | grep -i "textbox"
~/.claude/skills/chrome-control/scripts/chrome -p 1 type <tab_id> <ref> "<owner_email>"
# Click continue and check Gmail for code
~/.claude/skills/chrome-control/scripts/chrome -p 1 focus <gmail_tab_id>
~/.claude/skills/chrome-control/scripts/chrome -p 1 read <gmail_tab_id> | grep -i "instacart.*code"
Default store: Roche Bros. (Watertown area)
# Go to Roche Bros store
~/.claude/skills/chrome-control/scripts/chrome -p 1 navigate <tab_id> "https://www.instacart.com/store/roche-bros"
Performance: 16x faster than sequential approach (22s vs 360s for 6 items)
Strategy:
Complete Implementation:
#!/bin/bash
CHROME="~/.claude/skills/chrome-control/scripts/chrome -p 1"
ITEMS=("chicken+thighs" "napa+cabbage" "carrot" "snow+peas" "bean+sprouts" "scallions")
TABS=()
# Step 1: Open tabs ONE AT A TIME (serialized to avoid extension overload)
echo "Opening tabs..."
for item in "${ITEMS[@]}"; do
result=$($CHROME open "https://www.instacart.com/store/roche-bros/s?k=$item" 2>&1)
tab_id=$(echo "$result" | grep -o "tab [0-9]*" | grep -o "[0-9]*")
TABS+=($tab_id)
done
# Step 2: Wait for pages to load
sleep 3
# Step 3: Click Add buttons in PARALLEL using JS with aria-label selector
JS='(() => {
const btn = [...document.querySelectorAll("button")].find(b =>
(b.getAttribute("aria-label") || "").includes("Add 1")
);
if (btn) { btn.click(); return {ok:1}; }
return {ok:0};
})()'
for tab in "${TABS[@]}"; do
$CHROME js $tab "$JS" 2>/dev/null &
done
wait
# Step 4: Open cart and screenshot
$CHROME click ${TABS[0]} ref_26 # Click "View Cart" button
sleep 2
$CHROME screenshot ${TABS[0]}
# Step 5: Close worker tabs (keep one for cart view)
for tab in "${TABS[@]:1}"; do
$CHROME close $tab
done
CRITICAL: Use aria-label for Add buttons
// WRONG - innerText doesn't contain full text:
btn.textContent.includes("Add 1") // Returns false!
// CORRECT - product name is in aria-label:
btn.getAttribute("aria-label").includes("Add 1") // Returns true!
Why serialized tab opens? Opening 6+ tabs simultaneously overwhelms the Chrome extension connection, causing timeouts. Opening one at a time takes ~14s total but is 100% reliable.
Use direct search URLs instead of clicking through UI. This is faster and more reliable.
Search URL Pattern:
https://www.instacart.com/store/roche-bros/s?k=<search_term>
Example:
# Navigate directly to search results for "ground beef"
~/.claude/skills/chrome-control/scripts/chrome -p 1 navigate <tab_id> "https://www.instacart.com/store/roche-bros/s?k=ground+beef"
Programmatic Add to Cart via JS:
# After navigating to search results, use JS to find and click the first Add button
~/.claude/skills/chrome-control/scripts/chrome -p 1 js <tab_id> "
(() => {
// Find all Add buttons
const addBtns = [...document.querySelectorAll('button')].filter(b =>
b.textContent.includes('Add') && !b.textContent.includes('Added')
);
if (addBtns.length > 0) {
addBtns[0].click();
return {success: true, clicked: addBtns[0].textContent};
}
return {success: false, error: 'No Add button found'};
})()
"
Parse Search Results via JS:
# Get structured list of search results with names and prices
~/.claude/skills/chrome-control/scripts/chrome -p 1 js <tab_id> "
(() => {
const items = [];
// Product cards typically have price and name
document.querySelectorAll('[data-testid=\"product-card\"], [class*=\"ProductCard\"]').forEach(card => {
const name = card.querySelector('[class*=\"name\"], [data-testid=\"product-name\"]')?.textContent;
const price = card.querySelector('[class*=\"price\"]')?.textContent;
if (name) items.push({name: name.trim(), price: price?.trim()});
});
// Fallback: look for any item listings
if (items.length === 0) {
const text = document.body.innerText;
const matches = text.match(/\\$\\d+\\.\\d{2}/g);
return {fallback: true, prices: matches?.slice(0, 10)};
}
return items.slice(0, 10);
})()
"
Complete Optimized Subagent Prompt:
You are a grocery shopping subagent. Add "{item}" to Instacart cart.
Tab ID: {tab_id}
Chrome: ~/.claude/skills/chrome-control/scripts/chrome -p 1
Steps:
1. Navigate directly: navigate {tab_id} "https://www.instacart.com/store/roche-bros/s?k={item_url_encoded}"
2. Wait 2 seconds for results
3. Use JS to click first Add button:
js {tab_id} "document.querySelector('button[class*=\"add\"], button').click()"
4. Verify cart count increased via: read {tab_id} | grep "Items in cart"
Report success/failure.
Use this if direct URL doesn't work:
# 1. Click search box (ref_13)
~/.claude/skills/chrome-control/scripts/chrome -p 1 click <tab_id> ref_13
# 2. Type search term
~/.claude/skills/chrome-control/scripts/chrome -p 1 type <tab_id> ref_13 "sesame oil"
# 3. Click autocomplete suggestion link (NOT textbox)
~/.claude/skills/chrome-control/scripts/chrome -p 1 read <tab_id> | grep -i "<search_term>"
~/.claude/skills/chrome-control/scripts/chrome -p 1 click <tab_id> ref_16
# 4. Click Add button
~/.claude/skills/chrome-control/scripts/chrome -p 1 read <tab_id> | grep -i "add.*<item_name>"
~/.claude/skills/chrome-control/scripts/chrome -p 1 click <tab_id> <ref>
Use this if search isn't finding what you need:
# List category elements
~/.claude/skills/chrome-control/scripts/chrome -p 1 read <tab_id> | grep -i "<category_name>"
# Click on category
~/.claude/skills/chrome-control/scripts/chrome -p 1 click <tab_id> <ref>
# Find and add items
~/.claude/skills/chrome-control/scripts/chrome -p 1 read <tab_id> | grep -i "add.*<item_name>"
~/.claude/skills/chrome-control/scripts/chrome -p 1 click <tab_id> <ref>
Main Categories: | Category | Items | |----------|-------| | Produce > Fresh Vegetables | Onions, garlic, scallions, cucumbers, tomatoes | | Produce > Leafy Greens | Lettuce, spinach, kale | | Dairy & Eggs > Cheese | American cheese, cheddar, etc. | | Meat & Seafood | Ground beef, chicken, etc. | | Bakery > Buns & Rolls | Hamburger buns, hot dog buns | | Condiments & Sauces | Mayo, ketchup, soy sauce | | Condiments & Sauces > Asian Sauces | Soy sauce, teriyaki | | Oils, Vinegars & Spices > Vinegar | White vinegar, apple cider vinegar | | Oils, Vinegars & Spices > Cooking Oils > Sesame Oil | Sesame oil | | Baking Essentials > Sugars | Turbinado sugar, brown sugar |
Direct Category URLs:
https://www.instacart.com/store/roche-bros/collections/produce
https://www.instacart.com/store/roche-bros/collections/dairy-eggs
https://www.instacart.com/store/roche-bros/collections/meat-seafood
https://www.instacart.com/store/roche-bros/collections/bakery
https://www.instacart.com/store/roche-bros/collections/condiments-sauces
https://www.instacart.com/store/roche-bros/collections/oils-vinegars-spices
https://www.instacart.com/store/roche-bros/collections/baking-essentials
# Find and click cart button
~/.claude/skills/chrome-control/scripts/chrome -p 1 read <tab_id> | grep -i "cart"
~/.claude/skills/chrome-control/scripts/chrome -p 1 click <tab_id> <ref> # Usually ref_25 or similar "View Cart"
Cart appears as a sidebar panel on the right side of the page.
Use chrome js to extract structured price data directly from the DOM. This is more reliable than text parsing.
# Get all cart items with names and prices as JSON
~/.claude/skills/chrome-control/scripts/chrome -p 1 js <tab_id> "
(() => {
const items = [];
// Cart items have a specific structure - find item containers
document.querySelectorAll('[data-testid=\"cart-item\"], [class*=\"CartItem\"]').forEach(el => {
const name = el.querySelector('[class*=\"ItemName\"], [data-testid=\"item-name\"]')?.textContent?.trim();
const price = el.querySelector('[class*=\"price\"], [data-testid=\"item-price\"]')?.textContent?.trim();
if (name && price) items.push({name, price});
});
// Fallback: look for price patterns in cart sidebar
if (items.length === 0) {
const cartText = document.querySelector('[class*=\"cart\"], [data-testid=\"cart\"]')?.textContent || '';
const priceMatch = cartText.match(/\\$[\\d.]+/g);
return {fallback: true, prices: priceMatch};
}
return items;
})()
"
# Get cart total from the checkout button text
~/.claude/skills/chrome-control/scripts/chrome -p 1 js <tab_id> "
(() => {
const btn = document.querySelector('button[class*=\"checkout\"], [data-testid=\"go-to-checkout\"], button');
const allBtns = [...document.querySelectorAll('button')];
const checkoutBtn = allBtns.find(b => b.textContent.includes('checkout'));
const text = checkoutBtn?.textContent || '';
const match = text.match(/\\$([\d.]+)/);
return match ? {total: match[1], raw: text} : {raw: text};
})()
"
# Get full price breakdown from checkout page
~/.claude/skills/chrome-control/scripts/chrome -p 1 js <tab_id> "
(() => {
const text = document.body.innerText;
const lines = text.split('\\n');
const summaryIdx = lines.findIndex(l => l.includes('Summary'));
if (summaryIdx === -1) return {error: 'Summary not found'};
const summaryLines = lines.slice(summaryIdx, summaryIdx + 20);
const itemCount = summaryLines.find(l => /\\d+ items?/.test(l));
const prices = summaryLines.filter(l => /^\\$[\\d.]+$/.test(l.trim()));
const freeDelivery = summaryLines.some(l => l.includes('FREE'));
return {
itemCount: itemCount?.match(/(\\d+)/)?.[1],
itemSubtotal: prices[0],
serviceFee: summaryLines.find(l => l.includes('Service fee')) ? prices[1] : null,
total: prices[prices.length - 1],
freeDelivery
};
})()
"
# Use chrome read which returns the accessibility tree
~/.claude/skills/chrome-control/scripts/chrome -p 1 read <tab_id> | grep -E "checkout.*\\$|\\$[0-9]+\\.[0-9]+"
When reporting prices to user, include:
Example message format:
Checkout ready:
• Items (10): $36.22
• Delivery: FREE (Instacart+)
• Service fee: $2.49
• Total: $38.71
NEVER checkout without explicit permission. This involves payment.
Required before checkout:
Checkout process (only after all approvals):
User asks: "Find a recipe for tacos and order ingredients"
# Screenshot the recipe search results or recipe page
~/.claude/skills/chrome-control/scripts/chrome -p 1 screenshot <tab_id>
# Send screenshot to user via iMessage
[RECIPE SCREENSHOT from NYT Cooking]
Found: Korean Cheeseburgers with Sesame-Cucumber Pickles
⭐ 2,343 ratings | 25 min | by Kay Chun
Want this one, or should I find alternatives?
KOREAN CHEESEBURGERS - Ingredients:
WILL ADD TO CART:
• 1.5 lb ground beef
• 4 slices American cheese
• 4 hamburger buns
• 2 Persian cucumbers
• 2 tbsp scallions
• 1/4 cup soy sauce
• 2.5 tsp sesame oil
• 1/2 cup mayo
SKIPPING (assuming you have):
• Kosher salt
• Black pepper
• 2 tbsp sugar
• 2 tsp white vinegar
Want me to add/remove anything? Say "add the sugar" or "skip the mayo"
Cart Verification Checklist (MANDATORY — do this BEFORE sending cart to user):
# After adding items, ALWAYS verify:
1. Count items in cart vs items in grocery list
2. For each cart item, match to original ingredient
3. QUANTITY/VOLUME CHECK: Compare the product size (oz, ct, lb) × quantity
in cart against the recipe's required amount. Examples:
- Recipe needs 8 cups broth → 2x 32oz = 64oz = 8 cups ✅
- Recipe needs 8 cups broth → 2x 16.9oz = 33.8oz ≈ 4 cups ❌ (short!)
- Recipe needs 3 lemons → 3 ct ✅
Common pitfalls:
- Broth/stock: cartons vary wildly (16oz, 32oz, 48oz) — always do the math
- Spices: small containers may not cover recipe amount
- Produce by weight: verify package weight covers what recipe needs
4. Note any items that couldn't be found
5. Remind user what was SKIPPED (assumed in kitchen)
6. Present the cross-check to the user so they can verify before checkout
Example message format:
[CART SCREENSHOT - showing sidebar with items]
Cart ready at Roche Bros (6 items, $41.85):
✓ Napa Cabbage - $5.07
✓ Green Onions (Scallions) - $1.99
✓ Chicken Thighs - $11.88
✓ Bean Sprouts - $3.99
✓ Snow Peas - $8.99
✓ Carrots - $3.72
Subtotal: $41.85
Service fee: $2.93
Delivery: FREE (Instacart+)
Total: $44.78
SKIPPED (assuming you have):
• Canola/vegetable oil
• Soy sauce (light & dark)
• Oyster sauce
• Sesame oil
• Sugar, salt, pepper
• Garlic, ginger
Need any skipped items? Delivery: Tomorrow 2pm-6pm
NEVER checkout until user says something like:
Then:
Login modal won't close: Try clicking outside it or pressing Escape Search suggestions not appearing: Make sure you clicked the textbox first, then type Search not returning results: Click the autocomplete suggestion link, don't just press Enter Can't find item: Try different search terms or use category navigation 404 error on cart URL: Click the cart icon in the header instead
~/.claude/skills/chrome-control/scripts/chrome -p 1 tabs # List all tabs
~/.claude/skills/chrome-control/scripts/chrome -p 1 open <url> # Open new tab
~/.claude/skills/chrome-control/scripts/chrome -p 1 focus <tab_id> # Focus tab
~/.claude/skills/chrome-control/scripts/chrome -p 1 read <tab_id> # Get interactive elements
~/.claude/skills/chrome-control/scripts/chrome -p 1 click <tab_id> <ref> # Click element
~/.claude/skills/chrome-control/scripts/chrome -p 1 type <tab_id> <ref> "text" # Type text
~/.claude/skills/chrome-control/scripts/chrome -p 1 key <tab_id> Enter # Press key
~/.claude/skills/chrome-control/scripts/chrome -p 1 screenshot <tab_id> # Take screenshot
~/.claude/skills/chrome-control/scripts/chrome -p 1 scroll <tab_id> down 3 # Scroll
When generating a cooking_timeline widget, every step must be ingredient-accurate against the original recipe. Before sending the timeline, spawn a subagent (sonnet) to fact-check all steps.
You are an ingredient fact-checker for a cooking timeline. Given the ORIGINAL RECIPE and the GENERATED TIMELINE STEPS, verify each step's ingredients are correct.
ORIGINAL RECIPE:
{paste full recipe text — ingredients list + preparation instructions}
GENERATED TIMELINE STEPS:
{paste the JSON steps array you're about to send}
For EACH step that mentions an ingredient, check:
1. **Is it in the original recipe?** Flag any ingredient that appears in a step but NOT in the recipe.
2. **Is the amount correct?** If a step says "2 cloves garlic" but the recipe says "4 cloves", flag it.
3. **Is anything missing?** If the recipe says to add butter at this stage but the step doesn't mention it, flag it.
4. **Any accidental additions?** Sometimes when paraphrasing, ingredients get added that aren't in the recipe (e.g., "thyme" added to a step when the recipe never calls for thyme). Flag these.
Output format:
- ✅ Step "id": all ingredients correct
- ⚠️ Step "id": [issue description]
Be strict. Every ingredient in every step must trace back to the original recipe.
cooking_timeline widgetEvery recipe we cook gets saved as a markdown file in ~/recipes/ for future reference and search.
Filename: ~/recipes/YYYY-MM-DD-slug.md (date of first cook + slugified name)
Example: ~/recipes/2026-04-06-reverse-sear-ribeye.md
# Reverse Sear Ribeye with Roasted Mushrooms & Asparagus
**Source:** [NYT Cooking](https://cooking.nytimes.com/...) | improvised | etc.
**Serves:** 2
**Time:** ~75 min
**Tags:** beef, steak, date-night, reverse-sear
## Ingredients
- 2 ribeye steaks (1.5" thick)
- Kosher salt
- 2 tbsp avocado oil
- 2 tbsp butter
- ...full ingredient list...
## Steps
1. Salt steaks generously...
2. Preheat oven to 275°F...
...full preparation steps...
## Notes
- Internal temp: pull at 115°F for medium-rare
- Any modifications we made
- What worked, what to change next time
The archive is just markdown files — use Grep to search:
# Find all beef recipes
grep -rl "beef" ~/recipes/
# Find recipes by tag
grep -rl "date-night" ~/recipes/
# Find by ingredient
grep -rl "mushroom" ~/recipes/
If we cook something again with modifications, update the existing file:
## Cook Log section at the bottom with dated entries## Cook Log
### 2026-04-06
- First cook. Pulled at 115°F — perfect medium-rare.
- Mushrooms could use more balsamic next time.
### 2026-04-20
- Added rosemary to butter baste. Much better.
- Used 425°F for veggies instead of 400°F — better char.
development
Use when building React/Next.js components, dashboards, admin panels, apps, or any web interface. Trigger words - react, frontend, ui, dashboard, component, interface, web app, polish, audit, design review.
tools
Track flight status and get FlightAware links. Use when asked about flights, flight status, arrival times, or flight tracking. Trigger words - flight, flying, UA, AA, DL, landing, arriving, departure.
development
Query real-time locations of people sharing via Find My. Look up where someone is, reverse geocode GPS coordinates, set up geofence alerts. Trigger words - findmy, find my, location, where is, geofence, track location.
tools
Access Figma designs via MCP or Chrome. Use when asked about Figma files, design mockups, wireframes, or UI designs. Trigger words - figma, design, mockup, wireframe, UI design, FigJam.