# Project export: Mahm: Made At Home... Mmmm

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: Mahm is an AI nutritionist that turns “What should I eat?” into a personalized weekly meal plan based on your goals, like having a mom who's a nutritionist and a personal shopper.
- Devpost: https://devpost.com/software/mahm-made-at-home-mmmm
- GitHub: https://github.com/sindhu-satish/treehacks
- Video: https://www.youtube.com/embed/niOlAKOMbJ8?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 6 GitHub contributor(s) — Sindhu Satish (19 commits), Eli Zerr (7 commits), anika (5 commits), Cursor (5 commits), Claude Opus 4.5 (1 commits), carolynhe (1 commits)

## Devpost submission (written by the team)

### Inspiration

It's 6pm and you're hungry. You want to eat healthy, but you're lactose intolerant, you're on a tight budget, and you have no idea what's actually affordable at the grocery store near you. You could open MyFitnessPal, but it only counts calories after you've already eaten. You could browse recipe apps, but they don't know about your allergies, your budget, or what's in stock locally. 74% of Americans want to eat healthier, but don't know where to start (American Heart Association). Over $150 per household gets wasted monthly on food that goes uneaten. And there are zero apps that connect your dietary needs, your budget, and your local grocery stores into one experience. We built Mahm to fix that. Mahm is an AI nutritionist that knows your constraints, recommends meals that work for you, plans your week, tracks your nutrition, and finds the ingredients at real local stores with real prices. The name stands for Make At Home Mmmm, also a homonym for "mom," because that's what using it feels like. Someone who actually cares what you eat.

### What it does

Mahm has four modes that work together to take you from "I don't know what to eat" to a full week of meals with groceries sourced from your neighborhood. AI Nutritionist Chat Mahm is a multi-turn conversational agent that gathers context before making recommendations. Tell her you're a vegetarian, lactose intolerant, on an $80/week budget, and a beginner cook. Then she reasons across all your constraints at once (vegetarian + fatigue = possible iron deficiency) and recommends meals that fit. Push back, and she adapts. Say "I hate tofu" and she swaps every tofu dish for lentils and chickpeas, explaining each substitution. Under the hood, Mahm has 4 custom agent tools: search_recipes — hybrid search across 500+ recipes via Elasticsearch, with dietary and cuisine filtering get_nutrition — validates nutritional claims before Mahm states them find_stores — real-time local grocery price comparison via Bright Data MCP generate_meal_plan — creates weekly plans optimized for budget, nutrition, and variety AI Meal Planner & Recipe Generator Mahm generates personalized weekly meal plans based on your dietary needs, budget, schedule, and preferences. The plans account for variety, nutritional balance, and realistic time constraints. Once generated, you can swap, log, and plan meals. There are two additional ways to generate and log meals: Generate through chat — request a meal from Mahm and it's automatically slotted into the meal planning calendar Upload a photo — snap a picture of a meal you ate from anywhere, and Mahm identifies the dish and its nutrients. Nutrition & Meal Tracker A dashboard that gives you daily and weekly nutrition summaries — calories, macros, micronutrients — so you can see how your meals stack up against your goals over time. Autolog with calendar — for planned meals on the meal calendar. This means Mahm tracks both meals she recommends and meals you eat independently — giving you a complete picture of your nutrition, not just the days you cook at home. Smart Marketplace Ask Mahm where to buy ingredients and she scrapes real prices from local grocery stores using Bright Data's MCP tools (search_engine + scrape_as_markdown). Real data, real stores, real prices. The marketplace is woven into the conversation. Mahm recommends a recipe, offers to find ingredients, and shows local prices. One continuous flow from dietary advice to shopping list.

### How we built it

Architecture Mahm runs as three microservices: the agent app (Next.js, port 3000), a recipe data API (port 3001), and a Flask backend for the marketplace (port 5000). The agent proxies requests to the other services via internal API routes (/api/recipes → data-apis, /api/marketplace → backend) and falls back to cached data when any service is down. This separation means we could develop and test each service independently, and any single service going down doesn't crash the demo — which saved us at least twice during the hackathon. Agent Tool Loop & Prompt Engineering Anthropic's Claude powers the Mahm agent via the Messages API. We implemented a manual tool loop: Claude receives the conversation, decides which tool(s) to call, the agent executes the tool and returns the result, and Claude generates the next response. This loops until Claude responds without a tool call. The 4 custom tools — search_recipes, get_nutrition, find_stores, and generate_meal_plan — are defined as function schemas that Claude can invoke autonomously. Claude decides on its own which tool to use and when, based on the conversation context. For example, if a user mentions they're vegetarian and fatigued, Claude will call get_nutrition to check iron-rich foods before recommending, then call search_recipes with those filters. No hardcoded logic determines this flow — it emerges from the prompt and Claude's reasoning. The system prompt went through 8 revisions. Key design decisions: Strict nutrition grounding: Mahm is instructed to never state a calorie or nutrient claim without calling get_nutrition first. This eliminated hallucinated nutrition facts. Clarification-first behavior: Instead of immediately recommending, Mahm asks 2-3 follow-up questions to build a fuller picture of the user's constraints. This is what makes the multi-turn conversation feel like talking to a real nutritionist rather than getting search results. Graceful tool failure: If any tool returns an error, the agent receives it as structured JSON and responds with a helpful fallback rather than crashing the conversation. Personality layer: Mahm is warm but not overly casual — she feels like a knowledgeable friend, not a medical chatbot. We also built a cached demo conversation (GET /api/demo) that stores a pre-run multi-turn exchange, so we can instantly load a polished Greylock-style conversation for judges without waiting on API latency. Recipe Search — Supabase + Elasticsearch Recipes are stored in Supabase PostgreSQL — 500+ entries with dietary tags (vegetarian, vegan, gluten-free, dairy-free, etc.), cuisine type, cook time, skill level, and full ingredient lists. Supabase serves as the source of truth and handles user preferences and session data. Elasticsearch sits on top as the search and filtering layer. When the search_recipes tool fires, it queries Elasticsearch with a hybrid approach — combining keyword matching (for specific terms like "high iron" or "gluten-free") with semantic search via OpenAI GPT-4o embeddings (for intent-based queries like "something cozy for a cold night"). This hybrid approach is what lets Mahm understand both specific dietary filters and vague cravings in the same query. The Elasticsearch index is configured with weighted fields so dietary tags are prioritized over general description text — this is what prevents "vegan dinner" from returning pasta carbonara. Bright Data Scraping & API Integration The grocery marketplace is powered by Bright Data's MCP (Model Context Protocol) tools, specifically search_engine and scrape_as_markdown. The architecture works as follows: User asks Mahm where to buy ingredients for a recipe Claude calls the find_stores tool with the ingredient list and the user's zip code The agent app exposes a /api/scrape-ingredient endpoint that uses Bright Data's MCP to search for each ingredient across local grocery store websites search_engine finds product pages for each ingredient at stores near the user's location scrape_as_markdown extracts the structured price and availability data from those pages The Flask backend aggregates results across stores, compares prices, and returns a structured response to the agent Bright Data's unblocking proxy infrastructure is what makes this work. Grocery stores actively block programmatic access, and previous scraping approaches we tried failed within minutes. We also built fallback logic: if live scraping is slow or rate-limited, the system uses pre-scraped data for ~50 common ingredients at stores near Stanford. Meal Planner Algorithm The meal planner takes the user's full constraint profile (dietary restrictions, budget, skill level, time, preferences, dislikes) and generates a 7-day plan. The algorithm optimizes across several dimensions: Budget distribution — allocates the weekly budget across meals, front-loading cheaper meals to leave room for variety later in the week Nutritional balance — ensures daily protein, iron, fiber, and other key nutrients are distributed across meals rather than concentrated Variety — prevents the same cuisine or protein source from appearing on consecutive days Practical scheduling — assigns quicker meals to weekdays and more involved recipes to weekends based on available cooking time Once generated, the plan is presented in an editable interface where users can drag and drop meals between days, swap individual meals, or regenerate specific days while keeping the rest. Meal Logging The tracker supports three input methods, all feeding into the same nutrition dashboard: Chat-generated meals are logged automatically — when Mahm recommends a recipe, the nutrition data from get_nutrition is stored alongside the meal entry Photo uploads use a vision model to identify the dish, estimate portion size, and look up approximate nutritional content Frontend Next.js 15 + TypeScript + Tailwind CSS. The UI has five views: landing page, onboarding flow, chat interface, marketplace, and the meal planner/tracker dashboard. Google Calendar API handles meal schedule sync. The gingham and tomatoes were a fun appendage.

### Challenges we ran into

Captchas. Stores actively block programmatic price lookups. Our first approach got blocked within 20 minutes, and our second hit rate limits immediately. Bright Data's MCP tools with their unblocking proxy infrastructure made it viable. We also built graceful degradation — if scraping fails, Mahm tells you honestly instead of making up prices. Elasticsearch indexing. Our recipe search was returning pasta carbonara for "vegan dinner" because the initial index didn't weight dietary tags properly. We rebuilt the field weighting and hybrid scoring to prioritize dietary filters over general description text.

### Accomplishments we're proud of

Shipped 4 working modes in 36 hours — chat, meal planner, nutrition tracker, and marketplace Live grocery prices from real local stores via Bright Data MCP Hybrid recipe search (Elasticsearch + OpenAI embeddings) that understands both dietary filters and vague cravings End-to-end commerce flow: chat → recipe → find ingredients → real prices at real stores Sleep rotation meant everyone was functional for the demo Learning how to work with random people! Two of our original team members dropped out at the last minute — we literally picked up random people in the basement of Huang and everything worked out

### What we learned

Building the recipe search pipeline — Supabase for storage, Elasticsearch for hybrid search with OpenAI embeddings — taught me that search quality depends entirely on how you index. Weighting dietary tags above description text was the fix that made "vegan dinner" stop returning carbonara. Autonomous tool selection with Claude is powerful but requires extensive prompt iteration. The system prompt went through 8 versions. Multi-turn conversation design is essentially UX design. Bright Data's MCP tools are what made real-time scraping viable at hackathon speed, but also the trickiest to integrate due to rate limiting issues. Building the Flask backend to communicate with the agent's scraper endpoint through proxy routes was a crash course in microservice architecture under time pressure. The most important skill at a hackathon is knowing when to cut scope and when to push through. We planned 5 modes and shipped 4 because we made cut decisions early instead of debating them at 3am. Having all four modes polished for the demo made the difference.

### What's next

Grocery Delivery Integration The marketplace currently shows you prices — but you still have to go to the store yourself. The next step is connecting with delivery APIs (Instacart, DoorDash, Uber Eats) so you can go from Mahm's recommendation to groceries at your door without leaving the app. The commerce flow becomes: chat → recipe → find ingredients → order delivery. That's the full loop. Community Recipes & Social Features Right now Mahm searches our curated recipe database. We want to open this up — let users submit their own recipes, rate and review meals, and share meal plans with friends and family. On Agentverse, this becomes a recipe marketplace where creators can monetize their content and Mahm indexes community recipes alongside the curated ones. Cultural Cuisine Expansion Our current recipe database skews Western. We want to add properly sourced South Asian, West African, East Asian, Latin American, and Middle Eastern comfort food — not just fusion versions, but authentic recipes with local ingredient sourcing. Mahm should be able to recommend a proper dal makhani or jollof rice, with ingredients available at your neighborhood grocery store.

## README (from the GitHub repository)

# Mahm — TreeHacks 2026

**Make At Home Mmmm.** Your AI nutritionist that knows your allergies, budget, and local stores.

## Project structure

- **Data APIs** (Anika’s work): in **`data-apis/`** — see [data-apis/README.md](data-apis/README.md); run from the `data-apis/` folder.
- **Backend** (Python): in **`backend/`**.
- **Agent app**: in **`multiagents/`** — run from that folder for the Mahm chat UI and APIs below.

| Folder | Owner | Description |
|--------|-------|-------------|
| **multiagents/** | Person B (Agent) | Mahm chat UI + Claude agent. Main entry point for AI chat. |
| **data-apis/** | Person A | Recipe search API (Supabase). Run on port 3001 when using real recipes. |
| **backend/** | Person C | Marketplace API (Flask). Grocery prices, store lookup. Run on port 5000. |
| **ui/** | Person D | Full demo UI (landing, onboarding, chat, marketplace, calendar). Uses dummy data; not wired to agent yet. |

## Person B (Agent) — status

- [x] Claude Agent SDK (Anthropic Messages API) via `/api/chat`
- [x] System prompt with strict nutrition grounding (never state calories without `get_nutrition`)
- [x] Four tools: `search_recipes`, `get_nutrition`, `find_stores`, `generate_meal_plan`
- [x] Proxy routes: `/api/recipes` → data-apis, `/api/marketplace` → backend
- [x] Tools use real APIs when services are running; fall back to mocks otherwise
- [x] Multi-turn memory; Mahm asks clarifying questions before recommending
- [x] Graceful tool failure (errors as JSON, agent responds with fallback)
- [x] Cached demo: `data/demo-conversation.json` + `GET /api/demo`; "Load demo backup" in UI
- [ ] `get_nutrition` and `generate_meal_plan` still use mocks (Person A APIs pending)

## Run locally

### Quick start (agent only, mock data)

```bash
cd multiagents
cp .env.example .env.local
# Add ANTHROPIC_API_KEY to .env.local

npm install
npm run dev
```

Open [http://localhost:3000](http://localhost:3000). Chat with Mahm; use **Load demo backup** for the Greylock-style conversation.

### Full flow (real recipes + marketplace)

Run all three services (in separate terminals):

```bash
# Terminal 1 — multiagents (port 3000)
cd multiagents && npm run dev
```

```bash
# Terminal 2 — data-apis (port 3001); needs Supabase env in data-apis/.env
cd data-apis && npm run dev -- -p 3001
```

```bash
# Terminal 3 — backend (port 5000)
cd backend && python run.py
```

In `multiagents/.env`, set:
- `DATA_APIS_URL=http://localhost:3001`
- `BACKEND_URL=http://localhost:5000`

(Defaults in `.env.example`.) If data-apis or backend are down, tools fall back to mocks.

### Live ingredient scraping (Bright Data MCP)

For real grocery prices instead of the dev catalog:
1. Get an API key from [brightdata.com/cp/setting/users](https://brightdata.com/cp/setting/users)
2. In `multiagents/.env.local`: `BRIGHTDATA_API_KEY=your_key`
3. In `backend/.env`: `MARKETPLACE_PROVIDER=brightdata` and `SCRAPER_SERVICE_URL=http://localhost:3000`
4. Run multiagents and backend. The backend calls multiagents `POST /api/scrape-ingredient`, which uses Bright Data MCP (`search_engine` + `scrape_as_markdown`) to fetch prices.

## Test tool calls

| Method        | Command                                            | Notes                                                            |
|---------------|----------------------------------------------------|------------------------------------------------------------------|
| No API key    | `curl http://localhost:3000/api/test-tools`        | Runs all 4 tools with sample inputs                              |
| With agent    | Chat in UI, e.g. "I'm vegetarian, $80/week, beginner. What should I eat?" | Needs `ANTHROPIC_API_KEY`                            |

## API (multiagents)

| Endpoint            | Method | Description                                                    |
|---------------------|--------|----------------------------------------------------------------|
| `/api/chat`         | POST   | `{ "messages": [...] }` → `{ "text", "toolCalls?", "error?" }` |
| `/api/demo`         | GET    | Cached demo messages                                           |
| `/api/test-tools`   | GET    | Run all tools with sample inputs                               |
| `/api/recipes`         | GET    | Proxy to data-apis (query, max_results, dietary_filters)       |
| `/api/marketplace`     | GET    | Proxy to backend (ingredients, zip)                            |
| `/api/scrape-ingredient` | POST | Bright Data MCP scraper (store, zip, query → price info)       |

## Tech

- **multiagents**: Next.js 15, TypeScript, Tailwind, Anthropic Messages API
- **Tools**: Manual tool loop (4 tools, multi-turn)
- **Proxies**: `/api/recipes` → data-apis `POST /api/search_recipes`, `/api/marketplace` → backend `POST /api/marketplace`


## Detected evidence (automated analysis)

Indexed codebase: 96 recognized source files, 555 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers
- AI coding agent: Cursor — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (115 of 115)

```
.gitignore
backend/app/__init__.py
backend/app/auth.py
backend/app/cart.py
backend/app/health.py
backend/app/marketplace_utils.py
backend/app/marketplace.py
backend/app/meal_logs.py
backend/app/meal_plans.py
backend/app/nutrition.py
backend/app/parsing_utils.py
backend/app/recipes.py
backend/app/user_profile.py
backend/migrations/00001_initial_schema.sql
backend/migrations/00002_recipes_table.sql
backend/migrations/00003_user_profiles_schema.sql
backend/migrations/00004_users_password.sql
backend/migrations/README.md
backend/README.md
backend/run.py
backend/scripts/brightdata_request.py
backend/scripts/extract_walmart_html.py
backend/scripts/fetch_store_prices.py
backend/scripts/import_recipes.py
data-apis/.gitignore
data-apis/app/api/generate_meal_plan/route.ts
data-apis/app/api/get_nutrition/route.ts
data-apis/app/api/health/route.ts
data-apis/app/api/search_recipes/route.ts
data-apis/app/globals.css
data-apis/app/layout.tsx
data-apis/app/page.tsx
data-apis/data/price_estimates.ts
data-apis/eslint.config.mjs
data-apis/lib/supabase.ts
data-apis/next.config.ts
data-apis/package.json
data-apis/postcss.config.mjs
data-apis/README.md
data-apis/scripts/index_recipes_to_elastic.mjs
data-apis/scripts/search_elastic.mjs
data-apis/tsconfig.json
data-apis/types/contracts.ts
multiagents/.env.example
multiagents/agents/mahm-agent.ts
multiagents/app/api/chat/route.ts
multiagents/app/api/demo/route.ts
multiagents/app/api/marketplace/route.ts
multiagents/app/api/recipes/route.ts
multiagents/app/api/scrape-ingredient/route.ts
multiagents/app/api/test-tools/route.ts
multiagents/app/globals.css
multiagents/app/layout.tsx
multiagents/app/page.tsx
multiagents/data/demo-conversation.json
multiagents/lib/intent.ts
multiagents/lib/mahm-system-prompt.ts
multiagents/lib/mahm-tool-schemas.ts
multiagents/lib/mahm-tools.ts
multiagents/middleware.ts
multiagents/next-env.d.ts
multiagents/next.config.ts
multiagents/package.json
multiagents/README.md
multiagents/tsconfig.json
README.md
requirements.txt
ui/.env.example
ui/components.json
ui/eslint.config.mjs
ui/FEATURES.md
ui/next.config.ts
ui/package.json
ui/postcss.config.mjs
ui/README.md
ui/src/app/globals.css
ui/src/app/layout.tsx
ui/src/app/onboarding/page.tsx
ui/src/app/page.tsx
ui/src/app/profile/page.tsx
ui/src/app/recipe/[id]/page.tsx
ui/src/app/saved/page.tsx
ui/src/components/auth/LoginModal.tsx
ui/src/components/brand/MahmLogo.tsx
ui/src/components/calendar/GroceryList.tsx
ui/src/components/calendar/index.ts
ui/src/components/calendar/MealCalendar.tsx
ui/src/components/chat/ChatInput.tsx
ui/src/components/chat/ChatMessage.tsx
ui/src/components/chat/index.ts
ui/src/components/chat/ToolBadge.tsx
ui/src/components/chat/TypingIndicator.tsx
ui/src/components/marketplace/GroceryComparison.tsx
ui/src/components/marketplace/index.ts
ui/src/components/marketplace/StoreCard.tsx
ui/src/components/nutrition/NutritionDashboard.tsx
ui/src/components/recipe/index.ts
ui/src/components/recipe/NutritionBadge.tsx
ui/src/components/recipe/RecipeCard.tsx
ui/src/components/ui/avatar.tsx
ui/src/components/ui/badge.tsx
ui/src/components/ui/button.tsx
ui/src/components/ui/card.tsx
ui/src/components/ui/input.tsx
ui/src/components/ui/scroll-area.tsx
ui/src/components/ui/separator.tsx
ui/src/components/ui/skeleton.tsx
ui/src/components/ui/tabs.tsx
ui/src/contexts/AuthContext.tsx
ui/src/contexts/MahmContext.tsx
ui/src/lib/api.ts
ui/src/lib/dummy-data.ts
ui/src/lib/utils.ts
ui/src/types/index.ts
ui/tsconfig.json
```

### Dependencies

- data-apis/package.json: @elastic/elasticsearch@^9.3.1, @supabase/supabase-js@^2.95.3, @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, dotenv@^17.3.1, eslint@^9, eslint-config-next@16.1.6, next@16.1.6, openai@^6.22.0, react@19.2.3, react-dom@19.2.3, tailwindcss@^4, typescript@^5
- multiagents/package.json: @anthropic-ai/sdk@^0.32.1, @brightdata/mcp@^2.8.4, @modelcontextprotocol/sdk@^1.26.0, @types/node@^20, @types/react@^18, @types/react-dom@^18, eslint@^8, eslint-config-next@15.0.3, next@15.0.3, openai@^6.22.0, react@^18.3.1, react-dom@^18.3.1, typescript@^5, zod@^3.23.8
- requirements.txt: annotated-types, anyio, blinker, cachetools, certifi, cffi, charset-normalizer, click, colorama, cryptography, deprecation, dotenv, Flask, flask-cors, fsspec, h11, h2, hpack, httpcore, httpx, hyperframe, idna, itsdangerous, Jinja2, markdown-it-py, MarkupSafe, mdurl, mmh3, multidict, openai, packaging, postgrest, propcache, pycparser, pydantic, pydantic_core, Pygments, pyiceberg, PyJWT, pyparsing, pyroaring, python-dateutil, python-dotenv, realtime, requests, rich, six, storage3, StrEnum, strictyaml, supabase, supabase-auth, supabase-functions, tenacity, typing_extensions, typing-inspection, urllib3, websockets, Werkzeug, yarl, zstandard
- ui/package.json: @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9, eslint-config-next@16.1.6, lucide-react@^0.564.0, next@16.1.6, radix-ui@^1.4.3, react@19.2.3, react-dom@19.2.3, shadcn@^3.8.4, tailwind-merge@^3.4.0, tailwindcss@^4, tw-animate-css@^1.4.0, typescript@^5

### Recent commits (newest first)

- scripts
- bright data FINALLY integrated
- Merge pull request #3 from sindhu-satish/ui-updates
- Add OpenAI-powered recipe extraction and improve UX
- Refactor user authentication and profile management
- UI changes
- Merge branch 'main' of https://github.com/sindhu-satish/treehacks
- UI changes
- Update to use Bright Data Unlocker API
- Elastic search wired + indexed recipes_v1 + filters (allergens, cook time)
- Merge pull request #2 from sindhu-satish/ui
- README changes
- changes to brightdata
- backend
- Merge branch 'anika-data-apis'
- Ignore Next.js generated next-env.d.ts
- Fix hydration errors and add interactive meal calendar features
- Rebrand: Baloo 2 font, tomato/sage palette, hand-drawn mom logo
- Add features documentation
- Data APIs complete: search_recipes, get_nutrition (USDA), generate_meal_plan with budget + grocery list

## Key source files (fetched from GitHub, selected and truncated for size)

### ui/FEATURES.md

```markdown
# Mahm UI - Feature Documentation & API Requirements

## Overview

**Mahm** is an AI-powered nutritionist and meal planning application with the tagline *"Like having a mom who's also a nutritionist."* The app helps users get personalized meal recommendations, compare grocery prices across local stores, and plan their weekly meals.

---

## Pages & Features

### 1. Landing Page (`/`)

**Before Onboarding:**
- Hero section with app value proposition
- Feature cards (Personalized Meals, Best Prices, Weekly Plans)
- "Get Started Free" → Onboarding flow
- "Try Demo" → Skip to main app

**After Onboarding - 4-Tab Interface:**

#### Chat Tab
- Conversational AI chat with "Mahm"
- Message history with date separators
- Typing indicator when AI responds
- Tool badges showing AI actions (search_recipes, find_stores, etc.)
- Side panel with recommended recipes
- Inline marketplace results when discussing ingredients

#### Marketplace Tab
- Zip code input for location
- "Find Stores" button with loading animation
- Price comparison grid showing ingredient prices across stores
- Select cheapest option or manually pick stores
- Shopping cart with:
  - Items grouped by store
  - Quantity adjustment (+/-)
  - Store subtotals and total cost
  - Checkout button

#### Meal Calendar Tab
- 7-day calendar grid with week navigation
- Meal type toggles (Breakfast, Lunch, Dinner, Snacks, Dessert)
- Hover actions per meal:
  - 🔄 Swap meal
  - ✨ Regenerate new suggestion
  - 🍽️ Mark as eating out
  - × Skip meal
- Daily nutrition totals (calories, protein)
- Weekly cost estimate
- Integrated grocery list by category
- Nutrition summary dashboard

#### Photo Log Tab
- **Log a Meal**: Upload photo, mark as planned/unplanned, select meal type
- **Recipe from Photo**: Upload restaurant dish photo → generate homemade recipe
- Recent meal photos gallery

---

### 2. Onboarding Page (`/onboarding`)

**6-Step Flow:**
1. **Welcome** - Name input, feature preview
2. **Diet** - Dietary restrictions (vegetarian, vegan, keto, halal, etc.)
3. **Allergies** - Food allergies + disliked foods
4. **Goals** - Health goals (weight loss, muscle, energy) + cooking skill level
5. **Budget & Time** - Weekly budget ($30-$300), cooking time, household size
6. **Pantry** - Mark items already owned (oils, spices, grains, etc.)

---

### 3. Profile Page (`/profile`)

- Profile header with avatar
- Editable sections:
  - Dietary restrictions
  - Allergies & intolerances
  - Disliked foods (add/remove)
  - Health goals
  - Cooking skill level
  - Weekly budget
  - Available cooking time
  - Household size
- Save button with success feedback
- Persists to localStorage

---

### 4. Saved Recipes Page (`/saved`)

**3-Tab Interface:**

#### Saved Recipes Tab
- Grid of saved recipe cards
- Click to view full recipe

#### Made Recipes Tab (Cooking Journal)
- Recipes user has cooked
- "Made X times" badge
- Journal entries with date, rating, notes

#### Inspiration Tab
- Paste URL from TikTok/Instagram/YouT
[truncated — 5137 more characters]
```

### requirements.txt

```
annotated-types
anyio
blinker
cachetools
certifi
cffi
charset-normalizer
click
colorama
cryptography
deprecation
dotenv
Flask
flask-cors
fsspec
h11
h2
hpack
httpcore
httpx
hyperframe
idna
itsdangerous
Jinja2
markdown-it-py
MarkupSafe
mdurl
mmh3
multidict
packaging
postgrest
propcache
pycparser
pydantic
pydantic_core
Pygments
pyiceberg
PyJWT
pyparsing
pyroaring
python-dateutil
python-dotenv
realtime
requests
rich
six
storage3
StrEnum
strictyaml
supabase
supabase-auth
supabase-functions
tenacity
typing-inspection
typing_extensions
urllib3
websockets
Werkzeug
yarl
zstandard
openai

```

### multiagents/package.json

```
{
  "name": "mahm-treehacks",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.32.1",
    "@brightdata/mcp": "^2.8.4",
    "@modelcontextprotocol/sdk": "^1.26.0",
    "openai": "^6.22.0",
    "zod": "^3.23.8"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "15.0.3",
    "next": "15.0.3",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "typescript": "^5"
  }
}

```

### data-apis/package.json

```
{
  "name": "treehacks",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "@elastic/elasticsearch": "^9.3.1",
    "@supabase/supabase-js": "^2.95.3",
    "dotenv": "^17.3.1",
    "next": "16.1.6",
    "openai": "^6.22.0",
    "react": "19.2.3",
    "react-dom": "19.2.3"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.1.6",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### ui/package.json

```
{
  "name": "treehacks",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lucide-react": "^0.564.0",
    "next": "16.1.6",
    "radix-ui": "^1.4.3",
    "react": "19.2.3",
    "react-dom": "19.2.3",
    "tailwind-merge": "^3.4.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.1.6",
    "shadcn": "^3.8.4",
    "tailwindcss": "^4",
    "tw-animate-css": "^1.4.0",
    "typescript": "^5"
  }
}

```

### multiagents/app/layout.tsx

```typescript
import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
  title: "Mahm — Your AI Nutritionist",
  description: "Like having a mom who's also a nutritionist and a personal shopper.",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

```

### data-apis/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

```

### data-apis/app/page.tsx

```typescript
import Image from "next/image";

export default function Home() {
  return (
    <div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
      <main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
        <Image
          className="dark:invert"
          src="/next.svg"
          alt="Next.js logo"
          width={100}
          height={20}
          priority
        />
        <div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
          <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
            To get started, edit the page.tsx file.
          </h1>
          <p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
            Looking for a starting point or more instructions? Head over to{" "}
            <a
              href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
              className="font-medium text-zinc-950 dark:text-zinc-50"
            >
              Templates
            </a>{" "}
            or the{" "}
            <a
              href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
              className="font-medium text-zinc-950 dark:text-zinc-50"
            >
              Learning
            </a>{" "}
            center.
          </p>
        </div>
        <div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
          <a
            className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
            href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
            target="_blank"
            rel="noopener noreferrer"
          >
            <Image
              className="dark:invert"
              src="/vercel.svg"
              alt="Vercel logomark"
              width={16}
              height={16}
            />
            Deploy Now
          </a>
          <a
            className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
            href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
            target="_blank"
            rel="noopener noreferrer"
          >
            Documentation
          </a>
        </div>
      </main>
    </div>
  );
}

```

### multiagents/app/page.tsx

```typescript
"use client";

import { useState, useRef, useEffect } from "react";

type ToolCall = { name: string; input?: unknown };
type Message = { role: "user" | "assistant"; content: string; toolCalls?: ToolCall[] };

export default function Home() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");
  const [loading, setLoading] = useState(false);
  const [toolBadge, setToolBadge] = useState<string | null>(null);
  const bottomRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    bottomRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages, toolBadge]);

  async function loadDemo() {
    try {
      const res = await fetch("/api/demo");
      const data = await res.json();
      if (Array.isArray(data.messages)) setMessages(data.messages);
    } catch {
      setMessages([
        { role: "user", content: "I want to eat healthier but I don't know where to start." },
        { role: "assistant", content: "I'd love to help! To give you ideas that fit your life, tell me: any dietary restrictions? Budget per week? Cooking skill? And anything you've been feeling (e.g. tired) — it can shape what we focus on." },
      ]);
    }
  }

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    const text = input.trim();
    if (!text || loading) return;

    const userMessage: Message = { role: "user", content: text };
    setMessages((m) => [...m, userMessage]);
    setInput("");
    setLoading(true);
    setToolBadge(null);

    try {
      const res = await fetch("/api/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          messages: [...messages, userMessage].map((m) => ({
            role: m.role,
            content: m.content,
          })),
        }),
      });

      const data = await res.json();

      if (data.toolCalls?.length) {
        setToolBadge(
          `Used: ${data.toolCalls.map((t: { name: string }) => t.name).join(", ")}`
        );
      }

      setMessages((m) => [
        ...m,
        {
          role: "assistant",
          content: data.text || data.error || "No response.",
          toolCalls: data.toolCalls?.length ? data.toolCalls : undefined,
        },
      ]);
    } catch {
      setMessages((m) => [
        ...m,
        { role: "assistant", content: "Network error. Please try again." },
      ]);
    } finally {
      setLoading(false);
      setToolBadge(null);
    }
  }

  return (
    <main className="min-h-screen flex flex-col max-w-2xl mx-auto px-4 py-8">
      <header className="mb-6 flex items-start justify-between gap-4">
        <div>
          <h1 className="text-2xl font-semibold text-amber-900">Mahm</h1>
          <p className="text-sm text-amber-800/80">
            Make At Home Mmmm — your AI nutritionist & personal shopper
          </p>
        </div>
        <button
          type="button"
          onClick={loadDemo}
          className="shrink-0 rounded-lg border border-amber-300 bg-amber-50 px-3 py-1.5 text-xs font-medium text-amber-800 hover:bg-amber-100"
        >
          Load demo backup
        </button>
      </header>

      <div className="flex-1 overflow-y-auto space-y-4 mb-4">
        {messages.length === 0 && (
          <p className="text-gray-500 text-sm">
            Example: &quot;I&apos;m vegetarian, $80/week, trying to lose weight. What should I eat?&quot;
          </p>
        )}
        {messages.map((m, i) => (
          <div
            key={i}
            className={
              m.role === "user"
                ? "ml-auto max-w-[85%] bg-amber-700 text-white rounded-2xl rounded-tr-sm px-4 py-2"
                : "mr-auto max-w-[85%] bg-white border border-amber-200 rounded-2xl rounded-tl-sm px-4 py-2 shadow-sm"
            }
          >
            <p className="whitespace-pre-wrap text-sm">{m.content}</p>
            {m.role === "assistant" && m.toolCalls?.length ? (
              <p className="mt-2 text-xs text-amber-700/80">
                Tools used: {m.toolCalls.map((t) => t.name).join(", ")}
              </p>
            ) : null}
          </div>
        ))}
        {loading && (
          <div className="mr-auto max-w-[85%] bg-white border border-amber-200 rounded-2xl rounded-tl-sm px-4 py-2 shadow-sm">
            {toolBadge ? (
              <p className="text-xs text-amber-700">{toolBadge}</p>
            ) : (
              <p className="text-sm text-gray-500">Mahm is thinking…</p>
            )}
          </div>
        )}
        <div ref={bottomRef} />
      </div>

      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Ask Mahm anything…"
          className="flex-1 rounded-xl border border-amber-200 bg-white px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-amber-400"
          disabled={loading}
        />
        <button
          type="submit"
          disabled={loading || !input.trim()}
          className="rounded-xl bg-amber-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-amber-700 disabled:opacity-50"
        >
          Send
        </button>
      </form>
    </main>
  );
}

```

### ui/src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Nunito, Baloo_2, Geist_Mono } from "next/font/google";
import { AuthProvider } from "@/contexts/AuthContext";
import { MahmProvider } from "@/contexts/MahmContext";
import "./globals.css";

const nunito = Nunito({
  variable: "--font-nunito",
  subsets: ["latin"],
  weight: ["400", "500", "600", "700", "800"],
});

const baloo2 = Baloo_2({
  variable: "--font-baloo",
  subsets: ["latin"],
  weight: ["400", "500", "600", "700", "800"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Mahm | Your AI Nutritionist & Meal Planner",
  description: "Like having a mom who's also a nutritionist and a personal shopper. Get personalized meal recommendations, find local ingredients, and plan your whole week.",
  keywords: ["meal planning", "nutrition", "AI", "grocery", "recipes", "healthy eating"],
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${nunito.variable} ${baloo2.variable} ${geistMono.variable} font-sans antialiased`}
      >
        <AuthProvider>
          <MahmProvider>
            {children}
          </MahmProvider>
        </AuthProvider>
      </body>
    </html>
  );
}

```

[83 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]