
    epj                       d 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
 ddlmZ ddlmZmZmZmZmZ ddlmZmZ ddlmZ dd	lmZ dPd
Z ej        e          Zddlm Z m!Z!m"Z"m#Z# ddl$m%Z% dZ&dZ'dZ(dZ)dZ*dZ+dZ,dZ-dZ.dZ/dZ0 ed          1                                Z2d Z3dZ4dee5         de5fdZ6de7dee5         fdZ8dQd!e5d"e5d#e9deee5                  fd$Z:d%e7dee7         fd&Z;d'e7dee7         fd(Z<d)e7dee7         fd*Z=dRd,e7de7fd-Z>dPd.ee?         de7fd/Z@de,d0d1e7d2ee7         d3e7d4e7d5ee7         d6eAdee5e7e7f         fd7ZBd8e7d9e5de7fd:ZCd;e7dee?         fd<ZD G d= d>e           ZEde9fd?ZFde9fd@ZGde9fdAZHdBe?dCe?dee?         fdDZIdee?         fdEZJdddFdGdHe7dIe7dJee7         dKeee7                  dLe9dee7ef         fdMZKdSdNZLdO ZMdS )Tuf  
Buzz Platform Adapter for Hermes Agent.

A plugin-based gateway adapter that connects to a Buzz community relay
(Block's open-source human+agent collaboration platform, built on the
Nostr protocol) and relays messages to/from the Hermes agent.

The adapter does not speak Nostr itself — it shells out to the ``buzz``
CLI binary ("JSON in, JSON out") via ``asyncio.create_subprocess_exec``.
Inbound delivery uses a poll loop (the CLI is request/response); see the
"Known limitations" note in the platform docs.

Configuration in config.yaml::

    gateway:
      platforms:
        buzz:
          enabled: true
          extra:
            relay_url: https://mycommunity.communities.buzz.xyz
            channels:                  # channel UUIDs to watch (empty = all joined)
              - ccc2bc1a-7a82-5a8f-8c4e-57a070cbe7cd
            home_channel: ccc2bc1a-7a82-5a8f-8c4e-57a070cbe7cd
            poll_interval: 4           # seconds between poll sweeps
            cli_path: ""               # path to the buzz binary (default: PATH, then ~/bin/buzz)
            credentials_file: ""       # JSON file holding the nsec (fallback for BUZZ_PRIVATE_KEY)
            allowed_users: []          # empty = allow all; entries are hex pubkeys or npubs

Or via environment variables (overrides config.yaml):
    BUZZ_RELAY_URL, BUZZ_CHANNELS, BUZZ_HOME_CHANNEL, BUZZ_POLL_INTERVAL,
    BUZZ_CLI_PATH, BUZZ_CREDENTIALS_FILE, BUZZ_ALLOWED_USERS,
    BUZZ_ALLOW_ALL_USERS

The only secret is BUZZ_PRIVATE_KEY (nsec or hex) — it belongs in
``~/.hermes/.env``.  It is passed to the CLI via the subprocess
environment and is never logged.
    N)OrderedDict)datetime)Path)AnyDictListOptionalTuple)urlsplit
urlunsplit)UnscopedSecretError)
get_secretc                 z    	 t          | |          }n$# t          $ r t          j        |           }Y nw xY w||n|S )a  Scope-aware credential read with the default-profile startup fallback.

    Secondary profiles construct their adapters under a profile secret
    scope -- the scope is authoritative and a scoped miss returns ``default``
    (no cross-profile borrow from ``os.environ``, which may hold another
    profile's value). The DEFAULT profile's adapter constructs and sends
    *unscoped* under multiplexing, where a bare ``get_secret`` would raise
    ``UnscopedSecretError`` and crash this path; there ``os.environ`` is that
    profile's own value, so fall back to it. Same pattern as the Slack
    ``SLACK_APP_TOKEN`` read (#59739) and
    ``gateway/platforms/whatsapp_common.py::_get_wsecret``.
    )_scoped_get_secret_UnscopedSecretErrorosgetenv)namedefaultvals      D/home/thesage/.hermes/hermes-agent/plugins/platforms/buzz/adapter.py_get_scoped_secretr   8   sR     w//   ioo/33w.s    44)BasePlatformAdapter
SendResultMessageEventMessageType)Platform	   2   i     g      @      ?      >@g      4@i iD  zhermes-buzz-membershipz~/.config/buzzc                  B   	 ddl m}  | S # t          $ r ddl}t	          t
                                        d          }|j                            d|          }||j	        J |j        
                    |          }|j	                            |           |cY S w xY w)a  Import the sibling nostr_auth module in a loader-agnostic way.

    The adapter is imported both as a package module
    (``plugins.platforms.buzz.adapter``) and as a bare single-file module by
    the test plugin loader, where relative imports have no parent package.
       )
nostr_authr   Nznostr_auth.pyplugin_adapter_buzz_nostr_auth) r%   ImportErrorimportlib.utilr   __file__	with_nameutilspec_from_file_locationloadermodule_from_specexec_module)r%   	importlibpathspecmodules        r   _load_nostr_authr5   s   s             H~~''88~556VX\]]DK$;$;;0066'''s   
 BBB qpzry9x8gf2tvdw0s3jn54khce6mua7lvaluesreturnc                     d}d}| D ]9}|dz	  }|dz  dz  |z  }t          d          D ]}|||z	  dz  r||         ndz  }:|S )N)iWj;imP&ii3B=ib*r$      ir    r   )range)r7   	generatorchkvaluetopis         r   _bech32_polymodrA      sw    LI
C ; ;RiY1$u,q 	; 	;ASAXN:9Q<<:CC	;J    hrpc                 <    d | D             dgz   d | D             z   S )Nc                 2    g | ]}t          |          d z	  S )r    ord.0cs     r   
<listcomp>z&_bech32_hrp_expand.<locals>.<listcomp>   s"    %%%ACFFaK%%%rB   r   c                 2    g | ]}t          |          d z  S )   rF   rH   s     r   rK   z&_bech32_hrp_expand.<locals>.<listcomp>   s"    .H.H.Hqs1vv{.H.H.HrB    )rC   s    r   _bech32_hrp_expandrO      s1    %%%%%+.H.HC.H.H.HHHrB   Tfrombitstobitspadc                 &   d}d}g }d|z  dz
  }| D ]I}|dk     s||z	  r d S ||z  |z  }||z  }||k    r&||z  }|                     ||z	  |z             ||k    &J|r!|r|                     |||z
  z  |z             n||k    s|||z
  z  |z  rd S |S )Nr   r$   )append)	datarP   rQ   rR   accbitsretmaxvr>   s	            r   _convertbitsrZ      s    
CD
CK1D - -199(*944h%'fnnFNDJJtt+,,, fnn   	8JJ.$6777			sv}5=	tJrB   
pubkey_hexc                    	 t                               |           }n# t          $ r Y dS w xY wt          |          dk    rdS t	          |dd          }|dS t          d          |z   }t          |g dz             dz  fdt          d	          D             }d
d                    d ||z   D                       z   S )u=   Encode a 64-char hex pubkey as an ``npub1…`` bech32 string.N       r    npub)r   r   r   r   r   r   r$   c                 ,    g | ]}d d |z
  z  z	  dz  S )r    rM   rN   )rI   r@   polymods     r   rK   zhex_to_npub.<locals>.<listcomp>   s+    @@@!AQK'2-@@@rB      npub1r'   c              3   0   K   | ]}t           |         V  d S N)_BECH32_CHARSET)rI   ds     r   	<genexpr>zhex_to_npub.<locals>.<genexpr>   s'      IIA_Q/IIIIIIrB   )	bytesfromhex
ValueErrorlenrZ   rO   rA   r;   join)r[   rawrU   r7   checksumra   s        @r   hex_to_npubrp      s    mmJ''   tt
3xx2~~tQ""D|t''$.Ff'9'9'99::Q>G@@@@uQxx@@@HRWWIIIIIIIIIs    
,,r_   c                    |                                                                  } |                     d          sdS | t          d          d         }	 d |D             }n# t          $ r Y dS w xY wt          t          d          |z             dk    rdS t          |dd         ddd	
          }|t          |          dk    rdS t          |          	                                S )u=   Decode an ``npub1…`` bech32 string to a 64-char hex pubkey.rc   Nc                 B    g | ]}t                               |          S rN   )rf   indexrH   s     r   rK   znpub_to_hex.<locals>.<listcomp>   s&    <<<Q%%a((<<<rB   r_   r$   ir    r^   F)rR   r]   )
striplower
startswithrl   rk   rA   rO   rZ   ri   hex)r_   	data_partrU   decodeds       r   npub_to_hexrz      s    ::<<D??7## tS\\]]#I<<)<<<   tt)&11D899Q>>t49a666G#g,,",,t>>s   A# #
A10A1refc                     | pd                                                                 } | sdS |                     d          rt          |           S t	          j        d|           r| S dS )zANormalize a user reference (hex pubkey or npub) to lowercase hex.r'   Nrc   z[0-9a-f]{64})rt   ru   rv   rz   re	fullmatch)r{   s    r   _normalize_user_refr      sp    9"




#
#
%
%C t
~~g  3	|OS)) 
4rB   r'   
configuredc                 >   | rFt          |                                           }|                                rt          |          ndS t	          j        d          }|r|S t          j                    dz  dz  }|                                rt          |          ndS )u   Resolve the buzz CLI binary path portably.

    Order: explicit config value → ``buzz`` on PATH → ``~/bin/buzz``.
    Returns "" when nothing is found so callers can raise a config error.
    r'   buzzbin)r   
expanduseris_filestrshutilwhichhome)r   pfoundfallbacks       r   _resolve_cli_pathr      s      -'')),s1vvv",L  E y{{U"V+H$,,..63x===B6rB   extrac                    t          dd                                          }|r|S t          j        dd                                          p| pi                     dd          }|r#t          |                                          g}n;	 t          t          	                    d                    }n# t          $ r g }Y nw xY w|D ]}	 t          j        |                    d                    }n# t          t          f$ r Y ?w xY wt          |t                     sYdD ]X}|                    |          }t          |t"                    r,|                                r|                                c c S YdS )	zhResolve the Nostr private key: env first, then a credentials JSON.

    NEVER log the return value.
    BUZZ_PRIVATE_KEYr'   BUZZ_CREDENTIALS_FILEcredentials_filez*credentials*.jsonutf-8)encoding)nsecprivate_key_hexprivate_key)r   rt   r   r   getr   r   sorted_DEFAULT_CREDENTIALS_DIRglobOSErrorjsonloads	read_textrk   
isinstancedictr   )r   keyr   
candidatesr2   rU   fieldr>   s           r   _resolve_private_keyr      s   
 /
4
4
:
:
<
<C
 
2B77==??lEKRCTCTUgikClClJ :&&11334

	 8 = =>R S STTJJ 	 	 	JJJ	 
% 
%	:dnngn>>??DD$ 	 	 	H	$%% 	? 	% 	%EHHUOOE%%% %%++-- %{{}}$$$$$	% 2s$   'B4 4CC(C44DD)
input_texttimeoutcli_pathargs	relay_urlr   r   r   c                  K   t           j                                        }||d<   ||d<   t          j        | g|R |t          j        j        nt          j        j        t          j        j        t          j        j        |d d{V }	 t          j        |	                    ||
                    d          nd          |           d{V \  }}	nn# t          j        $ r\ |                                 |                                 d{V  ddt          j        d	d
|r|d         nd d| dd          fcY S w xY w|j        |j        nd|                    dd          |	                    dd          fS )u   Run the buzz CLI with an argument list (never a shell) and return
    ``(returncode, stdout, stderr)``.

    The private key travels via the subprocess environment only — it never
    appears in argv, so process listings and error logs stay clean.
    BUZZ_RELAY_URLr   N)stdinstdoutstderrenvr   r   |   r'   r   zbuzz r   z timed out after s)errormessage   replace)errors)r   environcopyasynciocreate_subprocess_exec
subprocessPIPEDEVNULLwait_forcommunicateencodeTimeoutErrorkillwaitr   dumps
returncodedecode)
r   r   r   r   r   r   r   procr   r   s
             r   
_exec_buzzr     s      *//

C%C)C/	  *4)?g %%WEWE_!&!&        DB&/:;QZ..w777W[\\ 
  
  
 
 
 
 
 
 
  B B B		iikkB
YCVZKb4PQ77`bCCu|CCC  $A  $A  B  B  B  	B  	B  	BB
  ?6Agi00gi00 s   
A	C A(D?>D?r   r   c                 &   | pd                                 }	 t          j        |          }t          |t                    r:|                    d          r%|                    dd           d|d          d| dS n# t          $ r Y nw xY w|pd| S )zExtract the human-readable message from the CLI's JSON error contract.

    stderr is ``{"error": "<category>", "message": "<detail>"}`` on failure;
    fall back to the raw (stripped) stderr when it isn't JSON.
    r'   r   r   z: z (exit )zbuzz CLI failed with exit code )rt   r   r   r   r   r   rk   )r   r   textrU   s       r   _cli_error_messager   @  s     Lb!!Dz$dD!! 	Zdhhy&9&9 	Zhhw00YYDOYYJYYYY   AAZAAAs   A"A< <
B	B	r   c                     	 t          j        | pd          }n# t          $ r g cY S w xY wt          |t                    sg S d |D             S )z8Parse CLI stdout expected to be a JSON array of objects.z[]c                 <    g | ]}t          |t                    |S rN   )r   r   )rI   items     r   rK   z$_parse_json_list.<locals>.<listcomp>X  s'    <<<TZd%;%;<D<<<rB   )r   r   rk   r   list)r   rU   s     r   _parse_json_listr   P  sk    z&.D))   			dD!! 	<<T<<<<s    ((c                   @    e Zd ZdZ fdZedefd            Zdddee         de	e         de
eeef         fd	Zd
ddedefdZd>dZ	 	 d?dedede	e         de	eeef                  def
dZd@deddfdZdedededefdZ	 	 	 dAdedede	e         de	e         de	eeef                  defdZdedeeef         fdZdefdZdefdZd>dZded eddfd!Zdeee	e         f         fd"Zd#eee	e         f         d$eddfd%Zd>d&Zd>d'Z d ed(eddfd)Z!d*eddfd+Z"d eddfd,Z#d ed-ed$eddfd.Z$d edefd/Z%d ed$edefd0Z&d ed-ed$eddfd1Z'dedefd2Z(dedefd3Z)d4edefd5Z*e+d-eddfd6            Z,d ed7eddfd8Z-d9eded(ed:ed;eded<eddfd=Z. xZ/S )BBuzzAdapterzPoll-based Buzz adapter implementing the BasePlatformAdapter interface.

    Instantiated by the adapter_factory passed to register_platform().
    c                 |   t          d          }t                                          ||           t          |di           pi }|| _        t          j        d          p|                    dd                                          | _	        t          t          j        dd                                          p$t          |                    dd          pd                    | _        t          j        d	          p|                    d
g           }t          |t                    r|                    d          }d |D             | _        t          j        d          p$t          |                    dd          pd                                          | _        	 t#          t          j        d          p|                    dt$                              }n# t&          t(          f$ r
 t$          }Y nw xY wt+          t,          |          | _        t          j        d          }||                    dd          }n|}t          |                                                                          dv| _        t          j        d          p$t          |                    dd          pd                                                                          }	|	dv r|	nd| _        t          j        d          p|                    dg           }
t          |
t                    r|
                    d          }
fd|
D             | _        d| _        d| _        d| _        d| _        d | _         d | _!        d | _"        d| _#        d| _$        d | _%        i | _&        i | _'        i | _(        i | _)        d| _*        d S )Nr   )configplatformr   r   r   r'   BUZZ_CLI_PATHr   BUZZ_CHANNELSchannels,c                     g | ]?}t          |t                    |                                +|                                @S rN   )r   r   rt   rH   s     r   rK   z(BuzzAdapter.__init__.<locals>.<listcomp>v  sC    #h#h#h!zRSUXGYGY#h^_^e^e^g^g#hAGGII#h#h#hrB   BUZZ_HOME_CHANNELhome_channelBUZZ_POLL_INTERVALpoll_intervalBUZZ_REQUIRE_MENTIONrequire_mentionT)false0nooffBUZZ_TRANSPORT	transportauto)r   	websocketpollBUZZ_ALLOWED_USERSallowed_usersc                 `    h | ]*}t          |t                    t          |          x(+S rN   )r   r   r   )rI   entry
normalizeds     r   	<setcomp>z'BuzzAdapter.__init__.<locals>.<setcomp>  sQ     &
 &
 &
%%%&
 :MU9S9S+S:&
&
 &
 &
rB   Fr   )+r   super__init__getattr_extrar   r   r   rt   r   r   r   r   r   splitr   r   float_DEFAULT_POLL_INTERVAL	TypeErrorrk   max_MIN_POLL_INTERVALr   ru   r   r   _allowed_pubkeys_private_key_self_pubkey
_self_npub_display_name
_poll_task_ws_task	_ws_ready
_ws_active_membership_since	_lock_key_channel_state_channel_names_channel_meta_user_names_poll_count)selfr   kwargsr   r   raw_channelsinterval_rm_raw_rm_cfg
_transportraw_allowedr   	__class__s              @r   r   zBuzzAdapter.__init__e  s   F##:::,,2 )$455S;PR9S9SZZ\\)Ior**0022Zc%))JPR:S:S:YWY6Z6Z
 

 y11NUYYz25N5NlC(( 	3'--c22L#h#h|#h#h#hY':;;gs599^]_C`C`Cfdf?g?gnnpp	.RY';<<r		/[q@r@rssHH:& 	. 	. 	.-HHH	. !3X>> )233?ii 1488GGG"7||113399;;C^^ I&''X3uyyf/M/M/WQW+X+X
%''%%'' 	 (25R'R'RX^ i 455W?TV9W9Wk3'' 	1%++C00K&
 &
 &
 &
$&
 &
 &
 "$ "$!"$ 370426!"(,/1.0 /1+-s   $<G! !G<;G<r8   c                     dS )NBuzzrN   )r  s    r   r   zBuzzAdapter.name  s    vrB   Nr   r   r   c                   K   | j         st          | j                  | _         t          | j        || j        | j         |           d {V S )Nr   r   r   )r   r   r   r   r   r   )r  r   r   s      r   _run_clizBuzzAdapter._run_cli  so        	B 4T[ A ADMn)!
 
 
 
 
 
 
 
 
 	
rB   F)is_reconnectr  c          
      8
  K   | j         s4t                              d           |                     ddd           dS | j        s4t                              d           |                     ddd           dS t          | j                  | _        | j        s4t                              d	           |                     dd
d           dS |                     ddg           d{V \  }}}|dk    rOt          ||          }t                              d| j         |           |                     d||dk               dS t          |          }|r|d                             d          s4t                              d           |                     ddd           dS t          |d         d                                                   | _        t          |d                             d          pd                                          | _        t#          | j                  pd| _        	 ddlm} | j          d| j         } |d|          sHt                              d| j        dd         | j                    |                     ddd           dS || _        n# t,          $ r
 d| _        Y nw xY w|                     dd g           d{V \  }}}|dk    rIt          ||          }t                              d!|           |                     d||dk               dS t          |          }	d" |	D             | _        |	D ]4}
|
                    d#          r|
| j        t          |
d#                   <   5| j        pt5          | j                  }|s4t                              d$           |                     dd%d           dS |D ]}|                     |d&'           d{V   |                     d(           d{V  d)}| j        d*v r\|                                  d{V rd+}n?| j        d+k    r4|                     d,d-d           |                                  d{V  dS |d)k    r+tA          j!        | "                                          | _#        | $                                 t          %                    d.| j         | j        p| j        dd/         tM          | j'                  ||d+k    rdnd0| j(        d1d2           dS )3z?Verify relay credentials, seed high-water marks, start polling.z"Buzz: relay URL must be configuredconfig_missingzBUZZ_RELAY_URL must be setF)	retryablezIBuzz: buzz CLI binary not found (set BUZZ_CLI_PATH or put 'buzz' on PATH)cli_missingzbuzz CLI binary not foundzABuzz: no private key (set BUZZ_PRIVATE_KEY or a credentials file)zBUZZ_PRIVATE_KEY must be setusersr   Nr   u0   Buzz: failed to fetch own profile from %s — %sconnect_failed   pubkeyuP   Buzz: 'users get' returned no profile — is the key a member of this community?z"buzz users get returned no profileTdisplay_namer'   )acquire_scoped_lock:r   u<   Buzz: identity %s… on %s already in use by another profiler^   lock_conflictz'Buzz identity in use by another profiler   r   u$   Buzz: failed to list channels — %sc                     i | ]o}|                     d           t          |                     d                     t          |                     d          p|                     d                     pS )
channel_idr   )r   r   )rI   chs     r   
<dictcomp>z'BuzzAdapter.connect.<locals>.<dictcomp>	  ss     
 
 
vvl##
|$$%%s266&>>+QRVVL=Q=Q'R'R
 
 
rB   r*  zFBuzz: no channels to watch (configure BUZZ_CHANNELS or join a channel)zno Buzz channels to watchgroup	chat_typeseedr   )r   r   r   ws_auth_failedzCBuzz WebSocket transport did not authenticate (transport=websocket)z<Buzz: connected to %s as %s, watching %d channel(s) via %s%s   z, poll interval z.1fr   ))r   loggerr   _set_fatal_errorr   r   r   r   r  r   r   r   r   ru   r   rt   r  rp   r   gateway.statusr&  r  r(   r	  r
  r   r   _seed_channel_discover_dmsr   _start_websocket
disconnectr   create_task
_poll_loopr  _mark_connectedinforl   r  r   )r  r  codeouterrr   profilesr&  lock_keylistedr+  watchr*  transport_useds                 r   connectzBuzzAdapter.connect  s     ~ 	LL=>>>!!"24P\a!bbb5} 	LLdeee!!-1LX]!^^^50==  	LL\]]]!!"24R^c!ddd5  $}}gu-=>>>>>>>>c3199(d33GLLKT^]deee!!"2Gtqy!QQQ5#C(( 	x{x88 	LLklll!!"24Xdh!iii5H 566<<>> !!@!@!FBGGMMOO%d&788>B
	"::::::.>>4+<>>H&&vx88 	R%bqb)N  
 %%#%NZ_ &    u%DNN 	" 	" 	"!DNNN	"  $}}j&-ABBBBBBBBc3199(d33GLL?III!!"2Gtqy!QQQ5!#&&
 

 
 

  	? 	?Bvvl## ?<>"3r,'7#8#89:d&9!:!: 	LLabbb!!"24O[`!aaa5   	D 	DJ$$Z7$CCCCCCCCCC  d +++++++++  >222**,,,,,,,, 	!,;..%%$Y" &   
 oo'''''''''uV##%1$//2C2CDDDOJN6$/#2#"6#$$ K//BB5aHZ5a5a5a5a	
 	
 	
 ts   2A)J% J% %J98J9c                 h  K   |                                   t          | dd          }|r+	 ddlm}  |d|           n# t          $ r Y nw xY wd| _        d| _        | j        rV| j                                        s=| j        	                                 	 | j         d{V  n# t          j        $ r Y nw xY wd| _        | j        rV| j                                        s=| j        	                                 	 | j         d{V  n# t          j        $ r Y nw xY wd| _        i | _        d| _        dS )z2Stop the inbound transport and drop runtime state.r  Nr   )release_scoped_lockr   F)_mark_disconnectedr   r6  rI  	Exceptionr  r  r  donecancelr   CancelledErrorr  r  r  )r  rC  rI  s      r   r:  zBuzzAdapter.disconnect:  s     !!!4d33 	">>>>>>##FH5555   !DN= 	!3!3!5!5 	M  """m########)   ? 	4?#7#7#9#9 	O""$$$o%%%%%%%%)    s3   > 
A
AB$ $B65B6:D DDchat_idcontentreply_tometadatac                   K   |st          dd          S dddt          |          ddg}|p|pi                     d	          }|r|d
t          |          gz  }|                     ||           d {V \  }}}	|dk    r$t          dt	          |	|          |dk              S 	 t          j        |pd          }
n# t          $ r i }
Y nw xY w|
                    d          }|r0|                     t          |          t          |                     t          t          |
                    dd                    |rt          |          nd |
          S )NFzEmpty message)successr   messagessend	--channel	--content-	thread_id
--reply-tor  r   r#  rT  r   r  {}event_idacceptedTrT  
message_idraw_response)
r   r   r   r  r   r   r   rk   
_mark_seenbool)r  rO  rP  rQ  rR  r   reply_targetr?  r@  rA  rU   r^  s               r   rV  zBuzzAdapter.sendZ  s       	De?CCCCFKW{CPDHN#7#7#D#D 	6\3|#4#455D#}}Tg}FFFFFFFFc3199(d33!)   
	:ckT**DD 	 	 	DDD	88J'' 	9 OOCLL#h--888*d3344(0:s8}}}d
 
 
 	
s   &B= =CCc                 
   K   dS )u+   Buzz has no typing indicator API — no-op.NrN   )r  rO  rR  s      r   send_typingzBuzzAdapter.send_typing}  s      rB   ra  emojic           	        K   | j         r|r|sdS dddt          |          d|g}|                     |           d{V \  }}}|dk    r5t                              d|dd	         |t          ||                     dS d
S )u   Add a reaction to a message via buzz-cli.

        Returns True on success, False on failure. Errors are logged but not
        raised — reactions are best-effort and should never block the main
        message flow.
        F	reactionsaddz--eventz--emojiNr   u5   Buzz: reaction add failed for message %s in %s — %s   T)r   r   r  r4  debugr   )r  rO  ra  rh  r   r?  _outrA  s           r   send_reactionzBuzzAdapter.send_reaction  s       } 	E 	 	5
 s:u

 !%d 3 3333333dC199LLG3B3*<S$*G*G   5trB   	image_urlcaptionc                 r  K   |                     d          s!t          |                                          nd}|Q|                                r<dddt	          |          dt	          |          ddg}|r|d	t	          |          gz  }|                     ||pd
           d{V \  }}	}
|dk    r$t          dt          |
|          |dk              S 	 t          j	        |	pd          }n# t          $ r i }Y nw xY w|                    d          }|r0|                     t	          |          t	          |                     t          t          |                    dd                    |rt	          |          nd|          S |r| d| n|}|                     ||||           d{V S )z@Send an image: local files upload via --file, URLs go as a link.)zhttp://zhttps://NrU  rV  rW  --filerX  rY  r[  r'   r  r   Fr#  r\  r]  r^  r_  Tr`  
)rQ  rR  )rv   r   r   r   r   r  r   r   r   r   rk   r   rc  rd  rV  )r  rO  rp  rq  rQ  rR  localr   r?  r@  rA  rU   r^  r   s                 r   
send_imagezBuzzAdapter.send_image  s      5>4H4HI`4a4akY**,,,gkFS\\#e**S	D  6s8}}55#'=='-R=#P#PPPPPPPND#sqyy!%7I#t7T7T`dhi`ijjjjz#+..   xx
++H =Gc(mm<<<TXXj$7788,4>3x===$!    -4B'((Y(((YYwx(YSSSSSSSSSs   C2 2D Dc                   K   t          |          }| j                            |          }|r|d         nd}| j                            |          }|| j        r|                     ddd|g           d {V \  }}}|dk    rq	 t          j        |pd          }t          |t                    r4|                    d          rt          |d                   }|| j        |<   n# t          $ r Y nw xY w|p|||d	S )
Nr/  r-  r   r   rW  r   r]  r   )r   typerO  )r   r  r   r	  r   r  r   r   r   r   rk   )	r  rO  stater/  r   r?  r@  _errrU   s	            r   get_chat_infozBuzzAdapter.get_chat_info  s(     g,,#''00*/<E+&&W	"&&w//<DM<$(MM:ukSZ2[$\$\\\\\\\OD#tqyy:ckT22D!$-- <$((62B2B <"4<007;+G4!   DwOOOs   AC% %
C21C2c                    t          | j                                                  }ddd                    |j        |j                  }|dvs|j        st          d          t          ||j        |j        pd|j	        df          S )Nwswss)httphttps)r}  r~  z(Buzz relay URL must use http(s) or ws(s)r'   )
r   r   rt   r   schemenetlocrk   r   r2   query)r  parsedr  s      r   _websocket_urlzBuzzAdapter._websocket_url  s    $...0011//33FM6=QQ&&fm&GHHH66=&+2CV\SUVWWWrB   c                 2  K   	 ddl }|                                  n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY wt          j                    | _        t          t          j	                              | _
        t          j        |                                           | _        	 t          j        | j                                        t           dz              d{V  n# t
          j        t"          f$ r t                              d           d| _        | j        rV| j                                        s=| j                                         	 | j         d{V  n# t
          j        $ r Y nw xY wd| _        Y dS w xY wdS )	zAStart the WS loop; True when it authenticates within the timeout.r   NzCBuzz: WebSocket transport unavailable (%s); falling back to pollingFr    r   z,Buzz: WebSocket did not authenticate in timeT)
websocketsr  rK  r4  r>  r   Eventr  inttimer  r;  _websocket_loopr  r   r   _WS_AUTH_TIMEOUTr   warningr  rL  rM  rN  )r  r  es      r   r9  zBuzzAdapter._start_websocket  s     	!!!! 	 	 	KK]_`aaa55555	 !!$TY[[!1!1+D,@,@,B,BCC	"4>#6#6#8#8BRUVBVWWWWWWWWWWW$l3 
	 
	 
	NNIJJJ#DO} T]%7%7%9%9 $$&&&-''''''''-   D DM55
	 tsK    
AAA9;C5 5A0F&E43F4FFF
FFc           	        K   t                      j        }t          j        |                                t
                     d{V }t          j        |          }t          |t                    rt          |          dk     s|d         dk    rt          d           || j        t          |d                   |                                 t          j        dd	          
          }|                    t          j        d|gd                     d{V  	 t          j        |                                t
                     d{V }t          j        |          }t          |t                    r|s`|d         dk    rIt          |          dk    r6|d         |d         k    r$|d         du rdS t          d|d                    |d         dv r/t          |          dk    r|d         nd}t          d|           )zNIP-42: wait for the relay's AUTH challenge, answer with a signed
        kind-22242 event (plus the optional NIP-OA owner-attestation tag from
        BUZZ_AUTH_TAG), and wait for the OK acknowledgment.r   Nr#  r   AUTHz/Buzz relay did not send a NIP-42 AUTH challenger$   BUZZ_AUTH_TAGr'   )r   	challenger   auth_tag_jsonr   r'  
separatorsTOKr   idzBuzz WebSocket AUTH rejected:    )NOTICECLOSEDzauthentication failedzBuzz WebSocket AUTH failed: )r5   build_auth_eventr   r   recvr  r   r   r   r   rl   ConnectionErrorr   r   r  r   r   rV  r   )r  r   r  rn   r   eventresponsedetails           r   _authenticate_websocketz#BuzzAdapter._authenticate_websocket  s%      ,-->$Y^^%5%5?OPPPPPPPPP*S//'4(( 	UCLL1,<,<
f@T@T!"STTT  )'!*oo))++)OR88	
 
 
 nnTZJOOOPPPPPPPPP	O()9)9CSTTTTTTTTTCz#Hh-- X {d""s8}}'9'9hqkUSW[>X>XA;$&&F%&TxPQ{&T&TUUU{222),X):):"@W%&MV&M&MNNN	OrB   subscription_idr*  c                 L  K   | j                             |          pi }t          t          |                    d          pt	          j                              dz
  d          }d|t
          g|g|dg}|                    t          j        |d                     d {V  d S )Nlast_tsr$   r   REQ)kindsz#hsincer  r  )	r  r   r   r  r  
_CHAT_KINDrV  r   r   )r  r   r  r*  ry  r  requests          r   _send_channel_subscriptionz&BuzzAdapter._send_channel_subscription  s      #''
339rC		),,;	<<q@!DD!l:,GG

 nnTZJGGGHHHHHHHHHHHrB   c                   K   i }t          t          | j                            D ],\  }}d| }|||<   |                     |||           d{V  -| j        ridt
          t          g| j        gt          | j        dz
  d          dg}|	                    t          j        |d                     d{V  d|t
          <   |S )	zySubscribe to every watched conversation plus membership events
        (kind 44100 p-tagged to us) for live DM discovery.zhermes-buzz-Nr  r$   r   )r  z#pr  r  r  )	enumerater   r  r  r   _WS_MEMBERSHIP_SUB_ID_WS_MEMBERSHIP_KINDr   r  rV  r   r   )r  r   subscriptionsrs   r*  r  r  s          r   _subscribe_websocketz BuzzAdapter._subscribe_websocket#  s      35!*40C+D+D!E!E 	Z 	ZE:4U44O-7M/*11)_jYYYYYYYYYY 	8%12,- !7!!;Q?? G ..G
!K!K!KLLLLLLLLL37M/0rB   r  r  c                   K   t          | j        t          |                    d          pd                    | _        t	          | j                  }|                     d           d{V  | j        D ]V}||v rdt          |           }|||<   |                     |||           d{V  t          
                    d|           WdS )zA membership event p-tagged to us: rediscover conversations and
        subscribe to any new ones (fresh DMs dispatch from their beginning).
created_atr   Fr0  Nzhermes-buzz-dm-z'Buzz: subscribed to new conversation %s)r   r  r  r   setr  r8  rl   r  r4  r>  )r  r   r  r  beforer*  r  s          r   _handle_membership_eventz$BuzzAdapter._handle_membership_event9  s       "%T%;S<AXAXA]\]=^=^!_!_T())  e ,,,,,,,,,- 	O 	OJV##DM0B0BDDO-7M/*11)_jYYYYYYYYYKKA:NNNN	O 	OrB   c                 (  K   ddl }d}	 	 	 |                    |                                 t          dddt                    4 d{V 	 }|                     |           d{V  |                     |           d{V }d| _        | j        | j        	                                 d}|2 3 d{V }	 t          j        |          }n1# t          t          f$ r t                              d           Y Jw xY wt!          |t"                    r|sf|d         d	k    rt%          |          d
k    rt'          |d                   }|d         }t!          |t(                    s|t*          k    r|                     |||           d{V  |                    |          }	| j                            |	pd          }
|	r4|
2|                     |	|
|           d{V  |                     |
           J|d         dk    r9t%          |          dk    r|d         nd}t7          t'          |                    |d         dk    r!t                              d|d                    6 	 ddd          d{V  n# 1 d{V swxY w Y   nv# t8          j        $ r  t<          $ rZ}d| _        t                              d||           t9          j        |           d{V  tA          |dz  d          }Y d}~nd}~ww xY w# d| _        w xY w)u3  Persistent authenticated subscription with bounded reconnect
        backoff. Events route through _handle_event() — identical semantics
        to the poll loop. On reconnect, per-channel `since` filters resume
        from the last observed timestamps (same-second overlap de-duped by
        event id).r   Nr!   Tr       )open_timeoutclose_timeoutping_intervalping_timeoutmax_sizez(Buzz: ignoring malformed WebSocket frameEVENTr  r$   r#  r'   r  r  zsubscription closedr  zBuzz: relay notice: %sFz3Buzz: WebSocket disconnected; retrying in %.1fs: %sr"   )!r  rG  r  r  _WS_MAX_MESSAGE_BYTESr  r  r  r  r  r   r   rk   r   r4  r  r   r   rl   r   r   r  r  r   r  _handle_event
_trim_seenr  r   rN  rK  sleepmin)r  r  backoffr   r  rn   r   r  r  r*  ry  r  r  s                r   r  zBuzzAdapter._websocket_loopG  sm      	3	$05/5)11++--%5&'&(%'!6  2     'V 'V 'V 'V 'V 'V 'V 'V #"::9EEEEEEEEE.2.G.G	.R.R(R(R(R(R(R(R*.>5 N..000"%)2 V V V V V V V#)*.*S//$.	#: ) ) ) &/Y Z Z Z () $.gt#<#< )G ) (&qzW44W9J9J25gaj//(/
'1%'>'> !-$,#26K#K#K*.*G*G	S`bg*h*h$h$h$h$h$h$h$h$,-:->->-O-O
(,(;(?(?
@Pb(Q(Q#- !;%2C*.*<*<ZPU*V*V$V$V$V$V$V$V$V$(OOE$:$:$:!(x!7!78;Gq8H8HNc&5c&kk&B&B B!(x!7!7 &/GQS U U U3 *3'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'V 'VP -     5 5 5&+DONN#XZacdeee!-000000000!'A+t44GGGGGG	5Y05d $DO####s   L =J 
A"I>,I*2I>4C	I>	+C74I>6C77E4I>,J >
JJ JJ L L)AK>9L >LL 	Lc                   K   	 	 t          j        | j                   d{V  | xj        dz  c_        	 | j        t          z  dk    r|                     d           d{V  t          | j                  D ]}|                     |           d{V  n;# t           j	        $ r  t          $ r t                              dd           Y nw xY w# t           j	        $ r  w xY w)	z:Poll every watched channel for new events until cancelled.TNr$   r   Fr0  zBuzz: poll sweep failedexc_info)r   r  r   r  _DM_DISCOVERY_EVERYr8  r   r  _poll_channelrN  rK  r4  r  )r  r*  s     r   r<  zBuzzAdapter._poll_loop  sR     	MmD$6777777777  A%  M'*==BB"00e0<<<<<<<<<&*4+>&?&? = =
"00<<<<<<<<<<=-     M M MNN#<tNLLLLLMM % 	 	 		s/   0C A!B C 5CC CC C$r/  c           
        K   |dt                      d}|| j        |<   |                     ddd|dt          t                    g           d{V \  }}}|dk    rOt
                              d|t          ||                     t          t          j	                              |d	<   dS t          |          D ]}|                    d
          }t          |                    d          pd          }	|rd|d         t          |          <   t          |d	         |	          |d	<   |                     |||           |                     |           dS )z>Initialize a channel's high-water mark from its newest events.r   r/  r  seenrU  r   rW  --limitNu&   Buzz: could not seed channel %s — %sr  r  r  r  )r   r  r  r   _FETCH_LIMITr4  r  r   r  r  r   r   r   _maybe_latch_dmr  )
r  r*  r/  ry  r?  r@  rA  r  r^  r  s
             r   r7  zBuzzAdapter._seed_channel  sw     'A{}}MM*/J'#}}ZCDUDUV 
  
 
 
 
 
 
 
c3 199NN8*FXY\^bFcFc  
  #49;;//E)F%c** 		; 		;EyyHUYY|449::J 4/3fc(mm,"5#3Z@@E)   UE::::rB   r1  c          	        K   |                      ddg           d{V \  }}}|dk    rt          |          D ]}t          |                    d          pd          }|r	|| j        v r2|r|                     |d           d{V  nddt                      d	| j        |<   | j                            |d
           |                      ddg           d{V \  }}}|dk    rdS t          |          D ]}t          |                    d          pd          }|s)|| j	        |<   | j                            |t          |                    d          p|                     || j        v s| 
                    |          s|r|                     |d           d{V  ddt                      d	| j        |<   dS )a  Watch DM conversations.  New ones found mid-run dispatch from their
        beginning (a fresh conversation has no history worth suppressing);
        ones present at startup are seeded like channels.

        ``dms list`` is only a best-effort source: on some hosted relays it
        returns ``[]`` even when DM conversations exist (#68871).  Those DMs
        DO surface in ``channels list`` as entries named "DM" with an empty
        description, so that listing is scanned as a fallback.  Fallback
        finds are watched as ``group`` and latch to ``dm`` via p-tag
        detection (_is_direct_message_event) rather than trusting the name
        alone to unlock the mention-free DM path.
        dmsr   Nr   dm_idr'   dmr.  r  DMr   r*  r   r-  )r  r   r   r   r  r7  r   r	  
setdefaultr
  _may_reclassify_as_dm)	r  r1  r?  r@  rz  r  r  r+  ch_ids	            r   r8  zBuzzAdapter._discover_dms  s>      !%ufo > >>>>>>>c4199&s++ < <BFF7OO1r22 )< < < j,,Ud,CCCCCCCCCC?CPQ[f[h[h1i1iD'.#..ud;;;; $z6.B C CCCCCCCc4199F"3'' 	i 	iB|,,233E (*Du%**5#bffVnn6M2N2NOOO+++43M3Me3T3T+ i(('(BBBBBBBBBB;BqZeZgZg-h-h#E**	i 	irB   c                   K   | j                             |          }|d S ddd|dt          t                    g}|d         r|dt          |d                   gz  }|                     |           d {V \  }}}|dk    r,t
                              d|t          ||                     d S t          |          D ]}| 	                    |||           d {V   | 
                    |           d S )	NrU  r   rW  r  r  z--sincer   u&   Buzz: poll of channel %s failed — %s)r  r   r   r  r  r4  rm  r   r   r  r  )r  r*  ry  r   r?  r@  rA  r  s           r   r  zBuzzAdapter._poll_channel  s*     #''
33=FE;
Is<GXGXY 	7 YE)$4 5 566D#}}T22222222c3199LL8*FXY\^bFcFc   F%c** 	? 	?E$$Z>>>>>>>>>>rB   ry  c           	        K   t          |                    d          pd          }t          |                    d          pd          }|r
||d         v rdS d|d         |<   t          |d         |          |d<   t          |                    d          pd          t          k    rdS t          |                    d	          pd                                          }|                    d
          }|r)t          |t                     r|                                sdS || j        k    rdS | 	                    |||           |d         dk    }|s| j
        r|                     |          sdS | j        r.|| j        vr%t                              d|dd                    dS |                     |          }	|                     |	||rdnd||                     |           d{V ||           d{V  dS )z>De-dupe, filter, and dispatch a single ``messages get`` event.r  r'   r  r   r  Nr  kindr$  rP  r/  r  u5   Buzz: ignoring message from unauthorized pubkey %s…r^   r-  )r   rO  r/  user_id	user_namera  r  )r   r   r  r   r  ru   r   rt   r   r  r   _is_mentionedr   r4  rm  _strip_mention_dispatch_message_resolve_user_name)
r  r*  ry  r  r^  r  r$  rP  is_dmdispatch_texts
             r   r  zBuzzAdapter._handle_event  sX     uyy,"--<005A66
 	8uV}44F"&fhuY/<<iuyy  %A&&*44FUYYx((.B//5577))I&& 	Z55 	W]]__ 	F T&&&F 	Z666k"d*  	- 	d6H6H6Q6Q 	F   	V43H%H%HLLPRXY[Z[Y[R\]]]F ++G44$$#0dd 33F;;;;;;;;! % 
 
 	
 	
 	
 	
 	
 	
 	
 	
 	
rB   c                 6   | j                             |          }|	|| j        vS t          |                    d          pd                                          }t          |                    d          pd                                          }|dk    o| S )av  True when the conversation's metadata does not rule out a DM.

        Known real community channels (real name or non-empty description in
        ``channels list``) must never turn into DMs just because a message
        p-tags us.  A conversation with no metadata at all is trusted only
        when the user did not explicitly configure it as a watched channel.
        Nr   r'   descriptionr  )r
  r   r   r   rt   )r  r*  metar   r  s        r   r  z!BuzzAdapter._may_reclassify_as_dm?  s     !%%j11<T]22488F##)r**0022$((=117R88>>@@t|/K/rB   c                 >     j         r                     |          sdS t          |                    d          pd          t          k    rdS t          |                    d          pd                                          }|r| j         k    rdS |                    d          }t          |t                    sdS t           fd|D                       }|sdS |                    d          }t          |t
                    o 
                    |           S )	u&  True when ``event`` is shaped like a direct message to us: a chat
        message from another user, p-tagged to our pubkey, whose content does
        NOT visibly mention us — i.e. the p-tag is structural DM addressing,
        not the artifact of a typed @mention (see block comment above).Fr  r   r$  r'   tagsc              3      K   | ]o}t          |t          t          f          oNt          |          d k    o;|d         dk    o/t	          |d                                                    j        k    V  pdS )r$   r   r   N)r   r   tuplerl   r   ru   r   )rI   tagr  s     r   rh   z7BuzzAdapter._is_direct_message_event.<locals>.<genexpr>]  s       
 

 	 sT5M** 9C19A#9 CF!!##t'88	
 
 
 
 
 
rB   rP  )r   r  r  r   r  r   ru   r   r   anyr  )r  r*  r  r$  r  p_tagged_to_selfrP  s   `      r   _is_direct_message_eventz$BuzzAdapter._is_direct_message_eventN  s=   
   	(B(B:(N(N 	5uyy  %A&&*445UYYx((.B//5577 	4#4445yy  $%% 	5 
 
 
 

 
 
 
 
 
   	5))I&&'3''K0B0B70K0K,KKrB   c                     |d         dk    s|                      ||          sdS d|d<   | j                            |d           t                              d|           dS )zLatch a group conversation to chat_type="dm" once any direct
        message is seen; the classification then sticks so subsequent
        un-mentioned messages in the conversation dispatch too.r/  r  Nr  zCBuzz: conversation %s reclassified as DM (message p-tagged to self))r  r	  r  r4  r>  )r  r*  ry  r  s       r   r  zBuzzAdapter._maybe_latch_dmi  sn     %%T-J-J:W\-]-]%F!k&&z4888Y[efffffrB   c                    |                                 }| j        r| j        |v rdS | j        r| j        |v rdS | j        rFdt	          j        | j                                                    d}t	          j        ||          rdS dS )z@True when the message addresses this agent (npub, hex, or name).Tz	(?<!\w)@?z(?!\w)F)ru   r   r   r  r}   escapesearch)r  rP  loweredpatterns       r   r  zBuzzAdapter._is_mentioneds  s    --// 	!2g!=!=4? 	t'994 	P29T-?-E-E-G-G#H#HPPPGy'** turB   c                    |                                 }g }| j        r,|                    t          j        | j                             | j        r,|                    t          j        | j                             | j        r,|                    t          j        | j                             |s|S dd                    |           d}t          j        |d|dt          j	                  }|                                 S )aC  Remove a leading @mention of this agent so the remaining text can be
        recognized as a slash command or clean prompt.

        Mirrors the Discord adapter, which strips its own ``<@id>`` mention
        before dispatch. Without this a channel message like ``@Chip /whoami``
        arrives with a leading ``@Chip``; the gateway's ``is_command()`` checks
        ``text.lstrip().startswith("/")`` and never fires the command. Only a
        LEADING mention is stripped (case-insensitive); mentions mid-sentence
        are left intact so normal prose is unaffected.
        z^@?(?:|z)[\s:,]*r'   r$   )countflags)
rt   r  rT   r}   r  r   r   rm   sub
IGNORECASE)r  rP  r   r   r  strippeds         r   r  zBuzzAdapter._strip_mention  s     }}
 	=bi(:;;<<<? 	:bi88999 	<bi(9::;;; 	K ;CHHZ00:::6'2t1BMJJJ~~rB   r$  c                   K   | j                             |          }||S d}|                     ddd|g           d{V \  }}}|dk    rMt          |          }|r<t	          |d                             d          pd                                          }|st          |          p|dd         }|| j         |<   |S )	aC  Resolve a pubkey to a display name (cached; falls back to npub prefix).

        Failures are cached too (negative caching): without it, every message
        from a profile-less pubkey re-runs ``users get`` each poll sweep,
        which amplifies badly when several adapter instances poll in one
        process.
        Nr'   r!  r   z--pubkeyr   r%  r3  )r  r   r  r   r   rt   rp   )r  r$  cachedr   r?  r@  rz  rB  s           r   r  zBuzzAdapter._resolve_user_name  s       !%%f--M $wz6.R S SSSSSSSc4199',,H J8A;??>::@bAAGGII 	8''163B37D#' rB   c                     | d         }t          |          t          k    r0|                    d           t          |          t          k    .d S d S )Nr  F)last)rl   	_SEEN_CAPpopitem)ry  r  s     r   r  zBuzzAdapter._trim_seen  sO    V}$ii)##LLeL$$$ $ii)######rB   r^  c                     | j                             |          }|"d |d         |<   |                     |           d S d S )Nr  )r  r   r  )r  r*  r^  ry  s       r   rc  zBuzzAdapter._mark_seen  sL    #''
33&*E&M(#OOE""""" rB   r   r  r  r  c           	        K   | j         sdS |                     || j                            ||          |||          }t	          |t
          j        |||rt          j        |          nt          j	                              }	| 
                    |	           d{V  	 |                     ||d           d{V  dS # t          $ r) t                              d|dd         d           Y dS w xY w)	z;Build a MessageEvent and hand it to the base class handler.N)rO  	chat_namer/  r  r  )r   message_typesourcera  	timestampu   👀z$Buzz: reaction failed for message %srl  Tr  )_message_handlerbuild_sourcer	  r   r   r   TEXTr   fromtimestampnowhandle_messagero  rK  r4  rm  )
r  r   rO  r/  r  r  ra  r  r  r  s
             r   r  zBuzzAdapter._dispatch_message  s?      $ 	F"")--gw?? # 
 
 $)!<FZh,Z888HLNN
 
 
 !!%(((((((((	a$$Wj&AAAAAAAAAAA 	a 	a 	aLL?CRC[_L``````	as    B? ?/C21C2r8   N)NNre   )NNN)0__name__
__module____qualname____doc__r   propertyr   r   r   r	   r
   r  r  rd  rG  r:  r   r   r   rV  rg  ro  rv  r{  r  r9  r  r  r  r   r  r  r<  r7  r8  r  r  r  r  r  r  r  r  staticmethodr  rc  r  __classcell__)r  s   @r   r   r   _  s        
Q Q Q Q Qf c    X
 NR 	
 	
 	
49 	
Xc] 	
V[\_adfi\iVj 	
 	
 	
 	
 5: m m mT md m m m m^   H #'-1!
 !
!
 !
 3-	!

 4S>*!
 
!
 !
 !
 !
F      3 C  PT    < "&"&-1$T $T$T $T #	$T
 3-$T 4S>*$T 
$T $T $T $TLP3 P4S> P P P P0X X X X X    6O O O O<I3 I\_ Idh I I I ItC#<N7O    ,OtCQYZ]Q^L^G_ Ohl Oqu O O O O<$ <$ <$ <$@   $c c d    6'i4 'iD 'i 'i 'i 'iRc d    &3
c 3
$ 3
t 3
PT 3
 3
 3
 3
^0 0 0 0 0 0L3 Lt L L L L L6g# gd g4 gD g g g gS T     c  c        6s s    , %$ %4 % % % \%
#S #C #D # # # #%a%a %a 	%a
 %a %a %a %a 
%a %a %a %a %a %a %a %arB   r   c                      t          j        dd                                          sdS t          t	                                S )z?Check if Buzz is configured: a relay URL plus a resolvable key.r   r'   F)r   r   rt   rd  r   rN   rB   r   check_requirementsr    s=    9%r**0022 u$&&'''rB   c                     t          | di           pi }t          j        d          p|                    dd          }t	          |ot          |                    S )z=Validate that the platform config has enough info to connect.r   r   r   r'   )r   r   r   r   rd  r   )r   r   relays      r   validate_configr    sW    FGR((.BEI&''E599["+E+EE5.u55666rB   c                      t          |           S )z6Check whether Buzz is configured (env or config.yaml).)r  )r   s    r   is_connectedr    s    6"""rB   yaml_cfgbuzz_cfgc                    |                     d|          pi }t          |t                    sdS ddddd}|                                D ]L\  }}|                     |          }|r0t	          j        |          st          |          t          j        |<   M|                     d          }|0t	          j        d	          st          |          t          j        d	<   |                     d
          }|kt	          j        d          sWt          |t          t          f          rd
                    d |D                       }t          |          t          j        d<   |                     d          }	|	kt	          j        d          sWt          |	t          t          f          rd
                    d |	D                       }	t          |	          t          j        d<   d|v rHt	          j        d          s4t          |d                                                   t          j        d<   d|v rHt	          j        d          s4t          |d                                                   t          j        d<   dS )u  Translate ``config.yaml`` ``buzz.extra`` keys into ``BUZZ_*`` env vars.

    Implements the ``apply_yaml_config_fn`` contract.  ``check_requirements``
    and the adapter's connect path read configuration from the environment, so
    a config.yaml-only setup (no ``BUZZ_*`` env vars beyond the secret) would
    otherwise fail the ``check_fn`` gate and be silently skipped at gateway
    startup.  This hook bridges the ``extra`` block into env, mirroring the
    Slack/Telegram pattern.  Env vars win over YAML — every assignment is
    guarded by ``not os.getenv(...)`` so explicit env overrides survive a
    config.yaml update.  ``BUZZ_PRIVATE_KEY`` is a secret and stays in ``.env``;
    it is never sourced from config.yaml here.
    r   Nr   r   r   r   )r   r   r   r   r   r   r   r   r   c              3   4   K   | ]}t          |          V  d S re   r   rH   s     r   rh   z%_apply_yaml_config.<locals>.<genexpr>  s(      991A999999rB   r   r   c              3   4   K   | ]}t          |          V  d S re   r!  )rI   as     r   rh   z%_apply_yaml_config.<locals>.<genexpr>!  s(      77!s1vv777777rB   allow_all_usersBUZZ_ALLOW_ALL_USERSr   r   )r   r   r   itemsr   r   r   r   r   r  rm   ru   )
r  r  r   	_str_keyssrcr   r   r  r   alloweds
             r   _apply_yaml_configr*    sC    LL(++1rEeT"" t%#+%	 I OO%% ' 'Siinn 	'ry~~ 	'!#hhBJsOyy))HBI.B$C$C+.x==
'(yy$$HBIo$>$>hu.. 	:xx9999999H&)(mm
?#ii((G29-A#B#Bge}-- 	8hh77w77777G+.w<<
'(E!!")4J*K*K!-07H1I-J-J-P-P-R-R
)*E!!")4J*K*K!-07H1I-J-J-P-P-R-R
)*4rB   c                     t          j        dd                                          } | rt                      sdS d| i}t          j        dd                                          }|r"d |                    d          D             |d<   t          j        d	d                                          }|r$	 t          |          |d
<   n# t          $ r Y nw xY wt          j        dd                                          }|r||d<   t          j        dd                                          p|                    d          pdgd         }|r|t          j        d|          d|d<   |S )u  Seed ``PlatformConfig.extra`` from env vars during gateway config load.

    Called BEFORE adapter construction so env-only setups show up in
    ``hermes gateway status`` and ``get_connected_platforms()``.  Returns
    ``None`` when Buzz isn't minimally configured.

    The special ``home_channel`` key is handled by the core hook — it becomes
    a proper ``HomeChannel`` on the ``PlatformConfig``.
    r   r'   Nr   r   c                 ^    g | ]*}|                                 |                                 +S rN   )rt   rH   s     r   rK   z#_env_enablement.<locals>.<listcomp>:  s-    PPP!aggiiPAGGIIPPPrB   r   r   r   r   r   r   r   r   BUZZ_HOME_CHANNEL_NAME)rO  r   r   )r   r   rt   r   r   r   rk   r   )r  r1  r   r  r   r   s         r   _env_enablementr.  *  s    I&++1133E ,.. tu%Dy"--3355H QPPx~~c/B/BPPPZy-r2288::H 	$)(OOD!! 	 	 	D	y"--3355H $#Z 9("--3355Z$((::N:N:VSURVXY9ZD 
I6== 
  
^ Ks   3C 
CCF)rZ  media_filesforce_documentrO  r   rZ  r/  r0  c                B  K   t          | di           pi }t          j        d          p|                    dd                                          }t          |          }t          t          j        dd                                          p$t          |                    dd          pd                    }	|r|sddiS |	sdd	iS |pd                                pJt          j        d
          p$t          |                    dd          pd                                          }
|
sddiS ddd|
ddg}|r|dt          |          gz  }|pg D ]}|dt          |          gz  }	 t          |	||||           d{V \  }}}n-# t          j
        $ r  t          $ r}dd| icY d}~S d}~ww xY w|dk    rddt          ||           iS 	 t          j        |pd          }n# t          $ r i }Y nw xY wdt          |                    d          pd          dS )a  One-shot send without a live adapter (out-of-process cron delivery).

    Used by ``tools/send_message_tool`` when ``hermes cron`` runs separately
    from the gateway process.  Without this hook, ``deliver=buzz`` cron jobs
    fail with ``No live adapter for platform 'buzz'``.
    r   r   r   r'   r   r   r   zLBuzz standalone send: BUZZ_RELAY_URL and BUZZ_PRIVATE_KEY must be configuredz/Buzz standalone send: buzz CLI binary not foundr   r   z?Buzz standalone send: no target channel (set BUZZ_HOME_CHANNEL)rU  rV  rW  rX  rY  r[  rs  r  Nz+Buzz standalone send failed to launch CLI: r   zBuzz standalone send failed: r]  Tr^  )rT  ra  )r   r   r   r   rt   r   r   r   r   r   rN  r   r   r   r   rk   )pconfigrO  r   rZ  r/  r0  r   r  r   r   targetr   r2   r?  r@  rA  r  rU   s                     r   _standalone_sendr4  O  s      GWb))/REY'((FEIIk2,F,FMMOOE&u--K 
	/2&&,,..V#eii
B6O6O6USU2V2V H  i ighh LJKKm""$$|3F)G)G)s3uyyYgikOlOlOrprKsKs(z(z(|(|F \Z[[V[#FD /s9~~..!r & &3t99%%L)deQX 
  
  
 
 
 
 
 
 
c33 !    L L LJqJJKKKKKKKLqyyX9KCQU9V9VXXYYz#+&&   3txx
/C/C/Ir+J+JKKKs0   )F F2!F-'F2-F2G( (G76G7c                  8   ddl m} m}m}m}m}m}m}m}  |d            |d          }|r |d| d            |dd          sd	S  |d
            |d           t                        | d|pd          }	|	s |d           d	S  |d|	
                                            | dd          }
|
r |d|

                                           nt                      s |d            | d |d          pd          }|r  |d|                    dd                      | d |d          pd          }|r |d|
                                           t                        |d            |dd          }|r$ |dd            |dd            |d           nH |dd             | d! |d          pd          } |d|r|                    dd          nd           t                        |d"            |d#           d	S )$zInteractive ``hermes gateway setup`` flow for the Buzz platform.

    Lazy-imports ``hermes_cli.setup`` helpers so the plugin stays importable
    in non-CLI contexts (gateway runtime, tests).
    r   )promptprompt_yes_nosave_env_valueget_env_valueprint_header
print_infoprint_warningprint_successr  r   z!Buzz: already configured (relay: r   zReconfigure Buzz?FNzNConnect Hermes to a Buzz community (Block's Nostr-based human+agent platform).zK   Requires the buzz CLI binary and a Nostr key that is a community member.z9Relay URL (e.g. https://mycommunity.communities.buzz.xyz)r'   )r   u-   Relay URL is required — skipping Buzz setupz<Nostr private key (nsec or hex; leave blank to keep current)T)passwordr   uN   No private key configured — set BUZZ_PRIVATE_KEY before starting the gatewayzEChannel UUIDs to watch (comma-separated, empty = all joined channels)r    z;Home channel UUID for cron/notification delivery (optional)r   u7   🔒 Access control: restrict who can talk to the agentz1Allow all community members to talk to the agent?r%  truer   uF   ⚠️  Open access — anyone in the community can command the agent.r   zLAllowed users (comma-separated npubs or hex pubkeys, empty to deny everyone)z*Buzz configuration saved to ~/.hermes/.envzFRestart the gateway for changes to take effect: hermes gateway restart)hermes_cli.setupr6  r7  r8  r9  r:  r;  r<  r=  printrt   r   r   )r6  r7  r8  r9  r:  r;  r<  r=  existing_relayr  r   r   r   	allow_allr)  s                  r   interactive_setuprE    sa   	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 L"]#344N 
H~HHHIII}0%88 	FJ_```J\]]]	GGGFC$"  E  EFFFN#U[[]]333
&OZ^
_
_
_C
 h)399;;7777!## hfgggvOo..4"  H  C(8(8b(A(ABBB6E1228b  D  :*DJJLL999	GGGJHIIIQSXYYI 
Z-v666+R000^____-w777&Z!M"677=2
 
 
 	+-XW__S"-E-E-EVXYYY	GGGM>???JWXXXXXrB   c                     |                      ddd t          t          t          ddgdt          t
          t          dt          dd	d
ddd           dS )z7Plugin entry point: called by the Hermes plugin system.r   r  c                      t          |           S re   )r   )cfgs    r   <lambda>zregister.<locals>.<lambda>  s    K$4$4 rB   r   r   zXRequires the buzz CLI binary (https://github.com/block/buzz) on PATH or at BUZZ_CLI_PATHr   r   r%  u   🐝FTzYou are collaborating in a Buzz workspace (Block's Nostr-based human+agent platform). Markdown IS supported. Users address you by @-mentioning your name or npub in channels; direct messages reach you without a mention. Keep responses conversational.)r   labeladapter_factorycheck_fnr  r  required_envinstall_hintsetup_fnenv_enablement_fnapply_yaml_config_fncron_deliver_env_varstandalone_sender_fnallowed_users_envallow_all_envrh  pii_safeallow_update_commandplatform_hintN)register_platformr  r  r  rE  r.  r*  r4  )ctxs    r   registerr[    su    44#'!&(:;o" * 00 ..,!JE  ' ' ' ' 'rB   re   )T)r'   r  )Nr  r   r   loggingr   r}   r   r  collectionsr   r   pathlibr   typingr   r   r   r	   r
   urllib.parser   r   agent.secret_scoper   r   r   r   r   	getLoggerr  r4  gateway.platforms.baser   r   r   r   gateway.configr   r  r  r   r  r   r   _CLI_TIMEOUTr  r  r  r  r   r   r5   rf   r  rA   r   rO   rd  rZ   rp   rz   r   r   r   r   r   r   r   r   r   r  r  r  r*  r.  r4  rE  r[  rN   rB   r   <module>rf     s  $ $L    				 				   # # # # # #             3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - - - J J J J J J ? ? ? ? ? ?/ / / /( 
	8	$	$            $ # # # # # 
	      !  0   4 011<<>>   6 5DI #    IC IDI I I I I  c  QUVYQZH[    *JC JHSM J J J J" c  hsm        $	S 	Xc] 	 	 	 	 7 7# 7s 7 7 7 7   #    H !%!' ' ''
s)' 	'
 ' ' ' 3S=' ' ' 'TBs B B B B B B =S =T$Z = = = =Ca Ca Ca Ca Ca% Ca Ca CaT(D ( ( ( (7t 7 7 7 7#D # # # #
+ + +(4. + + + +\"$ " " " "T  $'+ 0L 0L 0L0L 0L
 }0L $s)$0L 0L 
#s(^0L 0L 0L 0LfJY JY JY JYZ) ) ) ) )rB   