
    ,jN                        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m	Z	 ddl
mZmZmZ ddlmZmZ ddlmZ  ej        d          Z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<   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          Z$defdZ%defdZ&defdZ'defdZ(dedefdZ)deddfdZ*dedefd Z+dedefd!Z,dedee         fd"Z-defd#Z.dZded$ee         dee         fd%Z/dedefd&Z0d'edee         fd(Z1e	 G d) d*                      Z2dede3fd+Z4dede3fd,Z5dedefd-Z6dede7fd.Z8dedefd/Z9dede:fd0Z;ddd1ded2ee         d3ee         ddfd4Z<dee2         fd5Z=	 	 	 	 	 	 d[ded7ee         d8ed9ed:ed;ed2ee         defd<Z>d\ded=edee:         fd>Z?d\ded?edefd@Z@d'eddfdAZAd'eddfdBZBdededdfdCZCdeddfdDZDdefdEZEdeddfdFZFdefdGZGdHefdIZHdedJedefdKZIdLedee         fdMZJdNedOeddfdPZKdNedeLe         fdQZMdZdRedee         defdSZNdTedUedVeddfdWZOdTedUedefdXZPd'edefdYZQd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)ListOptional)is_excluded_skill_pathz^[a-z0-9][a-z0-9_-]{0,63}$)	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_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    8/home/ubuntu/.hermes/hermes-agent/hermes_cli/profiles.pyhas_bundled_skills_opt_outr(   o   sA    66>>@@@   uus    
,,
source_dirc                     |                                  t                                                       k    dt          dt          t                   dt          t                   ffd}|S )uo  Exclude infrastructure artifacts when cloning a profile via --clone-all.

    Two categories:
      1. 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.
      2. 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                 4   g }|D ]}|dk    s|                     d          r|                    |           3r\	 t          |                                           k    r|t          v r|                    |           z# t
          t          f$ r Y w xY w|S )N__pycache__)z.pycz.pyo.sock.tmp)endswithappendr   resolver   r%   
ValueError)r+   r,   ignoredentryis_default_sourcesource_resolveds       r'   _ignorez+_clone_all_copytree_ignore.<locals>._ignore   s     	 	E &&>>"CDD ' u%%%  	I..00OCC $CCC#NN5111,    D		 s   AB  BB)r3   _get_default_hermes_homestrr   )r)   r9   r7   r8   s     @@r'   _clone_all_copytree_ignorer<   w   s|    $ !((**O'+C+E+E+M+M+O+OO3 tCy T#Y       . N    >   state.db	auth.lock
errors.logstate.db-shmstate.db-wal.update_check.hermes_historyhermes_state.dbresponse_store.dbresponse_store.db-shmresponse_store.db-walr   r   r   	sandboxesaudio_cachecheckpointsimage_cacher   active_profiledocument_cachebrowser_screenshotsr   	auth.jsonr   r   r   r   r   >   tmprootsudotesthermesdefault>   acpmcpchatr   dumploginmodelsetuptoolsconfigdoctorhonchologoutr   statusupdategatewaypairingplugins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   r:    r=   r'   _get_profiles_rootrp      s     $%%
22r=   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_constantsrs   rr   s    r'   r:   r:      s%     988888""$$$r=   c                  $    t                      dz  S )z2Return the path to the sticky active_profile file.rM   rn   ro   r=   r'   _get_active_profile_pathrv      s    #%%(888r=   c                  4    t          j                    dz  dz  S )z)Return the directory for wrapper scripts.z.localr   )r   r   ro   r=   r'   _get_wrapper_dirrx      s    9;;!E))r=   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 emptyrV   )
isinstancer;   stripr4   casefoldlower)ry   strippeds     r'   normalize_profile_namer      sq     dC   4yyzz||H 97888i''y>>r=   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.
    rV   NzInvalid profile name z%. 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matchr4   _RESERVED_NAMES)ry   s    r'   validate_profile_namer     s     y%% 
*D * * *
 
 	
 &D & & &
 
 	
 r=   c                 j    t          |           }|dk    rt                      S t                      |z  S )z4Resolve a profile name to its HERMES_HOME directory.rV   )r   r:   rp   ry   canons     r'   get_profile_dirr   +  s7    "4((E	')))%''r=   c                 r    t          |           }|dk    rdS t          |                                          S )z)Check whether a profile directory exists.rV   T)r   r   is_dirr   s     r'   profile_existsr   3  s9    "4((E	t5!!((***r=   c                 4   t          |           }|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	          }|j        d
k    r|j	        
                                                                d
         }||r| dn|z  }|t          |          k    r,	 |                                }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: reserved names, hermes subcommands, existing binaries in PATH.
    'z' is a reserved namez$' conflicts with a hermes subcommandwin32wherewhichT   )capture_outputtexttimeoutr   .bat	hermes -pNz&' conflicts with an existing command ())r   r   _HERMES_SUBCOMMANDSrx   sysplatform
subprocessrun
returncodestdoutr|   
splitlinesr;   	read_text	ExceptionFileNotFoundErrorTimeoutExpired)ry   r   wrapper_dir
is_windowsresultexisting_pathexpectedcontents           r'   check_alias_collisionr   ?  s   
 #4((E.5....###>5>>>> #$$K(J"/WW7dA
 
 
 !!"M//11<<>>qAM"
&MnnnnNHH--&0022G"g--#t .    DTuTTMTTTT " z89    4s7   A9C< C" !C< "
C/,C< .C//C< <DDc                      t          t                                } | t          j                            dd                              t          j                  v S )z!Check if ~/.local/bin is in PATH.PATH )r;   rx   osenvirongetsplitpathsep)r   s    r'   _is_wrapper_dir_in_pathr   d  sA    &(())K"*..44::2:FFFFr=   targetc                    t          |           }|rt          |          n|}t                      }	 |                    dd           n-# t          $ r }t	          d| d|            Y d}~dS d}~ww xY wt
          j        dk    }|rQ|| dz  }	 |                    d| d	           |S # t          $ r }t	          d
| d|            Y d}~dS d}~ww xY w||z  }	 |                    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 %*
u    ⚠ Could not create wrapper at z#!/bin/sh
exec hermes -p z "$@"
)r   rx   mkdirr%   printr   r   
write_textchmodstatst_modeS_IEXECS_IXGRPS_IXOTH)ry   r   r   rh   r   er   wrapper_paths           r'   create_wrapper_scriptr   j  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$NOOO 	 	 	H\HHQHHIII44444	 #U*	##$Q$Q$Q$QRRR|0022:T\IDLX[_[gghhh 	 	 	H\HHQHHIII44444	sH   A
 

A4A//A4B- -
C7CC A-E 
E8E33E8c                 \   t                      }t          |           }t          j        dk    }||z  g}|r|                    d|| dz             |D ]W}|                                rA	 |                                }d|v r|                                  dS G# t          $ r Y Sw xY wXdS )zARemove the wrapper script for a profile. Returns True if removed.r   r   r   r   TF)	rx   r   r   r   insertr$   r   unlinkr   )ry   r   r   r   
candidatesr   r   s          r'   remove_wrapper_scriptr     s    "$$K"4((E(J %&J ;![e>>>9:::" 	 	   	&0022')) '')))44 *    	 5s   +,B
B('B(profile_namec                    t                      }|                                sdS t          |           }t          j        dk    }d| }d}d}t          |                                          D ]~}|                                s|r|j        dk    r%|s|j        r/	 |	                                }n# t          t          f$ r Y Xw xY w||vra|r|j        n|j        }	|	|k    r|	}z||	}||n|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.
    Nr   z
hermes -p r   )rx   r   r   r   r   sortediterdiris_filesuffixr   r%   UnicodeDecodeErrorstemry   )
r   r   r   r   needlecustomprofile_namedr6   r   aliass
             r'   find_alias_for_profiler     s?    #$$K t"<00E(J!%!!F F#'M++--..  }} 	 	%,&00 	el 		oo''GG+, 	 	 	H	  (8

ejE>>!MM^F'66]:s   B33CCc                   *   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S )ProfileInfoz$Summary information about a profile.ry   path
is_defaultgateway_runningNr\   providerFhas_envr   skill_count
alias_path
alias_namedistribution_namedistribution_versiondistribution_sourcer   descriptiondescription_auto)__name__
__module____qualname____doc__r;   __annotations__r   boolr\   r   r   r   r   intr   r   r   r   r   r   r   ro   r=   r'   r   r     s        ..
III
JJJE8C="Hhsm"""GTK!%J%%% !%J$$$'+x}+++*.(3-...)-#--- K
 #d"""""r=   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   Nrutf-8encodingry   ri   source)r   yamlopen	safe_loadr{   dictr   r   )r    mf_pathr   fdatas        r'   _read_distribution_metar     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}t          |dd          5 }|                    |          pi }ddd           n# 1 swxY w Y   |                    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   Nr   r   r   r\   rV   r   )	r$   r   r   r   r   r{   r;   r   r   )r    config_pathr   r   cfg	model_cfgs         r'   _read_config_modelr    sN   -K z+sW555 	*..##)rC	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	*GGGR((	i%% 	#d?"i&& 	a==++Ey}}W/E/Ey}}U_G`G```z   zzs;   C' AC' AC' A1C' AC' '
C54C5c                 V    	 ddl m}  || dz  d          duS # t          $ r Y dS w xY w)z<Check if a gateway is running for a given profile directory.r   )get_running_pidr   F)cleanup_staleN)gateway.statusr  r   )r    r  s     r'   _check_gateway_runningr  &  s[    222222{]:%PPPX\\\   uus    
((c                     | dz  }|                                 sdS d}|                    d          D ]}t          |          r|dz  }|S )z$Count installed skills in a profile.r   r   zSKILL.md   )r   rglobr	   )r    
skills_dircountmds       r'   _count_skillsr  /  sj    x'J qEz**  !"%% 	
Lr=   c                     | dz  S )Nzprofile.yamlro   r&   s    r'   _profile_yaml_pathr  J  s    ''r=   c                    t          |           }|                                sdddS 	 ddl}t          |dd          5 }|                    |          pi }ddd           n# 1 swxY w Y   n# t
          $ r dddcY S w xY wt          |t                    sdddS t          |	                    d	          pd          
                                t          |	                    d
d                    dS )u  Read ``<profile_dir>/profile.yaml`` and return a dict.

    Returns ``{"description": "", "description_auto": False}`` 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   Nr   r   r   r   r   )r  r   r   r   r   r   r{   r   r;   r   r|   r   )r    r   r   r   r   s        r'   read_profile_metar  N  s[    k**D<<>> >!u===>$g... 	+!>>!$$*D	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ > > >!u=====>dD!! >!u===488M228b99??AA *<e!D!DEE  s5   A0  A$A0 $A((A0 +A(,A0 0BB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<   t          |d	d          5 }|                    ||d
d
           ddd           dS # 1 swxY w Y   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   wF)	sort_keysdefault_flow_style)r   r   r   r  r   r   r   r{   r   r   r|   r   	safe_dump)r    r   r   r   r   existingr   loadeds           r'   write_profile_metar  g  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#'+,<'='=#$	dC'	*	*	* OaxeNNNO O O O O O O O O O O O O O O O O OsH   B* #B;B* BB* BB* *B98B9;D""D&)D&c                     g } t                      }t                      }|                                rt          |          \  }}t	          |          \  }}}t          |          }|                     t          d|dt          |          |||dz  	                                t          |          ||||                    dd          |                    dd                               t                      }	|	                                rht          |	                                          D ]E}
|
                                s|
j        }|dk    r&t                               |          sAt          |
          \  }}t%          |          }|rt&          j        d	k    }||r| d
n|z  }nd}t	          |
          \  }}}t          |
          }|                     t          ||
dt          |
          |||
dz  	                                t          |
          |r|	                                r|nd|||||                    dd          |                    dd                               G| S )z4Return info for all profiles, including the default.rV   Tr   r   r   r   F)ry   r   r   r   r\   r   r   r   r   r   r   r   r   r   r   N)ry   r   r   r   r\   r   r   r   r   r   r   r   r   r   r   )rx   r:   r   r  r   r  r2   r   r  r$   r  r   rp   r   r   ry   r   r   r   r   r   )r   r   default_homer\   r   	dist_namedist_versiondist_sourcemetaprofiles_rootr6   ry   r   r   r   s                  r'   list_profilesr"    s   H"$$K ,--L ,\::x/F|/T/T,	< ..2<@@!F*2244%l33'!- +33!XX&8%@@
 
 
 	 	 	" '((M "M113344 !	 !	E<<>> :Dy  !''-- 077OE8/55J " \W4
(:,]z,?,?,?,?S]^

!
3J53Q3Q0I|[$U++DOOK  6u = =!//11)%00*4W9J9J9L9LW::SW%"+%1$/ HH]B77!%*<e!D!D      $ Or=   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|	           |rK|	rIt          j        |	|t          |	                     t          D ]}||z                      d           nA|                    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           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|rJ|                                r6	 t?          ||                                d           n# t8          $ r Y nw xY wtA          |           |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.
    zx--no-skills is mutually exclusive with --clone / --clone-all (cloning explicitly copies skills from the source profile).rV   uS   Cannot create a profile named 'default' — it is the built-in profile (~/.hermes).	Profile '' already exists at Nr   get_hermes_homezSource profile 'activez' does not exist at ignoreT
missing_okr   r   i  r   )dirs_exist_okz# 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`.
Fr  )!r4   r   r   r   r$   FileExistsErrorrt   r,  r   r   shutilcopytreer<   r   r   r   _PROFILE_DIRS_CLONE_CONFIG_FILEScopy2r   r   r;   r%   _CLONE_SUBDIR_FILESparentr   hermes_cli.default_soulr3  r   r#   r|   r  _maybe_register_gateway_service)ry   r#  r$  r%  r&  r'  r   r   r    r)   r,  stalesubdirfilenamesrcdstsource_skillsrelpathenv_path	soul_pathr3  s                        r'   create_profilerG    s;   H  
l 
i 
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[[    /+Z /+-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##%% [{X/EUYZZZZ / + + 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	  {((** 	'--//!&    
  	 	 	D	 $E***sZ   "G%%
G21G29K 
K#"K# L 
L+*L+1M 
MM8$N 
N*)N*quietc                 6   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          }|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_HOME<   )envcwdr   r   r   r   u%   ⚠ Skill seeding returned exit code z  N   u!   ⚠ Skill seeding timed out (60s)u   ⚠ Skill seeding failed: )r(   r   __file__r;  r3   r   r   r   
executabler   r   r;   r   r   r|   jsonloadsr   stderrr   r   )r    rH  project_rootr   r   s        r'   seed_profile_skillsrY    s    "+.. 
#	
 
 	
 >>(/7799L^TAB @2:?}c+.>.>??L!!dB
 
 
 !!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0	F9FF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)          |           |r!t+          |          rt          d|            d}	 d }	 t-          j        ||           n&# t0          $ r t-          j        ||           Y nw xY wt          d|            n.# t2          $ r!}t          d| d|            |}Y d}~nd}~ww xY w	 t5                      }||k    rt7          d           t          d            n# t2          $ r Y nw xY w|t9          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.
    rV   zZCannot delete the default profile (~/.hermes).
To remove everything, use: hermes uninstallr)  ' does not exist.z

Profile: z	Path:    z	Model:   z (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.u   ✓ 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{   tuplePermissionErrorr   r   r   S_IWUSRr%   r   dirname)funcr   exc_statr;  s        r'   _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!)onexc)onerroru   ⚠ Could not remove r   u#   ✓ Active profile reset to defaultz#Could not remove profile directory z

Profile 'z
' deleted.)r   r   r4   r   r   r   r  r  r  r   r   rx   r$   r2   inputr|   KeyboardInterruptEOFError_cleanup_gateway_service!_maybe_unregister_gateway_service_stop_gateway_processr   r5  rmtree	TypeErrorr   get_active_profileset_active_profileRuntimeError)ry   rZ  r   r    r\   r   
gw_runningr   r  r  r  itemsr   has_wrapperitemconfirmremove_errorrg  r   r-  s                       r'   delete_profiler{    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 CABBB  		:U:::;;AACCGG!8, 	 	 	.!!!	 e, UK000 &e,,,  +k***  1 '' 	1///000 &*L.#	 #	 #	L	?M+^<<<<< 	? 	? 	?M+~>>>>>>	?*[**++++   8k88Q88999
#%%U??y)))7888    ^^^P\^^__eqq	
)
)
)
)***s`   <%G" ""HH7K ;J K  J52K 4J55K 
K6K11K6:2L- -
L:9L:c                 T   	 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 Y dS t          $ r}t          d|            Y d}~dS d}~ww xY w)ux  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 is governed by the profile's ``config.yaml``
    (``[gateway] port = …``) — there is no Python-side allocator
    (PR #30136 review item I5 retired the SHA-256-derived range
    [9200, 9800) because it was dead code through the entire stack).

    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_manageru+   ⚠ Could not register s6 gateway service: )	hermes_cli.service_managerr~  r  rt  r   supports_runtime_registrationregister_profile_gatewayr4   r   r   r~  r  mgrre  s        r'   r=  r=  I  s3   8EEEEEE!!##t++FBBBBBB!!##       		
 ,,.. C$$\22222    	 C C CACAABBBBBBBBBCs6   ) ) 
A	AAA4 4
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   r}  r  Nr  u-   ⚠ Could not unregister s6 gateway service: )r  r~  r  rt  r   r  unregister_profile_gatewayr   r  s        r'   rn  rn    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   NrN  )get_service_nameget_launchd_plist_pathLinuxz.configsystemduserz.service	systemctl--userdisableTF
   )r   checkr   stopr0  )r  r  zdaemon-reloadu   ✓ Service z removedDarwin	launchctlunloadu   ✓ Launchd service removedu   ⚠ Service cleanup: )r   r   r   r   r;   hermes_cli.gatewayr  r  systemr   r   r$   r   r   r   r   r   )
ry   r    	_platformold_homer  r  svc_namesvc_file
plist_pathr   s
             r'   rm  rm    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  "" 6 (C
OO<#'ub    !!T!2224555 + + +)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          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   {pid)terminate_pid)_pid_exists   g      ?u   ✓ Gateway stopped (PID r   T)forceu   ✓ Gateway force-stopped (PID u   ✓ Gateway already stoppedu   ⚠ Could not stop gateway: )timer$   r   r|   
startswithrU  rV  r   r  r  r  rangesleepr   ProcessLookupErrorr%   ra  r   )
r    _timepid_filerawr   r  _terminate_pidr  _r   s
             r'   ro  ro    s    ]*H?? 2  ""((**"%.."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D0 4D0 6D D0 DD0 DD0 0 E9	E9E44E9c                      t                      } 	 |                                                                 }|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.
    rV   )rv   r   r|   r   r   r%   )r   ry   s     r'   rr  rr    sk    
 $%%D~~%%'' 	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	S )
zlSet the sticky active profile.

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

 $%%DKdT222	t$$$$$ v&&ut|$$$Dr=   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+  rV   r  r   )rt   r,  r3   r:   rp   relative_topartslenr   r   r4   )r,  hermes_homeresolveddefault_resolvedr!  relr  s          r'   get_active_profile_namer    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 )zReturn an *ignore* callable for :func:`shutil.copytree`.

    At the root level it excludes everything in ``_DEFAULT_EXPORT_EXCLUDE_ROOT``.
    At all levels it excludes ``__pycache__``, sockets, and temp files.
    r+   contentsr!   c                 &   t                      }|D ]L}|dk    s|                    d          r|                    |           3|dv r|                    |           Mt          |           k    r|                    d |D                        |S )Nr.   )r/   r0   >   package.jsonpackage-lock.jsonc              3   ,   K   | ]}|t           v |V  d S N)_DEFAULT_EXPORT_EXCLUDE_ROOT).0cs     r'   	<genexpr>z:_default_export_ignore.<locals>._ignore.<locals>.<genexpr>N  s-      TT!7S2S2S12S2S2S2STTr=   )setr1   addr   rd   )r+   r  r5   r6   r  s       r'   r9   z'_default_export_ignore.<locals>._ignoreC  s    uu 	# 	#E%%8I)J)J%E""""???E"""	??h&&NNTThTTTTTTr=   )r;   listr  )r  r9   s   ` r'   _default_export_ignorer  <  s=    3 $ 3       Nr=   output_pathc                 `  
 ddl }t          |           }t          |           t          |          }|                                st          d| d          t          |          }t          |                              d                              d          }|dk    r|	                                5 }t          |          dz  }t          j        ||t          |                     t          j        |d	|d          }	t          |	          cddd           S # 1 swxY w Y   |	                                5 }t          |          |z  }d
dh
t          j        ||
fd           t          j        |d	||          }	t          |	          cddd           S # 1 swxY w Y   dS )zMExport a profile to a tar.gz archive.

    Returns the output file path.
    r   Nr)  r\  z.tar.gzz.tgzrV   r.  gztarrP   r   c                 (    t          |          z  S r  )r  )dr  _CREDENTIAL_FILESs     r'   <lambda>z export_profile.<locals>.<lambda>z  s    '83x=='H r=   )tempfiler   r   r   r   r   r   r;   removesuffixTemporaryDirectoryr5  r6  r  make_archive)ry   r  r  r   r    outputbasetmpdirstagedr   r  s             @r'   export_profiler  T  sF   
 OOO"4((E%   !%((K F DE D D DEEE+Fv;;##I..;;FCCD	 ((** 	 f&\\I-FO-k::   
 (w	JJF<<	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  	  
	$	$	&	& 	&f%(&1HHHH	
 	
 	
 	

 $T7FEBBF||	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s&   8AD!!D%(D% AF##F'*F'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   .ro   r  parts     r'   
<listcomp>z4_normalize_profile_archive_parts.<locals>.<listcomp>  s"    HHHd$i2G2GT2G2G2Gr=   c              3   "   K   | ]
}|d k    V  dS )z..Nro   r  s     r'   r  z3_normalize_profile_archive_parts.<locals>.<genexpr>  s&      77777777r=   )r  r   r   is_absolutedriver4   r  any)r  normalized_name
posix_pathwindows_pathr  s        r'    _normalize_profile_archive_partsr    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Lr=   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  )tarfiler   
getmembersr  ry   joinpathisdirr   isfiler4   r;  extractfiler5  copyfileobjr   r   moder%   )	r  r  r  tfmemberr  r   	extractedrB  s	            r'   _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  ry   r  r  )r  r  r  s      r'   	<setcomp>z1_inspect_profile_archive_roots.<locals>.<setcomp>  sP     
 
 
:6;GG5zzA~~~ !H ~~r=   c                 j    h | ]0}|                                 t          |j                  d          1S )r   )r  r  ry   )r  r  s     r'   r  z1_inspect_profile_archive_roots.<locals>.<setcomp>  sE       <<>>0==a@  r=   )r  r   r  )r  r  r  top_dirss       r'   _inspect_profile_archive_rootsr    s     NNN	gv	&	& "
 
--//
 
 
  	  mmoo  H               Os   ?A&&A*-A*archive_pathc                    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.rV   u   Cannot import as 'default' — that is the built-in root profile (~/.hermes). Specify a different name: hermes profile import <archive> --name <name>r)  r*  Tr   hermes_profile_import_)prefixz,Profile archive root is missing or invalid: )r  r   r$   r   r  r  popr4   r   r   r   r4  rp   r   r  r   r   renamer5  mover;   )r  ry   r  r  r  archive_rootinferred_namer   r    r!  r  staging_rootr  final_sources                 r'   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  r0      F)indentensure_asciir  Tr0  u   ✓ Honcho host updated:     → N)r:   r   r   r  r3   r%   r   r  rU  rV  r   JSONDecodeErrorr   r{   r   r   r  r   r  r  r   r   dumpsr  r   )r  r  r  old_hostlegacy_old_hostnew_hostr   seenr   r  r  r  source_hostblockbarerQ   s                   r'   _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                    t          |           }t          |          }t          |           t          |           |dk    rt          d          |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 )zrRename a profile: directory, wrapper script, service, active_profile.

    Returns the new profile directory.
    rV   z"Cannot rename the default profile.u.   Cannot rename to 'default' — it is reserved.r)  r\  z' already exists.u   ✓ Renamed r  u   ✓ Alias updated: u   ⚠ Cannot create alias 'u   ' — u   ✓ Active profile updated: )r   r   r4   r   r   r   r$   r4  r  rm  ro  r  r   ry   r'  r   r   r   rr  rs  r   )r  r  	old_canon	new_canonold_dirr  	collisions          r'   rename_profiler-  L  s-   
 'x00I&x00I)$$$)$$$I=>>>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   3G 
GGc                     t          |           }t          |           t          |          }|dk    r)|                                st	          d| d|           t          |          S )zResolve 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.
    rV   r)  r  )r   r   r   r   r   r;   )r   r   r    s      r'   resolve_profile_envr/    s     #<00E%   !%((K	+"4"4"6"6= = =5:= =
 
 	

 {r=   r  )NFFFFN)F)Rr   rU  r   rer5  r   r   r   dataclassesr   pathlibr   r   r   typingr   r   agent.skill_utilsr	   compiler   r7  r8  r:  r   r  r;   r   	frozensetr   r#   r   r(   r<   r  r   r   rp   r:   rv   rx   r   r   r   r   r   r   r   r   r   r   r`  r   r  r  r   r  r  r   r  r  r"  rG  rY  r{  r=  rn  rm  ro  rr  rs  r  r  r  r  r   r  r  r  r'  r-  r/  ro   r=   r'   <module>r7     s    *  				 				       



 ! ! ! ! ! ! 8 8 8 8 8 8 8 8 8 8 ! ! ! ! ! ! ! ! 4 4 4 4 4 49::  "        $s)   , 3<) = = = 3 3 3    0 D T    ,4 , , , ,f  )y * * *     2 )     
  i ! ! !   3D 3 3 3 3%$ % % % %9$ 9 9 9 9
*$ * * * *     $
 
 
 
 
 
<(# ($ ( ( ( (+ + + + + +" " " " " "JG G G G G$ $ $Xc] $htn $ $ $ $N     0); );# ); ); ); );` # # # # # # # #@   %        2D U    &     
t 
 
 
 
 
6(D (T ( ( ( (4 D    8 "&'+	O O OO #O tn	O
 
O O O OJAtK( A A A AL !%!%u u
uu u 	u
 u u #u 
u u u up* *T *$ *8D> * * * *ZQ Q Q4 QD Q Q Q Qh3C# 3C$ 3C 3C 3C 3ClEC ED E E E E6-*3 -*T -*d -* -* -* -*`$2t $2 $2 $2 $2 $2VC    S T    2    >T    0) )3 )4 ) ) ) )X# $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4S 4C 4D 4 4 4 4vc c      r=   