A Telegram bot and Mini App that track your household's grocery pantry, turn receipts into structured items, plan meals, and remind everyone before food expires. It supports Anthropic, OpenAI, Gemini, and DeepSeek, shared households, Telegram Stars subscriptions, and English, Chinese, French, and Spanish.
Self-hosted deployments can also connect Slack and WhatsApp through the deployment owner's connection setup console. Connections share the same pantry workflows and household permissions. Telegram remains the entry point for the Mini App and Stars payments.
Use the live service at @foodie_manager_bot. It is the easiest way to try receipt scanning, shared household pantries, meal planning, expiry reminders, and the Telegram Mini App without running a server. Start on the free plan, then invite your household or upgrade with Telegram Stars when you need more receipts, AI actions, or member seats.
The hosted Mini App is served from pantry-manager-production.up.railway.app, but account access is authenticated by Telegram—open it through the bot's Open app button. See the hosted service guide for share-ready promotional copy and onboarding instructions.
-
Receipt photo → pantry items: Send a photo to the bot. The configured LLM recognizes the store brand separately from food items, extracts estimated shelf lives, and stores the receipt and items in SQLite.
-
Daily digest: Each morning at your configured hour, you receive a message listing everything expiring within 7 days, with one-tap buttons to mark items as eaten, tossed, snoozed, or moved to the fridge/freezer.
-
Interactive pantry browsing:
/pantryopens the full active list or a single item card. Choose Receipt, Store, Category, or Expiry to organize it. Select several items to mark them eaten, tossed, or removed together, with a confirmation screen.Store recognition uses the receipt header or logo; include it in the photo.
/pantry storegroups purchases from the same store across receipts. Older receipts without store metadata and manually added food appear under Unknown store. The Mini App's Open pantry offers the same controls. Choose Select items, tap individual foods or Select this page, then choose Ate, Tossed, or Remove and confirm. Selections support up to 100 items across pages and expire after 30 minutes. Items already updated by another action are skipped; unselected foods remain active./pantry batchopens the selection screen directly, including for large inventories that exceed Telegram's message length limit. -
Storage-aware shelf life: Moving an item to the fridge or freezer resets its shelf-life clock from the date it was stored (one-way: default → fridge → frozen), using a curated USDA table with web-search and cache fallback.
-
Shelf life learning: When you apply a
/correctproposal, that correction can teach future imports of the same item. -
Manual add: Use
/addfor items you didn't receive a receipt for. The bot proposes parsed items before inserting them. -
Recipes from your pantry:
/cooksuggests a recipe built from what's about to expire, respecting your food profile (/prefs); save favorites and build a shopping list for what's missing. -
Meal planning and feedback:
/plan 3through/plan 7creates a multi-day dinner plan,/calendarexports it, and cooked actions plus/historykeep pantry quantities and meal history current. -
Natural conversation: Tell the bot “bought milk and eggs,” “remove milk from my shopping list,” “correct yogurt to expire Friday,” or “how long does salmon keep?” instead of memorizing every command.
-
Shared households and groups:
/invitemembers to share pantry, shopping, quota, and plans; in hosted mode,/bindlets the same household operate safely from a group chat. -
Mini App and billing: The Telegram Mini App provides account settings, quota visibility, Family-plan checkout, top-ups, and subscription management using Telegram Stars.
-
Flexible provider funding: Operators can keep providers on metered API keys or route individual providers through an existing Sub2API-backed subscription without restarting the bot.
- Python 3.12+
- uv
- Node.js and npm (only needed for a native Mini App build; Docker includes it)
- Or Docker Desktop / Docker Engine with Compose
- A Telegram bot token — create one via
@BotFather - An API key for at least one LLM provider capable of reading receipt photos: Anthropic, OpenAI, or Gemini (DeepSeek can't read photos and can't be the sole provider)
- Your Telegram user ID — get it from
@userinfobot
# 1. Copy .env.example to .env and fill in the Telegram fields plus one
# image-capable provider key.
# 2. Build and start the complete stack.
docker compose up --build -d
# 3. Check readiness and follow startup logs.
docker compose ps
docker compose logs -f pantry-manager
Open http://localhost:8000/healthz and expect {"ok": true}, then send
/start to the bot from the Telegram account in
ALLOWED_TELEGRAM_USER_ID. The named Docker volume preserves the SQLite
database across restarts.
Local mode is intentionally single-user and billing-free. Compose forces
HOSTED_FEATURES_ENABLED=false, OPEN_REGISTRATION=false, and
BILLING_ENABLED=false, so it does not expose household invites, public
registration, quotas, plans, checkout, or subscription UI. Pantry tracking,
receipt ingest, reminders, recipes, meal planning, and personal settings remain
available.
For native Windows, macOS, and Linux instructions, direct docker build /
docker run commands, Mini App HTTPS setup, and troubleshooting, see
docs/local-setup.md.
/correct <id> <free text>and/add <free text>parse with the configured text model and reply with a diff message. Tap Apply to commit or Cancel to discard. Proposals expire after 10 minutes./llm [anthropic|openai|gemini|deepseek]shows or changes the per-user LLM provider. DeepSeek still can't read receipt photos — photo calls fall back to a capable provider automatically — but does have native web search.- Any mutation to a pantry item (mark eaten / tossed / removed / snoozed / corrected / moved to fridge or freezer) invalidates pending corrections for that item in the same transaction.
/statsreports receipt accuracy, corrections, waste, estimated savings, cooked-meal follow-through, and LLM cost over the last 30 days./lang [en|zh|fr|es]sets your language; every household member can pick their own — the underlying data stays in English and is translated per message. Receipt recognition requests item names in all four languages and caches them for pantry views and digests. Units and item details follow the selected language. Mini App receipt result cards also update when you change your saved language, without rescanning the receipt.
Production commits receive automatic major.minor.patch version tags after CI
passes. GitHub releases are batched weekly; see versions and releases.
# Run all tests
uv run pytest
# Run all tests and report slow setup/call/teardown phases (same command as CI)
uv run pytest -q --durations=15
# Run the same static checks used by CI
uv run ruff check app tests bin migrations
uv run pyright app bin
# Audit runtime dependencies pinned in uv.lock
uv export --no-dev --no-emit-project --no-hashes --output-file requirements-audit.txt
uv run pip-audit --requirement requirements-audit.txt --strict --progress-spinner off
rm requirements-audit.txt
# Run a single test file
uv run pytest tests/test_core_services.py
# Run a single test by name
uv run pytest tests/test_core_services.py::test_shelf_life_defaultsEvery test still runs. Database-heavy tests copy an empty schema into their own temporary SQLite file; data, connections, commits, and rollbacks stay isolated. Migration tests continue to run real upgrades and downgrades from scratch. See test performance for measurements and fixture guidance.
- Create or import the project in Railway, then connect the service source to
awbjcj/pantry-manageron themasterbranch with automatic deploys enabled. - Add a persistent volume named
food-datamounted at/data. - Set Railway service variables matching
.env.example. - Push or merge to
master. Railway builds and deploys the new revision automatically; GitHub Actions runs the independent CI quality gates.
The container runs bin/run.py which backs up the database, runs Alembic migrations, registers per-user digest cron jobs, then starts long-polling.
For a rollback, use the Railway service's Deployments view to restore a previous successful deployment. This intentionally does not downgrade SQLite migrations; restore a database backup separately if a migration itself must be reverted.
| Command | Description |
|---|---|
| Send a photo | Parse a receipt and log all food items |
/add 2 lb chicken, dozen eggs |
Propose manual items without a receipt |
/list |
Show all active pantry items |
/list dairy |
Filter by category |
/list week |
Show items expiring within 7 days |
/list expired |
Show already-expired items |
/pantry [receipt|store|category|expires|batch|digest|<id>] |
Interactive pantry with store grouping and batch status controls |
/correct <id> <free text> |
Propose a natural-language correction |
/delete <id> |
Remove a wrongly imported item (does not teach future imports) |
/digest_at 7 |
Set your daily digest hour (0–23, in your timezone) |
/tz America/New_York |
Set your timezone |
/lang [en|zh|fr|es] |
Show or set your language |
/stats |
Show pantry statistics |
/llm [anthropic|openai|gemini|deepseek] |
Show or switch the LLM provider |
/prefs [sentence] |
Show learned personal/household food preferences or update household dietary settings |
/cook |
Get a recipe built from your pantry |
/history |
Show meals your household cooked |
/plan [3-7] |
Create a 3–7 day dinner plan (default 5 days) |
/calendar |
Export the active dinner plan as an .ics calendar file |
/shopping |
View your to-buy list; tap an item once bought |
/favorites |
View saved recipes; tap to re-cook against your current pantry |
/invite [family] |
Invite one person (or family for a reusable link) to your household |
/join <code> |
Join a household you were invited to |
/bind |
Bind the current hosted group chat to your household |
/household |
List household members |
/leave |
Leave your household |
/remove <id> |
(owner) Remove a member from your household |
/quota |
Show pooled household receipt and AI-action usage |
/buy |
Buy the Family plan or a quota top-up with Telegram Stars |
/billing |
Show the current subscription status |
/help |
Show all commands |
/prefs (also available in the Mini App) shows your personal food preferences
and the household's pooled preferences alongside the explicit dietary profile.
Personal history uses the member who marks an item eaten or tossed; it does not
infer who physically ate it. Individual actions, batch updates, and cooked-meal
confirmations all contribute automatically.
Food eaten strictly before expiry gets a positive signal equal to the fraction of its shelf life remaining: eating with 80% remaining is a stronger like than eating with 20% remaining. Tossing strictly before expiry contributes -1. Actions on or after expiry, removals, and invalid date ranges are neutral. Fridge/frozen items use the date they entered their current storage state. Each food's score is the sum of signals divided by the number of non-neutral signals plus two neutral prior observations, so repeated history strengthens the evidence while mixed likes/dislikes offset each other.
Household preferences softly influence /cook and /plan ranking and recipe
prompts; they never become dietary exclusions. Profiles rebuild from persistent
outcome snapshots, preserving the expiry known when the action happened. Older
eaten/tossed records have no action date or actor and remain unscored rather than
inventing those facts. The new history starts after migration 0023_pantry_outcome.
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
Yes | — | Bot token from @BotFather |
ALLOWED_TELEGRAM_USER_ID |
Yes | — | Bootstrap and default operator identity |
OPEN_REGISTRATION |
No | false |
Allow new Telegram users to provision free households; existing members and invites still work when false |
HOSTED_FEATURES_ENABLED |
No | false |
Enables deployment-only multi-tenant households, registration, quota/plan surfaces, and Stars billing; keep false for localhost |
BILLING_ENABLED |
No | false |
Enforce quota and expose Stars checkout; usage is still recorded when false |
WEB_APP_URL |
No | — | Public HTTPS root URL for the Telegram Mini App; when set, startup installs the bot's Open app menu button |
PORT |
No | 8000 |
HTTP port for the Mini App, API, and /healthz |
INGEST_PROVIDER |
No | first configured | Image-capable provider pinned for receipt ingest, independent of /llm; automatic preference is Gemini, then OpenAI, then Anthropic |
OPERATOR_TELEGRAM_IDS |
No | ALLOWED_TELEGRAM_USER_ID |
Comma-separated IDs allowed to use the private operator bot; admin authority is deployment-controlled, not a customer role |
OPERATOR_BOT_TOKEN |
No | — | Token for the optional, separate operator bot |
LLM_PROVIDER |
No | anthropic |
Default provider: anthropic, openai, gemini, or deepseek. Each user can override with /llm. deepseek is text-only, so at least one image-capable provider's key must also be set |
ANTHROPIC_API_KEY |
When using anthropic | — | Anthropic API key |
ANTHROPIC_MODEL |
No | claude-sonnet-5-5 |
Claude model to use for receipt parsing |
ANTHROPIC_TEXT_MODEL |
No | claude-haiku-5-5 |
Claude model to use for /correct and /add proposals |
ANTHROPIC_SEARCH_MODEL |
No | claude-sonnet-5-5 |
Claude model used for shelf-life web search — requires web search enabled on the Anthropic workspace |
OPENAI_API_KEY |
When using openai | — | OpenAI API key |
OPENAI_MODEL |
No | gpt-6.1-terra |
OpenAI model to use for receipt parsing, cook, and search |
OPENAI_TEXT_MODEL |
No | gpt-6.1-terra |
OpenAI model to use for /correct and /add proposals |
GEMINI_API_KEY |
When using gemini | — | Google Gemini API key (native google-genai SDK) |
GEMINI_MODEL |
No | gemini-3.8-flash |
Gemini model to use for receipt parsing |
GEMINI_TEXT_MODEL |
No | gemini-3.8-flash |
Gemini model to use for /correct and /add proposals |
DEEPSEEK_API_KEY |
When using deepseek | — | DeepSeek API key for text and standalone web search; receipt photos use another provider |
DEEPSEEK_MODEL |
No | deepseek-v4-pro |
DeepSeek model for text and search tasks: V4 Pro (deepseek-v4-pro) or V4.1 Flash (deepseek-flash) |
DEEPSEEK_BASE_URL |
No | https://api.deepseek.com |
DeepSeek API base URL (OpenAI-compatible) |
SUB2API_BASE_URL |
No | — | Shared Sub2API gateway root; HTTPS required except for loopback HTTP in ENV=dev |
SUB2API_ANTHROPIC_TOKEN |
No | — | Anthropic subscription-routing token; makes subscription mode the default for Anthropic |
SUB2API_OPENAI_TOKEN |
No | — | OpenAI subscription-routing token |
SUB2API_GEMINI_TOKEN |
No | — | Gemini subscription-routing token |
SUB2API_DEEPSEEK_TOKEN |
No | — | DeepSeek subscription-routing token |
SPOONACULAR_API_KEY |
No | — | Optional Spoonacular key; /cook and /plan otherwise use TheMealDB plus the configured LLM recipe source |
COOK_COST_CEILING_MICROS |
No | 100000 |
Per-/cook LLM spend ceiling in micro-USD ($0.10); raise if recipes come back empty |
PLAN_COST_CEILING_MICROS |
No | 150000 |
Per-/plan LLM spend ceiling in micro-USD ($0.15); raise if week plans come back empty |
DATABASE_PATH |
No | ./food.db |
Path to the SQLite database file |
LOG_LEVEL |
No | INFO |
Logging level |
ENV |
No | dev |
Set to prod for JSON-structured logs |
The current model defaults and first-party Standard API token prices (USD per million tokens; OpenAI/Claude checked October 28, 2026, Gemini September 22) are:
| Model | Use | Input | Output |
|---|---|---|---|
claude-sonnet-5-5 |
Claude receipt, cook, and search (default) | $2.00 | $10.00 |
claude-opus-5-5 |
Optional Claude override | $4.00 | $20.00 |
claude-haiku-5-5 |
Claude text tasks (up to 100K input tokens) | $0.10 | $0.50 |
claude-haiku-5-5 |
Claude text tasks (over 100K input tokens) | $0.50 | $2.50 |
gpt-6.1-terra |
All OpenAI tasks, including text | $1.00 | $5.00 |
gpt-6.1-sol |
Optional OpenAI override | $2.00 | $10.00 |
gemini-3.8-flash |
Gemini receipt, cook, search, and text | $0.75 | $3.75 |
These are ordinary input and output rates. GPT-6 rates above apply up to 272K input tokens; longer prompts have higher rates. Gemini 3.8 Flash has introductory rates through December 31, 2026. Haiku 5.5 rates were checked October 28, 2026; its 100K threshold includes cached input and changes the rates for the entire request. Text calls retain its default adaptive thinking at medium effort, with an 8,192-token output ceiling for thinking and visible output. Cache and search charges are separate. See OpenAI pricing, Claude pricing, and Gemini pricing.
GPT-6.1 Terra's model contract and Standard prices were checked October 28, 2026.
Main-model OpenAI calls use Responses with reasoning={"effort": "low"} and an 8,192-token
total output ceiling, including reasoning. Sampling controls are omitted.
Terra estimates account for reported cache reads ($0.05/M), 30-minute writes
($1.25/M), and 12-hour writes ($2.50/M); above 272K input tokens, input/cache
rates double and output costs $7.50/M for the full request. Missing cache details
use ordinary-input estimates. These first-party estimates do not establish
gateway billing or live model access. See the
Terra model page.
Claude defaults to Sonnet 5.5 for receipt parsing, cooking, and search. Opus 5.5
is optional: set ANTHROPIC_MODEL and/or ANTHROPIC_SEARCH_MODEL to
claude-opus-5-5 to select it. Haiku 5.5 remains the default for text tasks.
Both OPENAI_MODEL and OPENAI_TEXT_MODEL now default to Terra. Sol is an
explicit override with twice Terra's Standard token/cache rates. Known older
OpenAI and Claude environment values are upgraded by the application at startup;
the same policy applies when building Agno agents. For example, old Luna IDs map
to Terra, and older Claude Sonnet/Haiku/Opus IDs map to the corresponding 5.5
model. See the replacement map. Custom gateway model IDs
remain configurable. Stored usage/cost history and dated design records are
preserved. This is an application retirement policy, not a vendor shutdown claim.
Opus and Sonnet 5.5 receipt parsing uses automatic tool selection and also accepts
validated JSON text, following Claude's tool-choice contract.
DeepSeek pricing (checked October 18, 2026, USD per million tokens):
| Model | Cache hit, off-peak / peak | Cache miss, off-peak / peak | Output, off-peak / peak |
|---|---|---|---|
deepseek-flash |
$0.003 / $0.006 | $0.15 / $0.30 | $0.60 / $1.20 |
deepseek-v4-pro |
$0.022 / $0.044 | $0.66 / $1.32 | $1.98 / $3.96 |
The legacy deepseek-v4-flash and deepseek-v4-flash-vision-exp IDs bill at
Flash rates. Estimates use the response timestamp: weekday peak windows are
01:00–04:00 and 06:00–10:00 UTC. Other hours and weekends use off-peak rates;
Chinese public holidays also qualify for off-peak, but the bot conservatively
estimates peak-window holiday requests at peak rates. A missing timestamp uses
peak rates. Cached input is subtracted from ordinary input before pricing;
reasoning tokens are already included in billed output.
DeepSeek search uses the standalone POST /v1/tools/search API through function
tools on both models, with at most three searches per task. It charges $0.75 per
1,000 HTTP 200 requests with non-empty results; empty results and failures add no
search fee. These fees accrue alongside tokens from every model turn. The
November 1 billing start described for Harness/Claude Code/Codex server tools
does not defer standalone API charges. Built-in Responses web_search works
only with Pro and is ignored by Flash. Text/search tool turns send
reasoning={"effort": "low"} with max_output_tokens=9216, a hard ceiling
covering both reasoning and visible output, rather than a thinking-only budget.
Full reasoning output is replayed in memory between tool turns.
Search is requested with tool_choice="auto": DeepSeek rejects forced
required/named tool choices while thinking is enabled. Shelf-life lookup
returns unknown unless a successful search produced evidence. See the official
Search guide,
Responses compatibility, and
DeepSeek pricing.
Stars subscriptions renew every 30 days. Household owners can cancel renewal
from the Mini App; top-ups expire at the end of the current quota period.
For the complete purchase, verification, top-up, and cancellation flow, see
docs/telegram-subscriptions.md.
docs/adr/— architecture decision recordsdocs/operations.md— running and supervising the bot in productiondocs/local-setup.md— native and Docker setup for Windows, macOS, and Linuxdocs/hosted-service.md— live bot onboarding and promotional copydocs/telegram-subscriptions.md— user guide for Stars subscriptions and top-upsdocs/superpowers/— the spec + plan for every shipped version, in orderCONTEXT.md— domain glossary (ubiquitous language) used across the codebaseCLAUDE.md/AGENTS.md— architecture and conventions reference for AI coding agents working in this repo
This is a solo-maintained personal project, built and documented heavily with
AI coding agents. Issues and pull requests are welcome — for anything beyond a
small fix, please open an issue first to discuss the change. Run
uv run pytest, uv run ruff check app tests bin migrations, and
uv run pyright app bin before submitting a PR; CI runs the same checks.