# v2 git bundle
db71f2928831867a7ebfed3063cb04791a92318c refs/heads/bundle
db71f2928831867a7ebfed3063cb04791a92318c HEAD

PACK      7x>tree 96ea07962b9f8e5ae7c158b61e48ca87eae76c7b
parent 281329a1afad5d87ef17215963272e4c0a814787
author MythEclipse <asepharyanalm001@gmail.com> 1779062492 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1779062492 +0700

feat: add in-memory rate limiter

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
[cxBtree 97422055f2943fe49f550c3429e829885216f7ed
parent f9c0bc076893eb040fdc59e95a9cbd7c08df5da8
author MythEclipse <asepharyanalm001@gmail.com> 1779062446 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1779062446 +0700

feat: add Drizzle ORM database layer

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
eexHtree b96e8a268e19ef296429991b1ab4c70f424ed5c2
parent 77032538e8bffb263f793c974ead979ca771ccdf
author MythEclipse <asepharyanalm001@gmail.com> 1779062335 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1779062335 +0700

feat: add environment variables validation

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
"hx<tree 17dbff3e388f7c293abd61d88d72696d099169f5
parent 25c82bcf0b70907221eb56fb34a6631603c02388
author MythEclipse <asepharyanalm001@gmail.com> 1779062013 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1779062013 +0700

feat: add Winston logger setup

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
0bxGtree ab5243c311cad0d5e631c4d9e27357f09d491d01
parent f423e1f25df3d165e3635c6992010fc84e6585b6
author MythEclipse <asepharyanalm001@gmail.com> 1779061866 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1779061866 +0700

chore: add dependencies and configuration

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
g@xtree 67be35fa7bfe978a8a536d17f5da9e981a86ce70
parent 3fb3dc3d40c8351efd488bf094049b42df93f831
author MythEclipse <asepharyanalm001@gmail.com> 1779061733 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1779061733 +0700

docs: add telegram uploader backend implementation plan
Ztxtree a2975f17297ac440fabe924e3efc6c05cb73c6bb
parent cf55f6dee8872e65c63e2772754dc2ae03c0e3f9
author MythEclipse <asepharyanalm001@gmail.com> 1779061362 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1779061362 +0700

docs: add telegram uploader backend design spec
D!WPxtree 8d70b092d2c44596b3bb7f248945874a29f1c862
author MythEclipse <asepharyanalm001@gmail.com> 1779060842 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1779060842 +0700

Initialize TeleUploader project with Bun, including configuration files and basic setup
)Wx340031QK+KH-Iexd s)Qh>c'doUY_ʠ^swoMSSrE/U⪗P#{s̽f)j^YS(jPkڸ~go,TQRi^N~r6';Uyz XłM@!%?]w]^l܏*5"3/%B[NJȻ}_-㿸 19;1=U/8?aQf*b}E۸} UWW\/B*_(KkOU/SqPqQ2k0VgbT[?"[Z\`kPߝgw+:89?/-3M=k;VlYq l QƵxMN0F.nlSII3U6T%Ʒ]h>|vRL>Ff0g?PdEC%Ulon1њ⤋х,I.B:4ʩ!	Oo衛yYLn5=Jc0q$vjboG8;2N͢*(BeMRC%}=XyYxmP=k0+t~-\tXJpl%UIV2NzOӗ.ጒP"aqaGR,0@4aӴ0Š$*goWK*f&r\9xX}8k6vE&GYbnFjmC	WdaϹT~GwN
ck[62,ĉdԦ!3=!Lzy0kwrrsHKwOoxmVmo6_qS)l	žu]]q%:f"
I6=GJ$@"~]Ob6%1ME2V9-}UL3J6/dȗT`	JSbl6ر{aGCM#v,s'k73ĕiv	G
9- TWz3T9)&ӲiĘګ~\9ЂJoŒ*˴_{	ZicUZ#*EzQ'GT~pGFG1H4u5]lV*LzYM	IES3Zc!y(P鄶|Ga3cȥR8ʓ{n{e>mZ>,8ٙF{zVho|gϷ&97Q+F(|~L
38,y_ЍAp3hS^'mv&\"~CSГcBi-2KϴժSτCwDQ(+]&tZȪ3~ayxU_[6v7F(uxld+T6+ib튵I}ކŞ"_82Ϲ(ù1<&<~m=}A?WbM*#&W}('O$9:ii"5<{1JUa.?owۛo{|^s?jp6
ekӈ`YP%r(
UؕzNV7Պ,B<=}|8lj!D aJh4-K.5`p`ι/J?(YǨ7Y+Yi6vp߯ȳpDʏKb~9"Ƌ'@X˰TyS.mۓp-c9ܧv'0!ak?HV;~s]3fJ?|2XMxf*㬐< [%spwy?EK$WY8p 6<cgT7X?0pv	0ma	=Yt/މjt?`vlW @s2Wl+b@3_71&fz]T=zx=rFv<ec493Z̊[$  β*ySRJm/O9Q٭TelӧO[7?g^g^E;qr^"fNG!{8a{u0g]GɥǏ7gÓꌾYt{I]#XfM+/ޕD1¬ěF>LDƛ.3?ub#eY|I~ʰeNzٙv6e^%0{Itìqg-nt%0dhn4?~9])x{&/8w[J]ٜK'EleM5ɾ;=}[m.JY~s,J+m@3qRentbK)ѶL~C_-C;+L&Џ n :ߞYC+K2ȜЋipI[D-KcD7.K"N`H"̱0B'|#kM?Nh|9{leɒhFL4ލS2Fa;I&r]x[9v^| ĳBq$.JC/𪪹+']/4wE Tm)pL6hG$	؇dvA$̀¢X8b*]Ox(8hX{$$Zf^ʉ"o{IOiLҮ^D
_5޽],\?)id:k tH`incOΒ:$j@$&3Q&Ke$Ҡ	DkDT	%h\طD@X֓h ~ߵ1,Z98SB2ҟQPN{G{Et0-Zݲw=Zj>f}6"/F-N}&-Qxϖ	
2JAf^MzfI{BIA¡*dC>зBgmΫT[mEXm/up5]	<^I#4{0P\|pnC";/b"`thp~vr:LoVbzDǞ	CIUOg7<uTSYmz0^y_ٽp#t 
=JڿH$l+ 5VBMQcJEƫo9)z!ۓ7Ãv_^CN<".;ɭ:6 9_10Y .Oҏ46@̡铝$\
dۓ'^Q!>÷o?wIOGo<?>!Qust4]'ZQ90@Άhw)(<`hN2쇽`bgC=eg؂k0¼V,4WCQ_*YuN\[ږkq|Ԑ5s#轣wj.NNǰdoнF! m[b|zg1KZVir)
(4hZ(upK\?_52A8Ktckf `1DWWHdldmA;cF7wm@eh1	+q-г~!JӼcxjg#΀"ҥ7ْB{0mǍz >sF`>gto
r,&\/㢧p5.[18~ƤEdO[)5ltWcqF<ã'A1!iя.[)赏YG%9
5dEԀHC`)hcwwy01_x!0ƹL.ʈ?cb.Ð>**W0K>V1@osU
BҌ&2t%F+̀KqrXP4@DLfqݜ_~kEhSjulk:Xnؐ[U8AB
q
"wDL")~56lW\q-t2\X}ԽS:S?5Qdv@ɦo8g"PX;z/I$>*^k[UZJS5G ,K5{ߕ"@I]c'C-
<JAoN$BPL QBZQq~C'@.5bßͶpʚjxkPp٥6hCcNex(3', p؃(D0Y<K}˚٢C
}ްŃM4( >󤀔 RʤZ<]`ԫFû AP[)e\qGɭGP:1ʧbIv
jC FUڤJ<e6<Wdx}4rcUMֺ@*	lՋ*/{yr=bа(E3C5eK+50]RT'`,c(6YpG-ytKC~dƖ!,=.n6	}Ā{r47.ZA*a7[X摠SeЛ>h$4qDפX6S}5[qnIrq)DSL6n]su4tϒ^"g_*
's	"oTV4FYRÍͮʤҀx(*hH#	cH"VsP%4QHl۶!Ė/t9Ia3;m	dʀt.M]n{#^@;tn+S:Re((;6WFlȌ(+d,spzgk-"֌s .)QMhL04vYg
:ቷ]aI(jSGVw-P{jُ:,ҷ6"0gP5'hؠ5pA/x,Ns[2{&
*sjr}1@2\.&'WtSU&!aq0<J 
N+Lt$=tc'M\0rj%RI{btH5Rqt]', te-2ĒD*e#vBl>%#i"}]#JܹTjkX^t(P˟ V9s@VʷUZL{GCj]R9vM
f,ZP۽,N	t(8aq
P8y}pDE12r'u۵v0Jhꭧi 4Uv󙴛'(.|?̉\טPF7Uqb/4i; i#x	EUzmks0NЮv0<a>>3`ϝ;JcyK̈kƃp_ah쫆"W%.Лkq[l)joMM+Jxu?Xg/G"[krQv!I 9QGw3<6'r$q> 2	م"Dy}E0p+!w@(Q.bHO0,M"Ա8{b%72V~Y?;^s!9X,-`!rݻeXPg٘*\*M2gЇݲːt[}ES ||=<`$ n4]Mfimy<"۽#]/ڠ)g׫jJyce9!ߺ^eE)NSKqA)K;cJ0Vl}xig!8jZYOfKE501 k<496уJh z	ii2pIrTu4͖1b*Mwe(\4x
k*@)I K؟.eφPMUPPLIl1h#Mgm,=TqIe/Q~߼ E>hLx	^ O32AenX#{Vg/"Ih
b=M~|EU)Xv$C,3ײLkYV,3+YVB,3NJlJ5hyY',fVUvtx]QJUegnj7КX薑<XVG"ҡ0O}j+[<]Z,Xz:89;:]}}[,Jf]~Ҫ;.hsJЦކSfϗe[e;+DdO}-`;F~/>ތAq0D^{ c#tӤw؈\vͩkrIxs=:-P)>)6dBHWdl,]~Rl:g8oځ~M0<?g_6BNUǷӨ+:pS>v%jE @?X\+eJ9fi`%?k!D eG:*qĜTN* 3HQc4cKPЯd؋B'NϸF?O5(
˽~;f[=d8&{g=ޖq+|㖅`ǊyX㐢GV1kz)CR9}@#@&VI&~VUvc'״U]0|WO_߯ȮOprɭ^ίs}?[oPYވZHc`NO-NJbcdML!?s[-wU̘*#oýv1jR'	&P̼<\PfޑMD5 .J6E5F(l*6bi\OyuP7[ZC		>rMD7pM_%E\*>rAo;2z-vh2F{qk
kTNVĳ PWƑʙL~϶,JTɅUAie⬏6AP%~5P+qOY̭M5%'gfLn;IܾZ^\xI16 K5TkPI^Y96䔕qI{reLA
}wORac+W3etи6>wi}	!lln5ؽrVUd͖GܤX'#C1p򾰜sf|mvO-aªBhR´J<ku+u)ƃI2Ὦ?jkVYĉJ5>e+$a>y_g,ގ8c]>,/GW݋rWcz!\%xg=LI9gh=÷ͬmȵr	QK;~-תO\P:iG$h3ec6W;)0<lFPiH8 ZaH	Y%eh!Ʒ%Lo"
FW"[XϢ1⍖]O ndz#K6Yci>= 1?1<f>F!&gVިy6zIb>c|=)y*	u	uA,wI~
JNM25%_eH9Դ6=<؇%[uhm#+5# az%Qb+moÌZ6uom,P'=܁;W"ZF<UYۧ%Fw*hzeiqp0V
+~M=жk7dV~~rWC̨%R?<<jMb64ƥ\#~&es~WkҦWPT{X]gw^j\([t(Fp^o;rpWFwrl+ctd{ғh/+eZgaݕ*M%RQYpʶkj@*Ff}HrԹ92BI2.`Y/=7F&ۊzWqsu5lͯoQk7: G|
Fz qO6_sPj'^ʛ/<Q~/.:<Q{`o3m}
7aMEfcWc1/+w?T	2M,X6of}zރ!_W]("rK""{C,bAnEđMBQ_><^8onH 88崍Vy^C/*x/U9[}ל|"8]S<"^¹g,ӵ;\^w|B^?`ٴ!^-|bc%`9ЖU ube[,l$V-/$ei+Ud+mޛznޱɚJZ
wl//l?G6R͛@"򀤶!Y<SEB%s2⒚xI}>3?>%>8ߊsJ_x8j
ͣ'[(FjzSrzssk$(#;HLOI=l__gpf\ZjC2qX|+o75ejzDm,x
= =J[| q\L?,]~˧ssBM=芟Gt{R}%68<XI%mWQ˜x]8o(7߱A~CIi F(wU7(6M-5$SVvDnQW~_dL"s(L,AyQqm~1@2/5ɿ_|JCsUc|'Ő>0wFi#dL=Tf#eFW)xER:йpa:EPt3f>YlUǈhUx-`W p|wtgVߊАQ7#OY!Wsx|W瀇.U*<C΂nzZZZO
7~WZ'}vFFn|c6[G_VD*8Y+R!d*NE 2HL6rUR&wc3ԑ(Fu5-x{ށx*s4	w{^{kU_kUuz%{qWĻp>9LЃX*ċi?x	9>Odg_n[B[zK.fSup?>MLMgoE[U! G~鈌>*-ĩe߃
Gwy7%ߪɶr@b5UH'{zc_BwZ=:DB+/X/o_ְ4Oz;
y`@|<WY
B/%2IWϺd#_t b/iꉃX?~qOW+1 ~897-APa2~9ɄuSBc[T~HQss	aR~%{/O)5l-wk>(d}ܶ'۫NNM9c_ t5бɍ㵟 ]Ozܼ{nx +ɔ=bH_!k#$B:ԈJІwj;,<goM#1{E$ܵ$rkTQ%m9_U'ޕ]b5U ennE}˽Lؓ`X}	)[6{2gDN:GPLTS=QAk(coIͮ;zbĳy	QȩDCE;o񒔾eoZ6n4:dS/tŋTnWwPt3ov2QYɵO4ijsl\=čƩzV=2GE]ers}׿oV{84£H>)O	VyRӘ}ژ:o)?"I1%Ger0FA^n6oҰ1ت$^gZI;.LS^ꋧŵq[=l#ioCK[u%q-xƉ7ԇ&'獵X S;*cq vwz:_uVQGJLGH=Wz
O1ywt4r9Ǐa=ALY]4g=e|)՘xٵxݡzbHL z<HVP

.ȿƌO"Uzl$tG>	s)a9Ǐ(lZx #nxͽٲ̒.x_O<Lfں%(@"I'1zsIKkLp/É?~zLp6mR_P1nKIR.%קs.¾J7RUXa'o~Xj6wZ$B?~ys( ?[IXTr'_7[m5\}@5	?;WMgcT-dn=i=F??]䓺[ˬlZK_O4ߗa xLgTnSz&_gM7wnfWSx DHd>:)&f% ?[z1$nT\ݖB.%_8|KGψ.x]X^>jC[CYg} {(x+̱o=q1`׸pZÆ>x\6!BZ`˃I^n95%W.rPwRQTH'+,٬vKo?c~VY6qVG%e{S%81R|+©z[OA/=܄?.ɞeu==炍<`[GWUټ(e7"j5*%͐/]E Crnlw_<C#\ ].NN7gwIz!<[w]tvy(~t!~6?w:W!&
V]*Wgd<4|;cs4Ivz~or˲{ϼn2*)'߈|Dt!x(85y9b*yk+|X:g7=g|emu-͌JO4#L~ }B~߭(V:+ȭ݅Wd1ͻ3t
vѠEȭȺQ_*o@+.u9No%07r!(*<#igjtlw
Ҫ9L  "	z_.FxK8K0$s8G`x?y1ˏxtHܯVð4$<:4m029zP&8lܪKnsq	mGPh><#!7C02p{O_."Bџֽ&*+ܾLk-~)8>N&ʿV"v4"pCΓD*rN*;f؉6kǽT5e}PfmS.:δ,C؄)WUay&T(_B9U{\@]YaKʍ^̲18n0| n4n5Ҏ]QPnbr[~;oV6桵NiB'm1p·%T2Imft-8.'>̒@<|b3&5ɔ ygq\觠<V!7IY>j9|3ԂbNǊiұSBxśƍ,Ho..#+0\|͚H=%rܬy~9WJ2X1|v[L"p<coQ@Rj]m6xHhA[
Z3ŴPnS^哥'jU7jNOfeYD\],EYWXbwMgj{'37yZq̋gBf1ӍPK-p 0b8O&fkVnNx+"
eoS^a@\G95Lhtdu?#blw)0v;㛊x?.pf=n\3zeL;d!]IXxĢޖ-<s4FGsmw~TIZ|s,$#ɪGħȄ\nٜ\`eUsA!_gbi9ey\Dk	N:PZt	<V|S+olZU?2s%'bWd ءaobYJZ#Z$;e/Yn:TNʻ	 n'þv6܀vb0ssntl,h!Aiq`~TFeQ>ۚ<jgShYc\o"3nǺn`t}ɦ@-Y((X`.Ck-gN;i)&[i^٬MaBS{>]fgxӑw9y,1vK&QQOODީzc\@WU!y&T$0	KJ֔n-݊SL#s|Y7IVF'\Ɍt E@iX"muzLT|ʭ\6d[ßuEz4$I[>Ӏj?ި+5|MBV#gMD)LQebH6W{49tطeͩyA*^1`L ?5u-ϕAbҧ贰rfwz.2/h^?HXЁ3vM~kT^iL@ѭF)pi Ȫ=dnP-{d+ZJZ1/RE	MG0I>&!0jRV=]Z-JMt";ի[bǥHF#CRv'u(ޠQ4x0޹|ZyN%IF'cg9rIbfb_߯pǻE
ķC$8*qy};z-.[r3zƩucð'?B
YQIrRb먬;*lC6ViiWu0ky(>zmɯw
:z8U^%B^cÅ&gL܇(2&zP\ 1 |ٟ߷e7M8n= ~>KdA>H-0XP.Q"Dz~o;xj{0WԞ̧mBQg/0%.2AZ#,]Ͳct@}މf]^lL.ʿ>D_~nњmG&#
_ݓ!D$
 n
i
ɖLIG]Z(n<5`Hu0 3A3zWknn;4dY}z%ۇLBX'cph6i!fV٪s>;
h!Bcմ׋7[{>#+L&w<SA6wrH!^;hh5R5njdiڜqh`j2X˙vJo_!z}~4]8v?aay)xEt-1RNycs.>o%ARYnؔzݶg3rnet㱿q>o?.
kS.<Wm|!cذl$<)Y
NDčxBn{TvvH΄e@ JUtrLtO/T?^0L~ݻ1?SgvyZJg8$И\8.{ÙjOy~}OǪ< 8槈iAucdF^h/ |erbE??'Y))816<vi[һ.#:2K:m6z%P@	,yCXV>Z/% 틤0wtMRe/t?2=yY2Lis<awXn]Rgu:0j}bIێKNDV;o"}]l~YtMe4ajq[ٖ\9:zVfx'SY^A G4I '<r(x6ٶ}R|7\o*TO8.#,[N:bqyt#=2~KT^1+ipZ$7, j`,0`	 Lc\\̬|bmEG_O	I=Wofa;&P^ކNylIG+:PCF;'<cj~լ< k;)n1OM9|?j$aDd:ibimYs.(K)T]|/0kmǤ2flA]T}/zj1BI2*FQ2=])}5S/6e"'rN0ٶ3/dIlqՔWǾ-	wg"%!o8#H'\oA|AUUa(Ѥqx\7fkQFL:ʜpTrXPTf!`PM2^W9<ȭ]KK|CBbk_]vϙ*Oڋ.2UǚkH|YGGjWA1pG#aE.^ndࡒ1"/Τ]pI#:`dS uɯW~?x7',\>k& ![oc|h.:'Ǆ^	]1/ {*rymV,i}-C51i4:c(k4ףdKTD X:ł ؎`^OJ{Se	'VALbAvS&6Uy*'C%t^8	F<!?Qh\C]ֹaYuF0SCMO>r*vѡ3	K.INYGϯt:_)Yt/9'=`Ɵܬ7)z7zs>,|bO$^[	8`-Ei%/K("C#=jֳT^lj]ܶB]K"+U_띟U;֑{Z|%s0ln3t/6HK=8mM}rU痟}p&r/n5Y_3WxO6GRe!؆ea۾+pC0rWg˟0}*	te~Oс-®,_/Z ${ڧEXu-)mɅM+}vnw"i_BV5?)~蟗8x3)oeWY>cU^)LFK7WԆO7O>&@_3:?в꒲p3"GjLa?UNCRwZ^)zTWtH5揮<)[oykDEnvt 1S%(TszFK}j\Tq#%J9PO/$XWN柜/Ͽ(K2u6'!6*o-S䐵9]M.V7͐Qrg>כ>H3vƏInN?$O>imR_	şDOdP}B0os~>!zIλIVn>%ow\Bj\;o|KzxzFMxiG-NL5u(Y)殕)[N)͐ haWPRKk&jq/}c~ `'FҞ57 6K3"03[Ѫ^
6G;XaC?vz|?+7"XIL1
^f[ʻ}zd57s|jAvb6,db-+'X|]>?"HĊm`8Q-{W3ʃv-l2pA=1xc7QJOeB߮2ܯ2KhGyqt)p|U޺bP_,fS<g84th!wP	/4R,>an>}Up⃫٢9QU퇡 gV_D<c8Tpoe+4m*`ꢎZ@=o&"`E$%:\-o"8w]`wDgε1eaVkUꊚ䫹:3զ&v	Xtbl3g:T4Xpa[ՐHڇ`#Gi.113-
 m;pRɃq\㐂3tٟ;uY셲+jͷpNިXPZ^WfOOMv,ܦ2ً`-82-r><e#0|5Ze2?kP,#,o4>u$'JVY>?_Y|z_yP^c|'oB1O^DP>L>kVNv^1ΌKz1WO3YWY<pQ6}/r)̓"8G:0onC=ns֬jT	v9i7{൛*ڇ❒&գcx_OԦ`46vlͽg*;U|k#~rk9/rbhh}9Rj
vvF]mx&G^'?q?Oo?߅΀j3RLsE9LUH<vcN{LY"`~~
4rnv!?n7D?&GsDϡQkWʫYX !j`Exh mE		~Xb+Y7-x-C)g#W.y_^7uğb w8k.`&ɧ\wE?:ݭ}u;F1I7khd]n=,?9BlFr%Ѳ
=LwG# 9bO/ypm\TXhHw4sT~Bho Dƌ,毦i__<}U{Q<[IUˎ)/_v<ԼTnl7kxʼ~ᗟnbR,741mj42&:[%|PR=uZX"F\#Z ˟Sע;3kΘO%2Z6W'mxQ[pS냈m X$qӝ5W7_w~V7Z7v8ŀ˳^{Hh@*

7W.U.!éJm%0PcK@&-u>e[F#_x{x~=sC}H+2NOjbl@i#lE9RF^vyH(ƜQo&/[k/?O!)o4vuF]tlHVs!ic%G,+Lm(i'(dKPcoI3at/[5|3x}_q98G8ˆ8/TG>8p87LɣSbrY/ǘf7eIhan	2?vmg~y|G#zU(iG^:oGGwu`®.,l{'ڤYbjlbf<mu"1,TRD2ch:YD >@܍ _Of^j,g=`l։[A[VY7~795WKm"(}mK }d'?
__~=~5΢u@C#</h-~#FuUhBnB }S|tɷ۝wVN.$V.?*%ѮrpXi8CN}UXbd͚8N]lklkln;=u}SɻAV.I)	0CJZdÓ g^iiCrB]a};D~_<rY۳ImYl$r0)P=o5( TXdLE-{vr`6SdRcmyߗ~5v>ztb[ڀ̀~{Xp1h("#lf\a;6"5rzAϙ:5NIMK+ M/kϗMe~&8Ax7U1?.5l~v`[$Ls@Y|+!ƮK_0f^CؾJ:tJ'`1hXlHd64&SD|QCdBuGdyE.4 	Ę
6ȱI@hx<e=D̦BZH~ǝ#W,<ngRXjxT8.;) ީ8ȮzΫp{+̫mc#t[Β$x)AޓEIk{#-^XaN{(;ű]O'0%h/0ޣz}C*=-ЦL)_zZ:Fp{aqvK^YݜO[謡_OBD\drRc<
;<SלQzS8K<ɇĪz3ѝAc9L"l['[[sZLB5Z.e*OwNzbt4:En|t4C21b%RO]C?hx01e'",4ÎW#wCa^|5}ޜNøV͗8?o?;wZӼʨ?t%$AÕ10X!(;G}ie|\9e $K%ZoCt⫷AtOӢQ56>!$>IJ#ݝ/:mwoS}8h6nR<f{sG׼|ЛL7lI-)M5VS<IRD·eSi.w#N,SGEZMq$&Klnf7E׫>;+NuI\ܲ"2maݲZm1ݐp ):xG$xV3VO@W Wv__??ay89jts˳~\tqP'	~.>x !ŷ?OnTskѷGYf3VNQR)}_*	'Zz%xP[ifX ٮeFn9j] ˩Xfz߯/z}b$I0n;U:^5˩ۨ\ilhZw]O\Gܽ7m_"3Qil[F[Y9giD!6 Ii4>KYȕUC/j_n>;'slJ4C.x(+kдL ޥJ0ٌ	maW2YP`#8O_鈐qq71 1JmM>PY\#5m6)aZm(Ǆܮ]&s8[X0rjq^k}輸__Mpf+{
Bbů뮞+vӀZ2b䅻\Fn]nafY9
!+V1\
hfvIq\8Bci=E/Hu	֡K1];F-ݸ3+l\|_zI=g?|u,*Йt9Ie3Pz~t-LSVF#Ov/${_[g71;nx]~jgl<ŸeV9]ዢƔ8ŁLj$clцHnS	s焂AD97vuKVޒ*cz`^Ŀ_"u9iyLK}s>]P~3_z,_i?gJ|ڷz9?_'Ez,sN6% Eѯx3wdf`Qd|g89ci(#[n"S%kLïx#+^~/'R9hs2h6"v\7!6 8k,!$틂czb9eg-hgs.M\9s^<~a2Y:XpB٢mE],-fOͷc/Ò$dN]ckjdI̙-wZn B[ܘSO)}=of6YD$3F?;}M'_'{#я3F?z1UGDo2F?"nASK|Or]G$I&ooa=a`5;ŋ±	%<vt5vt`ZfLKNŚw(h^ܿTxVSɱa\}W7_>38O/[?$mKVn?7	$'L~RӼOq6eDy+ronSogߧDʷ+͜mX;Ц9oגlG" @E
2TVv	oiM6, 3}TÜ	/7Ϧ?Nd09I☖Hm$Q1^	F"`j?uh9Ȱx1߁׀Y]ДR\Vq{{L5ۭn$Kq	Fi;09s5at>E}qY1Kz6Ju#|	T6c,Dd9s<N%D&0g]р$#` w|@v,F 8")Yyav:YK[x_w~{o=JiNdY:RApV~acj,"E/xC9Q]=|<;9y\'ǑgQO[SCbRcic*Zfwy2%XsvT[~¶|x6;Ʊ0VV̠ct׼}Z]L60qVV `{Wt<̿gy7ʵ6@vv=wqEMj# g]@402^{vn"g=~ :{=>^ER7-2]l1!GB4It]6=%
ggst3F<0od_0N0l&6tKsYMUG$L1zZ|֝oo|wh~	CZoa/ՒX)ļ{# a IoXF.ɪ)&
;n\o-^/=YԴ٨+kvX; XnlI#
TkqA'Ԇ<:!<Z(-#g=|EjawCwF(B:V5ίo  $ĝP	RՆ+:Ojf*k%:@v-no|7צu)iMP
JQ.u)Nl晁3K.I[3[˼ȟF];*PX1n(&1?J!i]^m"?yGnN
PYAz<f@7UI}jnDP۴hN]8 bN2>\2OҎ]!O:Jל{Tnt`gζ4K4wӻYuRyҮWm/vA/Op7;`npt>F]~㾻xlgC\kN`E[yN+вB	QhzGKI),u\ RMF{NL6AoGxm)@фF m0[Qždc&?9Ӆ@*Qfqo7WͧtnVC/1|\;lrs,Lyސg
rGcP!8rBwJΘo.%èP8q]i48ryܙY*q'eT׎NjO:[0y~bK'iPi,fU37g2ΘptLa%5Gі䥼CNawP~z0X38꾑ǭeOBܳ(=74g]-iM0\F~%LYZXC ܂7	oA߸Dz)Y[Lث3lVk$N"x0p+`3-Uԛ#L66^p9qG"r./CCC\ϖ%>.E$1M7H!uQƫFjPYߐпpwqHςUkQWA;޴ CDE'Ro<kW*L:n	Xy8P&3d_ݲ-T	؉&Jqɋ#aҚg\_VdFT
$3chbe6"0$oJ,TZ_k/ܼ1G!¡C'
!B'X
v"]a٪jr7bQ
d.k{wo~ҭ, +Hb*s#hocz{;#ytl&ZdEq pĭ\LGa\yw"ْ2]z} h>F%XA5gKZW<dX²'A"eY(1}d_-TҢr& ]F9ji-.(I$·ZQh*j44\.*/r4Hז;sU|9ݨEbtJW7cgOBqŲNxk?`h.L9|qOHHMCp h.kߋ+>fGffMGbŐLEfw/5{߻~]fcPLA6 aάƫ/ˍs=\(YAj,sK=;]tdC8/9)<J|y e]E2r0gEMjz+D,m[pԈ<?AYks1Kke1Ab0<lBV'E`\'捜>OCr؇sl)l7	^.iE-c/.$yeHp&h$pGM+3(SGTVsԡ4M9Ld݈dLJ,>}iт8 먇=[bݛ>}0`:+$ZC1  Kz9W2hXb^Kxp-S4<g47f\<lzm*[rn	LE$n9_#fkrp:udPv35yӗ2:.if3ZNlsp }6s72c9R:Rz$ҕr@Wj6D.bk@t+8?X8"0P5˰)5WH5 AꭍMXNLޑyLJ/1jk8ʭw@}RP3Uގ*r߁b엛QǄ>Pc
#{"aH@h_^-TTguw^ZӂOɤPϫ _C.~+VJTĠTL6BZqJv#Π,L"8/	r.1>TK=]{YN΍vxٯS,3/K/};p
NtU;N݂0f^i/"J;@Wl9f}=$E7 z
6߂a10Ae(F/I0P@HlZD`[!{P0PȕXZ ]&Dy&$,$g_(0 =JYo IP5IցEtĦ|u,?;>Wpޥ;2Sיy#28J.5}(-JRӔDJOVqHoY,`Ǧx& 40000 superpowers 4inҴ~YN ʠx@ 40000 plans ^p@mU+Q40000 specs I*9 йtBx340031Q02025054-IIM/J--OLI-MJLNK-IMaq>>4)vUJ 84x340031Q02025054-IIM/J--OLI-MJLNKMI-LMa⾤S;I{Y_% JH۳xrHb*/;o6.p{`Xh6ڪ{{{HwJik[/TDb$"YO=S98`ãׇ~`^ORfI՝ߞeqX6ص2JɇA5l4jO26ؘJ%	}'Ž]kfA*U|l˗iv[a)^.xcf<_H*4~O׬{ݯ7X"&q@W%ZKǱ,ZR =\p}L\T^8afM[Wknx&Q17H	@hPd44Tc=ێɔ*%(r$72d|tKֿ)ŗLPjFU@sirÖZmJs=!BNŹ:-j.́!OL|ko^4HĂP<@Zh-KE$,o>CLz|jf]%CAEe9NBKހ>+	 ۀ[Zu'CZ<%V2-N_G`|H%DNȯ)ْQ%Lpb5aZ7b	
̏rz0{miD,4=/<?A4PҪF$BK""v|Z(h%Ys1oB[o ϟ490dp=Lu-ȖueN*m(	)Ss9!g3M~W̓"~PjgzWԝii*Y?MF\=g8Oyh(¥?e`NERMZ"up҂<+RYCĽ3MK<n;JZ}^Qd7d7Vx7}y+@k'Wz[T *jjIjbEN6)_Dcs%~wil9TcUw+`8Ӡek ~Ug7GS6q
r *r^X R9_ӽ eEb{lFmSlŰ|򳸕B9(C=_Akн.lq~38ÿ97{/`RVwԊWȏOQUJ^y<fE_b]ϟ2m cEEqU*WՒ&:XՌ:4D\|NVԔ!5.H-d%GzvJI	}	%
]ayf+SQ"r8PM8h8 d@U/K&yYk=eQUaNg%j&D	TZVQF:jlqJY9LǝvZ8g򏺋ntsO7BkEщ@	l.(=O*MQr(sw_nk*:fقǭvS'~Ŵ~@9)ޥx7}|{gy'+t<J{cb363@NB]y,蠅]lφTP7,@DAna@ ƚj_E|^Kb4k .P=r"R@ASPUAC%9VeHU1'8A$eDx\ݪwŬ'W82\6>^6#fvŔ*kF+K)?"hluBGKxp&zxٹ9T\e:_q\Klm흍Ox,U"ћLG?Fq8~]>Bw֝3^NY¼^{Ydxtn̽?"(;OۭB2xzzz~<M%7]GpJ7l6_OС>ͮ&orte6aDL'@Чyˮhyuvzp`H܉teq\D"[j}B5Q֩VnXS=dU678LXK]Lfe`nTSPk۬Q@$Ks6u	"g[[6U]xO+*a}hC54nE<հn8ZJ
O2 0
Z,@E} WZ 36t:v	;g8l!S1sW u칣Z\lB 	2DܯQ,R8{Lі.^xdحwV	ocq_ȋKO\";0PvRZ1G
jp@Tv
3b!LTDQn	O$KS뒔}wt@!脿ꄿg\w[xz^u&YpHn_kd`Ǐȧ8"7x8iu-8w"Lt ) =T |y6}!̇ߢ3Bw]JtPv?.`(k_mC|K+L> |v`OW+_e |4`m˓}V*-!EI%*R1$%	\<(WSfR+)mp22eiTF,ǆ[x!r	iLQd慀H\F>Ed؅.dKHrǯ<'E6|KVI 	T$`ڢo&=z2ɚnMJUUJ#EdR&v
lper"~gKsr<I06XaG$й kcExHDI0#D[[|Vl2	f $kn9)UeW.s\ޕ&x console.log("Hello via Bun!");	#xmQMO1+GD$17GI>Jl*n?v!;fޛ顃P&l2+BdׁفL@{	`+ôkՆbPkڀI~u\!rHX2>g݅V<_+?m
G#t1kp	
K]󷹸\/&q:]<|lA2/:6	?dmf2-6a=\.e%H|e:bC\'+MK|2.սLIͤuS&ڝEMOv}}A}W]0Hc,To/xOO0|9BE{BFָO(KM<5y	FGÛ)t`2Dg$AOG"JRf TTb#bm g8PǣB*lCWwj;[*2}Wvl|URueWJ4ܬw;l\ltVjeu&N|(Y1L;l</΀^O;:]?A]E<ÿ;KSf|QtfpQ!6ߺ%x31 $5eJ=}nslWXLLR|_]g䆙L]GLKK2sxTo9h:}ZI%xI 100644 index.js A0q+I,WXVe100644 schema.js ">chF}$མӿxoimport { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import logger from '../utils/logger.js';
import { files } from './schema.js';

const client = postgres(process.env.DATABASE_URL, {
  max: 10,
  idle_timeout: 20,
  connect_timeout: 10
});

export const db = drizzle(client, { schema: { files } });
export { files };
export default db;}t6xfimport { pgTable, text, bigint, timestamp, uuid } from 'drizzle-orm/pg-core';

export const files = pgTable('files', {
  id: uuid('id').primaryKey().defaultRandom(),
  publicId: text('public_id').unique().notNull(),
  telegramFileId: text('telegram_file_id').notNull(),
  telegramFileUniqueId: text('telegram_file_unique_id').notNull(),
  storageChatId: bigint('storage_chat_id', { mode: 'number' }).notNull(),
  storageMessageId: bigint('storage_message_id', { mode: 'number' }).notNull(),
  fileName: text('file_name').notNull(),
  mimeType: text('mime_type').notNull(),
  sizeBytes: bigint('size_bytes', { mode: 'number' }).notNull(),
  fileType: text('file_type').notNull(),
  uploaderId: bigint('uploader_id', { mode: 'number' }).notNull(),
  createdAt: timestamp('created_at').defaultNow().notNull(),
  updatedAt: timestamp('updated_at').defaultNow().notNull()
});!'dJx[import logger from './utils/logger.js';

const requiredEnv = {
  BOT_TOKEN: process.env.BOT_TOKEN,
  STORAGE_CHANNEL_ID: process.env.STORAGE_CHANNEL_ID,
  BASE_URL: process.env.BASE_URL,
  DATABASE_URL: process.env.DATABASE_URL,
  PORT: process.env.PORT
};

const missing = Object.entries(requiredEnv)
  .filter(([_, value]) => value === undefined || value === '')
  .map(([key]) => key);

if (missing.length > 0) {
  logger.error('Missing required environment variables:', missing);
  throw new Error(`Missing environment variables: ${missing.join(', ')}`);
}

export const config = {
  botToken: process.env.BOT_TOKEN,
  storageChatId: parseInt(process.env.STORAGE_CHANNEL_ID, 10),
  baseUrl: process.env.BASE_URL,
  databaseUrl: process.env.DATABASE_URL,
  port: parseInt(process.env.PORT, 10) || 3000,
  nodeEnv: process.env.NODE_ENV || 'development',
  logLevel: process.env.LOG_LEVEL || 'info',
  rateLimitWindowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 60000,
  rateLimitMaxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS, 10) || 30
};

logger.info('Environment variables loaded', { config: { ...config, botToken: config.botToken?.substring(0, 10) + '...' } });
xM 100644 logger.js gT`x]ɲU:SE100644 rateLimit.js 
ŵ_}gM41xSMO0WSZiv)H*I7d 6$Tg#`dqԂ$V`Ǖʡ8O=0FSAH%u9Zg>>@hې1!PRQ-挀[A]^N9r>
gO6/8567ѭ
D@}gH>NWcw: Hc<hL_k6m0X:",/9Gml VIG1OX8nudx̆Z^Mv<EQC@}Ϸwvg)jQ@rF2I):-qqUE~ýA ^?Ql_3®I	h	Cx1import logger from './logger.js';

const rateLimitMap = new Map();

export const checkRateLimit = (key) => {
  const now = Date.now();
  const windowMs = parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 60000;
  const maxRequests = parseInt(process.env.RATE_LIMIT_MAX_REQUESTS, 10) || 30;

  if (!rateLimitMap.has(key)) {
    rateLimitMap.set(key, { count: 0, reset: now + windowMs });
  }

  const record = rateLimitMap.get(key);

  if (now > record.reset) {
    record.count = 0;
    record.reset = now + windowMs;
  }

  if (record.count >= maxRequests) {
    logger.warn('Rate limit exceeded', { key, count: record.count, reset: record.reset });
    return false;
  }

  record.count++;
  return true;
};

export const cleanupRateLimitCache = () => {
  const now = Date.now();
  const windowMs = parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 60000;
  const keysToDelete = [];

  for (const [key, record] of rateLimitMap.entries()) {
    if (now > record.reset) {
      keysToDelete.push(key);
    }
  }

  keysToDelete.forEach(key => rateLimitMap.delete(key));
};
AW!x340031QHI+I-.*fuGX7kqs_fQWWS1z.T*+J,I,+c~k8oqW5ܱ 01<DxJimport { describe, it, expect } from "bun:test";
import { db, files } from "../src/db/index.js";
import { files as schemaFiles } from "../src/db/schema.js";

describe("Database Layer", () => {
  it("should export db instance", () => {
    expect(db).toBeDefined();
  });

  it("should export files schema from both index and schema", () => {
    expect(files).toBeDefined();
    expect(schemaFiles).toBeDefined();
    expect(files).toBe(schemaFiles);
  });

  it("should have correct schema properties", () => {
    expect(files.id).toBeDefined();
    expect(files.publicId).toBeDefined();
    expect(files.telegramFileId).toBeDefined();
    expect(files.telegramFileUniqueId).toBeDefined();
    expect(files.storageChatId).toBeDefined();
    expect(files.storageMessageId).toBeDefined();
    expect(files.fileName).toBeDefined();
    expect(files.mimeType).toBeDefined();
    expect(files.sizeBytes).toBeDefined();
    expect(files.fileType).toBeDefined();
    expect(files.uploaderId).toBeDefined();
    expect(files.createdAt).toBeDefined();
    expect(files.updatedAt).toBeDefined();
  });
});
f#gxzimport { describe, it, expect, beforeAll } from "bun:test";
import { config } from "../src/env.js";

describe("Environment Variables Validation", () => {
  it("config should have all required fields", () => {
    expect(config).toHaveProperty("botToken");
    expect(config).toHaveProperty("storageChatId");
    expect(config).toHaveProperty("baseUrl");
    expect(config).toHaveProperty("databaseUrl");
    expect(config).toHaveProperty("port");
    expect(config).toHaveProperty("nodeEnv");
    expect(config).toHaveProperty("logLevel");
    expect(config).toHaveProperty("rateLimitWindowMs");
    expect(config).toHaveProperty("rateLimitMaxRequests");
  });

  it("config.botToken should return BOT_TOKEN from process.env", () => {
    expect(config.botToken).toBe(process.env.BOT_TOKEN);
  });

  it("config.storageChatId should be parsed as integer from STORAGE_CHANNEL_ID", () => {
    expect(typeof config.storageChatId).toBe("number");
    expect(config.storageChatId).toBe(parseInt(process.env.STORAGE_CHANNEL_ID, 10));
  });

  it("config.port should default to 3000 when not specified", () => {
    expect(typeof config.port).toBe("number");
  });

  it("nodeEnv should be 'test' or 'development'", () => {
    expect(["test", "development"]).toContain(config.nodeEnv);
  });

  it("logLevel should default to 'info'", () => {
    expect(config.logLevel).toBe("info");
  });

  it("rateLimitWindowMs should default to 60000 when not specified", () => {
    expect(config.rateLimitWindowMs).toBe(60000);
  });

  it("rateLimitMaxRequests should default to 30 when not specified", () => {
    expect(config.rateLimitMaxRequests).toBe(30);
  });
});
)Aximport { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test";
import { checkRateLimit, cleanupRateLimitCache } from "../src/utils/rateLimit.js";
import logger from "../src/utils/logger.js";

// Spy on logger.warn
const warnSpy = spyOn(logger, "warn");

describe("Rate Limiter", () => {
  beforeEach(() => {
    warnSpy.mockClear();
    // Set custom env variables for predictable tests
    process.env.RATE_LIMIT_WINDOW_MS = "100"; // 100ms window
    process.env.RATE_LIMIT_MAX_REQUESTS = "3"; // max 3 requests
  });

  afterEach(() => {
    delete process.env.RATE_LIMIT_WINDOW_MS;
    delete process.env.RATE_LIMIT_MAX_REQUESTS;
  });

  it("should allow requests under the limit", () => {
    const key = "user-1";
    expect(checkRateLimit(key)).toBe(true);
    expect(checkRateLimit(key)).toBe(true);
    expect(checkRateLimit(key)).toBe(true);
    expect(warnSpy).not.toHaveBeenCalled();
  });

  it("should block requests exceeding the limit and log a warning", () => {
    const key = "user-2";
    expect(checkRateLimit(key)).toBe(true);
    expect(checkRateLimit(key)).toBe(true);
    expect(checkRateLimit(key)).toBe(true);

    // 4th request exceeds limit of 3
    expect(checkRateLimit(key)).toBe(false);
    expect(warnSpy).toHaveBeenCalled();
    const callArgs = warnSpy.mock.calls[0];
    expect(callArgs[0]).toBe("Rate limit exceeded");
    expect(callArgs[1].key).toBe(key);
  });

  it("should reset request count after the window passes", async () => {
    const key = "user-3";
    expect(checkRateLimit(key)).toBe(true);
    expect(checkRateLimit(key)).toBe(true);
    expect(checkRateLimit(key)).toBe(true);
    expect(checkRateLimit(key)).toBe(false); // blocked

    // Wait for window to expire (100ms)
    await new Promise((resolve) => setTimeout(resolve, 110));

    // Should be allowed again
    expect(checkRateLimit(key)).toBe(true);
  });

  it("should cleanup rate limit cache of expired keys", async () => {
    const key1 = "cleanup-1";
    const key2 = "cleanup-2";

    // Populate keys
    expect(checkRateLimit(key1)).toBe(true);
    expect(checkRateLimit(key2)).toBe(true);

    // Run cleanup immediately (none should be expired yet as 100ms hasn't passed)
    cleanupRateLimitCache();

    // Verify still tracked (counts shouldn't reset, e.g., if we consume remaining limits)
    expect(checkRateLimit(key1)).toBe(true); // request 2
    expect(checkRateLimit(key1)).toBe(true); // request 3
    expect(checkRateLimit(key1)).toBe(false); // request 4 (blocked)

    // Wait for window to expire
    await new Promise((resolve) => setTimeout(resolve, 110));

    // Run cleanup
    cleanupRateLimitCache();

    // Since they were deleted from the map, they should be initialized as new records
    // If they were cleaned up, we should be able to do 3 requests again
    expect(checkRateLimit(key1)).toBe(true); // 1
    expect(checkRateLimit(key1)).toBe(true); // 2
    expect(checkRateLimit(key1)).toBe(true); // 3
    expect(checkRateLimit(key1)).toBe(false); // 4 (blocked)
  });
});
e:H-x {
  "compilerOptions": {
    // Environment setup & latest features
    "lib": ["ESNext"],
    "target": "ESNext",
    "module": "Preserve",
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "allowJs": true,
    "types": ["bun"],

    // Bundler mode
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,

    // Best practices
    "strict": true,
    "skipLibCheck": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,

    // Some stricter flags (disabled by default)
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noPropertyAccessFromIndexSignature": false
  }
}
v@Px̼yC8\3A/R>p>VjW_f(0;u~L
NWfdŨ	 x-R*w0*7 
x% 100644 logger.js gT`x]ɲU:SEKdax zMM@9xo ?;5n8.DQ#W7\b'>Fϓ'03)*WdZ@YUc-40000 test H_7VݰY쌈瓊)3Tx
	sk8},
      "peerltypescript": "^5. C'typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/tB7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSwW  }
}
VlxJ -drizzle-orm": "^0.29.06,
    "nanoid": "^5.0.48
  }
}
;FxB 100644 env.js M g`'~06~>40000 utils ze1SvߕQ{>yZ2B1x' 100644 env.test.js g3S.s(D_ B%ux Xwl a*t)x  40000 utils ze1SvߕQ{>yZ2~kPx M)Hix8 (\6sɭyg-Q{'5HRK)@x # teleuploader

To install dependencies:

```bash
bun install
```

To run:

```bash
bun run index.ts
```

This project was created using `bun init` in bun v1.3.14. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
Mmx 8{
  "name": "teleuploader",
  "module": "index.ts",
  "type": "module",
  "private": true,
  "devDependencies": {
    "@types/bun": "latest"
  },
  "peerDependencies": {
    "typescript": "^5"
  }
}
4B:x(100644 .gitignore -'NW|ݟ0ad/
[100644 CLAUDE.md vLٝi~~r(	100644 README.md 6sɭyg-Q100644 bun.lock ?;5n8.DQ#40000 docs ?6'x3H۟100644 index.ts {,dTώsx6Ѧ100644 package.json '5HRK100644 tsconfig.json I}rR%~wڦx& 40000 superpowers JJ5&(zx  40000 specs I*9 йt菿ix	 uz ^iu=-