
    `gjNH                    
   U d Z ddlmZ ddlZddlmZmZ ddlmZm	Z	m
Z
 e G d d             Ze G d d	             Zg Zd
ed<   ddZddZddZddZddZddZddZddZddZddZddZddZddZddZ e        y)u  Unified removal contract for every credential source Hermes reads from.

Hermes seeds its credential pool from many places:

    env:<VAR>     — os.environ / ~/.hermes/.env
    claude_code   — ~/.claude/.credentials.json
    hermes_pkce   — ~/.hermes/.anthropic_oauth.json
    device_code   — auth.json providers.<provider> (nous, openai-codex, ...)
    qwen-cli      — ~/.qwen/oauth_creds.json
    gh_cli        — gh auth token
    config:<name> — custom_providers config entry
    model_config  — model.api_key when model.provider == "custom"
    manual        — user ran `hermes auth add`

Each source has its own reader inside ``agent.credential_pool._seed_from_*``
(which keep their existing shape — we haven't restructured them).  What we
unify here is **removal**:

    ``hermes auth remove <provider> <N>`` must make the pool entry stay gone.

Before this module, every source had an ad-hoc removal branch in
``auth_remove_command``, and several sources had no branch at all — so
``auth remove`` silently reverted on the next ``load_pool()`` call for
qwen-cli, nous device_code (partial), hermes_pkce, copilot gh_cli, and
custom-config sources.

Now every source registers a ``RemovalStep`` that does exactly three things
in the same shape:

    1. Clean up whatever externally-readable state the source reads from
       (.env line, auth.json block, OAuth file, etc.)
    2. Suppress the ``(provider, source_id)`` in auth.json so the
       corresponding ``_seed_from_*`` branch skips the upsert on re-load
    3. Return ``RemovalResult`` describing what was cleaned and any
       diagnostic hints the user should see (shell-exported env vars,
       external credential files we deliberately don't delete, etc.)

Adding a new credential source is:
    - wire up a reader branch in ``_seed_from_*`` (existing pattern)
    - gate that reader behind ``is_source_suppressed(provider, source_id)``
    - register a ``RemovalStep`` here

No more per-source if/elif chain in ``auth_remove_command``.
    )annotationsN)	dataclassfield)CallableListOptionalc                  X    e Zd ZU dZ ee      Zded<    ee      Zded<   dZ	ded<   y	)
RemovalResultu  Outcome of removing a credential source.

    Attributes:
        cleaned: Short strings describing external state that was actually
            mutated (``"Cleared XAI_API_KEY from .env"``,
            ``"Cleared openai-codex OAuth tokens from auth store"``).
            Printed as plain lines to the user.
        hints: Diagnostic lines ABOUT state the user may need to clean up
            themselves or is deliberately left intact (shell-exported env
            var, Claude Code credential file we don't delete, etc.).
            Printed as plain lines to the user.  Always non-destructive.
        suppress: Whether to call ``suppress_credential_source`` after
            cleanup so future ``load_pool`` calls skip this source.
            Default True — almost every source needs this to stay sticky.
            The only legitimate False is ``manual`` entries, which aren't
            seeded from anywhere external.
    )default_factoryz	List[str]cleanedhintsTboolsuppressN)
__name__
__module____qualname____doc__r   listr   __annotations__r   r        K/root/.hermes/venv/lib/python3.12/site-packages/agent/credential_sources.pyr
   r
   5   s1    $ t4GY4T2E92Hdr   r
   c                  T    e Zd ZU dZded<   ded<   ded<   dZded	<   d
Zded<   ddZy)RemovalStepu  How to remove one specific credential source cleanly.

    Attributes:
        provider: Provider pool key (``"xai"``, ``"anthropic"``, ``"nous"``, ...).
            Special value ``"*"`` means "matches any provider" — used for
            sources like ``manual`` that aren't provider-specific.
        source_id: Source identifier as it appears in
            ``PooledCredential.source``.  May be a literal (``"claude_code"``)
            or a prefix pattern matched via ``match_fn``.
        match_fn: Optional predicate overriding literal ``source_id``
            matching.  Gets the removed entry's source string.  Used for
            ``env:*`` (any env-seeded key), ``config:*`` (any custom
            pool), and ``manual:*`` (any manual-source variant).
        remove_fn: ``(provider, removed_entry) -> RemovalResult``.  Does the
            actual cleanup and returns what happened for the user.
        description: One-line human-readable description for docs / tests.
    strprovider	source_idzCallable[..., RemovalResult]	remove_fnNzOptional[Callable[[str], bool]]match_fn descriptionc                    | j                   dk7  r| j                   |k7  ry| j                  | j                  |      S || j                  k(  S )N*F)r   r   r   )selfr   sources      r   matcheszRemovalStep.matchesh   sD    ==CDMMX$===$==((''r   )r   r   r%   r   returnr   )r   r   r   r   r   r   r!   r&   r   r   r   r   r   N   s3    $ MN++04H-4K(r   r   zList[RemovalStep]	_REGISTRYc                0    t         j                  |        | S )N)r(   append)steps    r   registerr,   s   s    TKr   c                F    t         D ]  }|j                  | |      s|c S  y)u  Return the first matching RemovalStep, or None if unregistered.

    Unregistered sources fall through to the default remove path in
    ``auth_remove_command``: the pool entry is already gone (that happens
    before dispatch), no external cleanup, no suppression.  This is the
    correct behaviour for ``manual`` entries — they were only ever stored
    in the pool, nothing external to clean up.
    N)r(   r&   )r   r%   r+   s      r   find_removal_stepr.   x   s+      <<&)K r   c           	     N  
 ddl m}m} t               }|j                  t        d      d 

s|S t        t        j                  
            }d}	  |       }|j                         r2t        
fd|j                  d      j                         D              }|xr | } |
      }	|	r|j                  j                  d	
 d
       |r+|j                   j#                  d
 ddd
 d|  dg       |S |j                   j                  d
 d       |S # t        $ r Y w xY w)uR  env:<VAR> — the most common case.

    Handles three user situations:
      1. Var lives only in ~/.hermes/.env  → clear it
      2. Var lives only in the user's shell (shell profile, systemd
         EnvironmentFile, launchd plist) → hint them where to unset it
      3. Var lives in both → clear from .env, hint about shell
    r   )get_env_pathremove_env_valueenv:NFc              3  b   K   | ]&  }|j                         j                   d        ( yw)=N)strip
startswith).0lineenv_vars     r   	<genexpr>z%_remove_env_source.<locals>.<genexpr>   s0        

''7)16 s   ,/replace)errorsCleared z
 from .envzNote: z@ is still set in your shell environment (not in ~/.hermes/.env).zw  Unset it there (shell profile, systemd EnvironmentFile, launchd plist, etc.) or it will keep being visible to Hermes.u:     The pool entry is now suppressed — Hermes will ignore z  until you run `hermes auth add z`.zSuppressed env:uH    — it will not be re-seeded even if the variable is re-exported later.)hermes_cli.configr0   r1   r
   r%   lenr   osgetenvexistsany	read_text
splitlinesOSErrorr   r*   r   extend)r   removedr0   r1   resultenv_in_processenv_in_dotenvenv_pathshell_exportedclearedr9   s             @r   _remove_env_sourcerO      sP    A_FnnS[\*G "))G,-NM>??  $..i.@KKM  M $9M(9Nw'G	<=WI ' (LHi7zE
 	 M	 	gY '4 5	
 M-  s   A	D 	D$#D$c                    t        g d      S )u   ~/.claude/.credentials.json is owned by Claude Code itself.

    We don't delete it — the user's Claude Code install still needs to
    work.  We just suppress it so Hermes stops reading it.
    )u?   Suppressed claude_code credential — it will not be re-seeded.zGNote: Claude Code credentials still live in ~/.claude/.credentials.jsonz7Run `hermes auth add anthropic` to re-enable if needed.r   r
   r   rH   s     r   _remove_claude_coderT               r   c                &   ddl m} t               } |       dz  }|j                         r.	 |j	                          |j
                  j                  d       |S |S # t        $ r,}|j                  j                  d| d|        Y d}~|S d}~ww xY w)u?   ~/.hermes/.anthropic_oauth.json is ours — delete it outright.r   )get_hermes_homez.anthropic_oauth.jsonz*Cleared Hermes Anthropic OAuth credentialszCould not delete z: N)	hermes_constantsrW   r
   rB   unlinkr   r*   rF   r   )r   rH   rW   rI   
oauth_fileexcs         r   _remove_hermes_pkcer\      s    0_F "%<<J	INN!!"NO M6M  	ILL"3J<r# GHHM	Is   +A 	B$!BBc                    ddl m}m}m}  |       5   |       }|j	                  d      }t        |t              r| |v r|| =  ||       	 ddd       yddd       y# 1 sw Y   yxY w)z@Delete auth_store.providers[provider].  Returns True if deleted.r   )_auth_store_lock_load_auth_store_save_auth_store	providersNTF)hermes_cli.authr^   r_   r`   get
isinstancedict)r   r^   r_   r`   
auth_storeproviders_dicts         r   _clear_auth_store_providerrh      sr      
	 %'
#4nd+N0Jx(Z(    s   9AA&c                n    t               }t        |       r|j                  j                  d|  d       |S )u  Nous OAuth lives in auth.json providers.nous — clear it and suppress.

    We suppress in addition to clearing because nothing else stops a future
    `hermes auth add nous` (or any other path that writes providers.nous)
    from re-seeding before the user has decided to.  Suppression forces
    them to go through `hermes auth add nous` to re-engage, which is the
    documented re-add path and clears the suppression atomically.
    r=    OAuth tokens from auth storer
   rh   r   r*   r   rH   rI   s      r   _remove_nous_device_coderm      s4     _F!(+
2OPQMr   c                n    t               }t        |       r|j                  j                  d|  d       |S )u  MiniMax OAuth lives in auth.json providers.minimax-oauth — clear it.

    Same pattern as Nous: single-source OAuth state with refresh tokens.
    Suppression of the `oauth` source ensures the pool reseed path
    (_seed_from_singletons) doesn't instantly undo the removal.
    r=   rj   rk   rl   s      r   _remove_minimax_oauthro      s4     _F!(+
2OPQMr   c                    t               }t        |       r|j                  j                  d|  d       |j                  j                  d       |S )uC  xAI OAuth tokens live in auth.json providers.xai-oauth — clear them.

    Without this step, ``hermes auth remove xai-oauth <N>`` silently undoes
    itself: the central dispatcher only removes the in-memory pool entry,
    leaves ``providers.xai-oauth`` in auth.json intact, and on the next
    ``load_pool("xai-oauth")`` call ``_seed_from_singletons`` re-seeds the
    entry from the still-present singleton — credentials reappear with no
    user feedback. Clearing the singleton in step with the suppression set
    by the central dispatcher makes the removal stick.
    r=   rj   uZ   Run `hermes model` → xAI Grok OAuth (SuperGrok / Premium+) to re-authenticate if needed.)r
   rh   r   r*   r   rl   s      r   _remove_xai_oauth_device_coderq     sK     _F!(+
2OPQ
LLd Mr   c                    ddl m} t               }t        |       r|j                  j                  d|  d        || d       |j                  j                  g d       |S )u  Codex tokens live in TWO places: our auth store AND ~/.codex/auth.json.

    refresh_codex_oauth_pure() writes both every time, so clearing only
    the Hermes auth store is not enough — _seed_from_singletons() would
    re-import from ~/.codex/auth.json on the next load_pool() call and
    the removal would be instantly undone.  We suppress instead of
    deleting Codex CLI's file, so the Codex CLI itself keeps working.

    The canonical source name in ``_seed_from_singletons`` is
    ``"device_code"`` (no prefix).  Entries may show up in the pool as
    either ``"device_code"`` (seeded) or ``"manual:device_code"`` (added
    via ``hermes auth add openai-codex``), but in both cases the re-seed
    gate lives at the ``"device_code"`` suppression key.  We suppress
    that canonical key here; the central dispatcher also suppresses
    ``removed.source`` which is fine — belt-and-suspenders, idempotent.
    r   suppress_credential_sourcer=   rj   device_code)uH   Suppressed openai-codex device_code source — it will not be re-seeded.z<Note: Codex CLI credentials still live in ~/.codex/auth.jsonz:Run `hermes auth add openai-codex` to re-enable if needed.)rb   rt   r
   rh   r   r*   r   rG   )r   rH   rt   rI   s       r   _remove_codex_device_coderv      s\    " ;_F!(+
2OPQ x7
LL  
 Mr   c                    t        g d      S )u   ~/.qwen/oauth_creds.json is owned by the Qwen CLI.

    Same pattern as claude_code — suppress, don't delete.  The user's
    Qwen CLI install still reads from that file.
    )u<   Suppressed qwen-cli credential — it will not be re-seeded.zANote: Qwen CLI credentials still live in ~/.qwen/oauth_creds.jsonz8Run `hermes auth add qwen-oauth` to re-enable if needed.rQ   rR   rS   s     r   _remove_qwen_clirx   B  rU   r   c                b    ddl m}  || d       dD ]  } || d|         t        g d      S )uG  Copilot token comes from `gh auth token` or COPILOT_GITHUB_TOKEN / GH_TOKEN / GITHUB_TOKEN.

    Copilot is special: the same token can be seeded as multiple source
    entries (gh_cli from ``_seed_from_singletons`` plus env:<VAR> from
    ``_seed_from_env``), so removing one entry without suppressing the
    others lets the duplicates resurrect.  We suppress ALL known copilot
    sources here so removal is stable regardless of which entry the
    user clicked.

    We don't touch the user's gh CLI or shell state — just suppress so
    Hermes stops picking the token up.
    r   rs   gh_cli)COPILOT_GITHUB_TOKENGH_TOKENGITHUB_TOKENr2   )uX   Suppressed all copilot token sources (gh_cli + env vars) — they will not be re-seeded.z3Note: Your gh CLI / shell environment is unchanged.z5Run `hermes auth add copilot` to re-enable if needed.rQ   )rb   rt   r
   )r   rH   rt   r9   s       r   _remove_copilot_ghr~   O  sE    " ;x2G ?"8tG9-=>?     r   c                >    |j                   }t        d| ddg      S )u  Custom provider pools are seeded from custom_providers config or
    model.api_key.  Both are in config.yaml — modifying that from here
    is more invasive than suppression.  We suppress; the user can edit
    config.yaml if they want to remove the key from disk entirely.
    zSuppressed u    — it will not be re-seeded.zyNote: The underlying value in config.yaml is unchanged.  Edit it directly if you want to remove the credential from disk.rQ   )r%   r
   )r   rH   source_labels      r   _remove_custom_configr   l  s2     >>L
l^#AB	C   r   c            	     D   t        t        ddd t        d             t        t        ddd t        d	             t        t        d
dt        d             t        t        d
dt
        d             t        t        ddt        d             t        t        ddd t        d             t        t        ddt        d             t        t        ddt        d             t        t        ddt        d             t        t        ddd t        d              y!)"ui  Called once on module import.

    ORDER MATTERS — ``find_removal_step`` returns the first match.  Put
    provider-specific steps before the generic ``env:*`` step so that e.g.
    copilot's ``env:GH_TOKEN`` goes through the copilot removal (which
    doesn't touch the user's shell), not the generic env-var removal
    (which would try to clear .env).
    copilotrz   c                2    | dk(  xs | j                  d      S )Nrz   r2   r6   srcs    r   <lambda>z'_register_all_sources.<locals>.<lambda>  s    SH_Fv0F r   z/gh auth token / COPILOT_GITHUB_TOKEN / GH_TOKEN)r   r   r   r   r!   r#   r2   c                $    | j                  d      S )Nr2   r   r   s    r   r   z'_register_all_sources.<locals>.<lambda>  s    S^^F3 r   z?Any env-seeded credential (XAI_API_KEY, DEEPSEEK_API_KEY, etc.)	anthropicclaude_codez~/.claude/.credentials.json)r   r   r   r!   hermes_pkcez~/.hermes/.anthropic_oauth.jsonnousru   zauth.json providers.nouszopenai-codexc                2    | dk(  xs | j                  d      S )Nru   z:device_code)endswithr   s    r   r   z'_register_all_sources.<locals>.<lambda>  s    SM1QS\\.5Q r   z5auth.json providers.openai-codex + ~/.codex/auth.jsonz	xai-oauthzauth.json providers.xai-oauthz
qwen-oauthzqwen-cliz~/.qwen/oauth_creds.jsonzminimax-oauthoauthz!auth.json providers.minimax-oauthconfig:c                2    | j                  d      xs | dk(  S )Nr   model_configr   r   s    r   r   z'_register_all_sources.<locals>.<lambda>  s    S^^I6O#:O r   z)Custom provider config.yaml api_key fieldN)r,   r   r~   rO   rT   r\   rm   rv   rq   rx   ro   r   r   r   r   _register_all_sourcesr   z  s&    [hF$E	  [3$U	  [%1 
 [%5 
 [=*. 
 [=Q+K	  [/3 
 [". 
 [ G'7 
 [	O'?	 r   )r+   r   r'   r   )r   r   r%   r   r'   zOptional[RemovalStep])r   r   r'   r
   )r   r   r'   r   )r'   None)r   
__future__r   r@   dataclassesr   r   typingr   r   r   r
   r   r(   r   r,   r.   rO   rT   r\   rh   rm   ro   rq   rv   rx   r~   r   r   r   r   r   <module>r      s   +Z # 	 ( + +   0 ( ( (B  "	 !
.0f
$
(D
:>B  r   