+
    #jD                       R t ^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIH	t	 ]
! ]	! ]4      P                  4       P                  4      t]]P                  9  d   ]P                  P!                  ^ ]4       ^ RIHtHt ]! 4       t]R,          t]R,          t]R,          t. R$Ot. R%OtRtR	 R
 lt]3R R lltR R ltR R ltR tR t R t!R&R R llt"R R lt#R R lt$R R lt%R R lt&R t'R R  lt(R! t)R" t*]+R#8X  d
   ]*! 4        R# R# )'u/  Google Workspace OAuth2 setup for Hermes Agent.

Fully non-interactive — designed to be driven by the agent via terminal commands.
The agent mediates between this script and the user (works on CLI, Telegram, Discord, etc.)

Commands:
  setup.py --check                          # Is auth valid? Exit 0 = yes, 1 = no
  setup.py --client-secret /path/to.json    # Store OAuth client credentials
  setup.py --auth-url                       # Print the OAuth URL for user to visit
  setup.py --auth-code CODE                 # Exchange auth code for token
  setup.py --revoke                         # Revoke and delete stored token
  setup.py --install-deps                   # Install Python dependencies only

Agent workflow:
  1. Run --check. If exit 0, auth is good — skip setup.
  2. Ask user for client_secret.json path. Run --client-secret PATH.
  3. Run --auth-url. Send the printed URL to the user.
  4. User opens URL, authorizes, gets redirected to a page with a code.
  5. User pastes the code. Agent runs --auth-code CODE.
  6. Run --check to verify. Done.
)annotationsN)Path)display_hermes_homeget_hermes_homezgoogle_token.jsonzgoogle_client_secret.jsonzgoogle_oauth_pending.jsonzhttp://localhost:1c                    V ^8  d   QhRRRR/# )   payloaddictreturn )formats   "l/opt/hermes-venv/lib/python3.14/site-packages/../../../skills/productivity/google-workspace/scripts/setup.py__annotate__r   @   s            c                T    \        V 4      pVP                  R 4      '       g   RVR &   V# )typeauthorized_user)r	   get)r   
normalizeds   & r   "_normalize_authorized_user_payloadr   @   s*    gJ>>&!!.
6r   c                    V ^8  d   QhRRRR/# )r   pathr   r
   r	   r   )r   s   "r   r   r   G   s      d D r   c                r     \         P                  ! V P                  4       4      #   \         d    / u # i ; iN)jsonloads	read_text	Exception)r   s   &r   _load_token_payloadr   G   s1    zz$..*++ 	s   #& 66c                    V ^8  d   QhRRRR/# )r   r   r	   r
   	list[str]r   )r   s   "r   r   r   N   s     E E$ E9 Er   c                b  a V P                  R 4      ;'       g    V P                  R4      pV'       g   . # \        V\        4      '       d   VP                  4       MT Uu0 uF*  q"P	                  4       '       g   K  VP	                  4       kK,  	  upo\        V3R l\         4       4      # u upi )scopesscopec              3  8   <"   T F  qS9  g   K  Vx  K  	  R # 5ir   r   ).0r#   granteds   & r   	<genexpr>/_missing_scopes_from_payload.<locals>.<genexpr>S   s     DVEG/C%%Vs   
)r   
isinstancestrsplitstripsortedSCOPES)r   rawsr&   s   &  @r   _missing_scopes_from_payloadr1   N   s    
++h

7
77;;w#7C	2<S#2F2F399;C#O^#OQT[T[T]yqwwy#O^GDVDDD _s    B,<B,c                    V ^8  d   QhRRRR/# )r   missing_scopesr    r
   r*   r   )r   s   "r   r   r   V   s      9  r   c                >    R P                  R V  4       4      pRV R2# )
c              3  ,   "   T F
  pR V 2x  K  	  R# 5i)  - Nr   )r%   r#   s   & r   r'   )_format_missing_scopes.<locals>.<genexpr>W   s     CN5$ugNs   z=Token is valid but missing required Google Workspace scopes:
zW
Run the Google Workspace setup again from this same Hermes profile to refresh consent.)join)r3   bulletss   & r   _format_missing_scopesr;   V   s/    iiCNCCGH) a	ar   c            	         ^ RI p ^ RIp\        R4       R#   \         d     Mi ; i\        R4        \        P
                  ! \        P                  RRRR.\        ,           \        P                  R	7       \        R
4       R#   \        P                   db   p\        RT 24       \        R4       \        R4       \        R\        P                   RRP                  \        4       24        Rp?R# Rp?ii ; i)z@Install Google API packages if missing. Returns True on success.NzDependencies already installed.Tz%Installing Google API dependencies...z-mpipinstallz--quiet)stdoutzDependencies installed.z'ERROR: Failed to install dependencies: zKOn environments without pip (e.g. Nix), install the optional extra instead:z$  pip install 'hermes-agent[google]'zOr manually: z -m pip install  F)googleapiclientgoogle_auth_oauthlibprintImportError
subprocess
check_callsys
executableREQUIRED_PACKAGESDEVNULLCalledProcessErrorr9   )rA   rB   es      r   install_depsrM   _   s    #/0  

12^^T5)Y?BSS%%	
 	'((( 7s;<Y	
 	45cnn--=chhGX>Y=Z[\s%    %%AB C;AC66C;c                      ^ RI p ^ RIpR#   \         d.    \        4       '       g   \        P
                  ! ^4        R#  R# i ; i)z:Check deps are available, install if not, exit on failure.N)rA   rB   rD   rM   rG   exit)rA   rB   s     r   _ensure_depsrP   {   s3    # ~~HHQK s    1AAc                    \        RR7      '       g   R#  ^ RIHp  ^ RIHp VP                  \        \        4      4      pV ! RRVR7      pVP                  4       P                  ^R	7      P                  4        \        R
4       R#   \         dq   p\        T4      P                  4       pRT9   g   RT9   d1   \        RT 24       \        R4       \        R4       \        R4       M\        RT 24        Rp?R# Rp?ii ; i)zICheck auth with a real API call to detect disabled_client/account issues.T)quietF)buildCredentialscalendarv3)credentials)
maxResultsz'LIVE_CHECK_OK: Real API call succeeded.disabled_clientinvalid_clientz5LIVE_CHECK_FAILED: OAuth client or account disabled: z9  1. Check Google Cloud Console for disabled OAuth clientz2  2. Check myaccount.google.com for account statusz)  3. Do NOT retry with a disabled accountzLIVE_CHECK_FAILED: N)
check_authgoogleapiclient.discoveryrS   google.oauth2.credentialsrU   from_authorized_user_filer*   
TOKEN_PATHcalendarListlistexecuterC   r   lower)rS   rU   credsservicerL   err_strs         r   check_auth_liverh      s     D!!3955c*oF
De<##q#199;78 	a&,,.'+;w+FI!MNMNFG=>'s+,	s   A.B DA%C<<Dc                   V ^8  d   QhRR/# )r   rR   boolr   )r   s   "r   r   r      s     C Cd Cr   c           
        \         P                  4       '       g   \        R\          24       R# \        4        ^ RIHp ^ RIHp  VP                  \        \         4      4      p\        \         4      pTP                  '       d_   \        T4      pT'       d0   \        R\        T4       R24       T F  p\        R	T 24       K  	  T '       g   \        R
\          24       R# TP                  '       d   TP                   '       d    TP#                  T! 4       4       \         P%                  \&        P(                  ! \+        \&        P,                  ! TP/                  4       4      4      ^R7      4       \        \        \         4      4      pT'       d0   \        R\        T4       R24       T F  p\        R	T 24       K  	  T '       g   \        R\          24       R# \        R4       R#   \         d   p\        RT 24        Rp?R# Rp?ii ; i  \         d   p\        T4      P1                  4       pRT9   g   RT9   d]   \        RT 24       \        R4       \        R4       \        R4       \        R4       \        R4       \        R4       \        R4       M6RT9   g   RT9   d   \        RT 24       \        R4       M\        RT 24        Rp?R# Rp?ii ; i)zCCheck if stored credentials are valid. Prints status, exits 0 or 1.zNOT_AUTHENTICATED: No token at FrT   RequestzTOKEN_CORRUPT: Nz1AUTHENTICATED (partial): Token valid but missing z scopes:r7   zAUTHENTICATED: Token valid at Tindentz5AUTHENTICATED (partial): Token refreshed but missing z"AUTHENTICATED: Token refreshed at rZ   r[   zOAUTH_CLIENT_DISABLED: z7  The OAuth client or Google account has been disabled.z  Steps to resolve:uR       1. Check your Google Cloud Console — verify the OAuth client is not disabledzT    2. Check if your Google account itself has been disabled at myaccount.google.comzX    3. If the account is disabled, you can appeal at accounts.google.com/signin/recoveryuW       4. Do NOT retry API calls with a disabled account — this may worsen the situationzP    5. If the OAuth client is disabled, create a new one in Google Cloud Consoletoken_revokedinvalid_grantzTOKEN_REVOKED: z"  Re-run setup to re-authenticate.zREFRESH_FAILED: zTOKEN_INVALID: Re-run setup.)r`   existsrC   rP   r^   rU   google.auth.transport.requestsrm   r_   r*   r   r   validr1   lenexpiredrefresh_tokenrefresh
write_textr   dumpsr   r   to_jsonrd   )	rR   rU   rm   re   rL   r   r3   r0   rg   s	   &        r   r\   r\      sX   /
|<=N56
 55c*oF
 "*-G{{{5g>Ec.FYEZZbcd#QCj! $2:,?@}}},,, 	MM')$!!

6tzz%--/7RS ::Mj:YZNMcR`NaMbbjkl'AD*% (::,GH& 

()g  s#$@  	!fllnG G+/?7/J/s34OP+,jklmpqophi G+'/Is+,:;(,-!	s8   G, CH H ,H7HHK"B9KK"c                   V ^8  d   QhRR/# )r   r   r*   r   )r   s   "r   r   r      s     > >c >r   c                x   \        V 4      P                  4       P                  4       pVP                  4       '       g%   \	        RV 24       \
        P                  ! ^4        \        P                  ! VP                  4       4      pRX9  d4   RV9  d-   \	        R4       \	        R4       \
        P                  ! ^4       \        P                  \        P                  ! V^R7      4       \	        R\         24       R	#   \        P                   d%    \	        R4       \
        P                  ! ^4        Li ; i)
z4Copy and validate client_secret.json to Hermes home.zERROR: File not found: zERROR: File is not valid JSON.	installedwebzGERROR: Not a Google OAuth client secret file (missing 'installed' key).zQDownload the correct file from: https://console.cloud.google.com/apis/credentialsrn   zOK: Client secret saved to N)r   
expanduserresolverr   rC   rG   rO   r   r   r   JSONDecodeErrorCLIENT_SECRET_PATHry   rz   )r   srcdatas   &  r   store_client_secretr      s    
t*


!
)
)
+C::<<'u-.zz#--/*
 $5#4WXab!!$**T!"<=	'(:';
<=  ./s   #$D   6D98D9c                    V ^8  d   QhRRRR/# )r   stater*   code_verifierr   )r   s   "r   r   r      s       S r   c           
     p    \         P                  \        P                  ! RV RVR\        /^R7      4       R# )zAPersist the OAuth session bits needed for a later token exchange.r   r   redirect_urirn   N)PENDING_AUTH_PATHry   r   rz   REDIRECT_URIr   r   s   $$r   _save_pending_authr      s5      


 	
	r   c                   V ^8  d   QhRR/# )r   r
   r	   r   )r   s   "r   r   r   	  s      D r   c                    \         P                  4       '       g"   \        R4       \        P                  ! ^4        \
        P                  ! \         P                  4       4      p X P                  R4      '       d   V P                  R4      '       g-   \        R4       \        R4       \        P                  ! ^4       V #   \         d:   p\        RT 24       \        R4       \        P                  ! ^4        Rp?LRp?ii ; i)z9Load the pending OAuth session created by get_auth_url().z<ERROR: No pending OAuth session found. Run --auth-url first.z-ERROR: Could not read pending OAuth session: z4Run --auth-url again to start a fresh OAuth session.Nr   r   z2ERROR: Pending OAuth session is missing PKCE data.)
r   rr   rC   rG   rO   r   r   r   r   r   )r   rL   s     r   _load_pending_authr   	  s    ##%%LMzz+5578 88GDHH_$=$=BCDEK  =aSABDEs   (C D/D  Dc                    V ^8  d   QhRRRR/# )r   code_or_urlr*   r
   ztuple[str, str | None]r   )r   s   "r   r   r     s     $ $ $1G $r   c                $   V P                  R4      '       g   V R3# ^ RIHpHp V! V 4      pV! VP                  4      pRV9  d"   \        R4       \        P                  ! ^4       VP                  RR.4      ^ ,          pVR,          ^ ,          V3# )zJAccept either a raw auth code or the full redirect URL pasted by the user.httpNparse_qsurlparsecodez(ERROR: No 'code' parameter found in URL.r   )	
startswithurllib.parser   r   queryrC   rG   rO   r   )r   r   r   parsedparamsr   s   &     r   _extract_code_and_stater     s    !!&))D  /k"Ffll#FV89JJw'*E&>!e##r   c                 \   \         P                  4       '       g"   \        R4       \        P                  ! ^4       \        4        ^ RIHp  V P                  \        \         4      \        \        RR7      pVP                  RRR7      w  r#\        W1P                  R7       \        V4       R	# )
zAPrint the OAuth authorization URL. User visits this in a browser.:ERROR: No client secret stored. Run --client-secret first.FlowT)r"   r   autogenerate_code_verifierofflineconsent)access_typepromptr   N)r   rr   rC   rG   rO   rP   google_auth_oauthlib.flowr   from_client_secrets_filer*   r.   r   authorization_urlr   r   )r   flowauth_urlr   s       r   get_auth_urlr   /  s    $$&&JKN.((!#'	 ) D ,, - OH U2D2DE	(Or   c                   V ^8  d   QhRR/# )r   r   r*   r   )r   s   "r   r   r   G  s     ?W ?WS ?Wr   c                h   \         P                  4       '       g"   \        R4       \        P                  ! ^4       \        4       pT p\        V 4      w  rV'       d/   W1R,          8w  d"   \        R4       \        P                  ! ^4       \        4        ^ RIH	p ^ RI
HpHp \        \        4      p\        V\         4      '       dy   VP#                  R4      '       db   V! V! V4      P$                  4      pVP'                  R4      ;'       g    R.^ ,          P)                  4       p	V	'       d   V	P+                  4       pVP-                  \!        \         4      VVP'                  R	\.        4      VR,          VR
,          R7      p
 R\0        P2                  R&   V
P5                  V R7       V
P8                  p\;        \<        P>                  ! VPA                  4       4      4      p\C        VR4      '       d2   VPD                  '       d    \        VPD                  ;'       g    . 4      M. pV'       d   WR&   MV\        8w  d   W}R&   \G        V4      pV'       d)   \        RRPI                  V4       24       \        R4       \J        PM                  \<        PN                  ! V^R7      4       \P        PS                  RR7       \        R\J         24       \        R\U        4        R24       R#   \6         d;   p\        RT 24       \        R4       \        P                  ! ^4        Rp?ELRp?ii ; i)z8Exchange the authorization code for a token and save it.r   r   zKERROR: OAuth state mismatch. Run --auth-url again to start a fresh session.r   r   r   r#    r   r   )r"   r   r   r   1OAUTHLIB_RELAX_TOKEN_SCOPE)r   zERROR: Token exchange failed: z=The code may have expired. Run --auth-url to get a fresh URL.Ngranted_scopesr"   z5WARNING: Token missing some Google Workspace scopes: z, z#Some services may not be available.rn   T
missing_okz"OK: Authenticated. Token saved to zProfile-scoped token location: z/google_token.json)+r   rr   rC   rG   rO   r   r   rP   r   r   r   r   r   rb   r.   r)   r*   r   r   r   r,   r+   r   r   osenvironfetch_tokenr   rX   r   r   r   r{   hasattrr   r1   r9   r`   ry   rz   r   unlinkr   )r   pending_authraw_callbackreturned_stater   r   r   r   r   	scope_valr   rL   re   token_payloadactually_grantedr3   s   &               r   exchange_auth_coder   G  sw   $$&&JK%'LL248D.,AA[\N./ &\N,$$)@)@)H)H(<0667ZZ(00RD!4::<	&__.N((!%%nlC7#"?3 ) D36

/0d# E6tzz%--/7RSM
 <C5JZ;[;[`e`t`t`ttE0066B7z|"2h	6	!"0h1-@NEdiiP^F_E`ab34$**]1=>-	.zl
;<	+,?,A+BBT
UV5  .qc23MNs   %K, ,L17/L,,L1c                    \         P                  4       '       g   \        R4       R# \        4        ^ RIHp  ^ RIHp  V P                  \        \         4      \        4      pVP                  '       d)   VP                  '       d   VP                  V! 4       4       ^ RIpVP                  P!                  VP                  P                  RVP"                   2RRR/R	7      ^R
7       \        R4       \         P'                  RR7       \(        P'                  RR7       \        R\          24       R#   \$         d   p\        RT 24        Rp?L^Rp?ii ; i)z"Revoke stored token and delete it.zNo token to revoke.NrT   rl   z+https://oauth2.googleapis.com/revoke?token=POSTzContent-Typez!application/x-www-form-urlencoded)methodheaders)timeoutzToken revoked with Google.z9Remote revocation failed (token may already be invalid): Tr   zDeleted )r`   rr   rC   rP   r^   rU   rs   rm   r_   r*   r.   rv   rw   rx   urllib.requestrequesturlopentokenr   r   r   )rU   rm   re   urllibrL   s        r   revoker     s   #$N56O55c*ovN===U000MM')$NN""=ekk]K')LM # 
  	 	
 	*+ &-	HZL
!"  OI!MNNOs   AD3 A-D3 3E>EEc                    \         P                  ! R R7      p V P                  RR7      pVP                  RRRR7       VP                  RRR	R7       VP                  R
RRR7       VP                  RRRR7       VP                  RRRR7       VP                  RRRR7       VP                  RRRR7       V P	                  4       pVP
                  '       d)   \        P                  ! \        4       '       d   ^ M^4       \        VRR4      '       d+   \        P                  ! \        4       '       d   ^ M^4       R# VP                  '       d   \        VP                  4       R# VP                  '       d   \        4        R# VP                  '       d   \!        VP                  4       R# VP"                  '       d   \#        4        R# VP$                  '       d+   \        P                  ! \%        4       '       d   ^ M^4       R# R# )z'Google Workspace OAuth setup for Hermes)descriptionT)requiredz--check
store_truez)Check if auth is valid (exit 0=yes, 1=no))actionhelpz--check-livez9Check auth with a real API call (detects disabled_client)z--client-secretPATHzStore OAuth client_secret.json)metavarr   z
--auth-urlz!Print OAuth URL for user to visitz--auth-codeCODEzExchange auth code for tokenz--revokezRevoke and delete stored tokenz--install-depszInstall Python dependencies
check_liveFN)argparseArgumentParseradd_mutually_exclusive_groupadd_argument
parse_argscheckrG   rO   r\   getattrrh   client_secretr   r   r   	auth_coder   r   rM   )parsergroupargss      r   mainr     s   $$1Z[F///>E	y<gh	~lA|}	(&?_`	|L?bc	}f;YZ	z,=]^	'C`aDzzzjll*t\5))o''Q/				D../		4>>*					lnn!, 
r   __main__)z.https://www.googleapis.com/auth/gmail.readonlyz*https://www.googleapis.com/auth/gmail.sendz,https://www.googleapis.com/auth/gmail.modifyz(https://www.googleapis.com/auth/calendarz%https://www.googleapis.com/auth/drivez1https://www.googleapis.com/auth/contacts.readonlyz,https://www.googleapis.com/auth/spreadsheetsz)https://www.googleapis.com/auth/documents)zgoogle-api-python-clientzgoogle-auth-oauthlibzgoogle-auth-httplib2)F),__doc__
__future__r   r   r   r   rE   rG   pathlibr   r*   __file__r   parent_SCRIPTS_DIRr   insert_hermes_homer   r   HERMES_HOMEr`   r   r   r.   rI   r   r   r   r1   r;   rM   rP   rh   r\   r   r   r   r   r   r   r   r   __name__r   r   r   <module>r      s  , #   	  
  4>))+223sxxHHOOA|$ =..
 #>> "== 	
 a 
 $ &0 E84CL>,*$"0?WD#B-8 zF r   