pantrybase 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +12 -0
- package/.claude-plugin/plugin.json +27 -0
- package/LICENSE +21 -0
- package/README.md +153 -2
- package/bin/_resolve.sh +38 -0
- package/bin/pantrybase +20 -0
- package/bin/pb-server +13 -0
- package/dist/chunk-QUWSBZ2V.js +3429 -0
- package/dist/cli.js +441 -0
- package/dist/server.js +915 -0
- package/dist/web/assets/graph-qSs25SSD.js +3225 -0
- package/dist/web/assets/index-Dsc3USAJ.css +1 -0
- package/dist/web/assets/index-lHc1E7AV.js +1 -0
- package/dist/web/index.html +14 -0
- package/package.json +85 -4
- package/providers/generic/guide.md +13 -0
- package/providers/generic/profile.yaml +17 -0
- package/providers/ucook/guide.md +62 -0
- package/providers/ucook/profile.yaml +62 -0
- package/seed/ingredients.yaml +194 -0
- package/seed/vocabulary.yaml +277 -0
- package/skills/cook/SKILL.md +28 -0
- package/skills/ingest/SKILL.md +41 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Tag vocabulary. Copied to ./pantrybase/vocabulary.yaml on first use; edit that copy.
|
|
2
|
+
#
|
|
3
|
+
# mode: closed — only listed values are kept; unknown tags move to `other` (allergens are kept but flagged)
|
|
4
|
+
# mode: open — new values are accepted and reported so the vocabulary can grow
|
|
5
|
+
# synonyms map card wording to one value; parent builds a hierarchy (filtering on a parent matches children).
|
|
6
|
+
# Values are kebab-case. Matching ignores case, accents and punctuation.
|
|
7
|
+
|
|
8
|
+
diet:
|
|
9
|
+
mode: closed
|
|
10
|
+
values:
|
|
11
|
+
vegetarian: {synonyms: [veggie, veg, meat-free, meatless]}
|
|
12
|
+
vegan: {synonyms: [plant-based, plant based]}
|
|
13
|
+
vegan-adaptable: {synonyms: [vegan-option, can-be-vegan]}
|
|
14
|
+
pescatarian: {}
|
|
15
|
+
gluten-free: {synonyms: [gf]}
|
|
16
|
+
dairy-free: {synonyms: [df, lactose-free]}
|
|
17
|
+
low-carb: {synonyms: [carb-conscious, keto]}
|
|
18
|
+
high-protein: {synonyms: [protein-packed]}
|
|
19
|
+
low-calorie: {synonyms: [calorie-conscious, lean, light]}
|
|
20
|
+
halal: {}
|
|
21
|
+
family-friendly: {synonyms: [kid-friendly, family]}
|
|
22
|
+
|
|
23
|
+
protein:
|
|
24
|
+
mode: open
|
|
25
|
+
values:
|
|
26
|
+
chicken: {}
|
|
27
|
+
beef: {}
|
|
28
|
+
pork: {}
|
|
29
|
+
lamb: {}
|
|
30
|
+
turkey: {}
|
|
31
|
+
duck: {}
|
|
32
|
+
venison: {synonyms: [game]}
|
|
33
|
+
fish: {}
|
|
34
|
+
salmon: {parent: fish}
|
|
35
|
+
white-fish: {parent: fish, synonyms: [hake, cod, haddock]}
|
|
36
|
+
tuna: {parent: fish}
|
|
37
|
+
seafood: {}
|
|
38
|
+
prawns: {parent: seafood, synonyms: [shrimp]}
|
|
39
|
+
egg: {synonyms: [eggs]}
|
|
40
|
+
cheese: {synonyms: [halloumi, paneer]}
|
|
41
|
+
tofu: {}
|
|
42
|
+
tempeh: {}
|
|
43
|
+
legumes: {synonyms: [beans, pulses]}
|
|
44
|
+
chickpeas: {parent: legumes, synonyms: [chickpea]}
|
|
45
|
+
lentils: {parent: legumes, synonyms: [lentil]}
|
|
46
|
+
plant-based-meat: {synonyms: [meat-alternative, plant-based-nuggets, mock-meat]}
|
|
47
|
+
falafel: {parent: legumes}
|
|
48
|
+
|
|
49
|
+
starch:
|
|
50
|
+
mode: open
|
|
51
|
+
values:
|
|
52
|
+
rice: {}
|
|
53
|
+
pasta: {}
|
|
54
|
+
noodles: {}
|
|
55
|
+
potato: {synonyms: [potatoes]}
|
|
56
|
+
sweet-potato: {}
|
|
57
|
+
bread: {synonyms: [flatbread, naan, pita, sourdough]}
|
|
58
|
+
couscous: {}
|
|
59
|
+
grains: {synonyms: [quinoa, bulgur, barley, farro]}
|
|
60
|
+
polenta: {}
|
|
61
|
+
tortilla: {synonyms: [wraps]}
|
|
62
|
+
gnocchi: {}
|
|
63
|
+
none: {synonyms: [no-starch, carb-free]}
|
|
64
|
+
|
|
65
|
+
flavor:
|
|
66
|
+
mode: open
|
|
67
|
+
values:
|
|
68
|
+
spicy: {synonyms: [hot, chilli, fiery]}
|
|
69
|
+
mild: {}
|
|
70
|
+
smoky: {}
|
|
71
|
+
sweet: {}
|
|
72
|
+
savoury: {synonyms: [savory]}
|
|
73
|
+
umami: {}
|
|
74
|
+
tangy: {synonyms: [sour]}
|
|
75
|
+
zesty: {synonyms: [citrusy, lemony]}
|
|
76
|
+
fresh: {}
|
|
77
|
+
herby: {synonyms: [herbaceous]}
|
|
78
|
+
garlicky: {}
|
|
79
|
+
rich: {}
|
|
80
|
+
creamy: {}
|
|
81
|
+
cheesy: {}
|
|
82
|
+
fragrant: {synonyms: [aromatic]}
|
|
83
|
+
earthy: {}
|
|
84
|
+
nutty: {}
|
|
85
|
+
peppery: {}
|
|
86
|
+
sweet-and-sour: {}
|
|
87
|
+
|
|
88
|
+
texture:
|
|
89
|
+
mode: open
|
|
90
|
+
values:
|
|
91
|
+
crispy: {synonyms: [crisp]}
|
|
92
|
+
crunchy: {}
|
|
93
|
+
creamy: {}
|
|
94
|
+
silky: {synonyms: [smooth]}
|
|
95
|
+
saucy: {}
|
|
96
|
+
chewy: {}
|
|
97
|
+
tender: {}
|
|
98
|
+
hearty: {synonyms: [chunky]}
|
|
99
|
+
|
|
100
|
+
cuisine:
|
|
101
|
+
mode: open
|
|
102
|
+
values:
|
|
103
|
+
asian: {}
|
|
104
|
+
east-asian: {parent: asian}
|
|
105
|
+
chinese: {parent: east-asian}
|
|
106
|
+
japanese: {parent: east-asian}
|
|
107
|
+
korean: {parent: east-asian}
|
|
108
|
+
southeast-asian: {parent: asian, synonyms: [south-east-asian]}
|
|
109
|
+
thai: {parent: southeast-asian}
|
|
110
|
+
vietnamese: {parent: southeast-asian}
|
|
111
|
+
indonesian: {parent: southeast-asian}
|
|
112
|
+
malaysian: {parent: southeast-asian}
|
|
113
|
+
south-asian: {parent: asian}
|
|
114
|
+
indian: {parent: south-asian}
|
|
115
|
+
sri-lankan: {parent: south-asian}
|
|
116
|
+
middle-eastern: {synonyms: [levantine]}
|
|
117
|
+
lebanese: {parent: middle-eastern}
|
|
118
|
+
turkish: {parent: middle-eastern}
|
|
119
|
+
persian: {parent: middle-eastern}
|
|
120
|
+
israeli: {parent: middle-eastern}
|
|
121
|
+
african: {}
|
|
122
|
+
north-african: {parent: african}
|
|
123
|
+
moroccan: {parent: north-african}
|
|
124
|
+
south-african: {parent: african, synonyms: [braai]}
|
|
125
|
+
cape-malay: {parent: south-african}
|
|
126
|
+
west-african: {parent: african}
|
|
127
|
+
east-african: {parent: african}
|
|
128
|
+
european: {}
|
|
129
|
+
mediterranean: {parent: european}
|
|
130
|
+
italian: {parent: mediterranean}
|
|
131
|
+
greek: {parent: mediterranean}
|
|
132
|
+
spanish: {parent: mediterranean}
|
|
133
|
+
french: {parent: european}
|
|
134
|
+
british: {parent: european}
|
|
135
|
+
german: {parent: european}
|
|
136
|
+
portuguese: {parent: european}
|
|
137
|
+
american: {}
|
|
138
|
+
mexican: {parent: american, synonyms: [tex-mex]}
|
|
139
|
+
southern-us: {parent: american, synonyms: [cajun, creole]}
|
|
140
|
+
latin-american: {parent: american}
|
|
141
|
+
caribbean: {parent: american}
|
|
142
|
+
fusion: {}
|
|
143
|
+
|
|
144
|
+
dish:
|
|
145
|
+
mode: closed
|
|
146
|
+
values:
|
|
147
|
+
curry: {synonyms: [dhal, dal, korma, masala]}
|
|
148
|
+
stew: {synonyms: [tagine, casserole, chilli-con-carne]}
|
|
149
|
+
soup: {synonyms: [broth, ramen-soup, chowder]}
|
|
150
|
+
salad: {}
|
|
151
|
+
bowl: {synonyms: [buddha-bowl, grain-bowl, poke, rice-bowl]}
|
|
152
|
+
pasta: {synonyms: [spaghetti, tagliatelle, penne, linguine]}
|
|
153
|
+
pasta-bake: {synonyms: [baked-pasta, lasagne, lasagna, mac-and-cheese]}
|
|
154
|
+
stuffed-pasta: {synonyms: [ravioli, tortellini, stuffed-shells, cannelloni]}
|
|
155
|
+
risotto: {}
|
|
156
|
+
noodles: {synonyms: [stir-fried-noodles, pad-thai, ramen]}
|
|
157
|
+
stir-fry: {}
|
|
158
|
+
traybake: {synonyms: [sheet-pan, one-tray, tray-bake]}
|
|
159
|
+
roast: {}
|
|
160
|
+
flatbread: {synonyms: [pizza, naan-pizza, flammkuchen]}
|
|
161
|
+
burger: {}
|
|
162
|
+
sandwich: {synonyms: [toastie, sub]}
|
|
163
|
+
wrap: {synonyms: [burrito, shawarma-wrap]}
|
|
164
|
+
tacos: {synonyms: [taco, quesadilla, fajitas, nachos]}
|
|
165
|
+
platter: {synonyms: [mezze, mezze-platter, sharing-board, grazing]}
|
|
166
|
+
pie: {synonyms: [tart, quiche, pot-pie]}
|
|
167
|
+
fritters: {synonyms: [nuggets, croquettes, patties, falafels]}
|
|
168
|
+
skewers: {synonyms: [kebab, kebabs, sosaties]}
|
|
169
|
+
grain-dish: {synonyms: [pilaf, biryani, fried-rice, paella]}
|
|
170
|
+
steak: {}
|
|
171
|
+
dessert: {}
|
|
172
|
+
side: {}
|
|
173
|
+
other: {}
|
|
174
|
+
|
|
175
|
+
meal:
|
|
176
|
+
mode: closed
|
|
177
|
+
values:
|
|
178
|
+
breakfast: {synonyms: [brunch]}
|
|
179
|
+
lunch: {}
|
|
180
|
+
dinner: {synonyms: [supper, main]}
|
|
181
|
+
snack: {}
|
|
182
|
+
dessert: {}
|
|
183
|
+
side: {}
|
|
184
|
+
|
|
185
|
+
method:
|
|
186
|
+
mode: closed
|
|
187
|
+
values:
|
|
188
|
+
roast: {synonyms: [oven-roast, roasting]}
|
|
189
|
+
bake: {synonyms: [baking, oven-bake]}
|
|
190
|
+
grill: {synonyms: [broil, grilling, char, chargrill]}
|
|
191
|
+
braai: {synonyms: [barbecue, bbq]}
|
|
192
|
+
pan-fry: {synonyms: [fry, frying, sear, pan-sear]}
|
|
193
|
+
shallow-fry: {}
|
|
194
|
+
deep-fry: {}
|
|
195
|
+
saute: {synonyms: [sauteed, sweat, soften]}
|
|
196
|
+
stir-fry: {}
|
|
197
|
+
air-fry: {synonyms: [air-fryer]}
|
|
198
|
+
boil: {synonyms: [boiling, blanch]}
|
|
199
|
+
simmer: {synonyms: [reduce]}
|
|
200
|
+
steam: {}
|
|
201
|
+
poach: {}
|
|
202
|
+
braise: {synonyms: [slow-cook]}
|
|
203
|
+
toast: {synonyms: [dry-toast, toasting]}
|
|
204
|
+
blend: {synonyms: [puree, blitz, pulse]}
|
|
205
|
+
pickle: {synonyms: [quick-pickle]}
|
|
206
|
+
marinate: {}
|
|
207
|
+
mash: {synonyms: [crush]}
|
|
208
|
+
microwave: {}
|
|
209
|
+
raw: {synonyms: [no-cook, assemble]}
|
|
210
|
+
|
|
211
|
+
time:
|
|
212
|
+
mode: closed
|
|
213
|
+
values:
|
|
214
|
+
quick: {synonyms: [quick-and-easy, fast, 15-minute, 20-minute, express]}
|
|
215
|
+
weeknight: {}
|
|
216
|
+
under-30: {synonyms: [30-minute]}
|
|
217
|
+
under-45: {}
|
|
218
|
+
slow: {synonyms: [weekend, low-and-slow]}
|
|
219
|
+
|
|
220
|
+
season:
|
|
221
|
+
mode: closed
|
|
222
|
+
values:
|
|
223
|
+
spring: {}
|
|
224
|
+
summer: {synonyms: [hot-weather]}
|
|
225
|
+
autumn: {synonyms: [fall]}
|
|
226
|
+
winter: {synonyms: [cold-weather, warming, cosy, comfort]}
|
|
227
|
+
|
|
228
|
+
other:
|
|
229
|
+
mode: open
|
|
230
|
+
values:
|
|
231
|
+
one-pan: {synonyms: [one-pot, one-pot-wonder]}
|
|
232
|
+
make-ahead: {synonyms: [meal-prep]}
|
|
233
|
+
freezer-friendly: {}
|
|
234
|
+
leftovers-friendly: {}
|
|
235
|
+
date-night: {synonyms: [special-occasion]}
|
|
236
|
+
|
|
237
|
+
allergen:
|
|
238
|
+
mode: closed
|
|
239
|
+
values:
|
|
240
|
+
gluten: {}
|
|
241
|
+
wheat: {}
|
|
242
|
+
egg: {synonyms: [eggs]}
|
|
243
|
+
dairy: {synonyms: [cows-milk, cow-s-milk, milk, lactose]}
|
|
244
|
+
soy: {synonyms: [soya, soybeans]}
|
|
245
|
+
sesame: {}
|
|
246
|
+
tree-nuts: {synonyms: [nuts, tree-nut]}
|
|
247
|
+
peanuts: {synonyms: [peanut, groundnuts]}
|
|
248
|
+
sulphites: {synonyms: [sulfites, sulphur-dioxide]}
|
|
249
|
+
allium: {synonyms: [onion, garlic]}
|
|
250
|
+
fish: {}
|
|
251
|
+
crustaceans: {synonyms: [shellfish, crustacean]}
|
|
252
|
+
molluscs: {synonyms: [mollusks]}
|
|
253
|
+
celery: {}
|
|
254
|
+
mustard: {}
|
|
255
|
+
lupin: {}
|
|
256
|
+
|
|
257
|
+
equipment:
|
|
258
|
+
mode: open
|
|
259
|
+
values:
|
|
260
|
+
oven: {}
|
|
261
|
+
grill: {synonyms: [oven-grill, broiler]}
|
|
262
|
+
air-fryer: {}
|
|
263
|
+
microwave: {}
|
|
264
|
+
stovetop: {synonyms: [hob]}
|
|
265
|
+
pan: {synonyms: [frying-pan, large-pan, skillet]}
|
|
266
|
+
pot: {synonyms: [saucepan]}
|
|
267
|
+
wok: {}
|
|
268
|
+
roasting-tray: {synonyms: [baking-tray, tray, sheet-pan, roasting-pan]}
|
|
269
|
+
ovenproof-dish: {synonyms: [baking-dish, oven-dish, small-ovenproof-dish]}
|
|
270
|
+
blender: {synonyms: [food-processor, stick-blender]}
|
|
271
|
+
kettle: {}
|
|
272
|
+
grater: {}
|
|
273
|
+
sieve: {synonyms: [colander]}
|
|
274
|
+
paper-towel: {synonyms: [kitchen-paper, paper-towels]}
|
|
275
|
+
tongs: {}
|
|
276
|
+
whisk: {}
|
|
277
|
+
lid: {}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cook
|
|
3
|
+
description: Answer "what can I cook?" from the pantrybase recipe collection and the user's pantry, find recipes by ingredient, tag, cuisine, allergen or mood, keep the pantry up to date, and invent new recipes grounded in the collection. Use when the user asks for meal ideas, what to make with what they have, recipes matching criteria, a new recipe in the style of their collection, or to update their pantry.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cook from the collection
|
|
7
|
+
|
|
8
|
+
## Keep the pantry current
|
|
9
|
+
- "I bought…", "I have…", "we're out of…" → `pb_pantry_add {items}` / `pb_pantry_remove {names}`. Names are matched to the catalog (plurals, aliases, regional names), so use the user's words.
|
|
10
|
+
- Don't add staples one by one unless asked; salt, pepper, oil, water and other "from your kitchen" items never count against a recipe anyway.
|
|
11
|
+
|
|
12
|
+
## What can I cook?
|
|
13
|
+
1. `pb_pantry_match` — add `ingredients` for things the user mentions having right now; pass their constraints as filters (`tags: {diet: [vegetarian]}`, `exclude_allergens: [tree-nuts]`, `max_total_min: 30`).
|
|
14
|
+
2. Lead with full matches, then near misses (`missing` has 1–2 items) — say exactly what's missing, and suggest a swap from the pantry when an obvious one exists.
|
|
15
|
+
3. `pb_get_recipe` only for the recipe the user picks, then give it in a cook-friendly form.
|
|
16
|
+
|
|
17
|
+
## Find recipes
|
|
18
|
+
- Free text, mood or occasion ("something cosy and quick", "fresh summer lunch") → `pb_search {query}`; it's hybrid keyword + semantic.
|
|
19
|
+
- Criteria → `pb_search` with `tags` (assigned: cuisine, diet, dish, protein, starch, flavor, texture, method, meal, time, season; derived: allergen, equipment, technique, chef, provider, plan). Parents expand: `cuisine: [asian]` includes thai and korean.
|
|
20
|
+
- `pb_list_tags` shows which values exist.
|
|
21
|
+
|
|
22
|
+
## Invent a new recipe
|
|
23
|
+
1. `pb_synthesis_context` with a `query` for what the user wants, `use_pantry: true` if it should use what they have, and any filters. Read the source recipes, shared ingredients, pairings and techniques.
|
|
24
|
+
2. Write a genuinely new recipe: combine techniques and flavour patterns from the sources, prefer pantry ingredients, keep quantities realistic for the servings, and respect any diet or allergen constraints.
|
|
25
|
+
3. Show it to the user. If they want to keep it, save it with `pb_save_recipe`: `source: {kind: "synthesized"}`, `derived_from` = only the slugs you actually drew on, no `provider`, structured ingredients (`source: "kitchen"` for pantry items) and steps (`techniques`, `uses`), tags, and a `blurb`. It then shows up in search and the graph, linked to its sources.
|
|
26
|
+
|
|
27
|
+
## Tidy the catalog
|
|
28
|
+
- After saving, `new_ingredients` lists names the catalog didn't know. If one is really an existing ingredient under another name, `pb_merge_ingredients {from, into}` fixes every recipe and remembers the alias.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ingest
|
|
3
|
+
description: Capture recipe card photos into the pantrybase knowledge base — import a folder of photos, read each card (front and back), extract the full structured recipe with tags, nutrition and allergens, and save it. Use when the user wants to add, scan, import or process recipe cards or photos, or asks to continue or check progress on ingestion.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Ingest recipe cards
|
|
7
|
+
|
|
8
|
+
You turn photos in `pantrybase/inbox/` into recipes. The queue is on disk, so ingestion can stop at any point and resume later, even in a new session — just start again from step 2.
|
|
9
|
+
|
|
10
|
+
## 1. Get the photos in (if they aren't already)
|
|
11
|
+
|
|
12
|
+
- If the user names a folder or files: `pb_ingest_source {paths: [...]}`. Originals are copied, never moved; photos already known are skipped.
|
|
13
|
+
- Photos dropped straight into `pantrybase/inbox/`, or uploaded in the web UI, are picked up automatically.
|
|
14
|
+
|
|
15
|
+
## 2. Prepare once per session
|
|
16
|
+
|
|
17
|
+
1. `pb_inbox_next {window: 4}` — look at `progress` to see how much is left. If `done: true`, report and stop.
|
|
18
|
+
2. Look at the first photo with `pb_inbox_image {id}` and identify the provider from the logo/footer. Then `pb_providers {id: "<provider>"}` (or `"generic"`) and follow that guide for every card. Read it once, not per card.
|
|
19
|
+
3. `pb_list_tags {vocabulary: true}` once, so you reuse existing tag values instead of inventing near-duplicates.
|
|
20
|
+
|
|
21
|
+
## 3. Loop, one card at a time
|
|
22
|
+
|
|
23
|
+
1. `pb_inbox_next {window: 4}` gives the next photos in capture order.
|
|
24
|
+
2. If a photo has a `rotation_hint`, or shows up sideways or upside down, turn it upright first with `pb_inbox_rotate {id, degrees}` (clockwise: text reading bottom-to-top → 90, upside down → 180, top-to-bottom → 270) and view it again. Never transcribe a sideways photo.
|
|
25
|
+
3. View them in order with `pb_inbox_image`. Group the photos that belong to one card: usually a front (title, photo) followed by its back (ingredients, steps, nutrition — no title). Match a back to its front by order and by ingredients that echo the front's subtitle and description.
|
|
26
|
+
- `retake_of` means a near-identical photo of the same side: use the clearer one as the main image and attach the other with the same side, or skip it.
|
|
27
|
+
- If small print is unreadable, request `pb_inbox_image {id, max_px: 2400}` before giving up on a field.
|
|
28
|
+
4. Extract everything the guide asks for and call `pb_save_recipe` with `provider`, `source: {kind: "card"}`, the full structured record, your own `blurb`, and `ingest: [{id, side}]` listing **every** photo of the card.
|
|
29
|
+
5. Handle the response:
|
|
30
|
+
- `saved: true` — note the slug and move on. Glance at `vocabulary.rejected` and `unmatched_uses`; fix only clear mistakes (re-save with `slug`).
|
|
31
|
+
- `duplicate_of` — compare with that recipe (`pb_get_recipe`). Same card → attach the photos to it (`pb_save_recipe` with its `slug`, the same data and `ingest`) or `pb_inbox_resolve {status: "skipped", reason: "duplicate of <slug>"}`. Genuinely different → re-save with `allow_duplicate: true`.
|
|
32
|
+
- `already_ingested` — the photos were saved before; continue.
|
|
33
|
+
- `error` — read it; usually a photo id is wrong or already attached elsewhere.
|
|
34
|
+
6. Photos that aren't recipe cards → `skipped` with a reason. Photos you cannot read at all → `failed` with what's wrong ("back too blurry to read steps") so the user can re-shoot them.
|
|
35
|
+
7. A front whose back is missing: save what the front gives with `extraction.needs_review: true` and list the missing parts in `extraction.uncertain`. Don't hold up the batch waiting for it.
|
|
36
|
+
|
|
37
|
+
## Rules
|
|
38
|
+
|
|
39
|
+
- Never guess numbers (quantities, nutrition, times). Unreadable → null + `extraction.uncertain`.
|
|
40
|
+
- Don't echo extracted recipes into the conversation; the record is in the file. Report progress briefly every ~10 cards: done / remaining / flagged for review.
|
|
41
|
+
- Keep going until `pb_inbox_next` reports `done: true` or the user stops you. At the end, summarise: cards saved, photos skipped or failed (with reasons), recipes needing review (`pb_search {needs_review: true}`).
|