Ë
    âß²jcC ã                  óL  — 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	Z	ddl
mZmZ ddlmZmZ ddlmZ ddlmZmZmZmZmZmZ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"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z(m)Z)m*Z*m+Z+m,Z,m-Z- erddl.m/Z/  ej`                  d«      Z1dZ2ddœdvd„Z3dwd„Z4ddœdxd„Z5dyd„Z6dddddddœZ7de8d<    e9dh«      Z:de8d<   dzd„Z;d{d„Z<d|d„Z= e9ddh«      Z>de8d <   d}d!„Z?d~d"„Z@dd#„ZAd€d$„ZBd€d%„ZCd�d&„ZDdde+d'œ	 	 	 	 	 	 	 	 	 d‚d(„ZEdde+d'œ	 	 	 	 	 	 	 	 	 dƒd)„ZFd„d*„ZGdd+œd…d,„ZHd†d-„ZId‡dˆd.„ZJdd/œ	 	 	 	 	 d‰d0„ZKddd1œ	 	 	 	 	 	 	 dŠd2„ZL e9d3d4h«      ZMd‹d5„ZNd6ZO ej                   «       ZQdŒd7„ZRdŒd8„ZSeefd�d9„«       ZTdŽd:„ZUd;ZVd�d<„ZWd�d=„ZXd‘d>„ZYd’d?„ZZd“d@„Z[d”dA„Z\d•dB„Z]	 	 	 	 	 	 	 	 d–dC„Z^dDdEœd—dF„Z_	 	 	 	 	 	 	 	 	 	 d˜dG„Z`	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d™dH„ZaddIœ	 	 	 	 	 	 	 	 	 dšdJ„Zbd›dK„ZcdœdL„Zdd�dM„ZedDdNdOœ	 	 	 	 	 	 	 	 	 dždP„ZfdŸdQ„ZgdRe$dddddDdddddSœ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d dT„ZhdDdddUœ	 	 	 	 	 	 	 	 	 d¡dV„ZiddWœd¢dX„Zjd£dY„Zkd¤dZ„Zl G d[„ d\«      ZmdDddddd]œ	 	 	 	 	 	 	 	 	 	 	 d¥d^„ZndDddddd]œ	 	 	 	 	 	 	 	 	 	 	 d¥d_„Zod¦d`„Zpd¦da„Zq	 	 	 	 	 	 	 	 d§db„Zrd¦dc„Zsd¨dd„Ztd¦de„ZudfZvdgZwdhZxd©di„Zyddjœ	 	 	 	 	 	 	 	 	 	 	 dªdk„ZzdddddNdDddddlœ		 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d«dm„Z{d¬dn„Z|dNdDddoœ	 	 	 	 	 	 	 d­dp„Z}	 	 	 	 	 	 d®dq„Z~d—dr„Zd¯ds„Z€d°dt„Z�d±du„Z‚y)²a	  Nous Portal OAuth: device-code login, refresh, shared-store mirroring, JWT selection, status.

Split out of ``hermes_cli/auth.py``; origin helpers are imported lazily inside each function
so ``hermes_cli.auth.<name>`` patches still intercept (and no import cycle).
é    )ÚannotationsN)ÚcontextmanagerÚsuppress)ÚdatetimeÚtimezone)ÚPath)ÚTYPE_CHECKINGÚAnyÚCallableÚDictÚ	FrozenSetÚListÚOptional)Úurlparse)Ú_pool_entries)Ú_decode_jwt_claimsÚAUTH_LOCK_TIMEOUT_SECONDSÚ	AuthErrorÚDEFAULT_NOUS_CLIENT_IDÚDEFAULT_NOUS_INFERENCE_URLÚDEFAULT_NOUS_PORTAL_URLÚDEFAULT_NOUS_SCOPEÚDEFAULT_NOUS_WELCOME_URLÚ%DEVICE_AUTH_POLL_INTERVAL_CAP_SECONDSÚNOUS_AUTH_PATH_INVOKE_JWTÚNOUS_BILLING_MANAGE_SCOPEÚNOUS_DEVICE_CODE_SOURCEÚNOUS_INFERENCE_INVOKE_SCOPEÚNOUS_INVOKE_JWT_MIN_TTL_SECONDSÚ	_nous_errÚhttpx)ÚProviderConfigzhermes_cli.authz*Re-authenticate with: hermes auth add nousF©Úno_refresh_tokenc               óX   — |rdnd}t        d| › d|› dt        › �|rdd¬«      S | d¬«      S )	ae  Shared ``relogin=True`` error for an access token that is not a usable inference JWT.

    With no refresh token the failure is a state-shape one (nothing to redeem), so it carries the
    terminal ``nous_auth_missing_refresh_token`` code the pool recognises instead of the JWT
    ``reason``, which would bench the row as a transient outage (#113718).
    z" and no refresh token is availableÚ z8Nous Portal access token is not a usable inference JWT (ú)z. Únous_auth_missing_refresh_tokenT©Úrelogin)r    Ú_UNUSABLE_JWT_RELOGIN)Úreasonr$   Údetails      ú9/home/ubuntu/.hermes/hermes-agent/hermes_cli/auth_nous.pyÚ_unusable_invoke_jwt_errorr/   &   sX   € ñ 6FÑ1È2€FÜØ
BÀ6À(È!ÈFÈ8ÐSUÜ Ð
!ð	#á-=Ð)ÐSWôYð Yð DJÐSWôYð Yó    c                ó¸   — t        | t        «      r| j                  «       nd}|r5t        j                  |j                  d«      «      j                  «       dd S dS )zJReturn a short hash fingerprint for telemetry without leaking token bytes.r&   zutf-8Né   )Ú
isinstanceÚstrÚstripÚhashlibÚsha256ÚencodeÚ	hexdigest)ÚtokenÚcleaneds     r.   Ú_token_fingerprintr<   4   sH   € ä)¨%´Ô5ˆe�k‰kŒm¸2€GÙGNŒ7�>‰>˜'Ÿ.™.¨Ó1Ó2×<Ñ<Ó>¸sÀÐCÐXÐTXÐXr0   )Úsequence_idc               óþ   — t        j                  dd«      j                  «       j                  «       dvry d| i}|r||d<   |j	                  |«       t
        j                  dt        j                  |dd¬	«      «       y )
NÚHERMES_OAUTH_TRACEr&   >   Ú1ÚonÚyesÚtrueÚeventr=   zoauth_trace %sTF©Ú	sort_keysÚensure_ascii)	ÚosÚgetenvr5   ÚlowerÚupdateÚloggerÚinfoÚjsonÚdumps)rD   r=   ÚfieldsÚpayloads       r.   Ú_oauth_tracerR   :   sl   € Ü	‡y�yÐ% rÓ*×0Ñ0Ó2×8Ñ8Ó:ÐB\Ñ\ØØ&¨Ð.€GÙØ!,ˆ�ÑØ‡N�N�6ÔÜ
‡K�KÐ ¤$§*¡*¨WÀÐSXÔ"YÕZr0   c                óŠ   — t        j                  | j                  «       |z   t        j                  ¬«      j                  «       S )z.ISO timestamp *ttl_seconds* after *now* (UTC).©Útz)r   ÚfromtimestampÚ	timestampr   ÚutcÚ	isoformat)ÚnowÚttl_secondss     r.   Ú
_iso_afterr\   D   s-   € ä×!Ñ! #§-¡-£/°KÑ"?ÄHÇLÁLÔQ×[Ñ[Ó]Ð]r0   ©Ú	agent_keyÚagent_key_idÚagent_key_expires_atÚagent_key_expires_inÚagent_key_reusedÚagent_key_obtained_atúDict[str, Any]Ú_NOUS_EMPTY_AGENT_KEY_FIELDSzapi.nousresearch.comzFrozenSet[str]Ú_NOUS_STALE_PORTAL_HOSTSc                ó@   — ddl m}m}  |d¬«      } ||| ¬«      xs dS )zUPortal entitlement notice for *capability* (fresh account data), "" when unavailable.r   )Ú&format_nous_portal_entitlement_messageÚget_nous_portal_account_infoT©Úforce_fresh)Ú
capabilityr&   )Úhermes_cli.nous_accountrh   ri   )rl   rh   ri   Úaccount_infos       r.   Ú_portal_entitlement_messagero   Q   s%   € ÷Ná/¸DÔA€LÙ1°,È:ÔVÒ\ÐZ\Ð\r0   c                ó†   — t        t        «      5  t        d«      x}r|cd d d «       S 	 d d d «       | › d�S # 1 sw Y   ŒxY w)NzNous model accessz5 Check credits or billing in Nous Portal, then retry.)r   Ú	Exceptionro   )ÚerrorÚmessages     r.   Ú#_format_nous_entitlement_auth_errorrt   Y   sP   € Ü	”)Ó	ñ Ü1Ð2EÓFÐFˆ7ÐFØ÷ñ ØF÷ð ˆWÐIÐJÐJ÷ð ús	   �7·A c                ó  — | j                  d«      }t        |t        «      sy |j                  d«      xs dj                  «       }|rAt	        |«      j
                  t        v r%t        j                  d|t        «       t        |d<   y y y )NÚnousÚportal_base_urlr&   z3auth: migrating stale nous portal_base_url %s -> %s)
Úgetr3   Údictr5   r   Úhostnamerf   rL   Úwarningr   )Ú	providersrv   Ústoreds      r.   Ú_migrate_stale_nous_portal_urlr~   `   sx   € Ø�=‰=˜Ó €DÜ�dœDÔ!ØØ�h‰hÐ(Ó)Ò/¨R×6Ñ6Ó8€FÙ”(˜6Ó"×+Ñ+Ô/GÑGÜ�‰ØAÀ6ÔKbô	dä"9ˆÐÒð H€vr0   zinference-api.nousresearch.comzwelcome-api.nousresearch.comÚ_ALLOWED_NOUS_INFERENCE_HOSTSc                ój   — | t         v ry| syt        «       }|duxr t        |«      j                  | k(  S )u‡  Production hosts always; otherwise only the host the operator named in
    ``NOUS_INFERENCE_BASE_URL``.

    A non-production Portal's refresh response names that environment's inference gateway. The
    Portal-returned value is network provenance, so it does not get bearer-receive authority on
    its own â€” not even for a Nous-owned host: the operator's explicit override is the authority,
    and the network value is accepted exactly when it agrees with it. Then the persisted endpoint,
    the pricing scope and the proxy all follow the environment the operator chose, and the
    per-turn "refusing inference URL host" warning stops.
    TFN)r   Ú_nous_inference_env_overrider   rz   )rz   Úoverrides     r.   Ú_nous_inference_host_allowedrƒ   t   s>   € ð Ô0Ñ0ØÙØÜ+Ó-€HØ˜4ÐÒK¤H¨XÓ$6×$?Ñ$?À8Ñ$KÐKr0   c                óp  — t        | t        «      r| j                  «       nd}|sy	 t        |«      }|j
                  dk7  r!t        j                  d|j
                  «       yt        |j                  «      s!t        j                  d|j                  «       y|j                  d«      S # t        $ r Y yw xY w)u  Validate a Portal-returned inference URL against the host allowlist.

    Defense-in-depth: a compromised refresh response (MITM, response injection) could otherwise
    redirect every proxy request â€” bearing the user's inference JWT â€” to an attacker endpoint.
    r&   NÚhttpszEnous: refusing non-https inference URL scheme %r from Portal responsezenous: refusing inference URL host %r from Portal response (not in allowlist); falling back to defaultú/)r3   r4   r5   r   rq   ÚschemerL   r{   rƒ   rz   Úrstrip)Úurlr;   Úparseds      r.   Ú)_validate_nous_inference_url_from_networkr‹   ‡   s¥   € ô (¨¬SÔ1ˆc�i‰iŒk°r€GÙØðÜ˜'Ó"ˆð ‡}�}˜ÒÜ�‰ØSÐU[×UbÑUbô	dàÜ'¨¯©Ô8Ü�‰ð:à�O‰Oô	ð Ø�>‰>˜#ÓÐøô ò Ùðús   §B) Â)	B5Â4B5c                 ó    — ddl m}m} 	 | D ]  } ||«      }|sŒ|c S  y# |$ r( t        j	                  ddj                  | «      «       Y yw xY w)u„  The first set operator routing override among ``names`` (``NOUS_INFERENCE_BASE_URL``,
    ``HERMES_PORTAL_BASE_URL`` / its ``NOUS_PORTAL_BASE_URL`` alias), resolved through the profile
    secret scope, or None.

    ``get_secret`` already reads ``os.environ`` for a single-profile process, so the only time it
    raises is a multi-profile call that has lost its profile scope. That call has no authority to
    route on the launch profile's value â€” returning the ambient env there would send a secondary's
    tokens to the launch profile's Portal or inference host â€” so the override is simply absent.
    Absent is not free: default routing then applies, so a non-production deployment's token is
    spent against the production hosts. One WARNING per lost-scope event names the override; the
    downstream "ignoring invalid portal_base_url" line never says which caller lost its scope.
    r   )ÚUnscopedSecretErrorÚ
get_secretNu«   nous: %s unreadable â€” no profile secret scope on a multiplexed call; treating the override as absent (default routing applies). The caller needs a profile scope binding.r†   )Úagent.secret_scoper�   rŽ   rL   r{   Újoin)Únamesr�   rŽ   ÚnameÚvalues        r.   Ú_scoped_operator_overrider”   ¡   sd   € ÷ CðØò 	ˆDÙ˜tÓ$ˆEÚØ’ð	ð øØò Ü�‰ðfà�H‰H�U‹Oô	ñ ðús   Š  š  ž   *AÁAc                 ó0   — ddl m}   | t        d«      «      S )aŸ  User-set ``NOUS_INFERENCE_BASE_URL`` override (trailing slash stripped) or None.

    Documented dev/staging escape hatch; the env source is trusted, so unlike Portal-returned URLs
    it is intentionally NOT gated by the network host allowlist. Read through the profile-aware
    resolver so a multiplexed profile uses its own override and never inherits the default
    profile's process-wide value (#65941).
    r   ©Ú_optional_base_urlÚNOUS_INFERENCE_BASE_URL©Úhermes_cli.authr—   r”   r–   s    r.   r�   r�   ½   s   € õ 3ÙÔ7Ð8QÓRÓSÐSr0   c                 ó2   — ddl m}   | t        dd«      «      S )aU  ``HERMES_PORTAL_BASE_URL`` / ``NOUS_PORTAL_BASE_URL`` override or None.

    Documented dev/staging escape hatch (e.g. hosted agents on the staging Portal). Trusted env
    source: must NOT be gated by ``_NOUS_PORTAL_ALLOWED_HOSTS``, which rejects untrusted
    NETWORK-provided values persisted to auth.json, not operator config. Read through the
    profile secret scope like ``_nous_inference_env_override``: it is reached on every routed
    turn (``_nous_effective_routing``), and a raw environ read would POST a multiplexed
    secondary's refresh token to the DEFAULT profile's Portal.
    r   r–   ÚHERMES_PORTAL_BASE_URLÚNOUS_PORTAL_BASE_URLr™   r–   s    r.   Ú_nous_portal_env_overriderž   É   s   € õ 3ÙÔ7Ð8PÐRhÓiÓjÐjr0   c                ó  — t        «       }t        | t        «      r8|j                  d„ | j	                  dd«      j                  «       D «       «       |S t        | t        t        t         t        f«      r |j                  d„ | D «       Ž  |S )Nc              3  óB   K  — | ]  }|j                  «       sŒ|–— Œ y ­w©N)r5   )Ú.0Úparts     r.   ú	<genexpr>z _scope_values.<locals>.<genexpr>Ü   s   è ø€ Ò[˜tÈdÏjÉjÍl”dÑ[ùs   ‚˜ú,ú c              3  óT   K  — | ]   }t        |t        «      sŒt        |«      –— Œ" y ­wr¡   )r3   r4   Ú_scope_values)r¢   Úitems     r.   r¤   z _scope_values.<locals>.<genexpr>Þ   s    è ø€ Ò[°ÄZÐPTÔVYÕEZœ d×+Ñ[ùs   ‚(˜()	Úsetr3   r4   rK   ÚreplaceÚsplitÚlistÚtupleÚ	frozenset)Ú	raw_scopeÚscopess     r.   r¨   r¨   ×   sr   € ô “u€FÜ�)œSÔ!Ø�‰Ñ[ y×'8Ñ'8¸¸cÓ'B×'HÑ'HÓ'JÔ[Ô[ð €Mô 
�I¤¤e¬S´)Ð<Ô	=Øˆ�‰Ñ[¸	Ô[Ñ\Ø€Mr0   ©ÚscopeÚ
expires_atÚmin_ttl_secondsc               óž  — ddl m} t        | «      }|syt        |«      t        |j	                  d«      «      z  t        |j	                  d«      «      z  }t
        |vry|j	                  d«      }t        dt        |«      «      }t        |t        t        f«      r't        |«      t        j                  «       |z   k  rdS d	S  |||«      rd
S d	S )zDReturn None when the token can be used for inference, else a reason.r   )Ú_is_expiringÚaccess_token_not_jwtr³   ÚscpÚmissing_inference_invoke_scopeÚexpÚinvoke_jwt_expiringNÚ%invoke_jwt_expiry_unknown_or_expiring)rš   r·   r   r¨   rx   r   ÚmaxÚintr3   ÚfloatÚtime)	r:   r³   r´   rµ   r·   Úclaimsr±   r»   Úskews	            r.   Ú_nous_invoke_jwt_statusrÄ   â   s»   € õ -Ü Ó&€FÙØ%Ü˜EÓ"¤]°6·:±:¸gÓ3FÓ%GÑGÜ˜fŸj™j¨Ó/Ó0ñ1€Fä"¨&Ñ0Ø/Ø
�*‰*�UÓ
€CÜˆq”#�oÓ&Ó'€DÜ�#œœU�|Ô$Ü(-¨c«
´t·y±y³{ÀTÑ7IÒ(JÐ$ÐTÐPTÐTÙ6BÀ:ÈtÔ6TÐ2Ð^ÐZ^Ð^r0   c               ó*   — ddl m}  || |||¬«      d u S )Nr   ©rÄ   r²   )rš   rÄ   )r:   r³   r´   rµ   rÄ   s        r.   Ú_nous_invoke_jwt_is_usablerÇ   õ   s(   € õ 8Ù"Ø�U zÀ?ôTØW[ð\ð \r0   c                ó`   — ddl m}  ||| j                  d«      | j                  d«      ¬«      S )zW``_nous_invoke_jwt_status`` for *token* using *state*'s scope / expires_at (patchable).r   rÆ   r³   r´   )r³   r´   )rš   rÄ   rx   )Ústater:   rÄ   s      r.   Ú_state_invoke_jwt_statusrÊ   ý   s-   € å7Ù"Ø�U—Y‘Y˜wÓ'°E·I±I¸lÓ4KôMð Mr0   ©Úaccess_tokenc               ó`   — |€| j                  d«      n|}t        | |«      }|�t        |«      ‚y )NrÌ   )rx   rÊ   r/   )rÉ   rÌ   r:   r,   s       r.   Ú!_assert_nous_inference_jwt_usablerÎ     s9   € Ø)5Ð)=ˆE�I‰I�nÔ%À<€EÜ% e¨UÓ3€FØÐÜ(¨Ó0Ð0ð r0   c                óŠ   — ddl m}m}  || «      }|�*t        dt	        |t        j
                  «       z
  «      «      S  ||«      S )zNSeconds until *expires_at* (ISO), else *fallback_expires_in* coerced to a TTL.r   )Ú_coerce_ttl_secondsÚ_parse_iso_timestamp)rš   rÐ   rÑ   r¾   r¿   rÁ   )r´   Úfallback_expires_inrÐ   rÑ   Úexpires_epochs        r.   Ú_remaining_ttlrÔ     s>   € çIÙ(¨Ó4€MØÐ Ü�1”c˜-¬$¯)©)«+Ñ5Ó6Ó7Ð7ÙÐ2Ó3Ð3r0   c                óP  — t        | «      }|j                  d«      }t        |t        t        f«      rUt        t        «      5  t        j                  t	        |«      t        j                  ¬«      j                  «       cd d d «       S t        |t        «      r|S d S # 1 sw Y   ŒxY w)Nr»   rT   )r   rx   r3   r¿   rÀ   r   rq   r   rV   r   rX   rY   r4   )r:   Úfallback_expires_atrÂ   r»   s       r.   Ú_nous_jwt_expires_atr×     s†   € Ü Ó&€FØ
�*‰*�UÓ
€CÜ�#œœU�|Ô$Ü”iÓ ñ 	SÜ×)Ñ)¬%°«*¼¿¹ÔF×PÑPÓR÷	Sñ 	Sä",Ð-@Ä#Ô"FÐÐPÈDÐP÷	Sð 	Sús   Á<BÂB%)Úobtained_atc               óÂ  — ddl m} | j                  d«      } ||«      sy | j                  d«      }|sS| j                  d«      |k(  xr  ||«      }|r|n0t        j                  t
        j                  «      j                  «       }t        || j                  d«      «      }t        || j                  d«      «      }|r
|| d<   || d<   | j                  |d ||d|¬	«       y )
Nr   ©Ú_nonempty_strrÌ   rc   r^   r´   Ú
expires_inFr]   )rš   rÛ   rx   r   rZ   r   rX   rY   r×   rÔ   rK   )rÉ   rØ   rÛ   rÌ   Úexisting_obtained_atÚreuser´   rÜ   s           r.   Ú#_set_nous_agent_key_from_invoke_jwtrß     sÑ   € å-Ø—9‘9˜^Ó,€LÙ˜Ô&ØØ Ÿ9™9Ð%<Ó=ÐÙØ—	‘	˜+Ó&¨,Ñ6Ò^¹=ÐI]Ó;^ˆÙ.3Ñ*¼¿¹ÄhÇlÁlÓ9S×9]Ñ9]Ó9_ˆÜ% l°E·I±I¸lÓ4KÓL€JÜ 
¨E¯I©I°lÓ,CÓD€JÙØ(ˆˆlÑØ(ˆˆlÑØ	‡L�LØ¨TÈ
Ø'¸%ÐWbð õ dr0   ©rÌ   r=   c          	     ó¶   — ddl m}  ||«      r|| d<   t        | «       t        j	                  d«       t        d|t        | j                  d«      «      ¬«       y )Nr   rÚ   rÌ   z)Nous inference auth: using NAS invoke JWTÚnous_invoke_jwt_selected©r=   Úaccess_token_fp)rš   rÛ   rß   rL   ÚdebugrR   r<   rx   )rÉ   rÌ   r=   rÛ   s       r.   Ú_select_nous_invoke_jwtræ   1  sK   € å-Ù�\Ô"Ø ,ˆˆnÑÜ'¨Ô.Ü
‡L�LÐ<Ô=ÜØ"°Ü*¨5¯9©9°^Ó+DÓEöGr0   rÜ   ra   c                óh   — | j                  «       D ��ci c]  \  }}|t        vsŒ||“Œ c}}S c c}}w r¡   )ÚitemsÚ"_NOUS_EFFECTIVE_STATE_IGNORED_KEYS)rÉ   ÚkÚvs      r.   Ú_nous_effective_provider_staterì   B  s,   € Ø"Ÿ[™[›]×Z‘T�Q˜¨aÔ7YÒ.YˆAˆq‰DÓZÐZùÓZs   ”.¥.znous_auth.jsonc                 ó    — t        j                  dd«      j                  «       } | rt        | «      j	                  «       S ddlm}  |«       dz  S )au  Directory of the shared Nous token store: ``HERMES_SHARED_AUTH_DIR`` or ``<root>/shared/``.

    Outside any named profile so all profiles share it (``hermes --profile X auth add nous --type
    oauth`` one-tap imports it). Written on login AND every runtime refresh so the refresh_token
    stays current across profiles; a stale token just falls back to device-code.
    ÚHERMES_SHARED_AUTH_DIRr&   r   ©Úget_default_hermes_rootÚshared)rH   rI   r5   r   Ú
expanduserÚhermes_constantsrð   )r‚   rð   s     r.   Ú_nous_shared_auth_dirrô   J  sE   € ô �y‰yÐ1°2Ó6×<Ñ<Ó>€HÙÜ�H‹~×(Ñ(Ó*Ð*Ý8Ù"Ó$ xÑ/Ð/r0   c                 ó$  — t        «       t        z  } t        j                  j	                  d«      rNddlm}  |«       dz  t        z  j                  d¬«      }	 | j                  d¬«      }||k(  rt        d| › d�«      ‚| S # t        $ r | }Y Œ#w xY w)	NÚPYTEST_CURRENT_TESTr   rï   rñ   F)ÚstrictzDRefusing to touch real user shared Nous auth store during test run: z@. Set HERMES_SHARED_AUTH_DIR to a tmp_path in your test fixture.)
rô   ÚNOUS_SHARED_STORE_FILENAMErH   Úenvironrx   ró   rð   Úresolverq   ÚRuntimeError)Úpathrð   Úreal_home_sharedÚresolveds       r.   Ú_nous_shared_store_pathrÿ   X  s¥   € Ü Ó"Ô%?Ñ?€Dô 
‡z�z‡~�~Ð+Ô,Ý<á#Ó%¨Ñ0Ô3MÑMÏwÉwÐ^cÈwÓOdð 	ð	Ø—|‘|¨5�|Ó1ˆHð Ð'Ò'ÜØVØ�&ÐXðZó[ð [ð €Køô ò 	ØŠHð	ús   ÁB ÂBÂBc              #  óÂ   K  — ddl m} 	 t        «       j                  d«      } ||t
        | d«      5  d–— ddd«       y# t        $ r d–— Y yw xY w# 1 sw Y   yxY w­w)zâCross-profile lock for the shared Nous OAuth store.

    Lock ordering invariant: if both this and ``_auth_store_lock`` need to be held, acquire
    ``_auth_store_lock`` FIRST. All runtime refresh paths follow this order.
    r   )Ú
_file_lockz.lockNz+Timed out waiting for shared Nous auth lock)rš   r  rÿ   Úwith_suffixrû   Ú_nous_shared_lock_holder)Útimeout_secondsr  Ú	lock_paths      r.   Ú_nous_shared_store_lockr  l  sm   è ø€ õ +ðÜ+Ó-×9Ñ9¸'ÓBˆ	ñ 
ØÔ+¨_Ø5ó
7ñ ó 	÷ð øô ò ÛÙðú÷ð üs=   ‚AŠA  £A²A·	AÁ AÁAÁAÁAÁAÁAc                ó(   — t        | dz   t        «      S )Ng      @)r¾   r   ©r  s    r.   Ú_shared_lock_timeoutr	    s   € Üˆ Ñ$Ô&?Ó@Ð@r0   )rÌ   Úrefresh_tokenÚ
token_typer³   Ú	client_idrw   Úinference_base_urlrØ   r´   Úauth_methodÚaccount_tierÚ
anon_tokenÚuser_idÚorg_idc                ó¤  — ddl m}m}m}  |«       xs i }|j	                  d«      } ||«      sy ||j	                  d«      «      xs d} || j	                  d«      «      xs d}|j                  «       t        | j	                  d«      xs d«      j                  «       k7  }|s||kD  syt        D ]  }	|j	                  |	«      }
|
dvsŒ|
| |	<   Œ y	)
zACopy fresher shared OAuth tokens into a profile-local Nous state.r   )rÛ   rÑ   Ú_read_shared_nous_stater
  Fr´   ç        r&   >   Nr&   T)rš   rÛ   rÑ   r  rx   r5   r4   Ú_NOUS_SHARED_STATE_KEYS)rÉ   rÛ   rÑ   r  rñ   Úshared_refreshÚshared_access_expÚlocal_access_expÚrefresh_changedÚkeyr“   s              r.   Ú_merge_shared_nous_oauth_stater  ‹  sÑ   € ç\Ñ\Ù$Ó&Ò,¨"€FØ—Z‘Z Ó0€NÙ˜Ô(ØÙ,¨V¯Z©Z¸Ó-EÓFÒMÈ#ÐÙ+¨E¯I©I°lÓ,CÓDÒKÈÐØ$×*Ñ*Ó,´°E·I±I¸oÓ4NÒ4TÐRTÓ0U×0[Ñ0[Ó0]Ñ]€OÙÐ#4Ð7GÒ#GØÜ&ò ˆØ—
‘
˜3“ˆØ˜
Ò"ØˆE�#ŠJðð r0   c                óü  — | j                  d«      | j                  d«      | j                  d«      xs d| j                  d«      xs t        | j                  d«      xs t        | j                  d«      xs t        | j                  d«      xs  | j                  d	«      d
k(  rt        nt
        | j                  d«      | j                  d«      dœ	dD �ci c]  }| j                  |«      dvr|| |   “Œ c}¥S c c}w )zLThe defaulted OAuth core (tokens + routing + expiry) shared across profiles.rÌ   r
  r  ÚBearerr³   r  rw   r  r  Ú	anonymousrØ   r´   )	rÌ   r
  r  r³   r  rw   r  rØ   r´   )r  r  r  r  r  )Nr&   )rx   r   r   r   r   r   )Úsrcrê   s     r.   Ú_nous_shared_shaper!  ž  sè   € ð Ÿ™ Ó/À#Ç'Á'È/ÓBZØ—g‘g˜lÓ+Ò7¨xØ—‘˜Ó!Ò7Ô%7Ø—W‘W˜[Ó)ÒCÔ-CØŸ7™7Ð#4Ó5ÒPÔ9Pà!Ÿg™gÐ&:Ó;ò oØ(+¯©°Ó(>À+Ò(MÕ$ÔSmØ—w‘w˜}Ó-¸S¿W¹WÀ\Ó=Rñ-ð aö ,˜Ø�g‰g�a‹j 
Ñ*ð ˆc�!‰f‰9ò ,ð-ð -ùò,s   Ã C9c                ó.  — ddl m}m} | j                  d«      } || j                  d«      «      }|s  ||«      r || j                  d«      «      syddit	        | «      ¥d	t        j                  t        j                  «      j                  «       i¥}	 t        «       5  t        «       } |||d
¬«       ddd«       t        dt        «      t        |«      ¬«       y# 1 sw Y   Œ*xY w# t        $ r }t         j#                  d|«       Y d}~yd}~ww xY w)z®Persist a minimal copy of the Nous OAuth state to the shared store.

    Best-effort: failures are logged and swallowed; per-profile auth.json stays the source of truth.
    r   )rÛ   Ú_save_private_jsonr
  r  rÌ   NÚ_schemaé   Ú
updated_atT)rF   Únous_shared_store_written)rü   Úrefresh_token_fpz*Failed to write shared Nous auth store: %s)rš   rÛ   r#  rx   r!  r   rZ   r   rX   rY   r  rÿ   rR   r4   r<   rq   rL   rå   )rÉ   rÛ   r#  r
  Úis_guestrñ   rü   Úexcs           r.   Ú_write_shared_nous_stater+  ®  sø   € ÷
 BØ—I‘I˜oÓ.€Mñ ˜UŸY™Y |Ó4Ó5€HÙ™]¨=Ô9¹mÈEÏIÉIÐVdÓLeÔ>fØà�1ð>Ü*¨5Ó1ð>à”h—l‘l¤8§<¡<Ó0×:Ñ:Ó<ñ>€FðHÜ$Ó&ñ 	=Ü*Ó,ˆDÙ˜t V°tÕ<÷	=ô 	Ø'¬c°$«iÜ/°Ó>ö	@÷	=ð 	=ûô ò HÜ�‰ÐAÀ3×GÑGûðHús0   Â
C+ Â CÂ6(C+ ÃC(Ã$C+ Ã+	DÃ4DÄDc                 óÖ  — ddl m}  	 t        «       }|j	                  «       sy	 t        j                  |j                  d¬«      «      }t        |t        «      sy | |j                  d«      «      xs0  | |j                  d«      «      xr  | |j                  d	«      «      }|r|S dS # t        $ r Y yw xY w# t        t        f$ r!}t        j                  d||«       Y d}~yd}~ww xY w)
z¨Shared Nous OAuth state when present and well-formed, else None.

    None (missing / unreadable / malformed / lacking tokens) means "fall through to device-code".
    r   rÚ   Nz	utf-8-sig)Úencodingz.Shared Nous auth store at %s is unreadable: %sr  rÌ   r
  )rš   rÛ   rÿ   rû   Úis_filerN   ÚloadsÚ	read_textÚOSErrorÚ
ValueErrorrL   rå   r3   ry   rx   )rÛ   rü   rQ   r*  Ú
has_tokenss        r.   r  r  È  sÛ   € õ
 .ðÜ&Ó(ˆð �<‰<Œ>ØðÜ—*‘*˜TŸ^™^°[˜^ÓAÓBˆô �gœtÔ$ØÙ˜wŸ{™{¨<Ó8Ó9ò dÙ�g—k‘k .Ó1Ó2Òb±}ÀWÇ[Á[ÐQ`ÓEaÓ7bð á ˆ7Ð* dÐ*øô ò Ùðûô ”ZÐ ò Ü�‰ÐEÀtÈSÔQÜûðús(   ˆ
B) ¤%B8 Â)	B5Â4B5Â8C(ÃC#Ã#C(c                óê   — 	 t        «       5  t        «       j                  d¬«       ddd«       t        d| ¬«       y# 1 sw Y   ŒxY w# t        $ r }t
        j                  d|«       Y d}~yd}~ww xY w)zBRemove the shared Nous OAuth store after a terminal token failure.T)Ú
missing_okNÚnous_shared_store_cleared©r,   z*Failed to clear shared Nous auth store: %s)r  rÿ   ÚunlinkrR   rq   rL   rå   )r,   r*  s     r.   Ú_clear_shared_nous_stater9  à  sg   € ðHÜ$Ó&ñ 	>Ü#Ó%×,Ñ,¸Ð,Ô=÷	>äÐ0¸Ö@÷	>ð 	>ûô ò HÜ�‰ÐAÀ3×GÑGûðHús+   ‚
A	 Œ=§A	 ½AÁA	 Á		A2ÁA-Á-A2c           	     ó   — ddl m} ||j                  | j                  d«      | j                  d«      t	        | j                  d«      «      dœ}	  |«       }t        |«      |d<   	 t        j                  |«      }|j                  |j                  |j                  d¬	«       d}| j                  d«      }	t        |	t
        «      rj|	rh	 t        j                   |	«      }
|
j#                  |
j$                  xs t&        j(                  ¬«      t        j*                  t&        j(                  «      k  }||d<   |S # t        $ r d
|d<   Y Œ¤w xY w# t        $ r}t        |«      |d<   Y d}~ŒÄd}~ww xY w# t,        $ r d}Y ŒLw xY w)ad  Redaction-safe forensic record for a quarantine: fingerprints, sizes and booleans only.

    NEVER include a raw token/agent_key (credential-shaped literals get corrupted in logs). The
    12-char SHA-256 prefix correlates to NAS's refreshTokenHash without leaking the secret;
    provenance is client_id + agent_key_id (Nous state has no session_id).
    r   ©Ú_auth_file_pathr  r_   r
  )r,   Ú
error_coder  r_   r(  Úauth_json_pathT)Úauth_json_sizeÚauth_json_mtimeÚauth_json_existsFrA  Úauth_json_stat_errorNr´   )ÚtzinfoÚtoken_already_expired)rš   r<  Úcoderx   r<   r4   rH   ÚstatrK   Úst_sizeÚst_mtimeÚFileNotFoundErrorrq   Úreprr3   r   Úfromisoformatr«   rC  r   rX   rZ   r2  )rÉ   rr   r,   r<  ÚforensicÚ	auth_pathÚstr*  Úalready_expiredÚexpires_at_rawrŠ   s              r.   Ú_quarantine_forensicsrQ  ê  sZ  € õ 0à¨¯
©
ÀÇÁÈ;ÓAWØŸ	™	 .Ó1Ü.¨u¯y©y¸Ó/IÓJñ L€Hð

5Ù#Ó%ˆ	Ü%(¨£^ˆÐ!Ñ"ð	1Ü—‘˜Ó#ˆBØ�O‰OØ!Ÿz™z¸2¿;¹;ÐY]ð ô _ð '+€OØ—Y‘Y˜|Ó,€NÜ�.¤#Ô&©>ð	#Ü×+Ñ+¨NÓ;ˆFØ%Ÿ~™~°V·]±]Ò5RÄhÇlÁl˜~ÓSÜ!)§¡¬h¯l©lÓ!;ñ <ˆOð )8€HÐ$Ñ%Ø€Oøô !ò 	1Ø+0ˆHÐ'Ó(ð	1ûäò 5Ü+/°«9ˆÐ'Ö(ûð5ûô ò 	#Ø"ŠOð	#úsI   ÁE Á&=D7 Ã	A'E/ Ä7EÅE ÅEÅE Å	E,ÅE'Å'E,Å/E=Å<E=c          
     ó
  — ddl m}m}m} t        j                  dt        j                  t        | ||«      dd¬«      «       g |¢t        ¢­D ]  }| j                  |d«       Œ  |d||¬	«      | d
<   t        |«        |«        y)u8  Keep routing metadata but remove dead OAuth material so it is not replayed.

    Only for terminal errors (``*_refresh_failed`` = HTTP 400/401/403 invalid_grant / revoked /
    refresh_token_reused; ``*_auth_missing_refresh_token``), all ``relogin_required=True`` â€”
    transient 429/5xx never quarantine.
    r   )Ú_FLAT_OAUTH_TOKEN_KEYSÚ_last_auth_error_markerÚ!invalidate_nous_auth_status_cachez6Nous OAuth state quarantined (terminal auth death): %sTFrE   Nrv   r7  Úlast_auth_error)rš   rS  rT  rU  rL   r{   rN   rO   rQ  re   Úpopr9  )rÉ   rr   r,   rS  rT  rU  r  s          r.   Ú_quarantine_nous_oauth_staterX    s‰   € ÷\ñ \ô
 ‡N�NØ@Ü�
‰
Ô(¨°°vÓ>È$Ð]bÔcôeð HÐ'ÐGÔ*FÑGò ˆØ�	‰	�#�tÕðá6°v¸uÈVÔT€EÐ
ÑÜ˜VÔ$Ù%Õ'r0   c               ó$  — t        | d«      }|€yt        dt        › �h}|D �cg c](  }t        |t        «      r|j	                  d«      |v rŒ'|‘Œ* }}t        |«      t        |«      k7  }|r || d   d<   t        d||j                  ¬«       |S c c}w )zHRemove singleton-seeded Nous pool entries that contain dead OAuth state.rv   Fzmanual:ÚsourceÚcredential_poolÚ!nous_pool_device_code_quarantined)r,   r=  )r   r   r3   ry   rx   ÚlenrR   rE  )Ú
auth_storerr   r,   ÚentriesÚsingleton_sourcesÚeÚretainedÚremoveds           r.   Ú_quarantine_nous_pool_entriesrd  &  s¦   € ô ˜J¨Ó/€GØ€ØÜ0°GÔ<SÐ;TÐ2UÐVÐàöbØ¤:¨a´Ô#6¸1¿5¹5À»?ÐN_Ò;_Šðb€Hð bä�(‹mœs 7›|Ñ+€GÙØ08ˆ
Ð$Ñ% fÑ-ÜÐ8ÀÐTY×T^ÑT^Õ_Ø€Nùòbs   £(BÁBç      .@r  c                óþ  ‡	— ddl m}mŠ	m}m} 	 t        t        | «      ¬«      5   |«       }|s
	 ddd«       yi t        |«      ¥dddddœdœ¥} ||| dˆ	fd	„¬
«      } ‰	|«       ddd«       |S # 1 sw Y   S xY w# t        $ rw}t        |t        «      }t        	 ddt        |«      j                  i|rdt        |dd«      ini ¤Ž |r ||«      rt        d«       t         j#                  d|«       Y d}~yd}~ww xY w)zõRehydrate Nous OAuth state from the shared store via a forced refresh.

    Returns auth_state ready for ``persist_nous_credentials()``; None on any failure (expired
    token, portal unreachable) so the caller falls through to device-code.
    r   )r  r+  Úrefresh_nous_oauth_from_stateÚ_is_terminal_nous_refresh_errorr  NF©ÚinsecureÚ	ca_bundle)r^   r`   ÚtlsTc                ó   •—  ‰| «      S r¡   © )ÚupdatedÚ_reasonr+  s     €r.   ú<lambda>z/_try_import_shared_nous_state.<locals>.<lambda>J  s   ø€ Ñ9QÐRYÓ9Z€ r0   ©r  Úforce_refreshÚon_state_updateÚ
error_typer=  rE  Ú&shared_import_terminal_refresh_failurezShared Nous import failed: %s)Únous_shared_import_failed)rš   r  r+  rg  rh  r  r	  r!  rq   r3   r   rR   ÚtypeÚ__name__Úgetattrr9  rL   rå   )
r  r  rg  rh  rñ   rÉ   Ú	refreshedr*  Úis_authr+  s
            @r.   Ú_try_import_shared_nous_stater}  6  s  ø€ ÷)ó )ðÜ$Ô5IÈ/Ó5ZÔ[ñ 	0Ù,Ó.ˆFÙØ÷	0ð 	0ð
%?Ü$ VÓ,ð%?Ø;?ÐY]Ø$)¸Ñ=ò%?ˆEñ 6Ø ÀdÛ Zô\ˆIñ % YÔ/÷	0ð* Ð÷+	0ð* Ðûô ò Ü˜S¤)Ó,ˆÜØ'ñ	OÜ48¸³I×4FÑ4Fð	Oá=D�œg c¨6°4Ó8Ñ9È"ò	Oñ Ñ6°sÔ;Ü$Ð%MÔNÜ�‰Ð4°cÔ:Üûðús?   �A< ¤A/¯A< ¸-A/Á%A< Á/A9Á4A< Á9A< Á<	C<ÂA-C7Ã7C<c                óÌ  — | j                  |› d�d|id|dœ¬«      }|j                  dk(  r$|j                  «       }d|vrt        dd	d
¬«      ‚|S 	 |j                  «       }t        |j                  dd«      «      }t        |j                  d«      xs d«      }	|dv }
|dk(  sd|	j                  «       v rd}	d
}
t        |	||
¬«      ‚# t        $ r}t        dd
¬«      |‚d }~ww xY w)Nz/api/oauth/tokenzx-nous-refresh-tokenr
  )Ú
grant_typer  )ÚheadersÚdataéÈ   rÌ   z%Refresh response missing access_tokenÚinvalid_tokenTr)   zRefresh token exchange failedrr   Úinvalid_grantÚerror_description>   r„  rƒ  Úrefresh_token_reusedr†  rÞ   uá  Nous Portal detected refresh-token reuse and revoked this session.
This usually means an external process (monitoring script, custom self-heal hook, or another Hermes install sharing ~/.hermes/auth.json) called POST /api/oauth/token with Hermes's refresh token without persisting the rotated token back.
Nous refresh tokens are single-use â€” only Hermes may call the refresh endpoint. For health checks, use `hermes auth status` instead.
Re-authenticate with: hermes auth add nous)ÚpostÚstatus_coderN   r    rq   r4   rx   rJ   )Úclientrw   r  r
  ÚresponserQ   Úerror_payloadr*  rE  Údescriptionr*   s              r.   Ú_refresh_access_tokenr�  X  s  € ð �{‰{ØÐ
Ð+Ð,Ø'¨Ð7Ø+¸)ÑDð ó F€Hð ×Ñ˜sÒ"Ø—-‘-“/ˆØ Ñ(ÜÐCÀ_Ð^bÔcÐcØˆðPØ Ÿ™›ˆô ˆ}× Ñ  ¨/Ó:Ó;€DÜ�m×'Ñ'Ð(;Ó<Ò_Ð@_Ó`€KØÐPÐP€Gð Ð%Ò%¨°K×4EÑ4EÓ4GÑ)Gð9ð 	ð ˆÜ
�K ¨wÔ
7Ð7øô) ò PÜÐ7ÀÔFÈCÐOûðPús   ÁC Ã	C#ÃCÃC#c                ó¦   — ddl m}m}	 	  || |||¬«      S # t        $ r1}
 |	|
«      r#t	        ||
|¬«       t        ||
|¬«        |«        ‚ d}
~
ww xY w)zYRedeem the refresh token; on terminal failure quarantine state + pool, persist, re-raise.r   )r�  rh  ©r‰  rw   r  r
  r7  N)rš   r�  rh  r   rX  rd  )r‰  r^  rÉ   rw   r  r
  r,   Úpersistr�  rh  r*  s              r.   Ú_refresh_nous_or_quarantiner‘  }  s^   € ÷
 Wð	Ù$Ø¨?ÀiØ'ô)ð 	)øô ò Ù*¨3Ô/Ü(¨°¸FÕCÜ)¨*°cÀ&ÕIÙŒIØûðús   Š –	AŸ,AÁA©r  c               óÄ  — ddl m} t        j                  t        j
                  «      } ||j                  d«      «      }|d   | d<   |j                  d«      xs || d<   |j                  d«      xs | j                  d«      xs d| d<   |j                  d«      xs | j                  d«      | d<   |�|| d
<   |j                  «       | d<   || d<   t        ||«      | d<   y	)a  Write a successful Nous token-refresh payload into *state* (tokens + expiry fields).

    *inference_base_url*, when given, is the healed network-provenance URL to persist alongside
    the rotated tokens (key order in auth.json is preserved from the original login shape).
    r   )rÐ   rÜ   rÌ   r
  r  r  r³   Nr  rØ   r´   )	rš   rÐ   r   rZ   r   rX   rx   rY   r\   )rÉ   r{  r
  r  rÐ   rZ   Ú
access_ttls          r.   Ú_apply_nous_refreshed_tokensr•  �  sÖ   € õ 4Ü
�,‰,”x—|‘|Ó
$€CÙ$ Y§]¡]°<Ó%@ÓA€JØ% nÑ5€Eˆ.ÑØ&Ÿ]™]¨?Ó;ÒL¸}€Eˆ/ÑØ#Ÿ-™-¨Ó5Ò\¸¿¹À<Ó9PÒ\ÐT\€Eˆ,ÑØ—]‘] 7Ó+ÒA¨u¯y©y¸Ó/A€Eˆ'�NØÐ%Ø&8ˆÐ"Ñ#ØŸ=™=›?€Eˆ-ÑØ$€Eˆ,ÑÜ$ S¨*Ó5€Eˆ,Òr0   c                óJ   — t        | j                  d«      «      }|xs t        S )u{  Validated network-provenance inference URL from a refresh payload, healed to the default.

    A Portal URL rejected by the allowlist resets to the production default instead of leaving a
    previously-persisted bad host (e.g. a stale staging URL) in place â€” otherwise a poisoned
    auth.json re-validates to None on every refresh and silently re-uses the dead endpoint.
    r  )r‹   rx   r   )r{  r‰   s     r.   Ú_healed_nous_inference_urlr—  ¥  s$   € ô 4°I·M±MÐBVÓ4WÓ
X€CØÒ,Ô,Ð,r0   c                ó\   — t        j                  t        j                  | «      ddi|¬«      S )NÚAcceptzapplication/json)Útimeoutr€  Úverify)r!   ÚClientÚTimeout©r  r›  s     r.   Ú_nous_http_clientrŸ  °  s*   € Ü�<‰<Ü—‘˜oÓ.¸ÐCUÐ8VØôð r0   c                óZ   — | j                  «       }d|v rdnd|v rd|vrdnd|v rdnd}|| fS )zUSort key: opus > pro > haiku/flash > sonnet (sonnet is cheap/fast; best model first).Úopusr   ÚproÚsonnetr%  é   é   )rJ   )ÚmidÚlowÚranks      r.   Ú_model_priorityr©  ¶  s?   € à
�)‰)‹+€CØ˜3‘‰A¨°#©¸(È#Ñ:M¡AØ #‘o‘¨1ð 	à�#ˆ;Ðr0   Trž  c                óp  — ddl m} t        ||«      5 }|j                  | j	                  d«      › d�dd|› �i¬«      }ddd«       j
                  d	k7  r]d
|j
                  › �}	 |j                  «       }t        |j                  d«      xs |j                  d«      xs |«      }t        |d«      ‚|j                  «       j                  d«      }
t        |
t        «      sg S g }|
D ]`  }t        |t        «      r|j                  d«      nd} ||«      sŒ/d|j                  «       vsŒB|j!                  |j#                  «       «       Œb |j%                  t&        ¬«       t        t        j)                  |«      «      S # 1 sw Y   �ŒBxY w# t        $ r!}	t        j                  d|	«       Y d}	~	�Œ
d}	~	ww xY w)z6Fetch available model IDs from the Nous inference API.r   rÚ   r†   z/modelsÚAuthorizationzBearer )r€  Nr‚  z#/models request failed with status r…  rr   z'Could not parse error response JSON: %sÚmodels_fetch_failedr�  ÚidÚhermes©r  )rš   rÛ   rŸ  rx   rˆ   rˆ  rN   r4   rq   rL   rå   r    r3   r­   ry   rJ   Úappendr5   Úsortr©  Úfromkeys)r  Úapi_keyr  r›  rÛ   r‰  rŠ  rŒ  Úerrra  r�  Ú	model_idsr©   Úmodel_ids                 r.   Úfetch_nous_modelsr·  ¾  s‘  € õ .Ü	˜?¨FÓ	3ð <°vØ—:‘:Ø!×(Ñ(¨Ó-Ð.¨gÐ6Ø$¨°¨yÐ&9Ð:ð ó <ˆ÷<ð ×Ñ˜sÒ"Ø;¸H×<PÑ<PÐ;QÐRˆð	GØ—-‘-“/ˆCÜ˜cŸg™gÐ&9Ó:Ò]¸c¿g¹gÀgÓ>NÒ]ÐR]Ó^ˆKô ˜Ð%:Ó;Ð;Ø�=‰=‹?×Ñ˜vÓ&€DÜ�dœDÔ!Øˆ	Ø€IØò /ˆÜ%/°´dÔ%;�4—8‘8˜D”>Àˆá˜Õ" x°x·~±~Ó7GÒ'GØ×Ñ˜XŸ^™^Ó-Õ.ð	/ð
 ‡N�N”€NÔ'Ü”—‘˜iÓ(Ó)Ð)÷-<ñ <ûô ò 	GÜ�L‰LÐBÀA×FÒFûð	Gús$   “+E>Á%AF Å>FÆ	F5ÆF0Æ0F5c                óÄ   — ddl m} | j                  d«      } ||«      xr@ t        || j                  d«      | j                  d«      t	        dt        |«      «      ¬«      S )Nr   rÚ   r^   r³   r`   r²   )rš   rÛ   rx   rÇ   r¾   r¿   )rÉ   rµ   rÛ   r  s       r.   Ú_agent_key_is_usabler¹  Ü  sV   € Ý-Ø
�)‰)�KÓ
 €CÙ˜Óò 6Ô"<Ø�5—9‘9˜WÓ%°%·)±)Ð<RÓ2SÜ˜Aœs ?Ó3Ó4ô#6ð 6r0   r  )r  r³   rØ   r´   r^   r`   r  rj  rk  rs  rt  c               ó@   — t        | |||||||||	|
||dœdœ|||¬«      S )zõRefresh Nous OAuth state without mutating auth.json directly.

    ``on_state_update`` fires after a successful access-token refresh so callers owning persistent
    state can save the rotated refresh token before later validation can fail.
    ri  ©rÌ   r
  r  rw   r  r  r³   rØ   r´   r^   r`   rl  rr  )rg  )rÌ   r
  r  rw   r  r  r³   rØ   r´   r^   r`   r  rj  rk  rs  rt  s                   r.   Úrefresh_nous_oauth_purer¼  ä  sF   € ô )à(¸=ÐW`Ø.ÐFXØ$¨uÀ[Ø$°9Ø$8Ø (°yÑAñ	Cð (°}Ø'ô	)ð 	)r0   rr  c               óP  — ddl m}m}m}m} | j                  d«      xs i }|j                  d«      |j                  d«      }
}	| j                  dd«      | j                  dd«      | j                  d	«      xs t        | j                  d
«      xs t        j                  d«      | j                  d«      xs t        j                  d«      | j                  d«      xs d| j                  d«      xs t        | j                  d«      | j                  d«      | j                  d«      | j                  d«      t        |	«      |
dœdœ} ||	|
|¬«      }t        |xs d|«      5 }t        ||j                  d«      «      }|s|�~|j                  d«      }t        |t        «      r|s|�t!        |d¬«      ‚t#        ddd¬«      ‚ |||d
   |d	   |¬«      }t%        |||t'        |«      ¬«       |� |t)        |«      d «        ||«        ||«       ddd«       |S # 1 sw Y   |S xY w)!zURefresh Nous OAuth from a state dict (defaults filled in) without mutating auth.json.r   )rÎ   r�  Ú_resolve_verifyræ   rl  rj  rk  rÌ   r&   r
  r  rw   r†   r  r  r  r³   rØ   r´   r^   r`   ri  r»  ©rj  rk  Ú
auth_statere  NTr#   z.No refresh token is available for Nous Portal.r(   r)   r�  r’  Úpost_refresh_access_token)rš   rÎ   r�  r¾  ræ   rx   r   r   rˆ   r   r   ÚboolrŸ  rÊ   r3   r4   r/   r    r•  r—  ry   )r   r  rs  rt  rÎ   r�  r¾  ræ   rl  rj  rk  rÉ   r›  r‰  Úcurrent_invoke_jwt_statusÚrefresh_token_valuer{  s                    r.   rg  rg  ý  s  € ÷!ó !ð �'‰'�%‹.Ò
˜B€CØŸ'™' *Ó-¨s¯w©w°{Ó/Cˆi€HàŸ™ °Ó3ÀcÇgÁgÈoÐ_aÓFbØ—W‘W˜[Ó)ÒCÔ-CØŸG™GÐ$5Ó6ÒQÔ:Q×YÑYÐZ]Ó^à�G‰GÐ(Ó)ÒGÔ-GÏÉÐPSËØ—g‘g˜lÓ+Ò7¨xØ—‘˜Ó!Ò7Ô%7Ø—w‘w˜}Ó-¸S¿W¹WÀ\Ó=RØ—W‘W˜[Ó)À3Ç7Á7ÐKaÓCbÜ  ›N¸ÑCñ
E€Eñ  h¸)ÐPUÔV€FÜ	˜?Ò2¨d°FÓ	;ð '¸vÜ$<¸UÀEÇIÁIÈnÓD]Ó$^Ð!ÙÐ5ÐAØ"'§)¡)¨OÓ"<ÐÜÐ1´3Ô7Ñ?RØ,Ð8Ü4Ø1ÀDôJð JäØDÐFgØ ô"ð "ñ .Ø¨uÐ5FÑ/GØ Ñ,Ð<OôQˆIô )Ø�yÐ"5Ü#=¸iÓ#HõJð Ð*Ù¤ U£Ð-HÔIÙ)¨%Ô0Ù Ô&÷)'ð* €L÷+'ð* €Lús   Å#B.HÈH%)Úlabelc               ó  — ddl m}m} ddlm} t        | «      }|r5t        |«      j                  «       rt        |«      j                  «       |d<    |d|«        ||«        |d«      }t        d„ |j                  «       D «       d«      S )aÊ  Persist Nous OAuth credentials as the singleton provider state.

    Nous credentials are read from ``providers.nous`` (401 recovery, pool seeding) AND
    ``credential_pool.nous`` (runtime ``pool.select()``); a pool-only write broke expiry recovery.
    So: write the singleton, mirror to the shared store, then ``load_pool("nous")`` upserts the
    canonical ``device_code`` entry in place. ``label`` rides in the singleton so re-seeding keeps
    it.
    r   )Ú_save_active_provider_stater+  ©Ú	load_poolrÅ  rv   c              3  óH   K  — | ]  }|j                   t        k(  sŒ|–— Œ y ­wr¡   )rZ  r   )r¢   ra  s     r.   r¤   z+persist_nous_credentials.<locals>.<genexpr>;  s   è ø€ ÒR�q¨a¯h©hÔ:QÓ.Q”ÑRùs   ‚"›"N)
rš   rÇ  r+  Úagent.credential_poolrÉ  ry   r4   r5   Únextr_  )ÚcredsrÅ  rÇ  r+  rÉ  rÉ   Úpools          r.   Úpersist_nous_credentialsrÏ  *  sr   € ÷ VÝ/Ü�‹K€EÙ”�U“×!Ñ!Ô#Ü˜U›×)Ñ)Ó+ˆˆg‰Ù ¨Ô.Ù˜UÔ#Ù�VÓ€DÜÑR˜DŸL™L›NÔRÐTXÓYÐYr0   c                 óz   — 	 ddl m}   | d«       y# t        $ r }t        j	                  d|«       Y d}~yd}~ww xY w)zGBest-effort pool reseed after providers.nous changes; never fail login.r   rÈ  rv   z7Failed to sync Nous credential pool from auth store: %sN)rË  rÉ  rq   rL   rå   )rÉ  r*  s     r.   Ú_sync_nous_pool_from_auth_storerÑ  >  s8   € ðUÝ3Ù�&ÕøÜò UÜ�‰ÐNÐPS×TÑTûðUús   ‚ ‘	:š5µ:c                ó:  — ddl m}m}  || j                  d«      «      xs t        j                  d«      }t        «       }|r|j                  d«      }nZt        |«      }|j                  |j                  }}|dk(  xs |dk(  xr |dv }|r||vs|st        j                  d||«       t        }dd	lm}	 t         || j                  d
«      «      «      xs  |	| «      rt        nt         }
||
t#        «       xs |
t%        | j                  d«      xs t&        «      fS )a  ``(portal_url, stored_inference_url, effective_inference_url, client_id)`` from *state*.

    The stored inference URL is re-validated network-provenance (persisted); the effective one
    layers the runtime-only ``NOUS_INFERENCE_BASE_URL`` override on top and is never persisted.
    r   )Ú_NOUS_PORTAL_ALLOWED_HOSTSr—   rw   r†   r…   Úhttp>   ú	127.0.0.1Ú	localhostzXauth: ignoring invalid portal_base_url %r (host %r or scheme not allowed), using default©Úis_guest_stater  r  )rš   rÓ  r—   rx   r   rˆ   rž   r   rz   r‡   rL   r{   Úhermes_cli.anon_authrØ  r‹   r   r   r�   r4   r   )rÉ   rÓ  r—   Ú
portal_urlÚenv_portal_overrideÚparsed_portal_urlÚportal_hostr‡   Útrusted_schemerØ  Ústored_inference_urls              r.   Ú_nous_effective_routingrà  G  s-  € ÷ OÙ$ U§Y¡YÐ/@Ó%AÓBÒ]ÔF]×eÑeÐfiÓj€Jô 4Ó5ÐÙØ(×/Ñ/°Ó4‰
ä$ ZÓ0ÐØ/×8Ñ8Ð:K×:RÑ:R�VˆØ 7Ñ*ò LØ�fÑÒJ Ð0JÐ!Jð 	á˜kÐ1KÑKÑSaÜ�N‰NðAà˜Kô)ô 1ˆJõ 4ä1Ù˜uŸy™yÐ)=Ó>Ó?ó	Aò 	_á(6°uÔ(=Õ$ÔC]ð ð
 	Ð(Ô*FÓ*HÒ*`ÐL`ÜˆE�I‰I�kÓ"Ò<Ô&<Ó=ð?ð ?r0   c                  óz   — e Zd ZdZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd„Zdd„Zdd„Zd„ Zdd„Zdd„Z	dd„Z
dd	„Zdd
„Zdd„Zy)Ú_NousRuntimeResolvea`  Working set for one ``resolve_nous_runtime_credentials`` call.

    Holds the token pair + routing tuple that shared-store merges / refreshes replace mid-flight.
    ``persist`` skips writes where only derived TTL countdowns changed (keeps the mtime-keyed
    auth-status cache warm) and mirrors every real write to the shared store (best-effort).
    c               óN  — |||c| _         | _        | _        ||c| _        | _        || _        t        j                  «       j                  d d | _	        t        |«      | _        d| _        |j                  d«      | _        |j                  d«      | _        | j!                  «        y )Nr2   FrÌ   r
  )r^  rÉ   Ú_source_pathrs  Ústale_access_tokenr  ÚuuidÚuuid4Úhexr=   ry   Ú_persisted_stateÚpersisted_anyrx   rÌ   r
  Ú_reload_routing)Úselfr^  rÉ   Ústate_source_pathrs  rå  r  s          r.   Ú__init__z_NousRuntimeResolve.__init__t  s•   € ð :DÀUÐL]Ð6ˆŒ˜œ TÔ%6Ø6CÐEWÐ3ˆÔ˜DÔ3Ø.ˆÔÜŸ:™:›<×+Ñ+¨C¨RÐ0ˆÔÜ $ U£ˆÔØ"ˆÔØ!ŸI™I nÓ5ˆÔØ"ŸY™Y Ó7ˆÔØ×ÑÕr0   c                ó`   — t        | j                  «      \  | _        | _        | _        | _        y r¡   )rà  rÉ   rw   Ústored_inference_base_urlr  r  ©rì  s    r.   rë  z#_NousRuntimeResolve._reload_routing�  s'   € ä2°4·:±:Ó>ñ	ˆÔ	˜tÔ=¸tÔ?VØ	�r0   c                ó2  — ddl m}m} | j                  }t	        | j
                  «      }t	        |«      |k(  rt        d| j                  |¬«       y 	  || j                  d|| j                  «       t        d| j                  |t        |j                  d	«      «      t        |j                  d
«      «      ¬«       t        |«      | _        d| _         ||«       y # t        $ r2}t        d| j                  |t        |«      j                  ¬«       ‚ d }~ww xY w)Nr   )Ú_save_provider_state_to_sourcer+  Únous_state_persist_skipped)r=   r,   rv   Únous_state_persist_failed)r=   r,   ru  Únous_state_persistedr
  rÌ   )r=   r,   r(  rä   T)rš   ró  r+  rÉ   rì   ré  rR   r=   r^  rä  rq   rx  ry  r<   rx   ry   rê  )rì  r,   ró  r+  rÉ   Ú	persistedr*  s          r.   r�  z_NousRuntimeResolve.persist…  sñ   € ß\Ø—
‘
ˆÜ2°4×3HÑ3HÓIˆ	Ü)¨%Ó0°IÒ=ÜÐ5À4×CSÑCSÐ\bÕcØð	Ù*¨4¯?©?¸FÀEÈ4×K\ÑK\Ô]ô 	Ø"°×0@Ñ0@ÈÜ/°·	±	¸/Ó0JÓKÜ.¨u¯y©y¸Ó/HÓIõ	Kô !% U£ˆÔØ!ˆÔÙ  Õ'øô ò 	ÜØ+¸×9IÑ9IÐRXÜ ›9×-Ñ-õ/ð ûð		ús   ÁC Ã	DÃ$-DÄDc                ó@   — t        t        | j                  «      ¬«      S )Nr  )r  r	  r  rñ  s    r.   Úshared_lockz_NousRuntimeResolve.shared_lock›  s   € Ü&Ô7KÈD×L`ÑL`Ó7aÔbÐbr0   c                ód   — t        | j                  t        «      xr t        | j                  «      S r¡   )r3   rÌ   r4   rÂ  rñ  s    r.   Úhas_access_tokenz$_NousRuntimeResolve.has_access_tokenž  s%   € Ü˜$×+Ñ+¬SÓ1ÒM´d¸4×;LÑ;LÓ6MÐMr0   c                óB   — t        | j                  | j                  «      S r¡   )rÊ   rÉ   rÌ   rñ  s    r.   Úinvoke_jwt_statusz%_NousRuntimeResolve.invoke_jwt_status¡  s   € Ü'¨¯
©
°D×4EÑ4EÓFÐFr0   c                óÐ   — t        | j                  «      sy| j                  j                  d«      | _        | j                  j                  d«      | _        | j                  «        y)zSAdopt fresher shared-store tokens (caller holds the shared lock). True when merged.FrÌ   r
  T)r  rÉ   rx   rÌ   r
  rë  rñ  s    r.   Úmerge_sharedz _NousRuntimeResolve.merge_shared¤  sJ   € ä-¨d¯j©jÔ9ØØ ŸJ™JŸN™N¨>Ó:ˆÔØ!ŸZ™ZŸ^™^¨OÓ<ˆÔØ×ÑÔØr0   c                ó
  — | j                   }| j                  rk| j                  r^t        |t        «      rM|rJ|| j                  k7  r:| j                  «       €)t        d| j                  t        |«      ¬«       d| _        yyyyyyy)uó   Skip the refresh when a peer already rotated the grant.

        Under the store lock: if the bearer that failed upstream is no longer the one on disk and
        the on-disk one is usable, adopt it â€” never re-POST the shared grant.
        NÚrefresh_skipped_peer_rotatedrã   F)	rÌ   rs  rå  r3   r4   rý  rR   r=   r<   )rì  r:   s     r.   Úskip_refresh_if_peer_rotatedz0_NousRuntimeResolve.skip_refresh_if_peer_rotated­  s‡   € ð ×!Ñ!ˆØ×Ò 4×#:Ò#:¼zÈ%ÔQTÔ?UÑZ_Ø˜T×4Ñ4Ò4¸×9OÑ9OÓ9QÐ9YÜØ.¸D×<LÑ<LÜ 2°5Ó 9õ;ð "'ˆDÕð	 :ZÐ4ð [`Ð?UÐ#:Ðr0   c                ó.  ‡ — t        ‰ j                  t        «      r‰ j                  st        |xs dd¬«      ‚‰ j                  rdn|xs d}t        d‰ j                  |t        ‰ j                  «      ¬«       t        |‰ j                  ‰ j                  ‰ j                  ‰ j                  ‰ j                  dˆ fd„¬	«      }‰ j                  }t        |«      ‰ _        t        «       xs ‰ j                  ‰ _        t#        ‰ j                  |‰ j                  ‰ j                  ¬
«       ‰ j                  d   ‰ _        ‰ j                  d   ‰ _        t        d‰ j                  |t        |«      t        ‰ j                  «      ¬«       ‰ j'                  d«       y)zURedeem the refresh token, apply + persist the rotated pair (caller holds both locks).rs  Tr#   Úaccess_unusableÚrefresh_start)r=   r,   r(  Úruntime_access_refresh_failurec                 ó&   •— ‰ j                  d«      S )NÚ'terminal_runtime_access_refresh_failure)r�  rñ  s   €r.   rq  z-_NousRuntimeResolve.refresh.<locals>.<lambda>É  s   ø€ ˜DŸL™LÐ)RÓS€ r0   )r‰  r^  rÉ   rw   r  r
  r,   r�  r’  rÌ   r
  Úrefresh_success)r=   r,   Úprevious_refresh_token_fpÚnew_refresh_token_fprÁ  N)r3   r
  r4   r/   rs  rR   r=   r<   r‘  r^  rÉ   rw   r  r—  rð  r�   r  r•  rÌ   r�  )rì  r‰  rý  Úrefresh_reasonr{  Úprevious_refresh_tokens   `     r.   Úrefreshz_NousRuntimeResolve.refresh»  sc  ø€ ä˜$×,Ñ,¬cÔ2¸$×:LÒ:LÜ,Ø!Ò4 _ÀtôMð Mð  $×1Ò1‰OÐ8IÒ8^ÐM^ð 	äØ¨×)9Ñ)9À.Ü/°×0BÑ0BÓCõ	Eô 0Ø d§o¡o¸T¿Z¹ZØ ×0Ñ0¸D¿N¹NØ×,Ñ,Ð5UÛSô	Uˆ	ð
 "&×!3Ñ!3Ðô *DÀIÓ)NˆÔ&Ü">Ó"@Ò"bÀD×DbÑDbˆÔÜ$Ø�J‰J˜	 4×#5Ñ#5Ø#×=Ñ=õ	?ð !ŸJ™J ~Ñ6ˆÔØ!ŸZ™Z¨Ñ8ˆÔÜØ¨4×+;Ñ+;ÀNÜ&8Ð9OÓ&PÜ!3°D×4FÑ4FÓ!Gõ	Ið
 	�‰Ð0Õ1r0   c                óÊ  — ddl m}m}  || j                  «      r | j                  s| j                  «       �ƒ || j                  |«       | j                  d   | _        | j                  j                  d«      xs | j                  | _        t        «       xs | j                  | _
        | j                  d«       y| j                  «       s:| j                  «       5  | j                  «       r| j                  d«       ddd«       | j                  «       st        dd	d
¬«      ‚| j                  «       }| j!                  «        | j                  s|€y| j                  «       5  | j                  «       r1| j                  «       }| j                  d«       | j!                  «        | j                  s|�| j#                  ||«       ddd«       y# 1 sw Y   ŒÑxY w# 1 sw Y   yxY w)zTMerge from the shared store / refresh until the access token is a usable invoke JWT.r   )rØ  Úrefresh_guest_stateNrÌ   r  Úguest_exchangeÚ)runtime_shared_merge_missing_access_tokenz,No access token found for Nous Portal login.Únous_auth_missing_access_tokenTr)   Ú!post_shared_merge_access_unusable)rÙ  rØ  r  rÉ   rs  rý  rÌ   rx   rð  r�   r  r�  rû  rù  rÿ  r    r  r  )rì  r‰  rØ  r  rý  s        r.   Úensure_usable_access_tokenz._NousRuntimeResolve.ensure_usable_access_tokenÜ  s¦  € çLÙ˜$Ÿ*™*Ô%ð ×!Ò! T×%;Ñ%;Ó%=Ð%IÙ# D§J¡J°Ô7Ø$(§J¡J¨~Ñ$>�Ô!Ø15·±·±Ð@TÓ1UÒ1wÐY]×YwÑYw�Ô.Ü*FÓ*HÒ*jÈD×LjÑLj�Ô'Ø—‘Ð-Ô.ØØ×$Ñ$Ô&Ø×!Ñ!Ó#ñ NØ×$Ñ$Ô&Ø—L‘LÐ!LÔM÷Nð ×$Ñ$Ô&ÜØ>Ð@`Ðjnôpð pà ×2Ñ2Ó4ÐØ×)Ñ)Ô+Ø×"Ò"Ð&7Ð&CØØ×ÑÓñ 	8Ø× Ñ Ô"Ø$(×$:Ñ$:Ó$<Ð!Ø—‘Ð@ÔAØ×1Ñ1Ô3Ø×!Ò!Ð%6Ð%BØ—‘˜VÐ%6Ô7÷	8ð 	8÷Nð Nú÷	8ð 	8ús   Ã"GÅ"A"GÇGÇG"N)r^  rd   rÉ   rd   rí  zOptional[Path]rs  rÂ  rå  úOptional[str]r  rÀ   ÚreturnÚNone©r  r  ©r,   r4   r  r  )r  rÂ  ©r  r  )r‰  úhttpx.Clientrý  r  r  r  )r‰  r  r  r  )ry  Ú
__module__Ú__qualname__Ú__doc__rî  rë  r�  rù  rû  rý  rÿ  r  r  r  rn  r0   r.   râ  râ  l  su   „ ñðØ(ðØ1?ðØTbðàðà4AðàTYðà^bóó?ó(ò,cóNóGóó'ó2ôB8r0   râ  ©r  rj  rk  rs  rå  c                ó>  — ddl m}m}m} 	 t	        | ||||¬«      S # |$ r{}ddlm}	 ddl m}
  |	d«      xs i } |t        |j                  xs d«      |j                  d«      ¬	«       |j                  |
k(  r‚  |d
| ¬«      €‚ t	        | ||¬«      cY d}~S d}~ww xY w)a$  Resolve Nous inference credentials for runtime use (refreshing under the auth-store lock).

    A guest whose ``anon_`` credential NAS no longer knows (reaped or claimed) is retired and a new
    identity is set up once, transparently -- the one client rule covering both reap and claim.
    r   )ÚAnonCredentialDeadÚclear_dead_guestÚensure_portal_identityr   ©Úget_provider_auth_state)ÚANON_ACCOUNT_LOCKEDrv   Úanon_credential_deadr  )Ú
dead_tokenT)Úexplicitr  N)r  rj  rk  )rÙ  r"  r#  r$  Ú!_resolve_nous_runtime_credentialsrš   r&  r'  r4   rE  rx   )r  rj  rk  rs  rå  r"  r#  r$  Údead_excr&  r'  Údeads               r.   Ú resolve_nous_runtime_credentialsr.  ý  s²   € ÷ bÑaðUÜ0Ø+°hÈ)Ø'Ð<NôPð 	Pøð ò UÝ;Ý<Ù& vÓ.Ò4°"ˆÙœ˜XŸ]™]ÒDÐ.DÓEÐRV×RZÑRZÐ[gÓRhÕià�=‰=Ð/Ò/ØÙ!¨4ÀÔQÐYØÜ0Ø+°hÈ)ôUõ 	UûðUús   Œ œB¡A0BÂBÂBc                óÜ  — ddl m}m}m}m}m}	m}
m}  |d«      5 \  }}}|st        ddd¬«      ‚t        |||||| ¬«      } ||||¬	«      }t        d
|j                  t        |j                  d«      «      ¬«       t        | xs d|«      5 }|j                  |«        |||j                   ¬«        |	||j                   |j                  ¬«       |j#                  |j$                  |j&                  |j(                   ||«      ¬«       ddd«       |j+                  d«       ddd«       j,                  r |
«        j                  d«      }t/        |t0        «      r|st        dd«      ‚|j                  d«      }d|j2                  ||j                  d«      |t5        ||j                  d«      «      t6        t6        t1        xs  |«       «      dœ	S # 1 sw Y   ŒÍxY w# 1 sw Y   ŒÀxY w)u…  Resolve Nous inference credentials for runtime use (refreshing under the auth-store lock).

    ``stale_access_token`` is the bearer that just failed upstream (401): with ``force_refresh``,
    the refresh POST is skipped if the store (re-read under the lock) already holds a *different*
    usable token â€” a peer won the rotation; adopt it rather than invalidate a sibling's token.
    r   )rÎ   r<  Ú_provider_state_transactionr¾  ræ   rÑ  Ú_tls_state_from_verifyrv   z&Hermes is not logged into Nous Portal.Únous_auth_missingTr)   )rs  rå  r  r¿  Únous_runtime_credentials_startr
  )r=   r(  re  rË   rà   )rw   r  r  rl  NÚ&resolve_nous_runtime_credentials_finalr^   z*Failed to resolve a Nous inference API keyÚserver_errorr`   r_   ra   )	ÚproviderÚbase_urlr³  Úkey_idr´   rÜ   rZ  rM  Ú
state_path)rš   rÎ   r<  r0  r¾  ræ   rÑ  r1  r    râ  rR   r=   r<   rx   rŸ  r  rÌ   rK   rw   r  rð  r�  rê  r3   r4   r  rÔ   r   )r  rj  rk  rs  rå  rÎ   r<  r0  r¾  ræ   rÑ  r1  r^  rÉ   rí  Úrunr›  r‰  r³  r´   s                       r.   r+  r+    sÛ  € ÷ ÷  ñ  ñ 
% VÓ	,ð >Ñ0V°¸UÐDUÙÜÐDÐFYÐcgÔhÐhÜ!Ø˜Ð0ÀØ1À?ôTˆñ !¨(¸iÐTYÔZˆÜØ,¸#¿/¹/Ü/°·	±	¸/Ó0JÓKõ	Mô ˜Ò6°$¸Ó?ð 	4À6Ø×*Ñ*¨6Ô2Ù-¨eÀ#×BRÑBRÕSÙ#Ø C×$4Ñ$4À#Ç/Á/õSð
 �L‰LØ #× 3Ñ 3¸s¿}¹}Ø#&×#@Ñ#@Ù*¨6Ó2ð ô 4÷	4ð 	�‰Ð<Ô=÷->ð. ×ÒÙ'Ô)Ø�i‰i˜Ó$€GÜ�gœsÔ#©7ÜÐDÀnÓUÐUØ—‘Ð1Ó2€Jà¨×(>Ñ(>È7Ø—)‘)˜NÓ+¸:Ü$ Z°·±Ð;QÓ1RÓSÜ+ô /ÜÐ+Ò@©Ó/@ÓAñCð C÷'	4ð 	4ú÷>ð >ús%   ›A1G"ÂA>GÄ
G"ÇG	ÇG"Ç"G+c            	     ó   — dd d d d ddd dœS )NF)Ú	logged_inrw   r  Úaccess_expires_atr`   Úhas_refresh_tokenÚinference_credential_presentÚcredential_sourcern  rn  r0   r.   Ú_empty_nous_auth_statusrA  O  s"   € à¨tÈ4Ø!¸4ÐV[Ø(-ÀDñJð Jr0   c                 óÀ  ‡	‡
— ddl mŠ	 	 ddlm}   | d«      }|r)|j	                  «       rt        |j                  «       «      ng }|s
t        «       S t        |ˆ	fd„¬«      Š
dˆ
fd„	} |d	«      s
t        «       S  |d
«       |d«      }}t         |dd«      xs d«      j                  «       j                  «       }t        |«      xr |j                  d«      xs t        |«      } |dd«      }||r |d«      xs t        nd |d«      xs  |d«      xs  |d«      |r|nd |d«       |d«      t        |«      dd|› �d|› �dœ
S # t        $ r t        «       cY S w xY w)zËBest-effort status from the credential pool.

    Fallback only: the auth-store provider state is the runtime source of truth because it is what
    ``resolve_nous_runtime_credentials()`` refreshes.
    r   )rÑ   rÈ  rv   c           	     ó”   •—  ‰t        | dd «      «      xs d ‰t        | dd «      «      xs dt        t        | dd«      xs d«       fS )Nr`   r  r´   Úpriorityr   )rz  r¿   )ra  rÑ   s    €r.   rq  z,_snapshot_nous_pool_status.<locals>.<lambda>c  sP   ø€ Ù ¤¨Ð,BÀDÓ!IÓJÒQÈcÙ ¤¨¨L¸$Ó!?Ó@ÒGÀCÜ”˜˜J¨Ó*Ò/¨aÓ0Ð0ð,2€ r0   r¯  Nc                ó   •— t        ‰| |«      S r¡   )rz  )r’   ÚdefaultÚentrys     €r.   rq  z,_snapshot_nous_pool_status.<locals>.<lambda>g  s   ø€ ¬'°%¸¸wÓ*G€ r0   Úruntime_api_keyrÌ   r
  Ú	auth_typer&   ÚoauthrÅ  Úunknownrw   r  Úruntime_base_urlr7  r´   r`   Túpool:)
r<  rw   r  rÌ   r=  r`   r>  r?  r@  rZ  r¡   )rš   rÑ   rË  rÉ  Úhas_credentialsr­   r_  rA  r¾   r4   r5   rJ   rÂ  Ú
startswithr   rq   )rÉ  rÎ  r_  ÚattrrÌ   r
  rI  Úis_portal_oauthrÅ  rÑ   rG  s            @@r.   Ú_snapshot_nous_pool_statusrR  V  sq  ù€ õ 5ð)Ý3Ù˜Ó ˆÙ*.°4×3GÑ3GÔ3I”$�t—|‘|“~Ô&ÈrˆÙÜ*Ó,Ð,Ü�Gó "2ô 3ˆô HˆÙÐ%Ô&Ü*Ó,Ð,Ù&*¨>Ó&:¹DÀÓ<Q�mˆÜ™˜[¨"Ó-Ò3°Ó4×:Ñ:Ó<×BÑBÓDˆ	Ü˜|Ó,ò BØ× Ñ  Ó)Ò@¬T°-Ó-@ð 	á�W˜iÓ(ˆà(áHW‘Ð'Ó(ÒCÕ,CÐ]aáÐ)Ó*ÒZ©dÐ3EÓ.FÒZÉ$ÈzÓJZÙ,;™LÀÙ!% lÓ!3Ù$(Ð)?Ó$@Ü!% mÓ!4ÐVZØ#(¨¨ ¸eÀEÀ7¸Oñ
Mð 
	Møô ò )Ü&Ó(Ò(ð)ús   ŠAE Á'E Á9CE ÅEÅEc               ó€  — ddl m} | j                  d«      }| j                  d«      }|| j                  d«      | j                  d«      | j                  d«      | j                  d«      t        | j                  d	«      «      |t        |xs | j                  d
«      «      d|t	        |t
        «      r|nd || «      dœS )zVAuth-store-backed Nous status snapshot (shared by the live and refresh-free variants).r   r×  rÌ   r  rw   r  r´   r`   r
  r^   r^  N)r<  rw   r  r=  r`   r>  rÌ   r?  r@  rZ  r  Ú	free_tier)rÙ  rØ  rx   rÂ  r3   r4   )rÉ   r<  rZ  rØ  rÌ   r  s         r.   Ú_nous_status_from_staterU  ~  s¦   € õ 4Ø—9‘9˜^Ó,€LØ—9‘9˜^Ó,€Là°5·9±9Ð=NÓ3OØ#Ÿi™iÐ(<Ó=Ø"ŸY™Y |Ó4Ø %§	¡	Ð*@Ó AÜ! %§)¡)¨OÓ"<Ó=È|Ü(,¨\Ò-S¸U¿Y¹YÀ{Ó=SÓ(TØ)°Vä(2°<ÄÔ(E™È4Ù# EÓ*ñ
,ð 
,r0   c                 ód  — ddl m} m}  | d«      }|s
t        «       S t	        |t        |j                  d«      «      d¬«      }	  |«       } | d«      xs |}|j                  d|j                  d«      xs |j                  d«      |j                  d	«      xs$ |j                  d
«      xs |j                  d
«      |j                  d«      xs |j                  d«      |j                  d«      xs$ |j                  d«      xs |j                  d«      t        |j                  d«      «      ddd|j                  dd«      › �|j                  d«      dœ
«       |S # t        $ rI}|j                  dt        |«      t        t        |dd«      «      t        |dd«      dœ«       Y d}~|S d}~ww xY w)zEUncached implementation of get_nous_auth_status(). See that function.r   )r&  r.  rv   rÌ   r^  ©r<  rZ  Trw   r7  r  r´   r=  r`   r
  zruntime:rZ  Úportalr8  )
r<  rw   r  r=  r`   r>  r?  r@  rZ  r8  FÚrelogin_requiredrE  N)r<  rr   rY  r=  )rš   r&  r.  rR  rU  rÂ  rx   rK   r   r4   rz  )r&  r.  rÉ   Úbase_statusrÍ  Úrefreshed_stater*  s          r.   Ú_compute_nous_auth_statusr\  ‘  s§  € çYÙ# FÓ+€EÙÜ)Ó+Ð+Ü)Øœ˜eŸi™i¨Ó7Ó8ÀôO€Kð7Ù0Ó2ˆÙ1°&Ó9ÒB¸UˆØ×ÑØà×#Ñ#Ð$5Ó6Ò\¸+¿/¹/ÐJ[Ó:\à—	‘	˜*Ó%ò 9¨×)<Ñ)<Ð=QÓ)Rò 9Ø—?‘?Ð#7Ó8à×#Ñ# LÓ1ÒY°[·_±_ÐEXÓ5Yà—	‘	˜,Ó'ò ;¨?×+>Ñ+>Ð?UÓ+Vò ;Ø—?‘?Ð#9Ó:Ü!% o×&9Ñ&9¸/Ó&JÓ!KØ,0À|Ø  §¡¨8°XÓ!>Ð ?Ð@ÈEÏIÉIÐV^ÓL_ñaô 	bð( Ðøô ò 7Ø×ÑØ¬¨S«Ü $¤W¨SÐ2DÀeÓ%LÓ MÜ! # v¨tÓ4ñ6÷ 	7ð 	7ð Ðûð7ús   ÁDE Å	F/Å&>F*Æ*F/c                ó°   — | j                  d«      }t        |t        «      r5|j                  d«      r$| j                  d«      s| j                  d«      s|S y)zóThe persisted ``last_auth_error`` when it is a terminal quarantine with no credential left.

    Only terminal while there is no usable credential: if a later login repopulated tokens the stale
    marker must not keep reporting terminal.
    rV  rY  rÌ   r
  N)rx   r3   ry   )rÉ   Úlast_errs     r.   Ú_terminal_quarantine_markerr_  ³  sH   € ð �y‰yÐ*Ó+€HÜ�8œTÔ" x§|¡|Ð4FÔ'GØ—Y‘Y˜~Ô.°%·)±)¸OÔ2LØˆØr0   c                 ó‚  — ddl m}  	  | d«      }|s
t        «       S t	        ||j                  d«      «      }t        |«      }|du xs  t        |j                  d«      «      xr |du }t        ||d¬«      }|�6|j                  d	|j                  d
«      |j                  d«      xs d¬«       |S # t        $ r d}Y Œ­w xY w)u,  Refresh-free Nous auth snapshot for read-only display surfaces.

    NEVER calls ``resolve_nous_runtime_credentials()`` (no refresh POST / single-use token spent);
    ``logged_in`` = usable invoke JWT, or a refresh token not terminally quarantined â€” not proof
    the server still accepts it.
    r   r%  rv   NrÌ   r
  Úauth_store_localrW  TrE  rs   zre-login required)rY  r=  rr   )
rš   r&  rq   rR  rÊ   rx   r_  rÂ  rU  rK   )r&  rÉ   Ú
jwt_reasonr^  r<  Ústatuss         r.   Úget_nous_auth_status_localrd  À  sÏ   € õ 8ðÙ'¨Ó/ˆñ Ü)Ó+Ð+Ü)¨%°·±¸>Ó1JÓK€JÜ*¨5Ó1€HØ˜tÐ#Ò_¬¨e¯i©i¸Ó.HÓ)IÒ)^ÈhÐZ^ÐN^€IÜ$ U°iÐHZÔ[€FØÐØ�‰Ø!¨h¯l©l¸6Ó.BØ—,‘,˜yÓ)Ò@Ð-@ð 	ô 	Bð €Møô ò ØŠðús   ˆB0 Â0B>Â=B>ÚvalidÚterminalrK  c                 óÂ   — ddl m}  	  | d«      }|st        S t	        |«      �t
        S t        ||j                  d«      «      €t        S t        S # t        $ r d}Y ŒMw xY w)ub  Classify the Nous bootstrap session for the dashboard /api/status probe.

    Local auth-store state only; polled frequently, so it never resolves or refreshes. ANTI-FLAP:
    only a *terminal* failure maps to "terminal" â€” a rotation blip, network error, or expiring
    token must NOT (that would trigger a spurious NAS re-mint on a healthy box).
    r   r%  rv   NrÌ   )	rš   r&  rq   ÚNOUS_SESSION_UNKNOWNr_  ÚNOUS_SESSION_TERMINALrÊ   rx   ÚNOUS_SESSION_VALID)r&  rÉ   s     r.   Úget_nous_session_validityrk  á  sn   € õ 8ðÙ'¨Ó/ˆñ Ü#Ð#ô # 5Ó)Ð5Ü$Ð$Ü  u§y¡y°Ó'@ÓAÐIÜ!Ð!ô  Ðøô ò ØŠðús   ˆA ÁAÁA)Úon_pool_missc          
     óv  — ddl m} 	 ddlm}  || «      }|r†|j	                  «       rv|j                  «       }|�Wt        |dd«      xs t        |dd«      }	|	r9 ||	d«      s0dt         |«       «      t        |d	d«      |d
t        |dd«      › �|	dœS |� |«       x}
r|
S 	  |«       }dt         |«       «      |j                  d	«      |j                  d«      |j                  d«      |j                  d«      dœS # t        $ r Y Œfw xY w# t        $ r'}dt         |«       «      t        |«      dœcY d}~S d}~ww xY w)uÐ  Status snapshot for a store-backed OAuth provider (Codex, xAI).

    Pool first (where `hermes auth` / `hermes model` store device_code tokens), then
    *on_pool_miss* for a pool-derived degraded status, then the legacy state via *resolve*.

    The pool read is an observation (``peek``), not a lease: ``select()`` refreshes an expiring
    single-use token and, when that speculative POST fails transiently, benches the entry with a
    persisted cooldown â€” every credential-gated listing (``/model`` picker, doctor) then shows the
    provider as unconfigured while the runtime resolver still serves it. Refreshing stays with the
    runtime resolver reached through *resolve*, whose failures persist nothing.
    r   r;  rÈ  NrH  rÌ   r&   TÚlast_refreshrM  rÅ  rK  )r<  r^  rn  Ú	auth_moderZ  r³  ro  rZ  r³  F)r<  r^  rr   )rš   r<  rË  rÉ  rN  Úpeekrz  r4   rq   rx   r   )Úprovider_idÚis_expiringro  rú   rl  r<  rÉ  rÎ  rG  r³  ÚdegradedrÍ  r*  s                r.   Ú_pool_first_oauth_statusrt  û  sS  € õ 0ðÝ3Ù˜Ó%ˆÙ�D×(Ñ(Ô*Ø—I‘I“KˆEØÐ ä˜EÐ#4°dÓ;Òa¼wÀuÈnÐ^`Ó?að á¡;¨w¸Ô#:à%)¼¹_Ó=NÓ9OÜ(/°°~ÀtÓ(LØ%.Ø$)¬'°%¸À)Ó*LÐ)MÐ"NÐ[bñ	dð dð
 Ð'¹»Ð-G¨XÐ-GØ�ð]Ù“	ˆà¬S±Ó1BÓ-CØ!ŸI™I nÓ5ØŸ™ ;Ó/¸5¿9¹9ÀXÓ;NØ—y‘y Ó+ñ	-ð 	-øô	 ò Ùðûô ò ]Ø"´#±oÓ6GÓ2HÔSVÐWZÓS[Ñ\Õ\ûð]ús7   ˆBC9 ÂC9 ÂAD Ã9	DÄDÄ	D8ÄD3Ä-D8Ä3D8)	rw   r  r  r³   Úopen_browserr  rj  rk  Úon_verificationc        	        óÔ  — ddl m}	m}
m}m}m}m}m}m}m	}m
} |	d   }| xs: t        j                  d«      xs# t        j                  d«      xs |j                  j                  d«      } |xs# t        j                  d«      xs |j                  j                  d«      }|xs |j                   }|xs |j"                  }|rdn|r|nd	} |«       rd}t%        d
|j&                  › d�«       t%        d| › �«       |rt%        d«       n|rt%        d|› d�«       t)        ||«      5 } ||| ||¬«      }t+        |d   «      }t+        |d   «      }t-        |d   «      }t-        |d   «      } ||||d¬«       |�!t/        t0        «      5   |||«       ddd«       t3        dt5        |t6        «      «      }t%        d|› d�«        ||| |t+        |d   «      ||¬«      }ddd«       t9        j:                  t<        j>                  «      } |
jA                  dd«      «      } ||jA                  d«      «      xs |} | |k7  rt%        d| › �«       | | ||jA                  d«      xs ||jA                  d d!«      |d"   |jA                  d#«      |jC                  «       tE        ||«      | ||«      d$œtF        ¥}!	  ||!|d¬%«      S # 1 sw Y   �Œ1xY w# 1 sw Y   ŒòxY w# tH        $ r„}"|"jJ                  d&k(  ro|!jA                  d'tL        «      j                  d«      }#t%        «        t%         ||"«      «       t%        d(|#› d)�«       t%        «        t%        d*«       tO        d«      ‚‚ d}"~"ww xY w)+zMRun the Nous device-code flow and return full OAuth state without persisting.r   )
ÚPROVIDER_REGISTRYrÐ   Ú_is_remote_sessionr—   Ú_poll_for_tokenÚ_print_device_code_instructionsÚ_request_device_coder1  Úformat_auth_errorrg  rv   rœ   r�   r†   r˜   FTzStarting Hermes login via z...zPortal: z'TLS verification: disabled (--insecure)z$TLS verification: custom CA bundle (r'   )r‰  rw   r  r³   Úverification_uri_completeÚ	user_coderÜ   Úintervalu   â€”)ru  Úfailure_dashNr%  z$Waiting for approval (polling every zs)...Údevice_code)r‰  rw   r  r‚  rÜ   Úpoll_intervalr  z%Using portal-provided inference URL: r³   r  r  rÌ   r
  )rw   r  r  r³   r  rÌ   r
  rØ   r´   rÜ   rl  )r  rs  Úsubscription_requiredrw   z  Subscribe here: z/billingz<After subscribing, run `hermes model` again to finish setup.)(rš   rx  rÐ   ry  r—   rz  r{  r|  r1  r}  rg  rH   rI   rw   rˆ   r  r  r³   Úprintr’   rŸ  r4   r¿   r   rq   r¾   Úminr   r   rZ   r   rX   rx   rY   r\   re   r   rE  r   Ú
SystemExit)$rw   r  r  r³   ru  r  rj  rk  rv  rx  rÐ   ry  r—   rz  r{  r|  r1  r}  rg  ÚpconfigÚrequested_inference_urlr›  r‰  Údevice_dataÚverification_urlr  rÜ   r€  Úeffective_intervalÚ
token_datarZ   Útoken_expires_inÚresolved_inference_urlrÀ  r*  rÚ  s$                                       r.   Ú_nous_device_code_loginr�  (  sw  € ÷R÷ R÷ Rð   Ñ'€Gàò 	#œ2Ÿ9™9Ð%=Ó>ò 	#Ä"Ç)Á)ÐLbÓBcò 	#Ø×"Ñ"§F¡F¨3£Kð ð 	ò 	&œbŸi™iÐ(AÓBò 	&Ø×%Ñ%§v¡v¨c£{ð ð Ò.˜W×.Ñ.€IØÒ"�W—]‘]€EÙ"*™¹i±ÈT€FÙÔØˆÜ	Ð& w§|¡| n°CÐ
8Ô9Ü	ˆH�_Ð%Ð
&Ô'ÙÜÐ7Õ8Ù	ÜÐ4°Y°K¸qÐAÔBÜ	˜?¨FÓ	3ð $°vÙ*Ø¨?ÀiÐW\ô^ˆä˜{Ð+FÑGÓHÐÜ˜ KÑ0Ó1ˆ	Ü˜ \Ñ2Ó3ˆ
Ü�{ :Ñ.Ó/ˆÙ'Ø˜i°lÐQVõ	Xð Ð&Üœ)Ó$ñ =ÙÐ 0°)Ô<÷=ä  ¤C¨Ô2WÓ$XÓYÐÜÐ4Ð5GÐ4HÈÐNÔOÙ$Ø¨?ÀiÜ˜K¨Ñ6Ó7ÀJØ"ô$ˆ
÷!$ô( �,‰,”x—|‘|Ó
$€CÙ*¨:¯>©>¸,ÈÓ+JÓKÐá˜:Ÿ>™>Ð*>Ó?Ó@Ò[ÐD[ð àÐ!8Ò8ÜÐ5Ð6LÐ5MÐNÔOà*ÐBXØ¨¯©¸Ó)@Ò)IÀEØ —n‘n \°8Ó<Ø" >Ñ2Ø#Ÿ™¨Ó8Ø—}‘}“´jÀÐFVÓ6WØ&Ñ/EÀfÓ/Mñ(ô 'ð(€JðÙ,Ø¨ÀuôNð 	N÷1=ñ =ú÷$ð $ûôL ò 	Ø�8‰8Ð.Ò.Ø#Ÿ™Ð(9Ô;RÓS×ZÑZÐ[^Ó_ˆJÜŒGÜÑ# CÓ(Ô)ÜÐ& z l°(Ð;Ô<ÜŒGÜÐPÔQÜ˜Q“-ÐØûð	ús?   Ä&A"KÆ
KÆAKÊ6
K ËK	ËKËKË	M'Ë#A?M"Í"M'c                óÂ   — ddl m}m} t        t        «      5   || «       ddd«       t        t        «      5   |«        ddd«       y# 1 sw Y   Œ)xY w# 1 sw Y   yxY w)zZMirror to the shared store + reseed the pool, swallowing all errors (same as _login_nous).r   )rÑ  r+  N)rš   rÑ  r+  r   rq   )rÀ  rÑ  r+  s      r.   Ú_mirror_nous_state_best_effortr’  v  sR   € çYÜ	”)Ó	ñ -Ù  Ô,÷-ä	”)Ó	ñ *Ù'Ô)÷*ð *÷-ð -ú÷*ð *ús   ˜	A	¸AÁ	AÁA)ru  r  rv  c           	     ó\  — ddl m}m}m}m}  |d«      xs i }|d   }|j                  d«      }	t        |	t        «      r|	j                  «       ng }
t        t        j                  g |
xs t        g¢t        ‘«      «      } ||j                  d«      xs d|j                  d«      xs d|j                  d«      xs |j                  d	j                  |«      | ||¬
«      } |d|«       t!        |«       |j                  d«      }t        |t        «      xr t        |j                  «       v S )a,  Re-run the device flow requesting ``billing:manage`` (step-up on 403 insufficient_scope).

    The user must be ADMIN/OWNER and select "Allow Remote Spending" in the portal, else the
    server silently downscopes and this returns False. Persists like ``_login_nous`` minus the
    model picker.
    r   )rx  r�  rÇ  r&  rv   r³   rw   Nr  r  r¦   )rw   r  r  r³   ru  r  rv  )rš   rx  r�  rÇ  r&  rx   r3   r4   r¬   r­   ry   r²  r   r   r  r�   r’  )ru  r  rv  rx  r�  rÇ  r&  Úpriorrˆ  Ú
_raw_scopeÚprior_scopeÚ	requestedrÀ  Úgranteds                 r.   Ústep_up_nous_billing_scoper™    s!  € ÷!ó !ñ $ FÓ+Ò1¨r€EØ Ñ'€Gð —‘˜7Ó#€JÜ(2°:¼sÔ(C�*×"Ñ"Ô$È€KÜ”T—]‘]ð $TØ
Ò
6Ô5Ð6ð$TÜ9Rð$Tó Uó V€Iá(ØŸ	™	Ð"3Ó4Ò<¸Ø Ÿ9™9Ð%9Ó:ÒB¸dØ—)‘)˜KÓ(Ò=¨G×,=Ñ,=ÀSÇXÁXÈiÓEXØ!°?ÐTcô	e€Jñ
   ¨
Ô3Ü" :Ô.Ø�n‰n˜WÓ%€GÜ�gœsÓ#ÒTÔ(AÀWÇ]Á]Ã_Ð(TÐTr0   c           
     ó,  — ddl m} | j                  d«      xs | j                  d«      }t        |t        «      r|st        dd«      ‚ddlm}m}m	}m
}m} ddlm}	m}
m}  |«       }| j                  d	d
«      }t!        «        g }d
}d}|rr |	d«      } |d¬«      } |
«       }|r#t#        t$        «      5  t'        d«      }ddd«       |r|n|} ||||«      \  }}|} |||d¬«      }||k7  }|r |||d¬«      \  }}|rDddlm}  ||¬«      }|rt!        |«       t!        dt-        |«      › d�«        |||||d||¬«      S |r8|xs t.        j1                  d«      }t!        d«       t!        |xs d|› d�«       yt!        d«       y# 1 sw Y   ŒÊxY w)a  Fetch the curated Nous model list (tier/policy-filtered) and run the interactive picker.

    Returns the selected model id, or None when the user skipped / nothing was selectable.
    Raises on any fetch failure so the caller can print the "Login succeeded, but..." notice.
    r   )Ú_prompt_model_selectionr^   rÌ   z,No runtime API key available to fetch modelsrƒ  )Úget_curated_nous_model_idsÚcheck_nous_free_tierÚpartition_nous_models_by_tierÚ&union_with_portal_free_recommendationsÚ&union_with_portal_paid_recommendations)Úget_pricing_for_providerÚnous_policy_allowed_idsÚrestrict_to_nous_policyrw   r&   Frv   Trj   zpaid Nous modelsN)Úrescue_empty)rT  )Únous_policy_notice)rc  zShowing u=    curated models â€” use "Enter custom model name" for others.)ÚpricingÚunavailable_modelsrÚ  Úunavailable_messageÚconfirm_providerÚconfirm_base_urlÚconfirm_api_keyr†   z#No free models currently available.zUpgrade at z to access paid models.z,No curated models available for Nous Portal.)rš   r›  rx   r3   r4   r    Úhermes_cli.modelsrœ  r�  rž  rŸ  r   Úhermes_cli.models_pricingr¡  r¢  r£  r…  r   rq   ro   rm   r¥  r]  r   rˆ   )rÀ  r  r›  Úruntime_keyrœ  r�  rž  rŸ  r   r¡  r¢  r£  rµ  Ú_portalr§  r¨  Ú_policy_narrowedr¦  rT  Ú_policy_allowedÚunionÚ_before_policyr¥  Ú_policy_noticeÚ_urls                            r.   Ú_pick_nous_model_after_loginr¶  ž  sá  € õ 8Ø—.‘. Ó-ÒO°·±ÀÓ1O€KÜ�k¤3Ô'©{ÜÐFÈÓXÐX÷õ ÷ñ ñ
 +Ó,€IØ�n‰nÐ.°Ó3€GÜ	„GØ!ÐØÐØÐÙÙ*¨6Ó2ˆá(°TÔ:ˆ	á1Ó3ˆÙÜœ)Ó$ñ VÜ&AÐBTÓ&UÐ#÷Vñ
 7@Ñ2Ø7ð 	ñ # 9¨g°wÓ?Ñˆ	�7Ø"ˆÙ+¨I°ÐUYÔZˆ	Ø$¨Ñ6ÐÙÙ,IØ˜7¨dô-4Ñ)ˆIÐ)áÝ>Ù+Ð4DÔEˆÙÜ�.Ô!ÜØ”s˜9“~Ð&ð ':ð :ô	;ñ 'Ø˜wÐ;MÐZaØ 3ÀfØ/ÀôNð 	Nñ ØÒ2Ô2×:Ñ:¸3Ó?ˆÜÐ3Ô4ÜÐ!ÒP {°4°&Ð8OÐ%PÔQð ô 	Ð<Ô=Ø÷AVð Vús   Â5F
Æ
Fc                ó  — ddl m}m} ddlm}  |«       }|r ||«      ry	 t        «       }t        «        t        |rd|› �nd«        |dd¬	«      syt        d
«       t        | ¬«      }|€t        d«       |S # t        $ r d}Y ŒZw xY w)a  Codex-style auto-import: offer to rehydrate a Nous credential from another profile.

    Checks the shared store before launching a fresh device-code flow. Returns the refreshed
    auth state when the user accepted and the import succeeded, else None.
    r   )Ú_prompt_yes_nor  r×  Nz)Found existing Nous OAuth credentials at z,Found existing shared Nous OAuth credentialsz!Import these credentials? [Y/n]: Úy)rF  z3Rehydrating Nous session from shared credentials...r  uK   Could not refresh shared credentials â€” falling back to device-code login.)	rš   r¸  r  rÙ  rØ  rÿ   rû   r…  r}  )r  r¸  r  rØ  rñ   Úshared_pathrÀ  s          r.   Ú_offer_shared_nous_importr»  å  s™   € ÷ HÝ3Ù$Ó&€FÙ‘^ FÔ+ð ðÜ-Ó/ˆô 
„GÜ	Á{Ð5°k°]Ñ
CØ=ô?áÐ=ÀsÕKØÜ	Ð
?Ô@Ü.¸ÔO€JØÐÜÐ[Ô\ØÐøô ò ØŠðús   ¢
A9 Á9BÂBc                ó¢   — ddl m}m}m}  |«       5   |«       }| r| |d<   n|j	                  dd«        ||«       ddd«       y# 1 sw Y   yxY w)zUUndo the ``active_provider="nous"`` that ``_save_provider_state`` wrote during login.r   )Ú_auth_store_lockÚ_load_auth_storeÚ_save_auth_storeÚactive_providerN)rš   r½  r¾  r¿  rW  )Úprior_active_providerr½  r¾  r¿  r^  s        r.   Ú_restore_active_providerrÂ    sP   € çTÑTÙ	Ó	ñ %Ù%Ó'ˆ
Ù Ø,AˆJÐ(Ò)à�N‰NÐ,¨dÔ3Ù˜Ô$÷%÷ %ñ %ús   ’*AÁAc                ó4  — ddl m}m}m}m}m}m}m}m}	m	}
 t        | dd«      xs d}t        | dd«      xs, t        j                  d«      xs t        j                  d«      }	 t        |«      }|€j |t        | d	d«      t        | d
d«      t        | dd«      xs |j                  t        | dd«      t        | dd«       |t        t        | dd«      «      |¬«      }|d   } |«       5   |«       j!                  d«      }ddd«        |d|«      } |	|«        |«        t#        «        t#        d«       t#        d|› �«       d}	 t%        ||«      }|s,t/        «       t#        «        t#        d«       t#        d«       y |d||¬«      } ||«       t#        d|› �«       t#        d|› d�«       y# 1 sw Y   Œ²xY w# t&        $ rE}t)        |t*        «      r |
|«      n
t-        |«      }t#        «        t#        d|› �«       Y d}~Œ´d}~ww xY w# t0        $ r t#        d«       t3        d«      ‚t&        $ rd}ddlm} t8        j;                  d |«       t#        «         ||t=        t        | d	d«      «      ¬!«      D ]  }t#        |«       Œ t3        d"«      ‚d}~ww xY w)#z&Nous Portal device authorization flow.r   )	r½  r¾  r�  rÇ  Ú_save_model_choicerÑ  Ú_update_config_for_providerr+  r}  rš  Nre  rk  ÚHERMES_CA_BUNDLEÚSSL_CERT_FILErÚ  Úinference_urlr  r³   Ú
no_browserFrj  )rw   r  r  r³   ru  r  rj  rk  r  rÀ  rv   zLogin successful!z  Auth state: z?Login succeeded, but could not fetch available models. Reason: z:No provider change. Nous credentials saved for future use.z4  Run `hermes model` again to switch to Nous Portal.)Údefault_modelzDefault model set to: z  Config updated: z (model.provider=nous)z
Login cancelled.é‚   )Úsign_in_failure_linesznous login failed: %r)Úservice_hostr%  )rš   r½  r¾  r�  rÇ  rÄ  rÑ  rÅ  r+  r}  rz  rH   rI   r»  r  rÂ  rx   r…  r¶  rq   r3   r   r4   rÂ  ÚKeyboardInterruptr‡  Úhermes_cli.auth_error_copyrÌ  rL   rå   Ú_portal_host)Úargsrˆ  r½  r¾  r�  rÇ  rÄ  rÑ  rÅ  r+  r}  r  rk  rÀ  r  rÁ  Úsaved_toÚselected_modelr*  rs   Úconfig_pathrÌ  Úlines                          r.   Ú_login_nousrÖ    s˜  € ÷5÷ 5õ 5ô ˜d I¨tÓ4Ò<¸€Oä��k 4Ó(ò 	&¬B¯I©IÐ6HÓ,Iò 	&Ü�9‰9�_Ó%ð ð5Ü.¨Ó?ˆ
ØÐÙ0Ü '¨¨l¸DÓ AÜ#*¨4°À$Ó#GÜ! $¨°TÓ:ÒO¸g×>OÑ>OÜ˜d G¨TÓ2Ü!(¨¨|¸UÓ!CÐCØ /¼$¼wÀtÈZÐY^Ó?_Ó:`Ø#ô%ˆJð (Ð(<Ñ=Ðñ Óñ 	NÙ$4Ó$6×$:Ñ$:Ð;LÓ$MÐ!÷	Ná.¨v°zÓBˆá  Ô,Ù'Ô)ÜŒÜÐ!Ô"Ü�˜x˜jÐ)Ô*àˆð	_Ü9¸*ÐFXÓYˆNñ Ü$Ð%:Ô;ÜŒGÜÐNÔOÜÐHÔIØÙ1ØÐ&°nôFˆá˜>Ô*ÜÐ& ~Ð&6Ð7Ô8ÜÐ" ; -Ð/EÐFÕG÷;	Nð 	Nûô ò 	_Ü0:¸3Ä	Ô0JÑ'¨Ô,ÔPSÐTWÓPXˆGÜŒGÜÐSÐT[ÐS\Ð]×^Ñ^ûð	_ûô" ò ÜÐ"Ô#Ü˜‹oÐÜò ÝDÜ�‰Ð,¨cÔ2ÜŒÙ)¨#¼LÌÐQUÐWcÐeiÓIjÓ<kÔlò 	ˆDÜ�$�Kð	ä˜‹mÐûðúsc   Á$BH Ã'F/Ã>AH ÅF; Å-H Å>0H Æ/F8Æ4H Æ;	H	Ç;HÇ?H ÈH	È	H È'JÈ3AJÊJc                óD   — t        | xs t        «      j                  xs dS )Nzportal.nousresearch.com)r   r   rz   )rÚ  s    r.   rÐ  rÐ  P  s   € Ü�JÒ9Ô"9Ó:×CÑCÒ`ÐG`Ð`r0   )r,   r4   r$   rÂ  r  r   )r:   r
   r  r  )rD   r4   r=   r  rP   r
   r  r  )rZ   r   r[   r¿   r  r4   )rl   r4   r  r4   )rr   r   r  r4   )r|   rd   r  r  )rz   r  r  rÂ  )r‰   r  r  r  )r‘   r4   r  r  r  )r°   r
   r  zset[str])
r:   r
   r³   r
   r´   r
   rµ   r¿   r  r  )
r:   r
   r³   r
   r´   r
   rµ   r¿   r  rÂ  )rÉ   rd   r:   r
   r  r  )rÉ   rd   rÌ   r
   r  r  )r´   r
   rÒ   r
   r  r¿   r¡   )r:   r
   rÖ   r
   r  r  )rÉ   rd   rØ   r  r  r  )rÉ   rd   rÌ   r
   r=   r  r  r  )rÉ   rd   r  rd   )r  r   )r  rÀ   )r  rÀ   r  rÀ   )rÉ   rd   r  rÂ  )r   rd   r  rd   )rÉ   rd   r  r  )r  úOptional[Dict[str, Any]]r  )rÉ   rd   rr   r   r,   r4   r  rd   )rÉ   rd   rr   r   r,   r4   r  r  )r^  rd   rr   r   r,   r4   r  rÂ  )r  rÀ   r  rØ  )
r‰  r  rw   r4   r  r4   r
  r4   r  rd   )r‰  r  r^  rd   rÉ   rd   rw   r4   r  r4   r
  r4   r,   r4   r�  zCallable[[], None]r  rd   )
rÉ   rd   r{  rd   r
  r4   r  r  r  r  )r{  rd   r  r4   )r  rÀ   r›  r
   r  r  )r¦  r4   r  r®   )
r  r4   r³  r4   r  rÀ   r›  z
bool | strr  z	List[str])rÉ   rd   rµ   r¿   r  rÂ  )"rÌ   r4   r
  r4   r  r4   rw   r4   r  r4   r  r4   r³   r4   rØ   r  r´   r  r^   r  r`   r  r  rÀ   rj  úOptional[bool]rk  r  rs  rÂ  rt  ú/Optional[Callable[[Dict[str, Any], str], None]]r  rd   )
r   rd   r  rÀ   rs  rÂ  rt  rÚ  r  rd   )rÍ  rd   rÅ  r  r  )rÉ   rd   r  ztuple[str, str, str, str])r  rÀ   rj  rÙ  rk  r  rs  rÂ  rå  r  r  rd   )r  rd   )rÉ   rd   r<  rÂ  rZ  r4   r  rd   )rÉ   rd   r  rØ  )r  r4   )rq  r4   rr  zCallable[[str, int], bool]ro  r4   rú   zCallable[[], Dict[str, Any]]rl  z0Optional[Callable[[], Optional[Dict[str, Any]]]]r  rd   )rw   r  r  r  r  r  r³   r  ru  rÂ  r  rÀ   rj  rÂ  rk  r  rv  ú$Optional[Callable[[str, str], None]]r  rd   )rÀ  rd   r  r  )ru  rÂ  r  rÀ   rv  rÛ  r  rÂ  )rÀ  rd   r  r4   r  r  )rÁ  r
   r  r  )rˆ  r"   r  r  )rÚ  r  r  r4   )ƒr  Ú
__future__r   Úloggingr6   rN   rH   Ú	threadingrÁ   ræ  Ú
contextlibr   r   r   r   Úpathlibr   Útypingr	   r
   r   r   r   r   r   Úurllib.parser   Úhermes_cli.auth_codexr   Úhermes_cli.auth_constantsr   r   r   r   r   r   r   r   r   r   r   r   r   r   r    r!   rš   r"   Ú	getLoggerrL   r+   r/   r<   rR   r\   re   Ú__annotations__r¯   rf   ro   rt   r~   r   rƒ   r‹   r”   r�   rž   r¨   rÄ   rÇ   rÊ   rÎ   rÔ   r×   rß   ræ   ré   rì   rø   Úlocalr  rô   rÿ   r  r	  r  r  r!  r+  r  r9  rQ  rX  rd  r}  r�  r‘  r•  r—  rŸ  r©  r·  r¹  r¼  rg  rÏ  rÑ  rà  râ  r.  r+  rA  rR  rU  r\  r_  rd  rj  ri  rh  rk  rt  r�  r’  r™  r¶  r»  rÂ  rÖ  rÐ  rn  r0   r.   ú<module>rè     s1  ðòõ #ã Û Û Û 	Û Û Û ß /ß 'Ý ß P× PÑ PÝ !Ý /÷÷ ÷ ÷ ó ñ Ý.ð 
ˆ×	Ñ	Ð,Ó	-€àDÐ ð INõ YóYð >Bõ [ó^ð  tÀTØ °dÐUYñ0[Ð ˜nó [ñ ,5Ð6LÐ5MÓ+NÐ ˜.Ó Nó]óKó:ñ 1:Ø$à"ð;$ó 1%Ð ˜~ó %ó
Ló&ó4ó8	Tókóð !%¸Ø:ñ_Øð_Øð_Ø25ð_àð_à?Ló_ð( !%¸Ø:ñ\Øð\Øð\Ø25ð\àð\à?Có\óMð UYõ 1ó4ôQð <@ñdØðdØ+8ðdØDHódð* 37ÐUYñ	GØð	GØ,/ð	GØERð	GØ^bó	Gñ &/°Ð>TÐ/UÓ%VÐ "ó[ð .Ð Ø*˜9Ÿ?™?Ó,Ð ó0óð( Ø5Nó ó ðó$Að
FÐ óó&-ó Hó4+ó0Hó#óL(ð,ØðØ'0ðØ=@ðØEIóð  ?Cõ ðD"8Øð"8Ø.1ð"8Ø>Að"8ØRUð"8àó"8ðJØðØ)7ðØ@Nðàðà%(ðà9<ðàFIðð  ðð %3óð( )-ñ6Øð6Ø&4ð6ØEHð6à%ð6à15ó6ó,-óóð HLØñ*Øð*Ø),ð*Ø?Dð*àð*à"+ó*ó<6ð 3;ÐI[Ø!%À4Ø#È4Ø!¸dÐ_cØØGKñ)Øð)Ø&)ð)Ø69ð)ØLOð)àð)à,/ð)àCFð)ð ð)ð 4Að)ð ð	)ð <Ið	)ð
 ð)ð
 .<ð)ð
 P]ð)ð ð)ð Eð)ð Q_ó)ð4 6:ÐQVØGKñ*Ø	ð*Ø-2ð*ØJNð*àDð*àP^ó*ðZ OSõ Zó(Uó"?÷JN8ñ N8ðd !%ÀØ#¸5Ø(,ñUØðUØ0>ðUàðUà48ðUð &ðUð 2@óUð: !%ÀØ#¸5Ø(,ñ3CØð3CØ0>ð3Càð3Cà48ð3Cð &ð3Cð 2@ó3CólJó%)ðP,Øð,Ø)-ð,Ø7:ð,Ø?Mó,ó&óD
óð8 Ð Ø"Ð Ø Ð ó ð: FJñ*]Øð*]Ø&@ð*]ØMPð*]à)ð*]ð Cð*]ð O]ó*]ð\ )-ÐRVØ#¸DÐW[Ø!°EÐVZØ<@ñ	KØ%ðKØBOðKàðKà,9ðKàPTðKð ðKð .2ðKð GTðKð :ð	Kð FTó	Kó\*ð !¸4Ø<@ñUØðUØ38ðUà9ðUàEIóUð>DØðDØ47ðDØ<IóDóNó:	%ó?ôDar0   