
    .cj!                        U d Z ddlm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Z
ded<   dd	d
dddddZded<   d"dZd#dZd$dZd%dZ	 	 d&d'd!ZdS )(u  Dependency-light venv recovery that runs BEFORE hermes_cli.main's imports.

The ``hermes`` console entry point is ``hermes_cli.main:main``.  Importing
``hermes_cli.main`` pulls in third-party packages at module level (``dotenv``
via ``hermes_cli.env_loader``, ``yaml`` via ``hermes_cli.config``, ...).  In
the exact failure state the update-recovery markers exist for — a failed lazy
backend refresh or interrupted core install that wiped a core package's
import files (#57828) — a normal launch crashes *while importing main.py*,
before ``_recover_from_interrupted_install()`` can run.  The marker system is
unreachable precisely when it is needed most.

This module is deliberately **stdlib-only** so importing it can never fail on
a corrupted venv.  ``hermes_cli.main`` imports and calls
:func:`recover_if_needed` at the very top of its module body, before any
third-party import.

Scope: this early pass only repairs enough for ``hermes_cli.main`` to become
importable again (force-reinstall of the known-fragile core packages, using
the pins from pyproject.toml).  It NEVER clears the recovery markers — the
full, confirmed marker lifecycle stays with ``_recover_from_interrupted_install()``
in main.py, which runs right after import succeeds.
    )annotationsN)Path))yaml
SafeDumper)dotenvload_dotenv)clickCommand)certificontents)richprint)cryptography__version__)jwtencodeztuple[tuple[str, str], ...]LAZY_REFRESH_IMPORT_PROBESPyYAMLzpython-dotenvr	   r   r   r   PyJWT)r   r   r	   r   r   r   r   zdict[str, str]LAZY_REFRESH_REPAIR_PACKAGESreturnr   c                 b    t          t                                                    j        j        S )N)r   __file__resolveparent     </home/ice/.hermes/hermes-agent/hermes_cli/_early_recovery.py_project_rootr   ;   s     >>!!##*11r   packages	list[str]project_rootc                   |dz  }|                                 s| S 	 ddl}t          |d          5 }|                    |                              di                               dg           pg }ddd           n# 1 swxY w Y   n# t
          $ r | cY S w xY wi |D ]}|                    dd          d                                         }|}d	D ]$}	|	|v r|                    |	d          d         } n%|                                                    d
d          d                                                                         }
|
r||
<   fd| D             S )u  Map bare package names to their pinned specs from pyproject.toml.

    Stdlib-only (tomllib + naive requirement-head parsing — ``packaging`` may
    itself be broken in the failure state this module exists for).  Unknown
    packages fall back to their bare name.
    pyproject.tomlr   Nrbprojectdependencies;   )z==z>=z<=z~=><z!=[c                `    g | ]*}                     |                                |          +S r   )getlower).0pkgname_to_specs     r   
<listcomp>z!_pinned_specs.<locals>.<listcomp>\   s1    CCC3LSYY[[#..CCCr   )	is_filetomllibopenloadr.   	Exceptionsplitstripr/   )r    r"   	pyprojectr5   fraw_depsspecheadbareopkeyr2   s              @r   _pinned_specsrC   ?   s    //I )T"" 	Xa||A**9b99==nbQQWUWH	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	X 	X    $&L 	% 	%zz#q!!!$**,,: 	 	BTzzzz"a((+  jjll  a((+113399;; 	% $LCCCC(CCCCs5   B
 A A>2B
 >BB
 BB
 
BBc                     g } t           D ]\  }}	 t          j        |          }t          ||          st	          | d|           ?# t
          $ r8 t                              |          }|r|| vr|                     |           Y w xY w| S )a#  Import-probe the fragile core packages in THIS process.

    Returns repair package names (deduped, probe order) for modules that fail
    to import or lack their sentinel attribute.  Failed imports leave nothing
    in ``sys.modules``, so a post-repair retry in the same process works.
    z	 missing )	r   	importlibimport_modulehasattrImportErrorr8   r   r.   append)brokenmod_nameattrmodr1   s        r   _probe_broken_packagesrN   _   s     F4 # #$	#)(33C3%% @!X">">">">???@ 	# 	# 	#.228<<C #s&((c"""	# Ms   8A		?B
Bspecsboolc                   	 t          j        t          j        ddddg|d           n# t          $ r Y nw xY w	 t          j        t          j        dddd	g| |dd
          }n6# t          $ r)}t          d| t          j                   Y d}~dS d}~ww xY w|j        dk    r7|j        p|j        pddd         }|rt          |t          j                   dS dS )zensurepip + ``pip install --force-reinstall`` the given specs.

    Streams nothing to stdout (``hermes acp`` speaks JSON-RPC on stdout);
    output is captured and replayed to stderr only on failure.  Never raises.
    z-m	ensurepipz	--upgradez--default-pipT)cwdcapture_outputpipinstallz--force-reinstall)rS   rT   textu+     ✗ Early venv repair could not run pip: fileNFr    i0)	
subprocessrunsys
executabler8   r   stderr
returncodestdout)rO   r"   resultexctails        r   _run_repair_installre   s   s8   ^T;_M	
 	
 	
 	
 	

    	^T5)5HQ5Q	
 
 
    ACAA
SSSSuuuuu A44"eff= 	)$SZ((((u4s#   &) 
66)A$ $
B.BBPath | Noneargvlist[str] | NoneNonec                   	 |t           j        dd         n|}d|v rdS | t                      n| }|dz  }|dz  }|                                s|                                sdS |dz                                  sdS t                      }|sdS |dz  }	 t          j        |t          j        t          j	        z  t          j
        z            }t          j        |t          j                     d                                           t          j        |           ns# t          $ r[ 	 t!          j                    |                                j        z
  d	k    r|                                 n# t(          $ r Y nw xY wY dS t(          $ r Y nw xY w	 t+          ||          }	t-          d
d                    |           t           j                   t3          |	|          r*t                      st-          dt           j                   nZt-          dt           j                   t-          dt           j         dd                    |	          z   t           j                   	 |                                 dS # t(          $ r Y dS w xY w# 	 |                                 w # t(          $ r Y w w xY wxY w# t6          $ r Y dS w xY w)u1  Repair wiped core packages so ``hermes_cli.main`` can import at all.

    Fast path (no marker present) is two ``lstat`` calls.  Only acts when a
    recovery marker from a prior ``hermes update`` exists AND an import probe
    confirms a core package is actually broken.  Markers are intentionally
    NOT cleared here — ``_recover_from_interrupted_install()`` in main.py owns
    the confirmed marker lifecycle and runs immediately after import succeeds.

    Never raises: on any failure the import of main.py proceeds and surfaces
    the real error.
    Nr)   updatez.update-incompletez.lazy-refresh-incompleter$   z.update-incomplete.lock
i  uQ   ⚠ Core package(s) broken by an interrupted update — repairing before launch: z, rX   u     ✓ Core packages repaired.u9     ✗ Automatic repair incomplete. Recover manually with:z    z" -m pip install --force-reinstall  )r]   rg   r   existsr4   rN   osr6   O_CREATO_EXCLO_WRONLYwritegetpidr   closeFileExistsErrortimestatst_mtimeunlinkOSErrorrC   r   joinr_   re   r^   r8   )
r"   rg   argsrootcore_markerlazy_markerrJ   	lock_pathfdrO   s
             r   recover_if_neededr      sK   @#|sx|| tF"."6}L1177!!## 	K,>,>,@,@ 	F ''0022 	F')) 	F 44		BJ$:R[$HIIBHRBIKK+++2244555HRLLLL 	 	 	9;;!1!1!::TAA$$&&&   FF 	 	 	D		!&$//E@,0IIf,=,=@ @Z   
 #5$// 8N8P8P 5CJGGGGGO    M3>MMMhhuoo&     """""     """"      s   J; AJ; &J; ?J; J; BD  J;  
F+AE10F1
E>;F=E>>FJ; 	FJ; FJ; CJ *J   
J
J; JJ; J8J('J8(
J52J84J55J88J; ;
K	K	)r   r   )r    r!   r"   r   r   r!   )r   r!   )rO   r!   r"   r   r   rP   )NN)r"   rf   rg   rh   r   ri   )__doc__
__future__r   rE   ro   r[   r]   rw   pathlibr   r   __annotations__r   r   rC   rN   re   r   r   r   r   <module>r      sI    . # " " " " "     				     



       ;      "0 0     2 2 2 2D D D D@   (   B !%!O O O O O O Or   