§
    ‰eîif“  ã                  óú  — 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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 ddlmZmZmZmZ ddlmZ  e	j         e!¦  «        Z"dZ#d	Z$d
Z%dZ&dZ'e%› de&› d�Z(de'› �Z)ddl*Z+ e+j,        d¦  «        Z- e+j,        d¦  «        Z. e+j,        d¦  «        Z/ e+j,        d¦  «        Z0dZ1dZ2dZ3dZ4dZ5dZ6dZ7dZ8dZ9dZ:dZ;dZ< G d „ d!e=¦  «        Z>drd$„Z?drd%„Z@ ejA        ¦   «         ZBejC        e;fdsd(„¦   «         ZDi ZEd)eFd*<   dtd,„ZGdud.„ZHdvd0„ZIdvd1„ZJdvd2„ZKdud3„ZLe G d4„ d5¦  «        ¦   «         ZMe G d6„ d7¦  «        ¦   «         ZNdwd9„ZOdxd;„ZPdyd=„ZQdzdB„ZRdde9dCœd{dJ„ZSdde9dCœd|dL„ZTe9fd}dN„ZUi ZVdOeFdP<    ejW        ¦   «         ZXdQdRœd~dU„ZYdd€dY„ZZ G dZ„ d[ej[        j\        ¦  «        Z]d\Z^d]Z_e5fd�da„Z`d‚db„ZadQdce:dVddœdƒdh„Zbd„dk„Zcd…dl„ZddVdmœd†do„Zed‡dp„Zfdvdq„ZgdS )ˆux  Google OAuth PKCE flow for the Gemini (google-gemini-cli) inference provider.

This module implements Authorization Code + PKCE (S256) OAuth against Google's
accounts.google.com endpoints. The resulting access token is used by
``agent.gemini_cloudcode_adapter`` to talk to ``cloudcode-pa.googleapis.com``
(Google's Code Assist backend that powers the Gemini CLI's free and paid tiers).

Synthesized from:
- jenslys/opencode-gemini-auth (MIT) â€” overall flow shape, public OAuth creds, request format
- clawdbot/extensions/google/ â€” refresh-token rotation, VPC-SC handling reference
- PRs #10176 (@sliverp) and #10779 (@newarthur) â€” PKCE module structure, cross-process lock

Storage (``~/.hermes/auth/google_oauth.json``, chmod 0o600):

    {
      "refresh": "refreshToken|projectId|managedProjectId",
      "access": "...",
      "expires": 1744848000000,   // unix MILLIseconds
      "email": "user@example.com"
    }

The ``refresh`` field packs the refresh_token together with the resolved GCP
project IDs so subsequent sessions don't need to re-discover the project.
This matches opencode-gemini-auth's storage contract exactly.

The packed format stays parseable even if no project IDs are present â€” just
a bare refresh_token is treated as "packed with empty IDs".

Public client credentials
-------------------------
The client_id and client_secret below are Google's PUBLIC desktop OAuth client
for their own open-source gemini-cli. They are baked into every copy of the
gemini-cli npm package and are NOT confidential â€” desktop OAuth clients have
no secret-keeping requirement (PKCE provides the security). Shipping them here
is consistent with opencode-gemini-auth and the official Google gemini-cli.

Policy note: Google considers using this OAuth client with third-party software
a policy violation. Users see an upfront warning with ``confirm(default=False)``
before authorization begins.
é    )ÚannotationsN)Ú	dataclassÚfield)ÚPath)ÚAnyÚDictÚOptionalÚTuple©Úget_hermes_homeÚHERMES_GEMINI_CLIENT_IDÚHERMES_GEMINI_CLIENT_SECRETÚ681255809395Ú oo8ft2oprdrnp9e3aqf6av3hmdib135jz4uHgMPm-1o7Sk-geV6Cu5clXFsxlÚ-z.apps.googleusercontent.comzGOCSPX-zPOAUTH_CLIENT_ID\s*=\s*['\"]([0-9]+-[a-z0-9]+\.apps\.googleusercontent\.com)['\"]z;OAUTH_CLIENT_SECRET\s*=\s*['\"](GOCSPX-[A-Za-z0-9_-]+)['\"]z7([0-9]{8,}-[a-z0-9]{20,}\.apps\.googleusercontent\.com)z(GOCSPX-[A-Za-z0-9_-]{20,})z,https://accounts.google.com/o/oauth2/v2/authz#https://oauth2.googleapis.com/tokenz-https://www.googleapis.com/oauth2/v1/userinfozŽhttps://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profilei•  z	127.0.0.1z/oauth2callbacké<   g      4@i,  g      >@)ÚSSH_CONNECTIONÚ
SSH_CLIENTÚSSH_TTYÚHERMES_HEADLESSc                  ó*   ‡ — e Zd ZdZddœd
ˆ fd	„Zˆ xZS )ÚGoogleOAuthErrorz0Raised for any failure in the Google OAuth flow.Úgoogle_oauth_error©ÚcodeÚmessageÚstrr   ÚreturnÚNonec               óX   •— t          ¦   «                              |¦  «         || _        d S ©N)ÚsuperÚ__init__r   )Úselfr   r   Ú	__class__s      €ú4/home/div/.hermes/hermes-agent/agent/google_oauth.pyr#   zGoogleOAuthError.__init__“   s&   ø€ Ý‰Œ×Ò˜Ñ!Ô!Ð!ØˆŒ	ˆ	ˆ	ó    )r   r   r   r   r   r   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r#   Ú__classcell__)r%   s   @r&   r   r   �   sP   ø€ € € € € Ø:Ð:à4Hð ð ð ð ð ð ð ð ð ð ð ð r'   r   r   r   c                 ó*   — t          ¦   «         dz  dz  S )NÚauthzgoogle_oauth.jsonr   © r'   r&   Ú_credentials_pathr0   œ   s   € ÝÑÔ˜vÑ%Ð(;Ñ;Ð;r'   c                 óD   — t          ¦   «                              d¦  «        S )Nz
.json.lock)r0   Úwith_suffixr/   r'   r&   Ú
_lock_pathr3       s   € ÝÑÔ×*Ò*¨<Ñ8Ô8Ð8r'   Útimeout_secondsÚfloatc              #  ó´  K  — t          t          dd¦  «        }|dk    rF|dz   t          _        	 dV — t          xj        dz  c_        n# t          xj        dz  c_        w xY wdS t          ¦   «         }|j                             dd¬¦  «         t          j        t          |¦  «        t          j	        t          j
        z  d¦  «        }d}	 	 ddl}n# t          $ r d}Y nw xY w|�©t          j        ¦   «         t          d	t!          | ¦  «        ¦  «        z   }	 	 |                     ||j        |j        z  ¦  «         d}nO# t(          $ rA t          j        ¦   «         |k    rt+          d
|› d�¦  «        ‚t          j        d¦  «         Y nw xY wŒvn¹	 ddl}t          j        ¦   «         t          d	t!          | ¦  «        ¦  «        z   }	 	 |                     ||j        d¦  «         d}nO# t4          $ rA t          j        ¦   «         |k    rt+          d
|› d�¦  «        ‚t          j        d¦  «         Y nw xY wŒon# t          $ r d}Y nw xY wdt          _        dV — 	 |ru	 ddl}|                     ||j        ¦  «         nT# t          $ rG 	 ddl}	 |                     ||j        d¦  «         n# t4          $ r Y nw xY wn# t          $ r Y nw xY wY nw xY wt          j        |¦  «         dt          _        dS # t          j        |¦  «         dt          _        w xY w# 	 |ru	 ddl}|                     ||j        ¦  «         nT# t          $ rG 	 ddl}	 |                     ||j        d¦  «         n# t4          $ r Y nw xY wn# t          $ r Y nw xY wY nw xY wt          j        |¦  «         dt          _        n%# t          j        |¦  «         dt          _        w xY ww xY w)zNCross-process lock around the credentials file (fcntl POSIX / msvcrt Windows).Údepthr   é   NT©ÚparentsÚexist_oki€  Fg        z5Timed out acquiring Google OAuth credentials lock at ú.gš™™™™™©?)ÚgetattrÚ_lock_stater7   r3   ÚparentÚmkdirÚosÚopenr   ÚO_CREATÚO_RDWRÚfcntlÚImportErrorÚtimeÚ	monotonicÚmaxr5   ÚflockÚLOCK_EXÚLOCK_NBÚBlockingIOErrorÚTimeoutErrorÚsleepÚmsvcrtÚlockingÚLK_NBLCKÚOSErrorÚLOCK_UNÚLK_UNLCKÚclose)r4   r7   Úlock_file_pathÚfdÚacquiredrE   ÚdeadlinerP   s           r&   Ú_credentials_lockr[   §   sÝ  è è € õ •K ¨!Ñ,Ô,€EØˆq‚y€yØ! A™I�Ôð	#ØˆEˆEˆEåÐÔ Ñ"ÐÔÐø�KÐÔ Ñ"ÐÔÐÐÐÐØˆå‘\”\€NØÔ×Ò¨°tÐÑ<Ô<Ð<Ý	Œ•�^Ñ$Ô$¥b¤jµ2´9Ñ&<¸eÑ	DÔ	D€BØ€Hð;"ð	ØˆLˆLˆLˆLøÝð 	ð 	ð 	ØˆEˆEˆEð	øøøð ÐÝ”~Ñ'Ô'­#¨cµ5¸Ñ3IÔ3IÑ*JÔ*JÑJˆHð
%ð	%Ø—K’K  E¤M°E´MÑ$AÑBÔBÐBØ#�HØøÝ&ð %ð %ð %Ý”~Ñ'Ô'¨8Ò3Ð3Ý*ØeÐTbÐeÐeÐeñô ð õ ”J˜tÑ$Ô$Ð$Ð$Ð$ð%øøøð
%ð ð Ø���åœ>Ñ+Ô+­c°#µu¸_Ñ7MÔ7MÑ.NÔ.NÑN�ð
)ð	)ØŸš r¨6¬?¸AÑ>Ô>Ð>Ø#'˜ØøÝ"ð )ð )ð )Ýœ>Ñ+Ô+¨xÒ7Ð7Ý".Ø iÐXfÐ iÐ iÐ iñ#ô #ð õ œ
 4Ñ(Ô(Ð(Ð(Ð(ð)øøøð
)ð øõ ð  ð  ð  Ø���ð øøøð �ÔØˆˆˆð	"Øð ðØ �L�L�Là—K’K  E¤MÑ2Ô2Ð2Ð2øÝ"ð 	ð 	ð 	ðØ%˜˜˜ð!Ø"ŸNšN¨2¨v¬ÀÑBÔBÐBÐBøÝ&ð !ð !ð !Ø ˜Dð!øøøøøå&ð ð ð Ø˜ðøøøøøð	øøøõ ŒH�R‰LŒLˆLØ !�KÔÐÐøõ ŒH�R‰LŒLˆLØ !�KÔÐ!Ð!Ð!Ð!øð%	"Øð ðØ �L�L�Là—K’K  E¤MÑ2Ô2Ð2Ð2øÝ"ð 	ð 	ð 	ðØ%˜˜˜ð!Ø"ŸNšN¨2¨v¬ÀÑBÔBÐBÐBøÝ&ð !ð !ð !Ø ˜Dð!øøøøøå&ð ð ð Ø˜ðøøøøøð	øøøõ ŒH�R‰LŒLˆLØ !�KÔÐøõ ŒH�R‰LŒLˆLØ !�KÔÐ!Ð!Ð!Ð!Ð!Ð!Ð!Ð!sŸ  ¯A	 Á	A ÃC ÃL ÃC!ÃL Ã C!Ã!7L Ä%D? Ä>L Ä?AF
ÆL Æ	F
Æ
L Æ6H6 ÇG& Ç%H6 Ç&AH1È.H6 È0H1È1H6 È5L È6IÉL ÉIÉL ÉK2 ÉI< É;K2 É<
KÊJ:ÊJ)Ê(J:Ê)
J6Ê3J:Ê5J6Ê6J:Ê9KÊ:
KËKËKËKË
K2 ËKËK2 Ë2"LÌOÌN1ÌL<Ì;N1Ì<
NÍM:ÍM)Í(M:Í)
M6	Í3M:Í5M6	Í6M:Í9NÍ:
N	ÎNÎN	ÎNÎ
N1ÎNÎN1Î!OÎ1"OÏOúDict[str, str]Ú_scraped_creds_cacheúOptional[Path]c                 óÀ  — ddl } |                      d¦  «        }|sdS 	 t          |¦  «                             ¦   «         }n# t          $ r Y dS w xY wg }|j        }t          d¦  «        D ]b}|                     |¦  «         |dz                       ¦   «         r |                     |dz  dz  dz  ¦  «          n|j        |k    r n|j        }Œc|D ]Ž}|                     ¦   «         sŒ|dz  d	z  d
z  dz  |dz  d
z  dz  |d	z  d
z  dz  g}|D ]}|                     ¦   «         r|c c S Œ	 | 	                    d¦  «        D ]}	|	c c S Œx# t          t          f$ r Y Œ‹w xY wdS )a  Walk the user's gemini binary install to find its oauth2.js.

    Returns None if gemini isn't installed. Supports both the npm install
    (``node_modules/@google/gemini-cli-core/dist/**/code_assist/oauth2.js``)
    and the Homebrew ``bundle/`` layout.
    r   NÚgeminié   Únode_modulesz@googlezgemini-cli-coreÚdistÚsrcÚcode_assistz	oauth2.js)ÚshutilÚwhichr   ÚresolverS   r?   ÚrangeÚappendÚexistsÚrglobÚ
ValueError)
rf   r`   ÚrealÚsearch_dirsÚcurÚ_ÚrootÚ
candidatesÚcÚpaths
             r&   Ú_locate_gemini_cli_oauth_jsrv   ü   só  € ð €M€M€Mà�\Š\˜(Ñ#Ô#€FØð ØˆtðÝ�F‰|Œ|×#Ò#Ñ%Ô%ˆˆøÝð ð ð Øˆtˆtðøøøð !€KØ
Œ+€CÝ�1‰XŒXð ð ˆØ×Ò˜3ÑÔÐØ�.Ñ ×(Ò(Ñ*Ô*ð 	Ø×Ò˜s ^Ñ3°iÑ?ÐBSÑSÑTÔTÐTØˆEØŒ:˜ÒÐØˆEØŒjˆˆàð ð ˆØ�{Š{‰}Œ}ð 	Øð �6‰M˜EÑ! MÑ1°KÑ?Ø�6‰M˜MÑ)¨KÑ7Ø�5‰L˜=Ñ(¨;Ñ6ð
ˆ
ð
 ð 	ð 	ˆAØ�xŠx‰zŒzð Ø�����ðð	ØŸ
š
 ;Ñ/Ô/ð ð �Ø�����ðøå�Ð$ð 	ð 	ð 	ØˆHð	øøøð ˆ4s#   Ÿ!A Á
AÁAÄ)EÅEÅEúTuple[str, str]c                 ó:  — t                                d¦  «        r6t                                dd¦  «        t                                dd¦  «        fS t          ¦   «         } | €dt           d<   dS 	 |                      dd	¬
¦  «        }n># t          $ r1}t
                               d| |¦  «         dt           d<   Y d}~dS d}~ww xY wt                               |¦  «        pt                               |¦  «        }t                               |¦  «        pt                               |¦  «        }|r|                     d¦  «        nd}|r|                     d¦  «        nd}|t           d<   |t           d<   dt           d<   |rt
                               d| ¦  «         ||fS )zDExtract client_id + client_secret from the local gemini-cli install.ÚresolvedÚ	client_idÚ Úclient_secretNÚ1©r{   r{   úutf-8Úreplace)ÚencodingÚerrorsz"Failed to read oauth2.js at %s: %sr8   z#Scraped Gemini OAuth client from %s)r]   Úgetrv   Ú	read_textrS   ÚloggerÚdebugÚ_CLIENT_ID_PATTERNÚsearchÚ_CLIENT_ID_SHAPEÚ_CLIENT_SECRET_PATTERNÚ_CLIENT_SECRET_SHAPEÚgroupÚinfo)Úoauth_jsÚcontentÚexcÚ	cid_matchÚcs_matchrz   r|   s          r&   Ú_scrape_client_credentialsr“   0  s¦  € å×Ò 
Ñ+Ô+ð hÝ#×'Ò'¨°RÑ8Ô8Õ:N×:RÒ:RÐSbÐdfÑ:gÔ:gÐgÐgå*Ñ,Ô,€HØÐØ+.Õ˜ZÑ(ØˆvðØ×$Ò$¨g¸iÐ$ÑHÔHˆˆøÝð ð ð Ý�ŠÐ9¸8ÀSÑIÔIÐIØ+.Õ˜ZÑ(Øˆvˆvˆvˆvˆvøøøøðøøøõ #×)Ò)¨'Ñ2Ô2ÐVÕ6F×6MÒ6MÈgÑ6VÔ6V€IÝ%×,Ò,¨WÑ5Ô5Ð]Õ9M×9TÒ9TÐU\Ñ9]Ô9]€Hà&/Ð7�	—’ Ñ"Ô"Ð"°R€IØ)1Ð9�H—N’N 1Ñ%Ô%Ð%°r€Mà(1Õ˜Ñ%Ø,9Õ˜Ñ)Ø'*Õ˜Ñ$àð EÝ�ŠÐ9¸8ÑDÔDÐDà�mÐ#Ð#s   Á.B Â
CÂ&B<Â<Cr   c                 ó¦   — t          j        t          ¦  «        pd                     ¦   «         } | r| S t          rt          S t          ¦   «         \  }}|S ©Nr{   )rA   ÚgetenvÚENV_CLIENT_IDÚstripÚ_DEFAULT_CLIENT_IDr“   )Úenv_valÚscrapedrq   s      r&   Ú_get_client_idrœ   R  sQ   € ÝŒy�Ñ'Ô'Ð-¨2×4Ò4Ñ6Ô6€GØð ØˆÝð "Ý!Ð!Ý+Ñ-Ô-�J€GˆQØ€Nr'   c                 ó¦   — t          j        t          ¦  «        pd                     ¦   «         } | r| S t          rt          S t          ¦   «         \  }}|S r•   )rA   r–   ÚENV_CLIENT_SECRETr˜   Ú_DEFAULT_CLIENT_SECRETr“   )rš   rq   r›   s      r&   Ú_get_client_secretr    \  sR   € ÝŒyÕ*Ñ+Ô+Ð1¨r×8Ò8Ñ:Ô:€GØð ØˆÝð &Ý%Ð%Ý+Ñ-Ô-�J€A€wØ€Nr'   c                 óH   — t          ¦   «         } | st          dd¬¦  «        ‚| S )Na®  Google OAuth client ID is not available.
Hermes looks for a locally installed gemini-cli to source the OAuth client. Either:
  1. Install it: npm install -g @google/gemini-cli  (or brew install gemini-cli)
  2. Set HERMES_GEMINI_CLIENT_ID and HERMES_GEMINI_CLIENT_SECRET in ~/.hermes/.env

Register a Desktop OAuth client at:
  https://console.cloud.google.com/apis/credentials
(enable the Generative Language API on the project).Úgoogle_oauth_client_id_missingr   )rœ   r   )Úcids    r&   Ú_require_client_idr¤   f  s>   € Ý
Ñ
Ô
€CØð 
ÝðCð 2ð
ñ 
ô 
ð 	
ð €Jr'   c                 ó  — t          j        d¦  «        } t          j        |                      d¦  «        ¦  «                             ¦   «         }t          j        |¦  «                             d¦  «         	                    d¦  «        }| |fS )z1Generate a (verifier, challenge) pair using S256.é@   Úasciió   =)
ÚsecretsÚtoken_urlsafeÚhashlibÚsha256ÚencodeÚdigestÚbase64Úurlsafe_b64encodeÚrstripÚdecode)Úverifierr®   Ú	challenges      r&   Ú_generate_pkce_pairrµ   |  sp   € åÔ$ RÑ(Ô(€HÝŒ^˜HŸOšO¨GÑ4Ô4Ñ5Ô5×<Ò<Ñ>Ô>€FÝÔ(¨Ñ0Ô0×7Ò7¸Ñ=Ô=×DÒDÀWÑMÔM€IØ�YÐÐr'   c                  óV   — e Zd ZU ded<   dZded<   dZded<   edd	„¦   «         Zdd
„ZdS )ÚRefreshPartsr   Úrefresh_tokenr{   Ú
project_idÚmanaged_project_idÚpackedr   ú'RefreshParts'c                óÞ   — |s | d¬¦  «        S |                      dd¦  «        } | |d         t          |¦  «        dk    r|d         ndt          |¦  «        dk    r|d         nd¬¦  «        S )Nr{   )r¸   Ú|é   r   r8   ©r¸   r¹   rº   )ÚsplitÚlen)Úclsr»   Úpartss      r&   ÚparsezRefreshParts.parseŽ  s‚   € àð 	)Ø�3 RÐ(Ñ(Ô(Ð(Ø—’˜S !Ñ$Ô$ˆØˆsØ œ(Ý#& u¡:¤:°¢> >�u˜Q”x�x°rÝ+.¨u©:¬:¸ª>¨>˜u Qœx˜x¸rð
ñ 
ô 
ð 	
r'   c                óp   — | j         sdS | j        s| j        s| j         S | j         › d| j        › d| j        › �S )Nr{   r¾   rÀ   ©r$   s    r&   ÚformatzRefreshParts.format™  sS   € ØÔ!ð 	Ø�2ØŒð 	& tÔ'>ð 	&ØÔ%Ð%ØÔ$ÐRÐR t¤ÐRÐR¸Ô9PÐRÐRÐRr'   N)r»   r   r   r¼   ©r   r   )	r(   r)   r*   Ú__annotations__r¹   rº   ÚclassmethodrÅ   rÈ   r/   r'   r&   r·   r·   ˆ  s}   € € € € € € àÐÐÑØ€JÐÐÐÑØ ÐÐ Ð Ð Ñ àð
ð 
ð 
ñ „[ð
ðSð Sð Sð Sð Sð Sr'   r·   c                  óŒ   — e Zd ZU ded<   ded<   ded<   dZded<   dZded<   dZded	<   dd„Zedd„¦   «         Z	dd„Z
efdd„ZdS )ÚGoogleCredentialsr   Úaccess_tokenr¸   ÚintÚ
expires_msr{   Úemailr¹   rº   r   úDict[str, Any]c                óª   — t          | j        | j        | j        ¬¦  «                             ¦   «         | j        t          | j        ¦  «        | j        dœS )NrÀ   )ÚrefreshÚaccessÚexpiresrÑ   )	r·   r¸   r¹   rº   rÈ   rÎ   rÏ   rÐ   rÑ   rÇ   s    r&   Úto_dictzGoogleCredentials.to_dict®  sW   € å#Ø"Ô0Øœ?Ø#'Ô#:ðñ ô ÷ Šf‰hŒhØÔ'Ý˜4œ?Ñ+Ô+Ø”Zð	
ð 	
ð 		
r'   Údataú'GoogleCredentials'c                ó’  — t          |                     dd¦  «        pd¦  «        }t                               |¦  «        } | t          |                     dd¦  «        pd¦  «        |j        t          |                     dd¦  «        pd¦  «        t          |                     dd¦  «        pd¦  «        |j        |j        ¬¦  «        S )NrÔ   r{   rÕ   rÖ   r   rÑ   ©rÎ   r¸   rÐ   rÑ   r¹   rº   )r   rƒ   r·   rÅ   r¸   rÏ   r¹   rº   )rÃ   rØ   Úrefresh_packedrÄ   s       r&   Ú	from_dictzGoogleCredentials.from_dictº  sº   € å˜TŸXšX i°Ñ4Ô4Ð:¸Ñ;Ô;ˆÝ×"Ò" >Ñ2Ô2ˆØˆsÝ˜TŸXšX h°Ñ3Ô3Ð9°rÑ:Ô:ØÔ-Ý˜4Ÿ8š8 I¨qÑ1Ô1Ð6°QÑ7Ô7Ý�d—h’h˜w¨Ñ+Ô+Ð1¨rÑ2Ô2ØÔ'Ø$Ô7ð
ñ 
ô 
ð 	
r'   r5   c                ó   — | j         dz  S )Ng     @�@)rÐ   rÇ   s    r&   Úexpires_unix_secondsz&GoogleCredentials.expires_unix_secondsÇ  s   € ØŒ Ñ'Ð'r'   Úskew_secondsÚboolc                ó‚   — | j         r| j        sdS t          j        ¦   «         t          d|¦  «        z   dz  | j        k    S )NTr   éè  )rÎ   rÐ   rG   rI   )r$   rà   s     r&   Úaccess_token_expiredz&GoogleCredentials.access_token_expiredÊ  sB   € ØÔ ð 	¨¬ð 	Ø�4Ý”	‘”�c ! \Ñ2Ô2Ñ2°dÑ:¸d¼oÒMÐMr'   N©r   rÒ   )rØ   rÒ   r   rÙ   )r   r5   )rà   rÏ   r   rá   )r(   r)   r*   rÊ   rÑ   r¹   rº   r×   rË   rÝ   rß   ÚREFRESH_SKEW_SECONDSrä   r/   r'   r&   rÍ   rÍ   ¥  sÕ   € € € € € € àÐÐÑØÐÐÑØ€O€O�OØ€E€O€O€O�OØ€JÐÐÐÑØ ÐÐ Ð Ð Ñ ð

ð 

ð 

ð 

ð ð

ð 

ð 

ñ „[ð

ð(ð (ð (ð (ð 8Lð Nð Nð Nð Nð Nð Nð Nr'   rÍ   úOptional[GoogleCredentials]c                 óò  — t          ¦   «         } |                      ¦   «         sdS 	 t          ¦   «         5  |                      d¬¦  «        }ddd¦  «         n# 1 swxY w Y   t	          j        |¦  «        }nF# t          j        t          t          f$ r'}t           
                    d| |¦  «         Y d}~dS d}~ww xY wt          |t          ¦  «        sdS t                               |¦  «        }|j        sdS |S )z?Load credentials from disk. Returns None if missing or corrupt.Nr   ©r�   z1Failed to read Google OAuth credentials at %s: %s)r0   rk   r[   r„   ÚjsonÚloadsÚJSONDecodeErrorrS   ÚIOErrorr…   ÚwarningÚ
isinstanceÚdictrÍ   rÝ   rÎ   )ru   ÚrawrØ   r�   Úcredss        r&   Úload_credentialsró   Ô  s9  € åÑÔ€DØ�;Š;‰=Œ=ð ØˆtðÝÑ Ô ð 	3ð 	3Ø—.’.¨'�.Ñ2Ô2ˆCð	3ð 	3ð 	3ñ 	3ô 	3ð 	3ð 	3ð 	3ð 	3ð 	3ð 	3øøøð 	3ð 	3ð 	3ð 	3åŒz˜#‰ŒˆˆøÝÔ ¥'­7Ð3ð ð ð Ý�ŠÐJÈDÐRUÑVÔVÐVØˆtˆtˆtˆtˆtøøøøðøøøõ �d�DÑ!Ô!ð ØˆtÝ×'Ò'¨Ñ-Ô-€EØÔð ØˆtØ€Ls:   ¦A7 ´AÁA7 ÁAÁA7 ÁAÁA7 Á7B:ÂB5Â5B:rò   c           	     óâ  — t          ¦   «         }|j                             dd¬¦  «         t          j        |                      ¦   «         dd¬¦  «        dz   }t          ¦   «         5  |                     dt          j	        ¦   «         › dt          j        d¦  «        › �¦  «        }	 t          |d	d
¬¦  «        5 }|                     |¦  «         |                     ¦   «          t          j        |                     ¦   «         ¦  «         ddd¦  «         n# 1 swxY w Y   t          j        |t$          j        t$          j        z  ¦  «         t          j        ||¦  «         	 |                     ¦   «         r|                     ¦   «          nO# t0          $ r Y nCw xY w# 	 |                     ¦   «         r|                     ¦   «          w w # t0          $ r Y w w xY wxY wddd¦  «         n# 1 swxY w Y   |S )z6Atomically write creds to disk with 0o600 permissions.Tr9   r¿   )ÚindentÚ	sort_keysÚ
z.tmp.r<   é   Úwr   ré   N)r0   r?   r@   rê   Údumpsr×   r[   r2   rA   Úgetpidr©   Ú	token_hexrB   ÚwriteÚflushÚfsyncÚfilenoÚchmodÚstatÚS_IRUSRÚS_IWUSRr€   rk   ÚunlinkrS   )rò   ru   ÚpayloadÚtmp_pathÚfhs        r&   Úsave_credentialsr	  è  sU  € åÑÔ€DØ„K×Ò˜d¨TÐÑ2Ô2Ð2ÝŒj˜Ÿš™œ°¸dÐCÑCÔCÀdÑJ€Gå	Ñ	Ô	ð ð Ø×#Ò#Ð$P­B¬I©K¬KÐ$PÐ$P½'Ô:KÈAÑ:NÔ:NÐ$PÐ$PÑQÔQˆð	Ý�h ¨gÐ6Ñ6Ô6ð &¸"Ø—’˜Ñ!Ô!Ð!Ø—’‘
”
�
Ý”˜Ÿš™œÑ%Ô%Ð%ð&ð &ð &ñ &ô &ð &ð &ð &ð &ð &ð &øøøð &ð &ð &ð &õ ŒH�X�tœ|­d¬lÑ:Ñ;Ô;Ð;ÝŒJ�x Ñ&Ô&Ð&ðØ—?’?Ñ$Ô$ð &Ø—O’OÑ%Ô%Ð%øøÝð ð ð Ø�ðøøøøðØ—?’?Ñ$Ô$ð &Ø—O’OÑ%Ô%Ð%Ð%ð&øåð ð ð Ø�ðøøøøøøðð ð ñ ô ð ð ð ð ð ð øøøð ð ð ð ð €Ks’   Á%?G$Â%FÂ7ADÄFÄD	ÄFÄD	ÄAFÅ (F	ÆG$Æ	
FÆG$ÆFÆG$ÆGÆ(GÇGÇ
G	ÇGÇG	ÇGÇG$Ç$G(Ç+G(r   c                 ó  — t          ¦   «         } t          ¦   «         5  	 |                      ¦   «          n># t          $ r Y n2t          $ r&}t
                               d| |¦  «         Y d}~nd}~ww xY wddd¦  «         dS # 1 swxY w Y   dS )z"Remove the creds file. Idempotent.z3Failed to remove Google OAuth credentials at %s: %sN)r0   r[   r  ÚFileNotFoundErrorrS   r…   rî   )ru   r�   s     r&   Úclear_credentialsr     s  € åÑÔ€DÝ	Ñ	Ô	ð ]ð ]ð	]Ø�KŠK‰MŒMˆMˆMøÝ ð 	ð 	ð 	ØˆDÝð 	]ð 	]ð 	]Ý�NŠNÐPÐRVÐX[Ñ\Ô\Ð\Ð\Ð\Ð\Ð\Ð\øøøøð	]øøøð]ð ]ð ]ñ ]ô ]ð ]ð ]ð ]ð ]ð ]ð ]ð ]øøøð ]ð ]ð ]ð ]ð ]ð ]sB   �A?Ÿ4³A?´
A/¾A?Á 	A/Á	A*Á%A?Á*A/Á/A?Á?BÂBÚurlrØ   ÚtimeoutrÒ   c                ó*  — t           j                             |¦  «                             d¦  «        }t           j                             | |ddddœ¬¦  «        }	 t           j                             ||¬¦  «        5 }|                     ¦   «                              dd	¬
¦  «        }t          j
        |¦  «        cddd¦  «         S # 1 swxY w Y   dS # t           j        j        $ r€}d}	 |                     ¦   «                              dd	¬
¦  «        }n# t          $ r Y nw xY wd}	d|                     ¦   «         v rd}	t          d|j        › d|p|j        › �|	¬¦  «        |‚d}~wt           j        j        $ r}t          d|› �d¬¦  «        |‚d}~ww xY w)z;POST x-www-form-urlencoded and return parsed JSON response.r§   ÚPOSTz!application/x-www-form-urlencodedzapplication/json)úContent-TypeÚAccept)rØ   ÚmethodÚheaders©r  r   r€   ©r‚   Nr{   Úgoogle_oauth_token_http_errorÚinvalid_grantÚgoogle_oauth_invalid_grantz*Google OAuth token endpoint returned HTTP z: r   z#Google OAuth token request failed: Ú google_oauth_token_network_error)ÚurllibrÅ   Ú	urlencoder­   ÚrequestÚRequestÚurlopenÚreadr²   rê   rë   ÚerrorÚ	HTTPErrorÚ	ExceptionÚlowerr   r   ÚreasonÚURLError)
r  rØ   r  Úbodyr  Úresponserñ   r�   Údetailr   s
             r&   Ú
_post_formr*    s  € åŒ<×!Ò! $Ñ'Ô'×.Ò.¨wÑ7Ô7€DÝŒn×$Ò$ØØØà?Ø(ð
ð 
ð	 %ñ ô €GðÝŒ^×#Ò# G°WÐ#Ñ=Ô=ð 	#ÀØ—-’-‘/”/×(Ò(¨¸Ð(ÑCÔCˆCÝ”:˜c‘?”?ð	#ð 	#ð 	#ð 	#ñ 	#ô 	#ð 	#ð 	#ð 	#ð 	#ð 	#ð 	#øøøð 	#ð 	#ð 	#ð 	#ð 	#ð 	#øõ Œ<Ô!ð ð ð Øˆð	Ø—X’X‘Z”Z×&Ò& w°yÐ&ÑAÔAˆFˆFøÝð 	ð 	ð 	ØˆDð	øøøð /ˆØ˜fŸlšl™nœnÐ,Ð,Ø/ˆDÝØ[¸¼Ð[Ð[ÀVÐEYÈsÌzÐ[Ð[Øð
ñ 
ô 
ð ð	øøøøõ Œ<Ô ð ð ð ÝØ7°#Ð7Ð7Ø3ð
ñ 
ô 
ð ð	øøøøðøøøsm   Á!C Á;=CÂ8C ÃC	Ã	C ÃC	ÃC ÃFÃ&E!Ã))DÄE!Ä
D ÄE!ÄD Ä AE!Å!FÅ8FÆF)rz   r|   r  r   r³   Úredirect_urirz   úOptional[str]r|   c               ó”   — |�|nt          ¦   «         }|�|nt          ¦   «         }d| |||dœ}|r||d<   t          t          ||¦  «        S )z8Exchange authorization code for access + refresh tokens.NÚauthorization_code)Ú
grant_typer   Úcode_verifierrz   r+  r|   )rœ   r    r*  ÚTOKEN_ENDPOINT)	r   r³   r+  rz   r|   r  r£   ÚcsecretrØ   s	            r&   Úexchange_coder3  5  sn   € ð !Ð,ˆ)ˆ)µ.Ñ2BÔ2B€CØ,Ð8ˆmˆmÕ>PÑ>RÔ>R€Gà*ØØ!ØØ$ðð €Dð ð (Ø 'ˆˆ_ÑÝ•n d¨GÑ4Ô4Ð4r'   r¸   c               ó¶   — | st          dd¬¦  «        ‚|�|nt          ¦   «         }|�|nt          ¦   «         }d| |dœ}|r||d<   t          t          ||¦  «        S )zRefresh the access token.z;Cannot refresh: refresh_token is empty. Re-run OAuth login.Ú"google_oauth_refresh_token_missingr   Nr¸   )r/  r¸   rz   r|   )r   rœ   r    r*  r1  )r¸   rz   r|   r  r£   r2  rØ   s          r&   Úrefresh_access_tokenr6  M  s�   € ð ð 
ÝØIØ5ð
ñ 
ô 
ð 	
ð !Ð,ˆ)ˆ)µ.Ñ2BÔ2B€CØ,Ð8ˆmˆmÕ>PÑ>RÔ>R€Gà%Ø&Øðð €Dð
 ð (Ø 'ˆˆ_ÑÝ•n d¨GÑ4Ô4Ð4r'   rÎ   c                óü  — 	 t           j                             t          dz   dd| › �i¬¦  «        }t           j                             ||¬¦  «        5 }|                     ¦   «                              dd¬¦  «        }d	d	d	¦  «         n# 1 swxY w Y   t          j        |¦  «        }t          | 
                    d
d¦  «        pd¦  «        S # t          $ r&}t                               d|¦  «         Y d	}~dS d	}~ww xY w)zEBest-effort userinfo fetch for display. Failures return empty string.z	?alt=jsonÚAuthorizationzBearer )r  r  r   r€   r  NrÑ   r{   z%Userinfo fetch failed (non-fatal): %s)r  r  r  ÚUSERINFO_ENDPOINTr  r   r²   rê   rë   r   rƒ   r#  r…   r†   )rÎ   r  r  r(  rñ   rØ   r�   s          r&   Ú_fetch_user_emailr:  f  sJ  € ðÝ”.×(Ò(Ý Ñ+Ø$Ð&>°Ð&>Ð&>Ð?ð )ñ 
ô 
ˆõ Œ^×#Ò# G°WÐ#Ñ=Ô=ð 	DÀØ—-’-‘/”/×(Ò(¨¸Ð(ÑCÔCˆCð	Dð 	Dð 	Dñ 	Dô 	Dð 	Dð 	Dð 	Dð 	Dð 	Dð 	Døøøð 	Dð 	Dð 	Dð 	DåŒz˜#‰ŒˆÝ�4—8’8˜G RÑ(Ô(Ð.¨BÑ/Ô/Ð/øÝð ð ð Ý�ŠÐ<¸cÑBÔBÐBØˆrˆrˆrˆrˆrøøøøðøøøs<   ‚AC Á*BÁ;C ÂBÂC ÂBÂ;C Ã
C;ÃC6Ã6C;zDict[str, threading.Event]Ú_refresh_inflightF)Úforce_refreshr<  rá   c                ó  — t          ¦   «         }|€t          dd¬¦  «        ‚| s|                     ¦   «         s|j        S |j        }t
          5  t                               |¦  «        }|€ t          j	        ¦   «         }|t          |<   d}nd}ddd¦  «         n# 1 swxY w Y   |sF| 
                    t          ¬¦  «         t          ¦   «         }|�|                     ¦   «         s|j        S 	 	 t          |¦  «        }nS# t          $ rF}|j        dk    r5t                               d	t!          ¦   «         ¦  «         t#          ¦   «          ‚ d}~ww xY wt%          |                     d
d¦  «        pd¦  «                             ¦   «         }|st          dd¬¦  «        ‚t%          |                     dd¦  «        pd¦  «                             ¦   «         p|j        }	t)          |                     dd¦  «        pd¦  «        }
||_        |	|_        t)          t+          j        ¦   «         t-          d|
¦  «        z   dz  ¦  «        |_        t1          |¦  «         |j        |rOt
          5  t                               |d¦  «         ddd¦  «         n# 1 swxY w Y   |                     ¦   «          S S # |rOt
          5  t                               |d¦  «         ddd¦  «         n# 1 swxY w Y   |                     ¦   «          w w xY w)a!  Load creds, refreshing if near expiry, and return a valid bearer token.

    Dedupes concurrent refreshes by refresh_token. On ``invalid_grant``, the
    credential file is wiped and a ``google_oauth_invalid_grant`` error is raised
    (caller is expected to trigger a re-login flow).
    NzYNo Google OAuth credentials found. Run `hermes login --provider google-gemini-cli` first.Úgoogle_oauth_not_logged_inr   TFr  r  uh   Google OAuth refresh token invalid (revoked/expired). Clearing credentials at %s â€” user must re-login.rÎ   r{   z1Refresh response did not include an access_token.Úgoogle_oauth_refresh_emptyr¸   Ú
expires_inr   r   rã   )ró   r   rä   rÎ   r¸   Ú_refresh_inflight_lockr;  rƒ   Ú	threadingÚEventÚwaitÚLOCK_TIMEOUT_SECONDSr6  r   r…   rî   r0   r  r   r˜   rÏ   rG   rI   rÐ   r	  ÚpopÚset)r<  rò   ÚrtÚeventÚownerÚfreshÚrespr�   Ú
new_accessÚnew_refreshr@  s              r&   Úget_valid_access_tokenrO  ~  sÒ  € õ ÑÔ€EØ€}ÝØgØ-ð
ñ 
ô 
ð 	
ð
 ð " ×!;Ò!;Ñ!=Ô!=ð "ØÔ!Ð!ð 
Ô	€BÝ	ð ð Ý!×%Ò% bÑ)Ô)ˆØˆ=Ý”OÑ%Ô%ˆEØ$)Õ˜bÑ!ØˆEˆEàˆEðð ð ñ ô ð ð ð ð ð ð øøøð ð ð ð ð ð &à�
Š
Õ/ˆ
Ñ0Ô0Ð0Ý Ñ"Ô"ˆØÐ U×%?Ò%?Ñ%AÔ%AÐØÔ%Ð%ð ð
	Ý'¨Ñ+Ô+ˆDˆDøÝð 	ð 	ð 	ØŒxÐ7Ò7Ð7Ý—’ðIå%Ñ'Ô'ñô ð õ
 "Ñ#Ô#Ð#Øøøøøð	øøøõ ˜Ÿš .°"Ñ5Ô5Ð;¸Ñ<Ô<×BÒBÑDÔDˆ
Øð 	Ý"ØCØ1ðñ ô ð õ
 ˜$Ÿ(š( ?°BÑ7Ô7Ð=¸2Ñ>Ô>×DÒDÑFÔFÐ]È%ÔJ]ˆÝ˜Ÿš ,°Ñ2Ô2Ð7°aÑ8Ô8ˆ
à'ˆÔØ)ˆÔÝ¥¤	¡¤­c°"°jÑ.AÔ.AÑ AÀTÑIÑJÔJˆÔÝ˜ÑÔÐØÔ!àð 	Ý'ð 0ð 0Ý!×%Ò% b¨$Ñ/Ô/Ð/ð0ð 0ð 0ñ 0ô 0ð 0ð 0ð 0ð 0ð 0ð 0øøøð 0ð 0ð 0ð 0à�IŠI‰KŒKˆKˆKð	øˆ5ð 	Ý'ð 0ð 0Ý!×%Ò% b¨$Ñ/Ô/Ð/ð0ð 0ð 0ñ 0ô 0ð 0ð 0ð 0ð 0ð 0ð 0øøøð 0ð 0ð 0ð 0à�IŠI‰KŒKˆKˆKð	øøøst   Á?BÂBÂBÃ-C= Ã<J+ Ã=
EÄAEÅEÅDJ+ É"J
Ê
J	ÊJ	Ê+
K>Ê5KËK>ËK!Ë!K>Ë$K!Ë%K>r{   r¹   rº   c                ól   — t          ¦   «         }|€dS | r| |_        |r||_        t          |¦  «         dS )zFPersist resolved/discovered project IDs back into the credential file.N)ró   r¹   rº   r	  )r¹   rº   rò   s      r&   Úupdate_project_idsrQ  É  sK   € åÑÔ€EØ€}ØˆØð &Ø%ˆÔØð 6Ø#5ˆÔ Ý�UÑÔÐÐÐr'   c                  ó`   — e Zd ZU dZded<   dZded<   dZded<   dZded	<   dd„Zdd„Z	dd„Z
dS )Ú_OAuthCallbackHandlerr{   r   Úexpected_stateNr,  Úcaptured_codeÚcaptured_errorzOptional[threading.Event]ÚreadyrÈ   Úargsr   r   r   c                ó0   — t          j        d|z   g|¢R Ž  d S )NzOAuth callback: )r…   r†   )r$   rÈ   rX  s      r&   Úlog_messagez!_OAuthCallbackHandler.log_messageß  s&   € ÝŒÐ'¨&Ñ0Ð8°4Ð8Ð8Ð8Ð8Ð8Ð8r'   c                ó  — t           j                             | j        ¦  «        }|j        t          k    r+|                      d¦  «         |                      ¦   «          d S t           j                             |j        ¦  «        }| 	                    d¦  «        pdgd         }| 	                    d¦  «        pdgd         }| 	                    d¦  «        pdgd         }|t          | ¦  «        j        k    rEdt          | ¦  «        _        |                      dt                               d	¬
¦  «        ¦  «         �n	|r’|t          | ¦  «        _        t!          |¦  «                             dd¦  «                             dd¦  «                             dd¦  «        }|                      dt                               d|› �¬
¦  «        ¦  «         nu|r0|t          | ¦  «        _        |                      dt&          ¦  «         nCdt          | ¦  «        _        |                      dt                               d¬
¦  «        ¦  «         t          | ¦  «        j        �(t          | ¦  «        j                             ¦   «          d S d S )Ni”  Ústater{   r   r!  r   Ústate_mismatchi�  u'   State mismatch â€” aborting for safety.)r   ú&z&amp;Ú<z&lt;Ú>z&gt;zAuthorization denied: éÈ   Úno_codez(Callback received no authorization code.)r  rÅ   Úurlparseru   ÚCALLBACK_PATHÚsend_responseÚend_headersÚparse_qsÚqueryrƒ   ÚtyperT  rV  Ú_respond_htmlÚ_ERROR_PAGErÈ   r   r€   rU  Ú_SUCCESS_PAGErW  rG  )r$   ÚparsedÚparamsr\  r!  r   Úsafe_errs          r&   Údo_GETz_OAuthCallbackHandler.do_GETâ  s4  € Ý”×&Ò& t¤yÑ1Ô1ˆØŒ;�-Ò'Ð'Ø×Ò˜sÑ#Ô#Ð#Ø×ÒÑÔÐØˆFå”×&Ò& v¤|Ñ4Ô4ˆØ—’˜GÑ$Ô$Ð,¨¨¨aÔ0ˆØ—’˜GÑ$Ô$Ð,¨¨¨aÔ0ˆØ—
’
˜6Ñ"Ô"Ð* r d¨AÔ.ˆà•D˜‘J”JÔ-Ò-Ð-Ø(8�D�‰JŒJÔ%Ø×Ò˜s¥K×$6Ò$6Ð?hÐ$6Ñ$iÔ$iÑjÔjÐjÑjØð 	lØ(-�D�‰JŒJÔ%õ �E‘
”
ß’˜˜gÑ&Ô&ß’˜˜fÑ%Ô%ß’˜˜fÑ%Ô%ð	 ð ×Ò˜s¥K×$6Ò$6Ð?bÐX`Ð?bÐ?bÐ$6Ñ$cÔ$cÑdÔdÐdÐdØð 	lØ'+�D�‰JŒJÔ$Ø×Ò˜s¥MÑ2Ô2Ð2Ð2à(1�D�‰JŒJÔ%Ø×Ò˜s¥K×$6Ò$6Ð?iÐ$6Ñ$jÔ$jÑkÔkÐkå�‰:Œ:ÔÐ'Ý�‰JŒJÔ× Ò Ñ"Ô"Ð"Ð"Ð"ð (Ð'r'   ÚstatusrÏ   r'  c                óB  — |                      d¦  «        }|                      |¦  «         |                      dd¦  «         |                      dt          t	          |¦  «        ¦  «        ¦  «         |                      ¦   «          | j                             |¦  «         d S )Nr   r  ztext/html; charset=utf-8zContent-Length)r­   re  Úsend_headerr   rÂ   rf  Úwfilerý   )r$   rq  r'  r  s       r&   rj  z#_OAuthCallbackHandler._respond_html  s‘   € Ø—+’+˜gÑ&Ô&ˆØ×Ò˜6Ñ"Ô"Ð"Ø×Ò˜Ð)CÑDÔDÐDØ×ÒÐ)­3­s°7©|¬|Ñ+<Ô+<Ñ=Ô=Ð=Ø×ÒÑÔÐØŒ
×Ò˜Ñ!Ô!Ð!Ð!Ð!r'   )rÈ   r   rX  r   r   r   ©r   r   )rq  rÏ   r'  r   r   r   )r(   r)   r*   rT  rÊ   rU  rV  rW  rZ  rp  rj  r/   r'   r&   rS  rS  Ù  s—   € € € € € € Ø€NÐÐÐÑØ#'€MÐ'Ð'Ð'Ñ'Ø$(€NÐ(Ð(Ð(Ñ(Ø'+€EÐ+Ð+Ð+Ñ+ð9ð 9ð 9ð 9ð!#ð !#ð !#ð !#ðF"ð "ð "ð "ð "ð "r'   rS  uz  <!doctype html>
<html><head><meta charset="utf-8"><title>Hermes â€” signed in</title>
<style>
body { font: 16px/1.5 system-ui, sans-serif; margin: 10vh auto; max-width: 32rem; text-align: center; color: #222; }
h1 { color: #1a7f37; } p { color: #555; }
</style></head>
<body><h1>Signed in to Google.</h1>
<p>You can close this tab and return to your terminal.</p></body></html>
u­  <!doctype html>
<html><head><meta charset="utf-8"><title>Hermes â€” sign-in failed</title>
<style>
body {{ font: 16px/1.5 system-ui, sans-serif; margin: 10vh auto; max-width: 32rem; text-align: center; color: #222; }}
h1 {{ color: #b42318; }} p {{ color: #555; }}
</style></head>
<body><h1>Sign-in failed</h1><p>{message}</p>
<p>Return to your terminal â€” Hermes will walk you through a manual paste fallback.</p></body></html>
Úpreferred_portrÏ   ú"Tuple[http.server.HTTPServer, int]c                ó@  — 	 t           j                             t          | ft          ¦  «        }|| fS # t
          $ r&}t                               d| |¦  «         Y d }~nd }~ww xY wt           j                             t          dft          ¦  «        }||j        d         fS )NzLPreferred OAuth callback port %d unavailable (%s); requesting ephemeral portr   r8   )	ÚhttpÚserverÚ
HTTPServerÚREDIRECT_HOSTrS  rS   r…   r�   Úserver_address)rv  rz  r�   s      r&   Ú_bind_callback_serverr~  #  s·   € ð
Ý”×'Ò'­¸Ð(GÕI^Ñ_Ô_ˆØ�~Ð%Ð%øÝð 
ð 
ð 
Ý�ŠØZØ˜Cñ	
ô 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
øøøøð
øøøõ
 Œ[×#Ò#¥]°AÐ$6Õ8MÑNÔN€FØ�6Ô(¨Ô+Ð+Ð+s   ‚/2 ²
A"¼AÁA"c                 ó>   — t          d„ t          D ¦   «         ¦  «        S )Nc              3  ó>   K  — | ]}t          j        |¦  «        V — Œd S r!   )rA   r–   )Ú.0Úks     r&   ú	<genexpr>z_is_headless.<locals>.<genexpr>1  s*   è è € Ð8Ð8 �rŒy˜‰|Œ|Ð8Ð8Ð8Ð8Ð8Ð8r'   )ÚanyÚ_HEADLESS_ENV_VARSr/   r'   r&   Ú_is_headlessr†  0  s    € ÝÐ8Ð8Õ%7Ð8Ñ8Ô8Ñ8Ô8Ð8r'   T)Úforce_reloginÚopen_browserÚcallback_wait_secondsr¹   r‡  rˆ  r‰  c           
     ó"  — | s3t          ¦   «         }|r#|j        rt                               d¦  «         |S t	          ¦   «         }t          ¦   «         }t          ¦   «         \  }}t          j        d¦  «        }	t          ¦   «         r0|r.t                               d¦  «         t          |||	|||¦  «        S t          t          ¦  «        \  }
}dt          › d|› t          › �}|	t          _        dt          _        dt          _        t'          j        ¦   «         }|t          _        ||dt,          |	|dd	d
dœ	}t.          dz   t0          j                             |¦  «        z   dz   }t'          j        |
j        d¬¦  «        }|                     ¦   «          t=          ¦   «          t=          d¦  «         t=          d|› �¦  «         t=          ¦   «          |rP	 ddl}|                      |dd¬¦  «         n2# tB          $ r%}t           "                    d|¦  «         Y d}~nd}~ww xY wd}	 | #                    |¬¦  «        r/t          j        }t          j        }|rtI          d|› �d¬¦  «        ‚n(t                               d¦  «         tK          ¦   «         }	 |
 &                    ¦   «          n# tB          $ r Y nw xY w	 |
 '                    ¦   «          n# tB          $ r Y nw xY w| (                    d¬¦  «         ng# 	 |
 &                    ¦   «          n# tB          $ r Y nw xY w	 |
 '                    ¦   «          n# tB          $ r Y nw xY w| (                    d¬¦  «         w xY w|stI          dd¬¦  «        ‚tS          |||||¬¦  «        }tU          ||¬¦  «        S ) aÍ  Run the interactive browser OAuth flow and persist credentials.

    Args:
        force_relogin: If False and valid creds already exist, return them.
        open_browser: If False, skip webbrowser.open and print the URL only.
        callback_wait_seconds: Max seconds to wait for the browser callback.
        project_id: Initial GCP project ID to bake into the stored creds.
                    Can be discovered/updated later via update_project_ids().
    z9Google OAuth credentials already present; skipping login.é   z?Headless environment detected; using paste-mode OAuth fallback.úhttp://ú:Nr   ÚS256ÚofflineÚconsent©	rz   r+  Úresponse_typeÚscoper\  Úcode_challengeÚcode_challenge_methodÚaccess_typeÚpromptú?ú#hermesT)ÚtargetÚdaemonu,   Opening your browser to sign in to Googleâ€¦z,If it does not open automatically, visit:
  r   r8   )ÚnewÚ	autoraisezwebbrowser.open failed: %sr  zAuthorization failed: Ú!google_oauth_authorization_failedr   u=   Callback server timed out â€” offering manual paste fallback.g       @z)No authorization code received. Aborting.Úgoogle_oauth_no_code©rz   r|   ©r¹   )+ró   rÎ   r…   r�   r¤   r    rµ   r©   rª   r†  Ú_paste_mode_loginr~  ÚDEFAULT_REDIRECT_PORTr|  rd  rS  rT  rU  rV  rB  rC  rW  ÚOAUTH_SCOPESÚAUTH_ENDPOINTr  rÅ   r  ÚThreadÚserve_foreverÚstartÚprintÚ
webbrowserrB   r#  r†   rD  r   Ú_prompt_paste_fallbackÚshutdownÚserver_closeÚjoinr3  Ú_persist_token_response)r‡  rˆ  r‰  r¹   Úexistingrz   r|   r³   r´   r\  rz  Úportr+  rW  rn  Úauth_urlÚserver_threadrª  r�   r   r!  Ú
token_resps                         r&   Ústart_oauth_flowrµ  8  s<  € ð  ð Ý#Ñ%Ô%ˆØð 	˜Ô-ð 	Ý�KŠKÐSÑTÔTÐTØˆOå"Ñ$Ô$€IÝ&Ñ(Ô(€Må-Ñ/Ô/Ñ€HˆiÝÔ! "Ñ%Ô%€Eõ �~„~ð c˜,ð cÝ�ŠÐUÑVÔVÐVÝ  ¨9°e¸YÈÐWaÑbÔbÐbå(Õ)>Ñ?Ô?�L€FˆDØB�]ÐBÐB¨TÐBµ=ÐBÐB€Là+0ÕÔ(Ø*.ÕÔ'Ø+/ÕÔ(ÝŒOÑÔ€EØ"'ÕÔð Ø$ØÝØØ#Ø!'Ø Øð
ð 
€Fõ ˜sÑ"¥V¤\×%;Ò%;¸FÑ%CÔ%CÑCÀiÑO€HåÔ$¨FÔ,@ÈÐNÑNÔN€MØ×ÒÑÔÐå	�G„G€GÝ	Ð
8Ñ9Ô9Ð9Ý	Ð
D¸(Ð
DÐ
DÑEÔEÐEÝ	�G„G€Gàð <ð	<ØÐÐÐà�OŠO˜H¨!°tˆOÑ<Ô<Ð<Ð<øÝð 	<ð 	<ð 	<Ý�LŠLÐ5°sÑ;Ô;Ð;Ð;Ð;Ð;Ð;Ð;øøøøð	<øøøð €Dð(Ø�:Š:Ð3ˆ:Ñ4Ô4ð 
	,Ý(Ô6ˆDÝ)Ô8ˆEØð Ý&Ø4¨UÐ4Ð4Ø<ðñ ô ð ðõ �KŠKÐWÑXÔXÐXÝ)Ñ+Ô+ˆDð	Ø�OŠOÑÔÐÐøÝð 	ð 	ð 	ØˆDð	øøøð	Ø×ÒÑ!Ô!Ð!Ð!øÝð 	ð 	ð 	ØˆDð	øøøà×Ò 3ÐÑ'Ô'Ð'Ð'øð	Ø�OŠOÑÔÐÐøÝð 	ð 	ð 	ØˆDð	øøøð	Ø×ÒÑ!Ô!Ð!Ð!øÝð 	ð 	ð 	ØˆDð	øøøà×Ò 3ÐÑ'Ô'Ð'Ð'øøøàð 
ÝØ7Ø'ð
ñ 
ô 
ð 	
õ
 Øˆh˜Ø¨=ðñ ô €Jõ # :¸*ÐEÑEÔEÐEs‘   ÇG- Ç-
HÇ7HÈHÈ"A-K2 ÊJ% Ê%
J2Ê1J2Ê6K Ë
KËKË2MË4L	ÌMÌ	
LÌMÌLÌMÌL/Ì.MÌ/
L<Ì9MÌ;L<Ì<Mr´   r\  c           
     ó  — dt           › dt          › t          › �}||dt          ||ddddœ	}t          dz   t
          j                             |¦  «        z   d	z   }t          ¦   «          t          d
¦  «         t          d|› �¦  «         t          ¦   «          t          d¦  «         t          d¦  «         t          ¦   «          t          ¦   «         }	|	st          dd¬¦  «        ‚t          |	| |||¬¦  «        }
t          |
|¬¦  «        S )z/Run OAuth flow without a local callback server.rŒ  r�  r   rŽ  r�  r�  r‘  r˜  r™  z)Open this URL in a browser on any device:z  zGAfter signing in, Google will redirect to localhost (which won't load).z7Copy the full URL from your browser and paste it below.zNo authorization code provided.rŸ  r   r   r¡  )r|  r£  rd  r¤  r¥  r  rÅ   r  r©  r«  r   r3  r¯  )r³   r´   r\  rz   r|   r¹   r+  rn  r²  r   r´  s              r&   r¢  r¢  ¤  s'  € ð T�]ÐSÐSÕ-BÐSÅMÐSÐS€LàØ$ØÝØØ#Ø!'Ø Øð
ð 
€Fõ ˜sÑ"¥V¤\×%;Ò%;¸FÑ%CÔ%CÑCÀiÑO€Hå	�G„G€GÝ	Ð
5Ñ6Ô6Ð6Ý	ˆ/ˆxˆ/ˆ/ÑÔÐÝ	�G„G€GÝ	Ð
SÑTÔTÐTÝ	Ð
CÑDÔDÐDÝ	�G„G€Gå!Ñ#Ô#€DØð _ÝÐ@ÐG]Ð^Ñ^Ô^Ð^åØˆh˜Ø¨=ðñ ô €Jõ # :¸*ÐEÑEÔEÐEr'   c                 ó\  — t          ¦   «          t          d¦  «         t          d¦  «                             ¦   «         } | sd S |                      d¦  «        s|                      d¦  «        rct          j                             | ¦  «        }t          j                             |j        ¦  «        }| 	                    d¦  «        pdgd         pd S |                      d¦  «        rGt          j                             | d	d …         ¦  «        }| 	                    d¦  «        pdgd         pd S | S )
NzSPaste the full redirect URL Google showed you, OR just the 'code=' parameter value.zCallback URL or code: rŒ  zhttps://r   r{   r   r˜  r8   )
r©  Úinputr˜   Ú
startswithr  rÅ   rc  rg  rh  rƒ   )rñ   rm  rn  s      r&   r«  r«  Ï  s  € Ý	�G„G€GÝ	Ð
_Ñ`Ô`Ð`Ý
Ð(Ñ
)Ô
)×
/Ò
/Ñ
1Ô
1€CØð ØˆtØ
‡~‚~�iÑ Ô ð 7 C§N¢N°:Ñ$>Ô$>ð 7Ý”×&Ò& sÑ+Ô+ˆÝ”×&Ò& v¤|Ñ4Ô4ˆØ—
’
˜6Ñ"Ô"Ð* r d¨AÔ.Ð6°$Ð6à
‡~‚~�cÑÔð 7Ý”×&Ò& s¨1¨2¨2¤wÑ/Ô/ˆØ—
’
˜6Ñ"Ô"Ð* r d¨AÔ.Ð6°$Ð6Ø€Jr'   r¡  r´  c               ój  — t          |                      dd¦  «        pd¦  «                             ¦   «         }t          |                      dd¦  «        pd¦  «                             ¦   «         }t          |                      dd¦  «        pd¦  «        }|r|st	          dd¬¦  «        ‚t          ||t          t          j        ¦   «         t          d	|¦  «        z   d
z  ¦  «        t          |¦  «        |d¬¦  «        }t          |¦  «         t                               dt          ¦   «         ¦  «         |S )NrÎ   r{   r¸   r@  r   z<Google token response missing access_token or refresh_token.Ú&google_oauth_incomplete_token_responser   r   rã   rÛ   z$Google OAuth credentials saved to %s)r   rƒ   r˜   rÏ   r   rÍ   rG   rI   r:  r	  r…   r�   r0   )r´  r¹   rÎ   r¸   r@  rò   s         r&   r¯  r¯  à  s+  € õ
 �z—~’~ n°bÑ9Ô9Ð?¸RÑ@Ô@×FÒFÑHÔH€LÝ˜
Ÿš ¸Ñ;Ô;ÐA¸rÑBÔB×HÒHÑJÔJ€MÝ�Z—^’^ L°!Ñ4Ô4Ð9¸Ñ:Ô:€JØð 
˜}ð 
ÝØJØ9ð
ñ 
ô 
ð 	
õ Ø!Ø#Ý�œ	™œ¥c¨"¨jÑ&9Ô&9Ñ9¸TÑAÑBÔBÝ Ñ-Ô-ØØðñ ô €Eõ �UÑÔÐÝ
‡K‚KÐ6Õ8IÑ8KÔ8KÑLÔLÐLØ€Lr'   c                 ód   — t          d¬¦  «        } | j        | j        | j        | j        | j        dœS )zHRun the login flow and return a dict matching the credential pool shape.T)r‡  )rÎ   r¸   Úexpires_at_msrÑ   r¹   )rµ  rÎ   r¸   rÐ   rÑ   r¹   )rò   s    r&   Úrun_gemini_oauth_login_purer¾  þ  s>   € å¨4Ð0Ñ0Ô0€EàÔ*ØÔ,ØÔ)Ø”ØÔ&ðð ð r'   c                 ól   — dD ]0} t          j        | ¦  «        pd                     ¦   «         }|r|c S Œ1dS )z9Return a GCP project ID from env vars, in priority order.)ÚHERMES_GEMINI_PROJECT_IDÚGOOGLE_CLOUD_PROJECTÚGOOGLE_CLOUD_PROJECT_IDr{   )rA   r–   r˜   )ÚvarÚvals     r&   Úresolve_project_id_from_envrÅ    sN   € ðð ð ˆõ
 Œy˜‰~Œ~Ð# ×*Ò*Ñ,Ô,ˆØð 	ØˆJˆJˆJð	àˆ2r'   )r   r   )r4   r5   )r   r^   )r   rw   rÉ   )r   rç   )rò   rÍ   r   r   ru  )r  r   rØ   r\   r  r5   r   rÒ   )r   r   r³   r   r+  r   rz   r,  r|   r,  r  r5   r   rÒ   )
r¸   r   rz   r,  r|   r,  r  r5   r   rÒ   )rÎ   r   r  r5   r   r   )r<  rá   r   r   r~   )r¹   r   rº   r   r   r   )rv  rÏ   r   rw  )r   rá   )
r‡  rá   rˆ  rá   r‰  r5   r¹   r   r   rÍ   )r³   r   r´   r   r\  r   rz   r   r|   r   r¹   r   r   rÍ   )r   r,  )r´  rÒ   r¹   r   r   rÍ   rå   )hr+   Ú
__future__r   r¯   Ú
contextlibr«   Úhttp.serverry  rê   ÚloggingrA   r©   Úsocketr  rB  rG   Úurllib.errorr  Úurllib.parseÚurllib.requestÚdataclassesr   r   Úpathlibr   Útypingr   r   r	   r
   Úhermes_constantsr   Ú	getLoggerr(   r…   r—   rž   Ú_PUBLIC_CLIENT_ID_PROJECT_NUMÚ_PUBLIC_CLIENT_ID_HASHÚ_PUBLIC_CLIENT_SECRET_SUFFIXr™   rŸ   ÚreÚ_reÚcompiler‡   rŠ   r‰   r‹   r¥  r1  r9  r¤  r£  r|  rd  ræ   ÚTOKEN_REQUEST_TIMEOUT_SECONDSÚCALLBACK_WAIT_SECONDSrE  r…  ÚRuntimeErrorr   r0   r3   Úlocalr>   Úcontextmanagerr[   r]   rÊ   rv   r“   rœ   r    r¤   rµ   r·   rÍ   ró   r	  r  r*  r3  r6  r:  r;  ÚLockrA  rO  rQ  rz  ÚBaseHTTPRequestHandlerrS  rl  rk  r~  r†  rµ  r¢  r«  r¯  r¾  rÅ  r/   r'   r&   ú<module>rà     s9  ðð'ð 'ð 'ðR #Ð "Ð "Ð "Ð "Ð "à €€€Ø Ð Ð Ð Ø €€€Ø Ð Ð Ð Ø €€€Ø €€€Ø 	€	€	€	Ø €€€Ø €€€Ø €€€Ø Ð Ð Ð Ø €€€Ø Ð Ð Ð Ø Ð Ð Ð Ø Ð Ð Ð Ø (Ð (Ð (Ð (Ð (Ð (Ð (Ð (Ø Ð Ð Ð Ð Ð Ø -Ð -Ð -Ð -Ð -Ð -Ð -Ð -Ð -Ð -Ð -Ð -à ,Ð ,Ð ,Ð ,Ð ,Ð ,à	ˆÔ	˜8Ñ	$Ô	$€ð" *€Ø1Ð ð !/Ð Ø;Ð Ø=Ð ð %ð "ð "Ð'=ð "ð "ð "ð ð BÐ#?ÐAÐAÐ ð Ð Ð Ð Ø �S”[ØWñô Ð ð %˜œØBñô Ð ð �3”;ÐYÑZÔZÐ Ø"�s”{Ð#AÑBÔBÐ ð ?€Ø6€ØCÐ ð7ð ð Ð Ø€Ø!€ð Ð à $Ð ØÐ ØÐ ð TÐ ðð ð ð ð �|ñ ô ð ð<ð <ð <ð <ð9ð 9ð 9ð 9ð ˆiŒoÑÔ€ð ÔØ/Cð J"ð J"ð J"ð J"ñ ÔðJ"ðb (*Ð Ð )Ð )Ð )Ñ )ð1ð 1ð 1ð 1ðh$ð $ð $ð $ðDð ð ð ðð ð ð ðð ð ð ð,ð ð ð ð ðSð Sð Sð Sð Sñ Sô Sñ „ðSð8 ð'Nð 'Nð 'Nð 'Nð 'Nñ 'Nô 'Nñ „ð'Nð\ð ð ð ð(ð ð ð ð0	]ð 	]ð 	]ð 	]ð "ð "ð "ð "ðT  $Ø#'Ø2ð5ð 5ð 5ð 5ð 5ð 5ð6  $Ø#'Ø2ð5ð 5ð 5ð 5ð 5ð 5ð2 ;Xð ð ð ð ð ð( 13Ð Ð 2Ð 2Ð 2Ñ 2Ø'˜œÑ)Ô)Ð ð 5:ð Dð Dð Dð Dð Dð DðV	ð 	ð 	ð 	ð 	ð 2"ð 2"ð 2"ð 2"ð 2"˜DœKÔ>ñ 2"ô 2"ð 2"ðj€ð€ð 1Fð 
,ð 
,ð 
,ð 
,ð 
,ð9ð 9ð 9ð 9ð  ØØ#8ØðiFð iFð iFð iFð iFð iFðX(Fð (Fð (Fð (FðVð ð ð ð( ðð ð ð ð ð ð<	ð 	ð 	ð 	ð 
ð 
ð 
ð 
ð 
ð 
r'   