
    +Vj                     P   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  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,dedee         fd#Z-dedefd$Z.e	 G d% d&                      Z/dede0fd'Z1dede0fd(Z2dedefd)Z3dede4fd*Z5dee/         fd+Z6	 	 	 	 	 dQded-ee         d.ed/ed0ed1edefd2Z7dRded3edee8         fd4Z9dRded5edefd6Z:dededdfd7Z;deddfd8Z<defd9Z=deddfd:Z>defd;Z?d<efd=Z@ded>edefd?ZAd@edee         fdAZBdBedCeddfdDZCdBedeDe         fdEZEdSdFedee         defdGZFdHedIedJeddfdKZGdHedIedefdLZHdefdMZIdefdNZJdOedefdPZKdS )Tu_  
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Optionalz^[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    1/root/.hermes/hermes-agent/hermes_cli/profiles.pyhas_bundled_skills_opt_outr&   m   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)r1   _get_default_hermes_homestrr   )r'   r7   r5   r6   s     @@r%   _clone_all_copytree_ignorer:   u   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   r8    r;   r%   _get_profiles_rootrn      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_constantsrq   rp   s    r%   r8   r8      s%     988888""$$$r;   c                  $    t                      dz  S )z2Return the path to the sticky active_profile file.rK   rl   rm   r;   r%   _get_active_profile_pathrt      s    #%%(888r;   c                  4    t          j                    dz  dz  S )z)Return the directory for wrapper scripts.z.localr   )r   r   rm   r;   r%   _get_wrapper_dirrv      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 emptyrT   )
isinstancer9   stripr2   casefoldlower)rw   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.
    rT   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matchr2   _RESERVED_NAMES)rw   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.rT   )r~   r8   rn   rw   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.rT   T)r~   r   is_dirr   s     r%   profile_existsr   1  s9    "4((E	t5!!((***r;   c                    t          |           }|t          v rd| dS |t          v rd| dS t                      }	 t	          j        d|gddd          }|j        dk    rg|j                                        }|t          ||z            k    r/	 ||z  
                                }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whichT   )capture_outputtexttimeoutr   	hermes -pNz&' conflicts with an existing command ())r~   r   _HERMES_SUBCOMMANDSrv   
subprocessrun
returncodestdoutrz   r9   	read_text	ExceptionFileNotFoundErrorTimeoutExpired)rw   r   wrapper_dirresultexisting_pathcontents         r%   check_alias_collisionr   =  sZ   
 #4((E.5....###>5>>>> #$$KeTa
 
 
 !!"M//11MK%$7 8 888*U2==??G"g--#t .    DTuTTMTTTT " z89    4s6   AC
 B0 /C
 0
B=:C
 <B==C
 
C#"C#c                      t          t                                } | t          j                            dd                              t          j                  v S )z!Check if ~/.local/bin is in PATH.PATH )r9   rv   osenvirongetsplitpathsep)r   s    r%   _is_wrapper_dir_in_pathr   _  sA    &(())K"*..44::2:FFFFr;   c                 
   t          |           }t                      }	 |                    dd           n-# 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)	zCreate a shell wrapper script at ~/.local/bin/<name>.

    Returns the path to the created wrapper, or None if creation failed.
    Tparentsexist_oku   ⚠ Could not create : Nz#!/bin/sh
exec hermes -p z "$@"
u    ⚠ Could not create wrapper at )r~   rv   mkdirr$   print
write_textchmodstatst_modeS_IEXECS_IXGRPS_IXOTH)rw   r   r   ewrapper_paths        r%   create_wrapper_scriptr   e  sD   
 #4((E"$$K$6666   8k88Q88999ttttt &L KU K K KLLL<,,..6ETW[Wccddd   DDDDDEEEttttts.   7 
A!AA!*A-C 
D"C==Dc                     t                      t          |           z  }|                                r@	 |                                }d|v r|                                 dS n# t
          $ r Y nw xY wdS )zARemove the wrapper script for a profile. Returns True if removed.r   TF)rv   r~   r#   r   unlinkr   )rw   r   r   s      r%   remove_wrapper_scriptr   |  s    #%%(>t(D(DDL 	",,..Gg%%##%%%t &  	 	 	D	5s   ,A# #
A0/A0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S )ProfileInfoz$Summary information about a profile.rw   path
is_defaultgateway_runningNrZ   providerFhas_envr   skill_count
alias_pathdistribution_namedistribution_versiondistribution_source)__name__
__module____qualname____doc__r9   __annotations__r   boolrZ   r   r   r   r   intr   r   r   r   rm   r;   r%   r   r     s         ..
III
JJJE8C="Hhsm"""GTK!%J%%%'+x}+++*.(3-...)-#-----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encodingrw   rg   source)is_fileyamlopen	safe_loadry   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   rZ   rT   r   )	r#   r   r   r   r   ry   r9   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 ])}dt          |          vrdt          |          vr|dz  }*|S )z$Count installed skills in a profile.r   r   zSKILL.mdz/.hub/z/.git/   )r   rglobr9   )r   
skills_dircountmds       r%   _count_skillsr     sw    x'J qEz**  3r77""xs2ww'>'>QJELr;   c                     g } t                      }t                      }|                                rt          |          \  }}t	          |          \  }}}|                     t          d|dt          |          |||dz                                  t          |          |||                     t                      }|                                rt          |                                          D ]}	|	                                s|	j        }
t                              |
          s9t          |	          \  }}||
z  }t	          |	          \  }}}|                     t          |
|	dt          |	          |||	dz                                  t          |	          |                                r|nd|||                     | S )z4Return info for all profiles, including the default.rT   Tr   )rw   r   r   r   rZ   r   r   r   r   r   r   FN)rw   r   r   r   rZ   r   r   r   r   r   r   r   )rv   r8   r   r   r   r0   r   r   r#   r   rn   sortediterdirrw   r   r   )r   r   default_homerZ   r   	dist_namedist_versiondist_sourceprofiles_rootr4   rw   r   s               r%   list_profilesr     s   H"$$K ,--L ,\::x/F|/T/T,	<2<@@!F*2244%l33'!- +
 
 
 	 	 	 '((M M113344 	 	E<<>> :D!''-- 077OE8$t+J3J53Q3Q0I|[OOK  6u = =!//11)%00)3):):)<)<F::$"+%1$/       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           n|                    dd           t           D ]}||z                      dd           |t"          D ]3}||z  }|                                rt          j        |||z             4|dz  }|                                rt          j        ||dz  d           t&          D ]Q}||z  }|                                r6||z  }|j                            dd           t          j        ||           R|dz  }|                                s/	 ddlm} |                    |d           n# t0          $ r Y nw xY w|r1	 |t2          z                      dd           n# t4          $ r Y nw xY w|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).rT   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   )dirs_exist_okr   )DEFAULT_SOUL_MDr   r   zThis profile opted out of bundled-skill seeding (`hermes profile create --no-skills`).
Delete this file to re-enable sync on the next `hermes update`.
)r2   r~   r   r   r#   FileExistsErrorrr   r  r   r   shutilcopytreer:   r   r   r   _PROFILE_DIRS_CLONE_CONFIG_FILEScopy2_CLONE_SUBDIR_FILESparenthermes_cli.default_soulr  r   r   r"   r$   )rw   r   r   r   r   r   r   r   r'   r  stalesubdirfilenamesrcsource_skillsrelpathdst	soul_pathr  s                      r%   create_profiler    s   F  
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+::<< >LkH&<=== '1M##%% [{X/EUYZZZZ / + + 7*::<< +%/CJ$$TD$AAALc*** i'I 	??????  7 CCCC 	 	 	D	
  		33??T !	 @      	 	 	D	 s$   I= =
J
	J
J0 0
J=<J=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  r1   r   r   sys
executabler   r   r9   r   r   rz   jsonloadsr   stderrr   r   )r   r  project_rootr   r   s        r%   seed_profile_skillsr0    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%          ||           |rt'          |           |r!t)          |          rt          d|            	 t+          j        |           t          d|            n,# t.          $ r}t          d| d|            Y d}~nd}~ww xY w	 t1                      }||k    rt3          d           t          d           n# t.          $ r Y nw xY w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.
    rT   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 u   ⚠ Could not remove r   Nu#   ✓ Active profile reset to defaultz

Profile 'z
' deleted.)r~   r   r2   r   r   r   r   r   r   r   r   rv   r#   r0   inputrz   KeyboardInterruptEOFError_cleanup_gateway_service_stop_gateway_processr   r  rmtreer   get_active_profileset_active_profile)rw   r1  r   r   rZ   r   
gw_runningr   r   r   r   itemsr   has_wrapperitemconfirmr   r  s                     r%   delete_profilerC    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  +k***  1 '' 	1///000:k"""*[**++++ : : :8k88Q8899999999:#%%U??y)))7888    

)
)
)
)***s<   <%G" ""HH&&J 
J6J11J6:2K- -
K:9K: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  )rJ  rK  zdaemon-reloadu   ✓ Service z removedDarwin	launchctlunloadu   ✓ Launchd service removedu   ⚠ Service cleanup: )platformr   r   r   r9   hermes_cli.gatewayrE  rF  systemr   r   r#   r   r   r   r   r   )
rw   r   	_platformold_homerE  rF  svc_namesvc_file
plist_pathr   s
             r%   r9  r9  0  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   rz   
startswithr,  r-  r   r   r^  r_  rangesleepr   ProcessLookupErrorr$   PermissionErrorr   )
r   _timepid_filerawr   r]  _terminate_pidr_  _r   s
             r%   r:  r:  `  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.
    rT   )rt   r   rz   r   UnicodeDecodeErrorr$   )r   rw   s     r%   r<  r<    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.
    rT   r  8' does not exist. Create it with: hermes profile create Tr   r  r.   
N)r~   r   r   r   rt   r  r   r   with_suffixr   replace)rw   r   r   rO   s       r%   r=  r=    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  rT   r   custom)rr   r  r1   r8   rn   relative_topartslenr   r   r2   )r  hermes_homeresolveddefault_resolvedr   relrw  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-   r.   )zpackage.jsonz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>  s-      TT!7S2S2S12S2S2S2STTr;   )setr/   addr   rb   )r)   r  r3   r4   r~  s       r%   r7   z'_default_export_ignore.<locals>._ignore  s    uu 	# 	#E%%8I)J)J%E""""???E"""	??h&&NNTThTTTTTTr;   )r9   listr  )r~  r7   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  r3  z.tar.gzz.tgzrT   r  gztarrN   r   c                 (    t          |          z  S r  )r  )dr  _CREDENTIAL_FILESs     r%   <lambda>z export_profile.<locals>.<lambda>  s    '83x=='H r;   )tempfiler~   r   r   r   r   r   r9   removesuffixTemporaryDirectoryr  r  r  make_archive)rw   r  r  r   r   outputbasetmpdirstagedr   r  s             @r%   export_profiler    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   .rm   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..Nrm   r  s     r%   r  z3_normalize_profile_archive_parts.<locals>.<genexpr>%  s&      77777777r;   )rs  r   r   is_absolutedriver2   rw  any)r  normalized_name
posix_pathwindows_pathrw  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  rw   joinpathisdirr   isfiler2   r  extractfiler  copyfileobjr   r   moder$   )	r  r  r  tfmemberrw  target	extractedr  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  rw   rx  r  )r  r  rw  s      r%   	<setcomp>z1_inspect_profile_archive_roots.<locals>.<setcomp>T  sP     
 
 
:6;GG5zzA~~~ !H ~~r;   c                 j    h | ]0}|                                 t          |j                  d          1S )r   )r  r  rw   )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  J  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.rT   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  rx  popr2   r~   r   r   r  rn   r   r  r  r   renamer  mover9   )r  rw   r  r  r  archive_rootinferred_namer   r   r   r  staging_rootr  final_sources                 r%   import_profiler  c  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z  t                      dz  t          j                    dz  dz  g}t                      }|D ]}	 |                                }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                    r||
vr||
v rt          d| d	|            |
|         }t          |t                    r+d
|vr'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|            dS )zGRename Honcho host blocks for a renamed profile without changing peers.zhermes.zhoncho.jsonz.honchozconfig.jsonr   r   hostsu$   ⚠ Honcho host block not migrated: z already exists in aiPeerr  r   r.      F)indentensure_asciirq  Tr  u   ✓ Honcho host updated:     → N)r8   r   r   r  r1   r$   r   r  r,  r-  r   JSONDecodeErrorr   ry   r   r   r   r  rr  suffixr   dumpsrs  r   )r  r  r  old_hostnew_host
candidatesseenr   rz  rj  r  blockbarerO   s                 r%   _migrate_honcho_profile_hostr    s   ###H###H 	- ""]2	i-/J eeD &E &E	||~~HH 	 	 	HHH	t4<<>>	*T^^W^==>>CC-. 	 	 	H	   %&& 	(%*?*?u\\\VZ\\]]]heT"" 	#xu'<'<03x8>>#q))!,,XD"E(O))H--ht{V344	NN4:c!%HHH4OZaNbbbKK 	 	 	

d
++++   H	 	C(CCCCDDDDM&E &Es[   A""A10A1#(CC%$C%/AG44
H*?HH*
H# H*"H##H*)H*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.
    rT   z"Cannot rename the default profile.u.   Cannot rename to 'default' — it is reserved.r  r3  z' already exists.u   ✓ Renamed r  u   ✓ Alias updated: u   ⚠ Cannot create alias 'u   ' — u   ✓ Active profile updated: )r~   r   r2   r   r   r   r#   r  r   r9  r:  r  r   rw   r  r   r   r   r<  r=  r   )r  r  	old_canon	new_canonold_dirr  	collisions          r%   rename_profiler    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                      dS )z;Generate a bash completion script for hermes profile names.az  # Hermes Agent profile completion
# Add to ~/.bashrc: eval "$(hermes completion bash)"

_hermes_profiles() {
    local profiles_dir="$HOME/.hermes/profiles"
    local profiles="default"
    if [ -d "$profiles_dir" ]; then
        profiles="$profiles $(ls "$profiles_dir" 2>/dev/null)"
    fi
    echo "$profiles"
}

_hermes_completion() {
    local cur prev
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"

    # Complete profile names after -p / --profile
    if [[ "$prev" == "-p" || "$prev" == "--profile" ]]; then
        COMPREPLY=($(compgen -W "$(_hermes_profiles)" -- "$cur"))
        return
    fi

    # Complete profile subcommands
    if [[ "${COMP_WORDS[1]}" == "profile" ]]; then
        case "$prev" in
            profile)
                COMPREPLY=($(compgen -W "list use create delete show alias rename export import" -- "$cur"))
                return
                ;;
            use|delete|show|alias|rename|export)
                COMPREPLY=($(compgen -W "$(_hermes_profiles)" -- "$cur"))
                return
                ;;
        esac
    fi

    # Top-level subcommands
    if [[ "$COMP_CWORD" == 1 ]]; then
        local commands="chat model gateway setup status cron doctor dump config skills tools mcp sessions profile update version"
        COMPREPLY=($(compgen -W "$commands" -- "$cur"))
    fi
}

complete -F _hermes_completion hermes
rm   rm   r;   r%   generate_bash_completionr    s    - -r;   c                      dS )z:Generate a zsh completion script for hermes profile names.a  #compdef hermes
# Hermes Agent profile completion
# Add to ~/.zshrc: eval "$(hermes completion zsh)"

_hermes() {
    local -a profiles
    profiles=(default)
    if [[ -d "$HOME/.hermes/profiles" ]]; then
        profiles+=("${(@f)$(ls $HOME/.hermes/profiles 2>/dev/null)}")
    fi

    _arguments \
        '-p[Profile name]:profile:($profiles)' \
        '--profile[Profile name]:profile:($profiles)' \
        '1:command:(chat model gateway setup status cron doctor dump config skills tools mcp sessions profile update version)' \
        '*::arg:->args'

    case $words[1] in
        profile)
            _arguments '1:action:(list use create delete show alias rename export import)' \
                        '2:profile:($profiles)'
            ;;
    esac
}

_hermes "$@"
rm   rm   r;   r%   generate_zsh_completionr  H  s     r;   profile_namec                     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.
    rT   r  rp  )r~   r   r   r   r   r9   )r  r   r   s      r%   resolve_profile_envr  k  s     #<00E%   !%((K	+"4"4"6"6= = =5:= =
 
 	

 {r;   )NFFFF)Fr  )Lr   r,  r   rer  r   r   r*  dataclassesr   pathlibr   r   r   typingr   r   compiler   r  r  r  r   r  r9   r   	frozensetr   r"   r   r&   r:   r  r   r   rn   r8   rt   rv   r~   r   r   r   r   r   r   r   r   tupler   r   r   r   r   r   r  r   r0  rC  r9  r:  r<  r=  r}  r  r  r  r  r  r  r  r  r  r  r  r  rm   r;   r%   <module>r     s    *  				 				       



 ! ! ! ! ! ! 8 8 8 8 8 8 8 8 8 8 ! ! ! ! ! ! ! !9::  "        $s)   , 3<) = = = 3 3 3    0 D T    ,4 , , , ,f  )y * * *     2 )     
  i ! ! !   3D 3 3 3 3%$ % % % %9$ 9 9 9 9
*$ * * * *     $
 
 
 
 
 
<(# ($ ( ( ( (+ + + + + +     DG G G G G     .     & . . . . . . . ."   %        2D U    &     	t 	 	 	 	 	 3tK( 3 3 3 3p !%B B
BB B 	B
 B B 
B B B BJ* *T *$ *8D> * * * *Z_ _ _4 _D _ _ _ _D-*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2E3 2E# 2E 2EQU 2E 2E 2E 2Ej4S 4C 4D 4 4 4 4v/# / / / /d    Fc c      r;   