commit 638708bbc373add37a3eb5238d83a25f7346c820
Author: Chris Roberts <chris.roberts@learningunix.net>
Date: Thu, 2 Jul 2026 14:19:30 -0500
Initial project docs: CLAUDE.md conventions and SPEC.md plan
Diffstat:
| A | CLAUDE.md | | | 82 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | SPEC.md | | | 81 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 163 insertions(+), 0 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
@@ -0,0 +1,82 @@
+# CLAUDE.md — Recipe & Grocery Telegram Bot
+
+## Project Purpose
+
+A self-hosted Telegram bot for tracking recipes, managing a weekly staples
+list, and generating grocery lists. Runs as a long-polling service in an
+LXC/VM on Proxmox, alongside the rest of the homelab stack.
+
+## Dietary Constraints (hard requirements, not preferences)
+
+- **Ketogenic**: recipes/ingredients should be tagged `keto` automatically
+ based on carb content where determinable, but always allow manual override.
+- **Nut-free**: this is an allergy, not a preference. Any ingredient matching
+ the nut list below must hard-block the recipe from being marked nut-free,
+ and the bot should warn (not silently tag) when an ingredient is ambiguous
+ (e.g. "may contain tree nuts").
+ - Nut list: almond, walnut, pecan, cashew, pistachio, hazelnut, macadamia,
+ brazil nut, pine nut, peanut (technically a legume but treat as a nut
+ here). Keep this list in a single constant, not duplicated across files.
+
+## Tech Stack
+
+- Python 3.11+
+- `python-telegram-bot` (async, v20+) — long polling, no public webhook needed
+- SQLite via `sqlite3` stdlib or `sqlmodel` if an ORM is wanted — keep it
+ simple, this doesn't need SQLAlchemy's full weight
+- `systemd` service file for deployment (pattern-match the existing
+ `notify.sh` / MakeMKV LXC setup — same conventions for logging and restarts)
+
+## Data Model
+
+- `recipes(id, name, servings, instructions, source, is_keto, is_nut_free)`
+- `ingredients(id, recipe_id, name, quantity, unit, category)`
+- `staples(id, name, default_quantity, unit, category)`
+- `grocery_list_items(id, name, quantity, unit, category, source)`
+ - `source` is one of `staple`, `recipe`, `freeform` — keep provenance so
+ items can be intelligently merged/deduped and so `/clearlist` behavior
+ is predictable
+
+## Bot Commands (initial scope)
+
+- `/addrecipe` — conversational flow OR paste structured text/URL to parse
+- `/recipes [tag]` — list/filter recipes
+- `/staples` — show current staples list
+- `/staples add <item>` / `/staples remove <item>`
+- `/grocerylist [recipe1, recipe2, ...]` — staples + named recipes merged
+ into one list, quantities summed for matching items
+- `/add <item>` — freeform one-off add to the current grocery list
+- `/clearlist` — reset the working grocery list
+- `/random [tag]` — random recipe suggestion, optionally filtered
+
+## Merge Logic Rules
+
+- Same-name ingredients across recipes/staples/freeform sum their quantities
+ when units match; if units don't match, keep as separate line items rather
+ than guessing a conversion.
+- Category (produce, dairy, pantry, etc.) is used for grouping the final list
+ output, not for merge logic.
+
+## Code Style / Conventions
+
+- Match the conventions already used in Jake's other homelab projects:
+ small, readable functions over cleverness; explicit error handling;
+ Pushover notification on failures via the existing `notify.sh` pattern
+ where relevant (e.g. bot crash, DB write failure).
+- No unnecessary abstraction layers — this is a personal tool, not a
+ library. Prefer a flat file structure (`bot.py`, `db.py`, `models.py`,
+ `parsing.py`) over deep package nesting.
+- Config (Telegram bot token, DB path) via environment variables or a
+ `.env` file — never hardcoded, never committed.
+
+## Out of Scope (for now)
+
+- Web UI — SQLite backend should make this easy to bolt on later if wanted
+- Multi-user support — this is a single-household tool
+- Nutrition/macro calculation beyond keto/nut-free tagging
+
+## Testing
+
+- Unit tests for merge logic (the trickiest part) using `pytest`
+- Manual testing against a test Telegram bot token before pointing at the
+ real one
diff --git a/SPEC.md b/SPEC.md
@@ -0,0 +1,81 @@
+# Recipe & Grocery Bot — Project Spec
+
+## Overview
+
+A Telegram bot, self-hosted on the homelab, for tracking recipes with
+dietary tags (keto, nut-free), maintaining a weekly staples list, and
+generating merged grocery lists on demand.
+
+## Why Telegram over Slack
+
+Telegram's Bot API supports long polling, so the bot can run entirely
+inside the Tailscale network with no public-facing webhook, no reverse
+proxy, and no OAuth app-review process. Slack bots generally want a public
+HTTPS endpoint (or Socket Mode with more setup overhead) for the same
+result. Telegram is the lower-friction choice for a single-user homelab
+tool.
+
+## Setup Steps
+
+1. Create a bot via [@BotFather](https://t.me/BotFather) on Telegram, get
+ the bot token.
+2. Deploy target: new LXC or VM on Proxmox (or a lightweight container),
+ consistent with existing homelab conventions.
+3. `pip install python-telegram-bot sqlite-utils python-dotenv`
+4. Store the bot token in a `.env` file, excluded from version control.
+5. Initialize the SQLite DB from `models.py` on first run (auto-create
+ tables if they don't exist).
+6. Register a `systemd` unit for the bot process — restart on failure,
+ logs to journald (or wherever the rest of the stack logs to).
+7. Optional: wire a Pushover notification into `notify.sh` for bot crashes
+ or DB errors, matching the existing pattern.
+
+## Data Model
+
+See `CLAUDE.md` for full schema. Summary:
+
+| Table | Purpose |
+|---|---|
+| `recipes` | Name, servings, instructions, source, dietary flags |
+| `ingredients` | Per-recipe ingredient lines |
+| `staples` | Recurring weekly items with default quantities |
+| `grocery_list_items` | Current working list, tagged by source (staple/recipe/freeform) |
+
+## Command Reference
+
+| Command | Behavior |
+|---|---|
+| `/addrecipe` | Add a new recipe, conversationally or by pasting text |
+| `/recipes [tag]` | List recipes, optionally filtered by tag |
+| `/staples` | View the weekly staples list |
+| `/staples add <item>` | Add an item to staples |
+| `/staples remove <item>` | Remove an item from staples |
+| `/grocerylist [recipes...]` | Generate merged list: staples + named recipes |
+| `/add <item>` | Freeform add to the current grocery list |
+| `/clearlist` | Reset the working grocery list |
+| `/random [tag]` | Suggest a random recipe, optionally filtered |
+
+## Dietary Safety Notes
+
+Nut-free is treated as an allergy constraint, not a preference — the bot
+should never silently mark something nut-free if any ingredient is
+ambiguous. Keto tagging can be looser (auto-suggested, manually
+correctable).
+
+## Future Ideas (not in initial build)
+
+- Web UI reading from the same SQLite DB
+- Recipe URL parsing/import
+- Grocery list grouped by store section
+- Meal-planning calendar view
+
+## Handoff to Claude Code
+
+Point Claude Code at this directory with both `CLAUDE.md` and `SPEC.md`
+present. `CLAUDE.md` governs coding conventions and hard constraints;
+`SPEC.md` is the human-readable reference. Suggested first prompt:
+
+> Build the initial project structure per SPEC.md: SQLite schema, the
+> `/addrecipe`, `/recipes`, `/staples`, `/grocerylist`, `/add`, and
+> `/clearlist` commands, and a systemd unit file. Start with the DB layer
+> and merge logic, then wire up the bot commands.