
    `gj)                    p    d Z ddlmZ ddlmZmZmZ g dZddZddZ	ddZ
dd		 	 	 	 	 dd
ZddZddZy)u  Unified provider-credential lifecycle across every store Hermes reads.

A provider API key can live in up to THREE stores at once:

    1. ``~/.hermes/.env``                     — the canonical secret store
    2. ``~/.hermes/auth.json`` →
       ``credential_pool.<provider>[*]``      — env-seeded pool entries
       (``source == "env:<VAR>"``) persisted by the pool loader
    3. ``~/.hermes/config.yaml``              — inline mirrors written by the
       custom-endpoint flows (``model.api_key``, ``auxiliary.<task>.api_key``,
       ``custom_providers[*].api_key``)

Historically the desktop/dashboard endpoints (PUT/DELETE ``/api/env``) and the
TUI-gateway RPCs only mutated store 1. That divergence is the root cause of a
whole bug family:

    * #51071 / #59761 — deleting a key removes it from ``.env`` but the stale
      ``credential_pool`` entry (and ``provider_models_cache.json`` row)
      survives, so the provider keeps appearing in the model picker, even
      across restarts (the pool loader is additive-only).
    * #62269 — updating a key rewrites ``.env`` but leaves the OLD key in a
      higher-precedence ``config.yaml`` mirror (``model.api_key`` wins over
      env at client construction), producing persistent 401s with a key the
      UI no longer shows.

This module is the single choke point: every surface that saves or removes a
provider credential should route through :func:`save_provider_env_credential`
/ :func:`remove_provider_env_credential` so all three stores stay consistent.

OAuth preservation contract: removal only prunes credential-pool entries whose
``source`` is exactly ``env:<VAR>``. OAuth/device-code/manual/borrowed entries
(``device_code``, ``manual*``, ``gh_cli``, ``claude_code``, ``oauth``, …) and
the ``providers.<id>`` OAuth token blocks in auth.json are never touched —
deleting an API key must not revoke an OAuth grant for the same provider.

Secrecy contract: no function in this module logs, prints, or returns a
credential value. Results carry key NAMES and config PATHS only.
    )annotations)AnyDictList)save_provider_env_credentialremove_provider_env_credentialpurge_env_credential_referencesc                    	 ddl m} g }|j                         D ])  \  }}	 | |j                  xs dv r|j                  |       + |S # t        $ r g cY S w xY w# t        $ r Y Jw xY w)zCProvider ids whose registered api_key_env_vars include ``env_var``.r   )PROVIDER_REGISTRY )hermes_cli.authr   	Exceptionitemsapi_key_env_varsappend)env_varr   hitspidcfgs        R/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/credential_lifecycle.py_providers_for_env_varr   3   s    5 D%++- S	3//526C  K  	  		s"   A #AAA	A%$A%c           	     $   ddl m}m}m} d|  }g } |       5   |       }|j	                  d      }t        |t              s|cddd       S d}t        |j                               D ]  }	||	   }
t        |
t              s|
D cg c](  }t        |t              r|j	                  d      |k(  s|* }}t        |      t        |
      k(  rdd}|j                  |	       |r|||	<   ||	=  |r ||       ddd       |S c c}w # 1 sw Y   |S xY w)	u  Drop ``credential_pool`` entries seeded from ``env:<env_var>``.

    Operates across ALL providers in the pool (the source string names the
    env var unambiguously, and shared vars like GITHUB_TOKEN may seed more
    than one provider). Entries with any other source — OAuth, device-code,
    manual, borrowed-CLI — are preserved verbatim, as are the
    ``providers.<id>`` OAuth blocks.

    Returns the list of provider ids that had entries pruned.
    r   )_auth_store_lock_load_auth_store_save_auth_storeenv:credential_poolNFsourceT)r   r   r   r   get
isinstancedictlistkeyslenr   )r   r   r   r   r   pruned
auth_storepoolchangedproviderentriesentrykepts                r   _prune_env_pool_entriesr-   C   s)    UTG9FF		 )%'
~~/0$%	) )
 TYY[) 	#H8nGgt, %"5$/EIIh4G64Q D 
 4yCL(GMM(#!%XN!	#" Z(1)2 M)2 Ms$   *D8D-D 2AD DDc                     sg S ddl m}m} ddlm}m}  |       }|j                         sg S 	 t        |d      5 } ||      xs i }ddd       t        t              sg S g d fd}	 |	|j                  d      d       |j                  d	      }
t        |
t              r$|
j                         D ]  \  }} |	|d
|         |j                  d      }t        |t              r t        |      D ]  \  }} |	|d|         n4t        |t              r$|j                         D ]  \  }} |	|d|         r ||        |||d       S # 1 sw Y   xY w# t        $ r g cY S w xY w)u;  Reconcile config.yaml api_key mirrors that hold ``old_value``.

    Value-matched on purpose: we only touch a config entry when it provably
    holds the SAME credential that just changed in ``.env`` — an independent
    key the user configured for a different endpoint is left alone.

    ``new_value=None`` removes the mirror field; a string replaces it.
    Operates on the RAW user config (never the defaults-merged view) so the
    write doesn't bake defaults into the user's file. Returns the dotted
    paths that were updated (names only — never values).
    r   )atomic_yaml_writefast_safe_load)get_config_path$require_readable_config_before_writezutf-8)encodingNc                    t        | t              sy dD ]Z  }| j                  |      }t        |t              s%|k(  s+r| |<   n| j	                  |d        j                  | d|        \ y )N)api_keyapi.)r    r!   r   strpopr   )sectionkey_pathfieldcurrent	new_value	old_valuetoucheds       r   _fixz(_scrub_config_yaml_mirrors.<locals>._fix   so    '4(' 	6Ekk%(G'3'Gy,@%.GENKKt,(1UG45	6    model	auxiliaryz
auxiliary.custom_providerszcustom_providers.F)	sort_keys)r:   r   r;   r8   returnNone)utilsr/   r0   hermes_cli.configr1   r2   existsopenr   r    r!   r   r   r"   	enumerate)r?   r>   r/   r0   r1   r2   config_pathfuser_configrA   auxtaskslot_cfgcustomidxr+   namer@   s   ``               @r   _scrub_config_yaml_mirrorsrW   n   s    	7
 "#K	+0 	2A(+1rK	2 k4(	G6 		!7+
//+
&C#t!iik 	0ND(Zv./	0 __/0F&$#F+ 	3JC+C512	3	FD	!!<<> 	4KD%+D623	4 ,[9+{eDNQ	2 	2 	s(   E& EE& E#E& &E43E4T)clear_models_cachec                  t        |       }t        t        |      t        t        |             z        }	 ddlm} |D ]  } ||d|          	 |r|r	 ddlm} |D ]
  } ||        	 ||dS # t        $ r Y +w xY w# t        $ r Y w xY w)a  Remove non-.env references to an env-var credential.

    Prunes ``credential_pool`` env-seeded entries and (optionally) the
    affected providers' rows in ``provider_models_cache.json`` so the model
    picker stops advertising a provider whose key is gone (#59761).
    r   )suppress_credential_sourcer   )clear_provider_models_cache)pool_pruned	providers)	r-   sortedsetr   r   rZ   r   hermes_cli.modelsr[   )r   rX   r%   r]   rZ   r)   r[   s          r   r	   r	      s     %W-Fs6{S)?)H%IIJI
>! 	CH&x4y1AB	C i	E% 6+H56 "	::    	 	s#   A0 A? 0	A<;A<?	B
Bc                    ddl m}m}  |       j                  |       } || |       g }|r|r||k7  rt	        ||      }	 ddlm} t        |       D ]  } ||d|          	 d| |dS # t        $ r Y w xY w)a  Save/update a credential in ``.env`` and reconcile every mirror.

    After the ``.env`` write, any config.yaml mirror that held the PREVIOUS
    value of this var (``model.api_key`` etc.) is updated to the new value so
    a stale higher-precedence copy cannot shadow the rotation (#62269).
    Suppressed ``env:<VAR>`` pool sources are re-enabled so a deliberate
    re-add through the UI behaves like ``hermes auth add``.
    r   )load_envsave_env_value)unsuppress_credential_sourcer   T)okkeyconfig_updates)	rJ   rb   rc   r   rW   r   rd   r   r   )r   valuerb   rc   r?   rg   rd   r)   s           r   r   r      s     ;
w'I7E" "NyE13IuE
@.w7 	EH(T'3CD	E
 w.II  s    "A) )	A54A5c           
         ddl m}m}  |       j                  |       } ||       }t	        |       }|rt        |d      ng }d| ||d   |d   |t        |xs	 |d   xs |      dS )u4  Remove a credential from EVERY store it lives in.

    Clears the ``.env`` entry (and process env), prunes env-seeded
    ``credential_pool`` entries, drops the affected providers' model-cache
    rows, and removes any config.yaml mirror holding the same value.
    OAuth/device-code/manual credentials are preserved (see module docstring).

    ``found`` is True when ANY store held the credential — callers that
    previously 404'd on ".env miss" should key off this instead so a stale
    pool-only entry can still be cleaned up through the same button.
    r   )rb   remove_env_valueNTr\   r]   )re   rf   removedr\   r]   config_scrubbedfound)rJ   rb   rj   r   r	   rW   bool)r   rb   rj   r?   removed_from_envrefsrl   s          r   r   r      s~     =
w'I'0*73DEN0DATVO #M*+&*&P$}*=PQ rB   N)r   r8   rG   	List[str])r?   r8   r>   z
str | NonerG   rq   )r   r8   rX   rn   rG   Dict[str, Any])r   r8   rh   r8   rG   rr   )r   r8   rG   rr   )__doc__
__future__r   typingr   r   r   __all__r   r-   rW   r	   r   r   r   rB   r   <module>rw      s^   %N # " " (VAJ 15 ; ;)- ; ;FJ@rB   