+
    i<              	         a  RJ t;0 t R t^ RIt^ RIt^ RIt^ RIt^ RIHt ^ RIH	t	 ^ RI
HtHtHtHt ]P                  P!                  R4      t]R8X  d   RtRtMB]'       d   ]	! ]4      t]R,          tM(]	P(                  ! 4       R,          R	,          t]R,          t]	! ]P                  P!                  R
]! ]	P(                  ! 4       R,          R,          4      4      4      t]RK,          t]RL,          tRt] ^ k Rt] ^k Rt] ^k Rt] ^k Rt] ^k Rt] ^k Rt] ^k ]! RR7       ! R R4      4       t R R lt!R R lt"RMR R llt#R R lt$]3R  R! llt%R" R# lt&R$ R% lt'R& R' lt(R( R) lt)R* R+ lt*R, R- lt+R. R/ lt,R0 R1 lt-R2 R3 lt.R4 R5 lt/RNR6 R7 llt0R8 R9 lt1R: R; lt2R< R= lt3R> R? lt4R@ RA lt5RB RC lt6RD RE lt7RF RG lt8]5t9RH RI lt:R# )Oz8Environment and API key management for last30days skill.N)	dataclass)Path)OptionalDictAnyLiteralLAST30DAYS_CONFIG_DIR z.envz.config
last30daysCODEX_AUTH_FILEz.codexz	auth.jsonapi_keycodexnoneokmissingexpiredmissing_account_idT)frozenc                   ,   a  ] tR t^)t o V 3R ltRtV tR# )
OpenAIAuthc                v   < V ^8  d   Qh/ S[ S[,          ;R&   S[;R&   S[;R&   S[ S[,          ;R&   S[;R&   # )   tokensourcestatus
account_idcodex_auth_file)r   str
AuthSource
AuthStatus)format__classdict__s   "O/Users/bowang/.openclaw/workspace/skills/last30days-official/scripts/lib/env.py__annotate__OpenAIAuth.__annotate__)   sL     C=    	 
         N)__name__
__module____qualname____firstlineno____annotate_func____static_attributes____classdictcell__)r!   s   @r"   r   r   )   s      r%   r   c                R    V ^8  d   QhR\         R\        \        \        3,          /# r   pathreturn)r   r   r   )r    s   "r"   r#   r#   2   s"       c3h r%   c                   / pV P                  4       '       g   V# \        V R4      ;_uu_ 4       pV F  pVP                  4       pV'       d   VP                  R4      '       d   K4  RV9   g   K=  VP	                  R4      w  rEpVP                  4       pVP                  4       pV'       d)   V^ ,          R9   d   VR,          V^ ,          8X  d   V^R pV'       g   K  V'       g   K  WaV&   K  	  RRR4       V#   + '       g   i     T# ; i)z'Load environment variables from a file.r#=N)"')existsopenstrip
startswith	partition)r0   envflinekey_values   &      r"   load_env_filerD   2   s    
C;;==
	dCAD::<D4??3//d{ $s 3iikU1X3b	U1X8M!!BKE355$H  
 J 
 Js$   4C9$C9/A)C9C9'C99D
	c                h    V ^8  d   QhR\         R\        \        \         \        3,          ,          /# )r   r   r1   )r   r   r   r   )r    s   "r"   r#   r#   I   s'      s xS#X'? r%   c                6    V P                  R4      p\        V4      ^8  d   R# V^,          pR\        V4      ) ^,          ,          p\        P                  ! W#,           4      p\        P
                  ! VP                  R4      4      #   \         d     R# i ; i)z(Decode JWT payload without verification..Nr5   zutf-8)splitlenbase64urlsafe_b64decodejsonloadsdecode	Exception)r   partspayload_b64paddecodeds   &    r"   _decode_jwt_payloadrT   I   s    	C u:>Ahc+&&*+**;+<=zz'..122 s   !B	 A#B	 	BBc                <    V ^8  d   QhR\         R\        R\        /# )r   r   leeway_secondsr1   )r   intbool)r    s   "r"   r#   r#   W   s!     1 1# 1s 1D 1r%   c                    \        V 4      pV'       g   R# VP                  R4      pV'       g   R# V\        P                  ! 4       V,           8*  # )zCheck if JWT token is expired.Fexp)rT   gettime)r   rV   payloadrZ   s   &&  r"   _token_expiredr^   W   s<    !%(G
++e
C499;/00r%   c                F    V ^8  d   QhR\         R\        \         ,          /# )r   access_tokenr1   )r   r   )r    s   "r"   r#   r#   b   s      S Xc] r%   c                    \        V 4      pV'       g   R# VP                  R/ 4      p\        V\        4      '       d   VP                  R4      # R# )z*Extract chatgpt_account_id from JWT token.Nzhttps://api.openai.com/authchatgpt_account_id)rT   r[   
isinstancedict)r`   r]   
auth_claims   &  r"   extract_chatgpt_account_idrf   b   sC    !,/G:B?J*d##~~233r%   c                R    V ^8  d   QhR\         R\        \        \        3,          /# r/   )r   r   r   r   )r    s   "r"   r#   r#   m   s"      $ T#s(^ r%   c                    V P                  4       '       g   / #  \        V R4      ;_uu_ 4       p\        P                  ! V4      uuRRR4       #   + '       g   i     R# ; i  \         d    / u # i ; i)zLoad Codex auth JSON.r3   N)r9   r:   rL   loadrO   )r0   r?   s   & r"   load_codex_authrj   m   sN    ;;==	$__99Q< ___ 	s.   A# A
A# A 	A#  A# #A32A3c                \    V ^8  d   QhR\         \        \        ,          \        3,          /# r   r1   )tupler   r   )r    s   "r"   r#   r#   x   s!     ! !hsmS&8 9 !r%   c                 ^   \        4       p Rp\        V \        4      '       d\   V P                  R4      ;'       g    / p\        V\        4      '       d   VP                  R4      pV'       g   V P                  R4      pV'       g	   R\        3# \        V4      '       d	   R\        3# V\        3# )zsGet Codex access token from auth.json.

Returns:
    (token, status) where status is 'ok', 'missing', or 'expired'
Ntokensr`   )rj   rc   rd   r[   AUTH_STATUS_MISSINGr^   AUTH_STATUS_EXPIREDAUTH_STATUS_OK)authr   ro   s      r"   get_codex_access_tokenrt   x   s     DE$(#))rfd##JJ~.EHH^,E(((e(((.  r%   c                R    V ^8  d   QhR\         \        \        3,          R\        /# )r   file_envr1   )r   r   r   )r    s   "r"   r#   r#      s"     % %d38n % %r%   c           	        \         P                  P                  R4      ;'       g    V P                  R4      pV'       d&   \        V\        \
        R\        \        4      R7      # \        4       w  r#V'       d^   \        V4      pV'       d&   \        V\        \
        V\        \        4      R7      # \        R\        \        R\        \        4      R7      # \        R\        VR\        \        4      R7      # )z0Resolve OpenAI auth from API key or Codex login.OPENAI_API_KEYN)r   r   r   r   r   )osenvironr[   r   AUTH_SOURCE_API_KEYrr   r   r   rt   rf   AUTH_SOURCE_CODEXAUTH_STATUS_MISSING_ACCOUNT_IDAUTH_SOURCE_NONE)rv   r   codex_tokencodex_statusr   s   &    r"   get_openai_authr      s    jjnn-.PP(,,?O2PG&!0
 	
 !7 8K/<
!(%% #O 4  $10
 	
 O, r%   c                F    V ^8  d   QhR\         \        \        3,          /# rl   r   r   r   )r    s   "r"   r#   r#      s     " "DcN "r%   c            
     d   \         '       d   \        \         4      M/ p \        V 4      pRVP                  RVP                  RVP
                  RVP                  RVP                  /p. ROpV F?  w  rE\        P                  P                  V4      ;'       g    V P                  WE4      W$&   KA  	  V# )zBLoad configuration from ~/.config/last30days/.env and environment.rx   OPENAI_AUTH_SOURCEOPENAI_AUTH_STATUSOPENAI_CHATGPT_ACCOUNT_IDr   ))XAI_API_KEYN)OPENROUTER_API_KEYN)PARALLEL_API_KEYN)BRAVE_API_KEYN)OPENAI_MODEL_POLICYauto)OPENAI_MODEL_PINN)XAI_MODEL_POLICYlatest)XAI_MODEL_PINN)SCRAPECREATORS_API_KEYN)APIFY_API_TOKENN)
AUTH_TOKENN)CT0N)CONFIG_FILErD   r   r   r   r   r   r   ry   rz   r[   )rv   openai_authconfigkeysrA   defaults         r"   
get_configr      s     .9[}[)bH!(+K 	+++k00k00#[%;%;;66FD jjnnS)GGX\\#-G  Mr%   c                $    V ^8  d   QhR\         /# rl   rX   )r    s   "r"   r#   r#      s        t  r%   c                 *    \         P                  4       # )z#Check if configuration file exists.)r   r9   r&   r%   r"   config_existsr      s    r%   c                R    V ^8  d   QhR\         \        \        3,          R\        /# r   r   r1   r   r   r   rX   )r    s   "r"   r#   r#      s"        S#X  4  r%   c                    \        V P                  R4      4      p\        V P                  R4      4      ;'       d    V P                  R4      \        8H  pT;'       g    T# )zbCheck if Reddit search is available.

Reddit can use either ScrapeCreators (preferred) or OpenAI.
r   rx   r   )rX   r[   rr   )r   has_sc
has_openais   &  r"   is_reddit_availabler      sR    
 &**567Ffjj!123jj

CW8X\j8jJZr%   c                h    V ^8  d   QhR\         \        \        3,          R\        \        ,          /# r   r   r   r   r   )r    s   "r"   r#   r#      s&      d38n # r%   c                    V P                  R4      '       d   R# V P                  R4      '       d   V P                  R4      \        8X  d   R# R# )zDetermine which Reddit backend to use.

Priority: ScrapeCreators (cheaper, faster) > OpenAI (legacy)

Returns: 'scrapecreators', 'openai', or None
r   scrapecreatorsrx   r   openaiN)r[   rr   r   s   &r"   get_reddit_sourcer      s>     zz*++zz"##

3G(HN(Zr%   c                R    V ^8  d   QhR\         \        \        3,          R\        /# r   r   )r    s   "r"   r#   r#      s"      $sCx. S r%   c                   \        V 4      p\        V P                  R4      4      p\        V 4      pV'       d   V'       d   V'       d   R# R# V'       d   V'       d   R# R# V'       d   V'       d   R# R# V'       d   R# R# )	zDetermine which sources are available based on API keys.

Returns: 'all', 'both', 'reddit', 'reddit-web', 'x', 'x-web', 'web', or 'none'
r   allboth
reddit-webredditx-webxweb)r   rX   r[   has_web_search_keys)r   
has_reddithas_xaihas_webs   &   r"   get_available_sourcesr      sf    
 %V,J6::m,-G!&)Ggu+V+	&|4H4	!w*s*	r%   c                R    V ^8  d   QhR\         \        \        3,          R\        /# r   r   )r    s   "r"   r#   r#     s&     s sS#X s4 sr%   c                    \        V P                  R4      ;'       g+    V P                  R4      ;'       g    V P                  R4      4      # )z0Check if any web search API keys are configured.r   r   r   rX   r[   r   s   &r"   r   r     s>    

/0qqFJJ?Q4RqqV\V`V`apVqrrr%   c                h    V ^8  d   QhR\         \        \        3,          R\        \        ,          /# r   r   )r    s   "r"   r#   r#     s&      $sCx. Xc] r%   c                    V P                  R4      '       d   R# V P                  R4      '       d   R# V P                  R4      '       d   R# R# )zDetermine the best available web search backend.

Priority: Parallel AI > Brave > OpenRouter/Sonar Pro

Returns: 'parallel', 'brave', 'openrouter', or None
r   parallelr   braver   
openrouterNr[   r   s   &r"   get_web_search_sourcer     s?     zz$%%zz/""zz&''r%   c                R    V ^8  d   QhR\         \        \        3,          R\        /# r   r   )r    s   "r"   r#   r#   !  s"      T#s(^  r%   c                Z   \        V 4      p\        V P                  R4      4      p\        V 4      p^RIHp VP                  4       ;'       d    VP                  4       pT;'       g    TpV'       d   V'       d   V'       d   R# V'       d   V'       d   R# V'       d   R# V'       d   R# R# )zDetermine which sources are missing (accounting for Bird and ScrapeCreators).

Returns: 'all', 'both', 'reddit', 'x', 'web', or 'none'
r   bird_xr   r   r   r   r   )r   rX   r[   r   r	   r   is_bird_installedis_bird_authenticated)r   r   r   r   r   has_birdhas_xs   &      r"   get_missing_keysr   !  s}    
 %V,J6::m,-G!&)G '')LLf.J.J.LHxEe			r%   c                    V ^8  d   QhR\         R\         R\        R\        \         \        \         ,          3,          /# )r   	requested	availableinclude_webr1   )r   rX   rm   r   )r    s   "r"   r#   r#   <  sA     A A A A$ ASXY\^fgj^kYkSl Ar%   c                   VR8X  d   V R8X  d   R# V R8X  d   R# R# VR8X  d   V R8X  d   R# V R8X  d   R# R# V R8X  d(   V'       d   VR8X  d   R# VR8X  d   R# VR8X  d   R# VR3# V R8X  d   R# V R8X  d'   VR9  d   VR8X  d   RMR	pRR
V R23# V'       d   R# R# V R8X  d   VR8X  d   R# V'       d   R# R# V R8X  d   VR8X  d   R# V'       d   R# R# V R3# )a  Validate requested sources against available keys.

Args:
    requested: 'auto', 'reddit', 'x', 'both', or 'web'
    available: Result from get_available_sources()
    include_web: If True, add WebSearch to available sources

Returns:
    Tuple of (effective_sources, error_message)
r   r   r   Nr   r   r   xAIOpenAIzRequested both sources but z: key is missing. Use --sources=auto to use available keys.)r   zWNo API keys configured. The assistant can still search the web if it has a search tool.)r   N)r   zKNo API keys configured. Add keys to ~/.config/last30days/.env for Reddit/X.)r   zgOnly web search keys configured. Add OPENAI_API_KEY (or run codex login) for Reddit, XAI_API_KEY for X.)r   N)r   N)r   N)r   )r   N)r   z/Requested Reddit but only xAI key is available.)r   N)r   z-Requested X but only OpenAI key is available.)r   Nr&   )r   r   r   r   s   &&& r"   validate_sourcesr   <  sD    Fss%hh E% D  DFF"""h&))c!$$$EFI%(H4e(G8	A{|||HLL%%C JJ  d?r%   c                h    V ^8  d   QhR\         \        \        3,          R\        \        ,          /# r   r   )r    s   "r"   r#   r#     s&      c3h HSM r%   c                    ^RI Hp VP                  4       '       d   VP                  4       pV'       d   R# V P	                  R4      '       d   R# R# )u  Determine the best available X/Twitter source.

Priority: Bird (free) → xAI (paid API)

Args:
    config: Configuration dict from get_config()

Returns:
    'bird' if Bird is installed and authenticated,
    'xai' if XAI_API_KEY is configured,
    None if no X source available.
r   birdr   xaiN)r	   r   r   r   r[   )r   r   usernames   &  r"   get_x_sourcer     sC      !!//1 zz-  r%   c                $    V ^8  d   QhR\         /# rl   r   )r    s   "r"   r#   r#     s     + +D +r%   c                 .    ^RI Hp  V P                  4       # )z0Check if yt-dlp is installed for YouTube search.
youtube_yt)r	   r   is_ytdlp_installedr   s    r"   is_ytdlp_availabler     s    ((**r%   c                $    V ^8  d   QhR\         /# rl   r   )r    s   "r"   r#   r#            r%   c                     R# )zjCheck if Hacker News source is available.

Always returns True - HN uses free Algolia API, no key needed.
Tr&   r&   r%   r"   is_hackernews_availabler         
 r%   c                $    V ^8  d   QhR\         /# rl   r   )r    s   "r"   r#   r#     r   r%   c                     R# )zbCheck if Polymarket source is available.

Always returns True - Gamma API is free, no key needed.
Tr&   r&   r%   r"   is_polymarket_availabler     r   r%   c                R    V ^8  d   QhR\         \        \        3,          R\        /# r   r   )r    s   "r"   r#   r#     s&     W WS#X W4 Wr%   c                h    \        V P                  R4      ;'       g    V P                  R4      4      # )zCheck if TikTok source is available (ScrapeCreators or legacy Apify).

Returns True if SCRAPECREATORS_API_KEY or APIFY_API_TOKEN is set.
r   r   r   r   s   &r"   is_tiktok_availabler     s+    
 

34UU

CT8UVVr%   c                R    V ^8  d   QhR\         \        \        3,          R\        /# r   r   )r    s   "r"   r#   r#     s&     W WT#s(^ W Wr%   c                j    V P                  R4      ;'       g    V P                  R4      ;'       g    R# )zBGet TikTok API token, preferring ScrapeCreators over legacy Apify.r   r   r	   r   r   s   &r"   get_tiktok_tokenr     s-    ::./VV6::>O3PVVTVVr%   c                R    V ^8  d   QhR\         \        \        3,          R\        /# r   r   )r    s   "r"   r#   r#     s"     6 64S> 6d 6r%   c                6    \        V P                  R4      4      # )zCheck if Instagram source is available (ScrapeCreators).

Returns True if SCRAPECREATORS_API_KEY is set.
Instagram uses the same key as TikTok.
r   r   r   s   &r"   is_instagram_availabler     s     

3455r%   c                R    V ^8  d   QhR\         \        \        3,          R\        /# r   r   )r    s   "r"   r#   r#     s"     6 6S#X 63 6r%   c                8    V P                  R4      ;'       g    R# )z<Get Instagram API token (same ScrapeCreators key as TikTok).r   r	   r   r   s   &r"   get_instagram_tokenr     s    ::./5525r%   c                t    V ^8  d   QhR\         \        \        3,          R\         \        \        3,          /# r   r   )r    s   "r"   r#   r#     s*      S#X 4S> r%   c                    ^RI Hp VP                  4       p\        V P	                  R4      4      pVR,          '       d   RpMV'       d   RpMRpRVRVR	,          R
VR,          RVR,          RVRVR,          /# )zGet detailed X source status for UI decisions.

Returns:
    Dict with keys: source, bird_installed, bird_authenticated,
    bird_username, xai_available, can_install_bird
r   r   authenticatedr   r   Nr   bird_installed	installedbird_authenticatedbird_usernamer   xai_availablecan_install_birdcan_install)r	   r   get_bird_statusrX   r[   )r   r   bird_statusr   r   s   &    r"   get_x_source_statusr    s     ((*KM23M ?##	 	&+k2k/:Z0K6 r%   c                0   V ^8  d   Qh/ ^ \         9   d
   \        ;R&   ^\         9   d
   \        ;R&   ^\         9   d
   \        ;R&   ^\         9   d
   \        ;R&   ^\         9   d
   \        ;R&   ^\         9   d
   \        ;R&   ^\         9   d
   \        ;R&   # )r   r{   r|   r~   rr   rp   rq   r}   )__conditional_annotations__r   r   )r    s   "r"   r#   r#      s     > >< , +Z += ?> ( ': '? ?@ & %* %A ?D " !
 !E ?F , +Z +G ?H , +Z +I ?J B A
 AK ?r%   )r   r   r   )r   r   r   r   )<   )F)<r  __doc__rJ   rL   ry   r\   dataclassesr   pathlibr   typingr   r   r   r   rz   r[   _config_override
CONFIG_DIRr   homer   r   r   r   r{   r|   r~   rr   rp   rq   r}   r   rD   rT   r^   rf   rj   rt   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   is_apify_availabler  r#   )r  s   @r"   <module>r     s   >   	  !  / /
 ::>>"9: rJK&'Jv%Ky(<7Jv%Krzz~~&7TYY[8=SVa=a9bcd/0
EF
"+  + '  '%  %! !"+  +"+  +-A  A $  .1 "1 !*%P"J 
 *s
 6AH:+WW
66 ) r%   