{"id":50885941,"url":"https://github.com/kbennett2000/recipe-machine","last_synced_at":"2026-06-15T17:01:36.227Z","repository":{"id":359226817,"uuid":"1245086903","full_name":"kbennett2000/recipe-machine","owner":"kbennett2000","description":"Self-hosted recipe library that parses markdown files into a searchable, scalable, shopping-list-generating web app.","archived":false,"fork":false,"pushed_at":"2026-05-21T00:50:36.000Z","size":185,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-21T05:29:06.069Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kbennett2000.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-20T22:37:17.000Z","updated_at":"2026-05-21T00:50:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kbennett2000/recipe-machine","commit_stats":null,"previous_names":["kbennett2000/recipe-machine"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/kbennett2000/recipe-machine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbennett2000%2Frecipe-machine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbennett2000%2Frecipe-machine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbennett2000%2Frecipe-machine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbennett2000%2Frecipe-machine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kbennett2000","download_url":"https://codeload.github.com/kbennett2000/recipe-machine/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbennett2000%2Frecipe-machine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34372130,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-15T17:01:35.412Z","updated_at":"2026-06-15T17:01:36.217Z","avatar_url":"https://github.com/kbennett2000.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Recipe Machine\n\n\u003e A self-hosted recipe library that turns a directory of markdown\n\u003e files into a searchable, scalable, shopping-list-generating,\n\u003e editable web app. Designed for the home LAN.\n\n![Home page](docs/screenshots/home.png)\n\n## What this is\n\nRecipe Machine started as a personal pain point. A few dozen\nrecipes lived as markdown in a private repo — perfect for editing\nin `$EDITOR` and version-controlling, but unfriendly for any other\nuse: no scaling for guests, no shopping list when meal-planning,\nno full-text search, no \"what was that bread I made last fall.\"\nBrowsers want HTML, not folders of `.md` files.\n\nSo this is the smallest possible thing that fixes that: a\nsingle-container web app that **treats the markdown files as the\nsource of truth** and the SQLite database as a cache built from\nthem. v1.1 added a form-based web editor so you can also create,\nedit, and delete recipes without leaving the browser — but the\neditor writes to the same `.md` files. The DB never owns anything\nthe markdown doesn't.\n\nWhere the rules-based ingredient parser falls down — and recipes\nin the wild are full of edge cases — there's an\n[opt-in LLM fallback](docs/llm-fallback.md) that routes the\nremainder through Claude Haiku, caches results forever, and never\nphones home at request time.\n\n## What it does\n\n- **Browse** by category, search across titles + ingredients +\n  method + notes (FTS5).\n- **Scale** any recipe up or down. The math respects fractions,\n  rounds count-nouns (\"3 eggs × 1.5 → ~5 eggs\"), and the same\n  formatter runs in both PHP and JS — verified by a parity test.\n- **Shopping list** across multiple recipes at once with\n  aggregation by aisle (produce, dairy, baking, pantry, …),\n  unit-class conversion, and a shareable URL.\n- **Cooking mode** — distraction-free big-text view, tappable\n  timer phrases that start countdowns, Wake Lock to keep the\n  screen on, sessionStorage-backed step bookmark.\n- **Cross-linking** — explicit `[[recipe-slug]]` references plus\n  auto-detection of bare titles in notes prose, plus a Jaccard-\n  similarity \"Similar recipes\" section, plus an [/recipes index\n  page](docs/screenshots/recipes-index.png) that visualizes the\n  whole cross-link graph for the maintainer.\n- **Editing** — create new recipes or edit existing ones in a\n  form-based editor with live preview, sortable ingredient rows,\n  sub-groups, and a raw-markdown mode for power users. Unparsed\n  lines surface in a \"Verify these\" section with a one-click\n  Convert-to-structured action. See\n  [docs/editing.md](docs/editing.md) for the workflow.\n\n## Screenshots\n\n### Recipe detail page — Hawaiian Rolls\n![Recipe detail — Hawaiian Rolls](docs/screenshots/hawaiian-rolls.png)\n\nTimer phrases and temperatures get visually distinct pills.\nIngredients sub-group by `### Headers`. Sections that aren't\npresent (libation, notes) just don't render.\n\n### Cooking mode with a running timer\n![Cooking mode — Hawaiian Rolls step 19 with a 20-minute timer running](docs/screenshots/cooking-mode.png)\n\nOne step at a time, large type. Tap a timer phrase to start it;\nthe active-timers stripe shows the countdown across step\nnavigation. Wake Lock keeps the phone screen on; sessionStorage\nremembers where you were if the page reloads.\n\n### Shopping list — three recipes aggregated\n![Shopping list — 3 recipes aggregated by aisle](docs/screenshots/shopping-list.png)\n\nAdd multiple recipes, optionally at different scales, and they\naggregate into one ordered list grouped by aisle. The \"(Apple Pie,\nHoney Oat Bread)\" tag in parentheses shows which recipes\ncontributed each line.\n\n### Cross-link index page\n![/recipes index showing cross-link graph](docs/screenshots/recipes-index.png)\n\nA maintainer's view of every recipe and its outgoing/incoming\ncross-references. Useful for spotting \"isolated\" recipes that\ncould use a `[[ref]]`.\n\n### Search results\n![Search results for 'bread'](docs/screenshots/search.png)\n\nFTS5 across title, ingredient lines, method text, and notes.\nSub-second on this corpus.\n\n### Form-mode editor with live preview\n![Form-mode editor — Honey Oat Bread](docs/screenshots/p11e-form-honey-oat.png)\n\nEvery field is bound to a live preview pane on the right. Reorder\ningredient rows by drag, toggle to raw markdown for power-user\nedits, save back to the same `.md` file.\n\n### Creating a new recipe with live slug derivation\n![New recipe form with slug preview](docs/screenshots/p11g-slug-preview.png)\n\nType a title; the slug auto-derives below it\n(`My Sandwich Bread → my-sandwich-bread`). The slug is editable\npre-save and becomes immutable after — slug-as-filename is the\nstable identity for cross-references.\n\n## Architecture\n\n```\n                                        Web UI (browse, search, cook)\n                                                ↑\nrecipes/*.md  →  RecipeParser  →  SQLite cache (FTS5 + see-also)\n   (canonical)    (rules-first)            ↑\n       ↑               ↓                   │\n       │           IngredientLLMParser  →  ingredient_llm_cache\n       │           (Phase 9, opt-in)        (hits permanent,\n       │                                     misses 30-day TTL)\n       │\n       └──  Web editor  →  RecipeFileWriter  →  RecipeReindexer\n            (form/raw)     (atomic write)      (single-recipe DB update)\n```\n\n- **Parser-first**: every recipe goes through `RecipeParser` first.\n  Rules-based, deterministic, no external dependencies. Most lines\n  parse cleanly.\n- **LLM fallback**: lines the rules-based parser can't structure\n  (section headers, parentheticals, unconventional phrasings) get\n  routed to Claude Haiku as a batch. Results live forever in\n  `ingredient_llm_cache`; misses tombstone for 30 days so future\n  model improvements get picked up. The fallback is **indexer-only**\n  — no live API calls during page rendering.\n- **Editor as another writer**: the web editor (v1.1) goes through\n  the same `RecipeFileWriter` (atomic rename, slug validation,\n  containment check) as a hypothetical CLI importer would. After a\n  save, `RecipeReindexer::reindexOne` surgically updates just that\n  recipe's DB slice — no full corpus rebuild. The `.md` file on\n  disk remains the source of truth; deleting `database.sqlite` and\n  running `make reindex` reproduces the entire DB from the markdown.\n- **Cache, not source**: `make reindex` truncates and rebuilds the\n  whole SQLite database from the markdown. You can delete\n  `database/database.sqlite` and lose nothing material.\n- **Self-hosted**: one Docker container, one SQLite file, the\n  recipes/ directory mounted in. No external services required\n  unless you opt in to the LLM fallback.\n\nMore detail: [docs/llm-fallback.md](docs/llm-fallback.md) for the\nLLM architecture, [docs/recipe-format.md](docs/recipe-format.md)\nfor the markdown spec.\n\n## Running it yourself\n\nThe intended deployment is your own LAN — a Raspberry Pi, a NAS, a\nspare laptop, whatever can run Docker. There's no public-deployment\nstory; the app has no auth and treats every visitor as\nfully-privileged.\n\n**Prerequisites:** Docker + Docker Compose.\n\n**First boot:**\n\n```sh\ngit clone https://github.com/kbennett2000/recipe-machine.git\ncd recipe-machine\ncp .env.example .env\ntouch database/database.sqlite     # required for the bind mount\nmake rebuild                       # build + boot + migrate\nmake reindex                       # parse recipes/ into the cache\n```\n\nOpen \u003chttp://localhost:8000\u003e. To access from other devices on the\nLAN, replace `localhost` with the host's LAN IP (e.g.\n`http://192.168.1.42:8000`).\n\n**Changing the port:** the host-facing port is configurable via\n`APP_PORT` in `.env` — set e.g. `APP_PORT=8080` and re-run\n`make dev` (or `docker compose up -d`). The container-internal\nport stays at 8000.\n\n**Offline operation:** once the image is built and the recipes\nare indexed, Recipe Machine runs **fully offline** on your LAN —\nno outbound requests, no CDN dependencies, no telemetry. Fonts\n(Fraunces, Inter) are self-hosted in [public/fonts/](public/fonts/);\nJS/CSS are bundled into the image at build time; the SQLite cache\nis local. The **only** feature that talks to the internet is the\noptional LLM ingredient-parser fallback, and only when you\nexplicitly run `php artisan recipes:reindex --with-llm`. Leave\n`RECIPE_MACHINE_LLM_FALLBACK=false` (the default) to disable it\nentirely. See [docs/llm-fallback.md](docs/llm-fallback.md).\n\n**Add a recipe:**\n\nTwo paths. The web editor (v1.1) is easier for one-offs:\n\n1. Open `http://localhost:8000/recipes/new` in a browser.\n2. Type a title, pick a category, fill in ingredients + method.\n3. Click \"Create recipe\" — the file lands at\n   `recipes/\u003ccategory\u003e/\u003cslug\u003e.md` and the index updates.\n\nOr the terminal-first workflow that still works exactly as it did\nin v1.0:\n\n```sh\n$EDITOR recipes/desserts/your-recipe.md\nmake reindex\n```\n\nThe markdown file on disk is the source of truth either way —\nthe editor is just another writer. See\n[docs/recipe-format.md](docs/recipe-format.md) for the markdown\nformat and [docs/editing.md](docs/editing.md) for the web editor\nworkflow.\n\n**Enable the LLM fallback (optional):**\n\nSet in `.env`:\n\n```env\nRECIPE_MACHINE_LLM_FALLBACK=true\nANTHROPIC_API_KEY=sk-ant-...\n```\n\nThen `docker compose exec app php artisan recipes:reindex --with-llm`.\n\nCost is ~$0.013 per 30-recipe corpus pass (Claude Haiku pricing).\nSubsequent reindexes hit the cache for free.\n\n## The recipe format\n\nFrontmatter + standard markdown sections. The\n[full spec](docs/recipe-format.md) covers the details, but the\nshort version is:\n\n```markdown\n---\ntitle: Honey Oat Bread\ncategory: breads\nslug: honey-oat-bread\nservings: \"1 loaf (~12 slices)\"\nyields: 12\ncook_time: \"40m\"\noven_temp: \"350F\"\nlibation: \"Semi-sweet mead—honey loves honey.\"\n---\n\n## Ingredients\n\n- 3 cups flour\n- 2 1/4 tsp instant yeast\n- 1 1/4 cups warm milk\n- Salt to taste\n\n## Method\n\n1. Knead, rise 1–1½ hours, shape into loaf pan, rise 45–60 min.\n2. Bake **35–40 minutes at 350°F**.\n```\n\nSub-groups (`### Pancakes`, `### Filling`) let you scope\ningredient lists to parts of a multi-component recipe. Cross-\nreferences via `[[other-slug]]` resolve to links if the slug\nexists.\n\n## Development\n\nSee [docs/dev-workflow.md](docs/dev-workflow.md) for the full\nloop. Quick reference:\n\n```sh\nmake test       # full PHPUnit suite (498 tests, ~10s)\nmake parity     # PHP↔JS formatter parity check\nmake reindex    # rebuild SQLite from recipes/\nmake fresh      # nuke + reindex everything\nmake shell      # bash inside the container\n```\n\n## Future work\n\nDocumented at [TODO.md](TODO.md). The big-ticket items past v1.1:\na synonym table for ingredient deduplication, jump-to-step picker\nfor long recipes, swipe gestures for mobile cooking mode, phone-\nspecific first-draft data-entry UX in the editor, and a few prompt\n+ cache refinements for the LLM fallback. Nothing blocking the\nday-to-day \"look up a recipe and cook it\" workflow.\n\n## Credits\n\nFonts (Fraunces, Inter) are SIL OFL and self-hosted under\n[public/fonts/](public/fonts/). Built collaboratively with Claude\nCode as the engineer and a separate Claude conversation as the\nproduct owner — see [docs/credits.md](docs/credits.md) for the\nfull attribution and the why behind that workflow.\n\n## License\n\n[MIT](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkbennett2000%2Frecipe-machine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkbennett2000%2Frecipe-machine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkbennett2000%2Frecipe-machine/lists"}