
    %jN                        d Z ddlZddlZddlZddlZddlmZmZ ddlmZm	Z	 ddl
mZ ddlmZ ej        dk    rddlZd Zd	 Zn
ddlZd
 Zd ZddlZ ej                    dz  Zedz  Zedz  Zedz  ZdZdZdZdZe G d d                      Ze G d d                      Ze G d d                      Z G d d          Z de!fdZ"de!fdZ#dS ) zProfile management for hindsight-embed.

Handles creation, deletion, and management of configuration profiles.
Each profile has its own config, daemon lock, log file, and port.
    N)	dataclassfield)datetimetimezone)Path)Optionalwin32c                 h    t          j        |                                 t           j                   d S N)fcntlflockfilenoLOCK_EXfile_objs    g/home/rurouni/.hermes/hermes-agent/venv/lib/python3.11/site-packages/hindsight_embed/profile_manager.py	lock_filer   -   &    HOO%%u}55555    c                 h    t          j        |                                 t           j                   d S r   )r   r   r   LOCK_UNr   s    r   unlock_filer   0   r   r   c                     |                      d           t          j        |                                 t          j        d           d S Nr      )seekmsvcrtlockingr   LK_LOCKr   s    r   r   r   <   s:    ax((&.!<<<<<r   c                     |                      d           t          j        |                                 t          j        d           d S r   )r   r   r   r   LK_UNLCKr   s    r   r   r   @   s:    ax((&/1=====r   
.hindsightprofilesmetadata.jsonactive_profilei"  i"  i  i'  c                   P    e Zd ZU dZeed<   eed<   eed<   eed<   dZeed<   d ZdS )	ProfilePathszPaths and port for a profile.configlocklogportNui_logc                 |    | j         4| j        j        | j        j                            dd          z  | _         d S d S )N.logz.ui.log)r,   r*   parentnamereplaceselfs    r   __post_init__zProfilePaths.__post_init__a   s:    ;(/DHM,A,A&),T,TTDKKK r   )	__name__
__module____qualname____doc__r   __annotations__intr,   r4    r   r   r'   r'   W   sd         ''LLL
JJJ	III
IIIFDU U U U Ur   r'   c                   h    e Zd ZU dZeed<   eed<   eed<   dZee         ed<   dZ	e
ed<   dZe
ed	<   dS )
ProfileInfoz'Profile information including metadata.r0   r+   
created_atN	last_usedF	is_activedaemon_running)r5   r6   r7   r8   strr9   r:   r?   r   r@   boolrA   r;   r   r   r=   r=   f   sg         11
III
IIIOOO#Ix}###It ND     r   r=   c                   T    e Zd ZU dZdZeed<    ee          Z	ee
ef         ed<   dS )ProfileMetadatazMetadata for all profiles.r   version)default_factoryr#   N)r5   r6   r7   r8   rF   r:   r9   r   dictr#   rB   r;   r   r   rE   rE   r   sM         $$GS %d ; ; ;Hd39o;;;;;r   rE   c            	       l   e Zd ZdZd ZdefdZdefdZdefdZdefdZ	d Z
dee         fd	Zd
edefdZd
edee         fdZdd
edeeeef         z  deeef         dz  fdZd
efdZd
ee         fdZdefdZd
edefdZd
edeeef         fdZd
edefdZdedefdZdefdZdefdZ dS )ProfileManagerz3Manages configuration profiles for hindsight-embed.c                 .    |                                   dS )zInitialize the profile manager.N)_ensure_directoriesr2   s    r   __init__zProfileManager.__init__}   s      """""r   returnc                 .    t          j                    dz  S )zHGet config directory path dynamically (supports testing with temp HOME).r"   )r   homer2   s    r   _get_config_dirzProfileManager._get_config_dir   s    y{{\))r   c                 0    |                                  dz  S )z(Get profiles directory path dynamically.r#   rQ   r2   s    r   _get_profiles_dirz ProfileManager._get_profiles_dir   s    ##%%
22r   c                 0    |                                  dz  S )z#Get metadata file path dynamically.r$   )rT   r2   s    r   _get_metadata_filez!ProfileManager._get_metadata_file   s    %%''/99r   c                 0    |                                  dz  S )z)Get active profile file path dynamically.r%   rS   r2   s    r   _get_active_profile_filez'ProfileManager._get_active_profile_file   s    ##%%(888r   c                 X    |                                                      dd           dS )z!Ensure profile directories exist.T)parentsexist_okN)rT   mkdirr2   s    r   rL   z"ProfileManager._ensure_directories   s-      &&td&CCCCCr   c                    |                                  }|                                 }g }|                                 dz  }|                                rI|                    t          dt          dd|dk    |                     t                                         |j        	                                D ]w\  }}|                    t          ||d         |
                    dd          |
                    d          ||k    |                     |d                                        xt          |d 	          S )
z{List all profiles with their status.

        Returns:
            List of ProfileInfo objects with daemon status.
        embed N)r0   r+   r>   r?   r@   rA   r+   r>   r?   c                 &    | j         dk    | j         fS )Nr_   )r0   )ps    r   <lambda>z.ProfileManager.list_profiles.<locals>.<lambda>   s    qv|QV.D r   )key)_load_metadataget_active_profilerQ   existsappendr=   DEFAULT_PORT_check_daemon_runningr#   itemsgetsorted)r3   metadatar%   r#   default_configr0   infos          r   list_profileszProfileManager.list_profiles   sU    &&((0022 --//'9  "" 
	OO%!",2#'#=#=l#K#K  	 	 	 #+1133 
	 
	JD$OOf#xxb99"hh{33,4#'#=#=d6l#K#K  	 	 	 	 h$D$DEEEEr   r0   c                     |s)|                                  dz                                  S |                                 | dz  }|                                S )zCheck if a profile exists.

        Args:
            name: Profile name (empty string for default).

        Returns:
            True if profile exists.
        r^   .env)rQ   rf   rT   )r3   r0   config_paths      r   profile_existszProfileManager.profile_exists   s_      	?((**W4<<>>> ,,..D>!!###r   c                 V    |                                  }|D ]}|j        |k    r|c S dS )zGet profile information.

        Args:
            name: Profile name (empty string for default).

        Returns:
            ProfileInfo if profile exists, None otherwise.
        N)rp   r0   )r3   r0   r#   profiles       r   get_profilezProfileManager.get_profile   sD     %%'' 	 	G|t## $tr   Nport_or_configr(   c                    t          |t                    rd}|}n|}|t          d          |st          d          |                    dd                              dd                                          st          d| d          ||d	k     s|d
k    rt          d| d          |                                  |                                 }|A||j        v r#d|j        |         v r|j        |         d         }n|                     |          }| 	                                | dz  }d |
                                D             }|                    d                    |          dz              t          j        t          j                                                  }||j        v r!||j        |         d<   ||j        |         d<   n|||d|j        |<   |                     |           dS )a  Create or update a profile.

        Args:
            name: Profile name.
            port_or_config: Port number (int) or configuration dict. For backward compatibility,
                            if this is a dict, it's treated as config and port is auto-allocated.
            config: Configuration dict (KEY=VALUE pairs). Only used if port_or_config is an int.

        Raises:
            ValueError: If profile name is invalid or port is invalid.
        Nz.Config must be provided when port is specifiedzProfile name cannot be empty-r_   _zInvalid profile name 'z4'. Use alphanumeric chars, hyphens, and underscores.i   i  zInvalid port z. Must be between 1024-65535.r+   rr   c                 "    g | ]\  }}| d | S )=r;   ).0rc   values      r   
<listcomp>z1ProfileManager.create_profile.<locals>.<listcomp>  s*    JJJZS%3((((JJJr   
r?   )r+   r>   r?   )
isinstancerH   
ValueErrorr1   isalnumrL   rd   r#   _allocate_portrT   rj   
write_textjoinr   nowr   utc	isoformat_save_metadata)	r3   r0   rx   r(   r+   rm   rs   config_linesnow_isos	            r   create_profilezProfileManager.create_profile   s4    nd++ 	SD#FF "D~ !QRRR 	=;<<<||C$$,,S"55==?? 	rpdpppqqquPTPPPQQQ 	  """ &&(( <x(((Vx7H7N-N-N(.v6**400 ,,..D>JJ6<<>>JJJtyy66=>>> ,x|,,66888$$$3:Hd#K0.2Hd#F++ %$' 'Hd# 	H%%%%%r   c                    |st          d          |                     |          st          d| d          |                                 | dz  }|                                r|                                 |                                 | dz  }|                                r|                                 |                                 | dz  }|                                r|                                 |                                 }||j        v r|j        |= |                     |           |                                 |k    r| 	                    d           dS dS )zDelete a profile.

        Args:
            name: Profile name.

        Raises:
            ValueError: If profile name is invalid or doesn't exist.
        zCannot delete default profile	Profile '' does not existrr   .lockr.   N)
r   rt   rT   rf   unlinkrd   r#   r   re   set_active_profile)r3   r0   rs   	lock_pathlog_pathrm   s         r   delete_profilezProfileManager.delete_profile  s     	><===""4(( 	A????@@@ ,,..D> 	!    **,,$~~~=	 	 ))++mmm;?? 	OO &&((8$$$!$'))) ""$$,,##D))))) -,r   c                    |r(|                      |          st          d| d          |                                 }|r|                    |           dS |                                r|                                 dS dS )zSet the active profile.

        Args:
            name: Profile name to activate, or None to clear.

        Raises:
            ValueError: If profile doesn't exist.
        r   r   N)rt   r   rX   r   rf   r   )r3   r0   active_files      r   r   z!ProfileManager.set_active_profileG  s      	A++D11 	A????@@@3355 	%""4((((( !!## %""$$$$$% %r   c                     |                                  }|                                r&|                                                                S dS )zGet the currently active profile name.

        Returns:
            Profile name, or empty string if no active profile.
        r_   )rX   rf   	read_textstrip)r3   r   s     r   re   z!ProfileManager.get_active_profile[  sL     3355 	3((**00222rr   c                 t   t          j                    dz  }|dz  }|s!t          |dz  |dz  |dz  t                    S |                                 }|j                            |i                               d|                     |                    }t          || dz  || d	z  || d
z  |          S )zResolve paths for a profile.

        Args:
            name: Profile name (empty string for default).

        Returns:
            ProfilePaths with config, lock, log, and port.
        r"   r#   r^   zdaemon.lockz
daemon.log)r(   r)   r*   r+   r+   rr   r   r.   )r   rP   r'   rh   rd   r#   rk   r   )r3   r0   
config_dirprofiles_dirrm   r+   s         r   resolve_profile_pathsz$ProfileManager.resolve_profile_pathsf  s     Y[[</
!J. 	!G+-/-!	    &&(( $$T2..2264;N;Nt;T;TUUT---/4.$}}},	
 
 
 	
r   c                 \   |                      |          }i }|j                                        s|S t          |j                  5 }|D ]}|                                }|r|                    d          r.|                    d          r
|dd         }d|v rB|                    dd          \  }}|                                ||                                <   	 ddd           n# 1 swxY w Y   ddd	d
ddd}|                                D ]\  }	}
|	|v r|
|vr||	         ||
<   |S )a_  Load configuration from a profile's .env file.

        Args:
            name: Profile name (empty string for default).

        Returns:
            Dictionary of environment variable key-value pairs from the profile's .env file.
            Also includes simple key aliases (e.g., 'idle_timeout' for 'HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT').
        #zexport    Nr}   r   llm_api_keyllm_provider	llm_modelllm_base_url	log_levelidle_timeout)HINDSIGHT_API_LLM_API_KEYHINDSIGHT_API_LLM_PROVIDERHINDSIGHT_API_LLM_MODELHINDSIGHT_API_LLM_BASE_URLHINDSIGHT_API_LOG_LEVEL#HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT)r   r(   rf   openr   
startswithsplitrj   )r3   r0   pathsr(   flinerc   r   key_aliasesenv_key
simple_keys              r   load_profile_configz"ProfileManager.load_profile_config  s    **400|""$$ 	M %, 	81 8 8zz|| ts33 ??9-- $8D$;;!%C!3!3JC*/++--F399;;'8	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8 	8" *7*8'2*8'23A
 
 $/#4#4#6#6 	5 	5GZ&  Zv%=%=%+G_z"s   BC++C/2C/c                 &   t          t          j        |                                                                          d          }t
          |t          z  z   }|                                 }d |j        	                                D             }d}||v r4|t          k     r)t
          ||z   t          z  z   }|dz  }||v r|t          k     )|t          k    r<t          t
          t
          t          z             D ]
}||vr|c S t          d          |S )zAllocate a port for a profile using hash-based strategy.

        Args:
            name: Profile name.

        Returns:
            Port number (8889-9888).
           c                 H    h | ]}|                     d           |d           S )r+   )rk   )r~   ro   s     r   	<setcomp>z0ProfileManager._allocate_port.<locals>.<setcomp>  s1    cccDRVRZRZ[aRbRbc4<cccr   r   r   zNo available ports for profile)r:   hashlibsha256encode	hexdigestPROFILE_PORT_BASEPROFILE_PORT_RANGErd   r#   valuesrangeRuntimeError)r3   r0   hash_valr+   rm   allocated_portsattemptra   s           r   r   zProfileManager._allocate_port  s%    w~dkkmm44>>@@"EE H/A$AB &&((ccH4E4L4L4N4Nccc o%%'4F*F*F$G);?Q(QRDqLG o%%'4F*F*F (((,.?BT.TUU  O++HHH ,?@@@r   r+   c                     	 t          j                    5 }|                    d| dd          }|j        dk    cddd           S # 1 swxY w Y   dS # t          $ r Y dS w xY w)zCheck if daemon is running on a port.

        Args:
            port: Port number to check.

        Returns:
            True if daemon is responding.
        zhttp://127.0.0.1:z/healthr   )timeout   NF)httpxClientrk   status_code	Exception)r3   r+   clientresponses       r   ri   z$ProfileManager._check_daemon_running  s    	 36!::&G$&G&G&GQR:SS+s23 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3  	 	 	55	s3   A &AA AA AA 
A#"A#c                 x   |                                  }|                                st                      S 	 t          |          5 }t	          j        |          }t          |                    dd          |                    di                     cddd           S # 1 swxY w Y   dS # t          j        t          f$ ru}t          d| dt          j                   |                    d	          }|                                r|                    |           t                      cY d}~S d}~ww xY w)
z_Load profile metadata from disk.

        Returns:
            ProfileMetadata object.
        rF   r   r#   rF   r#   Nz"Warning: Failed to load metadata: z. Using empty metadata.filez	.json.bak)rV   rf   rE   r   jsonloadrk   JSONDecodeErrorIOErrorprintsysstderrwith_suffixrename)r3   metadata_filer   dataebackup_paths         r   rd   zProfileManager._load_metadata  s    //11##%% 	%"$$$	%m$$ jy||&txx	1/E/EPTPXPXYcegPhPhiiij j j j j j j j j j j j j j j j j j $g. 		% 		% 		%OQOOOZ   
 (33K@@K##%% 2$$[111"$$$$$$$$		%sC   B. AB!B. !B%%B. (B%)B. .D9A*D4.D94D9rm   c                     |                                   |                                 }|                    d          }t          |d          5 }t	          |           	 t          j        |j        |j        d|d           |	                                 t          j        |                                           t          |           n# t          |           w xY w	 ddd           n# 1 swxY w Y   |                    |           dS )zwSave profile metadata to disk with file locking.

        Args:
            metadata: ProfileMetadata to save.
        z	.json.tmpwr      )indentN)rL   rV   r   r   r   r   dumprF   r#   flushosfsyncr   r   r1   )r3   rm   r   	temp_filer   s        r   r   zProfileManager._save_metadata   s@    	  """ //11!--k::	)S!! 	QaLLL		 ( 0h>OPP   
 			$$$AA	 	 	 	 	 	 	 	 	 	 	 	 	 	 	" 	-(((((s+   C.AC=C.CC..C25C2r   )!r5   r6   r7   r8   rM   r   rQ   rT   rV   rX   rL   listr=   rp   rB   rC   rt   r   rw   r:   rH   r   r   r   re   r'   r   r   r   ri   rE   rd   r   r;   r   r   rJ   rJ   z   s       ==# # #* * * * *34 3 3 3 3:D : : : :9$ 9 9 9 9D D D%FtK0 %F %F %F %FN$3 $4 $ $ $ $" (=    A& A&3 A&d38n8L A&VZ[^`c[cVdgkVk A& A& A& A&F&*3 &* &* &* &*P%x} % % % %(	C 	 	 	 	
# 
, 
 
 
 
B. .S#X . . . .`3 3    @# $     % % % % %2) ) ) ) ) ) )r   rJ   rN   c                      t          j        d          x} r| S ddlm} |                                x}r|S t                      }|                                x}r|S dS )a?  Resolve which profile to use based on priority.

    Priority (highest to lowest):
    1. HINDSIGHT_EMBED_PROFILE environment variable
    2. CLI --profile flag (from global context)
    3. Active profile from file
    4. Default (empty string)

    Returns:
        Profile name to use (empty string for default).
    HINDSIGHT_EMBED_PROFILEr   )clir_   )r   getenvr_   r   get_cli_profile_overriderJ   re   )env_profiler   cli_profilepmr%   s        r   resolve_active_profiler      s     i 9:::{  22444{  
		B..000~  2r   rv   c                     | sdS t                      }|                    |           sSt          d|  dt          j                   t          d|  t          j                   t          j        d           dS dS )zValidate that a profile exists, exit if not.

    Args:
        profile: Profile name to validate.

    Exits:
        If profile doesn't exist, prints error and exits.
    NzError: Profile 'z' not found.r   z4Create it with: hindsight-embed configure --profile r   )rJ   rt   r   r   r   exit)rv   r   s     r   validate_profile_existsr   ?  s      			BW%% 	4w444	
 	
 	
 	
 	L7LL	
 	
 	
 	
 		 	r   )$r8   r   r   r   r   dataclassesr   r   r   r   pathlibr   typingr   platformr   r   r   r   r   rP   
CONFIG_DIRPROFILES_DIRMETADATA_FILEACTIVE_PROFILE_FILErh   r   r   UI_PORT_OFFSETr'   r=   rE   rJ   rB   r   r   r;   r   r   <module>r     s[      				 



 ( ( ( ( ( ( ( ( ' ' ' ' ' ' ' '            8 <7LLL6 6 66 6 6 6 MMM= = => > >
  TY[[<'
J&. #33      U U U U U U U U ! ! ! ! ! ! ! ! < < < < < < < <c) c) c) c) c) c) c) c)L    >S      r   