
    %j                        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 ddlZddlmZ ddlmZ ddlmZ ddlmZ d	d
lmZ d	dlmZmZmZmZmZ  ej        e          Z  ed          Z! ej        d          "                    ej#                    e$ ej%        dd                    Z&dZ'dee(         de)fdZ* G d de          Z+dS )z
Concrete implementation of EmbedManager using daemon-based architecture.

This module provides the production implementation of the embed management interface,
consolidating daemon lifecycle, profile management, and database URL resolution.
    N)Path)IOOptional)Console)Live)Panel)Text   )EmbedManager)UI_PORT_OFFSETProfileManager	lock_fileresolve_active_profileunlock_fileT)stderrhttpx&HINDSIGHT_EMBED_DAEMON_STARTUP_TIMEOUT180
log_handlereturnc                     t          j                    dk    rKt          t          dd          }t          t          dd          }||z  t          j        | t          j        ddS d| | dS )ad  Cross-platform kwargs to spawn a subprocess detached from the caller.

    On POSIX, `start_new_session=True` calls setsid(2) so the child
    survives the parent's terminal. On Windows there is no setsid: we use
    `DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP`, which also means the
    child has no console, so stdin/stdout/stderr MUST be redirected or any
    write from the child crashes with "handle is invalid".

    `log_handle` receives the child's stdout/stderr on both platforms so
    output never leaks into the parent's terminal (which would corrupt a
    TUI parent communicating over stdio).
    WindowsDETACHED_PROCESSr   CREATE_NEW_PROCESS_GROUPT)creationflagsstdinstdoutr   	close_fds)start_new_sessionr   r   )platformsystemgetattr
subprocessDEVNULLSTDOUT)r   detached_processcreate_new_process_groups      l/home/rurouni/.hermes/hermes-agent/venv/lib/python3.11/site-packages/hindsight_embed/daemon_embed_manager.py_detach_popen_kwargsr)   )   s     I%% #:/A1EE#*:7QST#U#U -0HH'  '
 
 	
 "      c            	       B   e Zd ZdZd Zdedz  defdZd!dedee         defdZdedefd	Z	dede
fd
Zdee         fdZedede
fd            Zedededz  fd            Zedede
fd            Zdede
fdZd!dededee         dz  de
fdZ	 d!dededee         dz  de
fdZdedededdfdZdee         fdZd"dededz  dedz  defdZd!dededz  de
fdZd#dededz  dede
fdZd!dededz  de
fdZd!dededee         dz  de
fdZdede
fd ZdS )$DaemonEmbedManagerzPProduction embed manager using daemon-based architecture with profile isolation.c                 ,    t                      | _        dS )z$Initialize the daemon embed manager.N)r   _profile_manager)selfs    r(   __init__zDaemonEmbedManager.__init__M   s     . 0 0r*   profileNr   c                 6    |dS t          j        dd|          S )z?Sanitize profile name for use in database names and file paths.Ndefaultz[^a-zA-Z0-9_-]-)resub)r/   r1   s     r(   _sanitize_profile_namez)DaemonEmbedManager._sanitize_profile_nameQ   s!    ?9v'g666r*   db_urlc                 J    |r|dk    r|S |                      |          }d| S )z
        Get the database URL for this profile.

        Args:
            profile: Profile name
            db_url: Optional override database URL

        Returns:
            Database connection string
        pg0zpg0://hindsight-embed-)r7   )r/   r1   r8   safe_profiles       r(   get_database_urlz#DaemonEmbedManager.get_database_urlW   s:      	fooM227;;6666r*   c                 J    | j                             |          }d|j         S )a  
        Get the URL for the daemon serving this profile.

        Args:
            profile: Profile name

        Returns:
            URL string (e.g., "http://127.0.0.1:54321")

        Raises:
            RuntimeError: If daemon is not running
        http://127.0.0.1:)r.   resolve_profile_pathsport)r/   r1   pathss      r(   get_urlzDaemonEmbedManager.get_urlg   s*     %;;GDD/5:///r*   c                     |                      |          }	 t          j        d          5 }|                    | 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)z*Check if daemon is running and responsive.   timeout/health   NF)rB   r   Clientgetstatus_code	Exception)r/   r1   
daemon_urlclientresponses        r(   
is_runningzDaemonEmbedManager.is_runningw   s    \\'**
	a((( 3F!::&<&<&<==+s23 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3  	 	 	55	s4   A) #AA) A  A) #A $A) )
A76A7c                    t          t                    j        j        j        dz  }|                                r-|dz                                  rdddt	          |          dddgS t          j                    d	k    rd
nd}t          t          j        d                    }||z  }|                                rt	          |          gS t          t                    j        j        }dD ]0}||z  |z  }|                                rt	          |          gc S 1ddl	m
} t          j        d|          }dd| gS )z&Find the command to run hindsight-api.zhindsight-api-slimzpyproject.tomluvrunz	--projectz--extraallzhindsight-apir   zhindsight-api.exescripts)binScriptsr
   __version__HINDSIGHT_EMBED_API_VERSIONuvxzhindsight-api@)r   __file__parentexistsstrr    r!   	sysconfigget_path rY   osgetenv)	r/   dev_api_pathbinary_namescripts_dir	candidatepackage_rootbin_dirrY   api_versions	            r(   _find_api_commandz$DaemonEmbedManager._find_api_command   si    H~~,3:=QQ   	dl5E&E%M%M%O%O 	d%c,.?.?ESbcc .6_->->)-K-K))Q`9-i8899+-	 	$	NN## H~~,3) 	( 	(G$w.<I!! (I''''( 	"!!!!!i ={KK55566r*   r@   c                     ddl }|                     |j        |j                  5 }|                    d           |                    d| f          dk    cddd           S # 1 swxY w Y   dS )zECheck if a port is in use using a socket connection (cross-platform).r   Nr
   	127.0.0.1)socketAF_INETSOCK_STREAM
settimeout
connect_ex)r@   ro   socks      r(   _is_port_in_usez"DaemonEmbedManager._is_port_in_use   s     	]]6>6+=>> 	=$OOA??K#6771<	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	=s   0A""A&)A&c                    ddl }	  |j                    dk    rt          j        g dddd          }|j        dk    rb|j                                        D ]H}d|  |v r?d	|v r;t          |                                	                                d
                   c S Int          j        ddd|  dgddd          }|j        dk    rW|j                                        r>t          |j                                        	                                d                   S n(# t          j
        t          t          t          f$ r Y nw xY wdS )z0Find the PID of the process listening on a port.r   Nr   )netstatz-anoz-pTCPT   )capture_outputtextrF   z
127.0.0.1:	LISTENINGlsofz-ti:z-sTCP:LISTEN)r    r!   r#   rS   
returncoder   
splitlinesintstripsplitTimeoutExpired
ValueErrorOSErrorFileNotFoundError)r@   r    resultlines       r(   _find_pid_on_portz$DaemonEmbedManager._find_pid_on_port   s    		x  I--#444#'	   $)) & 8 8 : : A A...$66;$;N;N#&tzz||'9'9';';B'?#@#@@@@ $UJJJ?#'	   $))fm.A.A.C.C)v}2244::<<Q?@@@):w@QR 	 	 	D	ts   BD# BD# #"EEpidc                    ddl }	 t          j        | |j                   t	          d          D ]>}t          j        d           	 t          j        | d           -# t          $ r Y  dS w xY wn# t          $ r Y dS w xY wdS )zOKill a process by PID and wait for it to exit. Returns True if process is gone.r   N2   皙?TF)signalrc   killSIGTERMrangetimesleepr   )r   r   _s      r(   _kill_processz DaemonEmbedManager._kill_process   s     			GC(((2YY    
3 GCOOOO      444 	   	 	 	44	us5   ?A/ AA/ 
A+&A/ *A++A/ /
A=<A=c                    |                      |          sdS d}	 t          j        d          5 }|                    d| d          }|j        dk    }ddd           n# 1 swxY w Y   n# t
          $ r d}Y nw xY w|r t                              d	| d
           dS |                     |          }| t          	                    d	| d           dS t          
                    d| d| d           |                     |          r t          
                    d| d           dS t          	                    d| d           dS )uF  
        Ensure the port is free before starting a daemon.

        Behavior:
          * Port free → True (nothing to do).
          * Port occupied by a *healthy* hindsight daemon → True without killing.
            The caller's "start" is effectively a no-op: the daemon is already up.
            Killing it would race concurrent starts (one process kills the other's
            freshly-started daemon, both rush to rebind the port).
          * Port occupied but /health is unreachable / non-200 → treat as a stale
            hindsight daemon (or foreign process) and attempt to reclaim by killing
            the PID listening on the port. This preserves the original intent of
            clearing stale daemons from version upgrades.
          * Kill failed, or non-hindsight process occupying the port → False.
        TFrD   rE   r>   rG   rH   NzPort z7 already serving a healthy hindsight daemon; reusing itz is in use by another processz#Clearing unhealthy process on port z (PID )zStale process (PID z	) stoppedzProcess (PID z) did not stop in time)ru   r   rI   rJ   rK   rL   loggerdebugr   warninginfor   )r/   r@   	health_okrN   rO   r   s         r(   _clear_portzDaemonEmbedManager._clear_port   s     ##D)) 	4 		a((( 8F!::&G$&G&G&GHH$0C7	8 8 8 8 8 8 8 8 8 8 8 8 8 8 8  	 	 	III	  	LL^^^^___4 $$T**;NNF4FFFGGG5L$LLcLLLMMMc"" 	KK<c<<<===4BsBBBCCCus4   A- %A!A- !A%%A- (A%)A- -A<;A<config
extra_argsc                 J   | j                             |          }|j        j                            dd           t          |j        d          5 }t          |           	 |                     |          rYt          	                    d| d           |r| 
                    ||j        |           	 t          |           ddd           dS |                     ||||          t          |           cddd           S # t          |           w xY w# 1 swxY w Y   dS )a  Start the daemon in background.

        Serializes concurrent start attempts via an exclusive flock on the
        profile's lock file, so two processes calling `start()` at the same
        time cannot race into `_clear_port` and kill each other's daemons.
        Inside the lock we re-check `is_running()`; the second caller sees
        the first caller's daemon and short-circuits.
        Tparentsexist_okwDaemon for profile 'z&' came up while waiting for start lockNr   )r.   r?   lockr]   mkdiropenr   rP   r   r   _register_profiler@   r   _start_daemon_locked)r/   r   r1   r   rA   lock_fds         r(   _start_daemonz DaemonEmbedManager._start_daemon  s    %;;GDD
t<<<
 %*c"" 
	%gg%??7++  LL!g!g!g!ghhh L..w
FKKK G$$$
	% 
	% 
	% 
	% 
	% 
	% 
	% 
	% 00%T^0__G$$$
	% 
	% 
	% 
	% 
	% 
	% 
	% 
	% G$$$$
	% 
	% 
	% 
	% 
	% 
	% 
	% 
	% 
	% 
	%s7   D"AD4DD(DDDDDc                    |rd| dnd}|j         }|j        }|                     |          s t                              d| d           dS |                     |          r9t                              d| d           |r|                     |||           d	S | j        	                    |          }i ||}	|	}t          j                                        }
d
dddddd}|                                D ]C\  }}|                    |          p|                    |          }|rt          |          |
|<   D|                                D ].\  }}|                    d          r|t          |          |
|<   /|                    d          p|
                    d          }|r||
d<   n|                     |          |
d<   |
d         }|                    d          }d|
vrd|
d<   d|
vrt          t$                    |
d<   d|
d<   t'          |
                    dt          t$                                        }|j                            d	d	           t          |          |
d<   |                                 ddt          |          dt          |          gz   }|r|                    |           	 t1          |d          5 }t3          j        |fd|
it7          |           ddd           n# 1 swxY w Y   t9          j                    }|}d }d!| d"d#g}d$| d%| d&}t;          t<          d'          5 }t?          d(                     |          d)*          }tC          ||d+d,-          }|"                    |           |#                                 t9          j                    |z
  tH          k     r|%                                r	 t1          |d.          5 }|&                    |           |'                                }|(                                }|D ]-} | )                                } | r|*                    |            .|d/d         }ddd           n# 1 swxY w Y   n# tV          $ r Y nw xY w|                     |          r|*                    d#           |*                    d0           t?          d(                     |          d)*          }tC          ||d+d,-          }|"                    |           |#                                 t9          j,        d1           |                     |          r7|*                    d2           |*                    d#           |*                    d3|            |rJ|-                    dd#          }!t]          j/                    d4z  d5z  |!z  }"|*                    d6|"            t?          d(                     |          d)*          }d7| d%| d&}#tC          ||#d8d,-          }|"                    |           |#                                 t<          0                                 |r|                     |||           	 ddd           d	S |*                    d#           |*                    d9           t?          d(                     |          d)*          }d:| d%| d&}$tC          ||$d;d,-          }|"                    |           |#                                 t<          0                                 n"t9          j                    |z
  d<k    rtt'          t9          j                    |z
            }%d=|% d>}&|r!|d?                             d@          r|&|d?<   n|*                    |&           t9          j                    }t?          d(                     |          d)*          }tC          ||d+d,-          }|"                    |           |#                                 t9          j,        dA           t9          j                    |z
  tH          k     ddd           n# 1 swxY w Y   |*                    d#           |*                    dB           |*                    d#           |*                    dC|            t?          d(                     |          d)*          }dD| d%| d&}'tC          ||'d;d,-          }t<          0                    |           t<          0                                 dS # tb          $ r}(dE|d           dFdG                     |           dH})tC          t?          |)d;*          dId;d,-          }*t<          0                    |*           t<          0                                 Y d}(~(dS d}(~(wtV          $ r~}(dJ|( dKdG                     |           dL| })tC          t?          |)d;*          dMd;d,-          }*t<          0                    |*           t<          0                                 Y d}(~(dS d}(~(ww xY w)Nz?Perform the actual daemon startup. Caller must hold paths.lock.	profile ''default profilezCannot start daemon: port z% is in use by a non-hindsight processFr   z!' already healthy; skipping spawnT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)llm_api_keyllm_provider	llm_modelllm_base_url	log_levelidle_timeout
HINDSIGHT_N HINDSIGHT_EMBED_API_DATABASE_URLHINDSIGHT_API_DATABASE_URLzpg0://r   1_HINDSIGHT_DAEMON_CHILDr   HINDSIGHT_API_DAEMON_LOGz--daemonz--idle-timeout--portabenvr   zStarting daemon for ...rb   z-[bold cyan]Starting Daemon[/bold cyan] [dim]( @ :)[/dim]consoleauto_refresh
dimstylecyanr
   rD   titleborder_stylepaddingru-   ✓ Daemon responding, verifying stability...rD   u    ✓ Daemon started successfully!Logs: z.pg0	instancesz
Database: u2   [bold green]✓ Daemon Started[/bold green] [dim](greenu(   ✗ Daemon crashed during initializationu-   [bold red]✗ Daemon Failed[/bold red] [dim](red   u   ⏳ Waiting for daemon... (
s elapsed)r}      ⏳      ?u$   ✗ Daemon failed to start (timeout)zSee full log: u7   [bold red]✗ Daemon Failed (Timeout)[/bold red] [dim](Command not found: 
Full command:  z7

Install hindsight-api with: pip install hindsight-api*   [bold red]✗ Command Not Found[/bold red]zFailed to start daemon: 

Command: 
Log file: u&   [bold red]✗ Startup Error[/bold red])2logr@   r   r   errorrP   r   r   r.   load_profile_configrc   environcopyitemsrJ   r_   
startswithr<   DEFAULT_DAEMON_IDLE_TIMEOUTr   r]   r   rl   extendr   r#   Popenr)   r   r   r   r	   joinr   updaterefreshDAEMON_STARTUP_TIMEOUTr^   seek	readlinestellrstripappendrL   r   replacer   homeprintr   )+r/   r   r1   rA   r   profile_label
daemon_logr@   profile_configmerged_configr   key_mapping
simple_keyenv_keyvaluekeydb_overridedatabase_urlis_pg0r   cmddaemon_log_handle
start_timelast_check_timelast_log_position	log_linesr   livecontentpanelf	new_linesr   pg0_namepg0_pathsuccess_title
fail_titleelapsed
status_msgtimeout_titlee	error_msgerror_panels+                                              r(   r   z'DaemonEmbedManager._start_daemon_locked'  s    3:P.G....?PY
z %% 	LLadaaabbb5 ??7## 	LLZZZZ[[[ >&&wf===4 .BB7KK4>4V4 joo 78282A
 
 $/#4#4#6#6 	* 	*JJJz**Afjj.A.AE *"5zzG !,,.. 	& 	&JC~~l++ &0Au::C jj!CDDsPrHsHs 	O0;C,--040E0Eg0N0NC,-78((22 %C//-3C)*0;;9<=X9Y9YC56 *-%& 377#H#NiJjJjkkll 	t<<<*-j//&' $$&&II*
 
  	#JJz"""G	 j$'' Z+< YY#Y1EFW1X1XYYYZ Z Z Z Z Z Z Z Z Z Z Z Z Z Z J(O !BBBBBGI^G^^QU^^^EgE::: N$dtyy335AAAgUQWXXXE"""ikkJ.1GGG!((** !!!%j#!6!6 ;! !'8 9 9 9,-KKMM	45FFHH 1,5 !? !?D+/;;==D'+ %?(1(8(8(>(>(>,5bccN	; ; ; ; ; ; ; ; ; ; ; ; ; ; ;  ) ! ! ! D! w// ("!((,,,!(()XYYY"&tyy';';5"I"I"I %gUY_ ` ` `E***
1??733 "%,,-OPPP%,,R000%,,-Bj-B-BCCC% J+7+?+?"+M+M+/9;;+?++MPX+X ) 0 01Hh1H1H I I I&*499Y+?+?u&M&M&MG oU\ o obf o o o * %*'U\fl$m$m$mE KK... LLNNN#MMOOO& N $ 6 6wf M M M#'iN$ N$ N$ N$ N$ N$ N$ N$l &,,R000%,,-WXXX&*499Y+?+?u&M&M&MG)sY`)s)sfj)s)s)sJ$)'RWag$h$h$hE KK... LLNNN#MMOOO! y{{_4q88"%dikkJ&>"?"?%V7%V%V%V
$ 92)A)A%)H)H 9,6IbMM%,,Z888*.)++"499Y#7#7uEEEG!'VU[\\\EKK&&&LLNNNJsOOOQ ikkJ.1GGGN$ N$ N$ N$ N$ N$ N$ N$ N$ N$ N$ N$ N$ N$ N$b R   CDDDR   :j::;;;499Y//u===GpV]ppcgpppM'UTZ[[[EMM%   MMOOO5  	 	 	Hc!f H Hchhsmm H H H   Ye,,,B"	  K MM+&&&MMOOO55555 
	 
	 
	j1jj388C==jj^hjjIYe,,,>"	  K MM+&&&MMOOO55555
	s   e "K?3e ?Le LAe Ba+Q>.A8Q2&Q>2Q6	6Q>9Q6	:Q>=a+>
Ra+
RG7a+e Ga+e +a//e 2a/3Ce 
iA7g		iA3iic                     	 d |                                 D             }|sdS | j                            |||           dS # t          $ r+}t                              d| d|            Y d}~dS d}~ww xY w)zRegister a named profile in metadata so it's discoverable by the CLI.

        Only saves HINDSIGHT_API_* config keys (not internal daemon keys).
        Silently ignores errors to avoid blocking daemon startup.
        c                 D    i | ]\  }}|                     d           ||S )HINDSIGHT_API_)r   ).0kvs      r(   
<dictcomp>z8DaemonEmbedManager._register_profile.<locals>.<dictcomp>  s0    \\\41aQ\\JZ=[=[\!Q\\\r*   NzFailed to register profile 'z' in metadata: )r   r.   create_profilerL   r   r   )r/   r1   r@   r   
api_configr  s         r(   r   z$DaemonEmbedManager._register_profile  s    	U\\6<<>>\\\J !00$
KKKKK 	U 	U 	ULLSSSPQSSTTTTTTTTT	Us    A A 
A7 A22A7c                     t          t                    j        j        j        dz  }|dz  dz  }|                                rdt	          |          gS ddlm} t          j        d|          }dd	d
| gS )z-Find the command to run the control plane UI.zhindsight-control-planerV   zcli.jsnoder
   rX   HINDSIGHT_EMBED_CP_VERSIONnpxz-yz&@vectorize-io/hindsight-control-plane@)	r   r\   r]   r^   r_   rb   rY   rc   rd   )r/   dev_cp_pathcli_jsrY   
cp_versions        r(   _find_ui_commandz#DaemonEmbedManager._find_ui_command#  s     8nn+29<UUu$x/==?? 	)CKK(( 	"!!!!!Y;[II
 tRjRRSSr*   ui_porthostnamec                 p    |)| j                             |          }|j        t          z   }|pd}d| d| S )z,Get the URL for the UI serving this profile.N0.0.0.0zhttp://r   )r.   r?   r@   r   )r/   r1   r1  r2  rA   hosts         r(   
get_ui_urlzDaemonEmbedManager.get_ui_url4  sI    ?)??HHEj>1G$9))))))r*   c                     |                      ||d          }	 t          j        d          5 }|                    | 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)	z*Check if the UI is running and responsive.rn   )r2  rD   rE   z/api/healthrH   NF)r6  r   rI   rJ   rK   rL   )r/   r1   r1  ui_urlrN   rO   s         r(   is_ui_runningz DaemonEmbedManager.is_ui_running<  s     'KHH	a((( 3F!::&<&<&<==+s23 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3  	 	 	55	s4   A, #AA, A##A, &A#'A, ,
A:9A:r4  c           	      
   | j                             |          }||j        t          z   }|                     ||          r"t
                              d| d|            dS |rd| dnd}|                     |          }|j        }t          j
                                        }t          |          |d<   ||d	<   ||d
<   |j                            dd           |                                 dt          |          d|d|gz   }		 t!          |d          5 }
t#          j        |	fd|it'          |
           ddd           n# 1 swxY w Y   t)          j                    }d|pd d| d}d| ddg}t+          t,          d          5 }t/          d                    |          d          }t3          ||dd          }|                    |           |                                 t)          j                    |z
  d k     r|                     ||          r|                    d!|            |                    d"|            t/          d                    |          d          }d#|pd d| d}t3          ||d$d          }|                    |           |                                 t,                                           	 ddd           dS t=          t)          j                    |z
            }d%| d&}|r!|d'                             d(          r||d'<   n|                    |           t/          d                    |          d          }t3          ||dd          }|                    |           |                                 t)          j         d)           t)          j                    |z
  d k     ddd           n# 1 swxY w Y   t,                              t3          t/          d*| d          d+| dd,d                     t,                                           dS # tB          $ ry d-|	d.          d/d0                    |	           d1}t,                              t3          t/          |d,          d2d,d                     t,                                           Y dS tD          $ r|}d3| d4d0                    |	           d5| }t,                              t3          t/          |d,          d6d,d                     t,                                           Y d}~dS d}~ww xY w)7a*  Start the control plane UI in background.

        Args:
            profile: Profile name.
            ui_port: Port for the UI. Defaults to daemon_port + 10000.
            hostname: Hostname to bind to. Defaults to 0.0.0.0.

        Returns:
            True if UI started successfully.
        Nz UI already running for profile 'z
' on port Tr   r   r   PORTHOSTNAMEHINDSIGHT_CP_DATAPLANE_API_URLr   r   z
--hostnamez	--api-urlwbr   z)[bold cyan]Starting UI[/bold cyan] [dim](r3   r   r   zStarting UI for r   rb   Fr   r   r   r   r   r   r      u#   ✓ UI started at http://127.0.0.1:r   u.   [bold green]✓ UI Started[/bold green] [dim](r   u   ⏳ Waiting for UI... (r   r}   r   r   z,UI failed to start (timeout)

See full log: u4   [bold red]✗ UI Failed (Timeout)[/bold red] [dim](:r   r   r   r   r   z(

Install Node.js and npx to run the UI.r   zFailed to start UI: r   r   u)   [bold red]✗ UI Startup Error[/bold red])#r.   r?   r@   r   r9  r   r   rB   ui_logrc   r   r   r_   r]   r   r0  r   r#   r   r)   r   r   r   r	   r   r   r   r   r   r   r   r   r   r   rL   )r/   r1   r1  r2  rA   r   api_urlr@  r   r  log_filer  r   r  r  r  r  r  r  r  r  r  s                         r(   start_uizDaemonEmbedManager.start_uiG  sa    %;;GDD?j>1Ggw// 	LLXGXXwXXYYY429P.G....?P,,w'' joo'llF"J07,- 	D4888 ##%%LL)
 
O	fd## Qx PP#P1Eh1O1OPPPQ Q Q Q Q Q Q Q Q Q Q Q Q Q Q Jj@T9jjZajjjE>M>>>CIgE::: $dtyy335AAAgUQWXXXE"""ikkJ.33))'7;; $!(()Xw)X)XYYY!(():&):):;;;"&tyy';';5"I"I"IwWMaXawwgnwww & !&g]QXbh i i iE***#%$ $ $ $ $ $ $ $( "$)++
":;;G!N7!N!N!NJ  5Yr]%=%=e%D%D 5(2	"!((444"499Y#7#7uEEEG!'VU[\\\EKK&&&LLNNNJsOOO3 ikkJ.33$ $ $ $ $ $ $ $ $ $ $ $ $ $ $D MMR&RRZ_```aQXaaa!&"	     MMOOO5  	 	 	wc!fwwchhsmmwww  MM%000F!&"	     MMOOO55 	 	 	bqbbsxx}}bbZ`bbIMM%000E!&"	     MMOOO55555	sw   Q "E	=Q 	EQ E?Q EO Q $C0O Q  O$$Q 'O$(AQ A?U		UA1U		Uc                 D   | j                             |          }||j        t          z   }|                     ||          s t
                              d| d           dS |                     |          }|6t
                              d| d|            |                     |           nt
          	                    d|            t          d          D ]/}|                     ||          s dS t          j        d	           0|                     ||           S )
zStop the UI for this profile.

        Args:
            profile: Profile name.
            ui_port: Port the UI is running on. Defaults to daemon_port + 10000.

        Returns:
            True if stopped successfully.
        NzUI not running for profile 'r   TzFound UI PID 	 on port zCould not find PID for UI port r?  r   )r.   r?   r@   r   r9  r   r   r   r   r   r   r   r   )r/   r1   r1  rA   r   r   s         r(   stop_uizDaemonEmbedManager.stop_ui  s1    %;;GDD?j>1G!!'733 	LLBBBBCCC4$$W--?LL@@@w@@AAAs####NNFWFFGGG r 	 	A%%gw77 ttJsOOOO%%gw7777r*   c                    |                      |          rXt                              d| d           |r6| j                            |          }|                     ||j        |           dS |                     |||          S )a  
        Ensure daemon is running, starting it if needed.

        Args:
            config: Environment configuration dict (HINDSIGHT_API_* vars)
            profile: Profile name for isolation
            extra_args: Extra CLI arguments to pass to hindsight-api (e.g. ["--offline"])

        Returns:
            True if daemon is running (started or already running), False on failure
        z$Daemon already running for profile 'r   Tr   )rP   r   r   r.   r?   r   r@   r   )r/   r   r1   r   rA   s        r(   ensure_runningz!DaemonEmbedManager.ensure_running  s     ??7## 	LLJJJJKKK D-CCGLL&&w
FCCC4!!&'j!IIIr*   c                 *   |                      |          s t                              d| d           dS | j                            |          }|j        }|                     |          }|6t                              d| d|            |                     |           nt                              d|            t          d          D ].}|                      |          s dS t          j        d	           /|                      |           S )
z
        Stop the daemon for this profile.

        Args:
            profile: Profile name

        Returns:
            True if stopped successfully, False otherwise
        z Daemon not running for profile 'r   TNzFound daemon PID rE  zCould not find PID for port r?  r   )rP   r   r   r.   r?   r@   r   r   r   r   r   r   )r/   r1   rA   r@   r   r   s         r(   stopzDaemonEmbedManager.stop  s    w'' 	LLFGFFFGGG4 %;;GDDz$$T**?LLASAA4AABBBs####NN@$@@AAA r 	 	A??7++ ttJsOOOO??7++++r*   )N)NN)Nr4  )__name__
__module____qualname____doc__r0   r_   r7   r   r<   rB   boolrP   listrl   staticmethodr   ru   r   r   r   dictr   r   r   r0  r6  r9  rC  rF  rH  rJ   r*   r(   r,   r,   J   s       ZZ1 1 17cDj 7S 7 7 7 77 7 7Xc] 7c 7 7 7 7 0s 0s 0 0 0 0 # $    !749 !7 !7 !7 !7F =c =d = = = \=  d
    \> 3 4    \ , , , , , ,\% %D %3 %DIPTDT %`d % % % %@ (,l ll l
 I$l 
l l l l\U UC U U$ U U U UT$s) T T T T"* *# *d
 *SSWZ *cf * * * *	 	S 	3: 	 	 	 	 	y y ycDj y3 y_c y y y yv8 8s 8S4Z 84 8 8 8 8BJ JT JC JT#YQUEU Jae J J J J(,C ,D , , , , , ,r*   r,   ),rN  loggingrc   r    r5   r#   r`   r   pathlibr   typingr   r   r   rich.consoler   	rich.liver   
rich.panelr   	rich.textr	   embed_managerr   profile_managerr   r   r   r   r   	getLoggerrK  r   r   setLevelWARNINGr   rd   r   r   bytesrR  r)   r,   rS  r*   r(   <module>ra     s     				  				                                                       ' ' ' ' ' ' k k k k k k k k k k k k k k		8	$	$
'


  '   # #GO 4 4 4 YRY'OQVWWXX  RY 4    BL, L, L, L, L, L, L, L, L, L,r*   