
    Oj%                        d Z ddlmZ ddlZddlZddlmZmZ ddlmZ  ee	      j                         j                  j                  ZdgZej                  j                  dd      Zdadd	Zd
 ZddZddZd Zy)u-  Google Calendar + Meet in-process MCP for Jarvis.

Gives Claude real calendar hands: create events, spin up a Google Meet with a
shareable link, and read what's coming up — all against Ahmed's own Google
account via a one-time desktop OAuth consent (no API key, no per-call billing).

Like ``desktop_control``, this runs IN-PROCESS via the Claude Agent SDK's
in-process MCP server (key ``"gcal"``; Claude sees ``mcp__gcal__<name>``). The
Google client libraries are imported lazily inside the auth helper so merely
importing this module never hard-fails when they're missing.

Auth: a desktop OAuth client (``GOOGLE_OAUTH_JSON``, default ``google_oauth.json``
at the project root). The FIRST calendar action opens a browser once for Ahmed
to approve; the resulting token is cached to ``GOOGLE_TOKEN_JSON`` (default
``google_token.json``) and refreshed silently forever after. Set
``MCP_GCAL=0`` to disable.

Times are RFC3339-ish local wall-clock (``2026-07-08T15:00:00``); the calendar
timezone defaults to ``CAL_TZ`` (Asia/Riyadh) so "3pm tomorrow" means 3pm here.
    )annotationsN)datetime	timedelta)Pathz(https://www.googleapis.com/auth/calendarCAL_TZzAsia/Riyadhc                     t         j                  j                  dd      dk(  ryt        t         j                  j                  dd      z  } | j	                         S )z5Whether the calendar MCP should be exposed to Claude.MCP_GCAL10FGOOGLE_OAUTH_JSONgoogle_oauth.json)osenvironget_ROOTexists)oauths    @/Users/ahmed/devFolder/Ultron/claude-voice/voice/gcal_control.pyenabledr   %   sC    	zz~~j#&#-BJJNN#68KLLE<<>    c                    t         t         S ddlm}  ddlm} ddlm} ddlm} t        t        j                  j                  dd      z  }t        t        j                  j                  d	d
      z  }d}|j                         r| j                  t        |      t               }|r|j"                  s|r/|j$                  r#|j&                  r|j)                   |              n<t+        d       |j-                  t        |      t               }|j/                  d      }|j1                  |j3                                 |dd|d      a t         S )u   Return an authenticated Calendar API service, running the one-time
    browser consent if no cached token exists. Blocking — callers invoke it
    via ``asyncio.to_thread`` so the voice loop never stalls.Nr   )Credentials)InstalledAppFlow)Request)buildr   r   GOOGLE_TOKEN_JSONzgoogle_token.jsonug     [gcal] first-time Google sign-in — a browser window will open; approve Jarvis to see your calendar.)portcalendarv3F)credentialscache_discovery)_SVCgoogle.oauth2.credentialsr   google_auth_oauthlib.flowr   google.auth.transport.requestsr   googleapiclient.discoveryr   r   r   r   r   r   from_authorized_user_filestr_SCOPESvalidexpiredrefresh_tokenrefreshprintfrom_client_secrets_filerun_local_server
write_textto_json)r   r   r   r   
oauth_path
token_pathcredsflows           r   _servicer7   -   s   
 5:6/(;=PQQJ(;=PQQJE55c*owOU]]u':':MM')$ ? @#<<J*D))q)1Eemmo.TueLDKr   c                    dd| dgiS )z2Wrap a plain string as an MCP text content result.contenttext)typer:    )msgs    r   _textr>   M   s    5677r   c                n    |r|S t        j                  |       }|t        |      z   j                         S )zBResolve the end time: explicit `end` wins, else start + `minutes`.)minutes)r   fromisoformatr   	isoformat)startendr@   dts       r   	_end_fromrF   R   s3    

				&B7++6688r   c                 <   ddl ddlm} m}  | ddt        t        t        t
        t        t        t        d      dfd       } | dd	t        t        t        t
        t        t        d
      dfd       } | dddt
        i      dfd       } |dd|||g      S )z4Build and return the in-process ``gcal`` MCP server.r   N)toolcreate_sdk_mcp_servercalendar_create_eventzCreate a calendar event on Ahmed's Google Calendar. Times are local wall-clock in ISO form like '2026-07-08T15:00:00'. Give either `end` or `duration_minutes` (default 30). Attendees (comma-separated emails) get an invite emailed automatically.)titlerC   rD   duration_minutes	attendeesdescriptionlocationc                (  	K   	 t        | j                  dd            j                         xs d}t        | j                  dd            j                         }|st        d      S t	        || j                  d      xs dj                         xs d t        | j                  d      xs d            }t        | j                  d	d            j                  d
      D cg c]"  }|j                         r|j                         $ c}||t        d|t        dd	rD cg c]  }d|i c}	d	<   | j                  d      rt        | d         	d<   | j                  d      rt        | d         	d<   	fd}
j                  |       d {   }rddj                         nd}t        d| d| dt         d| d|j                  dd       
      S c c}w c c}w 7 T# t        $ r}t        d|       cY d }~S d }~ww xY ww)NrK    
(untitled)rC   z)create_event failed: no start time given.rD   rL      rM   ,dateTimetimeZone)summaryrC   rD   emailrN   rO   c                     t               } | j                         j                  drdnd      j                         S )Nprimaryallnone)
calendarIdbodysendUpdatesr7   eventsinsertexecutesvcrM   r_   s    r   _doz8build_server.<locals>.calendar_create_event.<locals>._do}   s=    jzz|**(t)2 + @@G	Jr   u    — invited , z	Created '' at  ()z. htmlLinkzcreate_event failed: )r(   r   stripr>   rF   intsplit_TZ	to_threadjoin	Exception)argsrK   rC   rD   erg   evwhorM   r_   asyncios           @@r   rJ   z+build_server.<locals>.calendar_create_event`   s     	6"-.446F,E"-.446EHIIEDHHUO$9r#@#@#B#Jd); < BCEC TXXk267==cBQB ()FGggi BQI !&+=$'S9D
 ;D$E9agq\9$E[!xx&&)$}*=&>]#xx
##&tJ'7#8Z J ((--B<EM$))I"6!782C9UG5r#auBFF:r235 6 6-Q %F .  	60455	6sn   HA"G. )H*A:G. $'G"G. (G'3A$G. G,A	G. !H"G. .	H7H
HH
HHcalendar_create_meetingzCreate a calendar event WITH a Google Meet video link and return the link. Same fields as create_event; attendees (comma-separated emails) are emailed the invite + Meet link automatically.)rK   rC   rD   rL   rM   rN   c                0  	
K   	 t        | j                  dd            j                         xs d}t        | j                  dd            j                         }|st        d      S t	        || j                  d      xs dj                         xs d t        | j                  d      xs d            }t        | j                  d	d            j                  d
      D cg c]"  }|j                         r|j                         $ c}	||t        d|t        ddt        j                         j                  ddidid
	r	D cg c]  }d|i c}
d	<   | j                  d      rt        | d         
d<   	
fd}j                  |       d {   }|j                  dd      }	rddj                  	       nd}t        d| d| dt         d| d| 
      S c c}w c c}w 7 V# t        $ r}t        d|       cY d }~S d }~ww xY ww)NrK   rQ   MeetingrC   z+create_meeting failed: no start time given.rD   rL   rS   rM   rT   rU   createRequestr;   hangoutsMeet)	requestIdconferenceSolutionKey)rX   rC   rD   conferenceDatarY   rN   c                     t               } | j                         j                  ddrdnd      j                         S )Nr[      r\   r]   )r^   r_   conferenceDataVersionr`   ra   re   s    r   rg   z:build_server.<locals>.calendar_create_meeting.<locals>._do   sD    jzz|**(t*+)2 + @ AH	Jr   hangoutLinkz(no link returned)z
, invited rh   z	Meeting 'ri   rj   rk   z. Meet link: zcreate_meeting failed: )r(   r   rm   r>   rF   rn   ro   rp   uuiduuid4hexrq   rr   rs   )rt   rK   rC   rD   ru   rg   rv   linkrw   rM   r_   rx   s            @@r   ry   z-build_server.<locals>.calendar_create_meeting   s    $	8"-.446C)E"-.446EJKKEDHHUO$9r#@#@#B#Jd); < BCEC TXXk267==cBQB ()FGggi BQI !&+=$'S9#2!%!1!1.4n-E5 #	D ;D$E9agq\9$E[!xx&&)$}*=&>]#J ((--B66-)=>D9BJtyy345C9UG5r#au E''+f. / /5Q %F .
  	821#677	8so   HA"G2 )H*A:G2 $'G&AG2 G+AG2 G0AG2 %H&G2 2	H;HH	HHHcalendar_listzXList upcoming events on Ahmed's calendar over the next `days` days (default 7, max ~30).daysc           
     :  K   	 t        dt        t        | j                  d      xs d      d            fd}j	                  |       d {   }|st        d d      S g }|D ]m  }|j                  di       j                  d	      xs" |j                  di       j                  d
d      }|j                  | d|j                  dd              o t        dj                  |            S 7 # t        $ r}t        d|       cY d }~S d }~ww xY ww)Nr   r      rS   c                 F   t               } ddlm} t        j                  |j                        }|j                         }|t              z   j                         }| j                         j                  d||ddd      j                         }|j                  d	g       S )
Nr   )timezone)r   r[   T	startTime2   )r^   timeMintimeMaxsingleEventsorderBy
maxResultsitems)r7   r   r   nowutcrB   r   rb   listrd   r   )rf   r   r   tmintmaxresr   s         r   rg   z0build_server.<locals>.calendar_list.<locals>._do   s    j-ll8<<0}}iT22==?jjl''($!%{! ( # $+79  www++r   zNo events in the next z days.rC   rV   date?u    — rX   rR   
zcalendar_list failed: )	maxminrn   r   rq   r>   appendrr   rs   )	rt   rg   r   linesrv   whenru   r   rx   s	          @r   r   z#build_server.<locals>.calendar_list   s    	7q#c$((6"2"7a8"=>D, "++C00E5dV6BCCEvvgr*..  H#%66'2#6#:#:63#G vU266)\+J*KLM  5)** 1  	71!566	7sT   DAC7 C5C7 &D'BC7 4D5C7 7	D DDDDDgcalz1.0.0)nameversiontools)rt   dictreturnr   )rx   claude_agent_sdkrH   rI   r(   rn   )rH   rI   rJ   ry   r   rx   s        @r   build_serverr   Z   s    <	
!9 #csCSBC!6C!6F 

#F #csC1	2%82%8N 
/"$*C=2727: !$&=}M r   )r   bool)r=   r(   r   r   )rC   r(   rD   z
str | Noner@   rn   r   r(   )__doc__
__future__r   r   r   r   r   pathlibr   __file__resolveparentr   r)   r   r   rp   r"   r   r7   r>   rF   r   r<   r   r   <module>r      su   * # 	  ( X ''..5
6jjnnX}-@8
9Ar   