
    i                         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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e-fd$Z.dedefd%Z/dede0fd&Z1dee,         fd'Z2	 	 	 	 	 dMde#d)ee#         d*ed+ed,ed-edefd.Z3dNded/edee4         fd0Z5dNde#d1edefd2Z6de#deddfd3Z7deddfd4Z8de#fd5Z9de#ddfd6Z:de#fd7Z;d8efd9Z<de#d:e#defd;Z=d<e#dee#         fd=Z>d>ed?eddfd@Z?d>ede@e#         fdAZAdOdBe#dee#         defdCZBdDe#dEe#dFeddfdGZCdDe#dEe#defdHZDde#fdIZEde#fdJZFdKe#de#fdLZGdS )Pu_  
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.jsonz.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/piyush/.hermes/hermes-agent/hermes_cli/profiles.pyhas_bundled_skills_opt_outr   S   sA    66>>@@@   uus    
,,
source_dirc                     |                                  dt          dt          t                   dt          t                   ffd}|S )u  Ignore ``profiles/`` at the root of *source_dir* only.

    ``~/.hermes`` contains ``profiles/<name>/`` for sibling named profiles.
    ``shutil.copytree`` would otherwise duplicate that entire tree inside the
    new profile (recursive ``.../profiles/.../profiles/...``). Export already
    excludes ``profiles`` via ``_DEFAULT_EXPORT_EXCLUDE_ROOT`` — match that
    behavior for ``--clone-all``.
    	directorynamesr   c                     	 t          |                                           k    rd |D             S n# t          t          f$ r Y nw xY wg S )Nc                     g | ]
}|d k    |S )profiles ).0ns     r   
<listcomp>z?_clone_all_copytree_ignore.<locals>._ignore.<locals>.<listcomp>i   s    <<<aAOOOOO    )r   resolver   
ValueError)r"   r#   source_resolveds     r   _ignorez+_clone_all_copytree_ignore.<locals>._ignoref   sh    	I&&((O;;<<5<<<< <$ 	 	 	D		s   05 A	A	)r,   strr   )r    r/   r.   s     @r   _clone_all_copytree_ignorer1   [   sX     !((**O3 tCy T#Y       Nr+   >   state.db	auth.lock
.worktrees
errors.loghermes-agentstate.db-shmstate.db-wal.update_check.hermes_historyhermes_state.dbresponse_store.dbresponse_store.db-shmresponse_store.db-walbinr   r&   	sandboxesaudio_cachecheckpointsimage_cachenode_modulesactive_profiledocument_cachebrowser_screenshotsr   	auth.jsonr   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&   _get_default_hermes_homer'   r+   r   _get_profiles_rootrh      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_constantsrk   rj   s    r   rg   rg      s%     988888""$$$r+   c                  $    t                      dz  S )z2Return the path to the sticky active_profile file.rE   rf   r'   r+   r   _get_active_profile_pathrn      s    #%%(888r+   c                  4    t          j                    dz  dz  S )z)Return the directory for wrapper scripts.z.localr?   )r   r   r'   r+   r   _get_wrapper_dirrp      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 emptyrN   )
isinstancer0   stripr-   casefoldlower)rq   strippeds     r   normalize_profile_namerx      sq     dC   4yyzz||H 97888i''y>>r+   c                 p    | dk    rdS t                               |           s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).
    rN   NzInvalid profile name z%. Must match [a-z0-9][a-z0-9_-]{0,63})_PROFILE_ID_REmatchr-   )rq   s    r   validate_profile_namer|      s\     y%% 
*D * * *
 
 	

 
r+   c                 j    t          |           }|dk    rt                      S t                      |z  S )z4Resolve a profile name to its HERMES_HOME directory.rN   )rx   rg   rh   rq   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.rN   T)rx   r   is_dirr~   s     r   profile_existsr      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 ())rx   _RESERVED_NAMES_HERMES_SUBCOMMANDSrp   
subprocessrun
returncodestdoutrt   r0   	read_text	ExceptionFileNotFoundErrorTimeoutExpired)rq   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 )r0   rp   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 )rx   rp   mkdirr   print
write_textchmodstatst_modeS_IEXECS_IXGRPS_IXOTH)rq   r   r   ewrapper_paths        r   create_wrapper_scriptr   &  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)rp   rx   r   r   unlinkr   )rq   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S )ProfileInfoz$Summary information about a profile.rq   path
is_defaultgateway_runningNrT   providerFhas_envr   skill_count
alias_path)__name__
__module____qualname____doc__r0   __annotations__r   boolrT   r   r   r   r   intr   r'   r+   r   r   r   P  s         ..
III
JJJE8C="Hhsm"""GTK!%J%%%%%r+   r   c                    | dz  }|                                 sdS 	 ddl}t          |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   NrrT   rN   r   )	r   yamlopen	safe_loadr   rs   r0   dictr   )r   config_pathr   fcfg	model_cfgs         r   _read_config_modelr   ^  sI   -K z+s## 	*q..##)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% %
C32C3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   q  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   rglobr0   )r   
skills_dircountmds       r   _count_skillsr   z  sw    x'J qEz**  3r77""xs2ww'>'>QJELr+   c                  H   g } t                      }t                      }|                                rkt          |          \  }}|                     t          d|dt          |          |||dz                                  t          |                               t                      }|                                rt          |                                          D ]}|                                s|j        }t                              |          s9t          |          \  }}||z  }|                     t          ||dt          |          |||dz                                  t          |          |                                r|nd	  	                   | S )z4Return info for all profiles, including the default.rN   Tr   )rq   r   r   r   rT   r   r   r   FN)	rq   r   r   r   rT   r   r   r   r   )rp   rg   r   r   appendr   r   r   r   rh   sortediterdirrq   rz   r{   )	r&   r   default_homerT   r   profiles_rootentryrq   r   s	            r   list_profilesr     s   H"$$K ,--L ,\::x2<@@!F*2244%l33	
 	
 	
 		 		 		 '((M M113344 	 	E<<>> :D!''-- 077OE8$t+JOOK  6u = =!//11)%00)3):):)<)<F::$
 
 
 
 
 
 
 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).rN   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_MDutf-8encodingzThis profile opted out of bundled-skill seeding (`hermes profile create --no-skills`).
Delete this file to re-enable sync on the next `hermes update`.
)r-   rx   r|   r   r   FileExistsErrorrl   r   r   r   shutilcopytreer1   _CLONE_ALL_STRIPr   r   _PROFILE_DIRS_CLONE_CONFIG_FILEScopy2_CLONE_SUBDIR_FILESparenthermes_cli.default_soulr   r   r   r   r   )rq   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  r,   r   r   sys
executabler   r   r0   r   r   rt   jsonloadsr   stderrr   r   )r   r  project_rootr   r   s        r   seed_profile_skillsr!  =  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          d|            t          d|            |rt          d| |rd| d	nd
z              |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	 t/                      }||k    rt1          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.
    rN   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;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.)rx   r|   r-   r   r   r   r   r   r   r   rp   r   r   inputrt   KeyboardInterruptEOFError_cleanup_gateway_service_stop_gateway_processr   r   rmtreer   get_active_profileset_active_profile)rq   r"  r   r   rT   r   
gw_runningr   itemsr   has_wrapperitemconfirmr   r   s                  r   delete_profiler2  j  s    #4((E%   	:
 
 	

 "%((K F DE D D DEEE )55OE8'44J,,K	


   	
#k
#
#$$$ L!%!!%I%5(%5%5%5%5rJKKK )'+''((( 	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<   <%F" ""GG&&I 
I6I11I6:2J- -
J:9J: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   )r9  r:  zdaemon-reloadu   ✓ Service z removedDarwin	launchctlunloadu   ✓ Launchd service removedu   ⚠ Service cleanup: )platformr   r   r   r0   hermes_cli.gatewayr4  r5  systemr   r   r   r   r   r   r   r   )
rq   r   	_platformold_homer4  r5  svc_namesvc_file
plist_pathr   s
             r   r(  r(    s        z~~m,,H&*$'$4$4
=!OOOOOOOO((''))Hy{{Y.:VCF[F[F[[H   9 (Ix@#'ub     (FH=#'ub    4000<<<#'ub    7X7778888++//11J  "" 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                 F   ddl }ddl}| dz  }|                                sdS 	 |                                                                }|                    d          rt          j        |          ndt          |          i}t          |d                   }t          j
        ||j                   t          d          D ]R}|                    d           	 t          j
        |d           .# t          $ r t          d| d	           Y  dS w xY w	 t          j
        ||j                   n# t          $ 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   g      ?u   ✓ Gateway stopped (PID r   u   ✓ Gateway force-stopped (PID u   ✓ Gateway already stoppedu   ⚠ Could not stop gateway: )signaltimer   r   rt   
startswithr  r  r   r   killSIGTERMrangesleepProcessLookupErrorr   SIGKILLPermissionErrorr   )	r   _signal_timepid_filerawdatarL  _r   s	            r   r)  r)    s   ]*H?? 2  ""((**"%.."5"5Ltz#E3s88;L$u+
W_%%%r 	 	AKKQ%   8#888999	GC))))! 	 	 	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2sf   B5E C10E 1DE DE D2 1E 2
D?<E >D??E  F :	F FF 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.
    rN   )rn   r   rt   r   UnicodeDecodeErrorr   )r   rq   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.
    rN   r   8' does not exist. Create it with: hermes profile create Tr   r   .tmp
N)rx   r|   r   r   rn   r  r   r   with_suffixr   replace)rq   r   r   rI   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   rN   r   custom)rl   r   r,   rg   rh   relative_topartslenrz   r{   r-   )r   hermes_homeresolveddefault_resolvedr   relri  s          r   get_active_profile_namero  E  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 )N__pycache__)z.sockrb  )zpackage.jsonzpackage-lock.jsonc              3   ,   K   | ]}|t           v |V  d S N)_DEFAULT_EXPORT_EXCLUDE_ROOT)r(   cs     r   	<genexpr>z:_default_export_ignore.<locals>._ignore.<locals>.<genexpr>v  s-      TT!7S2S2S12S2S2S2STTr+   )setendswithaddr   r\   )r"   rr  ignoredr   rp  s       r   r/   z'_default_export_ignore.<locals>._ignorek  s    uu 	# 	#E%%8I)J)J%E""""???E"""	??h&&NNTThTTTTTTr+   )r0   listrz  )rp  r/   s   ` r   _default_export_ignorer  d  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.tgzrN   r   gztarrH   r   c                 (    t          |          z  S rv  )rz  )drr  _CREDENTIAL_FILESs     r   <lambda>z export_profile.<locals>.<lambda>  s    '83x=='H r+   )tempfilerx   r|   r   r   r   r   r0   removesuffixTemporaryDirectoryr   r   r  make_archive)rq   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   .r'   r(   parts     r   r*   z4_normalize_profile_archive_parts.<locals>.<listcomp>  s"    HHHd$i2G2GT2G2G2Gr+   c              3   "   K   | ]
}|d k    V  dS )z..Nr'   r  s     r   ry  z3_normalize_profile_archive_parts.<locals>.<genexpr>  s&      77777777r+   )re  r   r   is_absolutedriver-   ri  any)r  normalized_name
posix_pathwindows_pathri  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  rq   joinpathisdirr   isfiler-   r  extractfiler   copyfileobjr   r   moder   )	r  r  r  tfmemberri  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  rq   rj  r  )r(   r  ri  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  rq   )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.rN   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  rj  popr-   rx   r|   r   r   rh   r   r  r  r   renamer   mover0   )r  rq   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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   rb     F)indentensure_asciirc  Tr   u   ✓ Honcho host updated:     → N)rg   r   r   rz  r,   r   is_filer|  r  r  r   JSONDecodeErrorr   rs   r   r   r   r  rd  suffixr   dumpsre  r   )r  r  r  old_hostnew_host
candidatesseenr   rl  r[  r  blockbarerI   s                 r   _migrate_honcho_profile_hostr  8  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.
    rN   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: )rx   r|   r-   r   r   r   r   r   r   r(  r)  r  r   rq   r  r   r   r   r+  r,  r   )r  r  	old_canon	new_canonold_dirr  	collisions          r   rename_profiler  m  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
r'   r'   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 "$@"
r'   r'   r+   r   generate_zsh_completionr    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.
    rN   r   ra  )rx   r|   r   r   r   r0   )r  r   r   s      r   resolve_profile_envr    s     #<00E%   !%((K	+"4"4"6"6= = =5:= =
 
 	

 {r+   )NFFFF)Frv  )Hr   r  r   rer   r   r   r  dataclassesr   pathlibr   r   r   typingr   r   compilerz   r   r  r  r   r   r   r   r1   	frozensetrw  r   r   rh   rg   rn   rp   r0   rx   r|   r   r   r   r   r   r   r   tupler   r   r   r   r   r  r   r!  r2  r(  r)  r+  r,  ro  r  r  r  r  rz  r  r  r  r  r  r  r  r'   r+   r   <module>r     s   *  				 				       



 ! ! ! ! ! ! 8 8 8 8 8 8 8 8 8 8 ! ! ! ! ! ! ! !9::  "         0 D T    4    4  )y * * *     2 )     
  i ! ! !   3D 3 3 3 3%$ % % % %9$ 9 9 9 9
*$ * * * *     $
 
 
 
 
 
$(# ($ ( ( ( (+ + + + + +     DG G G G G     .     & 
& 
& 
& 
& 
& 
& 
& 
&D U    &     	t 	 	 	 	 	 +tK( + + + +` !%B B
BB B 	B
 B B 
B B B BJ* *T *$ *8D> * * * *ZZ Z Z4 ZD Z Z Z Zz-*3 -*T -*d -* -* -* -*`2t 2 2 2 2 2L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+   