
    Tj{6                        d Z ddlmZ ddlZddlZddlZddlZej                  j                  dd      j                  d      Zej                  j                  dd      Zdd	Zdd
Z	 	 d	 ddZddZd Zy)u  Smartlead cold-email control — in-process MCP so Jarvis runs Ahmed's outreach.

Smartlead (server.smartlead.ai) is the platform where Ahmed's cold-email
campaigns live — sender inboxes (warmed), campaigns, email sequences, the leads
in each campaign, and the reply/open analytics. This module gives Jarvis hands
on ALL of it by voice: report how campaigns are doing, spin one up, drop the
Ultron-scraped verified leads into it, write the follow-up sequence, and
start/pause it.

Auth: a single admin API key in .env as SMARTLEAD_API_KEY, passed as the
`?api_key=` query param on every call (Smartlead's scheme). MCP_SMARTLEAD=0
disables the whole server. No extra deps — stdlib urllib, same as the other
_control.py modules.

Tools (Claude sees them as mcp__smartlead__<name>):
  smartlead_campaigns        list campaigns + status
  smartlead_campaign_stats   analytics for one campaign (sent/open/reply/bounce)
  smartlead_senders          sender inboxes + warmup reputation/health
  smartlead_create_campaign  make a new campaign, returns its id
  smartlead_set_sequence     write the email steps/follow-ups for a campaign
  smartlead_add_leads        push leads (e.g. Ultron verified emails) into one
  smartlead_campaign_control start / pause / stop a campaign
  smartlead_api              generic passthrough for ANY other endpoint (full control)

The named tools cover the common voice actions; smartlead_api is the escape
hatch so "full control" really means full — any REST endpoint Smartlead exposes.
    )annotationsNSMARTLEAD_URLz"https://server.smartlead.ai/api/v1/SMARTLEAD_API_KEY c                 h    t         j                  j                  dd      dk(  ryt        t              S )z5On when a key is present and not explicitly disabled.MCP_SMARTLEAD10F)osenvirongetbool_KEY     E/Users/ahmed/devFolder/Ultron/claude-voice/voice/smartlead_control.pyenabledr   (   s&    	zz~~os+s2:r   c                    dd| dgiS )Ncontenttext)typer   r   )msgs    r   _textr   /   s    5677r   c                   |j                  d      sd|z   }d|v rdnd}t         | | dt         }|#t        j                  |      j                         nd}t        j                  j                  |||       }|j                  dd       |j                  d	d
       |j                  dd       	 t        j                  j                  ||      5 }|j                         }	ddd       	rt        j"                  |	      S i S # 1 sw Y   "xY w# t        j                  j                  $ rC}
|
j                         dd j                  dd      }t        d|
j                    d|       d}
~
ww xY w)zOne Smartlead REST call. api_key is appended as a query param. Raises
    RuntimeError with the server's message on a non-2xx so the tool can speak it.r   ?&zapi_key=N)datamethodzContent-Typezapplication/jsonz
User-AgentzuMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36Accept)timeouti  zutf-8ignorezHTTP z: )
startswith_BASEr   jsondumpsencodeurllibrequestRequest
add_headerurlopenreaderror	HTTPErrordecodeRuntimeErrorcodeloads)r   pathbodyr!   sepurlr   reqrrawedetails               r   _apir=   3   sM    ??3Tz##CGD6#htf
-C(,(84::d""$dD
..
 
 4
 
?CNN>#56 NN<45 NN8/07^^##C#9Q&&(C :
 "4::c?)r) :9<<!! 7$3&&w9U166("VH5667s0   2!D D$D D
D E1.>E,,E1c                ,   t        |       j                         } | sy| j                         r| S 	 t        dd      xs g D ]X  }t        |j	                  dd            j                         | j                         k(  s>t        |j	                  d            c S  t        dd      xs g D ]W  }| j                         t        |j	                  dd            j                         v s=t        |j	                  d            c S  	 y# t        $ r Y yw xY w)zDResolve a campaign id from an id or a (case-insensitive) name match.NGET
/campaignsnamer   id)strstripisdigitr=   r   lower	Exception)refcs     r   _campaign_idrJ   N   s    
c(..
C
{{}
e\*0b0A155$%++-<155;'' 1 e\*0b0Ayy{c!%%"34::<<155;'' 1
   s+   AD >D AD (D D 	DDc                   
 dd l 
ddlm} m}  | ddi       d$
fd       } | dddt        i      d$
fd	       } | d
di       d$
fd       } | dddt        i      d$
fd       } | ddt        t        d      d$
fd       } | ddt        t        d      d$
fd       } | ddt        t        d      d$
fd       } | ddt        t        t        d      d$
fd        }	 |d!d"||||||||	g#      S )%Nr   )toolcreate_sdk_mcp_serversmartlead_campaignszList Ahmed's Smartlead cold-email campaigns with their status (DRAFTED/ACTIVE/PAUSED/COMPLETED). Use for 'how are my campaigns', 'what campaigns do I have', 'is the outreach running'.c                  K   	 j                  t        dd       d {   }|st        d      S |D cg c]:  }d|j                  d       d|j                  d      d|j                  d	       < }}t        t	        |       d
dj                  |      z         S 7 c c}w # t        $ r$}t        dt        |      d d        cY d }~S d }~ww xY ww)Nr?   r@   uQ   No campaigns yet — the account has sender inboxes ready but nothing is running.z- [rB   z] rA   u    — statusz campaign(s):

zsmartlead_campaigns failed:    )	to_threadr=   r   r   lenjoinrG   rC   )argscampsrI   linesr;   asyncios        r   rN   z)build_server.<locals>.smartlead_campaignse   s     
		H!++D%FFE = > > $%# 155;-r!%%-):%h?PQ#  %CJ<7$))E:JJKK G%  	H7At~FGG	Hs_   CB& BB& CB& ?B!4*B& CB& !B& &	C/CC	CCCsmartlead_campaign_statszGet performance analytics for ONE campaign: sent, opens, replies, bounces, unsubscribes. `campaign` = the id or the campaign name. Use for 'how is the <name> campaign doing', 'any replies on the outreach'.campaignc                  K   j                  t        | j                  dd             d {   }|st        d      S 	 j                  t        dd| d       d {   fd}t        dj                  d	|       d
 |d       d |d       d |d       d |d       d |d       d |d      xs  |d       d      S 7 7 j# t
        $ r$}t        dt        |      d d        cY d }~S d }~ww xY ww)Nr[   r   uZ   Couldn't find that campaign — try smartlead_campaigns first to get the exact name or id.r?   /campaigns/z
/analyticsc                (    j                  | d      S )Nr   )r   )kas    r   <lambda>z@build_server.<locals>.smartlead_campaign_stats.<locals>.<lambda>   s    !%%1+r   	Campaign rA   z: sent 
sent_countz	, opened 
open_countz
, replied reply_countz
, bounced bounce_countz, unsubscribed unsubscribed_countz, leads campaign_lead_statstotal_count.z!smartlead_campaign_stats failed: rR   )rS   rJ   r   r   r=   rG   rC   )rV   cidgr;   r`   rY   s       @r   rZ   z.build_server.<locals>.smartlead_campaign_statsu   s4     %%lDHHZ4LMM > ? ?
	M''e{3%z5RSSA%AAEE&#./ 0,(	!L/1B C]+,Jq7H6I J  !"6 78 901EQ}5EFa	IJ J N
 T  	M<SVDS\NKLL	MsR   *C<CC<C C
 A'C C<
C 	C9C4.C9/C<4C99C<smartlead_senderszList the sender email inboxes and their warmup health (reputation, daily send limit). Use for 'how are my inboxes', 'are the emails warmed up', 'what's my sending reputation'.c                  K   	 j                  t        dd       d {   }|st        d      S g }|D ]v  }|j                  d      xs i }|j	                  d|j                  d       d|j                  dd	       d
|j                  dd       d|j                  d      rdnd        x t        t        |       ddj                  |      z         S 7 # t        $ r$}t        dt        |      d d        cY d }~S d }~ww xY ww)Nr?   z#/email-accounts/?offset=0&limit=100zNo sender inboxes configured.warmup_detailsz- 
from_emailu    — reputation warmup_reputationzn/az, message_per_dayr   z/day, warmup is_warmup_enabledonoffz inbox(es):
rQ   zsmartlead_senders failed: rR   )	rS   r=   r   r   appendrT   rU   rG   rC   )rV   accsrX   r;   wurY   s        r   rm   z'build_server.<locals>.smartlead_senders   s     
	F **eBD DD<==EUU+,2|,--=vv159:"uu.45 6&'ee,?&@deLNO  CI;m4tyy7GGHHD  	F5c!fTcl^DEE	FsO   D	C CC D	B%C D	C 	D"D;D<D	DD	smartlead_create_campaignzCreate a new (empty, DRAFTED) campaign and return its id. Follow up with smartlead_set_sequence + smartlead_add_leads, then smartlead_campaign_control to start it. `name` is the campaign name.rA   c                  K   t        | j                  dd            j                         }|st        d      S 	 j	                  t
        ddd|i       d {   }|j                  d      xs |j                  d      }t        d|d	| d
      S 7 :# t        $ r$}t        dt        |      d d        cY d }~S d }~ww xY ww)NrA   r   z.smartlead_create_campaign failed: need a name.POSTz/campaigns/createrB   campaign_idzCreated campaign z (id z(). Next: set its sequence and add leads.z"smartlead_create_campaign failed: rR   )rC   r   rD   r   rS   r=   rG   )rV   rA   outrk   r;   rY   s        r   ry   z/build_server.<locals>.smartlead_create_campaign   s      488FB'(..0IJJ	N))f1FD>C CC''$-9377=#9C,THE# ?A A B BC
  	N=c!fTcl^LMM	NsF   7CB B9B CB 	CB=7C8C=CCsmartlead_set_sequenceao  Set/overwrite a campaign's email sequence (the initial email + follow-ups). `campaign` = id or name. `sequence_json` = a JSON array of steps, each {"seq_number":1, "seq_delay_details":{"delay_in_days":0}, "subject":"...", "email_body":"<p>...</p>"}. Use {{first_name}} etc. for personalisation. Follow-ups usually have subject:"" so they thread under the first email.)r[   sequence_jsonc                  K   j                  t        | j                  dd             d {   }|st        d      S 	 t	        j
                  | j                  dd            }	 j                  t        dd	| d
d|i       d {    t        dt        |       d| d      S 7 y# t        $ r$}t        dt        |      d d        cY d }~S d }~ww xY w7 Q# t        $ r$}t        dt        |      d d        cY d }~S d }~ww xY ww)Nr[   r   Couldn't find that campaign.r   []z sequence_json isn't valid JSON: x   r{   r]   z
/sequences	sequenceszSaved a z-step sequence on campaign rj   zsmartlead_set_sequence failed: rR   
rS   rJ   r   r   r%   r3   rG   rC   r=   rT   )rV   rk   seqsr;   rY   s       r   r~   z,build_server.<locals>.smartlead_set_sequence   s     %%lDHHZ4LMM788	L::dhh=>D	K##fC5
;d#% % % 8CI;.I#aPQQ N
  	L;CF4CL>JKK	L%  	K:3q6$3<.IJJ	Ks{   *DB'D %B) &!C CC &D)	C2CCDCDC 	D$D=D>DDDsmartlead_add_leadsuS  Add leads to a campaign — this is how Ultron's scraped, verified emails get loaded for outreach. `campaign` = id or name. `leads_json` = a JSON array of leads, each {"email":"..", "first_name":"..", "last_name":"..", "company_name":"..", "phone_number":"..", "custom_fields":{..}}. email is required; the rest optional. Max 100 per call.)r[   
leads_jsonc           	       K   j                  t        | j                  dd             d {   }|st        d      S 	 t	        j
                  | j                  dd            }|st        d      S 	 j                  t        d	d
| dd|d d i       d {   }|j                  d|j                  dt        |                  }|j                  dd      }t        d| d| |rd| dz         S dz         S 7 # t        $ r$}t        dt        |      d d        cY d }~S d }~ww xY w7 # t        $ r$}t        dt        |      d d        cY d }~S d }~ww xY ww)Nr[   r   r   r   r   zleads_json isn't valid JSON: r   zNo leads to add.r{   r]   z/leads	lead_listd   upload_counttotal_leadsalready_added_to_campaignr   zAdded z lead(s) to campaign z (z were already in it).rj   zsmartlead_add_leads failed: rR   r   )rV   rk   leadsr;   r}   upduprY   s          r   r   z)build_server.<locals>.smartlead_add_leads   s     %%lDHHZ4LMM788	IJJtxxd;<E +,,		H))fC57eDSk*, ,C E
)KLB''5q9C6"%:3%@?Bb%:;M N NHKM N N N
  	I8QGHH	I
,  	H7At~FGG	Hs   *EC;E %C= %E3$D/ D-AD/ 2E3D/ :E=	D*D%D* E%D**E-D/ /	E8EEEEEsmartlead_campaign_controlzStart, pause, or stop a campaign's sending. `campaign` = id or name. `action` = 'start' | 'pause' | 'stop'. Use for 'start the outreach', 'pause the campaign', 'stop sending'.)r[   actionc                  K   j                  t        | j                  dd             d {   }|st        d      S dddd}|j                  t	        | j                  dd            j                               }|st        d	      S 	 j                  t        d
d| dd|i       d {    t        d| d| d      S 7 7 # t        $ r$}t        dt	        |      d d        cY d }~S d }~ww xY ww)Nr[   r   r   STARTPAUSEDSTOPPED)startpausestopr   z%action must be start, pause, or stop.r{   r]   z/statusrP   rb   u    → rj   z#smartlead_campaign_control failed: rR   )rS   rJ   r   r   rC   rF   r=   rG   )rV   rk   mrP   r;   rY   s        r   r   z0build_server.<locals>.smartlead_campaign_control   s	     %%lDHHZ4LMM788)Ds488Hb1288:;@AA	O##fC588V:LN N N9SEvha899 NN  	O>s1vds|nMNN	OsR   *C7CAC7!C ,C-C C7C 	C4C/)C4*C7/C44C7smartlead_apiu  Escape hatch for FULL control — call ANY Smartlead REST endpoint the named tools don't cover (webhooks, lead status, schedules, warmup toggles, delete, etc.). `method` = GET/POST/DELETE. `path` = the path after /api/v1, e.g. '/campaigns/123/leads?offset=0&limit=20' (api_key is added for you). `body_json` = JSON string for the request body (omit for GET). Returns the raw JSON. Prefer a named tool when one fits; use this for everything else.)r   r4   	body_jsonc                  K   t        | j                  dd            j                         }t        | j                  dd            j                         }|st	        d      S d }| j                  d      r	 t        j                  | d         }	 j                  t        |||       d {   }t        j                  |d	
      }t	        |d d t        |      dkD  rdz         S dz         S # t        $ r$}t	        dt        |      d d        cY d }~S d }~ww xY w7 r# t        $ r$}t	        dt        |      d d        cY d }~S d }~ww xY ww)Nr   r?   r4   r   z"smartlead_api failed: need a path.r   zbody_json isn't valid JSON: r   F)ensure_asciii  u    …[truncated]zsmartlead_api failed: i,  )rC   r   upperrD   r   r%   r3   rG   rS   r=   r&   rT   )rV   r   r4   r5   r;   r}   srY   s          r   r   z#build_server.<locals>.smartlead_api   s@     TXXh./557488FB'(..0=>>88K Lzz${"34	B))$dCCC

3U3A5D#a&4-%6PQQRPQQ  L;CF4CL>JKKL D  	B1#a&#,@AA	Bs~   A3E7C. D  +D,9D  %E&D  -E.	D7DDEDED   	E)EEEEE	smartleadz1.0.0)rA   versiontools)rV   dictreturnr   )rY   claude_agent_sdkrL   rM   rC   )rL   rM   rN   rZ   rm   ry   r~   r   r   r   rY   s             @r   build_serverr   a   s   <	
CDFH
H	H
H 

$S s
	
M
M" 

89;=F	=F$ 

%Q 3-	
N
N 

"? S
13K3K  

1 #
.0H0H* 

&2 c
*	,
O,
O 
/0 #C
8:B:B$ !'"$<>O(*@"$>O r   )r   r   )r   rC   r   r   )N   )r   rC   r4   rC   r5   zdict | list | Noner!   int)rH   rC   r   z
str | None)__doc__
__future__r   r%   r   urllib.errorr(   urllib.requestr   r   rstripr$   r   r   r   r=   rJ   r   r   r   r   <module>r      s~   6 #  	  


;	==CVC[ 	zz~~)2.8 =A**6&{r   