
    `gj<                       U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
mZ ddlmZmZ ddlmZ ddlmZ ddlmZmZmZ dd	lmZ dd
lmZmZ  ej8                  e      ZdZdZ de de  dZ!dZ"d8dZ# ed       G d d             Z$dZ%dZ&dZ'dZ(dZ)d9dZ*	 d:	 	 	 	 	 d;dZ+e G d d             Z,i Z-de.d <    ej^                         Z0d<d!Z1d=d"Z2e!fdddd#	 	 	 	 	 	 	 	 	 	 	 d>d$Z3ddddd%	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d?d&Z4d'Z5d@d(Z6d)d*	 	 	 	 	 	 	 dAd+Z7dddddd)d,	 	 	 	 	 	 	 	 	 	 	 	 	 dBd-Z8e G d. d/             Z9 e9       Z: ej^                         Z;da<d0e.d1<   dCd2Z=dDd3Z>dEdFd4Z?ddd5d)d6	 	 	 	 	 	 	 	 	 dGd7Z@y)Hu  Browser sign-in flow for the Honcho memory provider — no CLI step.

``begin_authorization`` / ``complete_authorization`` are the transport-agnostic
core: the code can arrive via the loopback listener here or a future
``hermes://`` handler. Endpoints are env-overridable with local-dev defaults
because ``/authorize`` (dashboard) and ``/oauth/token`` (API) live on
different origins.
    )annotationsN)	dataclass)BaseHTTPRequestHandler
HTTPServerPath)Callable)parse_qs	urlencodeurlparse)oauth)resolve_active_hostresolve_config_path	127.0.0.1i="  http://:	/callbackiX  c                    ddl m}  |t        |             }	 dt        |j                  |j	                                     z   S # t
        $ r |j                  cY S w xY w)u%  Home-relative display string for the consent screen.

    The absolute path (username + home layout) never leaves the machine — it's
    only shown to the user. Collapse ``$HOME`` to ``~``; for a path outside
    home, send the bare filename rather than leak an arbitrary absolute path.
    r   r   z~/)pathlibr   strrelative_tohome
ValueErrorname)path_Pathps      S/root/.hermes/venv/lib/python3.12/site-packages/plugins/memory/honcho/oauth_flow.py_display_config_pathr   )   sQ     &c$iAc!--

5666 vvs   *A AAT)frozenc                  :    e Zd ZU dZded<   ded<   ded<   ded<   y)OAuthEndpointsz7Resolved authorization-server URLs and client identity.r   authorize_url	token_url	client_idscopeN)__name__
__module____qualname____doc____annotations__     r   r"   r"   9   s    ANNJr-   r"   zhttps://app.honcho.devz"https://api.honcho.dev/oauth/tokenzhttp://localhost:3000z!http://localhost:8000/oauth/tokenzhermes-agentc                F     t               xr t         fddD              S )Nc              3  &   K   | ]  }|v  
 y wNr,   ).0hurls     r   	<genexpr>z#_is_loopback_url.<locals>.<genexpr>P   s     Q!Q#XQs   )	localhostr   z::1)boolany)r3   s   `r   _is_loopback_urlr8   O   s    9QQ/PQQQr-   c           	        | |7	 ddl m} |j                         }| xs |j                  } ||n|j                  }| xs dj                         dk(  xs t        |      }|rt        nt        }|rt        nt        }|r|s|j                  d       d}t        j                  j                  d|      j                  d      }t!        t        j                  j                  d	| d
      t        j                  j                  d|      t        j                  j                  dt"              t        j                  j                  dd            S # t
        $ r
 | xs d} Y 'w xY w)u  Resolve OAuth endpoints, zero-config by default.

    Keys off the host's honcho ``environment`` (production → cloud, local →
    localhost); a self-hosted ``base_url`` derives the token endpoint from the
    API host. Env vars override every field for unusual deployments.
    r   HonchoClientConfig
production local/z/oauth/tokenHONCHO_OAUTH_DASHBOARDHONCHO_OAUTH_AUTHORIZE_URLz
/authorizeHONCHO_OAUTH_TOKEN_URLHONCHO_OAUTH_CLIENT_IDHONCHO_OAUTH_SCOPEwrite)r#   r$   r%   r&   )plugins.memory.honcho.clientr;   from_global_configenvironmentbase_url	Exceptionlowerr8   _LOCAL_DASHBOARD_CLOUD_DASHBOARD_LOCAL_TOKEN_URL_CLOUD_TOKEN_URLrstriposenvirongetr"   _DEFAULT_CLIENT_ID)rH   rI   r;   cfgis_localdefault_dashboarddefault_token	dashboards           r   resolve_endpointsrZ   S   s9    h.	6G$779C%8K#+#7xS\\H !r((*g5S9I(9SH,4(:J(0$6FM#??3/0=

79JKRRSVWIjjnn%Ai[PZC[\**..!9=I**..!9;MNjjnn17;	   	6%5K	6s   6E E$#E$c                  ,    e Zd ZU ded<   ded<   ded<   y)_Pendingr   verifierredirect_urifloat
created_atN)r'   r(   r)   r+   r,   r-   r   r\   r\   v   s    Mr-   r\   zdict[str, _Pending]_pendingc                     t        j                  d      } t        j                  t	        j
                  | j                               j                               j                  d      j                         }| |fS )zDReturn (verifier, S256 challenge) for an authorization-code request.@      =)
secretstoken_urlsafebase64urlsafe_b64encodehashlibsha256encodedigestrP   decode)r]   	challenges     r   _pkcero      s[    $$R(H  0A!B!I!I!KL		 
 Yr-   c                    t         j                         D cg c]  \  }}| |j                  z
  t        kD  s|  }}}|D ]  }t         j	                  |d         y c c}}w r0   )ra   itemsr`   _PENDING_TTL_SECONDSpop)nowsr   expiredstates        r   _prune_pendingrx      sT    %^^-[TQq||1CFZ1Zq[G[ "UD!" \s
   AA)sourceconfig_pathrt   c               |   |t        j                          n|}t               \  }}t        j                  d      }t        5  t        |       t        |||      t        |<   ddd       | j                  || j                  |dd|d}|r||d<   |r||d<   | j                   d	t        |       |fS # 1 sw Y   QxY w)
a  Start an authorization: return ``(authorize_url, state)`` and stash PKCE.

    ``source`` tags the authorize link with the initiating surface
    (``hermes-desktop`` / ``hermes-cli``) so the consent side can attribute
    connects and vary behavior per surface. ``config_path`` is a home-relative
    *display* string for the consent screen (never the absolute path); callers
    pass the actual write path separately to ``complete_authorization``.
    N    )r]   r^   r`   S256code)r%   r^   r&   code_challengecode_challenge_methodresponse_typerw   ry   rz   ?)timero   re   rf   _pending_lockrx   r\   ra   r%   r&   r#   r   )		endpointsr^   ry   rz   rt   r]   rn   rw   paramss	            r   begin_authorizationr      s      $))+#C'Hi!!"%E	 as"H<\_`a (($#!'F !x +}%%&a	&(9':;UBB!a as   !B22B;)rz   hostapply_configrt   c          	        t         5  t        j                  |d      }ddd       t        d      t	        j
                  | j                  d| j                  ||j                  |j                  dt        j                        }|xs
 t               }	|xs
 t               }
t	        j                  |	|
|| j                  | j                  ||      }ddlm}  |        t         j#                  d|
       |S # 1 sw Y   xY w)	u   Exchange ``code`` for a grant and persist it. Raises on bad state/exchange.

    ``apply_config=False`` stores the tokens only, skipping the grant's config
    block — the CLI path, where settings stay wizard-owned.
    Nz&unknown or expired authorization stateauthorization_code)
grant_typer%   r~   r^   code_verifier)r%   token_endpointr   rt   r   )reset_honcho_clientz(Honcho OAuth grant installed for host %s)r   ra   rs   r   r   _http_post_formr$   r%   r^   r]   _REFRESH_TIMEOUT_SECONDSr   r   install_grantrF   r   loggerinfo)r   r~   rw   rz   r   r   rt   pendinggrantr   target_hostcredr   s                r   complete_authorizationr      s     
 ,,,ud+,ABB!!.",,#00$--	
 	&&
E /-/D/-/K%% **!D A
KK:KHKC, ,s   C77D s+  <!doctype html><meta charset=utf-8><title>Honcho connected</title><body style='font:14px ui-monospace,monospace;background:#0b0e14;color:#c9d1d9;display:flex;align-items:center;justify-content:center;height:100vh;margin:0'><div>Connected to Honcho. You can close this tab and return to Hermes.</div>c                     i  G fddt               } 	 t        t        t        f|       }|fS # t        $ r t        t        df|       }Y |fS w xY w)uS  Bind the one-shot callback server, returning it and its capture dict.

    Prefers :8765; if that's taken, falls back to an OS-assigned port. groudon's
    redirect matcher relaxes the port for loopback hosts, so the fallback still
    matches the seeded ``127.0.0.1`` redirect URI — the caller advertises the
    actual bound port.
    c                      e Zd Z fdZd Zy)'_bind_loopback_server.<locals>._Handlerc                   t        | j                        }|j                  dk7  r"| j                  d       | j                          y t	        |j
                        }|j                  d      xs dgd   d<   |j                  d      xs dgd   d<   |j                  d      xs dgd   d<   | j                  d       | j                  d	d
       | j                          | j                  j                  t               y )Nr   i  r~   r=   r   rw   error   zContent-Typeztext/html; charset=utf-8)r   r   send_responseend_headersr
   queryrS   send_headerwfilerE   _CALLBACK_HTML)selfparsedr   captureds      r   do_GETz._bind_loopback_server.<locals>._Handler.do_GET   s    dii(F{{k)""3'  "fll+F &

6 2 :rdA>HV!'G!4!<a @HW!'G!4!<a @HWs#^-GHJJ^,r-   c                     y r0   r,   )r   argss     r   log_messagez3_bind_loopback_server.<locals>._Handler.log_message  s    r-   N)r'   r(   r)   r   r   )r   s   r   _Handlerr      s    	-	r-   r   r   )r   r   LOOPBACK_HOSTLOOPBACK_PORTOSError)r   serverr   s     @r   _bind_loopback_serverr      sg      "H) &:]M:HE 8  :]A.98:s   0 AAg     r@timeoutc                  || _         	 t        j                         |z   }d|vrCt        j                         |k  r,| j                          d|vrt        j                         |k  r,| j	                          |j                  d      rt        d|d          d|vrt        d      |d   |j                  dd      fS # | j	                          w xY w)zServe a single ``/callback`` GET on ``server`` and return ``(code, state)``.

    Replies with a close-this-tab page, then stops. Raises ``TimeoutError`` if no
    callback arrives within ``timeout``.
    r~   r   zauthorization denied: z)no OAuth callback received before timeoutrw   r=   )r   r   	monotonichandle_requestserver_closerS   r   TimeoutError)r   r   r   deadlines       r   capture_loopback_coder     s     FN >>#g-H$)9H)D!!# H$)9H)D 	||G1(72C1DEFFXFGGFX\\'2666 	s   AB? ?C)rz   r   ry   r   open_urlr   c                   t               \  }}dt         d|j                  d    d}t               }	| xs
 t	               }
t        |	||t        |
            \  }ddl}|j                  t        j                  fdd	
      }|j                          t        |||      \  }}||k7  rt        d      t        |	|||
||      S )uT  Drive the full loopback flow: open browser → capture code → exchange → persist.

    ``open_url`` defaults to the system browser; tests inject a driver that
    follows the authorize redirect into the loopback callback. It always
    receives the authorize URL, so a CLI caller can also print it for
    browserless environments.
    r   r      r   )ry   rz   Nr   c                             S r0   r,   )r#   r   s   r   <lambda>z(authorize_via_loopback.<locals>.<lambda>S  s    Xm-D r-   T)targetdaemonr   u0   OAuth state mismatch — possible CSRF, aborting)rz   r   r   )r   r   server_addressrZ   r   r   r   
webbrowseropen	threadingThreadstartr   r   r   )rz   r   ry   r   r   r   r   r   r^   r   r   rw   r   openerr~   returned_stater#   s       `           @r   authorize_via_loopbackr   1  s    $ -.FH]O1V-B-B1-E,FiPL!#I/-/D.<<PQU<VM5 ?? %DTRF
LLN07SD.KLL!! r-   c                  *    e Zd ZU dZded<   dZded<   y)
FlowStatusidler   rw   r=   detailN)r'   r(   r)   rw   r+   r   r,   r-   r   r   r   h  s    E3FCr-   r   zthreading.Thread | None_flow_threadc                 $   	 ddl m}  | j                         }|j                  j	                  d      xs i j	                  |j
                        xs i }t        j                  j                  |      y|j                  ry	 y# t        $ r Y yw xY w)zJReport whether a credential is already stored: 'oauth', 'apikey', or none.r   r:   hosts)Tr   )Tapikey)FN)rF   r;   rG   rawrS   r   r   OAuthCredentialfrom_host_blockapi_keyrJ   )r;   rU   blocks      r   _detect_connectionr   s  s    
C 335W%+00:@b  007C ;;!    s   A1B 4B 	BBc                     t         5  t        j                  t        j                  }} d d d        t	               \  }} ||dS # 1 sw Y   xY w)Nrw   r   	connectedauth)_status_lock_statusrw   r   r   r   s       r   get_flow_statusr     sF    	 6w~~v6(*OItf9dSS6 6s   !AAc                h    t         5  | |ct        _        t        _        d d d        y # 1 sw Y   y xY wr0   )r   r   rw   r   rw   r   s     r   _set_statusr     s*    	 6(-v%w~6 6 6s   (1zhermes-desktoprz   r   ry   r   c                     xs
 t                xs
 t               t        5  t        j                  dk(  rDt
        r>t
        j                         r*t        j                  t        j                  dcddd       S d\  t        _        t        _        ddd       d	 fd}t        j                  |dd      at
        j                          t               S # 1 sw Y   HxY w)
u   Launch the loopback flow in a daemon thread; returns the initial status.

    Idempotent while a flow is pending — a second call is a no-op so a
    double-clicked button can't open two browser tabs / bind :8765 twice.
    r   r   N)r   zwaiting for browser consentc                     	 t               t        dd       y # t        $ r5} t        j	                  d|        t        dt        |              Y d } ~ y d } ~ ww xY w)Nr   r   zHoncho connectedz%Honcho OAuth loopback flow failed: %sr   )r   r   rJ   r   warningr   )excrz   r   ry   r   s    r   _runz,start_loopback_flow_background.<locals>._run  sR    	+"{f^ef%78 	+NNBCHS**	+s    	A+AAzhoncho-oauth-loopbackT)r   r   r   )returnNone)r   r   r   r   rw   r   is_aliver   r   r   r   r   )rz   r   ry   r   r   s   ```` r   start_loopback_flow_backgroundr     s     6!4!6K(&(D	 Q==I%,<;P;P;R$]]gnnEQ Q )Q%w~Q
+ + ##46MVZ[LQ Qs   AC?CC&)r   objectr   r   )r3   
str | Noner   r6   )NN)rH   r   rI   r   r   r"   )r   tuple[str, str])rt   r_   r   r   )r   r"   r^   r   ry   r   rz   r   rt   float | Noner   r   )r   r"   r~   r   rw   r   rz   Path | Noner   r   r   r6   rt   r   r   oauth.OAuthCredential)r   z!tuple[HTTPServer, dict[str, str]])r   r   r   dict[str, str]r   r_   r   r   )rz   r   r   r   ry   r   r   r6   r   zCallable[[str], None] | Noner   r_   r   r   )r   ztuple[bool, str | None])r   zdict[str, object])r=   )rw   r   r   r   r   r   )
rz   r   r   r   ry   r   r   r_   r   r   )Ar*   
__future__r   rg   ri   loggingrQ   re   r   r   dataclassesr   http.serverr   r   r   r   typingr	   urllib.parser
   r   r   plugins.memory.honchor   rF   r   r   	getLoggerr'   r   r   r   LOOPBACK_REDIRECT_URIrr   r   r"   rM   rO   rL   rN   rT   r8   rZ   r\   ra   r+   Lockr   ro   rx   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r,   r-   r   <module>r      s   #    	    ! :   6 6 ' Q			8	$ !--	J     $   , 7 * 6 
 $ R
 <@  .8  F    !#
 "	 " .#C "#C#C#C 	#C
 #C 
#C #CV  $00
0 0
 0 0 0 
0 0hT !J GL77"07>C776  $-1// / 	/
 / +/ / /n   
 ,y~~(,% , T6  $"     	 
    r-   