
    5Uj                       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ej                  j                  dd      j                  d      Zi Zded<   i Zded	<   d
ZdZdZdZd:dZd;dZ G d de      Z	 	 	 d<	 	 	 	 	 	 	 d=dZd>d?dZd@dZdAdZdBdZd;dZdCdZd:dZ dDdZ!dEdFdZ"dGdZ#dHdZ$dGdZ%dEdIdZ&d:d Z'dJd!Z(dKd"Z)dKd#Z*dKd$Z+d:d%Z,d:d&Z-dLdMd'Z.	 	 dN	 	 	 dOd(Z/	 	 dP	 	 	 dQd)Z0	 	 dR	 	 	 dSd*Z1	 	 dT	 	 	 dUd+Z2dVd,Z3	 	 dW	 	 	 	 	 dXd-Z4	 	 dY	 	 	 	 	 	 	 dZd.Z5	 	 d[	 	 	 	 	 d\d/Z6	 	 dR	 	 	 d]d0Z7dVd1Z8dVd2Z9	 	 	 d^	 	 	 	 	 	 	 d_d3Z:d`dad4Z;dbd5Z<dcddd6Z=	 	 	 	 ded7Z>	 	 d[	 	 	 	 	 dfd8Z?dgdhd9Z@y)iu  Asana — Ahmed's project management (projects, tasks, due dates, assignees).

THE single source of truth for Asana in this repo. Two doors use this ONE module:
  • the hosted connector  — jarvis-tools-service/mcp_server.py  (@mcp.tool wrappers)
  • voice Jarvis          — voice/asana_control.py               (thin SDK-MCP wrapper
                            that imports THIS file; no REST logic is duplicated)
Fix a bug here and both doors are fixed.

Plain HTTPS REST against https://app.asana.com/api/1.0 with a Personal Access
Token — no OAuth, no SDK, stdlib urllib only (the Docker image and the voice
engine both depend on there being no new deps).

Env: ASANA_PAT       — Personal Access Token (asana.com → Settings → Apps →
                       Developer apps → Personal access tokens).
     ASANA_WORKSPACE — optional; workspace gid or name to pin. If unset we
                       resolve (and cache) the account's first workspace.

Results are read aloud by voice Jarvis → return short plain prose / simple
dashed lists. No markdown, no raw JSON (the only exception is `raw()`, the
escape hatch, which is for the model, not for speech). Nothing here ever raises:
every failure comes back as one human sentence.

API gotchas honoured here (learned the hard way / from the OpenAPI spec):
  • {"data": …} envelope BOTH ways — wrap request bodies, unwrap responses.
  • opt_fields or you get NOTHING but gid/resource_type/name. Every call sends it.
  • projects/tags/followers are CREATE-ONLY on a task — changing them needs
    addProject / removeProject / addTag / addFollowers. A "move" is
    addProject + removeProject (add alone leaves the task in BOTH projects).
  • Project create needs a TEAM when the workspace is an organization.
  • Archive (PUT {archived:true}) ≠ delete. There is no /archive endpoint.
  • /tasks/search is PREMIUM (402 on free) → transparent typeahead fallback.
  • next_page lives OUTSIDE data → _paged() follows it.
  • 429 carries Retry-After; rejected calls still burn quota → we sleep + retry once.

NOTE: changing MCP tools needs a Railway redeploy (push to origin/jarvis-v2).
    )annotationsN	ASANA_URLzhttps://app.asana.com/api/1.0/zdict[str, str]	_WS_CACHE_RECENTz&Asana isn't configured (no ASANA_PAT).z8name,due_on,due_at,completed,assignee.name,projects.namea  name,notes,completed,completed_at,due_on,due_at,start_on,assignee.name,projects.name,parent.name,tags.name,followers.name,num_subtasks,memberships.project.name,memberships.section.name,custom_fields.name,custom_fields.display_value,created_at,modified_at,permalink_urlzIname,archived,due_on,owner.name,current_status.title,current_status.colorc                 ^    t         j                  j                  dd      j                         S )N	ASANA_PAT )osenvirongetstrip     jarvis-tools-service/asana.py_patr   A   s     ::>>+r*0022r   c                 (    t        t                     S N)boolr   r   r   r   
configuredr   E   s    <r   c                  &     e Zd ZdZdd fdZ xZS )
AsanaErrorzIA short, speakable failure reason. `.code` = HTTP status (0 if not HTTP).c                2    t         |   |       || _        y r   )super__init__code)selfmsgr   	__class__s      r   r   zAsanaError.__init__L   s    	r   )r   )r   strr   int)__name__
__module____qualname____doc__r   __classcell__)r   s   @r   r   r   I   s    S r   r   c           
     Z   |j                  d      sd|z   }t         | }|rU|j                         D 	ci c]  \  }}	|	dvs||	 }
}}	|
r+|d|v rdndt        j                  j                  |
      z   z  }|%t        j                  d|i      j                         nd}t        j                  j                  ||| j                               }|j                  dd	t                       |j                  d
d       ||j                  dd       	 t        j                  j                  ||      5 }|j                         }ddd       rt        j$                  |      ni }|r|S |j'                  d|      S c c}	}w # 1 sw Y   >xY w# t        j                   j"                  $ r}d}	 t        j$                  |j                         xs d      }|j'                  d      xs g }dj)                  d |D              dd }n# t*        $ r Y nw xY w|j,                  dk(  rd}	 t/        |j0                  j'                  d      xs d      }n# t*        $ r d}Y nw xY w|s;|dk  r6t3        j4                  t7        |d             t9        | |||||d      cY d}~S t;        dt=        |      xs d dd      |j,                  dk(  rt;        d d      |j,                  d!k(  rt;        d"d!      |j,                  d#k(  r|rd$| nd}t;        d%| d&d#      |j,                  d'k(  rt;        d(d'      t;        d)|j,                   d$|xs d* |j,                        d}~wt        j                   j>                  $ r)}t;        d+tA        |jB                        dd,        d}~ww xY w)-u   One Asana REST call. Returns the unwrapped `data` payload (or the whole
    envelope when raw_envelope=True, which is how _paged() sees `next_page`).
    Raises AsanaError with a short human reason — callers turn that into a sentence.
    r   )Nr
   ?&Ndata)r*   methodAuthorizationzBearer Acceptzapplication/jsonzContent-Type)timeoutr
   s   {}errors; c              3  R   K   | ]  }t        |j                  d d             ! yw)messager
   Nr    r   .0xs     r   	<genexpr>z_api.<locals>.<genexpr>q   s!     G$Qs155B#78$s   %'   i  g        zRetry-Afterr      g      ?T)_retriedu1   Asana is rate-limiting us (429) — try again in <   z	 seconds.i  u3   Asana rejected the token (401) — check ASANA_PAT.  uG   that needs a paid Asana plan (402) — it's not on this free workspace.  : z'Asana says no permission for that (403).i  z$Asana couldn't find that item (404).zAsana error zrequest failedzcouldn't reach Asana: x   )"
startswith_BASEitemsurllibparse	urlencodejsondumpsencoderequestRequestupper
add_headerr   urlopenreaderror	HTTPErrorloadsr   join	Exceptionr   floatheaderstimesleepmax_apir   r!   URLErrorr    reason)r+   pathbodyparamsr.   raw_enveloper:   urlkvcleanr*   reqrrawedetailerrerrswaitwhypayloads                         r   rZ   rZ   U   ss    ??3TzGD6
C"(,,.H.$!QAZ4GA.H3$;CC6<<3I3I%3PPPC262B4::vtn%,,.D
..
 
 4
 
GCNN?gdfX$67NN8/0~'9:%I^^##C#9Q&&(C :J "%djjo"G;;vw''e I :9<<!!  X	**QVVX./C778$*DYYG$GGMF 		66S=DQYY]]=9>Q? 


3tS>*FD$%)+ +Ct9?#9./24 4 66S=RTWXX66S=!# # 66S=#)Bvh-rCFse1MsSS66S=CSII<xr&2L<L1MNPQPVPVWW<<   I1#ahh-2E1FGHHIs   E>E>!F 6FF F	F N*.M!1AH	M!		HM!HM!*(IM!I!M! I!!:M!N*!C M!! N*$N%%N*c                b   g }t        |      }|j                  dd       t        d      D ]~  }t        d| |d      xs i }|j	                  d      xs g }||z  }|j	                  d      xs i }t        |t               r|j	                  d	      nd
}	|	rt        |      |k\  r n|	|d	<    |d
| S )zJGET a collection, following `next_page.offset` (which lives OUTSIDE data).limitd   
   GETT)r_   r`   r*   	next_pageoffsetN)dict
setdefaultrangerZ   r   
isinstancelen)
r]   r_   capoutp_envrowsnxtru   s
             r   _pagedr      s    CVALL#2Y5$qt<Bwwv$"tggk"(b&0d&;"SS(  t9r   c                (    |  dt        |      d d  S )Nz	 failed: r8   )r    )toolrh   s     r   _failr      s    V9SVDS\N++r   c                    | r|syt        t              dk\  r,t        j                  t        t	        t                    d       t        |      t        | j                         j                         <   y)u   Cache a task we just created (see _task_gid). Bounded — these processes
    (the voice engine, the Railway service) run for days.Nr8   )rz   r   popnextiterr    r   lower)namegids     r   	_rememberr      sL     s
7|sDg'.$'HGDJJL !r   c                   | sg S t        | t        t        f      rt        |       }nt        |       j	                  d      }|D cg c]5  }t        |      j                         st        |      j                         7 c}S c c}w )u!   'a, b' | ['a','b'] → ['a','b'].,)ry   listtupler    splitr   )valrC   r6   s      r   _listr      sa    	#e}%S	Cs#$)<EqSV\\^CFLLNE<<<s   B$Bc                x    t        | t              r | j                         j                         dv S t	        |       S )N)1trueyesydonecomplete)ry   r    r   r   r   )rc   s    r   _truthyr      s.    !Swwy $QQQ7Nr   c                    t         j                  d      rt        t               S t        j                  j                  dd      j                         t        ddddd	      xs g } | st        d
      d}j                         rt        fd| D        d      }n@r>j                         t        fd| D        d      }|xs t        fd| D        d      }|xs | d   }t        |j                  d            t         d<   t        |j                  dd            t         d<   |j                  d      rdndt         d<   t        t               S )zA{'gid','name','is_org'} for the workspace we operate in (cached).r   ASANA_WORKSPACEr
   rs   /workspacesname,is_organizationrq   
opt_fieldsrp   r_   z%that Asana account has no workspaces.Nc              3  ^   K   | ]$  }t        |j                  d             k(  s!| & yw)r   Nr3   )r5   wwants     r   r7   z_ws_info.<locals>.<genexpr>   s&     D1#aeeEl*;t*CQs   "--c              3  z   K   | ]2  }t        |j                  d d            j                         k(  r| 4 ywr   r
   Nr    r   r   r5   r   lows     r   r7   z_ws_info.<locals>.<genexpr>   s8      ?1AEE&"-.446#= s   8;c              3  x   K   | ]1  }t        |j                  d d            j                         v r| 3 ywr   r   r   s     r   r7   z_ws_info.<locals>.<genexpr>   s9      G1 #s155+<'='C'C'E E    7:r   r   is_organizationr   is_org)r   r   rv   r   r   r   rZ   r   isdigitr   r   r    )spacespickr   r   s     @@r   _ws_infor      s6   }}UI::>>+R0668D%(>MO URT @AAD||~DDdK	jjl ? ?@DF Nt G GHLN6!9D488E?+IeDHHVR01If!%*;!<#"Ih	?r   c                     t               d   S )z,Gid of the workspace we operate in (cached).r   )r   r   r   r   
_workspacer      s    :er   c                    |j                         t        fd| D        d      }|xs t        fd| D        d      S )z;Exact (case-insensitive) name match, then a contains-match.c              3  |   K   | ]3  }t        |j                  d d            j                         k(  s0| 5 ywr   r   r5   ir   s     r   r7   z_match.<locals>.<genexpr>   s2     J5aCfb(9$:$@$@$Bc$I5s   1<<Nc              3  x   K   | ]1  }t        |j                  d d            j                         v r| 3 ywr   r   r   s     r   r7   z_match.<locals>.<genexpr>   s9      B5a#aeeFB&7"8">">"@@ 5r   )r   r   )rC   refhitr   s      @r   _matchr      sH    
))+C
J5JD
QC I$ B5 BCGI Ir   c                @   t        | xs d      j                         } | sy| j                         r| S t               ddd}|	|rdnd|d<   t	        d	|      }t        ||       }|s(|d
u r$t	        d	t               ddd      }t        ||       }|rt        |d         S dS )u;   Resolve a project name (or gid) → gid. None if not found.r
   Nr   rq   )	workspacer   rp   r   falsearchived	/projectsFr   )r    r   r   r   r   r   )r   r   r_   projsr   s        r   _project_gidr      s    
ciR.


 C
{{}
!+VcRF'/VWz;'E

C8u${*,39C%I JUC !3s5z?+t+r   c           	        t        | xs d      j                         } | sy| j                         r| S t        j	                  | j                               }|r|S t               }t        dd| dd| ddd	
      xs g }t        ||       }|s't        dd| dddddd	
      xs g }t        ||       }|xs	 |r|d   nd}|r&t        | t        |d                t        |d         S y)u>  Resolve a task name (or gid) → gid. None if not found.

    Three tiers, because Asana's typeahead TEXT index lags several minutes
    (verified against the live API): a task created 2 seconds ago is invisible to
    `typeahead?query=…`, which would break "create it, now comment on it".
      1. gids and tasks we created in this process (instant),
      2. typeahead by name (the normal path for anything older),
      3. typeahead with an EMPTY query = the workspace's most-RECENT tasks, which
         is not text-indexed and therefore does see brand-new tasks.
    r
   Nrs   /workspaces/
/typeaheadtaskr9   r   resource_typequerycountr   r   rq   r   r   )
r    r   r   r   r   r   r   rZ   r   r   )r   cachedwshitsr   freshs         r   	_task_gidr     s    ciR.


 C
{{}
[[%F	BbT4#>  %"$ 	 s
CUl2$j9#b3 C" # )&( 	 UC 

,T$q'tC
#s3u:'3u:r   c                    t        |xs d      j                         }|r| sy|j                         r|S t        dd|  dddd	      xs g }t	        ||      }|rt        |d
         S dS )uA   Resolve a section (board column) name → gid inside one project.r
   Nrs   
/projects/z	/sectionsr   rq   r   r   r   )r    r   r   rZ   r   )project_gidr   secsr   s       r   _section_gidr   &  sz    
ciR.


 Ck
{{}
K=	:&,s;= C@B 	
s
C!3s5z?+t+r   c                    t        | xs d      j                         } | sy | j                         r| S t        ddt	                dddd      xs g }t        ||       }|rt        |d	         S d S )
Nr
   rs   r   /teamsr   rq   r   r   r   )r    r   r   rZ   r   r   )r   teamsr   s      r   	_team_gidr   3  sz    
ciR.


 C
{{}
,z|nF;'-<> DAC 


C!3s5z?+t+r   c                x   t        | xs d      j                         } | sy | j                         r| S t        dt	                dddi      }t        ||       }|rt        |d         S |rPt        ddt	                dd| iddi      xs i }|j                  d      rt        |j                  d            S d S y )	Nr
   r   z/tagsr   r   r   POSTr   )r    r   r   r   r   r   rZ   r   )r   createtagsr   mades        r   _tag_gidr   ?  s    
ciR.


 C
{{}
Le4|V6LMD
s
C
3u:Fl:<.>SM<*@B HEG 	'+xxs488E?#@D@r   c                     t         j                  d      r	t         d   S t        ddddi      xs i } t        | j                  dd            t         d<   t        | j                  dd            t         d	<   t         d   S )
uK   My own user gid (cached) — needed to filter 'assigned to me' client-side.mers   z	/users/mer   r   r   r   r
   me_name)r   r   rZ   r    )r   s    r   _me_gidr   P  sm    }}T	e[,)?	@	FBB"&&+,IdOrvvfb12IiT?r   c           	     8   t        | xs d      j                         } | r| j                         dv ry| j                         sd| v r| S 	 t	        ddt                dd| d	d
d      xs g }t        ||       }|rt        |d         S 	 | S # t        $ r Y | S w xY w)u   A user reference Asana accepts anywhere a user is expected.

    Asana takes a gid, an EMAIL, or the literal 'me' interchangeably — so there
    is no email→gid lookup to build. Only a spoken NAME ('Sara') needs resolving.
    r
   r   myselfr   ahmedminer   @rs   r   r   userrr   z
name,emailr   r   r   )r    r   r   r   rZ   r   r   rT   )r   r   r   s      r   _userr   Z  s     ciR.


 C#))+!GG
{{}s

E\*,zB#cB&L( ) /,. 	 T3s5z?"  J  Js   
>B 	BBc                j    | j                  d      xs | j                  d      xs dd d }|rd| S dS )Ndue_ondue_atr
   rr   , due )r   )tds     r   _duer   u  s;    	h7AEE(O1r3B7AVA3<$"$r   c                    | j                  d      xs i }t        |t              r|j                  d      nd }|rd| dS dS )Nassigneer    []r
   )r   ry   rv   )r   ar   s      r   _whor   z  sB    	jRA&q$/155=TDRvQ<'R'r   c                    | j                  d      rdnd}d| j                  dd       t        |        t        |        | S )N	completed (done)r
   - r   
(untitled))r   r   r   )r   r   s     r   
_task_liner     sA    k*9Dfl+,T!WId1gYtfEEr   c                :    dj                  d | xs g D              S )N, c              3     K   | ]D  }t        |t              r2|j                  d       r!t        |xs i j                  d d             F ywr   )ry   rv   r   r    r4   s     r   r7   z_names.<locals>.<genexpr>  s>      ?i"1d+f !'rvr23is   A
A)rS   )seqs    r   _namesr    s%    99 ?ciRi ? ? ?r   c                    t               st        S 	 t        ddddi      xs g } | syt               }g }| D ]`  }|j	                  d      rdnd	}t        |j	                  d
            |k(  rdnd}|j                  d|j	                  d       d| |        b t        |        ddj                  |      z   S # t        $ r}t        d|      cY d }~S d }~ww xY w)Nrs   r   r   r   r   z%That Asana account has no workspaces.r   organizationzpersonal workspacer   z
 (default)r
   r   r       — z Asana workspace(s):

asana_workspaces)r   _UNCONFIGUREDrZ   r   r   r    appendrz   rS   rT   r   )r   defaultlinesr   kindtagrh   s          r   
workspacesr    s    <,e]*,BCE KHJ 	:,A%&UU+<%=>CWD"%aeeEl"3w">,BCLL2aeeFm_E$u=>  f+45		%8HHH ,'++,s#   B: BB: :	CCCCc           
        t               st        S 	 t        dt               t	        |      rdndt
        d      }t        | xs d      j                         j                         }|r;|D cg c]0  }|t        |j                  dd            j                         v s/|2 }}|s
|rd| dS d	S g }|D ]  }|j                  d
      xs i }t        |t              r|j                  d      nd }t        |j                  d      t              r$|j                  d      xs i j                  d      nd }|j                  d|j                  d       t        |      z   |rd| dndz   |rd| ndz           t	        |      rdnd}	t        |       d|	 ddj                  |      z   S c c}w # t         $ r}
t#        d|
      cY d }
~
S d }
~
ww xY w)Nr   r   r   )r   r   r   r
   r   zNo Asana projects matching r?   z(No projects in that Asana workspace yet.current_statustitleownerr   r   r   r  zarchived Asana project(s)zAsana project(s) :
r  asana_projects)r   r
  r   r   r   
_F_PROJECTr    r   r   r   ry   rv   r  r   rz   rS   rT   r   )r   r   r   qr}   r  str  r  labelrh   s              r   projectsr    s   <*{#")("3$%& ' ""$**, %M1c!%%2C.D.J.J.L)LQEM@A1%!< ECEA'(.BB'1"d';BFF7OE:Dg;&QUU7^)r..v6+/ LL2aeeFm_-Q7-2bqM</4eE7O"> ?  07x/@+# 	e*QugS)DIIe,<<< N   *%q))*sC   AF0 ,0F+F+!F0 -F0 /C;F0 +F0 0	G9G
G
Gc                   t               st        S t        | xs d      j                         } | sy	 t	               }d| i}|rt        |      |d<   |rt        |      j                         dd |d<   |rt        |      j                         |d<   |rt        |      nd}|r|sd	|d
S |rt        dd| d|ddi      xs i }nt|j                  d      rFt        dd|d    dddd      xs g }	dj                  d |	dd D              }
d|
rd|
 dz   S dz   S |d   |d<   t        dd|ddi      xs i }|rd| nd}d |j                  d|       | d!|j                  d       dS # t        $ r}t        d"|      cY d}~S d}~ww xY w)#zkCreate a project. In an ORGANIZATION a team is required (Asana rule);
    in a personal workspace it isn't.r
   z1asana_project_create failed: need a project name.r   notesNrr   r   privacy_settingzNo Asana team called uU    — say which team it belongs to (asana_api GET /workspaces/{gid}/teams lists them).r   z/teams/r   r   zname,permalink_urlr   r   rs   r   r   r   rq   r   r  c              3  P   K   | ]  }t        |j                  d                ywr   Nr3   )r5   r   s     r   r7   z!project_create.<locals>.<genexpr>  s      HZQUU6]!3Z   $&uf   That Asana workspace is an organization, so a new project must belong to a team — tell me which teamz (available: ).r?   r   z	 in team zCreated Asana project  (id asana_project_create)r   r
  r    r   r   r   rZ   r   rS   rT   r   )r   teamr  r   privacyr   r^   tidr|   r   havewhererh   s                r   project_creater+    s    <tzr?  "DB0Zd^JDM [..0"5DN&)'l&8&8&:D"#!%io4+D8 4M M NvY7+-ABD JGI  vvhUl2e9+V$D39C$HJ PMO yy HU3BZ HHG7;]4&3F GADF G !#5	Dv{D+-ABD JGI &))D6"r'(='@uSWWUZ^L\\^__ 0+Q//0s2   A5E+ (A.E+ E+ AE+ +	F
4F?F
F
c                @   t               st        S 	 t        | d      }|sd| dS i }|rt        |      j	                         |d<   |rt        |      |d<   |rt        |      j	                         dd |d<   |rt        |      |d	<   |t        |      |d
<   |syt        dd| |ddi      xs i }|j                  d|       }	d
|v r|d
   rdnd d|	dS d|	ddj                  |j                                dS # t        $ r}
t        d|
      cY d}
~
S d}
~
ww xY w)u   Rename / re-note / re-date / re-own a project, or ARCHIVE it
    (archive=True → PUT {archived:true}; reversible — this is NOT a delete).Nr   No Asana project matching r?   r   r  rr   r   r  r   uI   Nothing to change — give a name, notes, due_on, owner, or archive=true.PUTr   r   zname,archivedr   ArchivedzUn-archivedz the Asana project u.   . (Archive is reversible — nothing deleted.)zUpdated Asana project  (r  r#  asana_project_update)r   r
  r   r    r   r   r   rZ   r   rS   keysrT   r   )projectr   r  r   r  archivepidr^   r|   pnamerh   s              r   project_updater8    sZ    <07T2/{!<<t9??,DLJDM [..0"5DN!%LDM&w/D$5Jse,d'9; A>@ 	(%)*%5z=I J$i'UW X'y499TYY[3I2J"MM 0+Q//0s/   C> A3C> <C> %C> >	DDDDc           
     x   t               st        S 	 t        | d      }|sd| dS t        |      D cg c]  }t	        |       }}t        |      D cg c]  }t	        |       }}g }|r|rP|D ]*  }	t        dd||	t        |      j                         d       , |j                  dt        |       d	|        n6t        dd
| dd|i       |j                  ddj                  |              |r6t        dd
| dd|i       |j                  ddj                  |              t        dd
| dddd      xs g }
g }|
D ]m  }|j                  d      xs i }|j                  d      }|j                  d|j                  d      xs |j                  d      xs d |rd| dndz          o |r&dt        |       z   d z   d!j                  |      z   dz   nt        |       d"|  d#}||rd$d$j                  |      z   z   S dz   S c c}w c c}w # t        $ r}t        d%|      cY d}~S d}~ww xY w)&zList / add / remove the people on a project ("invite people to a project").
    add / remove take gids, EMAILS, or 'me' (comma-separated).Nr-  r.  r?   r   z/memberships)parentmemberaccess_levelzadded z as r   z/addMembersmembersr  z/removeMemberszremoved rs   z/project_membershipsz!user.name,user.email,access_levelrq   r   r   r   r<  r   r   emailr(   r1  )r
   zProject r>   r0   z member(s) on :r  asana_project_members)r   r
  r   r   r   rZ   r    r   r  rz   rS   r   rT   r   )r4  addremover<  r6  r6   addsremssaidmr   whorf   ulvlheadrh   s                    r   project_membersrL    sf    < 17T2/{!<<"'*-*Qa*-"'-0-Qa-0A$'1*-l*;*A*A*CEF  fSYKtL>BCVz#k:Y<MNfTYYt_$567:cU.9It;LMKK(499T?"345EZu,@A=KM N TQS 	Af#A%%'CJJAEE&MBQUU7^BsCD),Bse1+"6 7 
 NR
S\)D0499T?BSHH:^G9A6 	tdiin,<<<<5 .04  1,a001sF   H H HH H'F$H H 
H 	H9#H4.H94H9c                   t               st        S t        |xs d      j                         }|sy	 t	        | d      }|sd| dS t        |xs d      j                         j                         j                  dd	      }d
}||vrddj                  |       dS |||d}|rt        |      j                         |d<   t        dd|ddi       d|  d|j                  d	d       dS # t        $ r}t        d|      cY d}~S d}~ww xY w)zWPost a project status update (on_track / at_risk / off_track / on_hold /
    complete).r
   z2asana_project_status failed: need the status text.Nr-  r.  r?   on_trackr  r~   )rN  at_risk	off_trackon_holdr   zstatus_type must be one of: r  )r:  textstatus_typer  r   z/status_updatesr   r   zPosted a status update on r  asana_project_status)r   r
  r    r   r   r   replacerS   rZ   rT   r   )	r4  rR  rS  r  r6  r  okr^   rh   s	            r   project_statusrW  5  s    <tzr?  "DC07T2/{!<<*
+11399;CCCMHR<1$))B-BB #T"EJ,,.DMV&lG5LM+G9E"**S#:N9OqQQ 0+Q//0s+   C+ AC+ AC+ +	D
4D?D
D
c           	     
   t               st        S 	 t        |       }|sd| dS 	 t        dd| dt        dz   i      xs i }|j                  dd	       |j                  d
      rdndz   g}|j                  d      xs i j                  d      }|r|j                  d|        |j                  d      s|j                  d      r=|j                  d|j                  d      xs |j                  d      xs ddd         |j                  d      r|j                  d|d           t        |j                  d            }|r|j                  d|        |j                  d      xs g D ]l  }|j                  d      xs i j                  d      }|j                  d      xs i j                  d      }|sN|j                  d| |rd| dndz          n |j                  d      xs i j                  d      }	|	r|j                  d|	        t        |j                  d             }
|
r|j                  d!|
        t        |j                  d"            }|r|j                  d#|        t        |j                  d$            }|r|j                  d%|        |j                  d&      xs g D ];  }|j                  d'      }|s|j                  |j                  d       d(|        = |j                  d)      rVt        |d)         j                         j                  d*d+      }|j                  d,|dd- t        |      d-kD  rd.ndz   z          t        dd| d/d0d1d2      xs g }|r\|j                  d3t        |       d4       ||dd5 D cg c],  }d6|j                  d       |j                  d
      rd7ndz   . c}z  }t        dd| d8d9d:d2      xs g }|D cg c]  }|j                  d;      d<k(  s| }}|r|j                  d=t        |       d>       |d?d D ]  }|j                  d@      xs i j                  ddA      }t        |j                  dBd            dd5 }t        |j                  dCd            j                         j                  d*d+      ddD }|j                  d6| dE| dF|         d*j                  |      S # t
        $ r t        dd| dt        i      xs i }Y w xY wc c}w c c}w # t        $ r}t        dG|      cY d}~S d}~ww xY w)HzvEverything about ONE task: notes, assignee, due, section, tags, subtasks,
    custom fields and the last few comments.No Asana task matching r?   rs   /tasks/r   z,dependencies.namer   r   r   r   u	    — DONEr
   r   z
Assignee: r   r   zDue: N   start_onzStarts: r  z	Project: membershipssectionr4  z	Section: z (in r?  r:  zSubtask of: r   zTags: 	followerszFollowers: dependencieszWaiting on: custom_fieldsdisplay_valuer>   r  r  r  zNotes: i  u   …	/subtaskszname,completed2   r   z
Subtasks (z):rr   z  - r   /storiesz0text,created_at,created_by.name,resource_subtyperq   resource_subtypecomment_addedz
Comments (z
), latest:
created_bysomeone
created_atrR     r1  z): 
asana_task)r   r
  r   rZ   _F_TASK_FULLr   r   r  r  r    r   rU  rz   rS   rT   r   )r   r(  r   r  rH  projrG  secprparr   folldepscfr   r^   subssstoriescmtsbywhentxtrh   s                           r   r   r   S  sD    <@&o,THA66	XUgcUOl-AA5C D JGI  EE&,/0#$55#5K2? @uuZ &B++F3LL:cU+,55?aeeHoLL5x!PQUU8_5JCR4P QRS55LL8AjM?34aeeJ'(LL9TF+,%%&,",A55#)r..v6C%%	"(b--f5By.2E"Q-2NO	 -
 uuX$"))&1LL<u-.aeeFm$LL6$)aeeK()LL;tf-.aeeN+,LL<v./55)/R/B&&)Cv/r#78 0 55>qz?((*224=DLLd4CjSY_ERT&UVWEWSE3*R=9 : @=? 	LL:c$i[34#CRy*(! QUU6]O,QUU;=O	UWX(* *EuuH5L?  !  	 #S7aaee,>&??&R7SLL:c$i[
;<"#YeeL)/R44VYG155r23CR8!%%+,224<<T3GMtB4r$s3%89	 
 yyo  	XUgcUO\<4PQWUWA	XT*
 T  &\1%%&sn   T$ S/ E=T$ DT$ CT$ (1T"T$ ;TTCT$ /$TT$ TT$ $	U-T>8U>Uc           	        t               st        S 	 	 t        dt        t	        |xs d      d            }t        |      }d }| rt        | d       }|sd| dS |rI|syt        ||      }|s	d|d	|  dS t        dd
}|sd|d<   t        d| d||dz        }	d| d|  d}
nd|r(|t        dd}|sd|d<   t        d||dz        }	d|  }
n:t        |      r.dt               t        dd}|sd|d<   t        d||dz        }	d}
ny|	D cg c]"  }t        |j                  d            |k(  s!|$ }	}|rdnd}|	s	d| d|
 dS |	d | D cg c]  }t        |       }}t        |	      |kD  rdt        |	      |z
   dnd}t        |	       d| d|
 d d!j!                  |      z   |z   S # t
        $ r d}Y w xY wc c}w c c}w # t
        $ r}t#        d"|      cY d }~S d }~ww xY w)#N   rd  rq   r-  r.  r?   u>   Give a project too — a section only exists inside a project.No section called  in r   nowcompleted_since
/sections//tasks   )r{   ztask(s) in r1  r?  r4  r   rp   r   )r   r   r   rp   ztask(s) assigned to youz9Give a project name, or set mine=true for your own tasks.r   openzNo r     
…and  more.r
   r  r  asana_tasks)r   r
  rY   minr!   rT   r   r   r   _F_TASKr   r   r   r   r   rz   rS   r   )r4  r   r^  r   rp   	want_doner6  sidr_   r   r  r   stater  morerh   s                   r   tasksr    sT   <-'	3s5;B/56E I&	w6C3G;a@@WsG,C+G;d7)1EE$+c:F,1()Jse62F	JD!'"WIQ7E!$GcJF,1()(F	:D!'+ET]"&Z\$+c;F,1()(F	:D-ENI4a4k(:#;y#H4I(fqq))(,Ve51A58;D	E8I3t9u,-V4rd)AeWAeWC0499U3CCdJJQ  	E	D J 6  ']A&&'sx   #F- #G	 G	 G	 6BG	 	G	 "F?0F?4G	 G	 G AG	 -F<8G	 ;F<<G	 		G(G#G(#G(c           	        t               st        S t        | xs d      j                         } | sy	 d| i}|rt        |      |d<   |rt        |      j                         d d |d<   |rt	        |      |d<   d }|rt        |d       }|sd	|d
S |rt        |      nd }	|r|	sd|dS |	rIt        dd|	 d|ddi      xs i }
t        |
j                  d            }|rYt        dd| dd|i       nE|r|g|d<   nt               |d<   t        dd|ddi      xs i }
t        |
j                  d            }t        | |       |r3|r1t        ||      }|rt        dd| dd|i       nd| d| d| d|d	S |rd| nd}|rd | nd}|r|rd!| d"nd}|j                  d      rd#|d    nd}d$| | | | | d| d%	S # t        $ r}t        d&|      cY d }~S d }~ww xY w)'Nr
   z+asana_task_create failed: need a task name.r   r  rr   r   r   r-  r.  u    — task not created.rY  u    — subtask not created.r   rZ  rc  r   r   r   /addProjectr4  r  r   r  r  /addTaskr   zCreated r  r$  u$   ) — but there's no section called z, so it's in the default one.z under r1  r?  r   zCreated Asana task r#  asana_task_create)r   r
  r    r   r   r   r   rZ   r   r   r   r   rT   r   )r   r  r4  r   r   r^  r:  r^   r6  rr  r|   r(  r  r*  undercolr{  rh   s                     r   task_creater    s`   <tzr?  "D<,-d^JDM [..0"5DN$XDw6C3G;>TUU#)it#,VJ6OPPvY7+V46 <9; cggen%CVwse;7)S9IJ$'5Z $.L[!vx|V6LMSQSCcggen%C$ssG,CVz#h7&#G"4($wiuSE B**14QS T$+$wi &,'&""!(S7)1ob,0HHX,>X'(B$THUGC5vU3%rRR -(!,,-s2   AG G )CG :A	G 	G#GG#G#c                   t               st        S 	 t        |       }|sd| dS i }|rt        |      j	                         |d<   |rt        |      |d<   |rt        |      j	                         d d |d<   |rmt        |      j	                         d d |d<   d|vrJt        dd	| d
di      xs i }	|	j                  d      xs |	j                  d      xs dd d }
|
sy|
|d<   |rt        |      |d<   |t        |      |d<   |syt        dd	| |d
di      xs i }|j                  d      rJt        j                  t        |       j	                         j                         d        t        |d   |       d|j                  d|       ddj                  |j                                dS # t        $ r}t!        d|      cY d }~S d }~ww xY w)NrY  r?   r   r  rr   r   r\  rs   rZ  r   zdue_on,due_atr   r   r
   uS   Asana needs a due date before it will take a start date — give me due_on as well.r   r   uR   Nothing to update — give a name, notes, due_on, start_on, assignee, or complete.r/  zUpdated Asana task r1  r  r#  asana_task_update)r   r
  r   r    r   rZ   r   r   r   r   r   r   r   rS   r3  rT   r   )r   r   r  r   r\  r   r   r(  r^   curr)  r|   rh   s                r   task_updater    s   <%-o,THA66t9??,DLJDM [..0"5DN"8}224Sb9D t#5GC5/#/"AC IFH wwx(JSWWX->-D"cr,J:!%X$XD ' 1D-5GC5/4v8NOUSU88FKKD	)//148d6lC($SWWVT%:$=R		$))+@V?WWYZZ -(!,,-s0   F1 B:F1  'F1 B(F1 1	G:GGGc           
        t               st        S 	 t        |       }|sd| dS |s|s|syt        dd| ddi      xs i }|j	                  d	|       }|j	                  d
      xs g D cg c]  }|j	                  d      s| }}g }	d}
|rht        |d      }
|
sd|dS d|
i}|rt        |
|      }|r||d<   n	d|d| dS t        dd| d|       |	j                  d| |rd| ndz          nu|rs|sy|d   }t        t        |d         |      }|sd|d|j	                  d	       dS t        dd| dd|i       |	j                  d| d|j	                  d	              g }|rt        |d      }|sd|dS ||dg}n/|
r-|D cg c]"  }t        |j	                  d            |
k7  s!|$ }}|D ]!  }t        dd| d dt        |d         i       # |r*|	j                  d!d"j                  d# |D              z          | d$d%j                  |	      z   dz   S c c}w c c}w # t        $ r}t        d&|      cY d}~S d}~ww xY w)'zMove a task. A real move: adding it to a project REMOVES it from the ones it
    was in (Asana's addProject alone would leave it in both).rY  r?   u4   Say where to move it — a project and/or a section.rs   rZ  r   zname,projects.namer   r   r  r   Nr-  r.  u    — nothing moved.r4  r^  r  r  r   r  z	added to z / r
   z4That task isn't in a project, so it has no sections.r   r  r  r   z	moved to )r   r   z/removeProjectzremoved from r  c              3  ^   K   | ]%  }t        |j                  d       xs |d          ' yw)r   r   Nr3   r5   r}   s     r   r7   ztask_move.<locals>.<genexpr>f  s/      4C=A 58f8Q54R=As   +-r>   r0   asana_task_move)r   r
  r   rZ   r   r   r   r  r    rS   rT   r   )r   r4  r^  remove_fromr(  r  tnamer}   wasrF  r6  r^   r  hostdropridrh   s                    r   	task_mover  1  s    <3+o,THA66w{I5GC5/')=>@ FCE 	%77:.4"4F4Quq4Fw6C3G;>QRR#S)D"30&)DO/{$wiGZ[[73%{3T:KK)G9-G3wiQSTUMq6Ds4;/9C+G;d488F;K:LANN:cU(3fc]CKK)G9D&1A0BCD{T:C3K?!DD56D"?s!c!%%,&73&>AsD?A73%~6C%M8RS KK$)) 4C=A4C +C C D|diio-33Q GD @  +&**+sr   H1 H1 A H1 ,H'H'H1 $"H1 4H1 <6H1 3AH1 H1 "H,5H,9A-H1 '
H1 1	I:IIIc                    t               st        S 	 t        |       }|sd| dS t        dd| ddiddi	      xs i }d
|j	                  d|       dS # t
        $ r}t        d|      cY d }~S d }~ww xY w)NrY  r?   r/  rZ  r   Tr   r   r   zMarked z complete in Asana.asana_task_complete)r   r
  r   rZ   r   rT   r   )r   r(  r|   rh   s       r   task_completer  m  s    </o,THA665GC5/K+>'02 857 	.11DEE /*A../s"   A /A 	A4A/)A4/A4c                   t               st        S 	 t        |       }|sd| dS t        dd| ddi      xs i }|j	                  d|       }t        dd|        t        |       j                         j                         t        |      j                         j                         fD ]  }t        j                  |d	        d
|dS # t        $ r}t        d|      cY d	}~S d	}~ww xY w)uR   Delete a task — it goes to Asana's Deleted Items and is recoverable for 30 days.rY  r?   rs   rZ  r   r   r   DELETENzDeleted the Asana task z8. It's in Deleted Items and can be restored for 30 days.asana_task_delete)r   r
  r   rZ   r   r    r   r   r   r   rT   r   )r   r(  gotr  rb   rh   s         r   task_deleter  {  s    <-o,THA665GC5/<2HIOR%X'd)//#))+SZ-=-=-?-E-E-GHAKK4  I)% 3( ( 	) -(!,,-s#   C B&C 	C+C& C+&C+c                f   t               st        S 	 t        |       }|sd| dS g }|r/t        dd| dt	        |      i       |j                  d|        t        |      D cg c]  }t	        |       }	}|	r6t        dd| dd	|	i       |j                  d
dj                  |	              t        |      D cg c]  }t	        |       }
}|
r6t        dd| dd	|
i       |j                  ddj                  |
              t        |      D ]@  }t        |d      }|sd|dc S t        dd| dd|i       |j                  d|        B t        |      D ]7  }t        |      }|st        dd| dd|i       |j                  d|        9 t        dd| ddi      xs i }|j                  d      xs i j                  d      }|rd| ndg}t        |j                  d	            }|r|j                  d|        t        |j                  d            }|r|j                  d|        |rd d!j                  |      z   d"z   nd#}||j                  d|        d$z   d%j                  |      z   S c c}w c c}w # t        $ r}t        d&|      cY d'}~S d'}~ww xY w)(zLWho and what is on a task: assignee, followers, tags. No args = just report.rY  r?   r/  rZ  r   zassigned to r   z/addFollowersr_  zfollowing: +r  z/removeFollowerszun-followed: T)r   z&Couldn't find or create the Asana tag z/addTagr  ztagged z
/removeTagz	untagged rs   r   z+name,assignee.name,followers.name,tags.namer   r   z	assignee 
unassignedz
followers r   ztags u	   Done — r0   z. r
   r>   u    · asana_task_peopleN)r   r
  r   rZ   r   r  r   rS   r   r   r  rT   r   )r   r   add_followersremove_followersadd_tag
remove_tagr(  rF  r6   rD  rE  r   r   r   rH  bitsrs  r   rK  rh   s                       r   task_peopler    s    <(-o,THA66'#*eHo)FGKK,xj12"'"67"6Qa"6773%}5T7JKKK,tyy&789"'(8"9:"9Qa"9:73%'78;:MNKK-		$'89:'ND4-C?xqII73%w/%>KK'$() # *%D4.CVwse:6Eiv./	 &
 '#G1I J PMO 	
uuZ &B++F3%()C5!l;aeeK()KK*TF+,aeeFm$KK%v'9=diio-42vt,-R006;;t3DDD= 8 ;6  -(!,,-sQ   J A J %J7AJ >JAJ /AJ 4DJ 
J 	J0J+%J0+J0c           	     ~   t               st        S 	 t        |       }|sd| dS g }t        |      D ]V  }t	        dd| dd|iddi      xs i }t        |t        |j                  d	      xs d
             |j                  |       X t	        dd| dddd      xs g }|r$dt        |       d|  ddj                  |       dnd
}|s	|xs |  dS |dd D cg c]  }t        |       }	}|t        |       d|  dz   dj                  |	      z   S c c}w # t        $ r}
t        d|
      cY d}
~
S d}
~
ww xY w)zEList a task's subtasks, or create them (add = comma-separated names).rY  r?   r   rZ  rc  r   r   r   r   r
   rs   z#name,completed,due_on,assignee.namerq   r   zAdded z subtask(s) to r>   r  z.
z has no subtasks.N(   z subtask(s) on r  r  asana_subtasks)r   r
  r   r   rZ   r   r    r   r  rz   rS   r   rT   r   )r   rB  r(  r   nmsubrv  rK  rw  r  rh   s              r   subtasksr    s}   <*o,THA66*BvY7&"+V46 <9; b#cggen234KKO	 
 EWSE3?#=O P VSU 	  T?4&499T?:K3O 	5dV#455(,Sb	2	1A	2T?4&<<tyy?OOO 3 *%q))*s;   D B1D D D0'D D 	D<&D71D<7D<c                    t               st        S t        |xs d      j                         }|sy	 t	        |       }|sd| dS t        dd| dd|i       d	| dS # t        $ r}t        d
|      cY d }~S d }~ww xY w)Nr
   z,asana_comment failed: need something to say.rY  r?   r   rZ  re  rR  zCommented on Asana task asana_comment)r   r
  r    r   r   rZ   rT   r   )r   rR  r(  rh   s       r   commentr    s    <tzr?  "D=)o,THA66Vwse8,vtn=)$33 )_a(()s#   A A 	A='A82A=8A=c                   t               st        S 	 t        |       }|sd| dS 	 t        dt	        t        |xs d      d            }t        d| ddd	i      }|D cg c]  }|j                  d
      dk(  s| }}|sd|  dS g }|| d D ]  }|j                  d      xs i j                  dd      }t        |j                  dd            dd }t        |j                  dd            j                         j                  dd      }	|j                  d      rdnd}
|j                  d| d| |
 d|	dd          t        |       d|  }|t        |      t        |      kD  rdt        |       dndz  }|dz   dj                  |      z   S # t        $ r d}Y [w xY wc c}w # t        $ r}t        d|      cY d}~S d}~ww xY w) zPRead a task's comment thread (system events like 'X assigned this' are skipped).rY  r?   r~  rr   rd  rZ  re  r   z:text,created_at,created_by.name,resource_subtype,is_pinnedrf  rg  zNo comments on Nri  r   rj  rk  r
   rR  r  r  	is_pinnedz	 (pinned)r   r  r>   ,  z comment(s) on z	, latest r@  asana_comments)r   r
  r   rY   r  r!   rT   r   r   r    r   rU  r  rz   rS   r   )r   rp   r(  rx  rw  ry  r  rz  r{  r|  pinrK  rh   s                r   commentsr    s   <*o,THA66	3s5;B/45E 73%x0V3X Y"S7aaee,>&??&R7S$TF!,,ufgA%%%+00CBquu\2./4DaeeFB'(..088sCC!"{!3+CLL2bTD6#bTc<=  d)OD62SYU-C)CJ<q)Ld{TYYu---!  	E	 T  *%q))*sY   F- #F 	F- F(9F(=	F- DF- F%!F- $F%%F- -	G6GGGc           
     N   t        | xs d      j                         }t               }g }|rk|t        dd}	|du rd|	d<   t	        d|	      }|r|D 
cg c]>  }
|j                         t        |
j                  dd            j                         v s=|
@ }}
nt        d	d
| dd|dt        d      xs g }|r|D 
ch c]  }
t        |
j                  d             }}
t        t        j                               D ]H  \  }}|j                         |vs||v r	 t        d	d| dt        i      }|s8|j                  |       J |r|j                         j                         }|dv rKt               }|D 
cg c]5  }
t        |
j                  d      xs i j                  dd            |k(  r|
7 }}
n~|D 
cg c]s  }
|t        |
j                  d      xs i j                  dd            j                         v s1|t        |
j                  d      xs i j                  dd            k(  r|
u }}
|r*|D 
cg c]  }
|
j                  d      s|
d   |k  s|
! }}
|r*|D 
cg c]  }
|
j                  d      s|
d   |k\  s|
! }}
|-|D 
cg c]"  }
t        |
j                  d            |k(  s!|
$ }}
|sOd}|sCt        d	d
t                dd|ddd      xs g }|rddj                  d |D              z   dz   }d| d| S |dd D 
cg c]  }
t!        |
       }}
t#        |      dkD  rdt#        |      dz
   d nd}t#        |       d!| d"d#j                  |      z   |z   S c c}
w c c}
w # t        $ r Y w xY wc c}
w c c}
w c c}
w c c}
w c c}
w c c}
w )$z^Free-tier path: typeahead (name-match only) or a project listing, then
    filter client-side.r
   rq   r  Fr  r  r  r   rs   r   r   r   r   r   r   rZ  r   r   r   r   Nr   r4     z
Matching projects: r  c              3  P   K   | ]  }t        |j                  d                ywr!  r3   r  s     r   r7   z#_search_fallback.<locals>.<genexpr>J  s     $GASv%7r"  r?   Nothing in Asana matches    r  r   Asana task(s) matching z6 (free-tier search: matches names, not descriptions):
r  )r    r   r   r  r   r   r   rZ   r   r   rC   r   r  r   r   rS   r   rz   )r   r6  rH  
due_before	due_afterr  r  r   r   r_   r   seenr  r   r   r   r   extrar   r  r  s                        r   _search_fallbackr    s    	EKR A	BD
 #F(-F$%h'#St!qwwyCfb8I4J4P4P4R'RAtDSE\"Z8#a#!B# $ *') 	 /34t!Ce%tD40C779B&#+ '#)5w(?AE KK& 1 iik!889D# Nt!AEE*-388CDL tD N  $ Mt!c155#4#:"?"?"KLRRTTc155#4#:"?"?r"JKK tD M Q4a155?q{j7P4QP4a155?q{i7O4PI4a4k(:#;y#H4I,z|nJ G!*Q$Q& ' -*,  099$G$GGHJMN*5)1UG<<$("I.IqZ]IE.14TRYs4y2~&f-RD4yk1% ;E Eii!%& 'c T 5 " NM RPI /sg   >M/M/<!M4M99:N	:A8N:NNN&N=NN"N5NN"9	NNc                   t               st        S t        | xs d      j                         } | s	|s|s|s|sy	 |dn
t	        |      }d}|rt        |d      }|sd|dS |rt        |      nd}t        ddd	}	| r| |	d
<   |r||	d<   |r||	d<   |rt        |      j                         dd |	d<   |rt        |      j                         dd |	d<   |	|rdnd|	d<   	 t        ddt                d|	      xs g }
|
sd| dS |
dd D cg c]  }t        |       }}t        |
      dkD  rdt        |
      dz
   dnd}t        |
       d| ddj                  |      z   |z   S # t        $ rd}|j                  dv rPt        | |t        |xs d      j                         t        |xs d      dd t        |xs d      dd |      cY d}~S  d}~ww xY wc c}w # t         $ r}t#        d|      cY d}~S d}~ww xY w) u   Find tasks. Uses Asana's premium search when the plan has it, else falls back
    to typeahead. NOTE: the search index lags 10-60s, so a task created seconds ago
    may not show — read it with asana_task instead.r
   z;asana_search failed: give a phrase, a project, or a filter.Nr-  r.  r?   rq   modified_at)r   rp   sort_byrR  zprojects.anyzassignee.anyrr   zdue_on.beforezdue_on.afterr   r   r   rs   r   z/tasks/searchr   )r<   r=   r  r  r  r  r  r  r  asana_search)r   r
  r    r   r   r   r   r  rZ   r   r   r   r  r   rz   rS   rT   r   )r   r4  r   r  r  r   r  r6  rH  r_   r   rh   r   r  r  s                  r   searchr  S  sS   
 <""$E*YL'(%-D793E	w6C3G;a@@!)eHor&-#02"F6N%(F>"%(F>"&)*o&;&;&=cr&BF?#%(^%9%9%;CR%@F>" ,5&7F;		jl^=I%' -*,  .uiq99(,Sb	2	1A	258Y^3t9r>*&1t9+5eYcB))E"#%)* 	+  	vv#'s(+HN(;(A(A(C(+J,<"(=cr(B(+IO(<Sb(A9N N 	 3  (^Q''(sn   'G "A;G E' <G G GA	G '	G0AGG	G GGG 	G;%G60G;6G;c                *   t               st        S d}d}|r	 t        j                  |      }|r	 t        j                  |      }	 t        t        |       j                         t        |      ||      }t        j                  |d      }|dd t        |      dkD  rdz   S d	z   S # t        $ r}dt        |      dd  cY d}~S d}~ww xY w# t        $ r}dt        |      dd  cY d}~S d}~ww xY w# t        $ r}t        d
|      cY d}~S d}~ww xY w)zOCall ANY Asana REST endpoint. Returns raw JSON (for the model, not for speech).Nzbody_json isn't valid JSON: r@   zparams_json isn't valid JSON: F)ensure_asciii  u    …[truncated]r
   	asana_api)r   r
  rG   rR   rT   r    rZ   rL   rH   rz   r   )	r+   r]   	body_jsonparams_jsonr^   r_   rh   r|   rw  s	            r   rg   rg     s   <DF	A::i(D 	CZZ,F%3v;$$&D	4@JJs/$xA,FF2FF  	A1#a&#,@@	A
  	C3CF4CL>BB	C  %[!$$%s_   B% C AC3 !C3 %	C	.C>C	C		C0C+%C0+C03	D<DDD)returnr    )r  r   )NNr  FF)r+   r    r]   r    r^   dict | Noner_   r  r.   r!   r`   r   r:   r   )r  )r]   r    r_   rv   r{   r!   r  
list[dict])r   r    rh   rT   r  r    )r   r    r   r    r  None)r  z	list[str])r  rv   )rC   r  r   r    r  r  )F)r   r    r   zbool | Noner  
str | None)r   r    r  r  )r   r    r   r    r  r  )r   r    r   r   r  r  )r   r    r  r    )r   rv   r  r    )r
   F)r   r    r   r   r  r    )r
   r
   r
   r
   )r   r    r&  r    r  r    r   r    r'  r    r  r    )r
   r
   r
   r
   N)r4  r    r   r    r  r    r   r    r  r    r  r    )r
   r
   r
   )
r4  r    rB  r    rC  r    r<  r    r  r    )rN  r
   )
r4  r    rR  r    rS  r    r  r    r  r    )r   r    r  r    )r
   Tr
   Frd  )r4  r    r   r   r^  r    r   r   rp   r!   r  r    )r
   r
   r   r
   r
   r
   )r   r    r  r    r4  r    r   r    r   r    r^  r    r:  r    r  r    )r
   r
   r
   r
   r
   N)r   r    r   r    r  r    r   r    r\  r    r   r    r  r    )
r   r    r4  r    r^  r    r  r    r  r    )r
   r
   r
   r
   r
   )r   r    r   r    r  r    r  r    r  r    r  r    r  r    )r
   )r   r    rB  r    r  r    )r   r    rR  r    r  r    )rr   )r   r    rp   r!   r  r    )r   r    r6  r  rH  r    r  r    r  r    r  r    )r   r    r4  r    r   r    r  r    r  r    r  r    )r
   r
   )
r+   r    r]   r    r  r    r  r    r  r    )Ar%   
__future__r   rG   r   rW   urllib.errorrD   urllib.parseurllib.requestr   r   rstriprB   r   __annotations__r   r
  r  rn  r  r   r   RuntimeErrorr   rZ   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r+  r8  rL  rW  r   r  r  r  r  r  r  r  r  r  r  r  r  rg   r   r   r   <module>r     s  #H #  	    


{$CDKKCP	>   8 EG 
 Y
3  6:MR=(=(.1=(FJ=(=(@",-=2
I,&!H
,	,"6%
(F
?,&*< NP"$'0'0),'0T QS,0 0 058 0F @B(*&1"%&1/2&1R @J "00'*0<E&P @B021'1'*-1'7:1'h PTCE3-3-+.3-=@3-JM3-l KMAE)-)-.1)-JM)-X <>!#9+9+(+9+x/-& EG;="$.-"%.-58.-.-),.-b*<) *F@' #@'36@'F @B@D1(1(,/1(IL1(p%r   