+
    j]%                        R t ^ RIt^ RIt^ RIHt ^ RIHt ^ RIHt ^ RI	t	^ RI
t
RtRtRt]P                  ! R4      t]P                  ! R	]P                   ]P"                  ,          4      t]P                  ! R
]P                   4      tR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R  ltR! R" ltR# R$ lt R% R& lt!]"R'8X  d   ^ RI#t#]$! ]#PJ                  4      ^8  d   ]&! R(4       ]#PN                  ! ^4       ]!! ]#PJ                  ^,          4      t(]('       d   ]&! R)]( 24       R# ]&! R*4       ]#PN                  ! ^4       R# R# )+uh  Recipe helpers backed by the vault's structured Recipe Library.

Recipes live as one structured note per file under ``Recipes/<Cookbook>/`` (and
``Recipes/Web/``), following ``Templates/Recipe.md``: YAML frontmatter, then
``## Ingredients`` (bullets) and ``## Steps`` (numbered). We read those notes
directly — no web scraping needed.

Legacy web links in the flat ``Recipes.md`` are still honored as a fallback so
older saved recipes stay reachable; those are fetched with ``primp`` (browser
impersonation, since sites like Serious Eats bot-block plain HTTP) and parsed
from the page's schema.org/Recipe JSON-LD.
N)date)Path)urlparseRecipeszRecipes/Webz
Recipes.mdz$-\s*\[([^\]]+)\]\((https?://[^)]+)\)z4<script[^>]*application/ld\+json[^>]*>(.*?)</script>z^---\n(.*?)\n---c                :    V ^8  d   QhR\         \        ,          /#    return)listr   )formats   "=/Users/mitch_tango/dev/rabbit-r1-livekit/agent/src/recipes.py__annotate__r   !   s     
 
DJ 
    c                     \         P                  \        ,          p V P                  4       '       g   . # . p\	        V P                  R 4      4       Fs  pVP                  V 4      P                   Uu0 uF  q3P                  4       kK  	  ppVP                  P                  4       R8X  g   RV9   d   Kb  VP                  V4       Ku  	  V# u upi )z*.mdz	readme.md_inbox)vaultVAULTRECIPES_DIRexistssortedrglobrelative_topartslowernameappend)rootfilespxr   s        r   _iter_recipe_filesr    !   s    ;;$D;;==	EDJJv&'$%MM$$7$=$=>$=q$=>66<<>[(H,=Q	 (
 L	 ?s   0C	c                <    V ^8  d   QhR\         R\        R\        /# )r   pathtextr	   )r   str)r   s   "r   r   r   .   s!      D   r   c                 V   \         P                  V4      pV'       d   \        P                  ! R VP                  ^4      \        P                  4      pV'       dE   VP                  ^4      P                  4       '       d    VP                  ^4      P                  4       # V P                  # )z^title:\s*(.+)$)_FM_REsearchregroupMstripstem)r"   r#   mtms   &&  r   	_title_ofr/   .   sl    dAYY)1771:rtt<"((1+##%%88A;$$&&99r   c                \    V ^8  d   QhR\         \        \        \        3,          ,          /# r   )r
   tupler$   )r   s   "r   r   r   7   s       DsCx1 r   c            	     \   . p \        4        FS  pV P                  \        WP                  4       4      \	        VP                  \        P                  4      4      34       KU  	  \        P                  ! \        4      ;'       g    RpV P                  \        P                  V4      4       V # )zReturn ``(title, locator)`` for every saved recipe.

The locator is a vault-relative note path for structured recipes, or an http
URL for legacy ``Recipes.md`` entries.
 )r    r   r/   	read_textr$   r   r   r   LEGACY_FILEextend_LINK_REfindall)outr   legacys      r   list_saved_recipesr;   7   sv     "$C!

Ia/Q]]5;;5O1PQR "__[)//RFJJx'(Jr   c                `    V ^8  d   QhR\         R\        \         \         3,          R,          /# )r   queryr	   N)r$   r1   )r   s   "r   r   r   E   s'     ( (s (uS#X5 (r   c                   V P                  4       P                  4       p \        4       pV'       g   R# V F(  w  r#V '       g   K  WP                  4       9   g   K%  W#3u # 	  \        \        P
                  ! RV 4      4      pR^ reV FO  w  r#\        V\        \        P
                  ! RVP                  4       4      4      ,          4      pWv8  g   KK  W#3TreKQ  	  V'       d   V# R# )zIFind a saved recipe by fuzzy (substring, then token-overlap) title match.Nz[a-z]+)r   r+   r;   setr(   r8   len)r=   savedtitlelocq_wordsbest
best_scorescores   &       r   find_reciperH   E   s    KKM!E E
5Ukkm+<  "**Y./GQ*
Gc"**Y"FGGH %|U*  4'4'r   c                R    V ^8  d   QhR\         R\         R\        \         ,          /# )r   r#   headingr	   )r$   r
   )r   s   "r   r   r   W   s%      3  c r   c                  a V P                  4       p\        V3R l\        V4       4       R4      pVf   . # . pW#^,           R  F<  pVP                  4       P	                  R4      '       d    V# VP                  V4       K>  	  V# )z;Return the lines under a '## heading' up to the next '## '.c              3      <"   T F?  w  rVP                  4       P                  4       R S 2P                  4       8X  g   K;  Vx  KA  	  R# 5i)## N)r+   r   ).0ilnrJ   s   &  r   	<genexpr>_section.<locals>.<genexpr>[   sB      	
)xxz!s7)_%:%:%<< A)s   8A
 
A
NrM   )
splitlinesnext	enumeratelstrip
startswithr   )r#   rJ   linesstartbodyrP   s   &f    r   _sectionr[   W   s    OOE	
"5)	

 	E }	DAIK 99;!!%((K 	B ! Kr   c                0    V ^8  d   QhR\         R\        /# )r   r"   r	   )r   dict)r   s   "r   r   r   l   s      d t r   c                    V P                  4       pR p\        P                  V4      pV'       d]   \        P                  ! RVP	                  ^4      \        P
                  4      pV'       d    VP	                  ^4      P                  4       p\        VR4       Uu. uFC  p\        P                  ! RV4      ;p'       g   K$  VP	                  ^4      P                  4       NKE  	  pp\        VR4       Uu. uFC  p\        P                  ! RV4      ;p'       g   K$  VP	                  ^4      P                  4       NKE  	  ppR\        W4      RVRVR	V/# u upi u upi )
r3   z^url:\s*(\S.*)$Ingredientsz\s*[-*]\s+(.+)Stepsz\s*\d+[.)]\s+(.+)r   ingredientsstepsurl)
r4   r&   r'   r(   r)   r*   r+   r[   matchr/   )	r"   r#   rc   fmumrP   r-   ra   rb   s	   &        r   _parse_noterg   l   s1   >>D
C	t	B	YY)288A;=((1+##%C 4//B+R00A0 	
/   4))B.33A3 	
) 
  		$%{s	 
s   !E #E3!E#Ec                >    V ^8  d   QhR\         R\        R,          /# )r   locatorr	   Nr$   r]   )r   s   "r   r   r      s        r   c                b   V P                  R4      '       d   \        V 4      # \        P                  V ,          pVP	                  4       '       g   R# \        V4      pVR,          '       gD   VP                  RR4      P                  R4      '       d   \        VR,          4      ;'       g    T# V# )zJLoad a recipe's name/ingredients/steps by locator (note path or http URL).httpNrb   rc   r3   )rW   
_fetch_webr   r   r   rg   get)ri   notedatas   &  r   load_reciperq      s    &!!'"";; D;;==tD==TXXeR0;;FCC$u+&..$.Kr   c                :    V ^8  d   QhR\         \        ,          /# r   )r
   r$   )r   s   "r   r   r      s      c r   c                   . p\        V \        4      '       dB   \        P                  ! RRV 4      P	                  4       pV'       d   VP                  V4       V# \        V \        4      '       d&   V  F  pVP                  \        V4      4       K  	  V# \        V \        4      '       d   V P                  R4      R8X  g   RV 9   d-   VP                  \        V P                  R. 4      4      4       V# \        P                  ! RRV P                  R4      ;'       g    V P                  R4      ;'       g    R4      pVP	                  4       '       d    VP                  VP	                  4       4       V# )zBFlatten schema.org recipeInstructions into a list of step strings.z<[^>]+>r3   @typeHowToSectionitemListElementr#   r   )
isinstancer$   r(   subr+   r   r
   r6   _walk_instructionsr]   rn   )instrrb   r#   items   &   r   ry   ry      s   E%vvj"e,224LL L 
E4	 	 DLL+D12  L 
E4	 	 99W/3D3MLL+EII6G,LMN
 L 66*b%))F*;*V*Vuyy?P*V*VTVWDzz||TZZ\*Lr   c                >    V ^8  d   QhR\         R\        R,          /# r   rc   r	   Nrj   )r   s   "r   r   r      s      C D4K r   c                   \         P                  ! RR^R7      pVP                  V 4      pVP                  ^8w  d   R# \        P                  VP                  4       EF  p \        P                  ! V4      p\        T\        4      '       d   TMTP                  RT.4      p\        T\        4      '       d   TMT. EFb  p\        T\        4      '       g   K  TP                  R. 4      p\        T\        4      '       d   TMT.pRT9   g   KR  RTP                  RR	4      R
TP                  R. 4       Uu. uF  q'       g   K  TP                  4       NK  	  upR\        TP                  R. 4      4      RT R\        TP                  R4      4      R\!        TP                  R4      4      R\#        TP                  R4      ;'       g+    TP                  R4      ;'       g    TP                  R4      4      R\%        T 4      P&                  P)                  RR	4      /u u # 	  EK  	  R#   \        P                   d     EK  i ; iu upi )zJFetch a recipe web page and parse its JSON-LD into name/ingredients/steps.chromemacos)impersonateimpersonate_ostimeoutNz@graphrt   Reciper   r3   ra   recipeIngredientrb   recipeInstructionsrc   authorservingsrecipeYield
total_time	totalTimecookTimeprepTimesourcezwww.)primpClientrn   status_code
_LDJSON_REr8   r#   jsonloadsJSONDecodeErrorrw   r
   r]   r+   ry   _author_name
_yield_str_iso_duration_to_humanr   netlocreplace)	rc   clientrespblockrp   itemsittypesrO   s	   &        r   rm   rm      s   \\hwPRSF::c?D3##DII.	::e$D #4..DHHXv4N%eT22%?Bb$''FF7B'E't44E5'E5 BFF62.!rvv>PRT7U#[7U!YZIAGGI7U#[/7KR0PQ3l266(+;<
266-+@ A "8{+WWrvvj/AWWRVVJEW# hsm22::62F  @ /0 + ## 		 $\s   "H,7	I	I	,IIc                $    V ^8  d   QhR\         /# r   r$   )r   s   "r   r   r      s      s r   c                 0   \        V \        4      '       d*   \        V P                  R R4      4      P	                  4       # \        V \
        4      '       d   V '       d   \        V ^ ,          4      # \        V \        4      '       d   V P	                  4       # R# )r   r3   )rw   r]   r$   rn   r+   r
   r   )as   &r   r   r      sh    !T155$%++--!TqAaD!!!Swwyr   c                $    V ^8  d   QhR\         /# r   r   )r   s   "r   r   r      s      S r   c                     \        V \        4      '       d   V '       d
   V R,          MRp \        V 4      P                  4       # )   r3   )rw   r
   r$   r+   )ys   &r   r   r      s,    !TAbEBq6<<>r   c                $    V ^8  d   QhR\         /# r   r   )r   s   "r   r   r      s       r   c                   \        V \        4      '       g   R# \        P                  ! RV 4      pV'       g   R# \	        VP                  ^4      ;'       g    ^ 4      \	        VP                  ^4      ;'       g    ^ 4      r2. pV'       d%   TP                  V R2V^8  d   RMR,           4       V'       d%   TP                  V R2V^8  d   RMR,           4       RP                  V4      # )zCConvert an ISO-8601 duration like 'PT1H30M' to '1 hour 30 minutes'.r3   zPT(?:(\d+)H)?(?:(\d+)M)?z hoursz minute )rw   r$   r(   r'   intr)   r   join)dr-   hoursminsr   s   &    r   r   r      s    a
		-q1AaggajooA&AGGAJOO!(<4Ewe_uqybABvW%qbAB88E?r   c                0    V ^8  d   QhR\         R\         /# )r   rB   r	   r   )r   s   "r   r   r      s     - -# -# -r   c                     \         P                  ! R RV 4      P                  4       p\         P                  ! RRV4      pT;'       g    RR,          # )z[/\\:*?"<>|]r   z\s+Untitled Recipe:Nx   N)r(   rx   r+   )rB   r   s   & r   _safe_filenamer      sC    66/3.446D66&#t$D%%%t,,r   c                >    V ^8  d   QhR\         R\         R,          /# r}   r   )r   s   "r   r   r      s     ' '3 '3: 'r   c                   \        V 4      pV'       d/   VP                  R4      '       g   VP                  R4      '       g   R# VP                  R4      ;'       g    Rp\         R\        V4       R2p\        P
                  V,          pVP                  P                  RRR	7       VP                  4       '       d   V# R
RRV 2RVP                  RR4       2RRVP                  RR4       2RV  2RRVP                  RR4       2RVP                  RR4       2RRRR
R.pR.pYaR,           Uu. uF  pRV 2NK
  	  up,          pVRR.,          pT\        VR,          ^4       UU	u. uF  w  rV RV	 2NK  	  up	p,          pVRR R!V  R"\        P                  ! 4       R# R$2R.,          pVP                  R%P                  WV,           4      4       V# u upi u up	pi )&zFetch a recipe URL and save it as a structured note in Recipes/Web/.

Returns the vault-relative path of the note (existing or newly written), or
None if no recipe could be extracted from the page.
ra   rb   Nr   r   /z.mdT)parentsexist_okz---ztype: recipeztitle: zsource: r   r3   z	cookbook:zauthor: r   zurl: zpage:z
servings: r   ztotal_time: r   ztags: [recipe/]zrating:z
last_made:z## Ingredientsz- z## Stepsz. z## Notesz<!-- Imported from z on z%Y.%m.%dz -->
)rm   rn   WEB_DIRr   r   r   parentmkdirr   rU   r   today
write_textr   )
rc   rp   rB   relro   re   rZ   rO   nr   s
   &         r   import_web_reciper      s    c?D//488G3D3DHHV11 1EIQ~e,-S
1C;;DKKdT2{{}}
 	
%
488Hb)*+
488Hb)*+
u
TXXj"-./
txxb123

B" DM232!r!X233DRDYtG}a%@A%@TQs"QC[%@AADR23%tDJJL;RRVWY[\\DOODIIbi()J 4As   1G*G__main__z%usage: python recipes.py <recipe-url>zSaved: z)Could not extract a recipe from that URL.))__doc__r   r(   datetimer   pathlibr   urllib.parser   r   r   r   r   r5   compiler7   SIr   r&   r    r/   r;   rH   r[   rg   rq   ry   rm   r   r   r   r   r   __name__sysr@   argvprintexitresult r   r   <module>r      s.    	   !  
::=>ZZOQSQUQUXZX\X\Q\]
	'	.
($*4(B -'T z
388}q56sxx{+Fx !9: r   