
    `gjN9                       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Zddl	m
Z
 ddlmZmZmZ  ej                  d      Zddd	d
ddddgddddd
dddd
dddd
dddd
dddd
dddd
dddd
dddd
dddd
dddd
ddd d
ddd!d
d"Zd#ed$<   i Zd%ed&<   i Zd'ed(<    ej*                         Zd)Zd6d*Zd7d+Zd8d,Zd9d-Zd:d.Zd;d<d/Zd=d0Z	 d>	 	 	 	 	 	 	 d?d1Zd@d2Z d@d3Z!dAd4Z"g d5Z#y)Bu  Auto-installation of LSP server binaries.

Tries to install missing servers using whatever package manager is
appropriate.  All installs go to a Hermes-owned bin staging dir,
``<HERMES_HOME>/lsp/bin/``, so we don't pollute the user's global
toolchain.

Strategies:

- ``auto`` — attempt to install with the best available package
  manager.  This is the default.
- ``manual`` — never install; if a binary is missing, the server is
  silently skipped and the user is told about it via ``hermes lsp
  status``.
- ``off`` — same as ``manual`` for now (kept distinct so we can
  evolve behavior later, e.g. logging differently).

The actual installs happen synchronously the first time a server is
needed and concurrent calls to :func:`try_install` for the same
package are deduplicated via a per-package lock.

Failure modes are non-fatal: every install path is wrapped in
try/except and returns ``None`` on failure.  The tool layer then
falls back to its in-process syntax checker, exactly as if the user
hadn't enabled LSP at all.
    )annotationsN)Path)AnyDictOptionalzagent.lsp.installnpmpyrightzpyright-langserver)strategypkgbintypescript-language-server
typescript)r
   r   r   
extra_pkgs@vue/language-serverzvue-language-serversvelte-language-serversvelteserver@astrojs/language-serverzastro-lsyaml-language-serverbash-language-serverintelephense!dockerfile-language-server-nodejszdocker-langservergozgolang.org/x/tools/gopls@latestgoplsmanual rust-analyzerclangdlua-language-serverpwsh)r	   r   r   r   r   r   r   r   r   r   r   r   r   
powershellzDict[str, Dict[str, Any]]INSTALL_RECIPESzDict[str, threading.Lock]_install_lockszDict[str, Optional[str]]_install_results)z.cmd.exez.batc                 (    t         j                  dk(  S )Nnt)osname     D/root/.hermes/venv/lib/python3.12/site-packages/agent/lsp/install.py_is_windowsr,   w   s    77d?r*   c                 
   t         j                  j                  d      } | =t         j                  j	                  t         j                  j                  d      d      } t        |       dz  dz  }|j                  dd       |S )z8Return the Hermes-owned bin staging dir for LSP servers.HERMES_HOME~z.hermeslspr   Tparentsexist_ok)r'   environgetpathjoin
expanduserr   mkdir)homeps     r+   hermes_lsp_bin_dirr<   {   sd    ::>>-(D|ww||BGG..s3Y?T
UU"AGGD4G(Hr*   c                   | g}t               r|t        |       j                         h}t        D ]Y  }t	        t        |       |z         }t        |      j                         }||vs8|j                  |       |j                  |       [ |S )zAReturn platform-native executable candidates for a staged binary.)r,   strlower_WINDOWS_WRAPPER_SUFFIXESr   appendadd)base
candidatesexistingsuffix	candidatekeys         r+   _native_binary_candidatesrI      s    J}IOO%&/ 	"FSY/0Ii.&&(C("!!),S!	" r*   c                X   t        t               | z        D ]D  }|j                         st        j                  |t        j
                        s9t        |      c S  t        j                  |       }|r|S t               r*t        D ]!  }t        j                  |  |       }|s|c S  y)z9Probe the staging dir + PATH for a binary named ``name``.N)rI   r<   existsr'   accessX_OKr>   shutilwhichr,   r@   )r(   stagedon_pathrF   s       r+   _existing_binaryrR      s    +,>,@4,GH ==?ryy9v; ll4 G}/ 	FlldVF8#45G	 r*   c                    t         5  t        j                  |       }|t        j                         }|t        | <   |cd d d        S # 1 sw Y   y xY wN)_install_lock_metar"   r5   	threadingLock)r   locks     r+   	_get_lockrY      sH    	 !!#&<>>#D"&N3  s   6AAc                8   |dvr3t         j                  | i       }|j                  d|       }t        |      S | t        v r	t        |    S t	        |       }|5  | t        v rt        |    cddd       S t        |       }|t        | <   |cddd       S # 1 sw Y   yxY w)u  Try to install ``pkg`` and return the binary path if successful.

    ``strategy`` is ``"auto"``, ``"manual"``, or ``"off"``.  In
    ``manual``/``off`` mode, this function only probes for an
    existing binary and returns ``None`` if not found.

    The install is cached per-package — a second call returns the
    same path (or ``None``) without reinstalling.  Concurrent calls
    are serialized.
    >   autor   N)r!   r5   rR   r#   rY   _do_install)r   r
   recipebin_namerX   results         r+   try_installr`      s     y  !$$S"-::eS)))
$$S>D	 ""#C(  S! &  s   B1BBc                   t         j                  |       }|t        j                  |       S |j                  dd      }|j                  d|       }t	        |      }|r|S |dk(  rt
        j                  d| |       y |dk(  r1t        |j                  d|       ||j                  d      xs g       S |d	k(  rt        |j                  d|       |      S |d
k(  rt        |j                  d|       |      S t
        j                  d||        y )Nr
   r   r   z0[install] %s requires manual install (recipe=%s)r   r   r   )r   r   pipz$[install] unknown strategy %r for %s)r!   r5   rN   rO   rR   loggerdebug_install_npm_install_go_install_pipwarning)r   r]   r
   r^   rE   s        r+   r\   r\      s     %F~||C  zz*h/Hzz%%H  )H8GfU5JJuc"zz,/52
 	

 46::eS18<<5FJJuc2H==
NN98SIr*   c           	     H   t        j                  d      }|t        j                  d|        yt	               j
                  }| gt        |xs g       z   }	 t        j                  d|dj                  |             t        j                  |ddt        |      dd	d
g|ddddt        j                        }|j                  dk7  r3t        j                  d| |j                  j                         dd        y	 |dz  dz  |z  }t%        |      D ]p  }	|	j'                         st	               |	j(                  z  }
|
j'                         s	 |
j+                  |	       t        |
j'                         r|
      c S |	      c S  t        j                  d| |       y# t        j                   t"        f$ r!}t        j                  d| |       Y d}~yd}~ww xY w# t"        t,        f$ r9 	 t        j.                  |	|
       n# t"        $ r t        |	      cY cY c S w xY wY w xY w)a  Install an npm package into our staging dir.

    Uses ``npm install --prefix`` so the binaries land in
    ``<staging>/node_modules/.bin/<bin_name>`` and we symlink them up
    one level for direct PATH-style access.

    ``extra_pkgs`` is a list of sibling packages to install in the
    same ``node_modules`` tree.  Used for LSP servers with runtime
    peer deps that npm doesn't auto-pull (typescript-language-server
    needs ``typescript`` next to it; intelephense ships standalone).
    r   Nz,[install] cannot install %s: npm not on PATHz$[install] npm install --prefix %s %s installz--prefixz--silentz	--no-fundz
--no-auditFT,  )checkcapture_outputtexttimeoutstdinr   z'[install] npm install failed for %s: %s  z([install] npm install errored for %s: %snode_modulesz.binz;[install] npm install for %s succeeded but bin %s not found)rN   rO   rc   infor<   parentlistr7   
subprocessrunr>   DEVNULL
returncoderh   stderrstripTimeoutExpiredOSErrorrI   rK   r(   
symlink_toNotImplementedErrorcopy2)r   r^   r   r   staginginstall_targetsprocenm_binclinks              r+   re   re      s     ,,u
C
{BCH "))Ged:#344O2HH_%	

 ~~)ZWz;P\o_no$$
 ??aNN93@Q@Q@STXUX@Y 	   ~%.9F&v. 588:%'!&&0D;;=&OOA& t{{}t44!445 NNPRUW_`+ %%w/ A3J  !45 &&Q-" &"1v& .&sO   B"F GG5GGH!)H ?H! HH!HH! H!c           	        t        j                  d      }|t        j                  d|        yt	               }t        t        j                        }t        |      |d<   	 t        j                  d| |       t        j                  |d| gdddd	|t        j                  
      }|j                  dk7  r3t        j                  d| |j                  j                         dd        y	 ||z  }t%               r|j'                  d      }|j)                         rt        |      S t        j                  d| |       y# t        j                   t"        f$ r!}t        j                  d| |       Y d}~yd}~ww xY w)z'Install a Go module to GOBIN=<staging>.r   Nz+[install] cannot install %s: go not on PATHGOBINz"[install] go install %s (GOBIN=%s)rk   FTiX  )rm   rn   ro   rp   envrq   r   z&[install] go install failed for %s: %srr   z'[install] go install errored for %s: %sr$   z:[install] go install for %s succeeded but bin %s not found)rN   rO   rc   rt   r<   dictr'   r4   r>   rw   rx   ry   rz   rh   r{   r|   r}   r~   r,   with_suffixrK   )r   r^   r   r   r   r   r   bin_paths           r+   rf   rf   -  sM   	d	B	zA3G "G
rzz
Cw<CL8#wG~~C $$
 ??aNN8#t{{?P?P?RSWTW?X 	   !H}''/8}
NNOQTV^_ %%w/ @#qIs   !BD; ;E5E00E5c                   t               j                  dz  }|j                  dd       	 t        j	                  d||        ddlm}  |dt        |      d| gd	
      }|j                  dk7  r7t        j                  d| |j                  xs dj                         dd        y	 |dz  g}t               r|j!                  |dz         |D ]  }t#        ||z        D ]l  }|j%                         st               |j&                  z  }	|	j%                         s	 |	j)                  |       t        |	j%                         r|	n|      c c S   y# t        j                  t        f$ r!}t        j                  d| |       Y d}~yd}~ww xY w# t        t*        f$ r; 	 t-        j.                  ||	       n # t        $ r t        |      cY cY c c S w xY wY w xY w)a;  Install a Python package into a hermes-owned target dir.

    We avoid polluting the user's site-packages by using
    ``pip install --target``.  Bins go into
    ``<staging>/python-packages/bin/`` which we symlink into
    ``<staging>/bin``.  Note: this only works for packages that ship a
    console script.
    zpython-packagesTr1   z$[install] pip install --target %s %sr   )_pip_installz--targetz--quietrl   )rp   z'[install] pip install failed for %s: %sr   Nrr   z([install] pip install errored for %s: %sr   Scripts)r<   ru   r9   rc   rt   hermes_cli.tools_configr   r>   rz   rh   r{   r|   rw   r}   r~   r,   rA   rI   rK   r(   r   r   rN   r   )
r   r^   
pip_targetr   r   r   script_dirs
script_dirr   r   s
             r+   rg   rg   R  s    $%,,/@@JTD1:JL8Z)S9
 ??aNN93ARPR@Y@Y@[\`]`@a 	   %&K}:	12! @
1*x2GH 	@H )+hmm;{{}11 4;;=4h??	@@ + %%w/ A3J $%89 11"LL48& 1#&x=001 91sN   A9E FF
)FF
GF43G4G	GG	GGc                    t         j                  |       }|r|j                  d|       n| }t        |      ry|r|j                  d      dk(  ryy)zReturn ``installed``, ``missing``, or ``manual-only`` for a package.

    Used by the ``hermes lsp status`` CLI to give users a quick
    overview of what's available without spawning anything.
    r   	installedr
   r   zmanual-onlymissing)r!   r5   rR   )r   r]   r^   s      r+   detect_statusr     sL       %F)/vzz%%SH!&**Z(H4r*   )r!   r`   r   r<   )returnbool)r   r   )rC   r   r   z
list[Path])r(   r>   r   Optional[str])r   r>   r   zthreading.Lock)r[   )r   r>   r
   r>   r   r   )r   r>   r   r   rT   )r   r>   r^   r>   r   zOptional[list]r   r   )r   r>   r^   r>   r   r   )r   r>   r   r>   )$__doc__
__future__r   loggingr'   rN   rw   sysrV   pathlibr   typingr   r   r   	getLoggerrc   r!   __annotations__r"   r#   rW   rU   r@   r,   r<   rI   rR   rY   r`   r\   re   rf   rg   r   __all__r)   r*   r+   <module>r      s  4 #  	   
   & &			.	/ ")<PQ ++
 $n	# %$ ' )! %% %%
 "'~nU2"* 'HQXY #+2oN#Bx@ )1DYZ
  (6Bw<.* <~ -/) .-/ * /#Y^^% 4  >H "&>	>> > 	>B"J-`r*   