
    `gjAc                    H   U d Z ddlmZ ddlZddlZddlZddlZddlmZm	Z	 ddl
m
Z
mZ ddlmZ ddlmZmZmZmZmZ ddlmZ d	Zd
ZdZdddddefZded<    eh d      Zded<    G d de      Ze G d d             Ze G d d             Z d2dZ!d3dZ"d4dZ#d5dZ$ ejJ                  d       Z&d6d!Z'd7d"Z(d8d#Z)d9d$Z*d:d%Z+d;d&Z,d<d'Z-e G d( d)             Z.d=d*Z/d>d+Z0	 d?	 	 	 	 	 	 	 d@d,Z1	 	 	 	 	 	 	 	 	 	 dAd-Z2dBd.Z3	 	 	 dC	 	 	 	 	 	 	 	 	 dDd/Z4	 dE	 	 	 	 	 dFd0Z5dGd1Z6y)Hu  Profile distributions — shareable, packaged Hermes profiles via git.

A distribution is a Hermes profile published as a git repository (or
installed from a local directory for development). Install with one command
from a git URL, update in place, and keep your local memories / sessions /
credentials untouched.

Where this fits relative to the existing pieces:

* ``hermes profile export/import`` — local backup / restore for a profile
  on your own machine. NOT a distribution format. Stays as-is.
* ``hermes skills install <url>`` — the URL install pattern we're mirroring,
  but at the profile granularity.

Subcommands (all live under ``hermes profile``, not a parallel tree):

    hermes profile install <source> [--name N] [--alias] [--force] [--yes]
    hermes profile update  <name>  [--force-config] [--yes]
    hermes profile info    <name>

``<source>`` is one of:

* A git URL (``github.com/user/repo``, ``https://github.com/...``, ``git@...``,
  ``ssh://``, ``git://``), optionally with ``#<ref>`` to pin a tag / branch /
  commit SHA.
* A local directory that already contains ``distribution.yaml`` — used
  during profile development before the first push.

Manifest format (``distribution.yaml`` at the profile root)::

    name: telemetry
    version: 0.1.0
    description: "Compliance monitoring harness"
    hermes_requires: ">=0.12.0"
    author: "..."
    license: "..."
    env_requires:
      - name: OPENAI_API_KEY
        description: "OpenAI API key"
        required: true
      - name: GRAPHITI_MCP_URL
        description: "Memory graph URL"
        required: false
        default: "http://127.0.0.1:8000/sse"
    distribution_owned:      # optional; sensible defaults apply
      - SOUL.md
      - skills/
      - cron/
      - mcp.json

Update semantics:

* Distribution-owned paths (SOUL.md, mcp.json, skills/, cron/,
  distribution.yaml) are replaced from the new source.
* ``config.yaml`` is distribution-owned but preserved on update unless
  ``--force-config`` is passed (user overrides typically live here).
* User-owned paths (memories/, sessions/, state.db, auth.json, .env,
  logs/, workspace/, home/, plans/, *_cache/, and anything under
  ``local/``) are never touched.
    )annotationsN)	dataclassfield)datetimetimezone)Path)AnyDictListOptionalTupleis_excluded_skill_pathzdistribution.yamlz.env.templatez.env.EXAMPLEzSOUL.mdconfig.yamlzmcp.jsonskillscronzTuple[str, ...]DEFAULT_DIST_OWNED>%   .envstate.db	auth.json	auth.lock
.worktrees
errors.loggateway.pidhermes-agentstate.db-shmstate.db-wal.update_checkprocesses.json.hermes_historyhermes_state.dbresponse_store.dbgateway_state.jsonresponse_store.db-shmresponse_store.db-walbinhomelogscachelocalplansbackupsmemoriesprofilessessions	sandboxes	workspaceaudio_cachecheckpointsimage_cachenode_modulesactive_profiledocument_cachebrowser_screenshots	frozensetUSER_OWNED_EXCLUDEc                      e Zd ZdZy)DistributionErrorz0Raised for distribution install/update failures.N)__name__
__module____qualname____doc__     R/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/profile_distribution.pyr<   r<      s    :rB   r<   c                  \    e Zd ZU ded<   dZded<   dZded<   dZd	ed
<   edd       ZddZ	y)EnvRequirementstrname descriptionTboolrequiredNOptional[str]defaultc           
     v   t        |t              s!t        dt        |      j                         t        |j                  d      xs d      j                         }|st        d       | |t        |j                  d      xs d      t        |j                  dd            |j                  d      	      S )
Nz*env_requires entry must be a mapping, got rG   rH   z!env_requires entry missing 'name'rI   rK   TrM   rG   rI   rK   rM   )	
isinstancedictr<   typer=   rF   getstriprJ   )clsdatarG   s      rC   	from_dictzEnvRequirement.from_dict   s    $%#<T$Z=P=P<QR  488F#)r*002#$GHHDHH]39r:$((:t45HHY'	
 	
rB   c                    | j                   | j                  d}| j                  sd|d<   | j                  | j                  |d<   |S )N)rG   rI   FrK   rM   rO   )selfouts     rC   to_dictzEnvRequirement.to_dict   sB    '+yyAQAQR}}#C
O<<#!\\C	N
rB   )rV   r	   returnz'EnvRequirement'r\   Dict[str, Any])
r=   r>   r?   __annotations__rI   rK   rM   classmethodrW   r[   rA   rB   rC   rE   rE      s<    
IKHd!G]!
 
rB   rE   c                      e Zd ZU ded<   dZded<   dZded<   dZded<   dZded<   dZded	<    e	e

      Zded<    e	e

      Zded<   dZded<   dZded<   edd       ZddZddZy)DistributionManifestrF   rG   0.1.0versionrH   rI   hermes_requiresauthorlicense)default_factoryzList[EnvRequirement]env_requires	List[str]distribution_ownedsourceinstalled_atc                V   t        |t              s't        t         dt	        |      j
                         t        |j                  d      xs d      j                         }|st        t         d      |j                  d      xs g }t        |t              st        d      |D cg c]  }t        j                  |       }}|j                  d      xs g }|rt        |t              st        d      |D cg c]D  }t        |      j                         st        |      j                         j                  d	      F }} | |t        |j                  d
      xs d      t        |j                  d      xs d      t        |j                  d      xs d      t        |j                  d      xs d      t        |j                  d      xs d      ||t        |j                  d      xs d      t        |j                  d      xs d      
      S c c}w c c}w )Nz must be a mapping, got rG   rH   z missing 'name'ri   zenv_requires must be a listrk   z!distribution_owned must be a list/rd   rc   rI   re   rf   rg   rl   rm   )
rG   rd   rI   re   rf   rg   ri   rk   rl   rm   )rP   rQ   r<   MANIFEST_FILENAMErR   r=   rF   rS   rT   listrE   rW   )	rU   rV   rG   env_raweri   dist_owned_rawprk   s	            rC   rW   zDistributionManifest.from_dict   s   $%#$%%=d4j>Q>Q=RS  488F#)r*002#'8&9$IJJ((>*0b'4(#$ABB=DE003EE"67=2*^T"B#$GHHAObASVWXSYS_S_Sac!flln2237bb+6w7DHH]39r:): ; ArBtxx)/R0+1r2%1txx)/R0TXXn5;<
 	
 F cs   ,H! H&*H&c                *   | j                   | j                  d}| j                  r| j                  |d<   | j                  r| j                  |d<   | j                  r| j                  |d<   | j
                  r| j
                  |d<   | j                  r+| j                  D cg c]  }|j                          c}|d<   | j                  r| j                  |d<   | j                  r| j                  |d<   | j                  r| j                  |d	<   |S c c}w )
N)rG   rd   rI   re   rf   rg   ri   rk   rl   rm   )rG   rd   rI   re   rf   rg   ri   r[   rk   rl   rm   )rY   rZ   rs   s      rC   r[   zDistributionManifest.to_dict   s    II||
 !%!1!1C%)%9%9C!";; KKCM<<!\\C	N8<8I8I"J1199;"JC""(,(?(?C$%;; KKCM"&"3"3C
 #Ks    Dc                b    | j                   rt        | j                         S t        t              S )z0Resolve which paths count as distribution-owned.)rk   rq   r   )rY   s    rC   owned_pathsz DistributionManifest.owned_paths   s(    ""//00&''rB   N)rV   r	   r\   z'DistributionManifest'r]   )r\   rj   )r=   r>   r?   r_   rd   rI   re   rf   rg   r   rq   ri   rk   rl   rm   r`   rW   r[   rx   rA   rB   rC   rb   rb      s    
IGSKOSFCGS).t)DL&D$)$$?	?FC L#
 
:.(rB   rb   c                h    	 dd l }|j                  |       S # t        $ r}t        d      |d }~ww xY w)Nr   z-PyYAML is required for distribution manifests)yamlImportErrorr<   	safe_load)textrz   excs      rC   
_load_yamlr      s?    Z >>$  Z OPVYYZs    	1,1c                2    dd l }|j                  | dd      S )Nr   F)	sort_keysdefault_flow_style)rz   	safe_dump)rV   rz   s     rC   
_dump_yamlr      s    >>$%E>JJrB   c                    | t         z  }|j                         sy	 t        |j                  d            }t        j                  |xs i       S # t        $ r}t        d| d|       |d}~ww xY w)zJReturn the manifest for *profile_dir*, or None if it isn't a distribution.Nutf-8encodingzFailed to parse z: )rp   is_filer   	read_text	Exceptionr<   rb   rW   )profile_dirmf_pathrV   r~   s       rC   read_manifestr     s~    --G??N'++W+=>  ))$*"55  N"27)2cU CD#MNs   A 	A0A++A0c                l    | t         z  }|j                  t        |j                               d       |S )Nr   r   )rp   
write_textr   r[   )r   manifestr   s      rC   write_manifestr     s3    --Gz("2"2"45HNrB   z!^\s*(>=|<=|==|!=|>|<)\s*(.+?)\s*$c                   t        |       j                         j                  d      }t        j                  d|d      d   }|j	                  d      }t        |      dk  r |j                  d       t        |      dk  r 	 t        |d         t        |d         t        |d         fS # t        $ r}t        d	|       |d
}~ww xY w)uL   Very small semver parser — major.minor.patch only.  Extra labels stripped.vz[-+]   r   .   0   zUnparseable version: N)
rF   rT   lstripresplitlenappendint
ValueErrorr<   )r   spartsr~   s       rC   _parse_semverr     s    Ac"A
!Q"AGGCLE
e*q.S e*q.HE!Hs58}c%(m<< H"7u =>CGHs   (B, ,	C	5CC	c                V   | r| j                         syt        j                  |       }|sd| j                         }}n"|j                  d      |j                  d      }}t	        |      }t	        |      }||k\  ||k  ||k(  ||k7  ||kD  ||k  d|   }|st        d| | d| d      y)	u   Raise DistributionError if ``current_version`` does not satisfy ``spec``.

    ``spec`` accepts a single comparator (``>=0.12.0``, ``==0.12.0``, etc.).
    Empty or blank spec is a no-op — no requirement.
    N>=r   r   )r   z<=z==z!=><z"This distribution requires Hermes z, but you have r   )rT   _VERSION_OP_REmatchgroupr   r<   )speccurrent_versionmoptargetcurtgtoks           rC   check_hermes_requiresr   +  s     tzz|T"A4::<FWWQZF

(C

CSjSjSjSjCiCi
 	

B 0VH =+,A/
 	
 rB   c                   g d}| j                   D ]  }|j                  r|j                  d|j                          |j                  rdnd}|j                  d| d       |j                  |j                  nd}|j                  rdnd}|j                  | |j
                   d|        |j                  d        d	j                  |      j                         d	z   S )
z4Generate a ``.env.template`` body from env_requires.)z=# Environment variables required by this Hermes distribution.z<# Copy to `.env` and fill in your own values before running.rH   z# rK   optionalz# ()rH   =
)ri   rI   r   rK   rM   rG   joinrstrip)r   linesreqstatusdefault_valprefixs         rC   _env_template_from_manifestr   O  s    E
 $$ ??LL2coo./0"||s6(!_%%([[%<ckk"||xz;-89R 99U""$t++rB   c                    | j                         } | j                  d      ry| j                  d      ry| j                  d      ryt        j                  d|       ryy)N.gitT)zgit@zssh://zgit://)zhttp://https://^github\.com/[\w.-]+/[\w.-]+/?$F)rT   endswith
startswithr   r   )r   s    rC   _looks_like_git_urlr   g  sS    		Azz&||01||+, 	xx2A6rB   c           
        t        j                  d|       rd| j                  d       } 	 t        j                  dddd| t        |      gdd	       y # t        $ r}t        d
      |d }~wt        j                  $ rM}|j                  r|j                  j                  dd      nd}t        d|j                                |d }~ww xY w)Nr   r   ro   gitclonez--depth1T)checkcapture_outputz$git is required for git-URL installsr   replace)errorsrH   zgit clone failed: )r   r   r   
subprocessrunrF   FileNotFoundErrorr<   CalledProcessErrorstderrdecoderT   )urldestr~   r   s       rC   
_git_cloner   w  s    	xx2C8C)*
PGYS#d)<	

  Q FGSP(( PAD""79"=QS"4V\\^4D EFCOPs$   'A 	CA))C?ACCc                   | j                         }t        |      r\|dz  }t        ||       t        j                  |dz  d       |t
        z  j                         st        dt
         d|d      ||fS t        |      j                         }|j                         rV|t
        z  j                         st        dt
         d| d	      |j                         t        |j                               fS t        d
| d      )u  Resolve *source* to a local directory containing distribution.yaml.

    Returns ``(staged_dir, provenance)`` where ``provenance`` is stored in the
    installed manifest's ``source:`` field so ``hermes profile update`` can
    re-pull from the same place.

    Accepts:
      * A git URL (https / ssh / git@ / bare github.com shorthand) — cloned
        into a temp directory; ``.git`` removed after clone.
      * A local directory already containing ``distribution.yaml``.
    r   r   T)ignore_errorsNo z at the root of z7. This repository is not a Hermes profile distribution.z in zH. A local-directory source must contain a distribution.yaml at its root.z$Cannot resolve distribution source: zF. Expected a git URL (e.g. github.com/user/repo) or a local directory.)rT   r   r   shutilrmtreerp   r   r<   r   
expanduseris_dirresolverF   )rl   workdirsrc_strcloned
path_guesss        rC   _stage_sourcer     s%    llnG 7#7"7F#fvoT:**335#'((8 DH H  w g))+J..779#'(ZL 9Y Y  !!#S););)=%>>>

.vj 9O 	O rB   c                    | j                  d      D ]2  }|j                         s	 |j                  |       }t	        d|        y# t        $ r |}Y w xY w)z=Reject symlinks before reading or copying distribution files.*z/Profile distributions cannot contain symlinks: N)rglob
is_symlinkrelative_tor   r<   )stagedentryrels      rC   _reject_distribution_symlinksr     sm    c" 	
!	##F+C  =cUC
 	
	

  	C	s   AAAc                  n    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<   dZd	ed<   dZd	ed<   y)InstallPlanzCSummary of what an install will do, surfaced for user confirmation.rb   r   r   
staged_dirrF   
provenance
target_dirrJ   existingTpreserves_configFhas_cron
has_skillsN)r=   r>   r?   r@   r_   r   r   r   rA   rB   rC   r   r     s@    M""ON!d!HdJrB   r   c                    | dz  }|j                         sy|j                  d      D ]  } y |j                  d      D ]  } y y)Nr   Fz*.jsonTz*.yaml)r   r   )r   cron_dir_s      rC   _has_cron_jobsr     sQ    H??^^H% ^^H% rB   c                p    | dz  }|j                         syt        d |j                  d      D              S )Nr   r   c              3  8   K   | ]  }t        |      rd   yw)r   Nr   ).0ru   s     rC   	<genexpr>z _count_skills.<locals>.<genexpr>  s      7Ma7Ps   zSKILL.md)r   sumr   )r   
skills_dirs     rC   _count_skillsr    s@    ("J ##J/  rB   c                6   ddl m}m}m} ddlm} t        | |      \  }}t        |       t        |      }	|	t        dt         d      t        |	j                  |       |xs |	j                  }
 ||
      } ||       |dk(  rt        d      ||	_        ||	_        t        j                   t"        j$                        j'                  d	      |	_         ||      }|j+                         }t-        |      }t/        |      }t1        |	|||||||dkD  
      S )zCStage *source* and produce a plan describing what install would do.r   get_profile_dirnormalize_profile_namevalidate_profile_name)__version__r   uM    found at the distribution root — this source is not a Hermes distribution.rM   u   Cannot install a distribution as 'default' — that is the built-in root profile (~/.hermes).  Pass --name <name> to install under a new profile.seconds)timespec)r   r   r   r   r   r   r   r   )hermes_cli.profilesr  r  r  
hermes_clir  r   r   r   r<   rp   r   re   rG   rl   r   nowr   utc	isoformatrm   r   r   r  r   )rl   r   override_namer  r  r  hermes_versionr   r   r   target_namecanonr   r   r   skill_counts                   rC   plan_installr    s2    
 9&vw7FJ!&)V$H#$ %8 8
 	
 (22NC  08==K";/E% 	
 	

 HM HO %LL6@@)@TH 'J  "Hf%H'K!?	 	rB   c                   |j                  dd       | j                         D ]  }|j                  }|t        v r|t        k(  rt        j                  ||t        z         ?|dk(  r|r|dz  j                         rZ||z  }|j                         rQ|j                         rt        j                  |       | j                         t        j                  ||fd       t        j                  ||        |j                  r:|t        z  j                         s#|t        z  j                  t        |      d       t!        ||       y)	aI  Copy distribution-owned files from *staged* into *target*.

    User-owned paths are never touched.  ``config.yaml`` is replaced only when
    ``preserve_config`` is False (fresh install or ``--force-config`` update).
    ``.env.template`` is renamed to ``.env.EXAMPLE`` in the target to avoid
    shadowing a real ``.env``.
    Tparentsexist_okr   c                |    t        |       j                         k(  r|D cg c]  }|t        v s| c}S g S c c}w N)r   r   r:   )dnamesnstaged_resolveds      rC   <lambda>z$_copy_dist_payload.<locals>.<lambda>D  s@    Aw(O; !&A1.@)@QA   As   99)ignorer   r   N)mkdiriterdirrG   r:   ENV_TEMPLATE_FILENAMEr   copy2ENV_EXAMPLE_FILENAMEexistsr   r   r   copytreeri   r   r   r   )r   r   r   preserve_configr   rG   r   r  s          @rC   _copy_dist_payloadr*  !  s"    LLL-! &zz%%((LL)= =>= _&=:P9X9X9Z}<<>{{}d#$nn.OOO LL%7&< f/C&C%K%K%M	&	&22'1G 	3 	

 68$rB   c                >    dD ]  }| |z  j                  dd        y)z2Create the bootstrap dirs a fresh profile expects.)	r-   r/   r   skinsr(   r+   r1   r   r'   Tr  N)r"  )r   r  s     rC   _bootstrap_user_dirsr-  W  s*    4 8	!4$78rB   c                   ddl m}m} t        j                  d      5 }t        | t        |      |      }|j                  r2|s0t        d|j                  j                   d|j                   d      t        |j                         t        |j                  |j                  |j                  d	
       |r: ||j                  j                        }| ||j                  j                         |cddd       S # 1 sw Y   yxY w)zInstall a distribution from *source* into a new profile.

    Returns the resolved :class:`InstallPlan`.  Use :func:`plan_install`
    first if you want to preview + prompt the user before calling this.
    r   )check_alias_collisioncreate_wrapper_scripthermes_dist_install_r   r  	Profile 'z' already exists at zP. Use `hermes profile update` to upgrade in place, or pass --force to overwrite.Fr)  N)r  r/  r0  tempfileTemporaryDirectoryr  r   r   r<   r   rG   r   r-  r*  r   )	rl   rG   forcecreate_aliasr/  r0  tmpplan	collisions	            rC   install_distributionr=  ^  s    
 
	$	$,B	C sFDITB==#DMM..//CDOOCT U0 0  	T__-OOOOMM!		
 -dmm.@.@AI %dmm&8&891  s   CC>>Dc                   ddl m}m}m}  ||       } ||        ||      }|j	                         st        d| d      t        |      }|t        d| dt         d      |j                  st        d| d      t        j                  d	
      5 }t        |j                  t        |      |      }	| |	_        t        |	j                  |	j                   |	j"                  |	j                         |	cddd       S # 1 sw Y   yxY w)aY  Re-pull the distribution for an existing profile and apply updates.

    The source is read from the installed profile's ``distribution.yaml``
    ``source:`` field.  Distribution-owned files are overwritten; user-owned
    data (memories, sessions, auth) is never touched.  ``config.yaml`` is
    preserved unless ``force_config`` is True.
    r   r  r4  ' does not exist.Nz' is not a distribution (no zG). Only profiles installed via `hermes profile install` can be updated.zd' has no recorded source.  Re-install with `hermes profile install <source> --name {canon} --force`.hermes_dist_update_r2  r3  r5  )r  r  r  r  r   r<   r   rp   rl   r6  r7  r  r   r   r*  r   r   r   )
profile_nameforce_configr  r  r  r  r   existing_manifestr:  r;  s
             rC   update_distributionrD    s+     #<0E% U#F==?)E72C DEE%f- w:;L:M NS S
 	
 ##w H H
 	

 
	$	$,A	B c$$I

 %1 0OOOOMM 11		
   s   A"DDc                    ddl m}m}m}  ||       } ||        ||      }|j	                         st        d| d      t        |      }|i S |j                         S )zReturn a structured view of a profile's distribution metadata.

    Returns an empty dict if the profile exists but has no manifest.
    Raises DistributionError if the profile itself doesn't exist.
    r   r  r4  r?  )r  r  r  r  r   r<   r   r[   )rA  r  r  r  r  r   r   s          rC   describe_distributionrF    sl      #<0E% U#F==?)E72C DEEV$H	rB   )r}   rF   r\   r	   )rV   r	   r\   rF   )r   r   r\   zOptional[DistributionManifest])r   r   r   rb   r\   r   )r   rF   r\   zTuple[int, int, int])r   rF   r   rF   r\   None)r   rb   r\   rF   )r   rF   r\   rJ   )r   rF   r   r   r\   rG  )rl   rF   r   r   r\   zTuple[Path, str])r   r   r\   rG  )r   r   r\   rJ   )r   r   r\   r   r  )rl   rF   r   r   r  rL   r\   r   )
r   r   r   r   r   rb   r)  rJ   r\   rG  )r   r   r\   rG  )NFF)
rl   rF   rG   rL   r8  rJ   r9  rJ   r\   r   )F)rA  rF   rB  rJ   r\   r   )rA  rF   r\   r^   )7r@   
__future__r   r   r   r   r6  dataclassesr   r   r   r   pathlibr   typingr	   r
   r   r   r   agent.skill_utilsr   rp   r$  r&  r   r_   r9   r:   r   r<   rE   rb   r   r   r   r   compiler   r   r   r   r   r   r   r   r   r   r  r  r*  r-  r=  rD  rF  rA   rB   rC   <module>rN     s  ;z # 	    ( '  3 3 4 ( ' %  
' O  !* + ! I 6;	 ;   > I( I( I(X K	6 @AH
H,0 P"(V
& 	 	 	 $(777 !7 	7t3%3%3% #3% 	3%
 
3%l8 	((
( ( 	(
 (Z 111 1rrB   