DoorDash (family ordering via dd-cli)
Order food for the Beck family: pick a restaurant, compose an order that covers everyone's
tastes + health + the kids' ages, build the cart with dd-cli, and place it only after
Idan approves the items, total, tip, card, and ETA.
Execution: dd-cli (installed + authed)
- Binary at
~/.local/bin/dd-cli, signed in as idanbeck@gmail.com (v0.2.0). For command navigation see thedd-cli-usageskill; drill with--helpon demand. - Always run
dd-cli --json-output <cmd>and read data from thestructuredContent.<field>object (e.g.structuredContent.addresses,.cards,.carts,.stores,.quote). Ignorewidget_type/assistant_instructions/ any "the widget above" text — terminal context, no UI. - Location: the default delivery address is 2267 Shibley Ave, San Jose (Willow Glen),
coords lat 37.285902 / lng -121.896708.
searchdoes not auto-use the saved default — resolve the default's lat/lng fromaddress listat order time (fall back to those coords) and pass--lat/--lngexplicitly. - Payment: 3 cards on file.
order submitcharges the default card. The default is thestructuredContent.cards[]entry whosepayment_method_id== top-leveldefault_payment_method_id.
Hard rules (do not skip)
- Allergy gate. Read the profile. If the Allergies field is
TODO, stop and ask Idan to fill it (or setnone). The helper enforces this (checkexits 2). - Payment approval gate — mandatory. The pipeline is:
cart add-items→order preview(no charge, authoritative pricing + ETA) → show Idan the approval report (every line item + who it's for, subtotal, fees, tax, tip, TOTAL, ETA, and the named default card brand+last4) → get an explicit "approved" → only thenorder submit. Neverorder submitwithout that. order submitis DESTRUCTIVE + NON-IDEMPOTENT. It charges the default card immediately, and re-running with the samecart_uuidcreates a duplicate order. Run it exactly once. If you're unsure whether it went through, runorder status— never re-submit.- Name the card + confirm the tip. Surface the default card (brand + last4) in the report;
default tip is 15% (profile), passed as
--tip-cents(CENTS). If Idan wants a different card, the CLI can't swap it — route him toorder checkout-urlto finish in the browser. - Reconcile before submit. The report's numbers must come from
order preview, not guesses. If a fresh preview drifts materially from what was approved, re-approve.
Data
- Profile (source of truth):
~/Library/Mobile Documents/iCloud~md~obsidian/Documents/idanbeck/Personal/Family Food Profile.md— tastes, allergies, per-person no-gos, health mode (balanced), budget (flexible, flag unusual), order-composition rules, restaurant list, and a machine-readable JSON block. - Helper (food-logic validator, no ordering/payment):
python3 ~/.claude/skills/doordash/doordash_skill.pyprofile— dump the parsed profile.schema— the order-JSON shape.check ORDER.json(orcheck -for stdin) — allergy gate + allergen scan + balanced/coverage/kid/iron checks + the approval report. Exit 0 = ready, 1 = blockers, 2 = refused (allergies unset / empty order).
- dd-cli commands used:
search·menu·cart add-items|show|remove-item|list|delete·order preview|submit|status|checkout-url|history·address list·payment-method list.
Flow
- Read the profile:
python3 doordash_skill.py profile. Confirm allergies are set. Note tastes/no-gos, health mode, budget, the kids (Sara 7, Zev 5 — picky/plain, Noa ~16mo — toddler, iron-priority), and the restaurant list. - Resolve location:
dd-cli --json-output address list→ theis_defaultentry's lat/lng (currently Shibley, 37.285902 / -121.896708). - Pick the restaurant: the one Idan asked for, or propose 1–2 from the profile. Rotate cuisine vs the Hits log. Authentic only (no American-Chinese — "we don't like the fake stuff").
- Find the store:
dd-cli --json-output search -q "<cuisine>" --lat 37.285902 --lng -121.896708 --limit 8→structuredContent.stores[].store_id. Pre-flight:dd-cli --json-output cart list— if an open cart already exists at that store, extend it (reuse itscart_uuid) orcart deleteit; don't silently stack a new one. - Read the menu:
dd-cli --json-output menu --store-id <id>→structuredContent.menu_id+items[].item_id+ prices. Use real names / prices / ids — don't invent them. - Compose the order per the profile's rules: protein + veg + starch; a noodle/rice base; a safe item for each kid (Zev = plain: fries/nuggets/plain noodles/cheese pizza); a soft, iron-rich, no-choking option Noa can share (soft beef / dark-meat chicken / tofu / lentils / spinach) ideally with a vitamin-C side; ≤1 fried; spicy = parents only; order +10–20% extra for leftovers; skip items that travel badly (soup dumplings) for delivery.
- Build the cart:
dd-cli --json-output cart add-items --store-id <id> --menu-id <menu_id> --items-json '[{"item_id":"...","item_name":"...","quantity":1}, ...]'→structuredContentreturns thecart_uuid. Keep passing thatcart_uuidon further calls. Adjust withcart show --cart-uuid <u>/cart remove-item --cart-uuid <u> --cart-item-id <n>(note:cart_item_idis the cart-lineid, notitem_id). Item customizations go innested_options[](each needsid+name+quantity). - Validate food logic: write the order JSON (names/prices from the menu; see shape below) and
run
python3 doordash_skill.py check order.json. Fix blockers (exit 1) / refusals (exit 2). - Preview (authoritative, NO charge):
dd-cli --json-output order preview --cart-uuid <u>→ read the real subtotal, fees, tax, and ETA fromstructuredContent.quote/delivery_availability. Compute the tip (15% default). - Show Idan the approval report — items + who (from your composition) with the real preview numbers + tip + grand total + ETA + the named default card. Wait for an explicit "approved." (He may edit — adjust the cart and re-preview.)
- Submit (charges default card, once):
dd-cli order submit --cart-uuid <u> --tip-cents <cents> -ythendd-cli --json-output order statusto confirm it went through. Never re-run submit — if in doubt, check status. - Log it: append a row to the Hits & Misses table in the profile (date, restaurant, what was ordered, later the verdict). Add good restaurants to the Restaurants table.
Order JSON shape (for the helper)
{
"restaurant": "Mian Sichuan Noodles & Dumplings",
"eta_minutes": 40,
"items": [
{"name": "Dan Dan Noodles (from menu)", "for": ["Idan", "Stacey"], "price": 13.95, "qty": 1, "tags": ["starch"]},
{"name": "Steamed Rice + shredded chicken", "for": ["Zev", "Noa"], "price": 4.00, "qty": 1, "tags": ["starch", "protein"]},
{"name": "Sautéed Spinach with garlic", "for": ["Noa", "table"], "price": 9.50, "qty": 1, "tags": ["veg", "protein"]}
],
"fees": {"delivery": 3.99, "service": 4.20, "tax": 3.10, "tip": 8.00}
}
for = who it's for (names, or "table" for shared). tags help the coverage check
(protein/veg/starch/fried); names are auto-tagged. Prices/ids come from dd-cli menu;
fees/tax/ETA come from dd-cli order preview — not guesses.
Notes
- JSON envelope: every
--json-outputresponse wraps data instructuredContent; read from there and ignorewidget_type/assistant_instructions. searchneeds explicit--lat/--lng— it does not auto-use the saved default address.order submitis silent-charge + non-idempotent — approval gate, run once, verify withorder status. A different card requiresorder checkout-url(browser).- Toddler safety for Noa is non-negotiable: no whole grapes/nuts/hard chunks/popcorn; nothing spicy; soft small pieces she can gum.
- Budget is flexible — don't optimize for cost, but flag anything unusually pricey (the helper flags items > $30 and totals > $150).
- Groceries/retail (not restaurants): use
find-nearby-stores/find-items/build-grocery-list, notsearch. - Keep the profile current: when Idan reacts to a meal, update the per-person Loves/No-gos and the Hits & Misses log so the skill learns.