
    `gj]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d+dZd,dZd-dZd.dZd/dZd0dZd1d2dZdedd3dZd4dZd4dZd5dZddd	 	 	 	 	 	 	 d6dZd7dZdZ d Z!d!Z"d+d"Z#d8d#Z$dd$d%d9d&Z%d:d'Z&d;d(Z'd<d)Z(d=d*Z)y)>up  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y)PetStoreErrorzRaised on install/IO failures.N)__name__
__module____qualname____doc__     B/root/.hermes/venv/lib/python3.12/site-packages/agent/pet/store.pyr   r       s    (r   r   T)frozenc                  v    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y)InstalledPetzA pet present on disk.strslugdisplay_namedescriptionr   	directoryspritesheet 
created_byc                6    | j                   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)returnbool)	r	   r
   r   r   __annotations__r   propertyr    r#   r   r   r   r   r   $   sQ     
IOJ* * . .r   r   c                 F    t               dz  } | j                  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   c                    | dz  }|j                         si S 	 t        j                  |j                  d            S # t        t
        f$ r#}t        j                  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?   ?   sj    :%H	zz(,,g,>??Z  4iE	s   $> A0A+%A0+A0c                    t        |j                  dd      xs d      j                         }|r| |z  }|j                         r|S dD ]  }| |z  }|j                         s|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.pngrB   )r   getstripr   )r   metadeclared	candidatenames        r   _resolve_spritesheetrI   J   s     488-r28b9??AH(	T $	
 )))r   c                h    t        t        |       j                               j                  }|dv ry|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   rD   rH   )r   segments     r   
_safe_slugrM   ]   s/     3t9??$%**G/!Nr   c                T   t        |       } | syt               | z  }|j                         syt        |      }t	        | t        |j                  dd      xs |       t        |j                  dd      xs d      |t        ||      t        |j                  dd      xs d            S )zCReturn the :class:`InstalledPet` for *slug*, or ``None`` if absent.NdisplayNamer   r   	createdBy)r   r   r   r   r   r   )rM   r0   is_dirr?   r   r   rC   rI   )r   r   rE   s      r   load_petrR   k   s    dD
T!I)$D-4<=39r:(D9txxR06B7 r   c                     g } t        t               j                               D ]I  }|j                         st	        |j
                        }|s,|j                  s9| j                  |       K | S )zBReturn every installed pet (dirs containing a usable spritesheet).)sortedr0   iterdirrQ   rR   rH   r    append)outchildpets      r   installed_petsrZ   ~   s[     C
**,- ||~uzz"3::JJsO Jr   c                ~    | r)t        | j                               }|r|j                  r|S t               }|r|d   S 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)rR   rD   r    rZ   )configured_slugrY   r)   s      r   resolve_active_petr]      sA     ,,./3::JD47$$r   F)forcetimeoutc               ,   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  }|j                  d
d
       |j                  j                         j                  d      d   j                  d      rdnd}|d| z  }t        |j                  ||       i }	|j                  r-t        |j                        r	 t        |j                  |      }	t'        |	t(              r|	s| |j*                  dd}	|j,                  |	d<   |	j/                  d|        |	j/                  d|j*                         |dz  j1                  t3        j4                  |	d      d       t	        |       }||j
                  st        d|  d      |S # t         $ r!}
t"        j%                  d| |
       Y d}
~
d}
~
ww xY w)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)r_   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   )idrO   r   rA   rf   rO   r2      indentr3   r4   zinstall of '' did not produce a spritesheet)agent.pet.manifestra   rM   r   rR   r    _is_petdex_hostspritesheet_urlr0   r-   lowersplitendswith	_downloadpet_json_url_download_json	Exceptionr;   r<   
isinstancedictr   rH   
setdefault
write_textr6   dumps)r   r^   r_   ra   existingentryr   
sprite_extsprite_pathrE   r>   rY   s               r   install_petr~      s    .dD.//~HHOOEtW-E}eD6)HIJJ
 5001HaPQQ
T!IOOD4O0 00668>>sCAFOOPVW]dJJ<88Ke##['B Doe.@.@A	H!%"4"4gFD dD!5+=+=bQ)..D	OOD$OOM5#5#56''

4(BW'U
4.C
{#**l4&0OPQQJ  	HLL;T3GG	Hs   G) )	H2HHc                    t        j                  dd| xs dj                         j                               j                  d      }|xs dS )zFLowercase, hyphenate, and strip a display name into a filesystem slug.z
[^a-z0-9]+-r   rY   )resubrD   rn   )rH   r   s     r   slugifyr      s?    66-tzr&8&8&:&@&@&BCII#ND=5r   c                    t        |       }|}d}t               |z  j                         r(| d| }|dz  }t               |z  j                         r(|S )zGA :func:`slugify` result that doesn't collide with an existing pet dir.rg   r      )r   r0   r    )rH   baser   counters       r   unique_slugr      s]    4=DDG:
$
$
&q	"1 :
$
$
& Kr   c                ^   t        | t        t        f      r|j                  t        |              yddlm} t        | t        t        f      r,|j                  |       5 }|j                  d      }ddd       n| j                  d      }j                  |ddddd	       y# 1 sw Y   !xY w)
zHWrite *source* (PIL image, bytes, or path) as a lossless WebP at *dest*.Nr   ImageRGBAWEBPTd      )formatlosslessqualitymethodexact)ru   bytes	bytearraywrite_bytesPILr   r   r   openconvertsave)sourcedestr   openedimages        r   _write_spritesheetr      s    &5),-v'&3+&ZZ 	+6NN6*E	+ 	+ v&	JJtFT3qPTJU		+ 	+s   B##B,r   )r   r   c                  t        |      }t               |z  }|j                  dd       |dz  }	 t        | |       ||xs ||xs d|j                  dd	}|d
z  j                  t        j                  |d      d       t        |      }||j                  st        d| d      |S # t        $ r}t        d| d|       |d}~ww xY w)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*   rB   z!could not write spritesheet for 'z': Nr   r"   )rf   rO   r   rA   rP   r2   rg   rh   r3   r4   zregister of generated pet 'rj   )r   r0   r-   r   rt   r   rH   rx   r6   ry   rR   r    )	r   r   r   r   r   r}   r>   rE   rY   s	            r   register_local_petr      s     4=D
T!IOOD4O000KY;4
 #+t"(b&++ D ''

4(BW'U
4.C
{#**9$?^_``J  Y?vSNOUXXYs   B- -	C6CCc                   ddl }ddl}t               }|| j                         z  }|j	                         j
                  |j	                         k7  s|j                         st        d|  d      |j                  }|j                         }|j                  |d|j                        5 }t        |j                               D ]P  }|j                         s|j                  j                  d      r0|j!                  || d|j                          R 	 ddd       | d|j#                         fS # 1 sw Y   xY w)	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   Nrb   z' is not installedwrK   /z.zip)iozipfiler0   rD   resolveparentrQ   r   rH   BytesIOZipFileZIP_DEFLATEDrT   rU   r   
startswithwritegetvalue)	r   r   r   rootr   rH   bufarchiver/   s	            r   
export_petr     s    :Dtzz|#I!!T\\^39;K;K;MeD6);<==>>D
**,C	c7#7#7	8 ;G9,,./ 	;D||~dii&:&:3&?dtfAdii[$9:	;; V4=#,,.((	; ;s   )-D4D43#D44D=      `   c                 F    t               dz  } | j                  dd       | S )Nz.thumbsTr*   )r0   r-   r.   s    r   _thumbs_dirr   3  s#    :	!DJJtdJ+Kr   c                    ddl m} 	  ||       j                  xs dj                         }|dk(  xs |j                  d      S # t        $ r Y yw xY w)uH   True only for petdex.dev hosts — bounds server-side fetch (anti-SSRF).r   )urlparser   Fz
petdex.devz.petdex.dev)urllib.parser   hostnamern   r:   rp   )urlr   hosts      r   rl   rl   9  sV    %&&,"335 <?4==#??  s   $A 	AAg      >@)
source_urlr_   c          
        | j                         } | syt               |  dz  }|j                         r	 |j                         S d}t        |       }|r'|j                  r	 |j                  j                         }|E|rCt        |      r8	 ddl	}|j                  ||dddi      }|j                          |j                  }|sy	 ddl}	dd	lm}
 |
j'                  |	j)                  |            5 }|j+                  d
      j-                  ddt/        t0        |j2                        t/        t4        |j6                        f      }t9        t:        t4        z  t0        z        }|j=                  t:        |f|
j>                        }|	j)                         }|jA                  |d       |jC                         }ddd       	 |jE                         |S # t        $ r Y w xY w# t        $ r d}Y vw xY w# t        $ r"}t        j                  d| |       Y d}~Yd}~ww xY w# 1 sw Y   mxY w# t        $ r!}t        j                  d| |       Y d}~yd}~ww xY w# t        $ r Y S w xY w)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.
    Nre   r   T
User-Agenthermes-agent-petdexr_   follow_redirectsheaderszthumb fetch failed for %s: %sr   r   PNG)r   zthumb crop failed for %s: %s)#rD   r   r   
read_bytesr9   rR   r    r   rl   httpxrC   raise_for_statuscontentrt   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   r_   cachesheet_bytesrY   r   respr>   r   r   imframer   r   datas                   r   thumbnail_pngr   D  sA    ::<DMtfDM)E}}	##%% !%K
4.C
szz	//446K zoj.I	E99!%%'<=	  D !!#,,K ZZ

;/0 	"BJJv&++As>2884c."))6TUE 8n4~EFFLL(F!3U]]CE**,CJJs5J)<<>D	"$ Kg  		  	K	  	ELL8$DD	E	" 	"  3T3?  Ks~   G !G# 7G5 *H/ 0CH#7H/  I 	G G #G21G25	H >HH #H,(H/ /	I8II	I)(I)c                
   ddl }t        |       } | sy	 t               |  dz  j                  d       t               | z  }|j                         sy|j                  |d       |j                          S # t        $ r Y Mw xY w)zIDelete an installed pet directory.  Returns True if anything was removed.r   NFre   T)
missing_ok)ignore_errors)	shutilrM   r   unlinkr9   r0   rQ   rmtreer    )r   r   r   s      r   
remove_petr     s    dD
	D6	&..$.? 
T!I
MM)4M0!!!  s    A6 6	BBc                   t        |       } |xs dj                         }| r|syt               | z  }|dz  }|j                         sy	 t	        j
                  |j                  d            }t        |t              si }||d<   | }t        |      }|r|| k7  rt               |z  j                         sd	 |j                  t               |z         	 t               |  dz  j                  t               | dz         t               |z  }|dz  }|}||d<   	 |j                  t	        j                   |d	
      d       |S # t        t        f$ r i }Y w xY w# t        $ r Y fw xY w# t        $ r | }Y ^w xY w# t        $ r Y yw xY w)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   Nr2   r3   r4   rO   re   rf   rg   rh   )rM   rD   r0   r   r6   r7   r8   r9   r:   ru   rv   r   r    renamer   rx   ry   )r   r   r   r=   rE   new_slugdesireds          r   
rename_petr     s    dD &B--/L|
T!I:%Hzz(,,g,>? dD!&DHl#G7d?HJ,@+H+H+J	XZ'12D6.66{}'RVGW7WX !
W,I :-HH!DJDJJtA6I O5 Z      	H	
  sZ   	%E 5E) -E ?E) (E: EE	E&#E) %E&&E) )E76E7:	FFc                  dd l }	 |j                  d| |dddi      5 }|j                          |j                  |j                  dz         }|j                  d      5 }|j                         D ]  }|j                  |        	 d d d        |j                  |       d d d        y # 1 sw Y   #xY w# 1 sw Y   y xY w# 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   replacert   r   )	r   r   r_   r   r   tmpfhchunkr>   s	            r   rq   rq     s    J\\!!#89  
 	 !!#""4;;#89C$ $2!__. $EHHUO$$ KK	 	$ $	 	  J23%r#?@cIJsM   C  A B4'B(B4C  (B1	-B44B=9C  =C   	C 	CC c                   dd l }|j                  | |dddi      }|j                          |j                         }t	        |t
              r|S i S )Nr   Tr   r   r   )r   rC   r   r6   ru   rv   )r   r_   r   r   r   s        r   rs   rs     sW    9945	  D 	99;DdD)41r1r   )r$   r   )r   r   r$   rv   )r   r   rE   rv   r$   r   )r   r   r$   r   )r   r   r$   InstalledPet | None)r$   zlist[InstalledPet]r   )r\   
str | Noner$   r   )r   r   r^   r%   r_   floatr$   r   )rH   r   r$   r   )r   r   r$   None)r   r   r   r   r   r   r$   r   )r   r   r$   ztuple[str, bytes])r   r   r$   r%   )r   r   r   r   r_   r   r$   zbytes | None)r   r   r$   r%   )r   r   r   r   r$   r   )r   r   r   r   r_   r   r$   r   )r   r   r_   r   r$   rv   )*r   
__future__r   r6   loggingr   dataclassesr   pathlibr   hermes_constantsr   	getLoggerr	   r;   _DOWNLOAD_TIMEOUTRuntimeErrorr   r   r0   r?   rI   rM   rR   rZ   r]   r~   r   r   r   r   r   r   r   r   r   rl   r   r   r   rq   rs   r   r   r   <module>r      s    #   	 !  ,			8	$ )L ) $. . .&*&&	% -2DU 4nV( # # 	#
 # #L)0 @ 35t HV"..bJ*2r   