+
    Uj;              	          R t ^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t^ RI
t^ RIHt ^ RIHt ^ RIHt ]! ]4      P#                  4       P$                  P$                  t]! ]P(                  P+                  R]! ]P$                  R,          4      4      4      t]P(                  P+                  RR	4      t]! ]P(                  P+                  R
]! ]P2                  ! 4       R,          R,          R,          R,          4      4      4      tRtR R ltR R ltR1R R lltR R ltR R lt R R lt!]"! R4      t#]"! R4      t$R R lt%R  R! lt&R" R# lt'R$ R% lt(R2R& R' llt)R( R) lt*R2R* R+ llt+R, R- lt,R. R/ lt-R0 t.R# )3u~  Storage HUD — a fast, lazy file browser over Supabase Storage.

The bucket 'jarvis-files' (on Ahmed's Supabase) is the WRITABLE shared file store
that any Jarvis / Claude on any device can read, save, and delete from. This
module is the engine side of the HUD's "pull up the storage" panel.

DESIGN RULE (the whole point): the LIST is METADATA-ONLY. `list()` / `show()`
fetch names + type + size + updated timestamp and NOTHING ELSE — no file bodies.
A file is downloaded ONLY when Ahmed actually opens it (`open_file` / `download`),
and even then it's cached to disk and re-fetched ONLY when the remote copy is
newer. This is deliberately unlike the memory graph, which was slow because it
bulk-loaded everything.

Ports the proven REST logic from jarvis-tools-service/storage.py (stdlib urllib,
no new deps). Endpoints under {SUPABASE_URL}/storage/v1/...:
  POST /object/list/{bucket}   → metadata list (name, updated_at, metadata.size…)
  GET  /object/{bucket}/{name} → the file body (only on open)
  POST /object/{bucket}/{name} → upload (x-upsert)
  DELETE /object/{bucket}/{name}

Creds come from the process env, else the Ultron repo's .env (the single source
of truth — SUPABASE_URL + SUPABASE_SECRET, the service_role key). MCP_STORAGE=0
disables the whole thing.

Engine → HUD event (voice.events.emit):
  storage {files:[{name,type,size,updated}]}   — open / refresh the panel
)annotationsN)datetime)Path)emit
ULTRON_ENVz.envSTORAGE_BUCKETzjarvis-filesJARVIS_STORAGE_CACHELibraryzApplication SupportJarvisstoragez.emptyFolderPlaceholderc                   V ^8  d   QhRR/# )   returnztuple[str, str] | None )formats   "C/Users/ahmed/devFolder/Ultron/claude-voice/voice/storage_control.py__annotate__r   ;   s        &      c                    \         P                  P                  RR4      p \         P                  P                  RR4      pV '       d	   V'       g   \        P	                  4       '       d    \        P                  4       P                  4        F  pVP                  4       pVP                  R4      '       g   RV9  d   K3  VP                  R4      w  r4pVP                  4       VP                  4       rSVR8X  d   V '       g   Tp Ky  VR8X  g   K  V'       d   K  TpK  	  V '       d	   V'       g   R# V P                  R4      V3#   \         d     L2i ; i)zG(base_url, service_key) from env, falling back to the Ultron repo .env.SUPABASE_URL SUPABASE_SECRET#=N/)osenvironget_ULTRON_ENVexists	read_text
splitlinesstrip
startswith	partition	Exceptionrstrip)urlkeylinek_vs         r   _credsr-   ;   s    
**..
,C
**..*B
/CC[//11	#--/::<zz|??3''3d?..-awwy!'')1&sC++CCC = C::c?C	  		s%   ,BE E E E EEc                   V ^8  d   QhRR/# )r   r   boolr   )r   s   "r   r   r   R   s          r   c                 h    \         P                  P                  RR4      R8X  d   R# \        4       RJ# )zBOn when the Supabase service creds are available and not disabled.MCP_STORAGE10FN)r   r   r   r-   r   r   r   enabledr4   R   s)    	zz~~mS)S084r   c               0    V ^8  d   QhRRRRRRRRRR	R
R/# )r   methodstrpathbodyzbytes | Noneheaderszdict | Nonetimeoutintr   bytesr   )r   s   "r   r   r   ]   sA     J J J3 Jl JJ.1J;@Jr   c           
        \        4       pV'       g   \        R 4      hVw  rg\        P                  P	                  V RV 2VV R7      pVP                  RRV 24       VP                  RV4       T;'       g    / P                  4        F  w  rVP                  W4       K  	   \        P                  P                  WR7      ;_uu_ 4       pVP                  4       uuRRR4       #   + '       g   i     R# ; i  \        P                  P                   dF   p\        RTP                   R	TP                  4       R
,          P                  RR4       24      hRp?ii ; i)z/storage not configured (no SUPABASE_URL/SECRET)z/storage/v1)datar6   AuthorizationzBearer apikey)r;   NzHTTP z: :N   Nutf-8ignore)r-   RuntimeErrorurllibrequestRequest
add_headeritemsurlopenreaderror	HTTPErrorcodedecode)r6   r8   r9   r:   r;   credsbaser(   reqr*   r,   res   &&&&&        r   _dorV   ]   s$   HELMMID
..
 
 D6TF!;$(. ! 0CNN?gcUO4NN8S!B%%'q (J^^##C#99Q668 :999<<!! JU166("ffhtn33GXFGI J 	JJs7   %(C< C(
C< (C9	3C< 9C< <E A EE c                   V ^8  d   QhRR/# r   r   z
list[dict]r   )r   s   "r   r   r   q   s     
@ 
@: 
@r   c                 @   ^ RI p \        RR\         2V P                  RRRRRR	R
RR//4      P	                  4       RR/4      pV P                  V4      pV Uu. uF:  pVP                  R
4      '       g   K  VP                  R
4      \        8w  g   K8  VNK<  	  up# u upi )uA   Raw Supabase list rows (METADATA ONLY — never the file bodies).NPOSTz/object/list/prefixr   limiti  sortBycolumnnameorderascContent-Typezapplication/json)jsonrV   BUCKETdumpsencodeloadsr   _PLACEHOLDER)rc   r?   rJ   is       r   	_raw_listrj   q   s    vvh/zz8R$#h%GI "FH 23	5D
 JJtE @u!uuV} !"v,!> Au @ @ @s   B6BBc               $    V ^8  d   QhRRRRRR/# )r   r_   r7   metadictr   r   )r   s   "r   r   r   ~   s!     6 6 64 6C 6r   c                P   \        V 4      P                  P                  R4      P                  4       pV'       d   V# T;'       g    / P	                  R4      ;'       g(    \
        P                  ! V 4      ^ ,          ;'       g    RpRV9   d   VP                  R4      R,          # R# )zA short type tag for the row icon: the extension if any, else the mime
subtype (e.g. 'pdf', 'png', 'plain'). Lowercased, no dot..mimetyper   r   )r   suffixlstriplowerr   	mimetypes
guess_typesplit)r_   rl   extmimes   &&  r   _kindrz   ~   s     t*


"
"3
'
-
-
/C

JJBJ'NN9+?+?+Ea+HNNBD"%+4::c?2525r   c                    V ^8  d   QhRRRR/# )r   isor7   r   floatr   )r   s   "r   r   r      s        r   c                    V '       g   R#  \         P                  ! V P                  RR4      4      P                  4       #   \         d     R# i ; i)zjParse an ISO-8601 timestamp (with 'Z' / fractional seconds) to epoch
seconds. 0.0 on anything unparseable.g        Zz+00:00)r   fromisoformatreplace	timestampr%   )r|   s   &r   _epochr      sF     %%ckk#x&@AKKMM s   3A   AAzCabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-() 0123456789abcdefc                    V ^8  d   QhRRRR/# r   r_   r7   r   r   )r   s   "r   r   r      s     2 23 23 2r   c                    \        V 4      P                  4       P                  R 4      p RP                  R V P	                  R4       4       4      # )r   r   c              3  l   "   T F*  p\        V4      \        9   d   \        V4      MR VR 2x  K,  	  R# 5i)r+   02xN)chr_KEEP).0bs   & r   	<genexpr>_enc_key.<locals>.<genexpr>   s2      20q !Vu_3q6AaW+=0s   24rC   )r7   r"   rs   joinrf   r_   s   &r   _enc_keyr      sD    t9??##C(D77 2 KK02 2 2r   c                    V ^8  d   QhRRRR/# )r   r(   r7   r   r   )r   s   "r   r   r      s     
( 
(3 
(3 
(r   c                   \        4       p^ \        V 4      r2W#8  d   W,          R8X  du   V^,           V8:  dg   W^,           ,          \        9   dO   W^,           ,          \        9   d7   VP                  \	        W^,           V^,            ^4      4       V^,          pK  VP                  \        W,          4      ^,          4       V^,          pK  VP                  RR4      # )    r+   rC   r   )	bytearraylen_HEXappendr<   ordrP   )r(   r   ri   ns   &   r   	_dec_namer      s    Ac#hq
%6S=QUaZC!H,<qSTAQHHSqS1r*+FAHHS[4'(FA88GY''r   c                   V ^8  d   QhRR/# rX   r   )r   s   "r   r   r      s      j r   c                    . p \        4        F  pVP                  R4      ;'       g    / pVP                  R4      pVf   VP                  R4      p\        VR,          4      pT P                  RTR\	        WB4      R\        V\        \        34      '       d   \        V4      M^ RVP                  R4      ;'       g    VP                  R4      ;'       g    R	/4       K  	  V # )
z`Metadata-only listing: [{name, type, size, updated}] sorted by name.
NEVER fetches file content.metadatasizecontentLengthr_   typeupdated
updated_atlastModifiedr   )rj   r   r   r   rz   
isinstancer<   r}   )outri   rl   r   displays        r   listr      s     C[uuZ &&Bxx<88O,DAfI&

GE'(D3,!?!?CIQquu\*LLdhh~.FLL"	
 	  Jr   c                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r      s      # # r   c                   \        V 4      p\        4        Fj  pVP                  R4      V8X  g   K  VP                  R4      ;'       g    / pVP                  R4      ;'       g    VP                  R4      ;'       g    Ru # 	  R# )z<The remote updated_at for one file (from the metadata list).r_   r   r   r   r   )r   rj   r   )r_   r(   ri   rl   s   &   r   _remote_updatedr      sj    
4.C[55=C55$**D55&HH$((>*BHHbH  r   c               $    V ^8  d   QhRRRRRR/# )r   r_   r7   r   
str | Noner   r   )r   s   "r   r   r      s!      3  s r   c                   \        V 4      P                  4       P                  R4      p V '       g   \        R4      h\        V ,          pVe   TM
\        V 4      p\        V4      pVP                  4       '       d:   V'       d2   VP                  4       P                  V^,
          8  d   \        V4      # \        RR\         R\        P                  P                  \        V 4      RR7       24      pVP                   P#                  RRR7       VP%                  V4       V'       d    \&        P(                  ! W$V34       \        V4      #   \*         d     Li ; i)zDownload ONE file to the local cache and return its path. Re-downloads
only when the remote copy is newer than the cached one (compared via the
remote updated_at vs the cached file's mtime). This is the ONLY path that
ever pulls bytes.r   zdownload: need a file nameGET/object/safeT)parentsexist_ok)r7   r"   rs   
ValueErrorCACHEr   r   is_filestatst_mtimerV   rd   rF   parsequoter   parentmkdirwrite_bytesr   utimeOSError)r_   r   dest
remote_iso	remote_tsr?   s   &&    r   downloadr      s   
 t9??##C(D5664<D#/_T5JJz"I||~~)		(<(<	A(M4yu&,,*<*<Xd^RU*<*V)WXYDKKdT2T	HHTy12 t9  		s   )E EEc                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r      s      C C r   c                v   \        V 4      p \        P                  R8X  d   \        P                  ! RV.4       V# \
        P                  R8X  d   \
        P                  ! V4       V# \        P                  ! RV.4        V#   \         d*   p\        R\        T4      R,           24        Rp?T# Rp?ii ; i)zoDownload (cached) then open the file with the OS default app so Ahmed can
view/edit it. Returns the local path.darwinopenntzxdg-openz  [storage] open failed: Nx   NN)r   sysplatform
subprocessPopenr   r_   	startfiler%   printr7   )r_   r8   rU   s   &  r   	open_filer      s     D>D:<<8#fd^, K WW_LL
 K j$/0 K  :)#a&,899K:s"   -B +B )B B8B33B8c               $    V ^8  d   QhRRRRRR/# )r   
local_pathr7   r_   r   r   r   )r   s   "r   r   r      s!      s *  r   c                   \         P                  P                  \        V 4      P	                  4       4      pV'       d&   \         P                  P                  V4      '       g   \        RV: 24      hV'       d)   \        V4      P	                  4       P                  R4      MR;'       g     \         P                  P                  V4      p\        P                  ! V4      ^ ,          ;'       g    Rp\        VR4      ;_uu_ 4       pVP                  4       pRRR4       \        RR\         R\        P                   P#                  \%        V4      RR	7       2XR
VRR/4       V#   + '       g   i     LV; i)zrUpload a LOCAL file into the bucket (upsert). `name` defaults to the
file's own basename. Returns the stored name.zno file at r   r   zapplication/octet-streamrbNrZ   r   r   rb   zx-upserttrue)r   r8   
expanduserr7   r"   isfileFileNotFoundErrorrs   basenameru   rv   r   rL   rV   rd   rF   r   r   r   )r   r_   lpry   fr9   s   &&    r   uploadr      s    
		C
O113	4BRWW^^B''+bV 455-1CIOO$$S)rRRbgg>N>Nr>RD%a(FF,FD	b$1vvx 
(6(!FLL$6$6x~C$6$P#QRTX	z624K	 
s   E&&E6	c                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r     s        r   c                X   \        V 4      P                  4       P                  R4      p V '       g   \        R4      h\	        RR\
         R\        P                  P                  \        V 4      RR7       24        \        V ,          P                  RR7       V #   \         d     T # i ; i)z8Remove a file from the bucket. Returns the deleted name.r   zdelete: need a file nameDELETEr   r   T)
missing_ok)r7   r"   rs   r   rV   rd   rF   r   r   r   r   unlinkr   r   s   &r   deleter     s    t9??##C(D344HVHAfll&8&8$c&8&R%STU	. K  Ks   ;B B)(B)c                   V ^8  d   QhRR/# rX   r   )r   s   "r   r   r     s      j r   c                      \        4       p \        RV R7       V #   \         d8   p\        R\	        T4      R,           24       \        R. R7       . u Rp?# Rp?ii ; i)u   List (metadata only) and push a `storage` event so the HUD opens/refreshes
the file panel. Returns the same list for the caller. Never raises — a viewer
refresh must not crash the assistant.r   )filesz"  [storage] panel refresh failed: r   N)r   r   r%   r   r7   )r   rU   s     r   showr     sT    Ye$ 23q6$<.ABYb!	s    A,AAAc                 ,  aa ^ RI o^ RIHp Hp R R loV ! RR/ 4      R VV3R ll4       pV ! R	R
R\        /4      R VV3R ll4       pV ! RRR\        R\        /4      R VV3R ll4       pV ! RRR\        /4      R VV3R ll4       pV! RRW#WE.R7      # )r   N)toolcreate_sdk_mcp_serverc                    V ^8  d   QhRRRR/# )r   msgr7   r   rm   r   )r   s   "r   r   "build_server.<locals>.__annotate__-  s     < <3 <4 <r   c                    R RRRV /./# )contentr   textr   )r   s   &r   _textbuild_server.<locals>._text-  s    VVVS9:;;r   storage_showuB  Pull up Ahmed's file storage on the HUD and list what's there. Use for 'pull up the storage', 'show my files', 'open storage', 'what files do I have'. Opens a fast panel (names + types only — nothing is downloaded) and returns the list. He can then open/delete files there, or you can with storage_open / storage_delete.c                    V ^8  d   QhRRRR/# r   argsrm   r   r   )r   s   "r   r   r   6  s     A A A$ Ar   c                  <"    SP                  \        4      G R j  xL
 pV'       g	   S! R4      # V Uu. uF:  pRVR,           2VP                  R4      '       d   RVR,           R2MR,           NK<  	  ppS! \        V4       R2R	P	                  V4      ,           4      #  Lu upi   \
         d&   pS! R
\        T4      R,           24      u R p?# R p?ii ; i5i)NzStorage is empty.z- r_   r   z  (z bytes)r   z# file(s) in storage (panel is up):

zstorage_show failed: N   N)	to_threadr   r   r   r   r%   r7   )r   r   r   linesrU   r   asyncios   &    r   r   "build_server.<locals>.storage_show0  s     
	A!++D11E011 $%# !F)%45EE&MM#ai[0rK K#  % CJ<'KL99U+, - - 2%
  	A0Q?@@	Asj   CB) B"B) B) CB) &B$B$6+B) !C"B) $B) )C4CCCCCstorage_openu   Open ONE stored file by name — downloads it (cached; only re-fetched if it changed) and opens it in its default app so Ahmed can view or edit it. `name` is the exact file name from storage_show.r_   c                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r   H  s     A A A$ Ar   c                >  <"   \        V P                  R R4      4      P                  4       pV'       g	   S! R4      #  SP                  \        V4      G Rj  xL
  S! RV R24      #  L  \
         d&   pS! R\        T4      R,           24      u Rp?# Rp?ii ; i5i)r_   r   z&storage_open failed: need a file name.NzOpened ro   zstorage_open failed: r   )r7   r   r"   r   r   r%   r   r_   rU   r   r   s   &  r   r  "build_server.<locals>.storage_openC  s      488FB'(..0ABB	A##It44474&*++ 5 	A0Q?@@	AsF   :BA* A(A* 'B(A* *B5BBBBBstorage_uploadzUpload a LOCAL file (by absolute path on this Mac) into Ahmed's storage so it syncs everywhere. `local_path` = the path; `name` = optional name to store it as (defaults to the file's own name). Refreshes the HUD panel.r   c                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r   W  s     
C 
C4 
CD 
Cr   c           
       <"   \        V P                  R R4      4      P                  4       pV'       g	   S! R4      #  SP                  \        T\        V P                  RR4      4      P                  4       ;'       g    R4      G Rj  xL
 pSP                  \
        4      G Rj  xL
  S! RV R24      #  L- L  \         d&   pS! R\        T4      R,           24      u Rp?# Rp?ii ; i5i)	r   r   z)storage_upload failed: need a local_path.r_   Nz	Uploaded z to storage.zstorage_upload failed: r   )r7   r   r"   r   r   r   r%   )r   r   r_   rU   r   r   s   &   r   r  $build_server.<locals>.storage_uploadR  s      ,+,224DEE	C **C 45;;=EEG GD##D)))9TF,788G) 	C23q6$<.ABB	Cs_   :C.A B; ?
B; 	B7
B; &B9'B; 6C.7B; 9B; ;C+C& C+!C.&C++C.storage_deletezXDelete a file from Ahmed's storage by name (from storage_show). Refreshes the HUD panel.c                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r   f  s     	C 	C4 	CD 	Cr   c                |  <"   \        V P                  R R4      4      P                  4       pV'       g	   S! R4      #  SP                  \        V4      G Rj  xL
  SP                  \
        4      G Rj  xL
  S! RV R24      #  L- L  \         d&   pS! R\        T4      R,           24      u Rp?# Rp?ii ; i5i)r_   r   z(storage_delete failed: need a file name.NzDeleted z from storage.zstorage_delete failed: r   )r7   r   r"   r   r   r   r%   r  s   &  r   r
  $build_server.<locals>.storage_deletec  s      488FB'(..0CDD	C##FD111##D)))8D6899 2) 	C23q6$<.ABB	CsX   :B<B	 BB	 4B5B	 B<B	 B	 	B9B4.B9/B<4B99B<r   z1.0.0)r_   versiontools)r   claude_agent_sdkr   r   r7   )r   r   r   r  r  r
  r   r   s         @@r   build_serverr  (  s    << 
.; =?@A A@A 
.F 3-	
A A
A 

% (4S&#&F	H

C 
CH

C 

%'-sm5	C 	C5	C !>J r   )NN   )N)/__doc__
__future__r   ru   r   r   r   urllib.errorrF   urllib.parseurllib.requestr   pathlibr   voice.eventsr   __file__resolver   _PROJECTr   r   r7   r   rd   homer   rh   r-   r4   rV   rj   rz   r   	frozensetr   r   r   r   r   r   r   r   r   r   r   r  r   r   r   <module>r     sF  6 #  	  
      >!!#**112::>>,"%hoo&>"?A B	(.	9 	RZZ^^		i"77(BYNOQ 	R ) . J(
@6  	I	K#$2
("&0 $Lr   