
    n@j]B                       d Z ddlmZ ddlZddlZddlZddlmZ ddlm	Z	 ddl
mZ  ej        e          ZdZ G d d	e          Z ed
           G d d                      ZdCdZdDdZdEdZdFdZdGdZdHdZdIdJdZd ed!dKd&ZdLd(ZdLd)ZdMd,Zd-d-d.dNd1ZdOd3Zd4Z d5Z!d6Z"dCd7Z#dPd9Z$d-d:d;dQd>Z%dRd?Z&dSd@Z'dTdAZ(dUdBZ)dS )Vup  On-disk pet store — install / list / resolve pets.

Pets live under ``get_hermes_home()/pets/<slug>/`` so every profile gets its
own set (we deliberately do **not** reuse petdex's ``~/.codex/pets`` default —
that's owned by the petdex npm CLI and isn't profile-aware).  Each installed
pet directory holds:

    pets/<slug>/
        pet.json            # {id, displayName, description, spritesheetPath}
        spritesheet.webp    # (or .png)

The active pet is resolved from the caller-supplied ``display.pet.slug`` config
value (falling back to the first installed pet), so this module stays free of
the config loader.
    )annotationsN)	dataclass)Path)get_hermes_homeg      N@c                      e Zd ZdZdS )PetStoreErrorzRaised on install/IO failures.N)__name__
__module____qualname____doc__     5/home/thesage/.hermes/hermes-agent/agent/pet/store.pyr   r       s        ((((r   r   T)frozenc                      e Zd ZU dZded<   ded<   ded<   ded<   ded<   d	Zded
<   edd            Zedd            ZdS )InstalledPetzA pet present on disk.strslugdisplay_namedescriptionr   	directoryspritesheet 
created_byreturnboolc                4    | j                                         S N)r   is_fileselfs    r   existszInstalledPet.exists/   s    '')))r   c                    | j         dk    S )N	generator)r   r    s    r   	generatedzInstalledPet.generated3   s    +--r   N)r   r   )	r	   r
   r   r   __annotations__r   propertyr"   r%   r   r   r   r   r   $   s           IIIOOOJ* * * X* . . . X. . .r   r   r   r   c                 V    t                      dz  } |                     dd           | S )z=Return the profile-scoped pets directory (created on demand).petsTparentsexist_ok)r   mkdirpaths    r   pets_dirr0   8   s-    v%DJJtdJ+++Kr   r   dictc                   | dz  }|                                 si S 	 t          j        |                    d                    S # t          t
          f$ r(}t                              d| |           i cY d }~S d }~ww xY w)Npet.jsonutf-8encodingzunreadable pet.json in %s: %s)r   jsonloads	read_textOSError
ValueErrorloggerdebug)r   pet_jsonexcs      r   _read_pet_jsonr@   ?   s    :%H 	z(,,g,>>???Z    4iEEE						s   'A A>A93A>9A>metac                    t          |                    dd          pd                                          }|r| |z  }|                                r|S dD ]}| |z  }|                                r|c S  | dz  S )zFind the spritesheet for a pet dir.

    Honors ``spritesheetPath`` from pet.json, else probes the conventional
    filenames (``spritesheet.{webp,png}`` and petdex R2's ``sprite.webp``).
    spritesheetPathr   )spritesheet.webpzspritesheet.pngzsprite.webpz
sprite.pngrD   )r   getstripr   )r   rA   declared	candidatenames        r   _resolve_spritesheetrJ   J   s     488-r228b99??AAH (	 	T  $	 		 )))r   r   r   c                x    t          t          |                                                     j        }|dv rdS |S )aS  Normalize a slug to a single bare path segment.

    Pet slugs index into ``pets_dir()/<slug>/`` for load/remove, so a value
    carrying path separators (``../``, absolute paths) could escape the pets
    directory. Strip every separator and reject ``.``/``..`` so callers can
    only ever name a direct child of the pets directory.
    )r   .z..r   )r   r   rF   rI   )r   segments     r   
_safe_slugrN   ]   s9     3t99??$$%%*G/!!rNr   InstalledPet | Nonec                   t          |           } | sdS t                      | z  }|                                sdS t          |          }t	          | t          |                    dd          p|           t          |                    dd          pd          |t          ||          t          |                    dd          pd                    S )zCReturn the :class:`InstalledPet` for *slug*, or ``None`` if absent.NdisplayNamer   r   	createdBy)r   r   r   r   r   r   )rN   r0   is_dirr@   r   r   rE   rJ   )r   r   rA   s      r   load_petrT   k   s    dD t

T!I t)$$D-44<==339r::(D99txxR006B77   r   list[InstalledPet]c                     g } t          t                                                                D ]I}|                                st	          |j                  }|r|j        r|                     |           J| S )zBReturn every installed pet (dirs containing a usable spritesheet).)sortedr0   iterdirrS   rT   rI   r"   append)outchildpets      r   installed_petsr]   ~   sv     C

**,,--  ||~~ 	uz"" 	3: 	JJsOOOJr   configured_slug
str | Nonec                    | r,t          |                                           }|r	|j        r|S t                      }|r|d         ndS )zResolve which pet to display.

    Precedence: the configured slug (``display.pet.slug``) if it's installed,
    otherwise the first installed pet alphabetically, otherwise ``None``.
    r   N)rT   rF   r"   r]   )r^   r\   r)   s      r   resolve_active_petra      sY      ,,..// 	3: 	JD$477$r   F)forcetimeoutrb   r   rc   floatc               x   ddl m} t          |           } | st          d          t	          |           }|r|j        r|s|S  || |          }|t          d|  d          t          |j                  st          d|  d	          t                      | z  }|	                    d
d
           |j        
                                                    d          d                             d          rdnd}|d| z  }t          |j        ||           i }	|j        r_t          |j                  rK	 t          |j        |          }	n3# t           $ r&}
t"                              d| |
           Y d}
~
nd}
~
ww xY wt'          |	t(                    r|	s| |j        dd}	|j        |	d<   |	                    d|            |	                    d|j                   |dz                      t3          j        |	d          d           t	          |           }||j        st          d|  d          |S )zDownload *slug* from the manifest into the pets directory.

    Idempotent: a fully-installed pet is returned as-is unless *force*.  Raises
    :class:`PetStoreError` / :class:`~agent.pet.manifest.ManifestError` on
    failure.
    r   )
find_entryzinvalid pet slug)rc   Npet 'z' is not in the petdex manifestz*refusing non-petdex spritesheet host for ''Tr*   ?.pngz.webpr   z pet.json fetch failed for %s: %sr   )idrQ   r   rC   rk   rQ   r3      indentr4   r5   zinstall of '' did not produce a spritesheet)agent.pet.manifestrf   rN   r   rT   r"   _is_petdex_hostspritesheet_urlr0   r-   lowersplitendswith	_downloadpet_json_url_download_json	Exceptionr<   r=   
isinstancer1   r   rI   
setdefault
write_textr7   dumps)r   rb   rc   rf   existingentryr   
sprite_extsprite_pathrA   r?   r\   s               r   install_petr      s    .-----dD 0.///~~H HO E JtW---E}IDIIIJJJ
 5011 RPPPPQQQ

T!IOOD4O000 06688>>sCCAFOOPVWWd]dJ8J888Ke#['BBBB D Hoe.@AA H	H!%"4gFFFDD 	H 	H 	HLL;T3GGGGGGGG	HdD!! R R5+=bQQ).D	OOD$OOM5#5666''
4(B(B(BW'UUU
4..C
{#*{P4PPPQQQJs   :E 
FE<<FrI   c                    t          j        dd| pd                                                                                              d          }|pdS )zFLowercase, hyphenate, and strip a display name into a filesystem slug.z
[^a-z0-9]+-r   r\   )resubrF   rs   )rI   r   s     r   slugifyr      sI    6-tzr&8&8&:&:&@&@&B&BCCII#NND=5r   c                    t          |           }|}d}t                      |z                                  r/| d| }|dz  }t                      |z                                  /|S )zGA :func:`slugify` result that doesn't collide with an existing pet dir.rl   r      )r   r0   r"   )rI   baser   counters       r   unique_slugr      sv    4==DDG::
$
$
&
& """"1 ::
$
$
&
&  Kr   destNonec                   t          | t          t          f          r$|                    t          |                      dS ddlm} t          | t          t          f          rC|                    |           5 }|	                    d          }ddd           n# 1 swxY w Y   n| 	                    d          }|
                    |ddddd	           dS )
zHWrite *source* (PIL image, bytes, or path) as a lossless WebP at *dest*.Nr   ImageRGBAWEBPTd      )formatlosslessqualitymethodexact)rz   bytes	bytearraywrite_bytesPILr   r   r   openconvertsave)sourcer   r   openedimages        r   _write_spritesheetr      s   &5),-- v'''&3+&& 'ZZ 	+6NN6**E	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ v&&	JJtFT3qPTJUUUUUs   8BB!Br   )r   r   r   r   c                  t          |          }t                      |z  }|                    dd           |dz  }	 t          | |           n(# t          $ r}t          d| d|           |d}~ww xY w||p||pd|j        dd	}|d
z                      t          j	        |d          d           t          |          }||j        st          d| d          |S )aw  Write a locally-generated pet into the store and return it.

    *spritesheet* may be a PIL image, raw WebP/PNG bytes, or a path. The pet
    appears in :func:`installed_pets` immediately, and because :func:`install_pet`
    returns an already-on-disk pet before consulting the manifest, it can be
    adopted (``pet.select`` / ``/pet <slug>``) without a manifest entry.
    Tr*   rD   z!could not write spritesheet for 'z': Nr   r$   )rk   rQ   r   rC   rR   r3   rl   rm   r4   r5   zregister of generated pet 'ro   )r   r0   r-   r   ry   r   rI   r|   r7   r}   rT   r"   )	r   r   r   r   r   r   r?   rA   r\   s	            r   register_local_petr      s*    4==D

T!IOOD4O00000KY;4444 Y Y YNNNNNOOUXXY #+t"(b&+  D ''
4(B(B(BW'UUU
4..C
{#*{_$___```Js   A 
A4A//A4tuple[str, bytes]c                   ddl }ddl}t                      }||                                 z  }|                                j        |                                k    s|                                st          d|  d          |j        }|	                                }|
                    |d|j                  5 }t          |                                          D ]P}|                                r:|j                            d          s |                    || d|j                    Q	 ddd           n# 1 swxY w Y   | d|                                fS )	u   Zip an installed pet's folder (pet.json + spritesheet) → (filename, bytes).

    Dotfiles (cached thumbs, backups) are skipped so the archive is a clean,
    re-importable pet package. Raises :class:`PetStoreError` if not installed.
    r   Nrg   z' is not installedwrL   /z.zip)iozipfiler0   rF   resolveparentrS   r   rI   BytesIOZipFileZIP_DEFLATEDrW   rX   r   
startswithwritegetvalue)	r   r   r   rootr   rI   bufarchiver/   s	            r   
export_petr     s    IIINNN::Dtzz||#I!T\\^^339;K;K;M;M3<D<<<===>D
**,,C	c7#7	8	8 ;G9,,..// 	; 	;D||~~ ;di&:&:3&?&? ;dt$9$9di$9$9:::	;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ===#,,..((s   ;A3D;;D?D?      `   c                 V    t                      dz  } |                     dd           | S )Nz.thumbsTr*   )r0   r-   r.   s    r   _thumbs_dirr   3  s+    ::	!DJJtdJ+++Kr   urlc                    ddl m} 	  ||           j        pd                                }n# t          $ r Y dS w xY w|dk    p|                    d          S )uH   True only for petdex.dev hosts — bounds server-side fetch (anti-SSRF).r   )urlparser   Fz
petdex.devz.petdex.dev)urllib.parser   hostnamers   r;   ru   )r   r   hosts      r   rq   rq   9  sz    %%%%%%&,"3355   uu<?4==#?#??s   $- 
;;g      >@)
source_urlrc   r   bytes | Nonec          
     z   |                                  } | sdS t                      |  dz  }|                                r%	 |                                S # t          $ r Y nw xY wd}t          |           }|r4|j        r-	 |j                                        }n# t          $ r d}Y nw xY w||r~t          |          ro	 ddl	}|
                    ||dddi          }|                                 |j        }n3# t          $ r&}t                              d| |           Y d}~nd}~ww xY w|sdS 	 ddl}	dd	lm}
 |
                    |	                    |                    5 }|                    d
                              ddt/          t0          |j                  t/          t4          |j                  f          }t9          t:          t4          z  t0          z            }|                    t:          |f|
j                  }|	                                }|                     |d           |!                                }ddd           n# 1 swxY w Y   n4# t          $ r'}t                              d| |           Y d}~dS d}~ww xY w	 |"                    |           n# t          $ r Y nw xY w|S )u  Return a small idle-frame PNG for *slug*, cached on disk.

    Crops the top-left (idle, frame 0) cell of the spritesheet and downsamples
    it to a thumbnail. Source preference: an installed spritesheet on disk, else
    *source_url* — but only when it points at petdex (so the gateway never
    fetches an arbitrary client-supplied URL). Returns ``None`` when there's no
    usable source or Pillow/network fails; callers render a placeholder.

    Doing this server-side sidesteps the renderer's CSP / R2 hotlink limits that
    break a direct ``<img src=cdn>`` and lets the result ride the authenticated
    gateway as a same-origin data URL.
    Nrj   r   T
User-Agenthermes-agent-petdexrc   follow_redirectsheaderszthumb fetch failed for %s: %sr   r   PNG)r   zthumb crop failed for %s: %s)#rF   r   r   
read_bytesr:   rT   r"   r   rq   httpxrE   raise_for_statuscontentry   r<   r=   r   r   r   r   r   r   cropmin_THUMB_FRAME_Wwidth_THUMB_FRAME_Hheightround_THUMB_WresizeNEARESTr   r   r   )r   r   rc   cachesheet_bytesr\   r   respr?   r   r   imframer   r   datas                   r   thumbnail_pngr   D  sa    ::<<D tMMtMMM)E}} 	##%%% 	 	 	D	 !%K
4..C
 sz 	/4466KK 	 	 	KKK	 zoj.I.I	ELLL99!%%'<=	   D !!###,KK 	E 	E 	ELL8$DDDDDDDD	E  t			ZZ

;//00 	"BJJv&&++As>2844c.")6T6TU E 8n4~EFFFLL(F!3U]CCE**,,CJJs5J)))<<>>D	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"    3T3???ttttt$   Ks   A 
A#"A#B B*)B*:C< <
D,D''D,42I  &C"II  II  II   
J*JJJ+ +
J87J8c                J   ddl }t          |           } | sdS 	 t                      |  dz                      d           n# t          $ r Y nw xY wt                      | z  }|                                sdS |                    |d           |                                 S )zIDelete an installed pet directory.  Returns True if anything was removed.r   NFrj   T)
missing_ok)ignore_errors)	shutilrN   r   unlinkr:   r0   rS   rmtreer"   )r   r   r   s      r   
remove_petr     s    MMMdD u
	D	&..$.????    

T!I u
MM)4M000!!!!s   (A 
AAc                   t          |           } |pd                                }| r|sdS t                      | z  }|dz  }|                                sdS 	 t	          j        |                    d                    }n# t          t          f$ r i }Y nw xY wt          |t                    si }||d<   | }t          |          }|r|| k    rt                      |z                                  s	 |                    t                      |z             	 t                      |  dz                      t                      | dz             n# t          $ r Y nw xY wt                      |z  }|dz  }|}||d<   n# t          $ r | }Y nw xY w	 |                    t	          j        |d	
          d           n# t          $ r Y dS w xY w|S )u  Rename a pet's ``displayName`` AND realign its slug/dir to match.

    Generated pets are hatched under a provisional, prompt-derived slug; when
    the user names the pet on the reveal screen we make that name the real
    identity so lists/subtitles show what they typed, not the prompt. The dir is
    renamed to ``slugify(name)`` (and the cached thumbnail moved alongside it)
    whenever that yields a free, different slug — otherwise the slug is left as
    is. Returns the resulting slug on success, or ``None`` on failure.
    r   Nr3   r4   r5   rQ   rj   rk   rl   rm   )rN   rF   r0   r   r7   r8   r9   r:   r;   rz   r1   r   r"   renamer   r|   r}   )r   r   r   r>   rA   new_slugdesireds          r   
rename_petr     s:    dD &B--//L | t

T!I:%H tz(,,g,>>??Z    dD!! &DHl##G 7d??HJJ,@+H+H+J+J?	XZZ'1222D.66{}}'GWGWGW7WXXXX    

W,I :-HH!DJJ 	 	 	HHH	DJtA666IIII   ttOsZ   (B BB4$F 9E F 
E F E   F FF+G   
GGc               
   dd l }	 |                    d| |dddi          5 }|                                 |                    |j        dz             }|                    d          5 }|                                D ]}|                    |           	 d d d            n# 1 swxY w Y   |                    |           d d d            d S # 1 swxY w Y   d S # t          $ r}t          d	|  d
|           |d }~ww xY w)Nr   GETTr   r   r   z.partwbzdownload failed for z: )r   streamr   with_suffixsuffixr   
iter_bytesr   replacery   r   )	r   r   rc   r   r   tmpfhchunkr?   s	            r   rv   rv     s   LLLJ\\!!#89  
 
 	 !!###""4;#899C$ $2!__.. $ $EHHUOOOO$$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ KK	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	  J J J?3??#??@@cIJs_   C AC)-B#C#B'	'C*B'	+CC CC CC 
D'C==Dc                   dd l }|                    | |dddi          }|                                 |                                }t	          |t
                    r|ni S )Nr   Tr   r   r   )r   rE   r   r7   rz   r1   )r   rc   r   r   r   s        r   rx   rx     so    LLL9945	   D 	99;;DdD))144r1r   )r   r   )r   r   r   r1   )r   r   rA   r1   r   r   )r   r   r   r   )r   r   r   rO   )r   rU   r   )r^   r_   r   rO   )r   r   rb   r   rc   rd   r   r   )rI   r   r   r   )r   r   r   r   )r   r   r   r   r   r   r   r   )r   r   r   r   )r   r   r   r   )r   r   r   r   rc   rd   r   r   )r   r   r   r   )r   r   r   r   r   r_   )r   r   r   r   rc   rd   r   r   )r   r   rc   rd   r   r1   )*r   
__future__r   r7   loggingr   dataclassesr   pathlibr   hermes_constantsr   	getLoggerr	   r<   _DOWNLOAD_TIMEOUTRuntimeErrorr   r   r0   r@   rJ   rN   rT   r]   ra   r   r   r   r   r   r   r   r   r   r   rq   r   r   r   rv   rx   r   r   r   <module>r     s     # " " " " "   				 ! ! ! ! ! !       , , , , , ,		8	$	$ ) ) ) ) )L ) ) ) $. . . . . . . .&      * * * *&      &	 	 	 	% % % % % -2DU 4 4 4 4 4 4n      V V V V( # # # # # #L) ) ) )0    @ @ @ @ 35t H H H H H HV" " " ".. . . .bJ J J J*2 2 2 2 2 2r   