
    sjn                       U d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlmZ ddlmZmZmZ ddlmZmZmZmZ ddlmZ  ej        e          Z ej        d          Z e            Zeee df                  e!d	<   g d
Z"g dZ#ddgZ$g dZ%e&e          e!d<    e'h d          Z(e'e          e!d<    e'h d          Z)e'e          e!d<   dZ*dede+fdZ,defdZ- e'h d          Z. e'h d          Z/ e'h d          Z0 e'h d          Z1defdZ2defdZ3defd Z4defd!Z5d"e de fd#Z6d"e ddfd$Z7d"e ddfd%Z8d"e defd&Z9d"e de+fd'Z:d"e dee          fd(Z;de+fd)Z<d|d"e d*ee          dee         fd+Z=d"e de+fd,Z>deddfd-Z?d.e dee          fd/Z@d0ZAdeBe e f         fd1ZCe G d2 d3                      ZDdedefd4ZEdedefd5ZFdede+fd6ZGi ZHeBe eeIeIeJf         f         e!d7<   d8ZKd9edeIfd:ZLdedeJfd;ZMdedefd<ZNdedeBfd=ZOdddd>ded?ee          d@ee+         dAee          ddf
dBZPd"e dAee          de fdCZQd.e dAe de fdDZRdeeD         fdEZS	 d|dFe+dGeee                   deee ef                  fdHZT	 	 	 	 	 	 d}d"e dJee          dKe+dLe+dMe+dNe+d?ee          defdOZUd~dedPe+deeB         fdQZVd~dPe+dee          fdRZWdSe dede&eJ         fdTZXdSe deddfdUZYdeddfdVZZd~d"e dWe+defdXZ[d.e ddfdYZ\d.e ddfdZZ]d"e deddfd[Z^deddfd\Z_de fd]Z`d"e ddfd^Zade fd_Zbd`efdaZcdbe d`e dce de fddZd e'h de          Ze e'dfh          Zfdgede+fdhZgdieddfdjZhd|d"e dke dleee e f                  defdmZidne dee          fdoZjdpedqeddfdrZkdpedee          fdsZld|dte d"ee          defduZmdve dwe dxeddfdyZndve dwe defdzZod.e de fd{ZpdS )u_  
Profile management for multiple isolated Hermes instances.

Each profile is a fully independent HERMES_HOME directory with its own
config.yaml, .env, memory, sessions, skills, gateway, cron, and logs.
Profiles live under ``~/.hermes/profiles/<name>/`` by default.

The "default" profile is ``~/.hermes`` itself — backward compatible,
zero migration needed.

Usage::

    hermes profile create coder          # fresh profile + bundled skills
    hermes profile create coder --clone  # also copy config, .env, SOUL.md, skills
    hermes profile create coder --clone-all  # full copy of source profile
    coder chat                           # use via wrapper alias
    hermes -p coder chat                 # or via flag
    hermes profile use coder             # set as sticky default
    hermes profile delete coder          # remove profile + alias + service
    N)	dataclass)PathPurePosixPathPureWindowsPath)DictListOptionalTuple)is_excluded_skill_pathz^[a-z0-9][a-z0-9_-]{0,63}$.!_WARNED_MISSING_ALLOWLIST_ENTRIES)	memoriessessionsskillsskinslogsplans	workspacecronhome)config.yaml.envSOUL.mdzmemories/MEMORY.mdzmemories/USER.md)gateway.pidgateway_state.jsonprocesses.json_CLONE_ALL_STRIP>   binprofilesnode_modules
.worktreeshermes-agent_CLONE_ALL_DEFAULT_EXCLUDE_ROOT>   state-snapshotsbackupsr   checkpointsstate.dbstate.db-shmstate.db-wal_CLONE_ALL_HISTORY_EXCLUDE_ROOTz.no-bundled-skillsprofile_dirreturnc                 ^    	 | t           z                                  S # t          $ r Y dS w xY w)z>Return True if the profile opted out of bundled-skill seeding.F)NO_BUNDLED_SKILLS_MARKERexistsOSErrorr*   s    7/home/agent/.hermes/hermes-agent/hermes_cli/profiles.pyhas_bundled_skills_opt_outr2      sA    66>>@@@   uus    
,,
source_dirc                     |                                  t                                                       k    dt          dt          t                   dt          t                   ffd}|S )uW  Exclude infrastructure artifacts when cloning a profile via --clone-all.

    Three categories:
      1. Root-level entries in ``_CLONE_ALL_HISTORY_EXCLUDE_ROOT`` — session
         history, backups, and snapshots that belong to the SOURCE profile
         and should never carry into a fresh clone.  Applies to any source.
      2. Root-level entries in ``_CLONE_ALL_DEFAULT_EXCLUDE_ROOT`` — known
         Hermes infrastructure directories that only the default profile
         (``~/.hermes``) ever contains.  Gated on ``source_dir`` actually
         being the default profile so a named-profile source never has its
         own data silently dropped.
      3. Universal exclusions at any depth — Python bytecode caches that
         are stale or regenerable (``__pycache__``, ``*.pyc``, ``*.pyo``)
         and runtime sockets / temp files (``*.sock``, ``*.tmp``).

    The export-side ignore (``_default_export_ignore``) uses the same
    two-tier pattern with the broader ``_DEFAULT_EXPORT_EXCLUDE_ROOT`` set
    because the export archive is a portable snapshot rather than a live
    clone.
    	directorynamesr+   c                 z   g }|D ]}|dk    s|                     d          r|                    |           3	 t          |                                           k    }n# t          t
          f$ r d}Y nw xY w|r?|t          v r|                    |           r|t          v r|                    |           |S )N__pycache__)z.pycz.pyo.sock.tmpF)endswithappendr   resolver/   
ValueErrorr)   r"   )r5   r6   ignoredentryat_rootis_default_sourcesource_resolveds        r1   _ignorez+_clone_all_copytree_ignore.<locals>._ignore   s     	* 	*E &&>>"CDD ' u%%% y//1133FZ(        	 
  *;;;NN5)))$ *2Q)Q)QNN5)))s   %A  A65A6)r=   _get_default_hermes_homestrr   )r3   rD   rB   rC   s     @@r1   _clone_all_copytree_ignorerG      s|    * !((**O'+C+E+E+M+M+O+OO3 tCy T#Y       6 N    >   	auth.lock
errors.log.update_check.hermes_historyhermes_state.dbresponse_store.dbresponse_store.db-shmresponse_store.db-walr   r   r   	sandboxesaudio_cacher%   image_cacher   active_profiledocument_cachebrowser_screenshotsr   r&   	auth.jsonr    r   r!   r'   r(   r   r   >   USER.md	AGENTS.md	CLAUDE.md	MEMORY.md	todo.jsondesktop.jsonsystem_prompt.mdr   r   pluginsscriptsr   r   	knowledgepreferencesr   r   .cursorrules>   tmprootsudotesthermesdefault>   acpmcpchatr   dumploginmodelsetuptoolsconfigdoctorhonchologoutr   statusupdategatewaypairingr_   profileversioninsightsr   whatsapp	uninstallc                  $    t                      dz  S )a  Return the directory where named profiles are stored.

    Anchored to the hermes root, NOT to the current HERMES_HOME
    (which may itself be a profile).  This ensures ``coder profile list``
    can see all profiles.

    In Docker/custom deployments where HERMES_HOME points outside
    ``~/.hermes``, profiles live under ``HERMES_HOME/profiles/`` so
    they persist on the mounted volume.
    r   rE    rH   r1   _get_profiles_rootr     s     $%%
22rH   c                  "    ddl m}   |             S )zReturn the default (pre-profile) HERMES_HOME path.

    In standard deployments this is ``~/.hermes``.
    In Docker/custom deployments where HERMES_HOME is outside ``~/.hermes``
    (e.g. ``/opt/data``), returns HERMES_HOME directly.
    r   get_default_hermes_root)hermes_constantsr   r   s    r1   rE   rE     s%     988888""$$$rH   c                  $    t                      dz  S )z2Return the path to the sticky active_profile file.rT   r   r   rH   r1   _get_active_profile_pathr   (  s    #%%(888rH   c                  4    t          j                    dz  dz  S )z)Return the directory for wrapper scripts.z.localr   )r   r   r   rH   r1   _get_wrapper_dirr   -  s    9;;!E))rH   namec                     t          | t                    st          |           } |                                 }|st          d          |                                dk    rdS |                                S )u  Return the canonical profile id used on disk and in CLI ``-p`` argv.

    Named profiles are stored lowercase under ``profiles/<id>/``. The special
    alias ``default`` is matched case-insensitively (``Default`` → ``default``).
    Dashboards and tools may pass title-cased display labels; normalize before
    validation, assignment, and subprocess spawn (see issue #18498).
    zprofile name cannot be emptyri   )
isinstancerF   stripr>   casefoldlower)r   strippeds     r1   normalize_profile_namer   6  sq     dC   4yyzz||H 97888i''y>>rH   c                     | dk    rdS t                               |           st          d| d          | t          v rt          d| d          dS )u  Raise ``ValueError`` if *name* is not a valid profile identifier.

    Validates the input as-given — strict lowercase match. Callers that accept
    mixed-case or title-cased input from users (dashboard UI, CLI args) should
    call :func:`normalize_profile_name` first. This separation keeps validate
    honest about what the on-disk directory name must look like, while
    ingress-point normalization handles UX flexibility (see #18498).

    Also rejects names in :data:`_RESERVED_NAMES` (``hermes``, ``test``,
    ``tmp``, ``root``, ``sudo``) that would create confusing on-disk
    collisions (a ``hermes`` profile inside ``~/.hermes/``) or get refused
    at alias-creation time anyway. ``default`` is a special pass-through —
    it's a valid alias for the built-in root profile.
    ri   NzInvalid profile name %. Must match [a-z0-9][a-z0-9_-]{0,63}zProfile name uz    is reserved — it collides with either the Hermes installation itself or a common system binary.  Pick a different name.)_PROFILE_ID_REmatchr>   _RESERVED_NAMESr   s    r1   validate_profile_namer   H  s     y%% 
*D * * *
 
 	
 &D & & &
 
 	
 rH   c                 `    t                               |           st          d| d          dS )u  Raise ``ValueError`` if *name* is not a safe wrapper-alias identifier.

    The alias is used verbatim as a filename under :func:`_get_wrapper_dir`
    (``~/.local/bin``), so it must be a single safe command name with no path
    separators or traversal segments — otherwise a value like ``../../.bashrc``
    would escape the wrapper directory and clobber arbitrary user files. We
    reuse the profile id regex, which already forbids ``/``, ``.``, and ``..``.
    zInvalid alias name r   N)r   r   r>   r   s    r1   validate_alias_namer   f  sL     %% 
*$ * * *
 
 	

 
rH   c                 j    t          |           }|dk    rt                      S t                      |z  S )z4Resolve a profile name to its HERMES_HOME directory.ri   )r   rE   r   r   canons     r1   get_profile_dirr   v  s7    "4((E	')))%''rH   c                 r    t          |           }|dk    rdS t          |                                          S )z)Check whether a profile directory exists.ri   T)r   r   is_dirr   s     r1   profile_existsr   ~  s9    "4((E	t5!!((***rH   c                    t          |           }	 t          |           n&# t          $ r}t          |          cY d}~S d}~ww xY w|t          v rd| dS |t
          v rd| dS t                      }t          j        dk    }	 t          j
        |rdnd|gddd	d
d          }|j        dk    r|j                                                                        d         }||r| dn|z  }|t          |          k    r.	 |                    d	          }d|v rdS n# t           $ r Y nw xY wd| d| dS n# t"          t          j        f$ r Y nw xY wdS )zReturn a human-readable collision message, or None if the name is safe.

    Checks: alias-name validity, reserved names, hermes subcommands, existing
    binaries in PATH.
    N'z' is a reserved namez$' conflicts with a hermes subcommandwin32wherewhichTutf-8replace   )capture_outputtextencodingerrorstimeoutr   .batr   	hermes -pz&' conflicts with an existing command ())r   r   r>   rF   r   _HERMES_SUBCOMMANDSr   sysplatform
subprocessrun
returncodestdoutr   
splitlines	read_text	ExceptionFileNotFoundErrorTimeoutExpired)	r   r   excwrapper_dir
is_windowsresultexisting_pathexpectedcontents	            r1   check_alias_collisionr     s    #4((EE""""   3xx.5....###>5>>>> #$$K(J"/WW7dWYXY
 
 
 !!"M//11<<>>qAM"
&MnnnnNHH--&00'0BBG"g--#t .    DTuTTMTTTT " z89    4sN   ! 
A?AAA;D7  D D7 
D*'D7 )D**D7 7EEc                      t          t                                } | t          j                            dd                              t          j                  v S )z!Check if ~/.local/bin is in PATH.PATH )rF   r   osenvirongetsplitpathsep)r   s    r1   _is_wrapper_dir_in_pathr     sA    &(())K"*..44::2:FFFFrH   targetc                 r   t          |           }|rt          |          n|}t          |           t                      }	 |                    dd           n-# t          $ r }t          d| d|            Y d}~dS d}~ww xY wt          j        dk    }|rS|| dz  }	 |                    d| d	d
           |S # t          $ r }t          d| d|            Y d}~dS d}~ww xY w||z  }	 t          j
        d          pd}|                    dt          j        |           d| dd
           |                    |                                j        t          j        z  t          j        z  t          j        z             |S # t          $ r }t          d| d|            Y d}~dS d}~ww xY w)u  Create a shell wrapper script at ~/.local/bin/<name>.

    The wrapper file is named after ``name`` (the alias). The profile it
    activates is ``target`` if given, otherwise ``name`` — this lets a custom
    alias name point at a differently-named profile without a post-hoc rewrite.

    On Windows, creates a ``.bat`` file instead of a POSIX shell script.
    Returns the path to the created wrapper, or None if creation failed.
    Tparentsexist_oku   ⚠ Could not create : Nr   r   z@echo off
hermes -p z %*
r   r   u    ⚠ Could not create wrapper at rh   z#!/bin/sh
exec z -p z "$@"
)r   r   r   mkdirr/   printr   r   
write_textshutilr   shlexquotechmodstatst_modeS_IEXECS_IXGRPS_IXOTH)	r   r   r   rz   r   er   wrapper_path
hermes_exes	            r1   create_wrapper_scriptr     s;    #4((E06A$V,,,EG "$$K$6666   8k88Q88999ttttt (J "^^^3	##$Ng$N$N$NY`#aaa 	 	 	H\HHQHHIII44444	 #U*	h//;8J##$du{:7N7N$d$dT[$d$d$dov#www|0022:T\IDLX[_[gghhh 	 	 	H\HHQHHIII44444	sI   A 
B#A>>B!B> >
C(C##C(1BF 
F6F11F6c                    t                      }t          |           }	 t          |           n# t          $ r Y dS w xY wt          j        dk    }||z  g}|r|                    d|| dz             |D ]Y}|                                rC	 |                    d          }d|v r|	                                  dS I# t          $ r Y Uw xY wZdS )	zARemove the wrapper script for a profile. Returns True if removed.Fr   r   r   r   r   r   T)r   r   r   r>   r   r   insertr.   r   unlinkr   )r   r   r   r   
candidatesr   r   s          r1   remove_wrapper_scriptr     s+   "$$K"4((EE""""   uu(J %&J ;![e>>>9:::" 	 	   	&00'0BB')) '')))44 *    	 5s   / 
==.B??
CCc                 (   | dz  }|                                 sdS 	 ddlm}m} ddlm}m}  |t          |                     }	  |            \  }}||k     r |dd            ||           dS #  ||           w xY w# t          $ r Y dS w xY w)	u  Bring a copied profile config.yaml up to the current schema.

    Profile creation can clone a config file that predates schema tracking (no
    ``_config_version``) or that is simply older than the running Hermes. If we
    leave it untouched, the first desktop/doctor view of the new profile shows a
    scary ``v0 → latest`` warning even though we just created the profile. Scope
    the normal migration pipeline to the new profile and keep it non-interactive.
    r   Nr   )reset_hermes_home_overrideset_hermes_home_override)check_config_versionmigrate_configFT)interactivequiet)	r.   r   r   r   hermes_cli.configr   r   rF   r   )	r*   config_pathr   r   r   r   tokencurrent_ver
latest_vers	            r1   #_migrate_profile_config_if_outdatedr     s     -K YYYYYYYYJJJJJJJJ(([)9)9::	.&:&:&<&<#KZ''5====&&u-----&&u----    		s)   (B  A3 &B 3B  B 
BBprofile_namec                 ^    t                                          t          |                     S )u  Return the alias name of the wrapper that activates *profile_name*, or None.

    A wrapper created by :func:`create_wrapper_script` is a file named after the
    alias whose body invokes ``hermes -p <profile>``. When the alias name equals
    the profile name this is trivial, but a custom alias (``hermes profile alias
    <profile> --name <custom>``) produces a differently-named file — so the
    display side cannot assume ``wrapper == profile`` and must reverse-look-up.

    A custom alias (name != profile) is preferred over the profile-named wrapper
    so ``profile list``/``show`` surface the command the user actually typed.
    Results are sorted for deterministic output when several aliases match.

    For listing ALL profiles at once, prefer :func:`build_alias_map` — calling
    this per-profile re-reads every wrapper file N times (O(N*M)); on a wrapper
    dir like ``~/.local/bin`` that also holds large unrelated binaries (ffmpeg
    etc.) that meant multi-second ``list_profiles`` latency and desktop timeouts.
    )build_alias_mapr   r   )r   s    r1   find_alias_for_profiler   "  s'    $   !7!E!EFFFrH   i    c                  X   t                      } i }|                                 s|S t          j        dk    }d}t	          |                                           D ]N}|                                s|r|j        dk    r&|s|j        r0	 t          |ddd          5 }|	                    t                    }ddd           n# 1 swxY w Y   n# t          t          f$ r Y w xY w|                    |          }|d	k    r||t          |          z   d         }|                                r.|                    dd
          d                                         nd}	|	st#          |	          }	|r|j        n|j        }
|
|	k    r|                    |	|
           I|
||	<   P|S )a  Single-pass reverse map ``{canonical_profile -> alias_name}``.

    Scans the wrapper dir ONCE (vs. :func:`find_alias_for_profile` per profile)
    and reads only a small head slice of each candidate wrapper, skipping
    binaries. A custom alias (file name != profile) wins over the profile-named
    wrapper, matching ``find_alias_for_profile``'s preference; deterministic via
    sorted iteration.
    r   z
hermes -p r   rr   strict)r   r   N   r   r   )r   r   r   r   sortediterdiris_filesuffixopenread_WRAPPER_READ_LIMITr/   UnicodeDecodeErrorfindlenr   r   r   stemr   
setdefault)r   r   r   prefixr@   fr   idxrestr   aliass              r1   r   r   @  s    #$$KF (JF++--.. " "}} 	 	%,&00 	el 		eS78DDD 6&&!4556 6 6 6 6 6 6 6 6 6 6 6 6 6 6+, 	 	 	H	 ll6"""99sS[[())*26**,,F

4##A&,,...B 	&u--(8

ej E>>eU++++!F5MMMs6   CC9CC			CC		CC%$C%c                   8   e Zd ZU dZeed<   eed<   eed<   eed<   dZe	e         ed<   dZ
e	e         ed<   d	Zeed
<   dZeed<   dZe	e         ed<   dZe	e         ed<   dZe	e         ed<   dZe	e         ed<   dZe	e         ed<   dZeed<   d	Zeed<   dZeed<   dS )ProfileInfoz$Summary information about a profile.r   path
is_defaultgateway_runningNro   providerFhas_envr   skill_count
alias_path
alias_namedistribution_namedistribution_versiondistribution_sourcer   descriptiondescription_autodisplay_name)__name__
__module____qualname____doc__rF   __annotations__r   boolro   r	   r  r  r  intr  r  r  r  r  r   r!  r"  r   rH   r1   r  r  u  s*        ..
III
JJJE8C="Hhsm"""GTK!%J%%% !%J$$$'+x}+++*.(3-...)-#--- K
 #d""" L#rH   r  c                    | dz  }|                                 sdS 	 ddl}t          |dd          5 }|                    |          pi }ddd           n# 1 swxY w Y   t	          |t
                    sdS |                    d          |                    d	          |                    d
          fS # t          $ r Y dS w xY w)u  Return ``(name, version, source)`` from the profile's ``distribution.yaml``
    if present; ``(None, None, None)`` otherwise.

    Failures (missing file, bad YAML) are swallowed — a bad manifest should
    never break ``hermes profile list`` for an unrelated profile.
    zdistribution.yaml)NNNr   Nr   r   r   r   r{   source)r  yamlr  	safe_loadr   dictr   r   )r*   mf_pathr,  r  datas        r1   _read_distribution_metar1    s'    //G??   '3111 	+Q>>!$$*D	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+$%% 	$##HHVHHYHHX
 	

        s:   B7 AB7 AB7 AB7 9=B7 7
CCc                    | dz  }|                                 sdS 	 ddlm}  ||          }|                    di           }t	          |t
                    r|dfS t	          |t                    r?|                    d          p|                    d          |                    d          fS dS # t          $ r Y dS w xY w)	zLRead model/provider from a profile's config.yaml. Returns (model, provider).r   )NNr   )read_user_config_rawro   Nri   r  )r.   r   r3  r   r   rF   r.  r   )r*   r   r3  cfg	model_cfgs        r1   _read_config_modelr6    s    -K z 	;:::::"";//GGGR((	i%% 	#d?"i&& 	a==++Ey}}W/E/Ey}}U_G`G```z   zzs   ?B3 AB3 3
C Cc                     	 ddl m}  || dz  d          	 dS n# t          $ r Y nw xY w	 ddl m}m}  || d	z            } ||| 
          duS # t          $ r Y dS w xY w)u  Check if a gateway is running for a given profile directory.

    Primary signal is the profile's ``gateway.pid`` (verified against the
    runtime lock).  That check fails closed whenever the lock isn't held by
    *this* reader — which is exactly the case for a dashboard process that is
    a separate s6 service from the gateway it's reporting on (Docker), or any
    launch-service-managed gateway that left a fresh ``gateway_state.json`` but
    no live PID file.  In those cases fall back to validating the PID recorded
    in the profile's own ``gateway_state.json`` against the live process table,
    mirroring the ``/api/status`` sidebar's liveness logic so the two surfaces
    agree.  Parameterized by ``profile_dir`` so it never mutates ``HERMES_HOME``.
    r   )get_running_pidr   F)cleanup_staleNT)get_runtime_status_running_pidread_runtime_statusr   )expected_home)gateway.statusr8  r   r:  r;  )r*   r8  r:  r;  runtimes        r1   _check_gateway_runningr?    s    222222OK-7uMMM 4    	
 	
 	
 	
 	
 	
 	
 	
 &%k4H&HII--g[QQQY]]]   uus    
))$A 
A A _SKILL_COUNT_CACHEg      >@
skills_dirc                 z   	 |                                  j        }n# t          $ r Y dS w xY w	 t          j        |           5 }|D ]L}	 |                    d          r#|                     d          j        }||k    r|}=# t          $ r Y Iw xY w	 ddd           n# 1 swxY w Y   n# t          $ r Y nw xY w|S )a\  Cheap change-signature for a skills tree.

    Max mtime of ``skills_dir`` and its immediate children (category dirs).
    Adding/removing a category bumps ``skills_dir``'s mtime; adding/removing a
    skill inside a category bumps that category dir's mtime. One ``scandir``
    (not a recursive walk) keeps this O(#categories), not O(#files).
    g        F)follow_symlinksN)r   st_mtimer/   r   scandirr   )rA  sigitr@   ms        r1   _skills_dir_signaturerI    s@   oo(   ssZ
## 	r  ||E|:: $!JJuJ==Fs77"#C   H	 	 	 	 	 	 	 	 	 	 	 	 	 	 	    Jse    
**B+ B9BB
BBBBB+ B##B+ &B#'B+ +
B87B8c                    | dz  }|                                 sdS t          |          }t          |          }t          j                    }t                              |          }|(|d         |k    r||d         z
  t          k     r|d         S d}|                    d          D ]}t          |          r|dz  }|||ft          |<   |S )zECount installed skills in a profile (cached by skills-dir signature).r   r   Nr     zSKILL.md)	r   rF   rI  timer@  r   _SKILL_COUNT_TTL_SECONDSrglobr   )r*   rA  key	signaturenowcachedcountmds           r1   _count_skillsrU    s    x'J q
j//C%j11I
)++C##C((F1I""6!9_ 888ayEz**  !"%% 	
(#u5sLrH   c                     | dz  S )Nzprofile.yamlr   r0   s    r1   _profile_yaml_pathrW  6  s    ''rH   c                 V   dddd}t          |           }|                                s|S 	 ddl}t          |dd          5 }|                    |          pi }ddd           n# 1 swxY w Y   n# t
          $ r |cY S w xY wt          |t                    s|S t          |	                    d	          pd          
                                t          |	                    d
d                    t          |	                    d          pd          
                                dS )u+  Read ``<profile_dir>/profile.yaml`` and return a dict.

    Returns ``{"description": "", "description_auto": False,
    "display_name": ""}`` when the file is missing or unreadable. Never
    raises — a corrupt profile.yaml on an unrelated profile must not
    break ``hermes profile list``.
    r   Fr   r!  r"  r   Nr   r   r   r   r!  r"  )rW  r  r,  r  r-  r   r   r.  rF   r   r   r(  )r*   emptyr  r,  r  r0  s         r1   read_profile_metar[  :  su    E2NNEk**D<<>> $g... 	+!>>!$$*D	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+   dD!! 488M228b99??AA *<e!D!DEEDHH^44:;;AACC  s5   A3 A'A3 'A++A3 .A+/A3 3BBrY  r   r!  r"  c                   |                                  st          d|            ddl}t          |           }i }|                                rl	 t          |dd          5 }|                    |          pi }ddd           n# 1 swxY w Y   t          |t                    r|}n# t          $ r i }Y nw xY w||
                                |d<   |t          |          |d<   |B|
                                r|
                                |d	<   n|                    d	d           dd
lm}	  |	||d           dS )zUpdate ``<profile_dir>/profile.yaml`` in place.

    Only the explicitly passed fields are overwritten; unspecified
    fields preserve existing values. Creates the file if missing.
    Profile directory itself must exist.
    z"profile directory does not exist: r   Nr   r   r   r   r!  r"  )atomic_yaml_writeF)	sort_keys)r   r   r,  rW  r  r  r-  r   r.  r   r   r(  poputilsr]  )
r*   r   r!  r"  r,  r  existingr  loadedr]  s
             r1   write_profile_metarc  U  s     T R[ R RSSSKKKk**DH||~~ 	dC'222 1a**0b1 1 1 1 1 1 1 1 1 1 1 1 1 1 1&$'' "! 	 	 	HHH	"-"3"3"5"5#'+,<'='=#$ 	/'3'9'9';';H^$$LL... ('''''dH666666s6   B* #B;B* BB* BB* *B98B9c                 R    |pd                                 }|r|| k    r| d|  dn| S )uW  Render a profile for display: ``display_name (canonical_id)``.

    Falls back to the bare canonical id when no display name is set (or it
    equals the id) — byte-for-byte the pre-feature rendering. Display names
    are presentation-only free text (Unicode fine); they are never a
    directory name, wrapper filename, or argv token.
    r    (r   )r   )r   r"  dns      r1   format_profile_labelrg    sC     
"	#	#	%	%B!:bDjjbDd:rH   c                 b   t          |           }t          |           t          |          }|                                st	          d| d          |pd                                }t          |          dk    r t          dt          |           d          t          ||           |S )zSet (or clear, with ``""``) a profile's user-facing display name.

    Presentation-only: the canonical profile id is untouched. Returns the
    stored value. Raises ``ValueError`` for names over 64 chars.
    	Profile '' does not exist.r   @   zDisplay name too long (z chars, max 64).)r"  )	r   r   r   r   r   r   r  r>   rc  )r   r"  r   r*   cleaneds        r1   set_profile_display_namerm    s     #<00E%   !%((K F DE D D DEEE!r((**G
7||bQ3w<<QQQRRR{9999NrH   c                     g } t                      }t                      }|                                rt          |          \  }}t	          |          \  }}}t          |          }|                     t          d|dt          |          |||dz  	                                t          |          ||||                    dd          |                    dd          |                    dd          	                     t                      }	|	                                rt                      }
t          |	                                          D ]}|                                s|j        }|dk    r&t"                              |          sAt          |          \  }}|
                    t'          |                    }|rt(          j        d
k    }||r| dn|z  }nd}t	          |          \  }}}t          |          }|                     t          di d|d|dddt          |          d|d|d|dz  	                                dt          |          d|r|	                                r|ndd|d|d|d|d|                    dd          d|                    dd          d|                    dd                     | S )z4Return info for all profiles, including the default.ri   Tr   r   r   r!  Fr"  )r   r  r  r  ro   r  r  r  r  r  r  r   r!  r"  r   r   Nr   r  r  r  ro   r  r  r  r  r  r  r  r  r   )r   rE   r   r6  r1  r[  r<   r  r?  r.   rU  r   r   r   r  r  r   r   r   r   r   r   )r   r   default_homero   r  	dist_namedist_versiondist_sourcemetaprofiles_root	alias_mapr@   r   r  r   r  s                   r1   list_profilesrv    ss   H"$$K ,--L ,\::x/F|/T/T,	< ..2<@@!F*2244%l33'!- +33!XX&8%@@."55
 
 
 	 	 	$ '((M ' $%%	M113344 "	 "	E<<>> :Dy  !''-- 077OE8"'=d'C'CDDJ " \W4
(:,]z,?,?,?,?S]^

!
3J53Q3Q0I|[$U++DOOK   TU !5 !7u = = =	
 e " //111 *%000 +5W9J9J9L9LW::SW &: #,) &2\ %0K !HH]B777 "&*<e!D!D!D  "XXnb999!    & OrH   	multiplexprofile_allowlistc                    t                      pd}| s|t          |          fgS dt                      fg}d}|tt                      }|D ]c}t	          |t
                    s	 t          |          }t          |           n# t          $ r Y Dw xY w|dk    r|	                    |           dt                      }|                                rt          |                                          D ]^}|                                s|j        }|dk    r%t                              |          s@|||vrG|                    ||f           _||t%          t          |d |D             z
                      }|rQ|t&          vrHt&          	                    |           t(                              dd                    |                     |S )ue  Return the ``(profile_name, hermes_home)`` pairs a gateway should serve.

    This is the single chokepoint for "which profiles does the inbound gateway
    handle" so later multiplexing phases never re-derive the set.

    - ``multiplex=False`` (default): returns exactly one entry for the *active*
      profile — byte-for-byte the single-profile behavior the gateway has
      always had. The name is ``"default"`` for the default profile or the
      active named profile's id.
    - ``multiplex=True``: returns the default profile plus every valid named
      profile under ``profiles/``, each paired with its own HERMES_HOME. When
      ``profile_allowlist`` is provided, only selected named profiles are
      included; the default profile is always served.

    Intentionally lightweight (a directory scan + name validation only): no
    per-profile config reads, gateway-running probes, or skill counts like
    :func:`list_profiles`. It runs on gateway startup and must stay cheap.

    The returned ``hermes_home`` is the path to pass to
    ``set_hermes_home_override`` when scoping a turn to that profile.
    ri   Nc                     h | ]\  }}|S r   r   ).0r   _s      r1   	<setcomp>z$profiles_to_serve.<locals>.<setcomp>(  s    )D)D)D74$)D)D)DrH   zCSkipping missing gateway.multiplex_profile_allowlist profile(s): %sz, )get_active_profile_namer   rE   setr   rF   r   r   r>   addr   r   r  r  r   r   r   r<   tupler   loggerwarningjoin)	rw  rx  activeserveallowedr@   r   rt  missings	            r1   profiles_to_server    s   2 %&&3)F 300122&/1I1K1K%L$ME"&G$%%& 		" 		"EeS)) -e44%d++++   y  D!!!&((M (M113344 
	( 
	(E<<>> :Dy  !''-- "t7':':LL$''''w)D)De)D)D)DDEEFF 	w&GGG-11':::NNU		'""  
 Ls   #B
BBF
clone_from	clone_allclone_configno_alias	no_skillsc                    |r||s|rt          d          t          |           }t          |           |dk    rt          d          t          |          }|                                rt          d| d|           d}	||s|rk|ddlm}
  |
            }	n-t          |          }t          |           t          |          }	|	                                st          d	|pd
 d|	           |rL|	rJt          j        |	|dt          |	                     t          D ]}||z                      d           nB|                    dd           t           D ]}||z                      dd           |	t"          D ]o}|	|z  }|                                rT||z  }t          j        ||           |dk    r4	 t'          j        t+          |          d           _# t,          $ r Y kw xY wp|	dz  }|                                rt          j        ||dz  dd           t.          D ]Q}|	|z  }|                                r6||z  }|j                            dd           t          j        ||           R|dz  }|                                sK	 |                    dd           t'          j        t+          |          d           n# t,          $ r Y nw xY w|dz  }|                                s/	 ddlm} |                    |d           n# t8          $ r Y nw xY w|r1	 |t:          z                      dd           n# t,          $ r Y nw xY w|st=          |           |rJ|                                r6	 tA          ||                                d           n# t8          $ r Y nw xY wtC          |           |S )a  Create a new profile directory.

    Parameters
    ----------
    name:
        Profile identifier (lowercase, alphanumeric, hyphens, underscores).
    clone_from:
        Source profile to clone from. If ``None`` and clone_config/clone_all
        is True, defaults to the currently active profile.
    clone_all:
        If True, do a full copytree of the source (all state).
    clone_config:
        If True, copy config files (config.yaml, .env, SOUL.md), installed
        skills, and selected profile identity files from the source profile.
    no_alias:
        If True, skip wrapper script creation.
    no_skills:
        If True, create an empty profile with no bundled skills, and write
        a marker file so ``hermes update`` skips re-seeding this profile's
        skills. Mutually exclusive with ``clone_config``/``clone_all`` (those
        explicitly copy skills from the source).

    Returns
    -------
    Path
        The newly created profile directory.
    Nz--no-skills is mutually exclusive with --clone / --clone-from / --clone-all (cloning explicitly copies skills from the source profile).ri   uS   Cannot create a profile named 'default' — it is the built-in profile (~/.hermes).ri  ' already exists at r   get_hermes_homezSource profile 'r  z' does not exist at Tsymlinksignore
missing_okr   r     r   )r  dirs_exist_ok# Per-profile secrets for this Hermes profile.
# API keys and tokens set here override the shell environment.
# Behavioral settings belong in config.yaml, not here.
r   r   r   )DEFAULT_SOUL_MDzThis profile opted out of bundled-skill seeding (`hermes profile create --no-skills`).
Delete this file to re-enable sync on the next `hermes update`.
F)r   r!  )"r>   r   r   r   r.   FileExistsErrorr   r  r   r   r   copytreerG   r   r   r   _PROFILE_DIRS_CLONE_CONFIG_FILEScopy2r   r   rF   r/   _CLONE_SUBDIR_FILESparentr   hermes_cli.default_soulr  r   r-   r   r   rc  _maybe_register_gateway_service)r   r  r  r  r  r  r   r   r*   r3   r  stalesubdirfilenamesrcdstsource_skillsrelpathenv_path	soul_pathr  s                        r1   create_profiler  3  sZ   H  
j,,	,J
 
 	
 #4((E%   	a
 
 	
 "%((K TR%RR[RRSSS Jl888888(**JJ/
;;J!*---(44J  "" 	#[:#9[[z[[    0+Z 0+-j99		
 	
 	
 	
 & 	: 	:E5 ((D(9999	: 	$666# 	F 	FF6!(((EEEE !/ ! ! 8+::<< !%0CLc***
  6))!HSXXu5555& ! ! ! D! '1M##%% j{X/EPTdhiiii / + + 7*::<< +%/CJ$$TD$AAALc*** V#H?? 
		K !	      HS]]E**** 	 	 	D	
 i'I 	??????  7 CCCC 	 	 	D	
  		33??T !	 @      	 	 	D	  9+K888
  {((** 	'--//!&    
  	 	 	D	 $E***sZ   "G((
G54G5 9K 
K'&K'L" "
L/.L/5M 
M"!M"$N2 2
N?>N?r   c           
      :   t          |           rg g g ddS t          t                    j        j                                        }	 t          j        t          j        ddgi t          j
        dt          |           it          |          ddddd	          }|j        d
k    rD|j                                        r+t          j        |j                                                  S |sat#          d|j                    |j                                        r1t#          d|j                                        dd                     dS # t
          j        $ r |st#          d           Y dS t(          $ r}|st#          d|            Y d}~dS d}~ww xY w)u  Seed bundled skills into a profile via subprocess.

    Uses subprocess because sync_skills() caches HERMES_HOME at module level.
    Returns the sync result dict, or None on failure.

    Profiles that opted out of bundled skills (via ``hermes profile create
    --no-skills`` — which writes ``.no-bundled-skills`` to the profile root)
    are skipped and get an empty-result dict so callers can report
    "opted out" instead of "failed".
    T)copiedupdateduser_modifiedskipped_opt_outz-cziimport json; from tools.skills_sync import sync_skills; r = sync_skills(quiet=True); print(json.dumps(r))HERMES_HOMEr   r   <   )envcwdr   r   r   r   r   r   u%   ⚠ Skill seeding returned exit code z  N   u!   ⚠ Skill seeding timed out (60s)u   ⚠ Skill seeding failed: )r2   r   __file__r  r=   r   r   r   
executabler   r   rF   r   r   r   jsonloadsr   stderrr   r   )r*   r   project_rootr   r   s        r1   seed_profile_skillsr    s    "+.. 
#	
 
 	
 >>(/7799L^TAB @2:?}c+.>.>??L!!dWYXZ
 
 
 !!fm&9&9&;&;!:fm1133444 	:M&:KMMNNN}""$$ :86=..00#688999t$    	75666tt    	42q22333ttttts&   B!E *A#E  F2	F;FFc                    g }t                      }|                                s|S t                      dz  }t          |                                          D ]}|                                rt
                              |j                  s7|j        dk    rC|dz  }|                                r]	 |	                                rt          j        ||           n|                    dd           t          j        t          |          d           |                    |j                   # t"          $ r'}| st%          d|j         d|            Y d	}~d	}~ww xY w|S )
u  Give every named profile that predates per-profile ``.env`` files one.

    Profiles created before the dashboard/CLI started seeding a ``.env``
    (PR #44792) have none, so once the Channels/Keys endpoints became
    profile-scoped those profiles stopped inheriting the root install's
    credentials and showed everything as unconfigured. To avoid breaking
    anyone on update, copy the DEFAULT install's ``.env`` into each named
    profile that lacks one — that preserves the effective credentials those
    profiles were already running with (they previously read the root
    ``.env`` via the process environment). Users can then diverge per
    profile from there.

    Falls back to the placeholder header when the default install has no
    ``.env`` itself. Never overwrites an existing profile ``.env``.

    Returns the list of profile names that received a backfilled ``.env``.
    r   ri   r  r   r   r  u%   ⚠ Could not seed .env for profile 'z': N)r   r   rE   r  r  r   r   r   r.   r  r   r  r   r   r   rF   r<   r/   r   )r   
backfilledrt  default_envr@   r  r   s          r1   backfill_profile_envsr  !  s   $ J&((M!! *,,v5K--//00 R R||~~ 	^%9%9%*%E%E 	:""6>?? 		R""$$ [(3333##O %	 $    HS]]E***ej)))) 	R 	R 	R RPejPPQPPQQQ	R s   7A=D55
E&?E!!E&r   c                 `   	 ddl }n# t          $ r g cY S w xY w	 |                                }n# t          $ r |}Y nw xY wt	          j                    h}	 |                    t	          j                                                              }|0|                    |j	                   |                                }|0n# t          $ r Y nw xY w	 |                    t	          j                              
                                }n# t          $ r d}Y nw xY wh d}d}t          j        d          }	t          h d          }
g }|                    g d          D ]}	 |j        }|                    d          }|||v r'||                    d	          |k    rC|                    d
          pg }|s]d                    |          t          j                            |d                                                   }t+          fd|D                       p|dk    p|                    d          }|st/          |          dk    r|	                    |          rjt          j                            t3          |d                                                             }d|v r|                    dd          d         n|}||
v }|swd |D             }||z  sd}t7          |          D ]\  }}|dv r7|dz   t/          |          k     r!t9          ||dz                      | k    rd} nH@|                    d          r1t9          |                    dd          d                   | k    rd} n|se	 |                                pi                     dd          }|r't?          |                                          |k    rd}n# t          $ r Y nw xY w|r|                     |           # |j!        |j"        |j#        f$ r Y t          $ r Y w xY w|S )u  PIDs of running Hermes *backends* bound to this profile.

    The ``gateway.pid`` file only tracks the messaging gateway.  A Desktop app
    spawns a headless ``serve`` (or legacy ``dashboard --no-open``) backend per
    profile that holds the profile's SQLite connection open and keeps writing
    sessions/WAL/sandbox files — the writer that makes ``rmtree`` hit
    ``ENOTEMPTY`` (and, pre-fix, resurrected the tree).  ``gateway.pid`` never
    names it, so find it by inspection: a Hermes backend subcommand
    (``serve``/``dashboard``/``gateway``) that is bound to *this* profile either
    by a ``--profile <canon>`` / ``-p <canon>`` selector or by a ``HERMES_HOME``
    that resolves to ``profile_dir``.

    Best-effort and tightly scoped: current-user processes only, backend
    subcommands only (never an interactive ``chat``/``tui``), and never this
    process or its ancestors.  Returns an empty list if ``psutil`` can't
    inspect anything.
    r   N>   r  rx   	dashboard)zhermes_cli.mainzhermes-gatewaytui_gatewayz^python[\d.]*w?(\.exe)?$>   
hermes-acprh   r!   )pidr   usernamecmdliner  r  r   c              3       K   | ]}|v V  	d S Nr   )r{  markerjoineds     r1   	<genexpr>z._profile_bound_backend_pids.<locals>.<genexpr>  s(      BBFf$BBBBBBrH   rh   rK  r  .c                 6    h | ]}|                                 S r   )r   )r{  toks     r1   r}  z._profile_bound_backend_pids.<locals>.<setcomp>  s     222cciikk222rH   F>   -p	--profileTz
--profile==r  r   )$psutilr   r=   r/   r   getpidProcessr  r  r  r  recompile	frozensetprocess_iterinfor   r  r  basenamer   any
startswithr  r   rF   rsplit	enumerater   r   r   r   r<   NoSuchProcessAccessDeniedZombieProcess)r   r*   r  resolved_dirskipr  current_userbackend_tokenshermes_markers_python_interpreter_re_HERMES_CONSOLE_SCRIPT_NAMESpidsprocr  r  argvexe_name	is_hermesscript_namescript_stemtokensboundir  env_homer  s                            @r1   _profile_bound_backend_pidsr  U  s   $   			#"**,, # # #"#
 ikk]D	,,3355 HHVZ   ]]__F      ~~bikk22;;==    766NIN  Z(CDD $--U-U-U#V#V D##$J$J$JKK K KJ	9D((5//C{cTkk'DHHZ,@,@L,P,P88I&&,"D  XXd^^Fw''Q006688HBBBB>BBBBB 1x'1&&x00 
  HTa4J4P4PQY4Z4Z !g..s47||<<BBDD?Bk?Q?Qk00a88;;Wb'+GG	  32T222F^+  E#D//  3---!a%#d))2C2C-d1q5k::eCC $ D ^^L11 -ciiQ.?.?.BCCuLL $   $ 4"99-LLH %DNN$:$:$<$<$L$L $     D
  !C   $f&96;OP 	 	 	H 	 	 	H	 Ks    0 ??A*C 
CC8D DD&"P	P%P?DPP,BPAOP
O(%P'O((PP+	P+*P+c                 b   t          | |          }|sdS 	 ddlmm} n# t          $ r Y dS w xY w|D ]+}	  ||           # t
          t          t          f$ r Y (w xY wt          j                    dz   }t          j                    |k     rGt          fd|D                       sn+t          j
        d           t          j                    |k     G|D ]9} |          r,	  ||d           # t
          t          t          f$ r Y 5w xY w:t          d	t          |           d
           dS )a  Terminate any Desktop-spawned / stray backends bound to this profile.

    Complements ``_stop_gateway_process`` (which only knows ``gateway.pid``):
    without this, a live ``serve``/``dashboard`` backend keeps creating files
    under the profile dir while ``rmtree`` walks it, so the final ``rmdir``
    fails with ``ENOTEMPTY`` and the delete doesn't converge.  Best-effort:
    any failure is reported and swallowed so it never makes delete worse.
    Nr   )_pid_existsterminate_pidg      $@c              3   .   K   | ]} |          V  d S r  r   )r{  r  r  s     r1   r  z)_stop_profile_backends.<locals>.<genexpr>  s-      44;;s##444444rH         ?Tforceu   ✓ Stopped z profile backend process(es))r  r=  r  r  r   ProcessLookupErrorPermissionErrorr/   rL  r  sleepr   r  )r   r*   r  _terminate_pidr  deadliner  s         @r1   _stop_profile_backendsr    s    'uk::D OOOOOOOOO      	N3"OW= 	 	 	H	 y{{T!H
)++
 
 4444t44444 	
3 )++
 
 
   ;s 	s$/////&A   	 

@T
@
@
@AAAAAs/     
..AAA#C11D
Dc                 j   d}d}t          |          D ]}	 	 t          j        | |           n&# t          $ r t          j        | |           Y nw xY w dS # t          $ rJ}|}|                                 sY d}~ dS ||dz
  k     rt          j        d|dz   z             Y d}~d}~ww xY w||dS )a  ``shutil.rmtree`` with a short retry loop for transient races.

    Even after stopping the gateway and profile backends, a just-terminated
    process can leave in-flight writes (SQLite ``-wal``/``-shm`` checkpoints,
    sandbox temp files) that land after ``rmtree`` has walked past a directory,
    surfacing as ``ENOTEMPTY`` (POSIX) or a transient ``PermissionError``
    (Windows file lock still releasing).  A few spaced retries let those settle
    instead of failing the whole delete on a race the next attempt would win.
       N)onexc)onerrorr  g333333?)ranger   rmtree	TypeErrorr/   r.   rL  r  )r*   onexc_handlerattemptslast_excattemptr   s         r1   _rmtree_with_retryr    s    H#H?? 0 0	0Bk????? B B Bk=AAAAAABFF 	0 	0 	0H%%'' A%%
3'A+.///	0  s8   /A AAAA
B,"B'?#B''B,yesc                    t          |           }t          |           |dk    rt          d          t          |          }|                                st          d| d          t          |          \  }}t          |          }t          |          }t          |          \  }}	}
t          d|            t          d|            |rt          d| |rd| d	nd
z              |rt          d|            |r+t          d| d|	pd            |
rt          d|
            dg}t                      |z  }|                                }|r|                    d| d	           t          d           |D ]}t          d|            |rt          d           |stt                       	 t          d| d                                          }n(# t           t"          f$ r t          d           |cY S w xY w||k    rt          d           |S t%          ||           t'          |           |rt)          |           t+          ||           	 ddlm} |                    |          }|rt          d| d           n# t2          $ r Y nw xY w|r!t5          |          rt          d|            d}	 d }t7          ||           t          d|            n.# t2          $ r!}t          d | d!|            |}Y d}~nd}~ww xY w	 t9                      }||k    rt;          d           t          d"           n# t2          $ r Y nw xY w|t=          d#| d!|           |t          d$| d%           |S )&zDelete a profile, its wrapper script, and its gateway service.

    Stops the gateway if running. Disables systemd/launchd service first
    to prevent auto-restart.

    Returns the path that was removed.
    ri   zZCannot delete the default profile (~/.hermes).
To remove everything, use: hermes uninstallri  rj  z

Profile: z	Path:    z	Model:   re  r   r   z	Skills:  zDistribution: @?zInstalled from: z;All config, API keys, memories, sessions, skills, cron jobszCommand alias (z
This will permanently delete:u     • u0     ⚠ Gateway is running — it will be stopped.zType 'z' to confirm: z
Cancelled.z
Cancelled.r   )MemoryStoreu   ✓ Released z0 memory-store connection(s) held by this processu   ✓ Removed Nc                    ddl }t          |t                    r|d         }t          |t                    r	 t	          j        |t	          j         |          j        |j        z             n# t          $ r Y nw xY wt          j	        
                    |          }|rF	 t	          j        |t	          j         |          j        |j        z             n# t          $ r Y nw xY w | |           dS  )a  onexc/onerror handler: add +w on PermissionError so rmtree can proceed.

            Handles two cases on NixOS (and other systems with read-only
            copies from immutable stores):
            1. The path itself isn't writable (e.g. a file with mode 0444)
            2. The *parent* directory isn't writable (e.g. mode 0555)

            Compatible with both the ``onexc`` API (3.12+, receives an
            exception instance) and the ``onerror`` API (3.11-, receives
            ``sys.exc_info()`` tuple).
            r   Nr  )r   r   r  r  r   r   r   S_IWUSRr/   r  dirname)funcr  r   _statr  s        r1   _make_writablez&delete_profile.<locals>._make_writable  s    !   
 #u%% !f#// HT274==#85=#HIIII   D .. )@5=)PQQQQ"   T




s#   4A- -
A:9A:4C 
C! C!u   ⚠ Could not remove r   u#   ✓ Active profile reset to defaultz#Could not remove profile directory z

Profile 'z
' deleted.)r   r   r>   r   r   r   r6  r?  rU  r1  r   r   r.   r<   inputr   KeyboardInterruptEOFError_cleanup_gateway_service!_maybe_unregister_gateway_service_stop_gateway_processr   plugins.memory.holographic.storer  release_all_underr   r   r  get_active_profileset_active_profileRuntimeError)r   r  r   r*   ro   r  
gw_runningr  rp  rq  rr  itemsr   has_wrapperitemconfirm_MemoryStore	_releasedremove_errorr  r   r  s                         r1   delete_profiler1  )  s    #4((E%   	:
 
 	

 "%((K F DE D D DEEE )55OE8'44J,,K+B;+O+O(I|[	


   	
#k
#
#$$$ L!%!!%I%5(%5%5%5%5rJKKK )'+''((( 4@y@@<+>3@@AAA 	42[22333 	FE
 $%%-L%%''K 86|666777	
+,,,  otoo B@AAA  		:U:::;;AACCGG!8, 	 	 	.!!!	 e, UK000 &e,,,  +k*** 5+...PPPPPP 22;??	 	_])]]]^^^     1 '' 	1///000 &*L*#	 #	 #	J 	;777*[**++++   8k88Q88999
#%%U??y)))7888    ^^^P\^^__eqq	
)
)
)
)***sN   <%G" ""HH"0J 
J J 	%K/ /
L9LL2M 
MMc                 X   	 ddl m}  |            dk    rdS ddl m}  |            }n# t          $ r Y dS t          $ r Y dS w xY w|                                sdS 	 |                    | d           dS # t          $ r Y dS t          $ r}t          d|            Y d}~dS d}~ww xY w)	u  Register a profile's gateway with s6 inside the container.

    No-op on host (systemd/launchd/windows) — those backends raise
    ``NotImplementedError`` on ``register_profile_gateway`` and the
    existing per-profile unit-generation paths handle lifecycle.

    Best-effort: any error (no backend detected, s6 not yet ready,
    etc.) is logged and swallowed so profile creation doesn't fail
    because the s6 supervision tree is in a weird state. The user
    can re-register manually later via the gateway start command,
    which goes through the same dispatch path.

    Port selection: each supervised profile gateway loads its own
    ``HERMES_HOME`` and binds the port resolved by ``gateway/config.py``
    from that profile's environment — ``API_SERVER_PORT`` (or
    ``platforms.api_server.extra.port`` in the profile's
    ``config.yaml``), defaulting to 8642. There is no ``[gateway] port``
    key and no Python-side allocator (PR #30136 review item I5 retired
    the SHA-256-derived range [9200, 9800) as dead code), so two
    profiles that both leave the port at its default will both try to
    bind 8642 — give each profile a distinct ``API_SERVER_PORT`` in its
    ``.env``.

    Host short-circuit: check ``detect_service_manager()`` first and
    return immediately if it isn't ``"s6"``. This keeps host
    (systemd/launchd/windows) profile creation completely silent —
    no ``get_service_manager()`` call, no exception path, no chance
    of the ``⚠ Could not register s6 gateway service`` warning ever
    rendering on a non-container machine. The earlier
    ``supports_runtime_registration()`` check still catches the case
    where detection somehow returns ``"s6"`` but the backend isn't
    actually the S6 one.
    r   detect_service_managers6Nget_service_managerF)	start_nowu+   ⚠ Could not register s6 gateway service: )	hermes_cli.service_managerr4  r7  r(  r   supports_runtime_registrationregister_profile_gatewayr>   r   r   r4  r7  mgrr   s        r1   r  r    s9   DEEEEEE!!##t++FBBBBBB!!##       		
 ,,.. C$$\U$CCCCC    	 C C CACAABBBBBBBBBCs6   ) ) 
A	AAA6 6
B)	B)B$$B)c                 <   	 ddl m}  |            dk    rdS ddl m}  |            }n# t          $ r Y dS t          $ r Y dS w xY w|                                sdS 	 |                    |            dS # t          $ r}t          d|            Y d}~dS d}~ww xY w)u  Tear down a profile's s6 gateway service inside the container.

    No-op on host. Idempotent: absent services are silently skipped
    by ``unregister_profile_gateway``.

    Same host short-circuit as :func:`_maybe_register_gateway_service`
    — see that docstring.
    r   r3  r5  Nr6  u-   ⚠ Could not unregister s6 gateway service: )r9  r4  r7  r(  r   r:  unregister_profile_gatewayr   r<  s        r1   r"  r"    s   	EEEEEE!!##t++FBBBBBB!!##      ,,.. E&&|44444 E E ECcCCDDDDDDDDDEs0   ) ) 
A	AAA4 4
B>BBc                 \   ddl }t          j                            d          }	 t	          |          t          j        d<   ddlm}m} |                                dk    r |            }t          j
                    dz  dz  dz  | d	z  }|                                r{t          j        d
dd|gddd           t          j        d
dd|gddd           |                    d           t          j        g dddd           t          d| d           n|                                dk    rk |            }|                                rMt          j        ddt	          |          gddd           |                    d           t          d           n)# t           $ r}	t          d|	            Y d}	~	nd}	~	ww xY w||t          j        d<   dS dt          j        v rt          j        d= dS dS # ||t          j        d<   ndt          j        v rt          j        d= w xY w)z9Disable and remove systemd/launchd service for a profile.r   Nr  )get_service_nameget_launchd_plist_pathLinuxz.configsystemduserz.service	systemctl--userdisableTF
   )r   checkr   stopr  )rF  rG  zdaemon-reloadu   ✓ Service z removedDarwin	launchctlunloadu   ✓ Launchd service removedu   ⚠ Service cleanup: )r   r   r   r   rF   hermes_cli.gatewayrA  rB  systemr   r   r.   r   r   r   r   r   )
r   r*   	_platformold_homerA  rB  svc_namesvc_file
plist_pathr   s
             r1   r!  r!  '  s        z~~m,,H&*$'$4$4
=!OOOOOOOO((''))Hy{{Y.:VCF[F[F[[H   9 (Ix@#'ub     (FH=#'ub    4000<<<#'ub    7X7778888++//11J  "" 5 (C
OO<#'ub    !!T!2223444 + + +)a))********+ (0BJ}%%%bj((
=))) )( (0BJ}%%bj((
=)))))s0   E;F!  G< !
G+G=G< GG< </H+c                    ddl }| dz  }|                                sdS 	 |                    d                                          }|                    d          rt          j        |          ndt          |          i}t          |d                   }ddlm	} dd	lm
}  ||           t          d
          D ]8}|                    d            ||          st          d| d            dS 9	  ||d           n# t          t          f$ r Y nw xY wt          d| d           dS # t          t           f$ r t          d           Y dS t"          $ r}	t          d|	            Y d}	~	dS d}	~	ww xY w)z0Stop a running gateway process via its PID file.r   Nr   r   r   {r  )r  )r     r  u   ✓ Gateway stopped (PID r   Tr  u   ✓ Gateway force-stopped (PID u   ✓ Gateway already stoppedu   ⚠ Could not stop gateway: )rL  r.   r   r   r  r  r  r)  r=  r  r  r  r  r   r  r/   r  r   )
r*   _timepid_filerawr0  r  r  r  r|  r   s
             r1   r#  r#  W  s   ]*H?? 2  ' 2288::"%.."5"5Ltz#E3s88;L$u+ 	CBBBBB......s r 	 	AKK;s## 8#888999	N3d+++++"G, 	 	 	D	6666777770 - - -+,,,,,, 2 2 20Q001111111112sH   CD2 6D2 8D D2 DD2 DD2 2 E;	E;E66E;c                      t                      } 	 |                     d                                          }|sdS |S # t          t          t
          f$ r Y dS w xY w)ztRead the sticky active profile name.

    Returns ``"default"`` if no active_profile file exists or it's empty.
    r   r   ri   )r   r   r   r   r	  r/   r  r   s     r1   r&  r&    so    
 $%%D~~w~//5577 	917;   yys   *> > AAc                    t          |           }t          |           |dk    r$t          |          st          d| d|           t	                      }|j                            dd           |dk    r|                    d           dS |                    d          }|	                    |dz   d	
           |
                    |           dS )zlSet the sticky active profile.

    Writes to ``~/.hermes/active_profile``. Use ``"default"`` to clear.
    ri   ri  8' does not exist. Create it with: hermes profile create Tr   r  r:   
r   r   N)r   r   r   r   r   r  r   r   with_suffixr   r   )r   r   r  rd   s       r1   r'  r'    s    
 #4((E%   	."7"7= = =5:= =
 
 	

 $%%DKdT222	t$$$$$ v&&ut|g666DrH   c                     ddl m}   |             }|                                }t                                                      }||k    rdS t	                                                      }	 |                    |          }|j        }t          |          dk    r(t          	                    |d                   r|d         S n# t          $ r Y nw xY wdS )a%  Infer the current profile name from HERMES_HOME.

    Returns ``"default"`` if HERMES_HOME is not set or points to ``~/.hermes``.
    Returns the profile name if HERMES_HOME points into ``~/.hermes/profiles/<name>``.
    Returns ``"custom"`` if HERMES_HOME is set to an unrecognized path.
    r   r  ri   r  custom)r   r  r=   rE   r   relative_topartsr  r   r   r>   )r  hermes_homeresolveddefault_resolvedrt  relre  s          r1   r~  r~    s     100000!/##K""$$H/1199;;###y&((0022M""=11	u::??~33E!H==?8O    8s   .AC 
CCroot_dirc                 >     dt           dt          dt          f fd}|S )u  Return an *ignore* callable for :func:`shutil.copytree`.

    Two-tier filtering:

    * **Root-level allow-list** — only entries whose name appears in
      ``_DEFAULT_EXPORT_INCLUDE_ROOT`` survive. Everything else (such as
      an unrelated ``x11-dev/`` directory in a Docker deployment where
      HERMES_HOME equals the cwd) is excluded. Blacklisting was tried
      first and proved unable to anticipate every non-Hermes file the
      user may have lying alongside HERMES_HOME (#58394).
    * **Universal exclusions at any depth** — ``__pycache__``, sockets,
      temp files; plus npm lockfiles, which may appear at the root.

    Surviving text files are later force-redacted by
    :func:`_scrub_export_secrets` before the archive is written.
    r5   contentsr+   c                 &   t                      }|D ]L}|dk    s|                    d          r|                    |           3|dv r|                    |           Mt          |           k    r|                    d |D                        |S )Nr8   )r9   r:   >   package.jsonpackage-lock.jsonc              3   ,   K   | ]}|t           v|V  d S r  )_DEFAULT_EXPORT_INCLUDE_ROOT)r{  r@   s     r1   r  z:_default_export_ignore.<locals>._ignore.<locals>.<genexpr>  s6        u<X/X/X/X/X/X/X rH   )r  r;   r  r   rw   )r5   rl  r?   r@   rj  s       r1   rD   z'_default_export_ignore.<locals>._ignore  s    uu 	# 	#E%%8I)J)J%E""""???E""" 	??h&&NN  #+      rH   )rF   listr  )rj  rD   s   ` r1   _default_export_ignorers    s=    $3 $ 3      " NrH   basebase_dirc                     ddl }|  d}|                    |d|j                  5 }|                    t	          t          |          |z            |           ddd           n# 1 swxY w Y   |S )u  Create ``<base>.tar.gz`` of ``root_dir/base_dir`` — GNU tar format.

    Not :func:`shutil.make_archive`: that writes PAX (Python's tarfile default
    since 3.8), whose fractional-mtime records macOS Archive Utility rejects —
    double-clicking an exported profile threw "Error 94 - Bad message." GNU
    format keeps long paths working (longlink extensions) and stays integer-
    mtime, so Finder, bsdtar, and gnutar all extract it.
    r   N.tar.gzzw:gz)format)arcname)tarfiler  
GNU_FORMATr  rF   r   )rt  rj  ru  rz  archive_pathtfs         r1   _make_profile_archiver~    s     NNN###L	lF73E	F	F A"
s4>>H,--x@@@A A A A A A A A A A A A A A As   5A((A,/A,>   .js.md.py.sh.ts.cfg.css.csv.ini.jsx.tsx.txt.xml.yml.zsh.bash.conf.html.json.toml.yaml.jsonlrc   r  c                     | j         }|t          v rdS |                                                    d          rdS | j                                        t
          v S )zETrue when *path* is a text-ish file we should secret-scrub on export.Tz.env.example)r   _EXPORT_REDACT_NAMESr   r;   r  _EXPORT_REDACT_SUFFIXESr]  s     r1   _should_redact_export_filer    sW    9D###tzz||^,, t;"999rH   stagedc                 2   ddl m} |                     d          D ]}	 |                                }n# t          $ r Y $w xY w|r;	 |                                r|                                rTn%# t          $ r Y aw xY w|                                szt          |          s	 |	                    d          }n# t          t          f$ r Y w xY w ||d          }||k    r|r|                                 |                    |d           dS )	u{  Force-redact secret-shaped strings in a staged export tree.

    Same ``agent.redact.redact_sensitive_text(..., force=True)`` pass used by
    ``hermes sessions export --redact``. Runs on the *staged copy only* so the
    live profile is never rewritten. ``force=True`` ignores
    ``security.redact_secrets`` / ``HERMES_REDACT_SECRETS`` — share archives
    must not emit raw keys even when the user has disabled live redaction.

    Symlinks to text files are materialized into regular files when their
    content changes, so redaction never follows a link back into the source
    profile (``copytree(..., symlinks=True)``).
    r   )redact_sensitive_text*r   r   Tr  N)agent.redactr  rN  
is_symlinkr/   r.   r   r  r  r   r	  r   r   )r  r  r  is_linkr   redacteds         r1   _scrub_export_secretsr    s}    322222S!! 4 4	oo''GG 	 	 	H	 	{{}}      	)$// 		>>7>33DD"G, 	 	 	H	 )(T:::t 	KKMMM73333;4 4s3   4
A A(A11
A>=A>'B>>CCoutput_pathextra_filesc           	         ddl }t          |           }t          |           t          |          }|                                st          d| d          t          |          }t          |                              d                              d          }dt          ddffd	}|d
k    r|	                                5 }	t          |	          d
z  }
t          j        ||
dt          |                      ||
           t          |
           t          ||	d
          }t          |          cddd           S # 1 swxY w Y   |	                                5 }	t          |	          |z  }
ddht          j        ||
dfd            ||
           t          |
           t          ||	|          }t          |          cddd           S # 1 swxY w Y   dS )u  Export a profile to a tar.gz archive.

    ``extra_files`` maps root-relative filenames (e.g. ``desktop.json``) to
    text content staged into the archive alongside the profile's own files —
    the desktop app uses it to bundle its appearance/interface overlay.

    Credential files (``auth.json``, ``.env``) are excluded, and secret-shaped
    strings in staged text files are force-redacted before the archive is
    written. Returns the output file path.
    r   Nri  rj  rw  z.tgzr  r+   c                     pi                                  D ]Q\  }}t          |          } | j        | }|j                            dd           |                    |d           Rd S )NTr   r   r   )r*   _normalize_profile_archive_partsjoinpathr  r   r   )r  ri  r   re  r   r  s        r1   _stage_extrasz%export_profile.<locals>._stage_extras`  s    (.B5577 	9 	9LC4S99E$V_e,FMt<<<g8888		9 	9rH   ri   Tr  rW   r   c                 (    t          |          z  S r  )r  )drl  _CREDENTIAL_FILESs     r1   <lambda>z export_profile.<locals>.<lambda>  s    '83x=='H rH   )tempfiler   r   r   r   r   r   rF   removesuffixTemporaryDirectoryr   r  rs  r  r~  )r   r  r  r  r   r*   outputrt  r  tmpdirr  r   r  s     `         @r1   export_profiler  I  s    OOO"4((E%   !%((K F DE D D DEEE+Fv;;##I..;;FCCD9d 9t 9 9 9 9 9 9 	 ((** 	 f&\\I-FO-k::	    M&!!!!&)))*4CCF<<	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  
	$	$	&	& &f%(&1HHHH		
 	
 	
 	
 	ff%%%&tVU;;F||                 s&   A1EE
E
%A+GG!$G!member_namec                 p   |                      dd          }t          |          }t          |           }|r/|                                s|                                s|j        rt          d|            d |j        D             }|rt          d |D                       rt          d|            |S )z4Return safe path parts for a profile archive member.\/zUnsafe archive member path: c                     g | ]}|d v|	S )>   r   r  r   r{  parts     r1   
<listcomp>z4_normalize_profile_archive_parts.<locals>.<listcomp>  s"    HHHd$i2G2GT2G2G2GrH   c              3   "   K   | ]
}|d k    V  dS )z..Nr   r  s     r1   r  z3_normalize_profile_archive_parts.<locals>.<genexpr>  s&      77777777rH   )r   r   r   is_absolutedriver>   re  r  )r  normalized_name
posix_pathwindows_pathre  s        r1   r  r    s    !))$44O//J";//L G!!##G ##%%G 	G EEEFFFHHj.HHHE GC7777777 GEEEFFFLrH   archivedestinationc           	      *   ddl }|                    | d          5 }|                                D ]H}t          |j                  } |j        | }|                                r|                    dd           M|                                st          d|j                   |j
                            dd           |                    |          }|t          d|j                   |5  t          |d          5 }t          j        ||           ddd           n# 1 swxY w Y   ddd           n# 1 swxY w Y   	 t          j        ||j        d	z             9# t"          $ r Y Fw xY w	 ddd           dS # 1 swxY w Y   dS )
zAExtract a profile archive without allowing path escapes or links.r   Nr:gzTr   z!Unsupported archive member type: zCannot read archive member: wbi  )rz  r  
getmembersr  r   r  isdirr   isfiler>   r  extractfiler   copyfileobjr   r   moder/   )	r  r  rz  r}  memberre  r   	extractedr  s	            r1   _safe_extract_profile_archiver    so   NNN	gv	&	& "mmoo 	 	F4V[AAE)[)51F||~~ TD999==??  EEE   Mt<<<v..I  !M!M!MNNN 3 3D.. 3#"9c2223 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3u!45555   /	                 s~   CF4D>D'	D>'D++D>.D+/D>2F>EFEF
E)'F)
E73F6E77FFFc                     ddl }|                    | d          5 }d |                                D             }|sd |                                D             }ddd           n# 1 swxY w Y   |S )a  Return the archive's top-level directory names.

    Profile imports expect exactly one root directory. Inspecting the archive
    before extraction lets us stage the import safely instead of mutating a
    live profile tree first and reconciling names later.
    r   Nr  c                     h | ]E}t          |j                  }t          |          d k    s|                                =|d         FS )r  r   )r  r   r  r  )r{  r  re  s      r1   r}  z1_inspect_profile_archive_roots.<locals>.<setcomp>  sP     
 
 
:6;GG5zzA~~~ !H ~~rH   c                 j    h | ]0}|                                 t          |j                  d          1S )r   )r  r  r   )r{  r  s     r1   r}  z1_inspect_profile_archive_roots.<locals>.<setcomp>  sE       <<>>0==a@  rH   )rz  r  r  )r  rz  r}  top_dirss       r1   _inspect_profile_archive_rootsr    s     NNN	gv	&	& "
 
--//
 
 
  	  mmoo  H               Os   ?A&&A*-A*r|  c                    ddl }t          |           }|                                st          d|           t	          |          }t          |          dk    r|                                nd}|p|}|st          d          |t          d          t          |          }t          |           |dk    rt          d          t          |          }|                                rt          d	| d
|           t                      }	|	                    dd           |                    d          5 }
t          |
          }t          ||           ||z  }|                                st          d|           |}||k    r||z  }|                    |           t%          j        t)          |          t)          |                     ddd           n# 1 swxY w Y   |S )zImport a profile from a tar.gz archive.

    If *name* is not given, infers it from the archive's top-level directory.
    Returns the imported profile directory.
    r   NzArchive not found: r  zpCannot determine profile name from archive. Specify it explicitly: hermes profile import <archive> --name <name>z=Profile archive must contain exactly one top-level directory.ri   u   Cannot import as 'default' — that is the built-in root profile (~/.hermes). Specify a different name: hermes profile import <archive> --name <name>ri  r  Tr   hermes_profile_import_)r  z,Profile archive root is missing or invalid: )r  r   r.   r   r  r  r_  r>   r   r   r   r  r   r   r  r  r   renamer   moverF   )r|  r   r  r  r  archive_rootinferred_namer   r*   rt  r  staging_rootr  final_sources                 r1   import_profiler    sz    OOO<  G>> A ?g ? ?@@@-g66H%(]]a%7%78<<>>>TL(LM 
S
 
 	
 K
 
 	
 #=11E%   	V
 
 	

 "%((K TR%RR[RRSSS&((Mt444		$	$,D	$	E	E 9F||%g|<<< </	!! 	M|MM   !5  '%/L\***C%%s;'7'78889 9 9 9 9 9 9 9 9 9 9 9 9 9 9" s   >BG&&G*-G*old_namenew_namenew_dirc                    d|  }d|  }d| }|dz  t                      dz  t          j                    dz  dz  g}t                      }|D ]4}	 |                                }	n# t
          $ r |}	Y nw xY w|	|v s|                                sD|                    |	           	 t          j	        |
                    d                    }
n# t
          t          j        f$ r Y w xY w|
                    d          }t          |t                    s||v r|n|}||vr||v rt          d	| d
|            ||         }t          |t                    r]d|vrY|                    d          r|                    dd          d         }n"d|v r|                    dd          d         n|}||d<   |                    |          ||<   |                    |j        dz             }	 |                    t          j        |
dd          dz   d           |                    |           n9# t
          $ r, 	 |                    d           n# t
          $ r Y nw xY wY w xY wt          d| d|            6dS )zGRename Honcho host blocks for a renamed profile without changing peers.hermes_zhermes.zhoncho.jsonz.honchozconfig.jsonr   r   hostsu$   ⚠ Honcho host block not migrated: z already exists in aiPeerr|  r  r  r:   rK  F)indentensure_asciir`  Tr  u   ✓ Honcho host updated:     → N)rE   r   r   r  r=   r/   r  r  r  r  r   JSONDecodeErrorr   r   r.  r   r  r   r_  ra  r  r   dumpsr   r   )r  r  r  old_hostlegacy_old_hostnew_hostr   seenr  rg  r[  r  source_hostblockbarerd   s                   r1   _migrate_honcho_profile_hostr  	  s-   ###H***O###H 	- ""]2	i-/J eeD ,H ,H	||~~HH 	 	 	HHH	t4<<>>	*T^^W^==>>CC-. 	 	 	H	   %&& 	"*e"3"3hhe##u\\\VZ\\]]]k"eT"" 	#xu'<'<%%i00 ["((a0037:k7I7I{((a0033{"E(O))K00ht{V344	NN4:c!%HHH4OZaNbbbKK 	 	 	

d
++++   H	 	F+FFHFFGGGGY,H ,Hs[   A''A65A6((CC*)C*/AH44
I*?II*
I# I*"I##I*)I*c                 V   t          |           }t          |           |dk    rV|pd                                st          d          t	          d|          }t          d| d           t                      S t          |          }t          |           |dk    rt          d          t          |          }t          |          }|                                st          d| d          |
                                rt          d| d	          t          |          rt          ||           t          |           |                    |           t          d
|j         d|j                    t#          |||           t%          |           t'          |          }|s"t)          |           t          d|            nt          d| d|            	 t+                      |k    r!t-          |           t          d|            n# t.          $ r Y nw xY w|S )u\  Rename a profile: directory, wrapper script, service, active_profile.

    The default profile's home IS the installation root, so "renaming" it
    sets a presentation-only ``display_name`` in profile.yaml instead —
    the canonical id stays ``default`` and every resolution path is
    untouched.

    Returns the (new) profile directory.
    ri   r   zDisplay name cannot be empty.u   ✓ Display name set: z! (canonical id remains 'default')u.   Cannot rename to 'default' — it is reserved.ri  rj  z' already exists.u   ✓ Renamed r  u   ✓ Alias updated: u   ⚠ Cannot create alias 'u   ' — u   ✓ Active profile updated: )r   r   r   r>   rm  r   rE   r   r   r   r.   r  r?  r!  r#  r  r   r  r   r   r   r&  r'  r   )r  r  	old_canonrl  	new_canonold_dirr  	collisions           r1   rename_profiler  T	  sz    'x00I)$$$IB%%'' 	><===*9h??QwQQQRRR')))&x00I)$$$IIJJJi((Gi((G>> J HI H H HIII~~ HF)FFFGGG g&& ' G444g&&& NN7	
:
:
:GL
:
:;;; !Iw??? )$$$%i00I Hi(((/I//0000F)FF9FFGGG9,,y)))<<<===    Ns   %3H 
H&%H&c                    t          |           }t          |           t          j                            dd                                          }|r.t          |          }|j        j        dk    r|j        j        n|}nt                      }|dk    rt          |          S |dz  |z  }|                                st          d| d|           t          |          S )a  Resolve a profile name to a HERMES_HOME path string.

    Called early in the CLI entry point, before any hermes modules
    are imported, to set the HERMES_HOME environment variable.

    When HERMES_HOME is already set, the configured spelling IS the
    launch root (it may be a junction/symlink alias of the platform
    default).  Keep that spelling so profile re-home does not destroy
    the launcher's lexical provenance -- the subprocess sanitizer needs
    it to match Hermes-owned PYTHONPATH entries written in the same
    spelling (#82581 junction follow-up).  Physically the paths are
    identical (junction-transparent); only the spelling is preserved.
    r  r   r   ri   ri  r_  )r   r   r   r   r   r   r   r  r   rE   rF   r   r   )r   r   r  r  re   r*   s         r1   resolve_profile_envr  	  s     #<00E%   z~~mR006688H *>> *2)=)K)Kx%%QY'))	4yy#e+K 
= = =5:= =
 
 	

 {rH   r  )NFFFFN)F)qr&  r  loggingr   r  r   r   r   r   r   rL  dataclassesr   pathlibr   r   r   typingr   r   r	   r
   agent.skill_utilsr   	getLoggerr#  r  r  r   r  r   r  rF   r'  r  r  r  r   rr  r  r"   r)   r-   r(  r2   rG   _DEFAULT_EXPORT_EXCLUDE_ROOTrq  r   r   r   rE   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r.  r   r  r1  r6  r?  r@  floatr)  rM  rI  rU  rW  r[  rc  rg  rm  rv  r  r  r  r  r  r  r  r1  r  r"  r!  r#  r&  r'  r~  rs  r~  r  r  r  r  r  r  r  r  r  r  r  r  r   rH   r1   <module>r     sl    *   				 				        



  ! ! ! ! ! ! 8 8 8 8 8 8 8 8 8 8 . . . . . . . . . . . . 4 4 4 4 4 4		8	$	$9:::=#%% !3uS#X#7 ? ? ?  "        $s)   , 3<) = = = 3 3 3   , 3<) = = = 3 3 3     0 D T    34 3 3 3 3t  )y * * *     B  )y * * *      )     
  i ! ! !   3D 3 3 3 3%$ % % % %9$ 9 9 9 9
*$ * * * *     $
 
 
 
 
 
<
c 
d 
 
 
 
 (# ($ ( ( ( (+ + + + + +' ' ' ' ' 'TG G G G G( ( (Xc] (htn ( ( ( (V     <T d    >G G# G G G G6  .c3h . . . .j                F   %        2D U    (     R ;= DeE5#$5667 < < < d u    6t     P(D (T ( ( ( (4 D    < "&'+"&)7 )7 )7)7 #)7 tn	)7
 3-)7 
)7 )7 )7 )7X	;s 	;(3- 	;C 	; 	; 	; 	;3 c c    ,GtK( G G G GX .2C CCS	*C 
%T	
C C C CP !%!%~ ~
~~ ~ 	~
 ~ ~ #~ 
~ ~ ~ ~B* *T *$ *8D> * * * *Z1 1 1$s) 1 1 1 1hJs J J$s) J J J JZ&B# &BD &BT &B &B &B &BRD D    <d d d4 dD d d d dN9C# 9C$ 9C 9C 9C 9CxEC ED E E E E6-*3 -*T -*d -* -* -* -*`$2t $2 $2 $2 $2 $2VC    S T    2    >#T # # # #L s c c    ( $) % % %    !y"   
:T :d : : : :,4$ ,44 ,4 ,4 ,4 ,4^< < <3 <Xd3PS8n=U <ae < < < <~# $s)    (4 d t    @D SX    2< < <HSM <T < < < <F9H3 9H# 9H 9HQU 9H 9H 9H 9Hx?S ?C ?D ? ? ? ?L"c "c " " " " " "rH   