From 024dcbceb0757ab7532b6bf0e76675ad307e11c6 Mon Sep 17 00:00:00 2001 From: ARIA Date: Sat, 9 May 2026 15:05:29 +0200 Subject: [PATCH] nvcurve with some fixes and better limits --- .github/screenshots/curve-editor.png | Bin 0 -> 203262 bytes .github/screenshots/dashboard.png | Bin 0 -> 429470 bytes .gitignore | 17 + CHANGELOG.md | 29 + LICENSE | 21 + README.md | 240 + frontend/.gitignore | 24 + frontend/README.md | 73 + frontend/eslint.config.js | 23 + frontend/index.html | 13 + frontend/package-lock.json | 4877 +++++++++++++++++ frontend/package.json | 37 + frontend/pnpm-lock.yaml | 3069 +++++++++++ frontend/pnpm-workspace.yaml | 3 + frontend/public/logo.svg | 6 + frontend/src/App.css | 42 + frontend/src/App.tsx | 159 + frontend/src/api/client.ts | 79 + frontend/src/api/websocket.ts | 61 + .../components/CurveEditor/CurveEditor.tsx | 975 ++++ .../components/CurveEditor/CurveToolbar.tsx | 130 + .../components/CurveEditor/CurveTooltip.tsx | 70 + .../components/Limits/PerformancePanel.tsx | 239 + frontend/src/components/Monitor/GaugeCard.tsx | 52 + .../src/components/Monitor/LiveMonitor.tsx | 66 + .../components/Monitor/PerformanceMonitor.tsx | 56 + frontend/src/components/Monitor/StatusBar.tsx | 128 + .../src/components/PointTable/PointRow.tsx | 124 + .../src/components/PointTable/PointTable.tsx | 119 + .../src/components/Profiles/ProfilePanel.tsx | 337 ++ .../src/components/common/ConfirmDialog.tsx | 50 + frontend/src/hooks/useCurve.ts | 28 + frontend/src/hooks/useGpu.ts | 17 + frontend/src/hooks/useMonitor.ts | 22 + frontend/src/index.css | 42 + frontend/src/main.tsx | 10 + frontend/src/store/curveStore.ts | 249 + frontend/src/types.ts | 75 + frontend/src/utils/curveHelpers.ts | 77 + frontend/src/utils/units.ts | 21 + frontend/tsconfig.app.json | 28 + frontend/tsconfig.json | 7 + frontend/tsconfig.node.json | 26 + frontend/vite.config.ts | 14 + nvcurve/__init__.py | 3 + nvcurve/__main__.py | 10 + nvcurve/cli.py | 1838 +++++++ nvcurve/client.py | 148 + nvcurve/config.py | 31 + nvcurve/daemon.py | 209 + nvcurve/hal/__init__.py | 13 + nvcurve/hal/gpu.py | 93 + nvcurve/hal/limits.py | 321 ++ nvcurve/hal/monitoring.py | 124 + nvcurve/hal/ranges.py | 31 + nvcurve/hal/snapshot.py | 156 + nvcurve/hal/vfcurve.py | 286 + nvcurve/nvapi/__init__.py | 11 + nvcurve/nvapi/bootstrap.py | 81 + nvcurve/nvapi/constants.py | 55 + nvcurve/nvapi/errors.py | 27 + nvcurve/nvapi/types.py | 76 + nvcurve/profiles/__init__.py | 1 + nvcurve/profiles/apply.py | 187 + nvcurve/profiles/native.py | 93 + nvcurve/safety.py | 72 + nvcurve/server.py | 1245 +++++ pyproject.toml | 32 + scripts/nv_vfcurve_rw.py | 1481 +++++ uv.lock | 531 ++ 70 files changed, 18890 insertions(+) create mode 100644 .github/screenshots/curve-editor.png create mode 100644 .github/screenshots/dashboard.png create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/eslint.config.js create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/pnpm-lock.yaml create mode 100644 frontend/pnpm-workspace.yaml create mode 100644 frontend/public/logo.svg create mode 100644 frontend/src/App.css create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/api/client.ts create mode 100644 frontend/src/api/websocket.ts create mode 100644 frontend/src/components/CurveEditor/CurveEditor.tsx create mode 100644 frontend/src/components/CurveEditor/CurveToolbar.tsx create mode 100644 frontend/src/components/CurveEditor/CurveTooltip.tsx create mode 100644 frontend/src/components/Limits/PerformancePanel.tsx create mode 100644 frontend/src/components/Monitor/GaugeCard.tsx create mode 100644 frontend/src/components/Monitor/LiveMonitor.tsx create mode 100644 frontend/src/components/Monitor/PerformanceMonitor.tsx create mode 100644 frontend/src/components/Monitor/StatusBar.tsx create mode 100644 frontend/src/components/PointTable/PointRow.tsx create mode 100644 frontend/src/components/PointTable/PointTable.tsx create mode 100644 frontend/src/components/Profiles/ProfilePanel.tsx create mode 100644 frontend/src/components/common/ConfirmDialog.tsx create mode 100644 frontend/src/hooks/useCurve.ts create mode 100644 frontend/src/hooks/useGpu.ts create mode 100644 frontend/src/hooks/useMonitor.ts create mode 100644 frontend/src/index.css create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/store/curveStore.ts create mode 100644 frontend/src/types.ts create mode 100644 frontend/src/utils/curveHelpers.ts create mode 100644 frontend/src/utils/units.ts create mode 100644 frontend/tsconfig.app.json create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 nvcurve/__init__.py create mode 100644 nvcurve/__main__.py create mode 100644 nvcurve/cli.py create mode 100644 nvcurve/client.py create mode 100644 nvcurve/config.py create mode 100644 nvcurve/daemon.py create mode 100644 nvcurve/hal/__init__.py create mode 100644 nvcurve/hal/gpu.py create mode 100644 nvcurve/hal/limits.py create mode 100644 nvcurve/hal/monitoring.py create mode 100644 nvcurve/hal/ranges.py create mode 100644 nvcurve/hal/snapshot.py create mode 100644 nvcurve/hal/vfcurve.py create mode 100644 nvcurve/nvapi/__init__.py create mode 100644 nvcurve/nvapi/bootstrap.py create mode 100644 nvcurve/nvapi/constants.py create mode 100644 nvcurve/nvapi/errors.py create mode 100644 nvcurve/nvapi/types.py create mode 100644 nvcurve/profiles/__init__.py create mode 100644 nvcurve/profiles/apply.py create mode 100644 nvcurve/profiles/native.py create mode 100644 nvcurve/safety.py create mode 100644 nvcurve/server.py create mode 100644 pyproject.toml create mode 100644 scripts/nv_vfcurve_rw.py create mode 100644 uv.lock diff --git a/.github/screenshots/curve-editor.png b/.github/screenshots/curve-editor.png new file mode 100644 index 0000000000000000000000000000000000000000..6256acafbdd0d76af92b57f49f3a05be99f228fa GIT binary patch literal 203262 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%0DQN~W1IIJw-Q~c2 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 literal 0 HcmV?d00001 diff --git a/.github/screenshots/dashboard.png b/.github/screenshots/dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..21640b10ac10e4f98a87bc7b940e255504cde35e GIT binary patch literal 429470 zcmb?@bzGd;vTYy)4G;+K?h+ul2X}XOcWWFH+}$lmaCf)h7J|D&f;)}BFEeLmj@|dp zz3=|I|L6eSdw12YUA0!NN~pZ77$Q6l{EHVa5GBNg6<@qShJ5h?)&Uk8_=z6o;Qosj zZ(c|U3o3&k4l`frqlw`Lt&Q}-qLLCUudLvQ)d?VK7PU6|t`y@IOt%-%%jeW3C@zP! zwhFzL`G6Yt=6)}IME95>IY!_ko}J^?+PqV(XKU@nQBz=$==Qf5h=CXX#luf38e;X{ z>FJY^-+%Gwx*B-VY2-72_wv7bRPaNC@h`Y{vDW|T(F8x>hYJ>iB(qOM(RY*ZgA)Ij zr*`%WPjN^xfq!|f-$Bg_p%Ov->vN8>iy-a=T+H8|0wVfRkbnCtdl$$w@{!7r>!Hs@W~KWx9k7}A>i?^gJ8L;tg-!oCa9Kw<`>n*6JY#MjTsyOLu6 zT;T6sH1R=P$Na;#{`U5+E4ZrQf41s+!V7f&OhW@QIPGGndE0-rEbxjwIktIPV#Tuh-&W*c}V=%0)O}uF6KY5;GP8FlRdbog?}*Dy5bxC z%zysYza6a#IX_;q|L!KJiU|}^n)1bK@cxadsN5mxV*g{)|CgKazn~BhS-%h+h#<4C zhO6THH!J;bIK>gF2v*yXP6PFC_D-OKND}>*m;-kCABWvvj{Eau04_)OmqK9(r}%qI z`)|IWs0!iR>6@(=R#tSIG$w&SF zd-*qe{L^{Y)&>Hlqp3{;_ix5ghyC^shWo>a|Mb59mI0Gf0GBYHVRr5R{pDAI1bImQ zzvz-iAWp!J_oDLz|LPMpBKy9}?%v*)9UV`wNJt{;>ST!6P0Ls@)$~T%{dee2`Zb^b z$<1o;5DoGFvC-{^;F@&KF{&WQ*3Y5l1^;;mO+0YQMeE^V=tApTN*JcK_d5&X4Pj6W zF@6y?*jvoACZl@#4uQsIe?-P#<^}HA|KKM2eecPH5Hyf_K5KAV!oNk8eZgD(`@c-S z4o|dxLKsqGGIEe)YGSfEnu%Nr&>cGj%Fd84aM_`oP28x#Q_wVLU zo8gz7oO~>3YMSI~5!*E*O2o^n$o;p~U;>zy2^`YHcnM8KN=HqZZKvcV(olyYDdKA{yJCbCY^ug za0YDugNjxNdd)h~`|HfZ#cx#xMwk0Da;Y6}I&E?XC9}Fe*;Pe;$^Oj}>zi-&Bmaj{ z^7ldB^co1=)={;gs01E}q(5p=a;Yw3W(|b*spk)~;vRqQ5O~1=0ii1K?~cWPnmYXs zP;*GC4tyF9v46Q1O|olZiIBc`u}7^r5MEa%^pF-#`-Oi@Fk-)EuTlc}w_H(J&6D;|;DVVM7^Kk&H~AoW%BeZ=t$()V zKDeH$DzgC&@sURBLVPJO&xY%i zJ9N`EQ&X@qtz+w`&u4o(H+*JWPlSOi{OGcDy^qZ%{7s2hjK5a=hXD9>Q0p~M5K3iv$dnspxTlAaNnCR`$^>#N%*R(Kjg9;j&K5g zPYLQmn;c5m;O291mO#75l>#d6qa?OsQUbM!9yu1H1bbzjBrh+oLa`z_6by`>g^Zb} zM2<2n2n5QJOr(K@hi}O_e|&sY$P+~bgTd<`p2&Q!_Gc76LssH41dv|*8N)n;OpkegoFzxM-e$%qndnz9sv`^3Vks@n21CyV>!!667Q0X4weTsA=6iN@t zO;+0uqXsRYs82rXJWL*A^QgJO2C3|a=n~6(qsU>aK(q^lvAXP48$0REwaxp+olgya zU#h}~=j#GOT5I%zMV|ig$%lCCTeG2eMA`DIJe=3Pakh3FL6S$A7!L6zH%Fq4IUHX^ zo8!{M@v&xyHE*P@D%=)D2L_)$kCnTNvf@S6E@(uNyiSJnuX1hte0xEwu(its`_mZ) zGW}XCFwJINtMKiIMI@mepNZU|T3Y#we!2By9k6zeS)lxTFJp@RC zpw2Tj<~nYpz?oRViR{kIBlhMt3q&LGSEN{1{lcSo@(iVr_tZB@EJsxR=pyiV9dhE# zyJ-u;Q8=L&9`lR=ipoS5$1oQH(bp^xT#8e zW$H#t2-`T$4$01kd}G_#L;o&UV1bs{-{$X+H~@ocOqr;tusS+??47o>ZHL}=v`Lle zbrM&!9(!E=n7lu?rPBIE0jQufrE!nwZI<3HnXLh(QH3U?T!Tk<#YvPw>sM4*XA4JU z0<8Cf>G#kPLa(xOoRYY54}w9~T_Bz;tKnq0X?Glz<0#7e=mDkXU0)jB<<16K+QIX8 zo?8o6KD`%@{Z3A=Y2QSIQfBpL8&i!amloe0?GtE(3?h_Ax# zoXU%5_>(W0zEqA_G@t6LbzVjiVOC<5NFJh83AO<_@E^62eE z=6tVsxc%McXpmH2%9yFw)9v13+w;@?>G?U0BClQhRnj=AyUoS2%UtFx*q}EEy_gQg z1YZQ=&TRZ;iUa0*X3Y#|6bwU zk>einYn=Ckn!24nkLL&+p%&kA{Ksouk2FWK`SobYxKLViiv0XuEPLOqfbUN_tF0H9 zA8z-u!8dEZOlISF5%}B~$ULVnB(<34lOpN&K0e6t-EP0ST2NuZWHSxiDXr}I_*p18 zM>dlyW@`!?hEr7d+A9JNQ#3W2=RAadgI5I-hk3-}*kB!I+L`gpx}=^*T-P28W^3VkFI_seb5`dO#U?35_6a0TnwACGXBV`b_3TvM}I7K@M3S6 zhVNJx+%faX{HI=Cm#y+8UG${PvA{r%$E#uOl7Js9lw~Svm*@3&2p8Zbo_MY#jOfFR zo~XheNkh$Q*rw)eCG8+*`H{~>oQDWkuX}F6*VmN+LCCUVsWFzaBP^-@nxq|~;^DX# z1<7z_K|#@@f=97>B)J2~r7#X6KvK4BM=y1DtY+@++IgIoX47^%G8SX5! z3`uKFEKOlG^Cp2@*!Lamq((D$o~3)SHrFw-x^hQEp0QQG_X^lo{tqguk64rE*ljH9 za!`rk2~;?!-=Owq%PVyh)*m;5D^C~9^3I;_?bd;q>j$509Rjg?VlbL=bM)5@K}S7J zrS(8=BkW!-lg_qJ(T)gI)=>k5zHQj(+i&_Cz)o!8@<7p#dHde?HfV3 zWw+Od3sz%$2M1P>m0FEitgEN*t>?Sa7;LIp@wr{Uu6KVT4(rnMItx_IP4tLp8`oa2 zn4=;kA?ZmK@8BpFIv4*~=ho#1u}RM~RjS4)oy~_2+y?wTy}bs^*k)t`^YwOX1a2!X zm_GMM>|?oKXW~-XW2{qk@9Rt?hAM?-B$(-C|zeP!E@n_Zxsf0Z$bo z9ZjMMo3ZzlVYOrF>~kg(?LxswV=@lMA>Z$Xka>%C$g{WI7=H4oQUr%jt@b1=?KC~z z9aPS28@_FAlun}F`gBm)sY`nv#$11qq;9wazTHi?d*`?|Bfzi_YyGlC9oe?#^h|0}| zMSvM6CjM+GtrA>Wspn9uSJl?o$WEhH5$3oPc7uu=KLo|k@2gy{se@05uEH%nti2q) zaERpl{K8vW7c@ra{||_&LK@O%)fQB1wMlOA(6vYbmB^d7&z3jUB*4Qo7rO?t>~LG`gr4a|N<`8#K+E#K_4F6C!stggFsX8Z6-9N^$+=tHIgXkvhnWR*`w? zi0fp-w4YMuq$ZpNGJ5t9SpkEsXMvNmCF3kOnTUzBFC=Jr$Y&bNW(EGh6N+7o8Mnf4 zvN0V$YmZ$ELj%gVS3kX>u^M|Dk70K;wI!Z&*@Hb4D$dvT>P%~o=;}we7y9koL0Aq3 z`*wJ8h-7siw(rf?!{mAW$>kD968lm(0UvzXD$>Dx()5>lzop7hbUQvLViWBtHl&Hj7t_w=4D3?) z!JEZp3|1YU3=kbAPNZaUK~V5S^54$LH;)!wKH?cQ3}k-I9%*9V|CsMOw-}DU=%2&M z|3VCTsgJPzn5l`|vpC=mCA4Krr)!EMNU4xPG@|M4VMEj3q3;~Y`mW}Rf!*)Rg?-S!9YS<9RC1N8LP&Nd5<@pvOuC~YlxMF&Xkl%Z zyGN*rfLV;QJ>XBoi+mJgFA07Vmm5J-XHZ}}taMsvQ9v-LFr*ljGJQ#A9if?TNSE!X z`vh|8(lb|R#&q0TY5LwC-%}TNB7;DEtz(O>eLq*?zW3@bDQZ4 z*2wOGz2$J%I(-znJUZ&7BC&nC53?5-C?XmPIlOC-7>v3R)6>eCymWN0b2*A$2CA>L zxYWx+A1&4++~1rq3b(B1gTYE6;o-Gu+uO$61{M>w6MUr|m2N}`;)kdcEJUqd*FPqE z8Qg$sMV}d)616qo5XQE72Lk}EOF(V(-JOM*nW#1L>3oDE0+(I(MBnmqYHf`$+xxm$ z6@Ip-z+57;p)B)OE|W_ojNU#nHyV%gHTFf&LaiBcK>1|r;Gp=!X)kh-N@)^G!Ae2` z+5tzS3FCkthPg#UbyluG(Mf?P>}g6UsA{8WlqpmB>OwlQLKYo zA&MOS`$Lw1pdcJW*9xj1VW{?FX-SzTsHmv#oOV0+A5PgQq*MHW%h2NX^weHluT-tF zqIbM(PS+C=Fy54e*+kZiV5Ttm;;#F+N!cwUv9w(eTeL@ zr;a)fDq!FfSW=h2H9N6iXm`|HpX5kmY{BfNl1 zfcJOY88$jyPY*ZoxFL|Iv1wjLkP)Z-bj=@#ZcdPTq>#_hw#tbMVWJpDruF^96=bb* zECLdEYw#z}PRcDz?VDs}*+SN`BK*Rn>_*&UTg%ES(55u@@_SZ1Rz75r$I6ksnHvL; znFdEq4gCe8EJBMkX*!!ts6p@XxBj!HAFJ(*IBeD*{ro<>(E0L6b7cC6-Hvlpc8?ml z%M(?Bpw~}d%GV#FlAY6?F<`&eQW+C#gYEV`N|Fy9W%GzpmwT&8=`_dWYH{cZm%p6Qe^p$KB_ka`s_!ArtSDP3UzP2dZxe1=ozv>ihYT zn+%axAoVCt6z5S!hvd@Ot*>WbdkovDFTExUftT^Y4#ulC2qgGp@QX1;>SkdP)w-TY-4xM8* zVFfUMjYZAH#O4b%^vf%m&p{sU?xnYdT37DwKfZ0a&e6Vveywm}h)-mKnb0Yh?aviW zMYx6Mi-^lMQxl2H&K+#5Thmr1*Y7(JwQ@m%aGE-H+~O6B+1CsCHA6HwD2SA~rpAX; z-`>7RrGIG&SEXcHKz;s}5pUHA3X2^9v@W#n%)}^XyYGY75It^M{$g>B(7&5ec--JP8HUq@+D_W3aUi8 zxFVW~y>U}#&UB{X`3N@q6yt;5;j4=#wKsRlp2``|;b?YDw)^VEGs~Ne)hh?3Ym-Fj zM)$(6dkC!}{oI0yHy3OLJW5o!q=wRHRcnG+Jl5bq_do2x(TpUA$6O)cZC&#~eU-IP z|L7m@HUA#Ykjr(IJaC_Al!L=55?naV)11Q^=HJ+1GW!Bl@I6@Z@XDcALXLejL@RQQU3fLx$W4~&rS6Rnl@8$Col-3O+8m*=UCn_ z#_?+zHxF{Eh8g{BqPN(+LJ_Juj3$McNYO;~b}!gvj2q;($_qB=8&R)$X1r=6eH*7o z-dib6wAy0IOhPjqLo}u$o}*YmbL(-mr8LV>AsU&6_1+w|qSFWTS=3l~maW!EBW|}$ zHk47~Vwkp4z;&1>n^^=7G?^52R zw^DOHUdEo_y}<)uy&-*tTBR;%H?^>Q1L4ehe;RIQB)O56%^`mkC#(&^UY3$QM6hOWqi8MUEM854NLk|W422T zh;b8w+ZlpO7sFPS?LoM>K41f@HmYI%=LduoHX9scZD5gd^?J)h zS4^ zR=vJhOnvz=RTv#5CneQ;)VLNE&Htn>1Dfbk)AJ5Yw{5|ul+F0|GZKe&cAylgsR$hl zD`K%8H^=+FB3T64WOOJcHklvIxUIcCqoH2#Q~#TtDb4!*9%E*MlT}_0(CG`~?u`5u z_q~@z$`ZT*4r#bC!6dw%*DCSny7RSW>}=NRrTsk)!otFvvsx!~4a`+^9XK5J@1T)8 zAkb;RAMRAX<@QzRcosQu3OpvkY@Tks+`lnG%+1T&I5-H-;=TW@R_RkRLt%36yI5xt zO)O;lF~f6n)3Dn80@{Ud@l!?yUc1&{=Kep4%Hv_S*+}+NU6FaA(0tlnr|weqgf$Xx)(~CE~`Hm$zr@(T%q)? ze>T(z{b{PBg81w_zbXh$W+E;+#Ljdj8DkR>(-%`p*WURQdx6|~&h*UC zAqCpHwJOU*WcId2-Yg#+7{w?Ib1v1g%AMC%JqDg8a->gQD@81nM>Wu%_nN)2CNw?o z_>SG3FrKn@%9xofu5ZiXl0S<@A&l4su(wTDE5takH~eTDy|cI6(-x-33QsU{A8rwS zJN4Gc9yGfoD&K1I(aLIWwq$-=v&nsX%p^nmnKCYy-6Hm|Od3z?Ht!l&Rln3A2-Bn& z*RnG$sT)N_O>|g-OvI*ZGLio$sP^vh>O6YdN3F`3Koz5*!Db20#|J`ON=igq`*m(E z(g)p-8Wq~Frb|@h#yk-N007y+kSUX?qMUou+nlhYZ3Jd zso%TIKrlQfT=vE>Qe3;gdxbO4kkT!KUG56-@t@bs`vnlJuTQ6j}imL+Sf)mrALs&|F2k zGi%MR#pMq-Cac-kgF(n}aAzDKd;TN_oe0Lx2Pj%q_1!V!m4hySAs2!_vaCERl)C5! z)O3Q`2Bb9kZ`~MsUPxA&`fe0EeJ4(RiJo1>lN`DOrqD#Kc+ko+kGoLF4D zy%2fZfJn80vL${YUcAw87nbHc17owyUGqS&sz?>Hrl!VumsesWiNo8(N~6f`5*`6^ zA+#hne68qUO1JLpbpC;TA(pd=YeFWEjb4!VtUXx)`41Z5*_G{3t+Z#k;66t;r{HKY z_tLX>o>I%%4lreL8#zE z$oUE#u{!g~8hKJ?5?c2NNb3>i{aCKZ%kPdN4*LMX!gaD~ljv@f=#`27V{b1wiC$|z zKO@6>G>cd3UTT=jY4^2o>S@RQac>YJuKoie9tUhLnRUR#T>EaCmGkW3p|vppsR<5KLfeD3nq{w zpis|{5fTy(Vl{n}6vkd!==E4~EMQ=2a1gly;&9MGA>kF52z~9lW`IiD2&K&OqnZuR zI25J53h&I9QB}ndre-H46~d$+5Shs0tzWD9MI^PJbV$KWO-)_RCool#h8Zke3sQuI z$Ij4rT#T`Q{tyVy)#m(DQ5wLS_lO6F3$-!H*n>+{Xdhxx-QVBJCG(G+Vy(xx~YMOI>_tSmj%-nI8V*UUvNH&7K=B3j|{JhK{xc{{%mhW>O9GoB4lyex}qK-2AGgqx^y6B59FTXNgHk!j2MuGQo~ z$7`<7L@OX;zVUtCXdbRae8cUAey#~SAL+QgTgG5dMf|H|lfi|i#XKp#m2HB+%cpl>181feTdTOHB{O<>OmDB8eN)Upyxgbtc;_e z;2mL}AopSNj5GX79asEAgP`3;S3f-~Gn79!36t7RA<$4!Zt;rw>xh~e;ssLIFu{rX z81IbdKB)SEcO)!NW;FFd+PTNX+GeSNdhO)X(aBJaNjT%yEaq|@-j#M2+m*H9Q}wRa zfDggWgB@+BX3oR7$8f*|R0lX@qFhKF#i_D)f}fcdK=p3npM7?BC=|Y#ok| zC>2Jj?TiL%k0_U6McA&~6}f9Reyq`(J@eoJjUcEG0}Xb&lF>+n#=h`95?Wf8&Mx<_ z#zls*WEeIKQyORWG=-!jRE*@iQ1;s?!K|CtK9jJBbxuhS{nQtF*I@Czpg6zF~7QP5;@lsrmwUycE5A84b5Git`sKIKaM-D;p|* zlWnfu_hiuWHQYk8nVYN=>nz`d%)n@xMB@7wtxSWMhsB~47PGfZrW>CIha2?ha^tGV zT{%0npV~kM33DBm{GitRA5F)sc3p&x$5m-Cn$h3SqGp{k2R2VOxKD4Y@WEoK4Y5h= z=Ap~2B+5+AM8PLZ^&MD0!X{1EcAU8&kSUr$U{+lkNKgqss8z|z;(=shnoMCf$z>C@wzMqS2t33xM?{-6N*`#T%OfbR4H40a zQ7@FuWCFU67c@eyTM3P|Qo8`>HaOm}>1NvEasb_^Z8BGHHMa@0WyZfUaL7`8F{WJc zzQ5KO0a}UyGroKTdU|@m+Xc-MS3Ks*wP5nR6!I_WdbCztOes|X045KpBQoP+9Wdnh zAK!0oA?U4N0rz@wXrpkZm@6+564I=X7%df*_{n=Z_@$8~`rhScC*tG1Y~Ls#L2!p9 zgPLqtT0oCibJhCYkPkrbMN`4^_Iw9?y=3>Tu~GzLOrxM?>I~?APM@*8D%Y(4K5YIA zkv=^*(O@uW7r7a@j8Yn%jIpmx|5TjY>wwEhPd zu*(88dXL6am|i0Dfr%uw{JFTeaB$ge>)49jFLqUZB|NW>R}irIMVo8VodV%8OtgUG zo2=^r*RQqS3Ep(V;{knpL!V?-3hQ-l+#MDXh`v}LrDj!g7-5Y#56VBhz1aJS#o5W> zGlz&XJ!fG!WV`Vy)2*harV4aMf-=>w(5;&wjTmUiIWSo9Rr6bSTs0;pW}(qehI*+g z{gIo>fukZcgI2>wRWBI{P9?hKddnIAA>$sP<#nkgO=KPcs8w`75UmxH zlTRE^57qn-MGHd4ALiEG4#d3J9SfqG9w-#3X$EjhT!pv{3P7OmNz8PIpQ+OEXww?LXj4)cqyx1n!jebJBHFN1+3I7F!$R{xQT+a}2|v=-?~` z21TQ6TeA=Ob~QRe?eN3LZV39$myDQ-t`+cay2Ir0 z==Cx67;E9WO^#0bD6HM4Znan9JUao%Im5e5bGTS9v!;ikf-*3(fHuo-R|^9j*eHCH zBdS`xGQ`9Y6L+&ILuz2-wTfp)`-hSM*~zu#McZi7QoW(FAp#W+Wr)e?escKYMgoxo}# zhu5%{+RrE-HCO2rINsL3)cAQl92cj5O@o}B^WelE=h~bGXx)mmn_FyG`y;KUYm-(q ziPgonHFLkVS}%~~7ZmiYti0^kRySZt?&5OPJQA{x6qtlng3870%a1pV3*6t8<7=KLd2J8 z%CnhhCb|kw=;V4#Ts0x8g7DpnVkqJEG#J<#aKyyKTr#>DdNHU~7=!h@6WImF8}$Aj zR~eOiK;z|?g7UQMW%NcLrkKHW(aq_!65~41dli`}NwSiYg1c^L3(#YmYku+=(G~}K z4i(L&ZwNeoz_nd3a)T}>pA80MaET7w4xKbVV5Y4phV@~vv9G*`e7;``2f8FTXUk5N z>(@&sBJCcCmv~ho!_7y<6<1632j)l5fW)Mn9qPxN-3uBOpv$$Cfr#_|`_JCsHQUG} zR^FZu5mj{*^B{)&NFSdkEP&1VB^Y>o6zAW$heK+JfJwLf@jGm5dJVfRYo!ZKZ}3}x z0gzt8`*$1>bW&Z-tLA1&2)IIFsNtFvN6(g;EA2tom9fsNYTri&FQHalNvV%6dQZ`_ z$CTvYnc%4I?_LI#I20V;mCs8Gk5qEM|HW1Ul|af!j(Y(Cm+t3#qj3C_`l!DTSSLy(7r-kKq7Oe;%V4^jkC0HBh&Kl@bqAO&c?BeW z&fGwfTTw;P!(qj=NVVXit0Cr#Ez(}iCXzt#*T1Rqcnef;>#3QgdIk$H4q7Y#;TTUfiuJLJ<+fnxVrn}E~4p_0Z_?fI(7$>8)6loF>$bmnS&mPjL#Lr zC_|E)M?cqOU=WL1hr`eMnGqQ?pFx470f@0}hzZY=$R6&Kwu*kwT$wN@yHsYADx(2c zulu9Dn3aG`DGkH+_V$^RkCjMY`*zlN#ZV0m>CT8n)Y~Y|-vhT1gS4N{uekvT)Fwl? zrsh;YMoRDe^SjVWrCzw}5tm>^czgQ@$C1+2K5MQEaaOx{ zt#Ww$h*yKj`9p8_^_LZ_Psko$*SB<;z;5cVuX&yE>|B{Lm57@Zs&J3(3>18%$4>j5 z5Jp8R8uMQfX8erGW)Ivq9P`-3v6NkW%gFG$cb@eXEu~meW47IpKQ^}<8eJu=y`a@x z#ZilzpkTM`fC?~!aLF`Tp@Z4pYI3n#es$d4kFeluYNAqxRW@K#uIYY30@MxC6zp|2 zOA3Wc1aG+%GysBX&ceU|g~s?f)<`NUna>;g=T?pDk;kkji>HKut}^+6btc!90duN+ zt*H%Ae7>5~X2J6J$0!gP;Ms$Zw)KGVF~5EDrNI|^=`NxVbL`|(VY*3M_BhWcGxq0? z*6vjs^4};uP1ivlt30ElJgcZV|% zzxNY1EcY&nBnc34(VvjJckBAJeDy#lskc&>d87;L+F5Qd*CwNBLf<3XBDbZ9BF}q{_r5;(X}pbi zJ}%r^3far=-wv=pSs>yP2o=x4n&qo^bUJQCzWM&smNhuxQ{7f@r16nQ62k0pYEx#& z{R!?7X5fb8W0r{hLAt(Tu!Jl|mG5vo*I3oCie*$9dxYSKT0>6%ST^pYT7XuiF6qj+ zQ1ErJyX8o;k{E~j&z}}R7o#|;T%|Ni6?WF2M3b4_ZF^9V-iBMQjFiDisv>WjXSqZL z#!9M>(IFCduU&k)tip~=w!cH=6xH+NgW{p4z2p~bCnM1n{9s4EF4>W}I9vp%q2fuL zxYNMIv%Bce;>uTP)8xZBQC+ldkoB}$G$|E9({1&FNq#jWx5h?*eth(i1BZoAT3~Ls z-~!c1MWxs~3O{_xWUfp@uJx71(-XwL8xJfnRAWVc|8bjab#4xsUaMhdZj;wjH*b7I zX68z;s)HfMS&MYf0XrDDDW)cwqZ!bPO_#{UD~xJq1uFXgb-fv@Gm1 zj=sgk{WQT(7zqd@(6kz?hbd|19&T1{s#KZFwVUCMACrTFgIltTLMUX?guX8}jJ3|y z(-XaWCqI@wD-1AJiC%AxoA-!W<)+bdPiYREdU|?@Z}8=K&N9-LDqjzDB8=H30A4`| zId=eX`82O`PFl1*pb{el3i|L^(i6oS32($nuZqG#%>3w2(1;3;RhMf^OIkOjPJ72R zM+&T+OREgom6esUm5K!=tbAl-!UHo!3QDzA6x$v^<>F`u9we13HlxiXa#}clgyp{V z`6bu@M1C}lNs5l@zYjbLRKqo)bHlLwK8vA^0cEVF$ArAX0g<~*`?SeseSIz$mcG-r zEkl!h2wR5tD8Zc$75Y|+pb7EdoQ+1csBG%B)Mn;wWE1PXnE)D71oMM8O|GN>DXB(l z)-hiZ=BlhPov2ES<+F1pg=&2m;>OX&%VH^9^yd2?nkJYJL&M`=^ORbRzJmLkTt4<4 za@o3b7+xwQ7wjt%a@@x|7YScglW{-cNPWag5bH|HSX%GC|?_))~x~uzd%`T_hip!7_wqcoqllGD=_I=H!B2S0xnkD%{V0pc1YLa$Q@fD8}1ymZF*y+3N) zO416l@)b;?LSVO=m8NZ_*J(imc+~FxNZj&GV4R{in9v6{H6d;gP;0I)*>&~-n$HNJ zq!2{A1Xy8JC=3(**lJeqyUYGwWd7W|xHwr_KHsewHNEa%Trxli{se0Rw;LB9N9C3f zraV-sV!<+;J~=L?nMU2`wt{mM3T6yp2{oEGfv134X}vSK9`pwnuwa&*NM!6k4HVtb z*e6_@JaoRkzL^Kx5+ff5fP!Qb=-Kvf55>PT>VG{5iaA-En2;Oc)a+TNS36@PCWf%E zu!tg=wYM%4kx(MBP7Ef`NJt=yVw)-1kxAKyg&Dc#eiL|JRrRh^IFrkz?8(WS-=SGD zFRr_q7j-XeSCRJR$VWWR6LQKu@3o$Vu`xQ?&;8&IK!UleJZ{ZKO43uqar0xct(u~z ze{)Z%P{iO=V2b@kA8qMsjbC!|Ve5bVMC#{fTB}xpBGMvD{GrEEL09rLyA8j0D zj))U6V~am;sb90M1y*nDm2RALTCYDG%;KXE=wdrY1KC6dNaxsq)WyHj?#WY7fd>s6 zkc$GD8Xe%>Ma|90gsBzLf`nrQ&?rnkM^lB0h^ngWK<7#(T3U`5wwIK8^MPVxRftT0 z(wV^f{`H5cFXhUq8>bft0>Z-R&WH0leLG_#+qCN+NowrXYB}gSSg{jniX_x1O;TmL zQgkie5la0Lf~NGCvLIu9{aw3&T*3t4TqHd`epVQ>)RWrM99aJfLBwy| zb<~g^uVSjo_$ zdn>ufT1Hd}A42ywpQ`S95rEUf%~C;CLY97lTi%_#S9oNM?Zs7`3!b-m737fiiW zVhO$`l>0)S@RiP$tvae3cy}X7sG9Hb28zgsC5Vr2EW>ad5c)Sqt5@QTSqXM%H6LM~ zyopWd&lC{zN)#mQ*T|G1fRV!zZ_gUEnmW~mg?xw(mUx- zJg0O5oi8yQt+aava|EWPq)vRB&mun?}M91G1*NkpxRb-dQ($3<3in7y1agz+0lHkSf3#rUVwXbyYR~4 zv&q-Y_jxBu-RgCGa)gR#pTy*#2%b1Ts8O^&`+)7=vpu{CZ#;e#)%Vh^)2M$lQjqIW zvIdD@xitGgxa6X+af~(4h;Vy{6SdLhfWl=gSN0vMz2SBS4dZz10X(O!i9wUL)F6U~ zW}~WJt`I@0P-RKKX(G)G@t#fdZjnYU10*CQ@!X+K%Vp3RBx*BekyaxL4Rj!pIQH3% z{N8!EcSKOI+Q+(usVS)&3NkY3V4x*O8;YjZL+;^ak|i=1m2BoBDP4y)DC?78@zmzlN;n`6A z(gu!+&kU>rv|Hw@97McoqB`t%6%V)@l}N%3G!yjiAisQ;BXu360Y;$k6;&2Cwsidm z>Z)5J;_B4@Wb|LaH8DdhJgOzQ_PH!WsH1sr@!MZ|D&6ov^w>BN$F+ovxk z3JmC3BpjvaW>h(p(;f^0IZ1cB>|D{q-JsowpqU+uY14JkLqwyt*>cA5;qD+Bi~E`s zm?%IDM#7(V35m1*1RYtomP+mv58zwE{0IK$j0h&9)d3(~gpo^O2+qcgEcevU=_1EV zFyve$+Y{fGs+LE67P6ZSB;csh8<8#^ruFgA4I-lDe7@ZDvAR`BT4b^CIt`^o9 z3#^-*ntDqrwu%U}ZQCnc0m)N~uEkac=QBXO=V9@HMO}QAS2PKKo8Gbi43+ZS1IOX} z-D_i~2I@D7w7()5QCMg03p!CWzxz7h*PF>;Cl%V7y*TQh&hFL;tpx}Va2aY#H)`l{XPqpk&N zamSt^V&#Ry%{#~N0pF_=bV=zl<-+1-Hu1B&b&sdk7CLYpCCnI`n$5!ju=kL}29it0 zMbg{aFw>Lv4r%hlQv0hwCz59%hy{sMMn=GteF8dDff7u>tPL5L9lP9>Wlm zyc-@%ZsZj8P%s5OhZWR4L(g8MC@y`Da{1VZeyq*`{HG;M?oH!-1H4r@zfIbGO3?IN-assYYvpuq9!-=#;=i5U+R$AE!d>OjtYppEy70}LT#&^DyilUwz$XjS<$v>VW-^PF~l z3Y(x}y#-gQQgm!Zao26u3^rKK;2`rqyp!Cjl!&J=Uz~Qd9U32RP;zF7yvrYL_q?$* z(LM`z-w4F~I{9nL&hmV+2DdPZ1DTeHNMH|DWF45$+bGQT`MzcVG$)Ad=&cv)ig&5k zlt7@Sr26`YhlesjEfGDd{Kx^el`{?=`*lB+8jC4YS8j2Qw~oB$yxSi2O2C-gQP|Gj z9%U3;!_`k%7+N;Aah$D#^yCSgpSf8(BbXc8Lt8N-$wy0W?M3YgPX5YNVQV$|=BMkp zr=8F3A!ga$*o`Z$$Sux4qu|lKM98vZL+?n%kV;`t46xp{X}R5x}|E{`ewu zfP!n~t3B9bL;a4JSP-v}f@w02Vjy5>`++zhRsk(FK28?58^Li$+e};PaIJH$qAVun z9ez?UMNJAICyr`*ei{I#?-*%q83pc^zsPt3Y)-wl0-d|Q0ve}~>1tc*od_Z@PfEwuU3yzP3V@|NteEXe~{rx{=YWfHpQd6P*f0%mfpen;PURwzPK@{nh zZV-?zr5kA$DJ|XIN_QizG}7JOEV{cxy1TxIz0W@9n{gcZ!*OQ0-uJnmUtG6SKVN%> zh}}n%v65e0XH39)k)cC#+!;fLr=bZPD@jhq2ZEKxCVoJjopGK^7CGDSSM5dBORz8}m)ds9V92DVD>7I5(JbY72g^VNiZEFUNN9tBgaZqI4dT0G;@>@YWf z{#9mjmzPorN1Em8y~5r}4jtuVJ?)a}^cYsD_Uon9iYr@x!5$qVoM%dD*=< z7CXV9plf`LELSB1%h4qyH%0tn!V--k0qyLBrocZDpM{k*5+q&@RKI-_0i&N*F1Vk5 zAx4g&divpqY+P`YK+RXZ`ODhrYQ!bftplXm3%E+kxra*I+fTQ?wzrdpem_VWG%B*T zyENyPP2>Hu-U)Ij{`JGA!RP0ndYc*=J0)Uwr;4@0_xI^5Ef-(5wULk}4FmHk!|~wO zwAl2G(Qq1SA61V7f7@9o7*f40DLG=Vju|U`%?e7j(kAwKL!h{0t*=|F?a)qR_cYVz9q8 z|Gcp*i%hGRB~coeksN8#TsnK1hY)N7_3E6He^Y9y#9HW*&(7|(^w!eIX`=R#tkT(# z^{?g?B}+`W-th1h$iENTI8X72hX`ay*0b=lq#Lm$DyiB@NYKhvlL{w3g1kswlQomUB3DS-YwD?5vmkOS8^om=oF)O>uT1+YSPzq7S!WO0JI)4GlaSe*8d&9GPD5dgMZz zuhDQsG@Gl$1n>Hyp?NCX;raD*HhK4Z8i(e~A6eT>W#@XpTY>$BURk!O?p#ju3T0@M~C8#xw}F{|A)$Jby$`7tx|J&rfs^(1Ao|-s?Fq3{Du~}qGY6^4$+M7U zho{Vp@NaED>fRY@K+?(Kdy{bL0?@q_#!Lvf?cd4EyR3^$or$O5%n!a~tPGph=25ER zQTnTV^%?y`@YH;bm9hh|{MqE$hv1&_p?uRxQ*}`f<+*H;|G+`4O#qf?ZWqFXgpu$9 zFwo$HXKOwfY6oHat^A;t(4Mb^&~?wek{Yy9pxCCC@`xQiO}t-*a>hHq_!@Ppuf$i0 zdt2lBdP)G(>e`}FG}dwYP*rX%R?d>K^HctWjNd8046Rc@EYrS2;!<J}^Gp%vvqYlfVdf)}E2$@@jLYGF$69*&HLgA4%Wt=yHLvV?RQRSjwoAFZNAtQ1$fmz!b{G#pPI}-r~vUd4D2CNEM?q)XbWi zR|pdANHJ7$v?J0fobnQz9w7Mb+uHh>u*~Dm5j5*n?ktKx&_q4%`IQI&2i)@pJT1M! z<+hHFk4QbmS`Dqs0-vE$TIuf5EychT`+^a%Luy1>S4Ydl-Q+3Cd4WXoO0h?CA2UkT8yqbbKzRRpBS!8UOWjViLGI4K z?-$-W0g}_t?$(Expz5kZNQJ13uxwQrH&aWfP@fvevGCq(#Be(7VhGcCEY#YlRT#02 z**W=PWv>9KRWeoX0NV_ixQ}UTf#!a{8xp-jtr0r2JC5*UF;4M%9a=OgIO`(?%-g5iJ&)DGGSaV2u)zcfhE(UG+) zsVyRx)uA^_s+~;1J9AY5`~pM@Z;>7MXN**HQ?4l1fPA%EzvbA=7M`A-&adJt1_tO+ z5LrYaa_1YmN}<}sHUf?XxRhO# zPYifVJYiLrP!0Nkgc&IG85xL>1+(RI`fpRs9~Rj7PzGC&g*MCD^6pI#>mS~Sw>Uz! z{zTnPmz^S^6zdPYfuztj8}ZIBu?Qr`9r#miyZq()CN&pllxs9oGc(>?a2do%@E0;( z-n(~wqTgF4uVyix6O+cX(ConqsxEmE@VMJHHq2`E_Al4gk`^~(F40io;X#J-0~hxk6skdYD+zjm9B(to6VuXiw(1p1 zjfWWvwVO1zqX@kmULiDgsB2keu)|H^#t%L-9 zMIMzyb=TYEK&RCKttjj}4@YCDz~kXHOj;q^J0eQxeIhknu0f8*CiknUu1Zv8k1WE|lx+1-At&weH3cpUMX(r?N4QigMm zA3xFS%7Vnkbb3($gVX)|7b+s6{Vq)W(lw|tMv);@X3QqB(UF|du`rQrKRE2EH=#rQ_)yyK4+e%=BzavI-%n@UKe?Y1Ur+A@)jl&>_H_6=3` z>(CJ45|2%7Y>Y>8v7RKvp8#On)+>gP9}$GmWFf|)TgiG&uH}9xMW}6;RZv4?%hfqb zW|zw$Yll7>8XWyRiZlf0WfyN>{S+TqN@#kgtMaIB3Mt`;ZW|wX=hiDPT5L*y{ggFOET~a^*W($IE0?DvK}YLZM7PA=Kbp5N z{P$nFTkf2$pz-V`14B-!Cg;&M$`C%kR2|aia zNuoOVNJ5`3w!h-T;@uH;L@I9sSfWz#XLDzT^3X@M18mdFUeFrsseair9(=?F-&mil zkbJBK48x}TqMa}>nDELZxONo|C=;;54OqqpI7h2WyBhtCvezy?muHcL%!#)KMSaTD zsy~`5N8Dw}DtoVVSgpTT#>jqTq@3}_j-7`v1a)}g3q>%C5%H%trr8~ORj3DW92X^N zCtn8kg~rM8_=z@0s7vcErT9&x_7lmD&t8|ZiYe-oSYYBZ55mmv@>Zh22zDLgTRtM> zi%Om>jxn~ZU=i}W<7A3OOu zc9^|ekjWVZxDN}JLN(pN@q9(G0K8o2zc^7%QCTa#*h`N;NGLgGg+Guek+&6Z}d9NKeRj(e^rKX4uAgq*?VZ`;2;d7 zhIEft+MYQ@ReIN;?1;#v4dSH*h5#=DfG9_1ExL(=MjdUD1TAgraT$)a^$4-`C^4e8GX0)XmouX{=xG_B;xk z%Zsgm!k>m^9jvzY5~-=N!DuvfBvcLDcOwJfLCj2DbZvuUG!drm&%e}^lq^F)U$8e3 z`ocniy9``nwq~9WY;2fMk7jj5l?=cK7DM21Iz-iFce_M!zh0dOX(y(WQL&?M?Y=hZ zkQU3uSO1ou)EPAJQ~*nJw=KvpF5MmBv|9CN1%0b6uRxqAz8d9o=A80>*j3GF??!1c z?rNtaSqH5|N^PDByKP58R7C4$aWRY!gxD+T+zx`G)?s!IQ#!3r&7cEZ4tKmDA*G@X@dNCD$a*Cp|w>+HE2HIg+7eAV~1!_4g z7R)i>xi;)UoRe6q27XPp&DcpAPW_=94wfvpvJ;0uktg@{wK&MHSY?tviGkbbDjPj0Ktv*ez#;vb0X+O`ICgj2N?Ro+; zFj_thzjEKg{?`*s((nWE?tm-K(f)l$eMdD`n|1D@@04dRL>p^u%{#YfI}tX9SS1^axZJP3`M1p1%O`Qr4BdkoB3=B}@pJS# z+=|K`7gf|Q^pwpBdEWH})QFeOU8`96^`^<0wCHZ;)w2%Iq`xymd3a%|oyJ*a^!ojk z##Y^+cDxHNnNIhPzQ1Y%fW-ao9*G!P+Z@c}zP3n_;SOFDSPXMGs%%}IJ<8Bj6Pm!e zC)y3aM~ib6fMse~W5S!n)FOrc@`^VW8A|%VgkiE+&zF+e+GK&U^NQ9A9Z2z0zYC<;2Sq}DfbD)QUKke$CAHa3 zpNzaP!n_26#Uij;ImUe)uMpO!I7mx9Ro@Ze;)PkI1^H7Wz1{tOa}al1B1~r2R$dVs zA;;w#Ahxw9SfqZTq#2$jph}cW?2@($GHPj6hIw3%jo1wM?WgVjwY6%PpGZEm-keY~ zEy=?;61y0EK(nzKHRYdqOj5%df!1lnMu0?F;4iaepNJ3HE=wV;bVE;=t5sE1uVyl1Ufss0Db?k{jPQ0-H#agR}mt+KhxWaj`%*hxNgS_ z|6lU$bGq9diQwit4hqFL`BThIorv@V(vH63w)a#fq-5oysS2VJw6Mu`(?+w!5iGm3 z#Twws@Ni1fXUq%xt|c?-MikOPcH*!*flZa(r+|LI%sg~7YCxF2@FY$N>7gpld$q4@w#!bZ21~4oPbY!y|I-3~Y@~FvM*pXn3~-ROagzD;{{;d3?nL6G zeHj9CydC4OI~B}4Cq9!`X_Q_4&V`J=*q1{4UXMJz7Q5q@5``nr;@?vDaDrvuoq}cb z5_U^V%i7HCT-n4+0Ky|-PiXLRWjzMfQCBekGYwr?2~qmfF6DVI$8E6$IF)T5)HOli*7JVmPL z&381S!`;<8#yv6S6=aS+1|j(VAW%avz;$1Ps(OyusHR>YTF7j?WgTG^#k6H#EKkW;L+IZD1tC1Aa!a5-o<=bl+i(43mI#m?l((F zj0=bYT?3AteSB%DHQ<$1%;_je#Rk<_RP1xA>`|9BS(&3;&ibwUc=g(01XwtiWIwqb?@_jIKlakJ1 zbW0+q1W=&IE_+-N0vZlKMiz=EQCAu(6igLoed0QM6ZtWK(uPJEg9Oy8pVV4%X}m~Q z%{P<^3JUwd;?mNc;CTPWmjk5#?@mjlT8KtGIR~%%-T<(uP_rCT@K}z%7U0};E6VX@ zV^;@D16Gj3BCw6P(mZc*0ZjqYGI#=7lVgmwPg!Ag+T@g546pc9-OR@=pdb5fMW$7d zoNsLJ_DVuVbwDAdwwAL{y@~+f6cSXt*>?FHcF8Ga2@r1HMEYf(>cNu9Xo7fX?Rxm# zB^$_A_`*wJBz^UswUaiSrp7{%|&}Fybo<6+$pr!;fRig9rnX@y*a};pC4O5`! z_`i#U4>a&avaQ2A#9rlNO!%(9%4V&jaByl{1h6nLg;%!K1{*Th)w(Cpc0!x3teh=X zmN>#wa+yWEz?flj@(sqo;RF~Q6Ziwp9dfCtjP7C;^dk0X2iS27!Ow4=8jl&?M3-%a zWAKdAe_E3D?3Tewn+IGQOI%I*GV;{WtdG#hq57bDDwg4aE z8U0dtsB3W8n{_Cau+|$TG}YW+3uYsAG(NGVb`zPtGjejZX0!E~D2zn9_&a$Gk(H4V z>o@~JY3W#&ct2}hU6*L=PG0Q>c~@ehdIbf+ z6Z4>EbiK|o$XP#YsyN;_GUcQ95!_#mSO(#K&Is2E`ruq6DG^VhQkDM^L`7U-Te-4g(XEQ^!c5o#w zC7YTZxJk$;kPwU;E#p|-;%aWYVxrtN2`ru~EqMIwu+0~)A1+&%%2rdWXHGh-Qx^D< zzpiIrGA#eTJXj{FU1T`6nn8}@BNmCF!Y2D|nWm7)_>ZO9pso1R@cew80>rysHcvhi zH6_t@bAMKXOV1w#*NBfPX7!NAPc%s5YD9c=O3yM?2U*Hz50sYDSasFzTXt$XLmxDQLUFHSel_);=WJ z{!X~l8Ey75rY|dP$2HH++Cj;|mVv1!&LB=-SyLk>%0`7xzifbSzT0@axp#8b!9AS2 z2XFYb@?`a_=8%;wD_QZ;qRYcv6Y8M=U&KBJ^kt+2?JG=vw-=mf`f)+cTl@t&yj($4 zoqeoPRo<;1f7LJuZH)=oH}>tLIy;-TR4Yu_g$^v}%WBbeHP;}H$~^{tdiv`7bWYk% zY?1h|h*TTGJBL87>q#^T_?25z$*tLbkJ3*3y(`=>Pxgej8||v`3cQ zg-$Q9D3%*1+C50=c;JLKF(u^RW7UvL+uD{hcC#GiHEmnm+}uFU`lXPHUQ(rHiZM(# zR=b#Y84QU{HO_R;-j0vaUlD>+v3gYH>1ONds>a-v#7+_T1Q_tRkYUm|5n-kGV)*mT zomH><4kQ*RZ-mld!OjZ^B>4=;A#qk%%voLWEjxn3lToldHhb;1@=e zH}%P-$Jk-M>3}G zcieS}X6%42B7E6Av+TA`^AdG|H%zdXh;a_1vm~@~IQ9*WXmn?-Pr@7PYY;qpRwFmwX`Ft^ z7uu!0iAH8R3FBC5?Td*&nJigm`!_Decf-gVqP07a$Xc|T#^=U;G2gESFF8u~+Dr6= zN>=UG$TwuIzc|}JWuNGFTuxw>3PHc%+*AHVn6`&2R~Wtwk#mYUM;zm zq~5H*g~Dn@NlKK_TfT2upFaflC8}`_$XJpVhm{p%qjIXT8r_G85qngACgYi#Gb&4+ z2@Vb}mnlDk`leDJ;zm{9gmT&#qIuU2g<3Qq7eFCP!24}8XV-{HVzV2Pn1T4k_6!3H zqU-6IYDd=@tWfwX-D}7#-@6oCu-*9$XV<0`&IxpNb%E|JR)ibcrxoOUP&K`>YV%m2 zwZY@I5hZrP*Z?vX%@{l1?K%1Ri88*G!1n*uX=yHJNbl?v4iTFjQ`yA0E|aNI{!l&m zL9!3P+dfVeX&B?barv3q2)EjeZ`FBwv8U^%IaA#>MT~`Is_L)*LAs1>?L9g=N(r;W**1$Fp$Sa&u0_KbLNPrXo z%Wg6E0`d-%kTAO&Y6|Fks*yEODu0!5wd!Y>DESlbUPoZySj)RxOV{9 z>ioD-&6nMagZy421M$2@;Es) zf`O93Xd^uWz9Vydy>OB$$RTcBjlyo`J^))v*ZDip{#HQua5$#7J zCss3L7%_O&2veZev1X$L7hl^kuJdIJH>6!)B3;b;p>{~TUlESK&V<|X$sU`%aS=vr zt1;r-mpZQF6lu?0047;86E;IJ!IM_@Zg6QSkAL6JSp{)&6}l)XDLElSuj;LE^!VV$ z?DZ(kW5+#ei^>*@VtdD?=YfwQ*Q+zuQ?MiZ)~V%$og(igPnjZll6a z=SzfpdL4h85dn*vh~;$^-}>Y)q5-4&n_(K!c93KmrMgG0NZrBS>T-kj^`196RKRJp zfxM6H2djQ8!1O{sFqMB2AzQ)1c^NxM=u9j1F9+TwL|aLXpUZfBrA5EanIT6(>(_)s z8pR8P+`MJO%3dFlrE+M$BVAQjl>ITGZ12Xq+>_GQy0YRPJpZhoKk*P@rgKnVB z;nVNHpez4Dc8HI&<1fkWqug&i*vIDs?DI=Foc}oPEWlCW5S#MBsC1MC=TJ1N!==2qHO)_C5gUdcfwg+(bVGf8ZMEpfVNgi%8`D5JDumwmhA>S6z$m3``olN!Xn1iDE+!aroth*?DSDF( z2201s(NPv_i}-dvx_HCv)hF*3ClMt^9oRpcr1p&x$}HO}pAN9;`1y#&L{H2zPn&8^ z3>#}!4I2wWua{@fSvuV-FJumapAH6@%xu(E-}+o%J2wIj@)WAT)xufnqRnK|S8wB? z6t0h0dguRi<>R9lUuo1>7TI-nck2P_@thYky?l4USCS(iz$)z07@>n(#uxLn_LeiHM4;KA z=dE#iG`gW{h|E4TX8KShH0S z-QTEG1ltL#Xc8R^S<(vKj+?Mm3aR)NU@Md-4fEfj0AX}u5^7^R%;#kG)K#txAgg<8 zV>F66pJSIcapG>xCja^Y3=ikQETEex<;!#hV^+~k{Sk8M5&8O6# z>!2}45a!{A%0tURHMQ z_JN>LC$6P*s9~9j+OG#ebFwC9%QSmHEt#mwM|#$ud(t-D`lPC^flvL)rF3OyCx}+L zcHG>u%+LKd>Y?=`H6XF&yrxs)ar zEJWhR)kt;m9AXZ}P-bXT#Odzvv1lD08}|nHRJ8>@v&k5fBVhP=O{8qA6Yw~G(h*a% zb1J>OyzEOb67KbQCR^80$;ipEW`Lkz9ThTaHGL;~wvLNU9JN4yN_-XqLO^TbX@5%a zK&p636^Nuj7jfCRZ^i%gWDpd{%VT??k`2zJ%2p}!Hxpf^^d}xf;NzN+Zh!X;KvP{J zi9oZe?I3W3O~a`|nPs>0_1BcB&xaV>tnHhaMIFF0oFONpGYoK$tAkRu4=Uza8bl<$ zj8`n?nyv?#R=hxo?K8gd$N=w_;vdu{ET}2=kJ{9Rmp29$#Mc@U1DN@zTCpFjps!RD@OQC!`-~*#R238Gpm{sA?WND^ehouT2jA_ z?4$&XwJy~TSBBmgGbMQsJlyEGTVB2uQm-IV|9hX5i0GEZB}i=f>WM7@03{`F_31IWPv2fwtgY{fFDqdySe#N99F z3wd53AjTEth|(>HH7QM=$`^*@=Gp1ngvF{|=YL}|t@`fCidHr>8*Wa7#QM^F2`18M z2{)`gR6fmui0n|I0bBrjvLs^cS%tZ5xcX zhlp*B`K)PN132?^3mVO3tdGI)TxkwKz70-C+67OYLFg(^Z9fs1K^O;SFckM`_tPxe zWYP`{Uq-DV#`AKbj^yF6z(8+ldI>?0CzzqVkkphP2wLEP3pO82f}f_?2^S^0o|Y_@$@sXdHg)4`Xe%4*6V?cd5wN~NXpAyWz^g1(9w7!e0%7J`fYhl}aRUv$ zOl`jH7BiJUFuZMTZMQSn6!ZOYXWHkh(!9L93wWvDE0B*zGsQ^I3!=Y+tkqf2b!y^V z6$7$5xy8X8+Zu)`I5kV%t}F?FfqbfAiHI*v-e@#vYEuZJYE>#;AVXeNp3T~K4yt@J-W0Fv0qS1Z+By&|Zukvi>Hb&iwN7TUq?Gl|Y`Ai3 zYG&p(yrX~27{Fux`4%@q^cLKY2?JnYVdY1JoA<})PTM6G#y*xSc03E7P^i>{>_Zq> z$bg|9cCQ`ceLlaqc#6-UE2dbM;kV696*5a`2%hw2nuiS^Uh&pg=Wa_DD~qdpt)BMgal+zNd-40VK06$3$_cd^WFSV?Dd zy^S|AXbUk05O->TXj9zB{L-*_m_=vepRY&)6UFZm92f3_!es-kb6cM)om!g|O-q*M zZj`>0nh!3Qzog$g%r2NuvHsdHj~?-X4Qk8(X^fnNok5!*flaw!YQ)Yw0QDe%dctb4 zUSbe|sCzWi2IN~CmjlPVa=t0_M9T&KG|GT=8MghB_q8HMYd#r>PZ)y-7W5C zU#t@&iaGBH<3neTK-9YM&a|vJ{K5dyapCt_iR+D=mdGfB#AWXKTMmQlY+9t>(&AbN z+K2Jf*{5R#~V?eO8UcCT)NYxr+h_dT%Vi&?s{sN<)D3K z^~AcPw1(ajU#u{0y)~6eQGdMnXx*cW5zYqJQ=c%aXS!7o(>AOxytcZZyp+_?Fu(OT z;*A?spsJEgBY`;cb9<9VS?8ccj?nC5_+W-+4;8s)eS&gUZY~KO^pp@jbsdLMwU{?l z|5zGHXOHugN0pP#QD7qB8)k^b*x)V)FQuGSO5t)FA`F#&RE(w9A(9q984Igr?QRt&CcJc3+bDkfe;c3R4?_Xo z`!DE&Tc#S5QDOCL$;f34Y=362na{^Nol%%xpGwM$pm=n&QC=@wpZ&S z*}FG}IHamX9>RsEO#hMmmz7}vcbdkcSx#B1_n%qN=Lh1s#T6%Lf0H%kNj8EkV7VxN zM;zxY+YF)(y=pw^W}vdTpT=9J)^>(vp+#&MNtK4u``KFt=F7`D*l`jTTKvw1(Hp{x zcQfOpS)}YlNdW_e74fA%o6L{WM_l4kZhr@R5iTqpDkIQHm4=FOEsR@Y41CP@!Ze1_ zi$+~>wvB!LqAZueN`L^865Y3l|x@HY!#4AVJrn{da z3ENCj15`?*mP1(WkA*~5V@Qna-s70#GtU|YC-Um|8MuJZI>t?d;y2qc zzEx;Dl&MAY_YI(A7nN7v6h`?u3xApWssN~C^oOO`2y(#nn>;yxH5fnFrT8GO0i8PC0NE&<&YVJ^1rLnQFN6O)y#R;FZNz+_=JAaLBBn8dSk z2D#ReKR+oR!Cza`CM(L)bTN5$$%H%wwgzsn_ombD-0?6Sa8Ab%xrjisvDq4kq{Oz( zy+cFYAPXh=x2~Q55=xl{7~p~H6Tb!z^HhrJWqF*fTfo-S|ES(T_|{y4o8f-{5iy0^ zFfk!Cw9r6!whrXA4E_w)VI|EN*0+A%?4JbXS56w_>)TtQVvp?WHEPz_laIRNd_m5c zAxNpTiSl(n@EZpx;S$*XG`)VZwP5$3;-T$QA8dp*0w47e!*KW0R_5mlfj@Cs<{TG3Gh_CN-@=i@ zU$k1Jp_MyNg&l6y@ZLso6ZN`&6V|yv8W!ENAwdYLqz48| zIP|LdB!5wy-)MVA<;|?%31521b{1~U>;|cL>89-$q8LzWV8zCl%uUg%vlqS`*u5u3 zGe+OX+@UzMWy5Dz+qy8D$g_ALbevL2Y%rZEO_U7|Rmb#Vz$e8C8N*)uouMPwlT;;h4Yj8jnMa|@V1~P~TR6wYgvruzI}|#<%OoS0 zd@JF6F!Y^gef{f2fzwQ`lbNN$lcI`@#r6U(!YM`U1D#0NI#9dXS$4* zv97nXl87bQgCF8*+ma|rEraJ>x85Vf3{1Wh(0JjT- z^BgWhL@y62yli|bma?0}?T1m%-2^T_L+*{#$p>$Zx zjv^MK(S}0L4t;GF+|zbvGWW{mChI+SvVoVYAK$oQ&4NnVwW-wKU~JtM1_w*TfpDzN@7-r@D)5w16@p7wITTfYS8%8PG6(>mmfwI z4dYp`*dyJbk4i#zwOMG}@~;vGlVpCDgA%bk=M`TJpjdhVm>^Dux798~r+{ zsx^p|#B&x1*q0V?+>8gCai@|gquyLp)!8VZ6m1~vCv*{g@gk;2HOmUUJRS&TL@yDHS zZ{$-@HS745+z@hszx&Lkh7yhHBHnh+oTtVC!pG)$f$K6ThK0W}PYp{(m3Kia2vt%6 zCb&9&W`mDUn?E+F|8+eRZ87QM^%m=^{P3dCAbt86-!$UoBPL9;bfh`>_y~cf7=6** z!s)=IUWs@*W!uMU(EF}(Q27#gN*tF*OdcXcz(4{u5246K>FhUp!f00QzCmjY(FR_Kf5~GL_ILz;#(W?e)(7*}@k^GA= zj#EcVLcUuv2iXBdX&&d}503j^w^iaKV&D4(oNnDc>xCKn)LUt8hkXg2&1h%aOr_y3 zZyp2Q;K!Hb?M@av!|E!!q2UcvpP6})u^B66#eog^o=hx!Afdr@@-J9)w>=7k$4b*l z$s;&3#M!C4{fc2ya2XUYug9cSY6aNrKajyx9&8r&!XS#3GG)@E*$V94=@K27Y%I-A zDW)bkc^r%&kdFNl>(yn2BcH_QxlsN-2ehzjua@*8-Uy3`dik*G{>f%T1l{eu3mj>w*kE8jxezxVJt7FT!=` zY!?;}oJ3>v&1OoY0Oe8;c=d}=#$X+MyZ^f8|NULl97Zghz?WJ;R8&P|y?b!K7~ils z(_znXm#FFO?$qF9buHo>6p&(t##@kFS%^9SiSSl^d2lckEcE)k)e%~mQR~BRQriF_ zpnL-`|2+CO$5EW-^;D={dDg?G@pq<~DFTkZ--aHciayid*IjBIArrBzOAr;TUF3j+ z<==B+C)&d}p$+V>H6R?c_eI=e*j>wepsHeP+TBj1Ji zk#FG9)Bt4_aP>l(ONPQ2nR5#puwRK)wtNqG+y?WGQVZ&Z^W>b7qn6M>7Wa4mgU)Vt1-@U_SzaR>mP1XK#VC!<`@{DI$J$_%% zm}#t*E$b-1{`Scjn{m)Plhj(&5zBFgn!hAsIlbieUr1I~O37LXh7Hg)v=l)SbRwV3 zioUj?Vee%3@T1OeKdISMqyj%lkMu98Jf5F3dyF%>=SB;EOF zsUg_fTANQY&6%m`rXC8PVXI_JSuD5iM{yw7QaUn4qs~XTkVUX^c-|2ZYBj5~esVP@ z0%;uP-NmQeaK%Yd&j1Q7G0m+hAoQ9xZ%5v0v1h85JOVk@v>D6I%`MYxdI`*AUx)t= z=^e}|-xx5GudTFs?54P?)4c@5M&q+b7UQXWMJKJWfv7%N5$>;WQd`TQrkX{8&t_V@ z^?*McW+xGe{RylU(n{?RvL|*rti0D2PtpZda{DA-z&Z?3+|BW<8m~2ew;x)Q;87L6 zQ?#2sk2Ii$=Hol=B9Hc^HH#y<>^x*0XM&kY+r4FoJtSrr+`n&LE2BxVBdnVHpXLom z3{&Z!cl>8z^tovf>4fp0q;5>ZZ<|ee0$}w(tQdf%<(9nM=!(qGkMDZe-i>8OZE!q@ zJ91I`_AQd%2rxfj<>Ugs*cAVe;shvchTS;|^gn;1{>_xpElgSkSYU#Y8jsC1=a368 z{~rOc{=W=M6nHOEv9Un{Iy%U7SvDWAc6t>`Npr%==q}XK8{XY5GIn}Yzhu@i7gP`7 zciGw9AUbtg3dsxeEku{6u5kH-=3D<;aG)s@jn9!y&&r*WN&DtUymZ0rtwDFb zS(F;|ET-0s_)EK@0NHBL5gN}vh1Aq_X77R(R|$mldkh%6K)D_G9L3Xzn|c-twO=?yrodBW7k0^`4lih~ZEs{TZr$Lfeb0YWIxB`9lQ0gH-H>k|U~ z8Ja^&Jl<1cAJMr#UYTW4mtr_`9iMAd@PYwi^u)ypy0wdQ3 zkU(7NxG&rZo7*nXA_#<&%ge5MZ}>(qHkX^kf^*=$rm$m)O=bB4Mz-cA!*NWa1D3#m z2jUK7w=E{DG;G>rDW-39#>+iZwjxgX>H}v?l&_3@h6fk&;HskPGte(hafOP-21%}O z{NsK(?peBz7_*S}t9Im9u6xsd_E>p!C$MS|hW66zxAicu{3WJ~VZTf4&)iw$v4qf+ z3N;K1`TbG`-8rA28GAv#x>)hJT)o5((tB6LkB1F7I!=#o&TdOO6lJu1&xI(+*!EDG z!RooTXoD8@!6>qTQwJ?9wmH#Yy`yC>xh^ofBeQ{gb+rp2G`cc+$w#iA{_iZn!U5E4 zNJg;6rUAl$NC?(Wsr0)(!B;uD_G9{W zCg^x_Et7JRHGQZ7n;CaWstuh2+00agC<|Uk$6f#X2SOM{^h0~#ax08I+dGsCMuyV< zW&3d%P$Abj?j?%=X}rUK6ZzwRN{~CKrcA3t-t*6cpZr1HDl- ztM0Ke7EY`2N5b8{6Ab-REIGifaR?+WRU%(r22w}*5htJR24ZL#&G!2nMiDfAPEMQ* zX~zC}GP}4IJKJ)S9fh>a8&Dc5k_Ta?$R%90?u^Q8z^r!$@pkL0MJEt&Paz1!WqrkR zlTT(|==JpQJfDs;M+5D`c0-ejM-s}VsEPItegpi&%8j3}eZ1-Ql9=_Aw0p;Ej$SOd zY(M;)EJ#nY_geAknXEm^!oG+geqsg9jstZt^t$ga)WK%ByMl3Jz4?x)t!f&zYvd0{ z{@Gy+08s~~t!P)7a!jFG3jVmx19&WgnY#_&|3}+bfJM1&?+c=oK_g0uq?CYk8gvOr!wk|5(j5v) zNq0*(QqrN)-Cfd+bi;p-$?@F#yXTyH{}1!{3^?xO&>+(G()9QL7Q|NPAG<8W0{H z-~J)rM(!z!mpo$fd0=`v0=QBhXrXS7oDv|Brvjy=DRU$&Y0UILVgY5;J(w>qkHxRe z>u1_;VPz>7PbDy$ogB($j4Lg1IG5-PT3Z4R7VuC$fC~$q$QcFT`_mS^&w7JgyXCD} zU(}mXVu%u3$&1P=f&9oJqeH7FrRDAIgtUCn=mcnfG-_PlE)1+IeMP@jwGJvXSxH*c zDgc!A52KsM#7Ew@<>htV)qPlMhK+`)p_r6Rk`)I+|Jk+LEajON0Y&6`ey!#-s%1ic zTL8;O#rec^d!b8mfLE;s4PdD}7%SF(Hrq_rU& z7+*xTLxI%apgEiyZ9$QTRr5I`=*KKjcKVQBKoyFFSng_~b$=sM{|$EXXLdnW#ZSqr zo*Ww0%5&{Wx=A|9bN7?iu5KLFi%##TbXu64}1cyvu_D@HHSD8Ne4csTMX|NR*)^b z-eyApvere0fVmUN*zw|MWrYQs_P7F|FY7&}u2U*Cqnas=0!(BaRx2qTo&cF!5TN4p zm700n#QcPYkDrlCF@qlxX_xZ2oSfb?nsX1ze1?_Vk#`YsJbsBFV>!%9AqQ%g!OqLH zyFQVfgB^-pehNC&)0SgG?n1ADc7gQv55;K*#Z|hXb?9)HASp-G_yK@CY>yRhcyXy9 zz#lDFbIW;Zie|#TKs#IZ*6gTJ#B>L}?Llo)M$f?xJ>rzj!_P)`TNINMA#WwzMG~&) zTq5$>sU4Z7q2keUiL!%l(^vi|C<)i$OrVdf^16#O)tBDZGhmd+Bd~zW&+M)We1M`Tc_K(= z?~;|K+h3|C=3{?%fO;@5s<~!SrQNu`_1cwt*(hntKACW9e_)FO3o9`vnWWkl{>eOP z+I)y!o1FqyP_p9^yWvc+&KU$ZD*hbO?+r1avzoji~bg$RTk# zVCNPn`$5z1^|$P?#Z3eKhyIrL{J1u;92_0{9MpZw{MybQ=7XjL*}eD3OI06aawGIc zOVT5!H>ASC$lcuC6D8wLmrfHBb42@>Zg>@NOfkND9AJBTJXA&oF!Y{mm0WyUe#hcS zoII%P_4Q8xt08%9BkHa*8>mIe(7I~S9E{9nyGh!r&7^rERiI5Pd)Bga{PykVrP9aH z#$mz$Xo0pngz1jcCy=Ch(BC6e0;zn3=0r#st8e^KJfX0(ljU@y;c?}nSE|mq^~|cO zK(SP?pH}u$fD+y3kiW%_GKTb>Z4EbuZ=C)Ee{Ljp;J2dX$HWODh~=bGn1c?QrK+P^ zMDO(CY5H)JpFTHAh_RcV+Ed#Ml<#|VL*dNK%l~;>)gl^$QLS_-*~F`tTf`{!cNHe* zO^@j+j`!5?C4FDXqRTfoGEWIt7kB6 zH8YmmlbZzDgd_K}bFN_-ulj-;crIO5!OmF5k{`3D_fvIA zDqzV~Xs0EoragQ+W|jyu+s~3sMqX4qrYXHd~((NhLTFR5P3M3RLRT$WWguVdb?K-UK=y9G(u>4iBazG%1o2Gl$mcG^lJ{_L|fV`%7<-gw- z?e^GQ!Ib3Equm61slWq~HURfeG}oFZHi*&#G9==X-AYWjADE1W?{oJjL4ix2@bm<@ zG*h5Wt6!tc*}3Hw2@_*G;Hr*$g`eB*bpjYm-!=zx zVG=J0qgcox0G}#pxwWyeb1|RkozEq`763H#5(H2d&CGg{Vfb^Pf1(}NvMG37&^2<$Ks3ZZ}c3dWZ>sMo;eFn0D?)O`U~% zvBF3mv;^`m9=^UWlGv5`?xL=q?rXr$wMDsu+zbgZF&dywegJ^+3(ZU|o?@j)#3c1- zU70a4@m-pR>POS{O$#jr*S!GVnXD1J`-JluHsUn}tj0GLj4U$`yeL1IjJ)sjpnJc( zPNdd$$EzyeT=+q~rZldXoSwHJM2yk#FwS2iQ98N#&{Y(aBM`4*%+4WWI`yNX({|(7 z&$q8Cr7QVf)CdSBOSXAWNpc1%x(6%m!ytyh+qrt*Xc#^qAf!;*!yyjbnT#&UcdE#v z8CYaxOA`fjHhoU3vX>D54BWkx!5V=)u3>|ctsW=+0GRNk;&xf#tC^;E5jDMDwXt|~&edXjyGEoK0B zS7e;^YCbH1*US62Tx~n2E9hgF*$GkZICp_>3q%MK;F&q#iKORW>ZRO#OfGAS7pf7hi0RiMXX6v{4XQ;@eLZ!JUF%b8Rg50 zbUGx)+V^;9#DJ?w^5fK7uX411(ivwuT;*7B*e3iMQ0HnsuWAZ*rmKuUp2vh* ztyFp(FFhw7-^`+d;&s_eExru6cXDb~MS6#1UX0}9IKivU%u~2-nK!!LQGqTsrM!uw zse2bA+H+%QUL35pavPu@I7kMoa*7s75~O)je6D=PLjqm$Zf*14)KR+vYx(*9;`2oJ zu-{N3iLps3tbE<+LZU$BYl=7r!raQ;Yq5^IS|=n< z5ffntbpUxntc`i?N%t<1@X5Dl67`uzHXlG(2>lf%8aZ z{c8QK*VBMlDE+iLee?7>h#&XQq8OwLBI46Xp@mWosqyWbod$Yx461%Sfkd`lKYctf8-K^-w7zl6e&5sRY&ki+5o-h$hPAAhTAM_!n;Jy`@ zoJ=>eS<5xJXZa~rDEQJ1Y5$q0IfowCrPs!TqBfSYqVCy;Z53Ou^A=&7&{fx#iN>j#Ly_Zsb(ut3+Ys85Rw;QTor0b(^KU1*oaF>*JeAX=_o+NwC_e~AQtCJ7 zyaeE-q_MVhTBDW&jyHH5l45L+H*~-$ykoe|)2C5{u!&krNKraP>is@_QInK40P!{i zuym>7RFu-eyHnHC*-Zd7vmsAo>DgQ6lo{^)cv=<~0r7{1A4{H3-?LbFa=h+*_DEX# z18N(JKEJLmCE(w=d=O-YORGdWZhlkhKC>H#^%_EiKf_1<=lmbUu$}=*%;j;1 zEpdl~*)WNQ#VW@mi=n`3(&0$uGagtk06K;Lf^*n5bHIJl^w}o#O4#dhG4J*=t-8m)0}<_ z7Z;IT0Mw*U7fJ$rA##FnebsfsXjfFarolee6+>7G%8(^Z)|0S1K=EAg0$BY$#777 zjKy6_uhlrhX0*(3j7fg{!jH#J`NTMzH6gkT{OVIYZpYtb)EIVJhfB14Uzj(H9n_Ln z9b>?#%zX$?u2r4vnjas`ldk#_p3tR;A6?8RmeDkPvpqlUHzAY7GvJ+R*kgE-U_oni zxZUGtn7Depv2ll;0sluVAoZT@rpXPaId03?(+dEgE!((7C+^KFN-A87_V?6oU+bD+ z;IFRU4_!k1U1(j;PNPzD?44`JE6WWNw+=unc@8ujxNf9?Vu4?ET3l|xx%weaeiFw} zpCr~ba}_r9+%(q3CHn8)Oj^s*7QvGizS-z$;j2$ zl2G;&^oNYbT0TwO!EW9Lnz5F!So!$#l$-X|>9+B_+X^aW7Orwa^l4nB^igaZ6ppP; zH>`ra{dm)Rl8l6fFLiuQNl8&ZIqb7r0pI}W2@0%w-E1^U_Yo;1=bRr*0yrOwQqj;> z>#Y37&)5{!pFdbm#1BQMcItf=y^(+HI9jkK%JqH&akOyrj)lS(_@xe8ER*$cFPX(A z_fUIsCIX_h$XBlli1-Ki0pD6t+|=8v6Y4eH#a(Lc8aMI|F;|4Ws0w{>-ygqfbY9E5 znO@v^g)Ewl-ngL1Sg)ZV$~iCdHp^#IuLep5SKTGehb6t0` zw||Bcg=T{S-Uz78&0jv{JrnDjX1e`3ShWJ(KacKUCY))ieRfV$XR5Qc_}y$zmBNv} zqaj|;b-onG&ic}g!EKn;9o<8~e$=UkexYQ zEi+5Qvr}!&r07n@x*~w3|L!ao_ua;cRYum$!Ogl0(Ss>Y{$9$RTBP(7uo!SXpwMXe z=w5J}{#x+FGB_+to!05oBykvJQ*Jcx(FnBXNw|BVQPCr->wAwpGhp?kqC4 z1t(oYv+PjyK$gfF7<8IK5Bwb(m0^m8>6xbLrRiQZ>CO$ZOU(=vMmTuLxGiZMOkola z780}3Hf)q1S20})fbzU*WX%<<{X~m?sKTmg$gyW4E@6k#>}1FLaV_1u5%-9Ass|D6 zz(U?_d;*D(x7s6mB}<*T1+AUS?!s)t=$lT#$UEC_$-f*euxJWwM1*~K4Kg1;yqw_6 zaC`dLuEv%DjA`tx%r8U}y&kr-HnMpGoBW_n{`8uyYZX#UJyCua@WjFpZZ~HG=%`); z&TE9N&LEi>Om?8P`i8PxJJZ%8s0jgNp+n$3624T}nsuCT&M2t}LSFU9IZMdxVKQ0T z100$0@k6rI?l{$Qt0hZeV9WiJ90cZ+v`*Ukv!jSNjg7m{7@Om+Dw@L}pV~OMxw)Yb z&v0I6-!j~&V)HwTT*3ay4pOa^tM5lTL0}$MZ35~~IW)th zrFa0!Ip^i;gz!UovYH)QpdaGe(53eV1I9O448!H-f@2i41huGSs-QYrShe*LhR>dZ z2OlU0?e~?S_@oyl5a1ubSP7>oCu#||2!&#qUk&A7&7@R^mB0w7O2^#`cJjN$+OEjl z&xTh34&;Dr@J4)n7vGq~Gx`IQvPE*{QdW6_Eq1ws1Utiql~-2}LiP6 ziA5Ryb=Gm4#3EWyrTbQHGjv_4zI}7HrD(J?Yj98@Wc`u~ki61u=pm`sg7o|C=j-E@ z^LJk?CX4YoWok|xPJTF1il|`gYdRpNqr;()z?W}u{M_(XqjYof5tbHcJ}Xt4Gu=3|Goq9WOZ@j;u97srb&#T858_&n+F(TcBbiaNTG zgv3J;k^HiHx?`mR1^3ugQ#-qx7#LH1l^=(;Xg=_a1oR9kpoh3S?`l44_NB@4YPCRuwRD6uz z`AU=fM?}E2l(HuV8*o@}MChf$m|`}{abZnwX#68H3vG>T=1ZdyF+xc(9B!D(rvjS+ zJB@Vi?AR2tkN~L!f8dT|p^v$t)9V0$HQ%q3_xQOICu%}@uU&y)Hmw=0LW92}^; z30b}+^_0hQsn%9?zR@Hg+`LaleUW{rkptbm)kxI8bmh9{nu=$Z8(Ugbin?q-V(cSL z!r@n0vONKKnX!^hFJ7vLz(Pl^nb8-@CBLCzJS(VA8H(yFN?~?>Q$9$ zRV~oQo~io+YQf=PoFm64drw!Zj@BqE4(3|I*`7y7&P!6IAsIM`i4`{QaYLL>F8SbX z@l+j~(IokREH6RALS*usVD?9rSWss*c`zU6R#7od&JniGLi0ydUh>D_y}! z06SrlM&kiiDD6ZYr_~N4-*-T8q$lY6-KN%gN<#!dIDzD7&icGgCn-#)k!)8mofq8u zL zH{jl0lKL3TQ(IdbJ#cY9;r*vH(WM}M2L3h*Ge%)@k=SmDXK{9fCPZOox|^5)w_Q5O zJ@cN^u}!0IEvMB=BfW)hxSJb-NB$ZJ=jQmKN=1gPuiy`DlC?Z1<=g|1fl}1AE!HuT zX-Mc@U0n(#rf=7J0M9z=s8IgiS>WZs^sy4jtZC^&5fJq$7J8laAqPc&lme3-R; z0{X1&m|S0Ag&XA$+G>p@Z1Zw(pyXfcrOQKtYYK{1*+-S52UGt_;xNn~&AHBzm!D4v z9cF(_@!+2#SViOp5`bP1Q-1h7sZ9Eg7#CG^mZQh;xI%nbRt;~CuF+R~Nh(SL3HasW zH%%k&YJG^#3Yf)@F?4POFk>WNp%QJrKeg~JrSj`(YxpT@Q2y<6p5u@P!Q1bz$wP#} zN8bV})Ug+^U32$y{Rv?9@$0KZ8maMCvCsSA!#5=@Ls}z0<#zrJOol@6Ibv#y-TdK9 z^F*X%F;=t2`6j<^@?F0r;IV#Q`TQGGNBEVu%joeW@0J}?SI~dBfB+uz%Y0nWZO&sl zXjN?v8R&6jWBqNcMMt^JyY5;ul zi_#}$HSPB}e_a2EqY0DWztIpC7a$VVNl}_Hx_1^oHV@Y+424R&ODSRz9^v5&4vM%m z;V&U?V?4AH=Sf-Sve1mCo(LR$z^mm>Q@aL^H=@4G{lE$O~{# zV)^Xs%8V0%e+=3uYWf6f#>TSB4e?Pq_`DHv1mIVg{)Q|%J9JQf!~(!wzs)*qj!k9J z;z_>9ZZH=F{hIP8_ux{#DW-}lLVL7uKLJbB7{E)_<)y_aQ9=T_|4%(V%GLvHI2JBH z*t1kFAq?{Wkn#@|{3^-;iW}`$3F13ckm zN4@|VcyMC_Fa%Re_=J69O1KsV;)WNQdl4t!MfOeWGmzhH{d$z2YlRO!N799x^baG0 z4Yx!DX1MPjB*a(;<^B14@^@STDsupcl9$tY1%TczkwA_Yp{N)oW#yzABMqtek2h3> z8VAEP_>N(W%D=xRIS>Xh|B_TxvJujd9EzN_R#?H3=o5CSBtJh^R~7h;4T@~ei{6ay zD1}4m*AZ_rnieq-Ym69L4cviR!H4GMUMv)<2|<4Mr?vZaSpWLzfdF6&`1W^In!$)C zF{MM|`1Kf*UI>E}-yeX|$7RLKZmL!)nmy=6R#xlB#}NYX@-%RGBaHQ0SuNq!>YN{6 z!B#YQ@!duUfBYwLfRX&NS?3=Q_WrA$@I3%xP(j11|9+jHi>W3=(1n)VRUNajv5`+* zoeNO&J~A}Kx{6madc!}DRzs!UF7G;;_V>m6?b@y_K^K(25qA7GHUDlD?|=Z_VY_GA z4if#PS^H_ee_!TcrGI>$5;{Y#!;9AI_bg~x4uMFsoo*AwM$n*Z&azt-qq9Q$_( zewRN3Q0tN#?QI5 z&u?EpC*^Nz^<*II9{hFf&EGB0kGc9^n3He1Tr>Lstdg*Xy&5^#O1=xX>-|5~+;7WA zB?Yzi~Z z%}&FGY7M6b{EPqYCx`d)KEW2}+>Ob9`BP%paKRdZjv>Sd=b!A9KX=A+Z~kmtbMWDf z0aFe9RXo!xu%GCL8A{3n4U1ytcOo9{0pgllcdYCADmOeu`l~& zwy!@qlMItqvkNo@$-k5z5NN&o39b8MGQTSS-?l~H*Vv>8P@5Hz3G$C+>$>#Zd!+}QN}>< z5V`X8efWdTK>tk$2E--I|1z>`C-RLK40UQUS4*ahP1i$bj79TjNN^RqS?8NMhePD@ zNi$b`PbWD+sSjZBJJzNV9cJf5B6hg||A&8Ewk~;~!6BgWT{!jsk! za3qiaFk;vPq$2lZi3**O1|bD#b-oc~kM zXc(@gNatqKwBiEX-WHrx#oWP{3G)D9dh7`oXs%|2GTiytF%2z)Fpx zZg5&s;br>~3-~tf!paMRe_n;>zx-2bzptu4tdhd;3!XK!0!@79#8FsyVfviVziZBa zGFCtDM+|hg-`oH3WPgE{=I_cINzP~T```Ym3DbQ5#G#B9^cGE57_R=|Kb$Mr15rbmb77Jn3?t1k&(^gkVSXFXWI#SCn(AMstH{HGUzP5*gKbKS)O&tKVbp+OM# zmmOc(I7l2d_(_$eif&QXI z;O_E~n7YKWrHt{w<75Ar@Sk(~H@uef_Hq&n$Th}{-18#rit)?{H!%MPm!4DrS0%95 zN51m&V)2XcE5C{3uRlAOYOvLFNVowPCfZy)>9?sbx}@-Rx&0Id%q2Phls~QX`L+6O zXo#r(NGp>;qPpUk@vP^J*0)>y`SUT>Yk`6}n-s#^wJ0dZE1>tE%ui#@sUk4FfM+s`GV#OLyO5{FL{>8A)>`?1svfByEv zH2keSi_b0lHn4*RX^iCGXTPwFPg674Vs%*j)irLJcHT4f(#aRXjOw)ua=dhiUxlfA z5$@VQw>^a(FlX^&BXKraO$ib)F(aT7JorR(N(?BAh$48MV-1J$6+>y2Ntm?7!gxr0+{vb;du_RvqDL0i;NQ00~ddxMD-1^z*Bc86T!s!ltGR zdibSA)~nsgnZg$UTvs%EaWztFakBV7dFgx`UXTFK1|Tt+N-N^ zeMH8_HW)hx`q%+as`X48>lv(!C}&K9>tKgpBvF z>%{Us$Th2gDiX_s7U(Gns6(g%(2m&>ahbVQ!=x7hM0W3xz@r8%HnSu#u9iFvLC1%r z6xM+)bVs3&1q2R9*CgMhr^l==K7AVP@5dWUcj=O?w2BHPKfn7f%iXVQ*XeFe;Xk$B z7hG8|!LZxX*q<1|^`rB}=hOhE6w1if;cr9XZ&?qr{DI9%9{ zO3uL{(9W)tARkqo4eB{W9zRav&NdoBnW^AiiQ$MpK@dVP=C%Lo z(b*saaxh`1qmc=lBFEh!9hfFGPSuH443AL*h8?qmS@}k=2uWklPQJY6^)D>E-$Cx2 z#)Qa=&gOJnvNBdK$aA#vC{t`NQr61Kmz0dmzUZJwaR; z4PNZ0TlvGP1Hb!cr0ZX7bYDP>-SuQa^By)hJq*GhVoiWrm0J&j98Ahrt=O$r!pG(^ z6NF=UPZlM$c9!~fwZ%g#K-=SrWj4a4OQME`S*jmDek?5gkT7ktCR1ME`?-{xjjeVq zFgluMx*?8i0CGed!S*~scf!L1>1e+UDkPLPR{d7Q!lH1%iBA6H%z=!Itl0zo&P=31 zPJoHy@=ZK;v!n%;n>VZPn2Z$aJ&K{Y2nu!9gGue^db+!lbvnl77Vj)B;$h$^ZY7Ps zy4-2-K~*0L65;9HmG;*STE_#!!!=)Z85CEY0>tCF;S7`V-a~pC8Wm}oo+SId*zQzG z-bG*_NJ*a(rOFL(WX5hG6`Yt0J@62;Iy%U+?}nQLv?j0T&qH%or0C`Mq zxo&IhEe(z7Bv33_&&*Wd4G^-tJ#_r)`(D0$L`IezG&5sST)$+Z-e;b2`sUV$^00Q% z`NaCMY=cah)OHLEjJYw-Z!XArg=D)5XZ1JYC?|^Xr($=VW0}oi<`ADe9@!}jVwW!e zwEAS#gg?lk^5!jtgRQu;sUpkc{SC;nPBa4yILt8YHKARjL3wUSy39oWQ7*Lly+l~t z+420Y6@jwf+a^2b<7pzz*=7?>z$&=eDrcjd>)2MpH;PH8F$OLM73%sJ8I@5Dt$qHG$|-XWj>mlu_!o#Ivo_Hv{l~V zvT>^vZ>U&EXc>*FfLM;t$FO#_9xY{78xc%PN8I~NQaTfuISX3vgG`){tk|c0o?RvP z(AKit;OLKk@Uo2oP~%a84%;vWwzFelvk#ZTyvTPV-}I~3dPqTzESG}yi@d-d_RzR= zmaufY+h8WzoQ^Sf$?mgzn9yp{( ppJAG(*tOjBt22>!WWu_@8`(&JzjGkWd#mh%G3k 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

;I_xA7D>*53lbw_Lan16&Dybit#l{ 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%0D9vcXX0XBrBtEyX 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 literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37365ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Python +__pycache__/ +*.py[cod] +.venv/ + +# Node +frontend/node_modules/ +node_modules/ + +# IDE/Misc +.vscode/ +.idea/ +/tmp/ +/dist/ +/build/ +*.egg-info/ +.claude \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..39cc1e0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.5.0] - 2026-03-23 + +### Changed +- **CLI Architecture Simplification**: The CLI has been decoupled from the FastAPI server and now operates as a stateless direct-HAL hardware administration tool. It no longer relies on the server for data reading or offset writing. +- **Consistent Privileges**: All CLI commands that interact with the hardware now explicitly require root privileges. +- **Background Daemon**: Added a new lightweight Unix socket daemon (`nvcurve daemon`) to handle auto-loading profiles on boot and managing the server's lifecycle. +- **Global Arguments**: Fixed global argument parsing in the CLI to allow global flags (like `--gpu`) to be placed after subcommands. + +## [0.4.0] - 2026-03-17 + +### Added +- **Multi-GPU Support** *(experimental — untested on real multi-GPU hardware)*: The server now manages all detected NVIDIA GPUs simultaneously under a single process. Each GPU gets its own isolated state (write lock, monitor clients, curve clients, active profile). REST endpoints and WebSocket subscriptions accept a `gpu_index` parameter. A new `/api/gpus` endpoint enumerates all GPUs with name, index, UUID, and PCI bus ID. +- **GPU Selector in Web UI**: When multiple GPUs are present, the status bar shows a dropdown to switch the active GPU. Switching resets all pending edits, selection state, and live monitoring for the new target. +- **Default Profile**: Added the ability to designate a profile as the default — it is applied automatically on server startup. + - CLI: `nvcurve profile default ` to set, `nvcurve profile default --clear` to unset. + - The web UI shows a filled star on the default profile and lets you toggle it with a single click. + - The setting persists to `/etc/nvcurve/config.json` (created by `service install`). If the config file is absent, the setting is in-memory for the current session only. +- **Curve Flattening**: Selecting two or more points and clicking "Flatten to [anchor]" in the toolbar sets each selected point to a different offset, such that all land on the same effective frequency as the anchor point. The anchor is the last explicitly clicked point (highlighted with an amber halo on the graph); bulk selections (box, range, Ctrl+A) preserve the existing anchor. +- **Server-Optional Profile Commands**: `profile apply`, `profile list`, and `profile default` no longer require the server to be running. When the server is absent they fall back to direct HAL calls or config-file writes, escalating to root via `sudo` automatically — the same pattern already used by snapshot commands. +- **Automated Setup Check**: `nvcurve setup` runs a consolidated 4-step hardware compatibility check: NvAPI function probe → V/F curve baseline read → non-destructive write-verify → automatic state restore. The write-verify defaults to the last GPU-domain point (safe on all GPU generations); override with `--point` and `--delta`. Pass `--full-mask` if writes fail on older GPUs such as Pascal. + +### Changed +- **Profile CLI syntax**: Profile commands now take the profile name as a positional argument instead of `--name` (e.g. `nvcurve profile apply balanced` instead of `nvcurve profile apply --name balanced`). +- **Improved Diagnostics**: The `read --diag` engine now reports driver version, VRAM totals, power limits, raw clock offsets, memory offset ranges, and raw boost masks in addition to the NvAPI function probe. +- **Offline Snapshots**: `snapshot save`, `restore`, and `list` bypass the server and fall back to direct HAL operations when the daemon is not running. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..897e4f7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 ekojsalim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d48cbd --- /dev/null +++ b/README.md @@ -0,0 +1,240 @@ +

+ NVCurve Logo +

NVCurve

+

Linux NVIDIA GPU V/F Curve Editor & OC Tool

+
+ +--- + +NVCurve brings MSI Afterburner-style per-point voltage-frequency curve control to Linux. It calls undocumented NvAPI functions directly via `libnvidia-api.so`, giving you precise frequency offsets control. A Python CLI handles scripting and headless use; a React web UI provides interactive curve editing, profile management, and live hardware monitoring. + +> [!WARNING] +> **This tool is experimental.** It has been verified on a range of NVIDIA GPUs and recent drivers, but compatibility is not guaranteed across all hardware and driver versions. The underlying NvAPI functions are undocumented and may change or disappear between driver releases. +> +> Read operations are generally safe. Write operations alter GPU operational parameters. **Follow the first-time setup steps below before applying any changes**, and proceed with caution. + +![NVCurve curve editor](.github/screenshots/curve-editor.png) + +## Features + +- **Per-Point Curve Editing**: Independently adjust the frequency offset for any voltage point on the GPU's V/F curve. +- **Curve Flattening**: Select multiple points and flatten them to a common frequency in one click using anchor-point targeting. +- **Live Monitoring**: Tracks GPU voltage, clock speed, temperature, and power draw via NvAPI and NVML. +- **Profile Management**: Save, apply, and set a default (auto-apply on startup) profile. Profile commands work with or without the server running. +- **Multi-GPU Support** *(experimental)*: Manage multiple NVIDIA GPUs simultaneously from a single server instance; switch between GPUs in the web UI. Untested on real multi-GPU hardware — feedback welcome. +- **Modern Web UI**: Interactive curve graph, point table, real-time monitoring dashboard, and GPU selector, all in the browser. + +## Prerequisites + +- **OS**: Linux +- **GPU**: NVIDIA GPU (tested on RTX 5090, Blackwell architecture) +- **Python**: 3.12+ +- **Privileges**: Root access is required for hardware interactions. The CLI will prompt for elevated privileges via `sudo` when needed. + +## Installation + +We recommend installing with [uv](https://docs.astral.sh/uv/getting-started/installation/): + +```bash +uv tool install nvcurve +``` + +*For frontend development, use `pnpm install` and `pnpm run dev` in the `frontend/` directory.* + +## Getting started + +### Step 1 — Verify hardware compatibility + +Before touching anything, confirm NvAPI is working correctly on your GPU and driver. **Do not skip this on an untested configuration.** + +```bash +nvcurve setup +``` + +This runs four checks in sequence: +1. **NvAPI function probe** — verifies all required functions resolve in the driver +2. **Curve read** — reads and displays your current V/F curve as a baseline +3. **Write-verify** — writes `+5 MHz` to the last GPU-domain point, reads it back, and confirms the driver accepted it and no other points were unexpectedly modified +4. **Restore** — automatically restores the snapshot from before the test write + +Only continue if `setup` reports **Compatible**. + +> Override the test point or delta with `nvcurve setup --point N --delta 5`. Pass `--full-mask` if writes fail on older GPUs (Pascal and earlier). + +### Step 2 — Launch the web UI + +With compatibility confirmed, the web UI is the primary interface for curve editing, monitoring, and profile management: + +```bash +nvcurve +``` + +This starts the backend server and opens the UI in your browser at `http://localhost:8042`. + +![NVCurve web UI dashboard](.github/screenshots/dashboard.png) + +To run the server in the background: + +```bash +nvcurve serve start --detach +nvcurve serve status +nvcurve serve stop +``` + +### Step 3 — (Optional) Install as a systemd service + +Register the nvcurve daemon as a systemd service so it starts automatically on boot: + +```bash +nvcurve service install +``` + +The daemon handles auto-loading GPU profiles on boot. The web server is separate and starts on demand (`nvcurve serve start`). To also have the web server start automatically on boot, pass `--auto-serve`: + +```bash +nvcurve service install --auto-serve +# optionally: --host 0.0.0.0 --port 8042 +``` + +This enables and starts the service immediately. Manage it with: + +```bash +nvcurve service start +nvcurve service stop +nvcurve service restart +nvcurve service status # shows daemon state + web server config +nvcurve service uninstall +``` + +To change the web server auto-start setting or address after install, use `service configure` — it updates the config and restarts the daemon in one step: + +```bash +sudo nvcurve service configure --auto-serve +sudo nvcurve service configure --no-auto-serve +sudo nvcurve service configure --host 0.0.0.0 --port 8042 +``` + +## Upgrading + +```bash +uv tool upgrade nvcurve +``` + +If you are running nvcurve as a systemd service, restart it afterwards to pick up the new version: + +```bash +nvcurve service restart +``` + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md) for a complete history of changes. + +## Web UI controls + +### Curve editor + +| Action | Result | +|---|---| +| Click point | Select (clears other selections) | +| Shift+click point | Add/remove point from selection | +| Ctrl/Cmd+A | Select all active points | +| Escape | Clear selection | +| Drag point | Stage a frequency offset edit | +| Drag point (multi-selection) | Moves all selected points together | +| Enter (single point selected) | Open inline input to type an exact offset | +| ↑ / ↓ arrow keys | Nudge selected point(s) ±1 MHz | +| Ctrl/Cmd + ↑ / ↓ | Nudge ±10 MHz | +| Tab / Shift+Tab | Step through points one by one | +| Shift+drag on background | Box select; draw a rubber-band rectangle | +| Drag on background | Pan the X axis | +| Alt+scroll | Zoom X axis around the cursor | + +### Point table + +| Action | Result | +|---|---| +| Click row | Select point | +| Ctrl/Cmd+click | Toggle point in/out of selection | +| Shift+click | Range select from last selected to clicked | +| Drag across rows | Range select by dragging | +| Select Before / Select After | Expand selection to all points before or after the currently selected one (appears when exactly one point is selected) | + +### Toolbar + +The **Global Offset** slider appears when all active points share a uniform delta. Dragging it stages that offset across every point simultaneously; equivalent to `nvcurve write --global` but interactive. + +## CLI reference + +The CLI is suited for scripting, headless systems, or quick one-off operations. All write commands support `--dry-run` to preview changes without applying them. + +### Reading + +```bash +nvcurve read # Condensed V/F curve +nvcurve read --full # All points +nvcurve read --json # JSON output +``` + +### Writing + +> [!NOTE] +> If you use LACT or similar tools, disable them first. Concurrent writes will overwrite each other. + +```bash +# Preview first +nvcurve write --global --delta 50 --dry-run +nvcurve write --point 80 --delta 100 --dry-run + +# Apply +nvcurve write --global --delta 50 +nvcurve write --point 80 --delta 100 +nvcurve write --range 70-90 --delta 75 +nvcurve write --reset # Reset all offsets to 0 +``` + +Snapshots are saved automatically before each write. Manual snapshot management: + +```bash +nvcurve snapshot save +nvcurve snapshot list +nvcurve snapshot restore +``` + +### Profiles + +Profile commands work whether or not the server is running. Apply and default operations escalate to root automatically when bypassing the server. + +```bash +nvcurve profile save balanced # Save current curve as a profile +nvcurve profile apply balanced # Apply a saved profile +nvcurve profile list # List profiles with active/default markers +nvcurve profile default balanced # Set profile to apply on server startup +nvcurve profile default --clear # Clear the startup default +nvcurve --gpu 1 profile default perf # Set default for a specific GPU (multi-GPU) +``` + +> [!NOTE] +> If you use LACT or similar tools and have a default profile set, disable the `lactd` service first — it applies its own curve on startup and will overwrite NVCurve's auto-applied profile: +> ```bash +> sudo systemctl disable --now lactd +> ``` + +### Diagnostics + +```bash +nvcurve read --diag # Probe all NvAPI functions +nvcurve inspect --point 80 # Raw buffer fields for a point +nvcurve inspect --range 78-82 +``` + +## Architecture + +NVCurve has two components: + +- **Python backend (`nvcurve/`)**: Talks directly to `libnvidia-api.so` (via ctypes) and `libnvidia-ml.so` to read and write hardware state. Exposes everything through a FastAPI REST + WebSocket server, keeping privileged operations isolated from the browser. +- **React frontend (`frontend/`)**: Runs in the browser and communicates with the backend over HTTP and WebSockets. Handles curve visualization, point editing, live monitoring, and profile management. + +## Disclaimer + +This software is provided as-is. The NvAPI functions it relies on are undocumented, unsupported officially by NVIDIA, and may change or break without notice between driver releases. NVCurve has been verified on a range of NVIDIA GPUs and recent drivers; behaviour on other hardware or driver versions is not guaranteed. Write operations alter GPU state. The authors accept no responsibility for hardware damage, system instability, or data loss resulting from the use of this software. diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..4dcad1f --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is currently not compatible with SWC. See [this issue](https://github.com/vitejs/vite-plugin-react/issues/428) for tracking the progress. + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..5e6b472 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..deeef78 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + NVCurve + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..e0c1c79 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,4877 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "d3": "^7.9.0", + "lucide-react": "^0.577.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "sonner": "^2.0.7", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@tailwindcss/vite": "^4.2.1", + "@types/d3": "^7.4.3", + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react-swc": "^4.2.2", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "tailwindcss": "^4.2.1", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.33.tgz", + "integrity": "sha512-jOlwnFV2xhuuZeAUILGFULeR6vDPfijEJ57evfocwznQldLU3w2cZ9bSDryY9ip+AsM3r1NJKzf47V2NXebkeQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.26" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.33", + "@swc/core-darwin-x64": "1.15.33", + "@swc/core-linux-arm-gnueabihf": "1.15.33", + "@swc/core-linux-arm64-gnu": "1.15.33", + "@swc/core-linux-arm64-musl": "1.15.33", + "@swc/core-linux-ppc64-gnu": "1.15.33", + "@swc/core-linux-s390x-gnu": "1.15.33", + "@swc/core-linux-x64-gnu": "1.15.33", + "@swc/core-linux-x64-musl": "1.15.33", + "@swc/core-win32-arm64-msvc": "1.15.33", + "@swc/core-win32-ia32-msvc": "1.15.33", + "@swc/core-win32-x64-msvc": "1.15.33" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.33.tgz", + "integrity": "sha512-N+L0uXhuO7FIfzqwgxmzv0zIpV0qEp8wPX3QQs2p4atjMoywup2JTeDlXPw+z9pWJGCae3JjM+tZ6myclI+2gA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.33.tgz", + "integrity": "sha512-/Il4QHSOhV4FekbsDtkrNmKbsX26oSysvgrRswa/RYOHXAkwXDbB4jaeKq6PsJLSPkzJ2KzQ061gtBnk0vNHfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.33.tgz", + "integrity": "sha512-C64hBnBxq4viOPQ8hlx+2lJ23bzZBGnjw7ryALmS+0Q3zHmwO8lw1/DArLENw4Q18/0w5wdEO1k3m1wWNtKGqQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.33.tgz", + "integrity": "sha512-TRJfnJbX3jqpxRDRoieMzRiCBS5jOmXNb3iQXmcgjFEHKLnAgK1RZRU8Cq1MsPqO4jAJp/ld1G4O3fXuxv85uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.33.tgz", + "integrity": "sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.33.tgz", + "integrity": "sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.33.tgz", + "integrity": "sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.33.tgz", + "integrity": "sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.33.tgz", + "integrity": "sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.33.tgz", + "integrity": "sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.33.tgz", + "integrity": "sha512-gtyvzSNR8DHKfFEA2uqb8Ld1myqi6uEg2jyeUq3ikn5ytYs7H8RpZYC8mdy4NXr8hfcdJfCLXPlYaqqfBXpoEQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.33", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.33.tgz", + "integrity": "sha512-d6fRqQSkJI+kmMEBWaDQ7TMl8+YjLYbwRUPZQ9DY0ORBJeTzOrG0twvfvlZ2xgw6jA0ScQKgfBm4vHLSLl5Hqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", + "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.12.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.3.tgz", + "integrity": "sha512-8oljBDGun9cIsZRJR6fkihn0TSXJI0UDOOhncYaERq6M0JMDoPLxyscwruJcb4GKS6dvK/d8xebYBg27h/duaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz", + "integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/type-utils": "8.59.2", + "@typescript-eslint/utils": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.2.tgz", + "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz", + "integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.2", + "@typescript-eslint/types": "^8.59.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz", + "integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz", + "integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz", + "integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz", + "integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz", + "integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.2", + "@typescript-eslint/tsconfig-utils": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.2.tgz", + "integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz", + "integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.3.0.tgz", + "integrity": "sha512-mOkXCII839dHyAt/gpoSlm28JIVDwhZ6tnG6wJxUy2bmOx7UaPjvOyIDf3SFv5s7Eo7HVaq6kRcu6YMEzt5Z7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.7", + "@swc/core": "^1.15.11" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.28", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.28.tgz", + "integrity": "sha512-Ic44hnOtFIgravCunj1ifSoQPSUrkNiJuH9Mf6jr2jjoA74icqV8wU0KuadXeOR8zuIJMOoTv0GuQjZ9ZYNMeA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.353", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz", + "integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.2.tgz", + "integrity": "sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.577.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz", + "integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.2.tgz", + "integrity": "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.2", + "@typescript-eslint/parser": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", + "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zustand": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.13.tgz", + "integrity": "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..9c9191a --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,37 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "d3": "^7.9.0", + "lucide-react": "^0.577.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "sonner": "^2.0.7", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@tailwindcss/vite": "^4.2.1", + "@types/d3": "^7.4.3", + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react-swc": "^4.2.2", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "tailwindcss": "^4.2.1", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1" + } +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..441648b --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,3069 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + d3: + specifier: ^7.9.0 + version: 7.9.0 + lucide-react: + specifier: ^0.577.0 + version: 0.577.0(react@19.2.4) + react: + specifier: ^19.2.0 + version: 19.2.4 + react-dom: + specifier: ^19.2.0 + version: 19.2.4(react@19.2.4) + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + zustand: + specifier: ^5.0.11 + version: 5.0.11(@types/react@19.2.14)(react@19.2.4) + devDependencies: + '@eslint/js': + specifier: ^9.39.1 + version: 9.39.4 + '@tailwindcss/vite': + specifier: ^4.2.1 + version: 4.2.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)) + '@types/d3': + specifier: ^7.4.3 + version: 7.4.3 + '@types/node': + specifier: ^24.10.1 + version: 24.12.0 + '@types/react': + specifier: ^19.2.7 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react-swc': + specifier: ^4.2.2 + version: 4.2.3(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)) + eslint: + specifier: ^9.39.1 + version: 9.39.4(jiti@2.6.1) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-react-refresh: + specifier: ^0.4.24 + version: 0.4.26(eslint@9.39.4(jiti@2.6.1)) + globals: + specifier: ^16.5.0 + version: 16.5.0 + tailwindcss: + specifier: ^4.2.1 + version: 4.2.1 + typescript: + specifier: ~5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.48.0 + version: 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1) + +packages: + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@rolldown/pluginutils@1.0.0-rc.2': + resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==} + + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} + cpu: [x64] + os: [win32] + + '@swc/core-darwin-arm64@1.15.18': + resolution: {integrity: sha512-+mIv7uBuSaywN3C9LNuWaX1jJJ3SKfiJuE6Lr3bd+/1Iv8oMU7oLBjYMluX1UrEPzwN2qCdY6Io0yVicABoCwQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.15.18': + resolution: {integrity: sha512-wZle0eaQhnzxWX5V/2kEOI6Z9vl/lTFEC6V4EWcn+5pDjhemCpQv9e/TDJ0GIoiClX8EDWRvuZwh+Z3dhL1NAg==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.15.18': + resolution: {integrity: sha512-ao61HGXVqrJFHAcPtF4/DegmwEkVCo4HApnotLU8ognfmU8x589z7+tcf3hU+qBiU1WOXV5fQX6W9Nzs6hjxDw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.15.18': + resolution: {integrity: sha512-3xnctOBLIq3kj8PxOCgPrGjBLP/kNOddr6f5gukYt/1IZxsITQaU9TDyjeX6jG+FiCIHjCuWuffsyQDL5Ew1bg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-arm64-musl@1.15.18': + resolution: {integrity: sha512-0a+Lix+FSSHBSBOA0XznCcHo5/1nA6oLLjcnocvzXeqtdjnPb+SvchItHI+lfeiuj1sClYPDvPMLSLyXFaiIKw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@swc/core-linux-x64-gnu@1.15.18': + resolution: {integrity: sha512-wG9J8vReUlpaHz4KOD/5UE1AUgirimU4UFT9oZmupUDEofxJKYb1mTA/DrMj0s78bkBiNI+7Fo2EgPuvOJfuAA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-x64-musl@1.15.18': + resolution: {integrity: sha512-4nwbVvCphKzicwNWRmvD5iBaZj8JYsRGa4xOxJmOyHlMDpsvvJ2OR2cODlvWyGFH6BYL1MfIAK3qph3hp0Az6g==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@swc/core-win32-arm64-msvc@1.15.18': + resolution: {integrity: sha512-zk0RYO+LjiBCat2RTMHzAWaMky0cra9loH4oRrLKLLNuL+jarxKLFDA8xTZWEkCPLjUTwlRN7d28eDLLMgtUcQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.15.18': + resolution: {integrity: sha512-yVuTrZ0RccD5+PEkpcLOBAuPbYBXS6rslENvIXfvJGXSdX5QGi1ehC4BjAMl5FkKLiam4kJECUI0l7Hq7T1vwg==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.15.18': + resolution: {integrity: sha512-7NRmE4hmUQNCbYU3Hn9Tz57mK9Qq4c97ZS+YlamlK6qG9Fb5g/BB3gPDe0iLlJkns/sYv2VWSkm8c3NmbEGjbg==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.15.18': + resolution: {integrity: sha512-z87aF9GphWp//fnkRsqvtY+inMVPgYW3zSlXH1kJFvRT5H/wiAn+G32qW5l3oEk63KSF1x3Ov0BfHCObAmT8RA==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/types@0.1.25': + resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} + + '@tailwindcss/node@4.2.1': + resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==} + + '@tailwindcss/oxide-android-arm64@4.2.1': + resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.1': + resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.1': + resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.1': + resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': + resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': + resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.1': + resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.1': + resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.2.1': + resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.2.1': + resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': + resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.1': + resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.1': + resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.2.1': + resolution: {integrity: sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.12.0': + resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@typescript-eslint/eslint-plugin@8.57.0': + resolution: {integrity: sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.57.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.57.0': + resolution: {integrity: sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.57.0': + resolution: {integrity: sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.57.0': + resolution: {integrity: sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.57.0': + resolution: {integrity: sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.57.0': + resolution: {integrity: sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.57.0': + resolution: {integrity: sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.57.0': + resolution: {integrity: sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.57.0': + resolution: {integrity: sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.57.0': + resolution: {integrity: sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react-swc@4.2.3': + resolution: {integrity: sha512-QIluDil2prhY1gdA3GGwxZzTAmLdi8cQ2CcuMW4PB/Wu4e/1pzqrwhYWVd09LInCRlDUidQjd0B70QWbjWtLxA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4 || ^5 || ^6 || ^7 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} + hasBin: true + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001777: + resolution: {integrity: sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + electron-to-chromium@1.5.307: + resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==} + + enhanced-resolve@5.20.0: + resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==} + engines: {node: '>=10.13.0'} + + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.26: + resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.1: + resolution: {integrity: sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.31.1: + resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.31.1: + resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.31.1: + resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.31.1: + resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.31.1: + resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.31.1: + resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.31.1: + resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.31.1: + resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.31.1: + resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.31.1: + resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.31.1: + resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.31.1: + resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.577.0: + resolution: {integrity: sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.36: + resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tailwindcss@4.2.1: + resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.57.0: + resolution: {integrity: sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + + zustand@5.0.11: + resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@esbuild/aix-ppc64@0.27.3': + optional: true + + '@esbuild/android-arm64@0.27.3': + optional: true + + '@esbuild/android-arm@0.27.3': + optional: true + + '@esbuild/android-x64@0.27.3': + optional: true + + '@esbuild/darwin-arm64@0.27.3': + optional: true + + '@esbuild/darwin-x64@0.27.3': + optional: true + + '@esbuild/freebsd-arm64@0.27.3': + optional: true + + '@esbuild/freebsd-x64@0.27.3': + optional: true + + '@esbuild/linux-arm64@0.27.3': + optional: true + + '@esbuild/linux-arm@0.27.3': + optional: true + + '@esbuild/linux-ia32@0.27.3': + optional: true + + '@esbuild/linux-loong64@0.27.3': + optional: true + + '@esbuild/linux-mips64el@0.27.3': + optional: true + + '@esbuild/linux-ppc64@0.27.3': + optional: true + + '@esbuild/linux-riscv64@0.27.3': + optional: true + + '@esbuild/linux-s390x@0.27.3': + optional: true + + '@esbuild/linux-x64@0.27.3': + optional: true + + '@esbuild/netbsd-arm64@0.27.3': + optional: true + + '@esbuild/netbsd-x64@0.27.3': + optional: true + + '@esbuild/openbsd-arm64@0.27.3': + optional: true + + '@esbuild/openbsd-x64@0.27.3': + optional: true + + '@esbuild/openharmony-arm64@0.27.3': + optional: true + + '@esbuild/sunos-x64@0.27.3': + optional: true + + '@esbuild/win32-arm64@0.27.3': + optional: true + + '@esbuild/win32-ia32@0.27.3': + optional: true + + '@esbuild/win32-x64@0.27.3': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': + dependencies: + eslint: 9.39.4(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@rolldown/pluginutils@1.0.0-rc.2': {} + + '@rollup/rollup-android-arm-eabi@4.59.0': + optional: true + + '@rollup/rollup-android-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-x64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.59.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.59.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.59.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.59.0': + optional: true + + '@swc/core-darwin-arm64@1.15.18': + optional: true + + '@swc/core-darwin-x64@1.15.18': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.15.18': + optional: true + + '@swc/core-linux-arm64-gnu@1.15.18': + optional: true + + '@swc/core-linux-arm64-musl@1.15.18': + optional: true + + '@swc/core-linux-x64-gnu@1.15.18': + optional: true + + '@swc/core-linux-x64-musl@1.15.18': + optional: true + + '@swc/core-win32-arm64-msvc@1.15.18': + optional: true + + '@swc/core-win32-ia32-msvc@1.15.18': + optional: true + + '@swc/core-win32-x64-msvc@1.15.18': + optional: true + + '@swc/core@1.15.18': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.25 + optionalDependencies: + '@swc/core-darwin-arm64': 1.15.18 + '@swc/core-darwin-x64': 1.15.18 + '@swc/core-linux-arm-gnueabihf': 1.15.18 + '@swc/core-linux-arm64-gnu': 1.15.18 + '@swc/core-linux-arm64-musl': 1.15.18 + '@swc/core-linux-x64-gnu': 1.15.18 + '@swc/core-linux-x64-musl': 1.15.18 + '@swc/core-win32-arm64-msvc': 1.15.18 + '@swc/core-win32-ia32-msvc': 1.15.18 + '@swc/core-win32-x64-msvc': 1.15.18 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.25': + dependencies: + '@swc/counter': 0.1.3 + + '@tailwindcss/node@4.2.1': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.20.0 + jiti: 2.6.1 + lightningcss: 1.31.1 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.1 + + '@tailwindcss/oxide-android-arm64@4.2.1': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.1': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.1': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.1': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.1': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.1': + optional: true + + '@tailwindcss/oxide@4.2.1': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.1 + '@tailwindcss/oxide-darwin-arm64': 4.2.1 + '@tailwindcss/oxide-darwin-x64': 4.2.1 + '@tailwindcss/oxide-freebsd-x64': 4.2.1 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.1 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.1 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.1 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.1 + '@tailwindcss/oxide-linux-x64-musl': 4.2.1 + '@tailwindcss/oxide-wasm32-wasi': 4.2.1 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.1 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.1 + + '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1))': + dependencies: + '@tailwindcss/node': 4.2.1 + '@tailwindcss/oxide': 4.2.1 + tailwindcss: 4.2.1 + vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1) + + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/estree@1.0.8': {} + + '@types/geojson@7946.0.16': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@24.12.0': + dependencies: + undici-types: 7.16.0 + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@typescript-eslint/eslint-plugin@8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.57.0 + '@typescript-eslint/type-utils': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.57.0 + eslint: 9.39.4(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.57.0 + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.57.0 + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.57.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3) + '@typescript-eslint/types': 8.57.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.57.0': + dependencies: + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/visitor-keys': 8.57.0 + + '@typescript-eslint/tsconfig-utils@8.57.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.57.0': {} + + '@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.57.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3) + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/visitor-keys': 8.57.0 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.57.0 + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.57.0': + dependencies: + '@typescript-eslint/types': 8.57.0 + eslint-visitor-keys: 5.0.1 + + '@vitejs/plugin-react-swc@4.2.3(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1))': + dependencies: + '@rolldown/pluginutils': 1.0.0-rc.2 + '@swc/core': 1.15.18 + vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1) + transitivePeerDependencies: + - '@swc/helpers' + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.4: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001777 + electron-to-chromium: 1.5.307 + node-releases: 2.0.36 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001777: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@7.2.0: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + + detect-libc@2.1.2: {} + + electron-to-chromium@1.5.307: {} + + enhanced-resolve@5.20.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@7.0.1(eslint@9.39.4(jiti@2.6.1)): + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + eslint: 9.39.4(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.4.26(eslint@9.39.4(jiti@2.6.1)): + dependencies: + eslint: 9.39.4(jiti@2.6.1) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.1 + keyv: 4.5.4 + + flatted@3.4.1: {} + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@16.5.0: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + internmap@2.0.3: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.31.1: + optional: true + + lightningcss-darwin-arm64@1.31.1: + optional: true + + lightningcss-darwin-x64@1.31.1: + optional: true + + lightningcss-freebsd-x64@1.31.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.31.1: + optional: true + + lightningcss-linux-arm64-gnu@1.31.1: + optional: true + + lightningcss-linux-arm64-musl@1.31.1: + optional: true + + lightningcss-linux-x64-gnu@1.31.1: + optional: true + + lightningcss-linux-x64-musl@1.31.1: + optional: true + + lightningcss-win32-arm64-msvc@1.31.1: + optional: true + + lightningcss-win32-x64-msvc@1.31.1: + optional: true + + lightningcss@1.31.1: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.31.1 + lightningcss-darwin-arm64: 1.31.1 + lightningcss-darwin-x64: 1.31.1 + lightningcss-freebsd-x64: 1.31.1 + lightningcss-linux-arm-gnueabihf: 1.31.1 + lightningcss-linux-arm64-gnu: 1.31.1 + lightningcss-linux-arm64-musl: 1.31.1 + lightningcss-linux-x64-gnu: 1.31.1 + lightningcss-linux-x64-musl: 1.31.1 + lightningcss-win32-arm64-msvc: 1.31.1 + lightningcss-win32-x64-msvc: 1.31.1 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.577.0(react@19.2.4): + dependencies: + react: 19.2.4 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.4 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.36: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react@19.2.4: {} + + resolve-from@4.0.0: {} + + robust-predicates@3.0.2: {} + + rollup@4.59.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 + fsevents: 2.3.3 + + rw@1.3.3: {} + + safer-buffer@2.1.2: {} + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.7.4: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + sonner@2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + source-map-js@1.2.1: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tailwindcss@4.2.1: {} + + tapable@2.3.0: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1): + dependencies: + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.8 + rollup: 4.59.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.12.0 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.31.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} + + zustand@5.0.11(@types/react@19.2.14)(react@19.2.4): + optionalDependencies: + '@types/react': 19.2.14 + react: 19.2.4 diff --git a/frontend/pnpm-workspace.yaml b/frontend/pnpm-workspace.yaml new file mode 100644 index 0000000..7c32629 --- /dev/null +++ b/frontend/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +onlyBuiltDependencies: + - '@swc/core' + - esbuild diff --git a/frontend/public/logo.svg b/frontend/public/logo.svg new file mode 100644 index 0000000..e0eccff --- /dev/null +++ b/frontend/public/logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..034a20a --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,159 @@ +import { useGpu } from './hooks/useGpu'; +import { useCurve } from './hooks/useCurve'; +import { useMonitor } from './hooks/useMonitor'; +import { StatusBar } from './components/Monitor/StatusBar'; +import { LiveMonitor } from './components/Monitor/LiveMonitor'; +import { CurveEditor } from './components/CurveEditor/CurveEditor'; +import { PointTable } from './components/PointTable/PointTable'; +import { PerformancePanel } from './components/Limits/PerformancePanel'; +import { PerformanceMonitor } from './components/Monitor/PerformanceMonitor'; +import { ProfilePanel } from './components/Profiles/ProfilePanel'; +import { api } from './api/client'; +import { useCurveStore } from './store/curveStore'; +import { Toaster } from 'sonner'; +import { Loader, ChevronDown } from 'lucide-react'; +import { useState, useRef, useEffect } from 'react'; + +export default function App() { + const gpuInfo = useGpu(); + const { curve, wsStatus: curveWsStatus } = useCurve(); + const { monitor, monitorHistory, wsStatus: monitorWsStatus } = useMonitor(); + const { setCurve, activeProfile, setActiveProfile, selectedGpuIndex } = useCurveStore(); + + const [activeTab, setActiveTab] = useState<'curve' | 'performance'>('curve'); + const [activeDomain, setActiveDomain] = useState<'gpu' | 'memory'>('gpu'); + const [isProfileOpen, setIsProfileOpen] = useState(false); + const profileRef = useRef(null); + + const currentVoltageMv = monitor?.voltage_mv ?? null; + + function handleRefresh() { + api.curve(selectedGpuIndex).then(setCurve).catch(console.error); + } + + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (profileRef.current && !profileRef.current.contains(event.target as Node)) { + setIsProfileOpen(false); + } + } + document.addEventListener("mousedown", handleClickOutside); + + // Fetch initial active profile + api.profiles(selectedGpuIndex).then(data => setActiveProfile(data.active)).catch(console.error); + + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [selectedGpuIndex, setActiveProfile]); + + // Worst connection status wins + const wsStatus = + monitorWsStatus === 'disconnected' || curveWsStatus === 'disconnected' + ? 'disconnected' + : monitorWsStatus === 'connecting' || curveWsStatus === 'connecting' + ? 'connecting' + : 'connected'; + + return ( +
+ + + + {wsStatus !== 'connected' && ( +
+ + {wsStatus === 'connecting' ? 'Reconnecting to backend...' : 'Connection lost. Retrying...'} +
+ )} + +
+ {/* Tab Header and Profile Selector */} +
+
+ + +
+
+ + {isProfileOpen && ( +
+ +
+ )} +
+
+ + {/* Main content area */} +
+ {activeTab === 'curve' ? ( + <> +
+
+ {curve ? ( + + ) : ( +
+ Loading curve… +
+ )} +
+ +
+ +
+
+ + {curve && ( +
+ p.domain === activeDomain)} + currentVoltageMv={activeDomain === 'gpu' ? currentVoltageMv : null} + readOnly={activeDomain === 'memory'} + /> +
+ )} + + ) : ( +
+
+ +
+
+ +
+
+ )} +
+
+
+ ); +} diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts new file mode 100644 index 0000000..b63f020 --- /dev/null +++ b/frontend/src/api/client.ts @@ -0,0 +1,79 @@ +import type { CurveState, GpuInfo, MonitoringSample, SnapshotInfo, LimitsState, ProfileData } from '../types'; + +async function get(path: string, gpuIndex?: number): Promise { + const url = gpuIndex !== undefined ? `/api${path}?gpu_index=${gpuIndex}` : `/api${path}`; + const res = await fetch(url); + if (!res.ok) { + const text = await res.text().catch(() => res.statusText); + throw new Error(`GET ${url}: ${res.status} — ${text}`); + } + return res.json() as Promise; +} + +async function post(path: string, body?: unknown, gpuIndex?: number): Promise { + const url = gpuIndex !== undefined ? `/api${path}?gpu_index=${gpuIndex}` : `/api${path}`; + const res = await fetch(url, { + method: 'POST', + headers: body != null ? { 'Content-Type': 'application/json' } : {}, + body: body != null ? JSON.stringify(body) : undefined, + }); + if (!res.ok) { + const text = await res.text().catch(() => res.statusText); + throw new Error(`POST ${url}: ${res.status} — ${text}`); + } + // Some endpoints return no body (204) + const ct = res.headers.get('content-type') ?? ''; + if (ct.includes('application/json')) return res.json() as Promise; + return undefined as unknown as T; +} + +async function del(path: string, gpuIndex?: number): Promise { + const url = gpuIndex !== undefined ? `/api${path}?gpu_index=${gpuIndex}` : `/api${path}`; + const res = await fetch(url, { method: 'DELETE' }); + if (!res.ok) { + const text = await res.text().catch(() => res.statusText); + throw new Error(`DELETE ${url}: ${res.status} — ${text}`); + } + const ct = res.headers.get('content-type') ?? ''; + if (ct.includes('application/json')) return res.json() as Promise; + return undefined as unknown as T; +} + +export const api = { + gpus: () => get('/gpus'), + gpu: (gpuIndex: number) => get('/gpu', gpuIndex), + curve: (gpuIndex: number) => get('/curve', gpuIndex), + ranges: (gpuIndex: number) => get>('/ranges', gpuIndex), + voltage: (gpuIndex: number) => get<{ voltage_uv: number; voltage_mv: number }>('/voltage', gpuIndex), + monitor: (gpuIndex: number) => get('/monitor', gpuIndex), + snapshots: (gpuIndex: number) => get('/snapshots', gpuIndex), + + /** Write per-point frequency deltas. deltas: { pointIndex: deltaKhz } */ + writeDeltas: (deltas: Record, gpuIndex: number) => + post<{ ok: boolean; freq_warnings?: string[] }>('/curve/write', { deltas }, gpuIndex), + + /** Reset all frequency deltas to zero. */ + resetCurve: (gpuIndex: number) => post('/curve/reset', undefined, gpuIndex), + + /** Get performance limits mapping */ + limits: (gpuIndex: number) => get('/limits', gpuIndex), + + /** Set performance limits */ + updateLimits: (updates: Partial, gpuIndex: number) => + post('/limits', updates, gpuIndex), + + /** Reset power limit and memory offset to hardware defaults */ + resetLimits: (gpuIndex: number) => post('/limits/reset', undefined, gpuIndex), + + /** Profile Management */ + profiles: (gpuIndex: number) => get<{ profiles: ProfileData[], active: string | null, auto_load: string | null }>('/profiles', gpuIndex), + saveProfile: (name: string, gpuIndex: number) => post<{ ok: boolean; filepath: string }>('/profiles', { name }, gpuIndex), + applyProfile: (name: string, gpuIndex: number) => post(`/profiles/${encodeURIComponent(name)}/apply`, undefined, gpuIndex), + deleteProfile: (name: string) => del(`/profiles/${encodeURIComponent(name)}`), + renameProfile: (oldName: string, newName: string) => + post(`/profiles/${encodeURIComponent(oldName)}/rename`, { new_name: newName }), + + /** Server config */ + setAutoLoadProfile: (name: string | null, gpuIndex: number) => + post<{ ok: boolean; auto_load_profile: string | null }>('/config', { auto_load_profile: name, gpu_index: gpuIndex }), +}; diff --git a/frontend/src/api/websocket.ts b/frontend/src/api/websocket.ts new file mode 100644 index 0000000..d9f4f82 --- /dev/null +++ b/frontend/src/api/websocket.ts @@ -0,0 +1,61 @@ +type MessageHandler = (data: T) => void; +type StatusHandler = (status: 'connecting' | 'connected' | 'disconnected') => void; + +export interface WsHandle { + close: () => void; +} + +export function createWsConnection( + path: string, + onMessage: MessageHandler, + onStatus: StatusHandler, + gpuIndex: number = 0, +): WsHandle { + let ws: WebSocket | null = null; + let delay = 1000; + let stopped = false; + let timer: ReturnType | null = null; + + function connect() { + if (stopped) return; + const protocol = location.protocol === 'https:' ? 'wss' : 'ws'; + const url = `${protocol}://${location.host}${path}`; + onStatus('connecting'); + ws = new WebSocket(url); + + ws.onopen = () => { + delay = 1000; + onStatus('connected'); + ws?.send(JSON.stringify({ action: 'subscribe', gpu_index: gpuIndex })); + }; + + ws.onmessage = (e) => { + try { + onMessage(JSON.parse(e.data as string) as T); + } catch { + // ignore malformed + } + }; + + ws.onclose = () => { + if (stopped) return; + onStatus('disconnected'); + timer = setTimeout(connect, delay); + delay = Math.min(delay * 2, 8000); + }; + + ws.onerror = () => { + ws?.close(); + }; + } + + connect(); + + return { + close() { + stopped = true; + if (timer) clearTimeout(timer); + ws?.close(); + }, + }; +} diff --git a/frontend/src/components/CurveEditor/CurveEditor.tsx b/frontend/src/components/CurveEditor/CurveEditor.tsx new file mode 100644 index 0000000..ca0e854 --- /dev/null +++ b/frontend/src/components/CurveEditor/CurveEditor.tsx @@ -0,0 +1,975 @@ +import { useState, useCallback, useRef, useEffect, useMemo } from 'react'; +import { Check, X, RotateCcw, RefreshCw } from 'lucide-react'; +import { scaleLinear } from 'd3'; +import type { VFPoint, CurveState } from '../../types'; +import { CurveTooltip } from './CurveTooltip'; +import { CurveToolbar } from './CurveToolbar'; +import { ConfirmDialog } from '../common/ConfirmDialog'; +import { voltExtent, refBaseMhz, detectClampedPoints } from '../../utils/curveHelpers'; +import { useCurveStore } from '../../store/curveStore'; + +interface Props { + curve: CurveState; + activeDomain: 'gpu' | 'memory'; + onDomainChange: (d: 'gpu' | 'memory') => void; + currentVoltageMv: number | null; + currentClockMhz: number | null; + onRefresh: () => void; +} + +const MARGIN = { top: 16, right: 24, bottom: 48, left: 64 }; + +/** Drag-tooltip info (shown while actively dragging a point) */ +interface DragInfo { + pointIndex: number; + /** Current pending delta kHz while dragging */ + currentDeltaKhz: number; + /** SVG inner coords of the point, for tooltip positioning */ + cx: number; + cy: number; +} + +/** Box-select rubber-band rect (SVG inner coords) */ +interface BoxRect { x0: number; y0: number; x1: number; y1: number } + +export function CurveEditor({ curve, activeDomain, onDomainChange, currentVoltageMv, currentClockMhz, onRefresh }: Props) { + const readOnly = activeDomain === 'memory'; + const [hoveredPoint, setHoveredPoint] = useState(null); + const [dragInfo, setDragInfo] = useState(null); + const [boxRect, setBoxRect] = useState(null); + const [inlineInput, setInlineInput] = useState<{ pointIndex: number; value: string } | null>(null); + const svgRef = useRef(null); + const containerRef = useRef(null); + const [svgCursor, setSvgCursor] = useState('default'); + + // ─── Dynamic SVG dimensions ────────────────────────────────────────────── + const [svgW, setSvgW] = useState(680); + const svgWRef = useRef(680); + svgWRef.current = svgW; + const INNER_W = svgW - MARGIN.left - MARGIN.right; + + const [svgH, setSvgH] = useState(460); + const svgHRef = useRef(460); + svgHRef.current = svgH; + const INNER_H = svgH - MARGIN.top - MARGIN.bottom; + + useEffect(() => { + const el = containerRef.current; + if (!el) return; + const ro = new ResizeObserver(entries => { + const { width, height } = entries[0].contentRect; + const w = Math.round(width); + const h = Math.min(Math.round(height), 500, Math.round(window.innerHeight * 0.58)); + setSvgW(prev => prev === w ? prev : Math.max(300, w)); + setSvgH(prev => prev === h ? prev : Math.max(200, h)); + }); + ro.observe(el); + return () => ro.disconnect(); + }, []); + + // Store + const { + pendingDeltas, + selectedPoints, + anchorPoint, + stageEdit, + stageMultiEdit, + selectPoint, + selectRange, + clearSelection, + effectiveMhz, + applyEdits, + discardEdits, + resetAllDeltas, + hasNegativeFreqWarning, + } = useCurveStore(); + + const [busy, setBusy] = useState(false); + const [actionError, setActionError] = useState(null); + const [dialog, setDialog] = useState<'apply' | 'reset' | null>(null); + + async function handleApplyConfirm() { + setDialog(null); + setBusy(true); + setActionError(null); + try { + await applyEdits(onRefresh); + } catch (e) { + setActionError(e instanceof Error ? e.message : String(e)); + } finally { + setBusy(false); + } + } + + async function handleResetConfirm() { + setDialog(null); + setBusy(true); + setActionError(null); + try { + await resetAllDeltas(onRefresh); + } catch (e) { + setActionError(e instanceof Error ? e.message : String(e)); + } finally { + setBusy(false); + } + } + + const pts = useMemo( + () => curve.points.filter(p => p.domain === activeDomain), + [curve.points, activeDomain], + ); + const clampedPoints = useMemo(() => detectClampedPoints(pts), [pts]); + + // Clear selection and pending when switching domains + useEffect(() => { + clearSelection(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [activeDomain]); + + // ─── X-axis zoom / viewport ────────────────────────────────────────────── + // Use a slightly larger padding to space things out better + const [xViewport, setXViewport] = useState<[number, number]>(() => voltExtent(pts, 40)); + + // Reset viewport when the set of points changes (curve refresh) + const ptsKey = pts.map((p) => p.index).join(','); + useEffect(() => { + setXViewport(voltExtent(pts, 40)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ptsKey]); + + // Compute Y domain accounting for pending changes + const allEffective = pts.map((p) => effectiveMhz(p)); + const allBase = pts.map((p) => refBaseMhz(p)); + const freqMin = Math.min(...allBase, ...allEffective) - 50; + const freqMax = Math.max(...allBase, ...allEffective) + 50; + + const xScale = scaleLinear().domain(xViewport).range([0, INNER_W]); + const yScale = scaleLinear().domain([freqMin, freqMax]).range([INNER_H, 0]); + + // Keep scales in a ref so event handler closures always see the latest + const scalesRef = useRef({ xScale, yScale, xViewport, innerW: INNER_W }); + scalesRef.current = { xScale, yScale, xViewport, innerW: INNER_W }; + + const xTicks = xScale.ticks(8); + const yTicks = yScale.ticks(6); + + // ─── Zoom factor (for toolbar slider) ──────────────────────────────────── + const [fullExtentMin, fullExtentMax] = voltExtent(pts); + const fullWidth = (fullExtentMax - fullExtentMin) + 80; + const currentWidth = xViewport[1] - xViewport[0]; + const zoomFactor = Math.max(1, Math.min(10, fullWidth / currentWidth)); + + function handleZoomChange(factor: number) { + const pad = 40; + const newWidth = Math.max(20, fullWidth / factor); + const center = (xViewport[0] + xViewport[1]) / 2; + let newMin = center - newWidth / 2; + let newMax = center + newWidth / 2; + if (newMin < fullExtentMin - pad) { newMin = fullExtentMin - pad; newMax = newMin + newWidth; } + if (newMax > fullExtentMax + pad) { newMax = fullExtentMax + pad; newMin = newMax - newWidth; } + setXViewport([newMin, newMax]); + } + + // Polylines + const visiblePts = pts.filter((p) => { + const v = p.volt_mv; + return v >= xViewport[0] && v <= xViewport[1]; + }); + + const baseLine = visiblePts + .map((p) => `${xScale(p.volt_mv).toFixed(1)},${yScale(refBaseMhz(p)).toFixed(1)}`) + .join(' '); + + const effectiveLine = visiblePts + .map((p) => `${xScale(p.volt_mv).toFixed(1)},${yScale(p.freq_mhz).toFixed(1)}`) + .join(' '); + + const hasPending = pendingDeltas.size > 0; + const pendingLine = hasPending + ? visiblePts + .map((p) => `${xScale(p.volt_mv).toFixed(1)},${yScale(effectiveMhz(p)).toFixed(1)}`) + .join(' ') + : null; + + // ─── Coordinate helpers ────────────────────────────────────────────────── + function svgToContainer(svgX: number, svgY: number): { x: number; y: number } | null { + const svg = svgRef.current; + const container = containerRef.current; + if (!svg || !container) return null; + const svgRect = svg.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); + const scaleX = svgRect.width / svgWRef.current; + const scaleY = svgRect.height / svgHRef.current; + return { + x: svgRect.left - containerRect.left + (svgX + MARGIN.left) * scaleX, + y: svgRect.top - containerRect.top + (svgY + MARGIN.top) * scaleY, + }; + } + + function clientToInner(clientX: number, clientY: number): { x: number; y: number } | null { + const svg = svgRef.current; + if (!svg) return null; + const rect = svg.getBoundingClientRect(); + return { + x: (clientX - rect.left) * (svgWRef.current / rect.width) - MARGIN.left, + y: (clientY - rect.top) * (svgHRef.current / rect.height) - MARGIN.top, + }; + } + + // ─── SVG cursor ────────────────────────────────────────────────────────── + function handleSvgMouseMove(e: React.MouseEvent) { + if (isPanning.current) return; + const next = e.shiftKey ? 'crosshair' : 'default'; + setSvgCursor(c => c === next ? c : next); + } + + function handleSvgMouseLeave() { + if (!isPanning.current) setSvgCursor('default'); + } + + // ─── Point hover ───────────────────────────────────────────────────────── + const handleMouseEnter = useCallback((p: VFPoint) => { + setHoveredPoint(p); + }, []); + + const handleMouseLeave = useCallback(() => { + setHoveredPoint(null); + }, []); + + // ─── Drag state (refs — survive renders without triggering effects) ─────── + const dragState = useRef<{ + active: boolean; + pointIndex: number; + startY: number; + startDeltaKhz: number; + pointInitialDeltas: Map; + } | null>(null); + /** True if the mouse moved meaningfully during the current mousedown */ + const dragMoved = useRef(false); + + // ─── Box select refs ───────────────────────────────────────────────────── + const boxStartRef = useRef<{ x: number; y: number } | null>(null); + const isBoxSelecting = useRef(false); + + // ─── Pan refs ───────────────────────────────────────────────────────────── + const panState = useRef<{ startX: number; startY: number; startViewport: [number, number] } | null>(null); + const isPanning = useRef(false); + + // ─── Point mousedown → start drag ──────────────────────────────────────── + function handlePointMouseDown(e: React.MouseEvent, p: VFPoint) { + if (readOnly) return; + containerRef.current?.focus(); + e.stopPropagation(); + e.preventDefault(); + + const inner = clientToInner(e.clientX, e.clientY); + if (!inner) return; + + const currentDelta = pendingDeltas.has(p.index) + ? pendingDeltas.get(p.index)! + : p.delta_khz; + + const initialDeltas = new Map(); + + if (selectedPoints.has(p.index) && selectedPoints.size > 1) { + // Feature: Dragging a point in a multi-selection moves the whole selection + for (const index of selectedPoints) { + const pt = pts.find(x => x.index === index); + if (pt) { + initialDeltas.set(index, pendingDeltas.has(index) ? pendingDeltas.get(index)! : pt.delta_khz); + } + } + } else { + // Default: Just drag this point + initialDeltas.set(p.index, currentDelta); + } + + dragState.current = { + active: true, + pointIndex: p.index, + startY: inner.y, + startDeltaKhz: currentDelta, + pointInitialDeltas: initialDeltas + }; + dragMoved.current = false; + } + + // ─── SVG background mousedown → pan OR shift+drag = box select ─────────── + function handleSvgMouseDown(e: React.MouseEvent) { + containerRef.current?.focus(); + if (e.button !== 0) return; + const inner = clientToInner(e.clientX, e.clientY); + if (!inner) return; + + if (e.shiftKey) { + // Shift+drag = box select + isBoxSelecting.current = true; + boxStartRef.current = inner; + setBoxRect({ x0: inner.x, y0: inner.y, x1: inner.x, y1: inner.y }); + } else { + // Plain drag = pan + isPanning.current = true; + setSvgCursor('grabbing'); + panState.current = { startX: inner.x, startY: inner.y, startViewport: [...scalesRef.current.xViewport] as [number, number] }; + } + } + + // ─── Scroll wheel → zoom x-axis around cursor (Alt + scroll) ──────────── + function handleWheel(e: React.WheelEvent) { + if (!e.altKey) return; // plain scroll → let page scroll normally + e.preventDefault(); + const inner = clientToInner(e.clientX, e.clientY); + if (!inner) return; + + const { xScale: sx, xViewport: vp } = scalesRef.current; + const zoomFactor = e.deltaY < 0 ? 1.15 : 1 / 1.15; // scroll up = zoom in + const mouseVolt = sx.invert(inner.x); + const [vpMin, vpMax] = vp; + const currentWidth = vpMax - vpMin; + const newWidth = Math.max(20, Math.min(voltExtent(pts)[1] - voltExtent(pts)[0] + 40, currentWidth / zoomFactor)); + const anchorFrac = (mouseVolt - vpMin) / currentWidth; + let newMin = mouseVolt - anchorFrac * newWidth; + let newMax = mouseVolt + (1 - anchorFrac) * newWidth; + // Clamp to full extent + const [fullMin, fullMax] = voltExtent(pts); + const pad = 20; + if (newMin < fullMin - pad) { newMax += (fullMin - pad - newMin); newMin = fullMin - pad; } + if (newMax > fullMax + pad) { newMin -= (newMax - fullMax - pad); newMax = fullMax + pad; } + setXViewport([newMin, newMax]); + } + + // ─── Global mousemove + mouseup ────────────────────────────────────────── + useEffect(() => { + function onMove(e: MouseEvent) { + // --- Point drag --- + if (dragState.current?.active) { + const inner = clientToInner(e.clientX, e.clientY); + if (!inner) return; + const ds = dragState.current; + const dyPx = inner.y - ds.startY; + + // Consider moved if more than 3px + if (Math.abs(dyPx) > 3) dragMoved.current = true; + + const { yScale: ys } = scalesRef.current; + const [yRangeBottom, yRangeTop] = ys.range() as [number, number]; + const [yDomainBottom, yDomainTop] = ys.domain() as [number, number]; + const pxPerMhz = (yRangeBottom - yRangeTop) / (yDomainTop - yDomainBottom); + const deltaMhz = -dyPx / pxPerMhz; + + // Calculate the absolute delta for the primary dragged point + const primaryNewDeltaKhz = Math.round(ds.startDeltaKhz + deltaMhz * 1000); + const primaryClamped = Math.max(-1000_000, Math.min(1000_000, primaryNewDeltaKhz)); + + // Difference to apply to all other points in the selection + const validDeltaDiff = primaryClamped - ds.startDeltaKhz; + + if (ds.pointInitialDeltas.size > 1) { + const edits = new Map(); + ds.pointInitialDeltas.forEach((initialDelta, index) => { + const newDelta = initialDelta + validDeltaDiff; + const clampedNewDelta = Math.max(-1000_000, Math.min(1000_000, newDelta)); + edits.set(index, clampedNewDelta); + }); + stageMultiEdit(edits); + } else { + stageEdit(ds.pointIndex, primaryClamped); + } + + // Update drag tooltip info + const point = pts.find((p) => p.index === ds.pointIndex); + if (point) { + const cx = scalesRef.current.xScale(point.volt_mv); + const deltaChange = primaryClamped - point.delta_khz; + const cy = scalesRef.current.yScale(point.freq_mhz + deltaChange / 1000); + setDragInfo({ pointIndex: ds.pointIndex, currentDeltaKhz: primaryClamped, cx, cy }); + } + return; + } + + // --- Pan --- + if (isPanning.current && panState.current) { + const inner = clientToInner(e.clientX, e.clientY); + if (!inner) return; + const ps = panState.current; + // How many volts does 1 SVG-inner-px correspond to? + const voltPerPx = (ps.startViewport[1] - ps.startViewport[0]) / scalesRef.current.innerW; + const dxVolt = (inner.x - ps.startX) * voltPerPx; + const [fullMin, fullMax] = voltExtent(pts); + const pad = 20; + const vpWidth = ps.startViewport[1] - ps.startViewport[0]; + let newMin = ps.startViewport[0] - dxVolt; + let newMax = ps.startViewport[1] - dxVolt; + // Clamp so we don't pan completely outside + if (newMin < fullMin - pad) { newMin = fullMin - pad; newMax = newMin + vpWidth; } + if (newMax > fullMax + pad) { newMax = fullMax + pad; newMin = newMax - vpWidth; } + setXViewport([newMin, newMax]); + return; + } + + // --- Box select --- + if (isBoxSelecting.current && boxStartRef.current) { + const inner = clientToInner(e.clientX, e.clientY); + if (!inner) return; + setBoxRect({ + x0: boxStartRef.current.x, + y0: boxStartRef.current.y, + x1: inner.x, + y1: inner.y, + }); + } + } + + function onUp(e: MouseEvent) { + // --- End drag --- + if (dragState.current?.active) { + dragState.current = null; + setDragInfo(null); + return; + } + + // --- End pan --- + if (isPanning.current) { + isPanning.current = false; + setSvgCursor('default'); + if (panState.current) { + const inner = clientToInner(e.clientX, e.clientY); + if (inner) { + const dx = Math.abs(inner.x - panState.current.startX); + const dy = Math.abs(inner.y - panState.current.startY); + if (dx < 3 && dy < 3) { + // It was just a click on the background, not a pan, so clear selection + clearSelection(); + } + } + } + panState.current = null; + return; + } + + // --- End box select --- + if (isBoxSelecting.current && boxStartRef.current) { + isBoxSelecting.current = false; + const inner = clientToInner(e.clientX, e.clientY); + if (inner) { + const finalRect = { + x0: boxStartRef.current.x, + y0: boxStartRef.current.y, + x1: inner.x, + y1: inner.y, + }; + const minX = Math.min(finalRect.x0, finalRect.x1); + const maxX = Math.max(finalRect.x0, finalRect.x1); + const minY = Math.min(finalRect.y0, finalRect.y1); + const maxY = Math.max(finalRect.y0, finalRect.y1); + const sizeTrivial = Math.abs(finalRect.x1 - finalRect.x0) < 4 + && Math.abs(finalRect.y1 - finalRect.y0) < 4; + + if (!sizeTrivial) { + const { xScale: sx, yScale: ys } = scalesRef.current; + const selected = pts + .filter((p) => { + const cx = sx(p.volt_mv); + const cy = ys(effectiveMhz(p)); + return cx >= minX && cx <= maxX && cy >= minY && cy <= maxY; + }) + .map((p) => p.index); + selectRange(selected); + } else { + clearSelection(); + } + } + boxStartRef.current = null; + setBoxRect(null); + } + } + + window.addEventListener('mousemove', onMove); + window.addEventListener('mouseup', onUp); + return () => { + window.removeEventListener('mousemove', onMove); + window.removeEventListener('mouseup', onUp); + }; + // Only re-register when structural deps change; scales are accessed via scalesRef + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [pts, pendingDeltas, stageEdit, selectRange, clearSelection, effectiveMhz]); + + return ( +
+ {/* Header: title + domain toggle + action buttons */} +
+
+ V/F Curve + {/* Domain toggle */} +
+ {([{label: 'GPU', value: 'gpu'}, {label: 'Memory', value: 'memory'}] as const).map((d) => ( + + ))} +
+ {readOnly && ( + read-only + )} +
+
+ {!readOnly && hasPending && ( + + {pendingDeltas.size} pending + + )} + {!readOnly && ( + <> + + + + + )} + +
+
+ + {/* Banners */} + {hasNegativeFreqWarning() && ( +
+ ⚠ One or more pending deltas would produce a negative effective frequency. The driver will clamp to 0 MHz. +
+ )} + {actionError && ( +
+ ⚠ {actionError} + +
+ )} + + {/* Confirm dialogs */} + {dialog === 'apply' && ( + setDialog(null)} + /> + )} + {dialog === 'reset' && ( + setDialog(null)} + /> + )} + +
+ setXViewport(voltExtent(pts))} + isZoomed={Math.abs((xViewport[1] - xViewport[0]) - (voltExtent(pts)[1] - voltExtent(pts)[0])) > 5} + readOnly={readOnly} + zoomFactor={zoomFactor} + onZoomChange={handleZoomChange} + /> +
+ + {/* Need tabIndex=0 to capture keyboard events */} +
{ + if (inlineInput) { + if (e.key === 'Escape') setInlineInput(null); + return; // Let the input handle it + } + + if (e.key === 'a' && (e.ctrlKey || e.metaKey)) { + e.preventDefault(); + selectRange(pts.map(p => p.index)); + return; + } + + if (e.key === 'Escape') { + e.preventDefault(); + clearSelection(); + return; + } + + if (e.key === 'Tab') { + e.preventDefault(); + if (pts.length === 0) return; + const currentSelected = Array.from(selectedPoints); + if (currentSelected.length === 0) { + selectPoint(pts[0].index); + } else { + const lastSelected = e.shiftKey ? Math.min(...currentSelected) : Math.max(...currentSelected); + const idx = pts.findIndex(p => p.index === lastSelected); + if (idx >= 0) { + let nextIdx = e.shiftKey ? idx - 1 : idx + 1; + if (nextIdx < 0) nextIdx = pts.length - 1; + if (nextIdx >= pts.length) nextIdx = 0; + selectPoint(pts[nextIdx].index); + } + } + return; + } + + if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { + e.preventDefault(); + if (selectedPoints.size === 0) return; + const changeMhz = e.key === 'ArrowUp' ? 1 : -1; + const multiplier = (e.ctrlKey || e.metaKey) ? 10 : 1; + const changeKhz = changeMhz * multiplier * 1000; + + const edits = new Map(); + for (const idx of selectedPoints) { + const p = pts.find(pt => pt.index === idx); + if (p) { + const current = pendingDeltas.has(idx) ? pendingDeltas.get(idx)! : p.delta_khz; + const clamped = Math.max(-500_000, Math.min(500_000, current + changeKhz)); + edits.set(idx, clamped); + } + } + stageMultiEdit(edits); + return; + } + + if (e.key === 'Enter') { + e.preventDefault(); + if (selectedPoints.size === 1) { + const idx = Array.from(selectedPoints)[0]; + const p = pts.find(pt => pt.index === idx); + if (p) { + const initVal = (pendingDeltas.has(idx) ? pendingDeltas.get(idx)! : p.delta_khz) / 1000; + setInlineInput({ pointIndex: idx, value: initVal.toFixed(1) }); + // We'll focus the input in an effect + } + } + return; + } + }} + > + + + + + + + + + {/* Grid */} + {xTicks.map((t) => ( + + ))} + {yTicks.map((t) => ( + + ))} + + {/* X axis */} + {xTicks.map((t) => ( + + + {t.toFixed(0)} + + ))} + + {/* Y axis */} + {yTicks.map((t) => ( + + + {t.toFixed(0)} + + ))} + + {/* Axis labels */} + Voltage (mV) + Frequency (MHz) + + {/* Border */} + + + + {/* Base curve */} + + + {/* Confirmed effective curve */} + + + {/* Pending effective curve */} + {pendingLine && ( + + )} + + {/* Current voltage / clock crosshairs — GPU domain only */} + {!readOnly && currentVoltageMv != null && xScale(currentVoltageMv) >= 0 && xScale(currentVoltageMv) <= INNER_W && ( + + )} + {!readOnly && currentClockMhz != null && yScale(currentClockMhz) >= 0 && yScale(currentClockMhz) <= INNER_H && ( + + )} + {!readOnly && currentVoltageMv != null && currentClockMhz != null && + xScale(currentVoltageMv) >= 0 && xScale(currentVoltageMv) <= INNER_W && + yScale(currentClockMhz) >= 0 && yScale(currentClockMhz) <= INNER_H && ( + + )} + + {pts.map((p) => { + if (p.volt_mv < xViewport[0] - 5 || p.volt_mv > xViewport[1] + 5) return null; + + const cx = xScale(p.volt_mv); + /** Confirmed position (hardware state — VFP effective frequency) */ + const confirmedCy = yScale(p.freq_mhz); + /** Staged/pending position (what will be applied) */ + const pendingCy = yScale(effectiveMhz(p)); + const hasPendingEdit = pendingDeltas.has(p.index); + /** + * The "main" interactive circle sits at the pending position when + * there's a staged edit — that's the point the user is working with. + * Otherwise it sits at the confirmed position. + */ + const mainCy = hasPendingEdit ? pendingCy : confirmedCy; + const ghostCy = hasPendingEdit ? confirmedCy : null; // dim ring showing where it was + + const isHovered = hoveredPoint?.index === p.index; + const isSelected = selectedPoints.has(p.index); + const isDragging = dragInfo?.pointIndex === p.index; + const isAnchor = isSelected && selectedPoints.size >= 2 && p.index === anchorPoint; + + let fill = readOnly ? '#6366f1' : '#34d399'; + if (!readOnly && hasPendingEdit && !isSelected) fill = '#22d3ee'; + if (!readOnly && isSelected) fill = '#22d3ee'; + + // Tweaked radius for less bloated flat regions + const r = isDragging ? 5.5 : isHovered || isSelected || hasPendingEdit ? 4.5 : 2.5; + + return ( + + {/* Anchor ring — amber outer halo marking the flatten reference point */} + {isAnchor && ( + + )} + + {/* Dim ring at confirmed position (only visible when there's a pending edit) */} + {!readOnly && ghostCy !== null && Math.abs(ghostCy - mainCy) > 0.5 && ( + + )} + + {/* Drop-line from confirmed → pending while dragging */} + {!readOnly && isDragging && ghostCy !== null && Math.abs(ghostCy - mainCy) > 1 && ( + + )} + + {/* Main circle */} + handleMouseEnter(p)} + onMouseLeave={handleMouseLeave} + onMouseDown={readOnly ? undefined : (e) => handlePointMouseDown(e, p)} + onClick={readOnly ? undefined : (e) => { + if (dragMoved.current) return; + selectPoint(p.index, e.shiftKey); + }} + /> + + ); + })} + + + {/* Box-select rubber band */} + {boxRect && ( + + )} + + + + {/* Inline Input Overlay */} + {inlineInput && (() => { + const pt = pts.find(p => p.index === inlineInput.pointIndex); + if (!pt) return null; + const cx = scalesRef.current.xScale(pt.volt_mv); + const currentDelta = pendingDeltas.has(pt.index) ? pendingDeltas.get(pt.index)! : pt.delta_khz; + const deltaChange = currentDelta - pt.delta_khz; + const cy = scalesRef.current.yScale(pt.freq_mhz + deltaChange / 1000); + const pos = svgToContainer(cx, cy); + if (!pos) return null; + return ( +
+ setInlineInput({ ...inlineInput, value: e.target.value })} + onBlur={() => setInlineInput(null)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + const mhz = parseFloat(inlineInput.value); + if (!isNaN(mhz) && isFinite(mhz)) { + const clamped = Math.max(-500_000, Math.min(500_000, Math.round(mhz * 1000))); + stageEdit(pt.index, clamped); + } + setInlineInput(null); + // Refocus the container + containerRef.current?.focus(); + } + if (e.key === 'Escape') { + setInlineInput(null); + containerRef.current?.focus(); + } + }} + className="w-20 bg-zinc-800 text-cyan-300 rounded px-1.5 py-0.5 border-2 border-cyan-500 shadow-lg outline-none text-xs font-mono text-center" + /> +
+ ); + })()} + + {/* Hover / Active tooltip */} + {(() => { + if (dragInfo) return null; + let tooltipPt = hoveredPoint; + if (!tooltipPt && selectedPoints.size === 1) { + tooltipPt = pts.find(p => p.index === Array.from(selectedPoints)[0]) || null; + } + if (!tooltipPt) return null; + + return ( + + ); + })()} + + {/* Drag tooltip — shown while actively dragging */} + {dragInfo && (() => { + const point = pts.find((p) => p.index === dragInfo.pointIndex); + if (!point) return null; + const deltaMhz = dragInfo.currentDeltaKhz / 1000; + const deltaChange = dragInfo.currentDeltaKhz - point.delta_khz; + const effMhz = point.freq_mhz + deltaChange / 1000; + return ( +
+
+ ↕ Point {point.index} + {point.volt_mv.toFixed(0)} mV +
+
0 ? 'text-cyan-400' : deltaMhz < 0 ? 'text-orange-400' : 'text-zinc-400'}`}> + Δ {deltaMhz > 0 ? '+' : ''}{deltaMhz.toFixed(1)} MHz +
+
+ → {effMhz.toFixed(0)} MHz +
+
+ ); + })()} +
+
+ ); +} diff --git a/frontend/src/components/CurveEditor/CurveToolbar.tsx b/frontend/src/components/CurveEditor/CurveToolbar.tsx new file mode 100644 index 0000000..d169ffb --- /dev/null +++ b/frontend/src/components/CurveEditor/CurveToolbar.tsx @@ -0,0 +1,130 @@ +import { useState, useMemo, useEffect } from 'react'; +import { ZoomIn, RotateCcw, Minus } from 'lucide-react'; +import { useCurveStore } from '../../store/curveStore'; +import type { VFPoint } from '../../types'; + +interface Props { + /** All curve points — used by global offset slider */ + activePts: VFPoint[]; + /** Called to reset the x-axis zoom to default */ + onResetZoom: () => void; + /** True if the chart is currently zoomed in */ + isZoomed: boolean; + /** True when viewing a read-only domain (memory) */ + readOnly?: boolean; + /** Current zoom factor (1 = no zoom) */ + zoomFactor: number; + /** Called when user changes zoom via slider */ + onZoomChange: (factor: number) => void; +} + +export function CurveToolbar({ activePts, onResetZoom, isZoomed, readOnly, zoomFactor, onZoomChange }: Props) { + const { pendingDeltas, selectedPoints, anchorPoint, curve, stageRangeEdit, flattenToAnchor } = useCurveStore(); + + const [offsetMhz, setOffsetMhz] = useState(0); + + const uniformDeltaMhz = useMemo(() => { + if (activePts.length === 0) return 0; + const firstD = pendingDeltas.get(activePts[0].index) ?? activePts[0].delta_khz; + const uniform = activePts.every((p) => (pendingDeltas.get(p.index) ?? p.delta_khz) === firstD); + return uniform ? firstD / 1000 : null; + }, [activePts, pendingDeltas]); + + useEffect(() => { + if (uniformDeltaMhz !== null) { + setOffsetMhz(uniformDeltaMhz); + } + }, [uniformDeltaMhz]); + + function handleOffsetChange(mhz: number) { + setOffsetMhz(mhz); + stageRangeEdit(activePts, Math.round(mhz * 1000)); + } + + return ( +
+ {/* Zoom control */} +
+ + onZoomChange(Number(e.target.value))} + className="w-20 h-1 cursor-pointer accent-cyan-400" + /> + + {zoomFactor.toFixed(1)}× + + {isZoomed && ( + + )} +
+ + {/* Divider */} + + + {/* Global offset slider — GPU only */} + {!readOnly && uniformDeltaMhz !== null && ( +
+ Global Offset + handleOffsetChange(Number(e.target.value))} + className="w-32 accent-cyan-400" + title={`${offsetMhz > 0 ? '+' : ''}${offsetMhz} MHz`} + /> + 0 ? 'text-cyan-400' : offsetMhz < 0 ? 'text-orange-400' : 'text-zinc-500', + ].join(' ')} + > + {offsetMhz > 0 ? '+' : ''}{offsetMhz} MHz + +
+ )} + + {/* Flatten — visible when 2+ points are selected */} + {!readOnly && selectedPoints.size >= 2 && (() => { + const anchor = anchorPoint !== null && selectedPoints.has(anchorPoint) + ? anchorPoint + : Math.min(...selectedPoints); + const anchorDelta = + pendingDeltas.get(anchor) ?? + curve?.points.find(p => p.index === anchor)?.delta_khz ?? + 0; + const label = `·${anchor} ${anchorDelta >= 0 ? '+' : ''}${anchorDelta / 1000} MHz`; + return ( + + ); + })()} + + {/* Legend — right-aligned */} +
+ effective + pending + current +
+
+ ); +} diff --git a/frontend/src/components/CurveEditor/CurveTooltip.tsx b/frontend/src/components/CurveEditor/CurveTooltip.tsx new file mode 100644 index 0000000..6d4e752 --- /dev/null +++ b/frontend/src/components/CurveEditor/CurveTooltip.tsx @@ -0,0 +1,70 @@ +import { fmt } from '../../utils/units'; +import type { VFPoint } from '../../types'; + +interface Props { + point: VFPoint; + /** Pending delta in kHz (from store), if any */ + pendingDeltaKhz?: number; + /** True if this point is being held up by monotonicity enforcement */ + isClamped?: boolean; +} + +/** + * HTML tooltip rendered as an absolutely-positioned overlay on top of the + * SVG so it's never clipped by the SVG viewport. + */ +export function CurveTooltip({ point, pendingDeltaKhz, isClamped }: Props) { + const hasPending = pendingDeltaKhz !== undefined; + const pendingMhz = hasPending ? pendingDeltaKhz! / 1000 : 0; + const deltaChange = hasPending ? pendingDeltaKhz! - point.delta_khz : 0; + const pendingEffMhz = hasPending ? point.freq_mhz + deltaChange / 1000 : null; + + return ( +
+
Point {point.index}
+
+ Volt: {fmt.mv(point.volt_mv, 1)} +
+
+ Offset: + 0 ? 'text-emerald-400' : point.delta_khz < 0 ? 'text-red-400' : 'text-zinc-400'}> + {point.delta_khz > 0 ? '+' : ''}{fmt.mhz(point.delta_mhz, 1)} + +
+
+ Eff.: {fmt.mhz(point.freq_mhz, 0)} + {isClamped && ⇡} +
+ {isClamped && ( +
+ Clamped by lower-voltage point +
+ )} + {hasPending && ( + <> +
+
+ Pending: + 0 ? 'text-cyan-400' : pendingMhz < 0 ? 'text-orange-400' : 'text-zinc-400'}> + {pendingMhz > 0 ? '+' : ''}{pendingMhz.toFixed(1)} MHz + +
+
+ → Eff.: {fmt.mhz(pendingEffMhz, 0)} +
+
+ + )} +
+ ); +} diff --git a/frontend/src/components/Limits/PerformancePanel.tsx b/frontend/src/components/Limits/PerformancePanel.tsx new file mode 100644 index 0000000..61c3b7d --- /dev/null +++ b/frontend/src/components/Limits/PerformancePanel.tsx @@ -0,0 +1,239 @@ +import { useState, useEffect } from 'react'; +import { Check, X, RotateCcw } from 'lucide-react'; +import { api } from '../../api/client'; +import { useCurveStore } from '../../store/curveStore'; +import type { LimitsState } from '../../types'; +import { toast } from 'sonner'; +import { ConfirmDialog } from '../common/ConfirmDialog'; + +type Pending = Pick; + +export function PerformancePanel() { + const { selectedGpuIndex } = useCurveStore(); + const [limits, setLimits] = useState(null); + const [pending, setPending] = useState>({}); + const [loading, setLoading] = useState(true); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + const [confirmApply, setConfirmApply] = useState(false); + const [confirmReset, setConfirmReset] = useState(false); + + async function fetchLimits() { + try { + setLoading(true); + setLimits(await api.limits(selectedGpuIndex)); + } catch { + toast.error('Failed to load performance limits'); + } finally { + setLoading(false); + } + } + + useEffect(() => { fetchLimits(); }, [selectedGpuIndex]); + + async function handleApply() { + setBusy(true); + setError(null); + try { + await api.updateLimits(pending, selectedGpuIndex); + useCurveStore.getState().setActiveProfile(null); + setPending({}); + setConfirmApply(false); + await fetchLimits(); + toast.success('Performance limits applied'); + } catch (e: any) { + setError(e.message ?? String(e)); + setConfirmApply(false); + } finally { + setBusy(false); + } + } + + async function handleReset() { + setBusy(true); + setError(null); + try { + await api.resetLimits(selectedGpuIndex); + useCurveStore.getState().setActiveProfile(null); + setPending({}); + setConfirmReset(false); + await fetchLimits(); + toast.success('Performance limits reset to defaults'); + } catch (e: any) { + setError(e.message ?? String(e)); + setConfirmReset(false); + } finally { + setBusy(false); + } + } + + if (loading && !limits) { + return ( +
+
+
+
+
+
+
+ ); + } + + if (!limits) return null; + + const hasPending = Object.keys(pending).length > 0; + + // Power + const pwrVal = pending.power_limit_w ?? limits.power_limit_w ?? 0; + const pwrMin = limits.min_power_limit_w ?? 100; + const pwrMax = limits.max_power_limit_w ?? 600; + + // Memory offset + const memVal = pending.mem_offset_mhz ?? limits.mem_offset_mhz ?? 0; + const memMin = limits.min_mem_offset_mhz ?? -2000; + const memMax = Math.min(limits.max_mem_offset_mhz ?? 3000, 3000); + + return ( + <> +
+ + {/* ── Header ─────────────────────────────────────────────────────── */} +
+ Performance + + {hasPending && ( + + {Object.keys(pending).length} pending + + )} + +
+ + + +
+
+ + {/* ── Error banner ────────────────────────────────────────────────── */} + {error && ( +
+ ⚠ {error} + +
+ )} + +
+ + {/* ── Board Power Limit ─────────────────────────────────────────── */} +
+
+ Board Power Limit +
+ { + const v = parseInt(e.target.value); + if (!isNaN(v)) setPending(p => ({ ...p, power_limit_w: v })); + }} + className="w-14 bg-zinc-950 border border-zinc-800 rounded text-xs px-2 py-1 text-right font-mono focus:outline-none focus:border-cyan-500" + /> + W +
+
+
+ {pwrMin} + setPending(p => ({ ...p, power_limit_w: parseInt(e.target.value) }))} + className="flex-1 accent-cyan-400 h-1 cursor-pointer" + /> + {pwrMax} +
+
+ + {/* ── Memory Clock Offset ───────────────────────────────────────── */} +
+
+ Memory Clock Offset +
+ { + const v = parseInt(e.target.value); + if (!isNaN(v)) setPending(p => ({ ...p, mem_offset_mhz: v })); + }} + className="w-16 bg-zinc-950 border border-zinc-800 rounded text-xs px-2 py-1 text-right font-mono focus:outline-none focus:border-cyan-500" + /> + MHz +
+
+
+ {memMin} + setPending(p => ({ ...p, mem_offset_mhz: parseInt(e.target.value) }))} + className="flex-1 accent-cyan-400 h-1 cursor-pointer" + /> + +{memMax} +
+
+ +
+
+ + {confirmApply && ( + setConfirmApply(false)} + /> + )} + {confirmReset && ( + setConfirmReset(false)} + /> + )} + + ); +} diff --git a/frontend/src/components/Monitor/GaugeCard.tsx b/frontend/src/components/Monitor/GaugeCard.tsx new file mode 100644 index 0000000..595b234 --- /dev/null +++ b/frontend/src/components/Monitor/GaugeCard.tsx @@ -0,0 +1,52 @@ +interface Props { + label: string; + value: string; + history: number[]; + unit?: string; + color?: string; // tailwind color class for the sparkline stroke + max?: number; +} + +const SPARKLINE_H = 32; +const SPARKLINE_W = 120; + +function Sparkline({ data, color = '#a78bfa', max }: { data: number[]; color?: string; max?: number }) { + const points = data.length >= 2 ? (() => { + const dataMax = max ?? Math.max(...data, 1); + const dataMin = Math.min(...data, 0); + const range = dataMax - dataMin || 1; + return data.map((v, i) => { + const x = (i / (data.length - 1)) * SPARKLINE_W; + const y = SPARKLINE_H - ((v - dataMin) / range) * SPARKLINE_H; + return `${x},${y}`; + }).join(' '); + })() : null; + + return ( + + {points && ( + + )} + + ); +} + +export function GaugeCard({ label, value, history, color = '#a78bfa', max }: Props) { + return ( +
+
{label}
+
{value}
+
+ +
+
+ ); +} diff --git a/frontend/src/components/Monitor/LiveMonitor.tsx b/frontend/src/components/Monitor/LiveMonitor.tsx new file mode 100644 index 0000000..7724a1f --- /dev/null +++ b/frontend/src/components/Monitor/LiveMonitor.tsx @@ -0,0 +1,66 @@ +import { GaugeCard } from './GaugeCard'; +import { fmt } from '../../utils/units'; +import type { MonitoringSample } from '../../types'; + +interface Props { + monitor: MonitoringSample | null; + history: MonitoringSample[]; +} + +function pluck( + history: MonitoringSample[], + key: K, +): number[] { + return history.map((s) => (s[key] as number | null) ?? 0); +} + +export function LiveMonitor({ monitor, history }: Props) { + return ( +
+
+
+ Live Monitor +
+
+ + + + +
+ +
+
+
+
+ ); +} diff --git a/frontend/src/components/Monitor/PerformanceMonitor.tsx b/frontend/src/components/Monitor/PerformanceMonitor.tsx new file mode 100644 index 0000000..37319ea --- /dev/null +++ b/frontend/src/components/Monitor/PerformanceMonitor.tsx @@ -0,0 +1,56 @@ +import { GaugeCard } from './GaugeCard'; +import { fmt } from '../../utils/units'; +import type { MonitoringSample } from '../../types'; + +interface Props { + monitor: MonitoringSample | null; + history: MonitoringSample[]; +} + +function pluck( + history: MonitoringSample[], + key: K, +): number[] { + return history.map((s) => (s[key] as number | null) ?? 0); +} + +export function PerformanceMonitor({ monitor, history }: Props) { + const memUsed = monitor?.mem_used_mib ?? null; + const memTotal = monitor?.mem_total_mib ?? null; + const memLabel = memUsed != null && memTotal != null + ? `${memUsed.toFixed(0)} / ${memTotal.toFixed(0)} MiB` + : '—'; + + return ( +
+
+
+ Live Monitor +
+
+ + + +
+
+
+ ); +} diff --git a/frontend/src/components/Monitor/StatusBar.tsx b/frontend/src/components/Monitor/StatusBar.tsx new file mode 100644 index 0000000..bbcb2d9 --- /dev/null +++ b/frontend/src/components/Monitor/StatusBar.tsx @@ -0,0 +1,128 @@ +import { Cpu, Wifi, WifiOff, Loader, Terminal, ChevronDown } from 'lucide-react'; +import type { GpuInfo, MonitoringSample } from '../../types'; +import { fmt } from '../../utils/units'; +import { useCurveStore } from '../../store/curveStore'; +import { useState, useRef, useEffect } from 'react'; + +interface Props { + gpuInfo: GpuInfo | null; + wsStatus: 'connecting' | 'connected' | 'disconnected'; + monitor: MonitoringSample | null; +} + +const statusIcon = { + connected: , + connecting: , + disconnected: , +}; + +const statusText = { + connected: 'Connected', + connecting: 'Connecting…', + disconnected: 'Disconnected', +}; + +export function StatusBar({ gpuInfo, wsStatus, monitor }: Props) { + const { availableGpus, selectedGpuIndex, setSelectedGpuIndex } = useCurveStore(); + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + } + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + return ( +
+
+ {/* Left: GPU name */} +
+ NVCurve + + NVCurve + +
+ + {availableGpus.length > 1 ? ( +
+ + + {isOpen && ( +
+ {availableGpus.map((gpu) => ( + + ))} +
+ )} +
+ ) : ( + <> + + + {gpuInfo?.name ?? 'No Device'} + + + )} + + {gpuInfo?.driver_version && ( +
+ + {gpuInfo.driver_version} +
+ )} +
+ + {/* Centre: live stats */} +
+ + + + + +
+ + {/* Right: connection status */} +
+ {statusIcon[wsStatus]} + {statusText[wsStatus]} +
+
+
+ ); +} + +function StatPill({ label, value, color }: { label: string; value: string; color: string }) { + return ( +
+ {label} + {value} +
+ ); +} diff --git a/frontend/src/components/PointTable/PointRow.tsx b/frontend/src/components/PointTable/PointRow.tsx new file mode 100644 index 0000000..9c2179d --- /dev/null +++ b/frontend/src/components/PointTable/PointRow.tsx @@ -0,0 +1,124 @@ +import { useState, useRef, useEffect } from 'react'; +import { fmt } from '../../utils/units'; +import type { VFPoint } from '../../types'; +import { useCurveStore } from '../../store/curveStore'; + +interface Props { + point: VFPoint; + isCurrent: boolean; + isSelected: boolean; + isClamped: boolean; + pendingDeltaKhz?: number; + shouldAutoScroll?: boolean; + onMouseDown?: (e: React.MouseEvent) => void; + onMouseEnter?: () => void; +} + +export function PointRow({ point, isCurrent, isSelected, isClamped, pendingDeltaKhz, shouldAutoScroll, onMouseDown, onMouseEnter }: Props) { + const { stageEdit } = useCurveStore(); + const [editing, setEditing] = useState(false); + const [inputValue, setInputValue] = useState(''); + const inputRef = useRef(null); + const trRef = useRef(null); + + useEffect(() => { + if (shouldAutoScroll && trRef.current) { + // @ts-expect-error: the typing seems to not include the valid 'container' option + trRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest', container: 'nearest' }); + } + }, [shouldAutoScroll]); + + const hasPending = pendingDeltaKhz !== undefined; + const displayDeltaMhz = hasPending ? pendingDeltaKhz / 1000 : point.delta_mhz; + const displayDeltaKhz = hasPending ? pendingDeltaKhz : point.delta_khz; + // Effective = current effective + delta change + const deltaChange = hasPending ? pendingDeltaKhz - point.delta_khz : 0; + const displayEffMhz = point.freq_mhz + deltaChange / 1000; + + const deltaColor = hasPending + ? displayDeltaKhz > 0 ? 'text-cyan-400' : displayDeltaKhz < 0 ? 'text-orange-400' : 'text-zinc-400' + : point.delta_khz > 0 ? 'text-emerald-400' : point.delta_khz < 0 ? 'text-red-400' : 'text-zinc-500'; + + function startEdit() { + setInputValue((displayDeltaMhz).toFixed(1)); + setEditing(true); + setTimeout(() => { + inputRef.current?.select(); + }, 0); + } + + function commitEdit() { + const mhz = parseFloat(inputValue); + if (!isNaN(mhz) && isFinite(mhz)) { + const clamped = Math.max(-1000, Math.min(1000, mhz)); + stageEdit(point.index, Math.round(clamped * 1000)); + } + setEditing(false); + } + + function cancelEdit() { + setEditing(false); + } + + return ( + { + if (editing) return; + onMouseDown?.(e); + }} + onMouseEnter={() => { + if (editing) return; + onMouseEnter?.(); + }} + > + {point.index} + {fmt.mv(point.volt_mv, 0)} + + {/* Offset — click to edit inline */} + { e.stopPropagation(); startEdit(); }}> + {editing ? ( + setInputValue(e.target.value)} + onBlur={commitEdit} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === 'Tab') { e.preventDefault(); commitEdit(); } + if (e.key === 'Escape') cancelEdit(); + }} + className="w-20 bg-zinc-700 text-cyan-300 rounded px-1 py-0 border border-cyan-500 outline-none text-xs" + style={{ fontFamily: 'monospace' }} + /> + ) : ( + + {hasPending && ✎} + {displayDeltaKhz > 0 ? '+' : ''}{displayDeltaMhz.toFixed(1)} MHz + + )} + + + {/* Eff. Freq */} + + {fmt.mhz(displayEffMhz, 0)} + {isClamped && !hasPending && ( + ⇡ + )} + + + + {isCurrent && ◀ now} + {isSelected && !isCurrent && ●} + + + ); +} diff --git a/frontend/src/components/PointTable/PointTable.tsx b/frontend/src/components/PointTable/PointTable.tsx new file mode 100644 index 0000000..50b23d2 --- /dev/null +++ b/frontend/src/components/PointTable/PointTable.tsx @@ -0,0 +1,119 @@ +import { useState, useMemo, useEffect } from 'react'; +import { PointRow } from './PointRow'; +import type { VFPoint } from '../../types'; +import { findCurrentPoint, detectClampedPoints } from '../../utils/curveHelpers'; +import { useCurveStore } from '../../store/curveStore'; + +interface Props { + points: VFPoint[]; + currentVoltageMv: number | null; + readOnly?: boolean; +} + +export function PointTable({ points, currentVoltageMv, readOnly }: Props) { + const { pendingDeltas, selectedPoints, selectPoint, selectRange } = useCurveStore(); + const currentPoint = findCurrentPoint(points, currentVoltageMv); + const clampedPoints = useMemo(() => detectClampedPoints(points), [points]); + + const [dragStartIdx, setDragStartIdx] = useState(null); + + useEffect(() => { + function onUp() { setDragStartIdx(null); } + window.addEventListener('mouseup', onUp); + return () => window.removeEventListener('mouseup', onUp); + }, []); + + return ( +
+
+ Points + {!readOnly && pendingDeltas.size > 0 && ( + + {pendingDeltas.size} staged + + )} + {readOnly && ( + read-only + )} + {points.length} points + {!readOnly && selectedPoints.size === 1 && ( +
+ + +
+ )} +
+
+ + + + + + + + + + + {points.map((p) => ( + { + if (e.shiftKey) { + const currentSelected = Array.from(selectedPoints); + if (currentSelected.length > 0) { + const last = Math.max(...currentSelected); + const min = Math.min(last, p.index); + const max = Math.max(last, p.index); + const toSelect = points.filter(a => a.index >= min && a.index <= max).map(a => a.index); + selectRange(toSelect); + } else { + selectPoint(p.index, false); + } + } else if (e.ctrlKey || e.metaKey) { + selectPoint(p.index, true); + } else { + setDragStartIdx(p.index); + selectPoint(p.index, false); + } + }} + onMouseEnter={readOnly ? undefined : () => { + if (dragStartIdx !== null) { + const min = Math.min(dragStartIdx, p.index); + const max = Math.max(dragStartIdx, p.index); + const toSelect = points.filter(a => a.index >= min && a.index <= max).map(a => a.index); + selectRange(toSelect); + } + }} + /> + ))} + +
#VoltageOffsetEff. Freq +
+
+
+ ); +} diff --git a/frontend/src/components/Profiles/ProfilePanel.tsx b/frontend/src/components/Profiles/ProfilePanel.tsx new file mode 100644 index 0000000..fc3df63 --- /dev/null +++ b/frontend/src/components/Profiles/ProfilePanel.tsx @@ -0,0 +1,337 @@ +import { useState, useEffect, useRef } from 'react'; +import { Save, Trash2, Check, ChevronRight, Pencil, Star } from 'lucide-react'; +import { api } from '../../api/client'; +import type { ProfileData } from '../../types'; +import { toast } from 'sonner'; +import { useCurveStore } from '../../store/curveStore'; + +interface ProfilePanelProps { + activeProfile: string | null; + onProfileApplied: (name: string | null) => void; +} + +export function ProfilePanel({ activeProfile, onProfileApplied }: ProfilePanelProps) { + const { selectedGpuIndex, gpuInfo } = useCurveStore(); + const [profiles, setProfiles] = useState([]); + const [loading, setLoading] = useState(true); + const [autoLoadProfile, setAutoLoadProfile] = useState(null); + + // Save form + const [isSaveOpen, setIsSaveOpen] = useState(false); + const [newName, setNewName] = useState(''); + const [isSaving, setIsSaving] = useState(false); + const saveInputRef = useRef(null); + + // Inline delete confirmation + const [deletingName, setDeletingName] = useState(null); + const [isDeleting, setIsDeleting] = useState(false); + + // Inline rename + const [renamingName, setRenamingName] = useState(null); + const [renameValue, setRenameValue] = useState(''); + const [isRenaming, setIsRenaming] = useState(false); + const renameInputRef = useRef(null); + + // Apply in-flight + const [applyingName, setApplyingName] = useState(null); + + async function fetchProfiles() { + try { + const data = await api.profiles(selectedGpuIndex); + setProfiles(data.profiles); + onProfileApplied(data.active); + setAutoLoadProfile(data.auto_load); + } catch { + toast.error('Failed to load profiles'); + } finally { + setLoading(false); + } + } + + async function handleSetAutoLoad(name: string | null) { + try { + await api.setAutoLoadProfile(name, selectedGpuIndex); + setAutoLoadProfile(name); + if (name) toast.success(`"${name}" will load on server start`); + else toast.success('Auto-load cleared'); + } catch (e: any) { + toast.error('Failed to update default profile: ' + (e.message || String(e))); + } + } + + useEffect(() => { fetchProfiles(); }, [selectedGpuIndex]); + + useEffect(() => { + if (isSaveOpen) saveInputRef.current?.focus(); + else setNewName(''); + }, [isSaveOpen]); + + useEffect(() => { + if (renamingName) renameInputRef.current?.focus(); + }, [renamingName]); + + async function handleSave(e: React.FormEvent) { + e.preventDefault(); + if (!newName.trim()) return; + try { + setIsSaving(true); + await api.saveProfile(newName.trim(), selectedGpuIndex); + toast.success(`Profile "${newName.trim()}" saved`); + setIsSaveOpen(false); + await fetchProfiles(); + } catch (e: any) { + toast.error('Failed to save: ' + (e.message || String(e))); + } finally { + setIsSaving(false); + } + } + + async function handleApply(name: string) { + try { + setApplyingName(name); + await api.applyProfile(name, selectedGpuIndex); + onProfileApplied(name); + toast.success(`"${name}" applied`); + } catch (e: any) { + toast.error(`Failed to apply "${name}": ` + (e.message || String(e))); + } finally { + setApplyingName(null); + } + } + + async function handleDeleteConfirm(name: string) { + try { + setIsDeleting(true); + await api.deleteProfile(name); + toast.success(`"${name}" deleted`); + if (activeProfile === name) onProfileApplied(null); + if (autoLoadProfile === name) setAutoLoadProfile(null); + setDeletingName(null); + await fetchProfiles(); + } catch (e: any) { + toast.error('Failed to delete: ' + (e.message || String(e))); + } finally { + setIsDeleting(false); + } + } + + async function handleRename(e: React.FormEvent, oldName: string) { + e.preventDefault(); + if (!renameValue.trim() || renameValue.trim() === oldName) { + setRenamingName(null); + return; + } + try { + setIsRenaming(true); + await api.renameProfile(oldName, renameValue.trim()); + toast.success(`Renamed to "${renameValue.trim()}"`); + if (activeProfile === oldName) onProfileApplied(renameValue.trim()); + if (autoLoadProfile === oldName) setAutoLoadProfile(renameValue.trim()); + setRenamingName(null); + await fetchProfiles(); + } catch (e: any) { + toast.error('Failed to rename: ' + (e.message || String(e))); + } finally { + setIsRenaming(false); + } + } + + if (loading && profiles.length === 0) { + return ( +
+
+
+
+ ); + } + + return ( +
+ {/* Header */} +
+ +

Profiles

+
+ + {/* Save section */} +
+ {isSaveOpen ? ( +
+ setNewName(e.target.value)} + disabled={isSaving} + onKeyDown={e => e.key === 'Escape' && setIsSaveOpen(false)} + className="flex-1 min-w-0 bg-zinc-950 border border-zinc-700 rounded px-3 py-1.5 text-sm focus:outline-none focus:border-pink-500 focus:ring-1 focus:ring-pink-500 disabled:opacity-50" + /> + + +
+ ) : ( + + )} +
+ + {/* Profile list */} +
+ {profiles.length === 0 ? ( +

+ No profiles saved yet. +

+ ) : ( +
+ {profiles.map(p => { + const pts = Object.keys(p.curve_deltas).length; + const badges = [ + pts > 0 ? `${pts} pts` : null, + p.power_limit_w != null ? `${p.power_limit_w}W` : null, + p.mem_offset_mhz != null ? `${p.mem_offset_mhz > 0 ? '+' : ''}${p.mem_offset_mhz} MHz mem` : null, + ].filter(Boolean).join(' · '); + + const isActive = activeProfile === p.name; + const isAutoLoad = autoLoadProfile === p.name; + const isApplying = applyingName === p.name; + const isConfirmingDelete = deletingName === p.name; + const isRenaming_ = renamingName === p.name; + + return ( +
+ {isConfirmingDelete ? ( +
+ Delete "{p.name}"? +
+ + +
+
+ ) : isRenaming_ ? ( +
handleRename(e, p.name)} className="flex items-center gap-2 px-3 py-2"> + setRenameValue(e.target.value)} + disabled={isRenaming} + onKeyDown={e => e.key === 'Escape' && setRenamingName(null)} + className="flex-1 min-w-0 bg-zinc-950 border border-zinc-600 rounded px-2 py-1 text-sm focus:outline-none focus:border-pink-500 focus:ring-1 focus:ring-pink-500 disabled:opacity-50" + /> + + +
+ ) : ( +
+
+ {isActive + ? + : + } +
+
+

+ {p.name} +

+ {isAutoLoad && ( + + )} + {gpuInfo && p.gpu_name !== gpuInfo.name && ( + + {p.gpu_name} + + )} +
+ {badges &&

{badges}

} +
+
+ +
+ + + + +
+
+ )} +
+ ); + })} +
+ )} +
+
+ ); +} diff --git a/frontend/src/components/common/ConfirmDialog.tsx b/frontend/src/components/common/ConfirmDialog.tsx new file mode 100644 index 0000000..11389bb --- /dev/null +++ b/frontend/src/components/common/ConfirmDialog.tsx @@ -0,0 +1,50 @@ +interface Props { + message: string; + detail?: string; + confirmLabel?: string; + isDestructive?: boolean; + onConfirm: () => void; + onCancel: () => void; +} + +export function ConfirmDialog({ + message, + detail, + confirmLabel = 'Confirm', + isDestructive = false, + onConfirm, + onCancel, +}: Props) { + return ( +
{ if (e.target === e.currentTarget) onCancel(); }} + > +
+

{message}

+ {detail &&

{detail}

} + {!detail &&
} +
+ + +
+
+
+ ); +} diff --git a/frontend/src/hooks/useCurve.ts b/frontend/src/hooks/useCurve.ts new file mode 100644 index 0000000..4cde202 --- /dev/null +++ b/frontend/src/hooks/useCurve.ts @@ -0,0 +1,28 @@ +import { useEffect, useRef, useState } from 'react'; +import { api } from '../api/client'; +import { createWsConnection } from '../api/websocket'; +import { useCurveStore } from '../store/curveStore'; +import type { CurveState } from '../types'; + +export function useCurve() { + const { curve, setCurve, selectedGpuIndex } = useCurveStore(); + const [wsStatus, setWsStatus] = useState<'connecting' | 'connected' | 'disconnected'>('connecting'); + const wsRef = useRef | null>(null); + + useEffect(() => { + // Initial fetch + api.curve(selectedGpuIndex).then(setCurve).catch(console.error); + + // Subscribe to /ws/curve for push updates after writes + wsRef.current = createWsConnection( + '/ws/curve', + (data) => setCurve(data), + setWsStatus, + selectedGpuIndex, + ); + + return () => wsRef.current?.close(); + }, [setCurve, selectedGpuIndex]); + + return { curve, wsStatus }; +} diff --git a/frontend/src/hooks/useGpu.ts b/frontend/src/hooks/useGpu.ts new file mode 100644 index 0000000..665d3fb --- /dev/null +++ b/frontend/src/hooks/useGpu.ts @@ -0,0 +1,17 @@ +import { useEffect } from 'react'; +import { api } from '../api/client'; +import { useCurveStore } from '../store/curveStore'; + +export function useGpu() { + const { gpuInfo, setGpuInfo, setAvailableGpus, selectedGpuIndex } = useCurveStore(); + + useEffect(() => { + api.gpus().then(setAvailableGpus).catch(console.error); + }, [setAvailableGpus]); + + useEffect(() => { + api.gpu(selectedGpuIndex).then(setGpuInfo).catch(console.error); + }, [setGpuInfo, selectedGpuIndex]); + + return gpuInfo; +} diff --git a/frontend/src/hooks/useMonitor.ts b/frontend/src/hooks/useMonitor.ts new file mode 100644 index 0000000..e8ada76 --- /dev/null +++ b/frontend/src/hooks/useMonitor.ts @@ -0,0 +1,22 @@ +import { useEffect, useRef, useState } from 'react'; +import { createWsConnection } from '../api/websocket'; +import { useCurveStore } from '../store/curveStore'; +import type { MonitoringSample } from '../types'; + +export function useMonitor() { + const { monitor, monitorHistory, pushMonitor, selectedGpuIndex } = useCurveStore(); + const [wsStatus, setWsStatus] = useState<'connecting' | 'connected' | 'disconnected'>('connecting'); + const wsRef = useRef | null>(null); + + useEffect(() => { + wsRef.current = createWsConnection( + '/ws/monitor', + pushMonitor, + setWsStatus, + selectedGpuIndex, + ); + return () => wsRef.current?.close(); + }, [pushMonitor, selectedGpuIndex]); + + return { monitor, monitorHistory, wsStatus }; +} diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..1d29f68 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,42 @@ +@import "tailwindcss"; + +*, *::before, *::after { + box-sizing: border-box; +} + +html, body, #root { + height: 100%; + margin: 0; + padding: 0; +} + +body { + background-color: #09090b; /* zinc-950 */ + color: #f4f4f5; /* zinc-100 */ + font-family: system-ui, -apple-system, sans-serif; + -webkit-font-smoothing: antialiased; +} + +/* Tailwind v4 dark mode via media (fallback) */ +@media (prefers-color-scheme: light) { + body { + background-color: #09090b; + color: #f4f4f5; + } +} + +/* scrollbar styling for dark theme */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} +::-webkit-scrollbar-track { + background: #18181b; +} +::-webkit-scrollbar-thumb { + background: #3f3f46; + border-radius: 3px; +} +::-webkit-scrollbar-thumb:hover { + background: #52525b; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/frontend/src/store/curveStore.ts b/frontend/src/store/curveStore.ts new file mode 100644 index 0000000..b20a6ab --- /dev/null +++ b/frontend/src/store/curveStore.ts @@ -0,0 +1,249 @@ +import { create } from 'zustand'; +import type { CurveState, GpuInfo, MonitoringSample, VFPoint } from '../types'; +import { api } from '../api/client'; +import { toast } from 'sonner'; + +const HISTORY_SIZE = 120; // ~60s at 2Hz + +interface CurveStore { + // Hardware state (from API) + availableGpus: GpuInfo[]; + selectedGpuIndex: number; + curve: CurveState | null; + gpuInfo: GpuInfo | null; + monitor: MonitoringSample | null; + monitorHistory: MonitoringSample[]; + + activeProfile: string | null; + + // Phase 4 — edit state + /** point index → pending delta in kHz (overrides curve.points[i].delta_khz for display) */ + pendingDeltas: Map; + selectedPoints: Set; + /** + * The anchor point for multi-point operations (e.g. flatten). + * Set to the last explicitly clicked point. Bulk selects (box, range, Ctrl+A) + * leave it unchanged; clearSelection resets it to null. + * If null or not in selectedPoints, operations fall back to the lowest selected index. + */ + anchorPoint: number | null; + + // Hardware state setters + setAvailableGpus: (gpus: GpuInfo[]) => void; + setSelectedGpuIndex: (index: number) => void; + setCurve: (c: CurveState) => void; + setGpuInfo: (g: GpuInfo) => void; + pushMonitor: (s: MonitoringSample) => void; + setActiveProfile: (name: string | null) => void; + + // Edit actions + stageEdit: (pointIndex: number, deltaKhz: number) => void; + stageMultiEdit: (edits: Map) => void; + stageRangeEdit: (points: VFPoint[], deltaKhz: number) => void; + discardEdits: () => void; + /** + * POST pending deltas to the backend, then call onSuccess (which re-fetches + * the curve) and clear the pending state. + */ + applyEdits: (onSuccess: () => void) => Promise; + /** POST /api/curve/reset, call onSuccess, clear pending state. */ + resetAllDeltas: (onSuccess: () => void) => Promise; + + // Selection actions + selectPoint: (index: number, multi?: boolean) => void; + selectRange: (indices: number[]) => void; + clearSelection: () => void; + /** + * Stage all selected points to the anchor point's current effective delta. + * Falls back to the lowest selected index if anchorPoint is null or deselected. + * No-op if fewer than 2 points are selected. + */ + flattenToAnchor: () => void; + + // Derived helper — effective MHz for a point including any pending delta + effectiveMhz: (point: VFPoint) => number; + // True if any pending delta would produce a negative effective frequency + hasNegativeFreqWarning: () => boolean; +} + +export const useCurveStore = create()((set, get) => ({ + availableGpus: [], + selectedGpuIndex: 0, + curve: null, + gpuInfo: null, + monitor: null, + monitorHistory: [], + activeProfile: null, + pendingDeltas: new Map(), + selectedPoints: new Set(), + anchorPoint: null, + + setAvailableGpus: (availableGpus) => set({ availableGpus }), + setSelectedGpuIndex: (selectedGpuIndex) => { + set({ selectedGpuIndex, curve: null, gpuInfo: null, monitor: null, monitorHistory: [], pendingDeltas: new Map(), selectedPoints: new Set(), anchorPoint: null }); + }, + setCurve: (curve) => set({ curve }), + setGpuInfo: (gpuInfo) => set({ gpuInfo }), + setActiveProfile: (activeProfile) => set({ activeProfile }), + pushMonitor: (sample) => + set((s) => { + const history = [...s.monitorHistory, sample]; + if (history.length > HISTORY_SIZE) history.shift(); + return { monitor: sample, monitorHistory: history }; + }), + + stageEdit: (pointIndex, deltaKhz) => + set((s) => { + const next = new Map(s.pendingDeltas); + const point = s.curve?.points.find(p => p.index === pointIndex); + if (point && point.delta_khz === deltaKhz) { + next.delete(pointIndex); + } else { + next.set(pointIndex, deltaKhz); + } + return { pendingDeltas: next }; + }), + + stageMultiEdit: (edits) => + set((s) => { + const next = new Map(s.pendingDeltas); + edits.forEach((deltaKhz, index) => { + const point = s.curve?.points.find(p => p.index === index); + if (point && point.delta_khz === deltaKhz) { + next.delete(index); + } else { + next.set(index, deltaKhz); + } + }); + return { pendingDeltas: next }; + }), + + stageRangeEdit: (points, deltaKhz) => + set((s) => { + const next = new Map(s.pendingDeltas); + for (const p of points) { + next.set(p.index, deltaKhz); + } + return { pendingDeltas: next }; + }), + + discardEdits: () => + set({ pendingDeltas: new Map(), selectedPoints: new Set(), anchorPoint: null }), + + applyEdits: async (onSuccess) => { + const { pendingDeltas, selectedGpuIndex } = get(); + if (pendingDeltas.size === 0) return; + + // Convert Map to plain record for the API + const deltas: Record = {}; + pendingDeltas.forEach((v, k) => { deltas[k] = v; }); + + try { + const result = await api.writeDeltas(deltas, selectedGpuIndex); + set({ pendingDeltas: new Map(), selectedPoints: new Set(), activeProfile: null }); + if (result?.freq_warnings?.length) { + toast.warning('Curve applied — driver clamped some points to 0 MHz (negative freq delta)'); + } else { + toast.success('Curve applied successfully'); + } + onSuccess(); + } catch (e: any) { + toast.error('Failed to apply curve: ' + (e.message || String(e))); + } + }, + + resetAllDeltas: async (onSuccess) => { + const { selectedGpuIndex } = get(); + try { + await api.resetCurve(selectedGpuIndex); + set({ pendingDeltas: new Map(), selectedPoints: new Set(), activeProfile: null }); + toast.success('Curve reset to hardware defaults'); + onSuccess(); + } catch (e: any) { + toast.error('Failed to reset curve: ' + (e.message || String(e))); + } + }, + + selectPoint: (index, multi = false) => + set((s) => { + const next = new Set(s.selectedPoints); + let anchor = s.anchorPoint; + if (multi) { + if (next.has(index)) { + next.delete(index); + if (anchor === index) anchor = next.size > 0 ? [...next].at(-1)! : null; + } else { + next.add(index); + anchor = index; // last explicitly added point is the new anchor + } + } else { + if (next.size === 1 && next.has(index)) { + next.clear(); + anchor = null; + } else { + next.clear(); + next.add(index); + anchor = index; + } + } + return { selectedPoints: next, anchorPoint: anchor }; + }), + + selectRange: (indices) => + // Bulk selects don't change the anchor — preserve it if still in the new selection. + set((s) => { + const next = new Set(indices); + const anchor = s.anchorPoint !== null && next.has(s.anchorPoint) ? s.anchorPoint : null; + return { selectedPoints: next, anchorPoint: anchor }; + }), + + clearSelection: () => + set({ selectedPoints: new Set(), anchorPoint: null }), + + flattenToAnchor: () => { + const { selectedPoints, anchorPoint, pendingDeltas, curve, stageMultiEdit } = get(); + if (selectedPoints.size < 2 || !curve) return; + + const anchor = anchorPoint !== null && selectedPoints.has(anchorPoint) + ? anchorPoint + : Math.min(...selectedPoints); + + const anchorPt = curve.points.find(p => p.index === anchor); + if (!anchorPt) return; + const anchorPendingDelta = pendingDeltas.get(anchor) ?? anchorPt.delta_khz; + const anchorEffectiveKhz = anchorPt.freq_khz + anchorPendingDelta; + + const edits = new Map(); + for (const idx of selectedPoints) { + const pt = curve.points.find(p => p.index === idx); + if (!pt) continue; + edits.set(idx, anchorEffectiveKhz - pt.freq_khz); + } + stageMultiEdit(edits); + }, + + effectiveMhz: (point) => { + const { pendingDeltas } = get(); + if (pendingDeltas.has(point.index)) { + // freq_mhz is the current effective (VFP freq, already includes applied delta). + // Adjust by the change in delta: (pending - current_delta). + const pendingKhz = pendingDeltas.get(point.index)!; + const deltaChange = pendingKhz - point.delta_khz; + return point.freq_mhz + deltaChange / 1000; + } + return point.freq_mhz; + }, + + hasNegativeFreqWarning: () => { + const { pendingDeltas, curve } = get(); + if (!curve || pendingDeltas.size === 0) return false; + for (const [index, pendingKhz] of pendingDeltas) { + const p = curve.points.find((pt) => pt.index === index); + if (!p || p.freq_khz === 0) continue; + // freq_khz is current effective; true base = freq_khz - delta_khz + // new effective = true_base + pendingKhz = freq_khz + (pendingKhz - delta_khz) + if (p.freq_khz + pendingKhz - p.delta_khz < 0) return true; + } + return false; + }, +})); diff --git a/frontend/src/types.ts b/frontend/src/types.ts new file mode 100644 index 0000000..f6188d5 --- /dev/null +++ b/frontend/src/types.ts @@ -0,0 +1,75 @@ +export interface VFPoint { + index: number; + freq_khz: number; + freq_mhz: number; + volt_uv: number; + volt_mv: number; + delta_khz: number; + delta_mhz: number; + effective_freq_khz: number; + effective_freq_mhz: number; + domain: 'gpu' | 'memory'; +} + +export interface CurveState { + gpu_name: string; + timestamp: number; + points: VFPoint[]; +} + +export interface MonitoringSample { + timestamp: number; + voltage_uv: number | null; + voltage_mv: number | null; + clock_mhz: number | null; + temp_c: number | null; + power_w: number | null; + fan_pct: number | null; + pstate: number | null; + pstate_label: string | null; + mem_used_bytes: number | null; + mem_total_bytes: number | null; + mem_used_mib: number | null; + mem_total_mib: number | null; + gpu_util_pct: number | null; + mem_util_pct: number | null; + mem_clock_mhz: number | null; +} + +export interface GpuInfo { + name: string; + index: number; + driver_version: string | null; + vram_bytes: number | null; + vram_gib: number | null; +} + +export interface SnapshotInfo { + filepath: string; + timestamp: string; + gpu: string; + nonzero_offsets: number; + size: number; +} + +export interface LimitsState { + // Power + power_limit_w: number | null; + default_power_limit_w: number | null; + min_power_limit_w: number | null; + max_power_limit_w: number | null; + // Clock offsets — current values + gpc_offset_mhz: number | null; + mem_offset_mhz: number | null; + // Memory offset — hardware-reported range + min_mem_offset_mhz: number | null; + max_mem_offset_mhz: number | null; +} + +export interface ProfileData { + name: string; + gpu_name: string; + curve_deltas: Record; + mem_offset_mhz: number | null; + power_limit_w: number | null; +} diff --git a/frontend/src/utils/curveHelpers.ts b/frontend/src/utils/curveHelpers.ts new file mode 100644 index 0000000..9aa9aaa --- /dev/null +++ b/frontend/src/utils/curveHelpers.ts @@ -0,0 +1,77 @@ +import type { VFPoint } from '../types'; + +/** + * Approximate reference frequency (MHz) for a point: effective − delta. + * + * `p.freq_mhz` is the current effective frequency as reported by GetVFPCurve + * (already including any applied boost delta). Subtracting the delta gives an + * approximation of the hardware base. + * + * Note: NVIDIA enforces monotonicity across the V/F curve — a large delta on + * a lower-voltage point pushes up neighbouring points' effective frequencies + * even when those points have zero delta. So for monotonicity-affected points, + * `effective - delta` doesn't recover the true unmodified base. It is still + * useful as a faint reference line ("where this point would sit with no boost"). + */ +export function refBaseMhz(p: VFPoint): number { + return p.freq_mhz - p.delta_mhz; +} + +/** Find which VF point the GPU is currently near based on voltage reading */ +export function findCurrentPoint( + points: VFPoint[], + voltage_mv: number | null, +): VFPoint | null { + if (voltage_mv == null || points.length === 0) return null; + return points.reduce((best, p) => + Math.abs(p.volt_mv - voltage_mv) < Math.abs(best.volt_mv - voltage_mv) ? p : best, + ); +} + +/** Voltage domain extent, with padding */ +export function voltExtent(points: VFPoint[], padMv = 20): [number, number] { + if (points.length === 0) return [600, 1100]; + const min = Math.min(...points.map((p) => p.volt_mv)); + const max = Math.max(...points.map((p) => p.volt_mv)); + return [min - padMv, max + padMv]; +} + +/** Frequency domain extent for the effective (boosted) curve, with padding */ +export function freqExtent(points: VFPoint[], padMhz = 50): [number, number] { + if (points.length === 0) return [1000, 3000]; + const allFreqs = points.flatMap((p) => [p.freq_mhz, refBaseMhz(p)]); + const min = Math.min(...allFreqs); + const max = Math.max(...points.map((p) => p.freq_mhz)); + return [min - padMhz, max + padMhz]; +} + +/** + * Detect points whose effective frequency is being held up by NVIDIA's + * monotonicity enforcement rather than their own offset. + * + * Walk points in voltage order, tracking the "ceiling" — the highest + * effective frequency seen so far and the offset that produced it. A point is + * clamped when: + * 1. Its effective freq is at or below the ceiling (hasn't moved past it) + * 2. Its own offset is lower than the offset that set the ceiling + * + * This catches cases like: point 103 has +950 MHz → effective 3907 MHz, + * point 104 has +315 MHz → effective also 3907 MHz (clamped). + */ +export function detectClampedPoints(points: VFPoint[]): Set { + const clamped = new Set(); + let ceiling = -Infinity; + let ceilingOffset = -Infinity; + + for (const p of points) { + if (p.freq_mhz <= ceiling && p.delta_khz < ceilingOffset) { + clamped.add(p.index); + } + if (p.freq_mhz > ceiling) { + ceiling = p.freq_mhz; + ceilingOffset = p.delta_khz; + } + } + + return clamped; +} diff --git a/frontend/src/utils/units.ts b/frontend/src/utils/units.ts new file mode 100644 index 0000000..df52bbd --- /dev/null +++ b/frontend/src/utils/units.ts @@ -0,0 +1,21 @@ +export const fmt = { + mhz: (v: number | null | undefined, decimals = 0) => + v == null ? '—' : `${v.toFixed(decimals)} MHz`, + mv: (v: number | null | undefined, decimals = 0) => + v == null ? '—' : `${v.toFixed(decimals)} mV`, + celsius: (v: number | null | undefined, decimals = 0) => + v == null ? '—' : `${v.toFixed(decimals)} °C`, + watts: (v: number | null | undefined, decimals = 0) => + v == null ? '—' : `${v.toFixed(decimals)} W`, + pct: (v: number | null | undefined, decimals = 0) => + v == null ? '—' : `${v.toFixed(decimals)}%`, + mib: (v: number | null | undefined) => + v == null ? '—' : `${v.toFixed(0)} MiB`, +}; + +export const conv = { + khzToMhz: (khz: number) => khz / 1000, + uvToMv: (uv: number) => uv / 1000, + mvToUv: (mv: number) => mv * 1000, + mhzToKhz: (mhz: number) => mhz * 1000, +}; diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..a9b5a59 --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..cd6b806 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' +import tailwindcss from '@tailwindcss/vite' + +export default defineConfig({ + plugins: [react(), tailwindcss()], + server: { + proxy: { + '/api': 'http://localhost:8042', + '/ws': { target: 'ws://localhost:8042', ws: true }, + }, + allowedHosts: ["localhost", ".localhost", ".ts.net"] + }, +}) diff --git a/nvcurve/__init__.py b/nvcurve/__init__.py new file mode 100644 index 0000000..1ff8abc --- /dev/null +++ b/nvcurve/__init__.py @@ -0,0 +1,3 @@ +"""nvcurve — Linux GPU V/F curve editor for NVIDIA GPUs.""" + +__version__ = "0.5.0" diff --git a/nvcurve/__main__.py b/nvcurve/__main__.py new file mode 100644 index 0000000..bec7271 --- /dev/null +++ b/nvcurve/__main__.py @@ -0,0 +1,10 @@ +"""Allow running as: python -m nvcurve""" + +import sys + +if len(sys.argv) > 1 and sys.argv[1] == "daemon": + from .daemon import run + run() +else: + from .cli import main + main() diff --git a/nvcurve/cli.py b/nvcurve/cli.py new file mode 100644 index 0000000..9772fab --- /dev/null +++ b/nvcurve/cli.py @@ -0,0 +1,1838 @@ +"""nvcurve CLI + +Normal use: + nvcurve Launch web UI (escalates to root if needed) + nvcurve read [--full|--json] Read V/F curve (escalates to root) + nvcurve write [--point N|--range A-B|--global|--reset] --delta D [--dry-run] + nvcurve verify --point N --delta D Write-verify cycle (requires root) + nvcurve snapshot [save|restore|list] Manage snapshots + nvcurve gpus List detected NVIDIA GPUs + nvcurve profile [save|apply|list|default] Manage profiles + +Web server (on-demand, for the GUI): + nvcurve serve start [--detach] Start web server (escalates to root) + nvcurve serve stop Stop running web server + nvcurve serve status Check web server status + +Daemon (systemd service for auto-load profiles): + nvcurve daemon Run the daemon (requires root) + nvcurve autoload Apply auto-load profiles from config (requires root) + nvcurve service install [--serve] Register systemd daemon (escalates to root) + nvcurve service configure Update config + restart daemon (escalates to root) + nvcurve service uninstall Remove systemd service (escalates to root) + nvcurve service start/stop/restart/status Manage systemd service + +First-time / diagnostic commands (bypass server, escalate to root): + nvcurve setup Hardware compatibility check (diag + verify + restore) + nvcurve read --diag Probe all NvAPI functions + nvcurve read --raw Raw hex dumps of hardware buffers + nvcurve inspect [--point N|--range A-B] Raw ClockBoostTable field detail +""" + +import argparse +import json +import struct +import sys +import time +import os + +from .config import Config, default_config +from .client import NvCurveClient, ServerNotRunning, ApiError +from .nvapi.constants import ( + VFP_SIZE, VFP_BASE, VFP_STRIDE, + CT_SIZE, CT_BASE, CT_STRIDE, + CT_POINTS, +) + + +# ── Utilities ───────────────────────────────────────────────────────────────── + +def hexdump(data: bytes, start: int, length: int, cols: int = 16) -> str: + lines = [] + end = min(start + length, len(data)) + for off in range(start, end, cols): + chunk = data[off:off + cols] + hx = " ".join(f"{b:02x}" for b in chunk) + asc = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk) + lines.append(f" {off:04x}: {hx:<{cols * 3}} {asc}") + return "\n".join(lines) + + +def parse_range(s: str): + """Parse 'A-B' into (A, B) tuple.""" + parts = s.split("-") + if len(parts) != 2: + raise argparse.ArgumentTypeError(f"Expected A-B format, got '{s}'") + try: + a, b = int(parts[0]), int(parts[1]) + except ValueError: + raise argparse.ArgumentTypeError(f"Non-integer in range: '{s}'") + if a > b: + raise argparse.ArgumentTypeError(f"Start > end in range: {a}-{b}") + if a < 0 or b >= CT_POINTS: + raise argparse.ArgumentTypeError(f"Range {a}-{b} outside 0–{CT_POINTS - 1}") + return (a, b) + + +# ── Output formatters ───────────────────────────────────────────────────────── + +def print_curve(points, offsets, voltage, domains=None, full=False): + """Print formatted V/F curve table.""" + if voltage: + print(f"Current voltage: {voltage / 1000:.1f} mV") + + if domains: + gpu_count = sum(1 for d in domains if d == "gpu") + mem_count = sum(1 for d in domains if d == "memory") + parts = [f"{gpu_count} GPU core points"] + if mem_count: + parts.append(f"{mem_count} memory points") + parts.append(f"{gpu_count + mem_count} total") + print(f"Curve: {', '.join(parts)}") + print() + + current_idx = None + if voltage: + for i, (f, v) in enumerate(points): + if v > 0 and abs(v - voltage) < 10000: + current_idx = i + break + + if full: + show = list(range(len(points))) + else: + show = [] + prev_freq = -1 + for i, (f, v) in enumerate(points): + if f == 0 and v == 0: + continue + if domains and i < len(domains) and domains[i] == "memory": + show.append(i) + elif f != prev_freq or i == len(points) - 1: + show.append(i) + prev_freq = f + + if domains: + print(f"{'#':>3s} {'Freq':>8s} {'Voltage':>8s} {'Offset':>8s} {'Domain'}") + print("-" * 56) + else: + print(f"{'#':>3s} {'Freq':>8s} {'Voltage':>8s} {'Offset':>8s}") + print("-" * 42) + + for i in show: + f, v = points[i] + if f == 0 and v == 0: + continue + freq_s = f"{f / 1000:.0f} MHz" + volt_s = f"{v / 1000:.0f} mV" + offset_s = "" + if offsets and offsets[i] != 0: + offset_s = f"{offsets[i] / 1000:+.0f} MHz" + domain = domains[i] if domains and i < len(domains) else "" + marker = "" + if current_idx is not None and i == current_idx: + marker = " <-- current" + elif f < 1_000_000 and v > 0 and not domain: + marker = " (low-power)" + if domains: + print(f"{i:3d} {freq_s:>8s} {volt_s:>8s} {offset_s:>8s} {domain}{marker}") + else: + print(f"{i:3d} {freq_s:>8s} {volt_s:>8s} {offset_s:>8s}{marker}") + + print() + if domains: + gpu_idxs = [i for i, d in enumerate(domains) if d == "gpu"] + mem_idxs = [i for i, d in enumerate(domains) if d == "memory"] + + gpu_active = [(points[i][0], points[i][1]) for i in gpu_idxs + if i < len(points) and points[i][0] > 0] + if gpu_active: + freqs = [f for f, v in gpu_active] + volts = [v for f, v in gpu_active] + print(f"GPU core: {min(freqs)/1000:.0f} – {max(freqs)/1000:.0f} MHz, " + f"{min(volts)/1000:.0f} – {max(volts)/1000:.0f} mV " + f"({len(gpu_active)} points)") + + mem_active = [(points[i][0], points[i][1]) for i in mem_idxs + if i < len(points) and points[i][0] > 0] + if mem_active: + freqs = [f for f, v in mem_active] + volts = [v for f, v in mem_active] + print(f"Memory: {min(freqs)/1000:.0f} – {max(freqs)/1000:.0f} MHz, " + f"{min(volts)/1000:.0f} – {max(volts)/1000:.0f} mV " + f"({len(mem_active)} points)") + + if offsets: + gpu_offsets = [offsets[i] for i in gpu_idxs + if i < len(offsets) and offsets[i] != 0] + if gpu_offsets: + vals = set(gpu_offsets) + if len(vals) == 1: + print(f"GPU offset: {next(iter(vals))/1000:+.0f} MHz " + f"(uniform across {len(gpu_offsets)} points)") + else: + print(f"GPU offsets: {len(gpu_offsets)} points active " + f"(range: {min(vals)/1000:+.0f} to {max(vals)/1000:+.0f} MHz)") + else: + active = [(f, v) for f, v in points if f > 0 and v > 0] + if active: + freqs = [f for f, v in active] + volts = [v for f, v in active] + print(f"Frequency range: {min(freqs)/1000:.0f} – {max(freqs)/1000:.0f} MHz") + print(f"Voltage range: {min(volts)/1000:.0f} – {max(volts)/1000:.0f} mV") + print(f"V/F points: {len(active)}") + if offsets: + nonzero = sum(1 for o in offsets if o != 0) + if nonzero > 0: + vals = set(o for o in offsets if o != 0) + if len(vals) == 1: + print(f"Global offset: {next(iter(vals))/1000:+.0f} MHz " + f"(applied to {nonzero} points)") + else: + print(f"Per-point offsets active on {nonzero} points " + f"(range: {min(vals)/1000:+.0f} to {max(vals)/1000:+.0f} MHz)") + + +def output_json(gpu_name, points, offsets, voltage, domains=None): + """Output JSON format.""" + gpu_points = [i for i, d in enumerate(domains) if d == "gpu"] if domains else [] + mem_points = [i for i, d in enumerate(domains) if d == "memory"] if domains else [] + data = { + "gpu": gpu_name, + "current_voltage_uV": voltage, + "layout": { + "vfp_curve": {"size": VFP_SIZE, "base": VFP_BASE, + "stride": VFP_STRIDE, "max_entries": len(points)}, + "clock_table": {"size": CT_SIZE, "base": CT_BASE, + "stride": CT_STRIDE, "delta_offset": 0x14, + "max_entries": CT_POINTS}, + }, + "curve_info": { + "gpu_points": gpu_points, + "mem_points": mem_points, + "total_points": len(points), + }, + "vf_curve": [], + } + if points: + for i, (f, v) in enumerate(points): + if f > 0 or v > 0: + entry = {"index": i, "freq_kHz": f, "volt_uV": v} + if offsets: + entry["freq_offset_kHz"] = offsets[i] + if domains and i < len(domains): + entry["domain"] = domains[i] + data["vf_curve"].append(entry) + print(json.dumps(data, indent=2)) + + +# ── Diagnostics (direct HAL, root required) ─────────────────────────────────── + +def run_diagnostics(gpu, gpu_name, gpu_index: int = 0): + """Probe all known NvAPI functions and report results.""" + from .hal.vfcurve import get_boost_mask + from .hal.monitoring import init_nvml, get_driver_version, get_vram_total + from .hal.limits import get_power_limit, get_clock_offsets, get_mem_offset_range + from .nvapi.bootstrap import nvcall, query_interface + from .nvapi.constants import FUNC, MASK_SIZE, VOLT_SIZE, RANGES_SIZE, PERF_SIZE, VBOOST_SIZE + + init_nvml() # best-effort; diagnostics degrade gracefully without it + + # ── System info ─────────────────────────────────────────────────────────── + print("=== System ===") + print() + driver = get_driver_version() + vram = get_vram_total(gpu_index) + print(f" GPU: {gpu_name}") + print(f" Driver: {driver or '(unavailable — NVML not initialised)'}") + if vram is not None: + print(f" VRAM: {vram / (1024 ** 3):.1f} GiB ({vram:,} bytes)") + else: + print(" VRAM: (unavailable)") + print() + + # ── Function probe ──────────────────────────────────────────────────────── + print("=== Function probe ===") + print() + + probes = [ + ("GetVFPCurve", FUNC["GetVFPCurve"], VFP_SIZE, 1, True), + ("GetClockBoostMask", FUNC["GetClockBoostMask"], MASK_SIZE, 1, True), + ("GetClockBoostTable", FUNC["GetClockBoostTable"], CT_SIZE, 1, True), + ("GetCurrentVoltage", FUNC["GetCurrentVoltage"], VOLT_SIZE, 1, False), + ("GetClockBoostRanges", FUNC["GetClockBoostRanges"], RANGES_SIZE, 1, False), + ("GetPerfLimits", FUNC["GetPerfLimits"], PERF_SIZE, 2, False), + ("GetVoltBoostPercent", FUNC["GetVoltBoostPercent"], VBOOST_SIZE, 1, False), + ("SetClockBoostTable", FUNC["SetClockBoostTable"], CT_SIZE, 1, True), + ] + + for name, fid, size, ver, needs_mask in probes: + ptr = query_interface(fid) + resolved = "resolved" if ptr else "NOT FOUND" + print(f" {name:30s} 0x{fid:08X} size=0x{size:04X} ver={ver} {resolved}") + + # ── Read function tests ─────────────────────────────────────────────────── + mask_bytes = None + print() + print("=== Read function tests ===") + + mask_bytes, mask_err = get_boost_mask(gpu) + if not mask_bytes: + print(f" WARNING: Failed to get boost mask: {mask_err}") + + for name, fid, size, ver, needs_mask in probes: + if name.startswith("Set"): + continue + + def fill(buf, _nm=needs_mask, _fid=fid, _mask=mask_bytes): + if _nm and _mask: + for i in range(32): + buf[4 + i] = _mask[i] + + d, err = nvcall(fid, gpu, size, ver=ver, pre_fill=fill) + status = f"OK ({len(d)} bytes)" if d else f"FAILED: {err}" + print(f" {name:30s} {status}") + if d: + vw = struct.unpack_from(" None: + """Open URL in browser, switching back to the original user if running under sudo.""" + import subprocess + sudo_user = os.environ.get("SUDO_USER") + if sudo_user and os.geteuid() == 0: + try: + subprocess.Popen( + ["runuser", "-u", sudo_user, "--", "xdg-open", url], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return + except Exception: + pass + import webbrowser + webbrowser.open(url) + + + +def require_root(): + """Ensure the process is running as root, re-invoking via sudo if necessary.""" + if os.geteuid() != 0: + # Forward display/session vars so the server can open the browser as the + # original user (Wayland sockets are user-owned; Firefox refuses to run as root). + passthrough = [ + f"{k}={v}" + for k in ("DISPLAY", "WAYLAND_DISPLAY", "XDG_RUNTIME_DIR", + "DBUS_SESSION_BUS_ADDRESS", "XAUTHORITY") + if (v := os.environ.get(k)) + ] + try: + # PYTHONDONTWRITEBYTECODE prevents root-owned __pycache__ in site-packages. + os.execvp("sudo", [ + "sudo", "env", "PYTHONDONTWRITEBYTECODE=1", *passthrough, + sys.executable, "-m", "nvcurve", *sys.argv[1:] + ]) + except Exception as e: + print(f"nvcurve: sudo failed: {e}", file=sys.stderr) + sys.exit(1) + + +_SERVER_INFO_FILE = "/run/nvcurve.json" # runtime: written by server, deleted on exit +_PERSISTENT_CONFIG_FILE = "/etc/nvcurve/config.json" # persistent: written by service install +_DAEMON_SOCKET_PATH = "/run/nvcurve-daemon.sock" + +_ALLOWED_HOSTS = {"127.0.0.1", "::1", "localhost"} + + +def _daemon_send(cmd: dict) -> dict | None: + """Send a JSON command to the daemon and return its response. + + Returns None if the daemon socket is not available (daemon not running). + """ + import socket as _socket + try: + with _socket.socket(_socket.AF_UNIX, _socket.SOCK_STREAM) as sock: + sock.settimeout(5.0) + sock.connect(_DAEMON_SOCKET_PATH) + sock.sendall(json.dumps(cmd).encode() + b"\n") + buf = b"" + while not buf.endswith(b"\n"): + chunk = sock.recv(4096) + if not chunk: + break + buf += chunk + return json.loads(buf) + except (FileNotFoundError, ConnectionRefusedError, OSError): + return None + + +def _safe_host(host: str, cfg: Config) -> str: + """Return host if it is a loopback address, otherwise fall back to cfg.host. + + 0.0.0.0 (bind-all) is silently remapped to 127.0.0.1 — it's a valid local + server address, just not usable as a client connection target. + """ + if host in ("0.0.0.0", "::"): + return "127.0.0.1" + if host not in _ALLOWED_HOSTS: + print(f"nvcurve: ignoring untrusted host '{host}' in server info; " + f"using {cfg.host}", file=sys.stderr) + return cfg.host + return host + + +def _log_file() -> str: + return "/var/log/nvcurve.log" if os.geteuid() == 0 else "/tmp/nvcurve.log" + + +def _read_server_info() -> dict | None: + """Read the server's runtime info (host, port, pid) from its info file. + + The file is written by the server process on startup and deleted on exit. + Returns None if the file is absent, stale, or unreadable. + """ + try: + with open(_SERVER_INFO_FILE) as f: + info = json.load(f) + # Verify the process is still alive + os.kill(info["pid"], 0) + return info + except (FileNotFoundError, KeyError, ProcessLookupError, OSError, json.JSONDecodeError): + return None + + +def _discover_server_url(cfg: Config) -> str: + """Return the server's base URL using a three-level priority chain: + + 1. /run/nvcurve.json — runtime info written by the running server process + 2. /etc/nvcurve/config.json — persistent config written by `service install` + 3. Config defaults — 127.0.0.1:8042 + """ + # 1. Runtime info (most accurate — reflects the actual running port) + info = _read_server_info() + if info: + host = _safe_host(info["host"], cfg) + return f"http://{host}:{info['port']}" + + # 2. Persistent config (survives reboots; written by `service install`) + try: + with open(_PERSISTENT_CONFIG_FILE) as f: + data = json.load(f) + host = _safe_host(data.get("host", cfg.host), cfg) + port = data.get("port", cfg.port) + return f"http://{host}:{port}" + except (FileNotFoundError, json.JSONDecodeError, KeyError): + pass + + # 3. Hardcoded defaults + return f"http://{cfg.host}:{cfg.port}" + + +# ── Subcommand handlers ─────────────────────────────────────────────────────── + +def _show_curve(gpu_name, points, offsets, voltage, args, domains=None) -> None: + """Format and print curve data — shared by HTTP and direct-HAL paths.""" + if args.json: + output_json(gpu_name, points, offsets, voltage, domains=domains) + return + print(f"GPU: {gpu_name}") + print_curve(points, offsets, voltage, domains=domains, full=args.full) + + +def cmd_read(args): + # ── Direct HAL paths (need root) ────────────────────────────────────────── + if args.diag: + require_root() + from .hal.gpu import get_gpu + gpu, gpu_name = get_gpu(index=getattr(args, "gpu_index", 0)) + run_diagnostics(gpu, gpu_name, gpu_index=getattr(args, "gpu_index", 0)) + return + + if args.raw: + require_root() + from .hal.gpu import get_gpu + from .hal.vfcurve import read_clock_table_raw, get_boost_mask + from .hal.monitoring import read_voltage + from .nvapi.bootstrap import nvcall + from .nvapi.constants import FUNC + gpu, gpu_name = get_gpu(index=getattr(args, "gpu_index", 0)) + + print(f"GPU: {gpu_name}") + + mask_bytes, _ = get_boost_mask(gpu) + def fill_vfp(buf): + if mask_bytes: + for i in range(32): + buf[4 + i] = mask_bytes[i] + + vfp_raw, _ = nvcall(FUNC["GetVFPCurve"], gpu, VFP_SIZE, ver=1, pre_fill=fill_vfp) + ct_raw, _ = read_clock_table_raw(gpu) + + if vfp_raw: + print() + print("=== VFP Curve (0x21537AD4) — header + first entries ===") + print(hexdump(vfp_raw, 0x00, 0x48)) + print(" --- data at 0x48, stride 0x1C ---") + print(hexdump(vfp_raw, 0x48, VFP_STRIDE * 5)) + + if ct_raw: + print() + print("=== ClockBoostTable (0x23F1B133) — header + first entries ===") + print(hexdump(ct_raw, 0x00, 0x44)) + print(" --- data at 0x44, stride 0x24, freqDelta at +0x14 ---") + print(hexdump(ct_raw, 0x44, CT_STRIDE * 5)) + print() + + from .hal.vfcurve import read_curve + curve_state, _ = read_curve(gpu, gpu_name) + voltage, _ = read_voltage(gpu) + if curve_state: + points = [(p.freq_khz, p.volt_uv) for p in curve_state.points] + offsets = [p.delta_khz for p in curve_state.points] + domains = [p.domain for p in curve_state.points] + _show_curve(gpu_name, points, offsets, voltage, args, domains=domains) + return + + # ── Normal path — direct HAL (requires root) ────────────────────────────── + require_root() + from .hal.gpu import get_gpu + from .hal.vfcurve import read_curve + from .hal.monitoring import read_voltage as _read_voltage + gpu, gpu_name = get_gpu(index=getattr(args, "gpu_index", 0)) + curve_state, curve_err = read_curve(gpu, gpu_name) + if not curve_state: + print(f"Failed to read V/F curve: {curve_err}") + return + voltage, _ = _read_voltage(gpu) + points = [(p.freq_khz, p.volt_uv) for p in curve_state.points] + offsets = [p.delta_khz for p in curve_state.points] + domains = [p.domain for p in curve_state.points] + _show_curve(gpu_name, points, offsets, voltage, args, domains=domains) + + +def cmd_inspect(args): + """Show detailed raw ClockBoostTable fields. Requires root (direct HAL).""" + require_root() + from .hal.gpu import get_gpu + from .hal.vfcurve import read_clock_table_raw, read_clock_entry_full, read_curve + + gpu, gpu_name = get_gpu(index=getattr(args, "gpu_index", 0)) + raw, err = read_clock_table_raw(gpu) + if not raw: + print(f"Failed to read ClockBoostTable: {err}") + return + + curve_state, _ = read_curve(gpu, gpu_name) + points_data = {} + gpu_indices = set() + mem_indices = set() + if curve_state: + for p in curve_state.points: + points_data[p.index] = (p.freq_khz, p.volt_uv) + if p.domain == "memory": + mem_indices.add(p.index) + else: + gpu_indices.add(p.index) + + if args.point is not None: + indices = [args.point] + elif args.range: + indices = list(range(args.range[0], args.range[1] + 1)) + else: + defaults = [0, 1, 50, 51, 80, 126] + if mem_indices: + mp = min(mem_indices) + defaults.extend([mp - 1, mp, mp + 1, max(mem_indices)]) + else: + defaults.append(127) + indices = sorted(set(defaults)) + + print(f"GPU: {gpu_name}") + if curve_state: + parts = [f"{len(gpu_indices)} GPU core points"] + if mem_indices: + parts.append(f"{len(mem_indices)} memory points") + parts.append(f"{len(gpu_indices) + len(mem_indices)} total") + print(f"Curve: {', '.join(parts)}") + print(f"ClockBoostTable entry detail (stride=0x{CT_STRIDE:02X}, " + f"9 fields × 4 bytes)") + print() + + for p in indices: + if p < 0 or p >= CT_POINTS: + continue + entry = read_clock_entry_full(raw, p) + off = CT_BASE + p * CT_STRIDE + + domain_label = "" + if p in mem_indices: + domain_label = " [MEMORY]" + elif p in gpu_indices: + domain_label = " [GPU]" + + freq_str = "" + if p in points_data: + f, v = points_data[p] + freq_str = f" (VFP: {f/1000:.0f} MHz @ {v/1000:.0f} mV)" + + print(f"Point {p:3d} — buffer offset 0x{off:04X}{domain_label}{freq_str}") + for key, val in entry.items(): + if key == "freqDelta_kHz": + continue + if "0x14" in key: + print(f" {key}: {val:12d} (0x{val & 0xFFFFFFFF:08X})" + f" = {val/1000:+.0f} MHz ← freqDelta") + else: + print(f" {key}: {val:12d} (0x{val:08X})") + print() + + +def cmd_write(args): + delta_khz = int(args.delta * 1000) + max_delta_khz = int(args.max_delta * 1000) if args.max_delta is not None else None + point_deltas = {} + + if args.reset: + if args.dry_run: + print("DRY RUN — would reset all offsets to 0.") + return + require_root() + from .hal.gpu import get_gpu + from .hal.vfcurve import reset_offsets + gpu, _ = get_gpu(index=getattr(args, "gpu_index", 0)) + reset_offsets(gpu) + print("Reset: all offsets set to 0.") + return + + elif args.point is not None: + point_deltas[args.point] = delta_khz + print(f"Target: point {args.point}, delta {args.delta:+.0f} MHz " + f"({delta_khz:+d} kHz)") + + elif args.range: + start, end = args.range + for i in range(start, end + 1): + point_deltas[i] = delta_khz + print(f"Target: points {start}–{end} ({len(point_deltas)} points), " + f"delta {args.delta:+.0f} MHz") + + elif args.glob: + print(f"Target: all active points (global), " + f"delta {args.delta:+.0f} MHz") + + else: + print("Error: specify --point N, --range A-B, --global, or --reset") + return + + if args.dry_run: + if args.glob: + print() + print("DRY RUN — would send:") + print(f" Target: Global active points") + print(f" Delta: {delta_khz:+d} kHz ({args.delta:+.0f} MHz)") + else: + keys = sorted(point_deltas.keys()) + preview = keys[:5] + tail = f"...and {len(keys) - 5} more" if len(keys) > 5 else "" + print() + print("DRY RUN — would send:") + print(f" Points: {preview}{(' ' + tail) if tail else ''}") + print(f" Delta: {delta_khz:+d} kHz ({args.delta:+.0f} MHz)") + if max_delta_khz is not None: + print(f" Max delta override: {args.max_delta:+.0f} MHz") + return + + require_root() + from .hal.gpu import get_gpu + from .hal.vfcurve import write_offsets, read_curve + from .safety import validate_write, check_negative_freq_warnings + + gpu_index = getattr(args, "gpu_index", 0) + gpu, gpu_name = get_gpu(index=gpu_index) + + if args.glob: + # Build per-point deltas for all active GPU-domain points (mirrors server behaviour) + curve_state, curve_err = read_curve(gpu, gpu_name) + if not curve_state: + print(f"Failed to read curve: {curve_err}", file=sys.stderr) + sys.exit(1) + point_deltas = {p.index: delta_khz for p in curve_state.points if p.domain == "gpu"} + + effective_max = max_delta_khz if max_delta_khz is not None else default_config.max_delta_khz + errors = validate_write(point_deltas, effective_max) + if errors: + for e in errors: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + if default_config.auto_snapshot: + from .hal.snapshot import save as snapshot_save + snapshot_save(gpu, gpu_name, default_config.snapshot_dir, default_config.max_snapshots) + + ret, desc = write_offsets(gpu, point_deltas) + if ret != 0: + print(f"Write failed ({ret}): {desc}", file=sys.stderr) + sys.exit(1) + + print(f"Write OK — {len(point_deltas)} point(s) updated.") + + try: + if not args.glob: + curve_state, _ = read_curve(gpu, gpu_name) + if curve_state: + vfp_freqs = [p.freq_khz for p in curve_state.points] + for w in check_negative_freq_warnings(point_deltas, vfp_freqs, []): + print(f"WARNING: {w}") + except Exception: + pass + + +def cmd_verify(args): + """Write-verify-read cycle — runs directly against hardware (requires root).""" + require_root() + + from .hal.gpu import get_gpu + from .hal.vfcurve import read_clock_offsets, write_offsets + from .hal.snapshot import save as snapshot_save + + delta_khz = int(args.delta * 1000) + + if args.point is not None: + points = [args.point] + elif args.range: + points = list(range(args.range[0], args.range[1] + 1)) + else: + print("Error: --point or --range required for verify mode") + return + + point_deltas = {p: delta_khz for p in points} + + gpu, gpu_name = get_gpu(index=getattr(args, "gpu_index", 0)) + + print("=== Write-Verify Cycle ===") + print(f"GPU: {gpu_name}") + print(f"Points: {points[0]}{'–' + str(points[-1]) if len(points) > 1 else ''}") + print(f"Delta: {args.delta:+.0f} MHz ({delta_khz:+d} kHz)") + print() + + # Step 1: read before state + before_offsets, err = read_clock_offsets(gpu) + if before_offsets is None: + print(f"Failed to read current state: {err}") + return + + # Step 2: snapshot before write + filepath = snapshot_save(gpu, gpu_name, default_config.snapshot_dir) + if filepath: + print(f"Snapshot saved: {filepath}") + + # Step 3: write + print("Writing and verifying...") + ret, desc = write_offsets(gpu, point_deltas) + if ret != 0: + print(f"Write failed ({ret}): {desc}") + return + + time.sleep(0.2) + + # Step 4: read after state + after_offsets, err = read_clock_offsets(gpu) + if after_offsets is None: + print(f"Verification read failed: {err}") + return + + print() + print("Verification results:") + all_matched = True + for p in sorted(point_deltas): + expected = point_deltas[p] + actual = after_offsets[p] if p < len(after_offsets) else 0 + match = actual == expected + if not match: + all_matched = False + match_s = "OK" if match else "MISMATCH" + print(f" Point {p:3d}: expected {expected/1000:+8.0f} MHz, " + f"got {actual/1000:+8.0f} MHz [{match_s}]") + + collateral = [ + {"point": i, "before_khz": before_offsets[i], "after_khz": after_offsets[i]} + for i in range(min(len(before_offsets), len(after_offsets))) + if i not in point_deltas and before_offsets[i] != after_offsets[i] + ] + print() + if collateral: + print("Unintended side effects detected:") + for c in collateral: + print(f" WARNING: Point {c['point']} changed: " + f"{c['before_khz']/1000:+.0f} → {c['after_khz']/1000:+.0f} MHz") + else: + print("No unintended side effects detected.") + + print() + print("=" * 50) + if all_matched and not collateral: + print("RESULT: Write verified successfully.") + elif not all_matched: + print("RESULT: Write verification FAILED — offsets don't match.") + else: + print("RESULT: Write applied but with unexpected side effects.") + + print() + print("To undo this change, run:") + print(" nvcurve snapshot restore") + + +def cmd_snapshot(args): + if args.action == "save": + require_root() + from .hal.gpu import get_gpu + from .hal.snapshot import save as _snapshot_save + gpu, gpu_name = get_gpu(index=getattr(args, "gpu_index", 0)) + path = _snapshot_save(gpu, gpu_name, default_config.snapshot_dir, default_config.max_snapshots) + if path is None: + print("Failed to save snapshot.", file=sys.stderr) + sys.exit(1) + print(f"Snapshot saved: {path}") + + elif args.action == "restore": + require_root() + from .hal.gpu import get_gpu + from .hal.snapshot import restore as _snapshot_restore + gpu, _ = get_gpu(index=getattr(args, "gpu_index", 0)) + ok = _snapshot_restore(gpu, default_config.snapshot_dir, args.file) + if not ok: + print("Restore failed — no snapshot found.", file=sys.stderr) + sys.exit(1) + print("Snapshot restored.") + + elif args.action == "list": + from .hal.snapshot import list_snapshots as _list_snapshots + snapshots = [ + {"filepath": s.filepath, "timestamp": s.timestamp, + "gpu": s.gpu, "nonzero_offsets": s.nonzero_offsets} + for s in _list_snapshots(default_config.snapshot_dir) + ] + if not snapshots: + print("No snapshots found.") + return + print("Snapshots:") + for s in snapshots: + print(f" {s['timestamp']} {s['gpu']} non-zero: {s['nonzero_offsets']}") + print(f" {s['filepath']}") + + +def _profile_config_read() -> dict: + """Read /etc/nvcurve/config.json, returning {} if absent or unreadable.""" + import json as _json + try: + with open(_PERSISTENT_CONFIG_FILE) as f: + return _json.load(f) + except Exception: + return {} + + +def _profile_config_write(key: str, value) -> None: + """Write a single key into /etc/nvcurve/config.json (creates or updates).""" + import json as _json + import os as _os + _os.makedirs(_os.path.dirname(_PERSISTENT_CONFIG_FILE), exist_ok=True) + data = _profile_config_read() + if value is None: + data.pop(key, None) + else: + data[key] = value + with open(_PERSISTENT_CONFIG_FILE, "w") as f: + _json.dump(data, f, indent=2) + + +def _gpu_stable_key_offline(gpu_index: int) -> str | None: + """Resolve a stable GPU key offline (without the server) by calling discover_gpus(). + + Returns None if the GPU index does not exist (discovery succeeded but index not found). + Falls back to 'idx:{n}' if discovery itself fails (can't tell whether GPU exists). + """ + try: + from .hal.gpu import discover_gpus + infos = discover_gpus() + for info in infos: + if info.index == gpu_index: + if info.uuid: + return info.uuid + if info.pci_bus_id is not None: + return f"pci:{info.pci_bus_id:04x}" + return f"idx:{gpu_index}" + # Discovery succeeded but this index wasn't among the found GPUs. + return None + except Exception: + # Discovery failed entirely — fall back, can't validate existence. + return f"idx:{gpu_index}" + + +def _profile_config_set_default(gpu_index: int, name: str | None) -> None: + """Set or clear the default profile for a specific GPU in config.json.""" + import json as _json + import os as _os + _os.makedirs(_os.path.dirname(_PERSISTENT_CONFIG_FILE), exist_ok=True) + data = _profile_config_read() + # Migrate old single-key format on write + if "auto_load_profile" in data: + data.setdefault("auto_load_profiles", {})["idx:0"] = data.pop("auto_load_profile") + gpu_key = _gpu_stable_key_offline(gpu_index) + if gpu_key is None: + raise ValueError(f"GPU {gpu_index} not found") + profiles = data.setdefault("auto_load_profiles", {}) + if name is None: + profiles.pop(gpu_key, None) + else: + profiles[gpu_key] = name + if not profiles: + data.pop("auto_load_profiles", None) + with open(_PERSISTENT_CONFIG_FILE, "w") as f: + _json.dump(data, f, indent=2) + + +def cmd_profile(args): + if args.action == "list": + import glob as _glob, json as _json, os as _os + gpu_index = getattr(args, "gpu_index", 0) + profile_dir = default_config.profile_dir + cfg_data = _profile_config_read() + raw_defaults = cfg_data.get("auto_load_profiles", {}) + if not raw_defaults and "auto_load_profile" in cfg_data: + raw_defaults = {"idx:0": cfg_data["auto_load_profile"]} + gpu_key = _gpu_stable_key_offline(gpu_index) + auto_load = raw_defaults.get(gpu_key) if gpu_key is not None else None + raw = sorted(_glob.glob(_os.path.join(profile_dir, "*.json"))) + profiles = [] + for path in raw: + try: + with open(path) as f: + p = _json.load(f) + name = _os.path.splitext(_os.path.basename(path))[0] + profiles.append({"name": name, "curve_deltas": p.get("curve_deltas", {})}) + except Exception: + pass + if not profiles: + print("No profiles found.") + return + print("Profiles:") + for p in profiles: + markers = [] + if p["name"] == auto_load: markers.append("default") + marker_str = f" [{', '.join(markers)}]" if markers else "" + pts = len(p["curve_deltas"]) + print(f" - {p['name']} ({pts} pts){marker_str}") + + elif args.action == "default": + clearing = getattr(args, "clear", False) + gpu_index = getattr(args, "gpu_index", 0) + if not clearing and not args.name: + print("Error: profile name required (or use --clear)") + return + require_root() + try: + _profile_config_set_default(gpu_index, None if clearing else args.name) + except ValueError as e: + print(f"Error: {e}", file=sys.stderr) + return + if clearing: + print(f"Auto-load profile cleared for GPU {gpu_index}.") + else: + print(f"Auto-load profile set to '{args.name}' for GPU {gpu_index}.") + + elif args.action == "save": + if not args.name: + print("Error: profile name required for save") + return + require_root() + import os as _os + from .hal.gpu import get_gpu + from .hal.vfcurve import read_curve + from .hal.limits import get_clock_offsets, get_power_limit + from .profiles.native import ProfileData, save_profile + + gpu_index = getattr(args, "gpu_index", 0) + gpu, gpu_name = get_gpu(index=gpu_index) + + curve_state, curve_err = read_curve(gpu, gpu_name) + if not curve_state: + print(f"Failed to read curve: {curve_err}", file=sys.stderr) + sys.exit(1) + + curve_deltas = {str(p.index): p.delta_khz for p in curve_state.points if p.delta_khz != 0} + + try: + power_info = get_power_limit(gpu_index) + offsets = get_clock_offsets(gpu_index) + power_limit_w = power_info.get("power_limit_w") + mem_offset_mhz = offsets.get("mem_offset_mhz") + except Exception: + power_limit_w = None + mem_offset_mhz = None + + data = ProfileData( + name=args.name, + gpu_name=gpu_name, + curve_deltas=curve_deltas, + mem_offset_mhz=mem_offset_mhz, + power_limit_w=power_limit_w, + ) + filepath = save_profile(default_config.profile_dir, data) + print(f"Saved profile '{args.name}' to {filepath}") + + elif args.action == "apply": + if not args.name: + print("Error: profile name required for apply") + return + require_root() + import os as _os + from .hal.gpu import get_gpu + from .hal.limits import set_clock_offsets, set_power_limit + from .hal.vfcurve import write_offsets, reset_offsets + from .hal.snapshot import save as snapshot_save + from .profiles.native import load_profile + from .safety import validate_write + + gpu_index = getattr(args, "gpu_index", 0) + gpu, gpu_name = get_gpu(index=gpu_index) + + safe_name = "".join(c for c in args.name if c.isalnum() or c in " _-()").strip() + filepath = _os.path.join(default_config.profile_dir, f"{safe_name}.json") + try: + profile = load_profile(filepath) + except FileNotFoundError: + print(f"Profile '{args.name}' not found.", file=sys.stderr) + sys.exit(1) + + errs = [] + if profile.mem_offset_mhz is not None: + ok, msg = set_clock_offsets(None, profile.mem_offset_mhz, gpu_index) + if not ok: + errs.append(f"Mem offset: {msg}") + + if profile.power_limit_w is not None: + ok, msg = set_power_limit(profile.power_limit_w, gpu_index) + if not ok: + errs.append(f"Power limit: {msg}") + + if profile.curve_deltas: + deltas = {int(k): v for k, v in profile.curve_deltas.items()} + errors = validate_write(deltas, default_config.max_delta_khz) + if errors: + errs.append("Curve: " + "; ".join(errors)) + else: + if default_config.auto_snapshot: + snapshot_save(gpu, gpu_name, default_config.snapshot_dir, default_config.max_snapshots) + ret, desc = write_offsets(gpu, deltas) + if ret != 0: + errs.append(f"Curve write failed ({ret}): {desc}") + else: + reset_offsets(gpu) + + for e in errs: + print(f" warning: {e}", file=sys.stderr) + print(f"Applied profile '{args.name}'.") + + +def cmd_gpus(args): + """List all detected NVIDIA GPUs with index, name, UUID, and PCI bus ID.""" + from .hal.gpu import discover_gpus + gpus = [ + {"index": i.index, "name": i.name, "uuid": i.uuid, "pci_bus_id": i.pci_bus_id} + for i in discover_gpus() + ] + if not gpus: + print("No NVIDIA GPUs detected.") + return + for g in gpus: + uuid = g.get("uuid") or "N/A" + pci = g.get("pci_bus_id") + pci_str = f"PCI 0x{pci:04x}" if pci is not None else "PCI N/A" + print(f" [{g['index']}] {g['name']} — {uuid} — {pci_str}") + + +def cmd_setup(args): + """One-shot hardware compatibility check: diag → read → write-verify → restore.""" + explicit_point = getattr(args, "point", None) + verify_delta_mhz = getattr(args, "delta", 5.0) or 5.0 + verify_delta_khz = int(verify_delta_mhz * 1000) + + require_root() + + from .hal.gpu import get_gpu + from .hal.vfcurve import read_curve, read_clock_offsets, write_offsets + from .hal.monitoring import read_voltage + from .hal.snapshot import save as snapshot_save, restore as snapshot_restore + + sep = "─" * 60 + + print(sep) + print(" NVCurve Setup — Hardware Compatibility Check") + print(sep) + print() + + gpu, gpu_name = get_gpu(index=getattr(args, "gpu_index", 0)) + + # ── Step 1: NvAPI diagnostics ────────────────────────────────────────────── + print("Step 1/4 NvAPI function probe") + print() + run_diagnostics(gpu, gpu_name, gpu_index=getattr(args, "gpu_index", 0)) + print() + + # ── Step 2: read current curve ───────────────────────────────────────────── + print("Step 2/4 Current V/F curve") + print() + curve_state, curve_err = read_curve(gpu, gpu_name) + if not curve_state: + print(f"FAILED to read V/F curve: {curve_err}", file=sys.stderr) + sys.exit(1) + voltage, _ = read_voltage(gpu) + pts = [(p.freq_khz, p.volt_uv) for p in curve_state.points] + offsets = [p.delta_khz for p in curve_state.points] + domains = [p.domain for p in curve_state.points] + print_curve(pts, offsets, voltage, domains=domains) + print() + + # Resolve verify point: explicit override, or last GPU-domain point. + gpu_points = [p for p in curve_state.points if p.domain == "gpu"] + if explicit_point is not None: + verify_point = explicit_point + elif gpu_points: + verify_point = gpu_points[-1].index + else: + verify_point = len(curve_state.points) - 1 + + # ── Step 3: write-verify cycle ───────────────────────────────────────────── + if verify_point >= len(curve_state.points): + print(f"Step 3/4 Write-verify (SKIPPED — point {verify_point} not present; " + f"GPU has {len(curve_state.points)} points)") + print() + print(sep) + print(" RESULT: Diagnostics passed. Write-verify skipped.") + print(f" Use --point to specify a valid point index (0–{len(curve_state.points) - 1}).") + print(sep) + return + + print(f"Step 3/4 Write-verify ({verify_delta_mhz:+.0f} MHz at point {verify_point})") + print() + + snap_path = snapshot_save(gpu, gpu_name, default_config.snapshot_dir, default_config.max_snapshots) + if snap_path: + print(f" Snapshot saved: {snap_path}") + + before_offsets, err = read_clock_offsets(gpu) + if before_offsets is None: + print(f" FAILED to read state before write: {err}", file=sys.stderr) + sys.exit(1) + + full_mask = getattr(args, "full_mask", False) + ret, desc = write_offsets(gpu, {verify_point: verify_delta_khz}, full_mask=full_mask) + if ret != 0: + print(f" Write FAILED ({ret}): {desc}") + print() + print(sep) + print(" RESULT: Write path is NOT working on this configuration.") + print(sep) + sys.exit(1) + + time.sleep(0.2) + + after_offsets, err = read_clock_offsets(gpu) + if after_offsets is None: + print(f" Verification read FAILED: {err}", file=sys.stderr) + sys.exit(1) + + actual = after_offsets[verify_point] + matched = actual == verify_delta_khz + collateral = [ + i for i in range(min(len(before_offsets), len(after_offsets))) + if i != verify_point and before_offsets[i] != after_offsets[i] + ] + + if matched: + print(f" Point {verify_point}: {verify_delta_khz / 1000:+.0f} MHz OK") + else: + print(f" Point {verify_point}: MISMATCH — expected {verify_delta_khz / 1000:+.0f} MHz, " + f"got {actual / 1000:+.0f} MHz") + if collateral: + print(f" WARNING: {len(collateral)} collateral point(s) changed: {collateral[:5]}") + else: + print(" No collateral changes") + + # ── Step 4: restore ──────────────────────────────────────────────────────── + print() + print("Step 4/4 Restoring snapshot") + print() + ok = snapshot_restore(gpu, default_config.snapshot_dir, snap_path) + if ok: + print(" Hardware state restored to baseline.") + else: + print(" WARNING: Restore failed. Run: nvcurve snapshot restore", file=sys.stderr) + + print() + print(sep) + if matched and not collateral: + print(" RESULT: Compatible. NVCurve is ready to use.") + print() + print(" Next: nvcurve launch web UI") + print(" nvcurve service install auto-start on boot") + elif not matched: + print(" RESULT: Write verification FAILED — this configuration is not supported.") + else: + print(" RESULT: Write applied but unexpected collateral changes detected.") + print(" Review the output above before using write operations.") + print(sep) + + +def cmd_service(args): + """Manage the nvcurve systemd service.""" + action = getattr(args, "action", None) + if not action: + print("Usage: nvcurve service [install|uninstall|start|stop|restart|status]") + return + + unit_path = "/etc/systemd/system/nvcurve.service" + + if action == "install": + require_root() + import subprocess + + exec_start = f"{sys.executable} -m nvcurve daemon" + + unit = ( + "[Unit]\n" + "Description=NVCurve NVIDIA GPU V/F Curve Daemon\n" + "After=nvidia-persistenced.service\n" + "Wants=nvidia-persistenced.service\n" + "\n" + "[Service]\n" + "Type=simple\n" + f"ExecStart={exec_start}\n" + "Restart=on-failure\n" + "RestartSec=5\n" + "Environment=PYTHONDONTWRITEBYTECODE=1\n" + "\n" + "[Install]\n" + "WantedBy=multi-user.target\n" + ) + + with open(unit_path, "w") as f: + f.write(unit) + print(f"Unit file written to {unit_path}") + + # Write persistent config. + os.makedirs("/etc/nvcurve", exist_ok=True) + persistent_cfg: dict = {} + try: + with open(_PERSISTENT_CONFIG_FILE) as f: + persistent_cfg = json.load(f) + except Exception: + pass + host = getattr(args, "host", "127.0.0.1") + port = getattr(args, "port", 8042) + auto_serve = getattr(args, "auto_serve", False) + persistent_cfg.update({"host": host, "port": port, "auto_serve": auto_serve}) + with open(_PERSISTENT_CONFIG_FILE, "w") as f: + json.dump(persistent_cfg, f, indent=2) + print(f"Persistent config written to {_PERSISTENT_CONFIG_FILE}") + if auto_serve: + print(f" Web server will auto-start on boot at {host}:{port}") + else: + print(f" Web server default: {host}:{port} (start on demand: nvcurve serve start)") + + try: + subprocess.run(["systemctl", "daemon-reload"], check=True) + + was_active = subprocess.run( + ["systemctl", "is-active", "--quiet", "nvcurve"], + ).returncode == 0 + + subprocess.run(["systemctl", "enable", "--now", "nvcurve"], check=True) + print("Service enabled and started.") + + if was_active: + print() + print("Note: the service was already running and is still on the old version.") + print(" Restart it to pick up the update: nvcurve service restart") + + print() + print("Useful commands:") + print(" systemctl status nvcurve") + print(" journalctl -u nvcurve -f") + print(" nvcurve service uninstall") + except subprocess.CalledProcessError as e: + print(f"systemctl failed: {e}", file=sys.stderr) + + elif action == "uninstall": + require_root() + import subprocess + + if not os.path.exists(unit_path): + print("Service is not installed.") + return + try: + subprocess.run(["systemctl", "stop", "nvcurve"], check=False) + subprocess.run(["systemctl", "disable", "nvcurve"], check=False) + os.remove(unit_path) + if os.path.exists(_PERSISTENT_CONFIG_FILE): + os.remove(_PERSISTENT_CONFIG_FILE) + subprocess.run(["systemctl", "daemon-reload"], check=True) + print("Service stopped, disabled, and removed.") + except Exception as e: + print(f"Error during uninstall: {e}", file=sys.stderr) + + elif action == "start": + require_root() + import subprocess + if not os.path.exists(unit_path): + print("Service is not installed. Run: nvcurve service install") + return + try: + subprocess.run(["systemctl", "start", "nvcurve"], check=True) + print("Service started.") + except subprocess.CalledProcessError as e: + print(f"systemctl start failed: {e}", file=sys.stderr) + + elif action == "stop": + require_root() + import subprocess + if not os.path.exists(unit_path): + print("Service is not installed.") + return + try: + subprocess.run(["systemctl", "stop", "nvcurve"], check=True) + print("Service stopped.") + except subprocess.CalledProcessError as e: + print(f"systemctl stop failed: {e}", file=sys.stderr) + + elif action == "restart": + require_root() + import subprocess + if not os.path.exists(unit_path): + print("Service is not installed. Run: nvcurve service install") + return + try: + subprocess.run(["systemctl", "restart", "nvcurve"], check=True) + print("Service restarted.") + except subprocess.CalledProcessError as e: + print(f"systemctl restart failed: {e}", file=sys.stderr) + + elif action == "status": + import subprocess + + if os.path.exists(unit_path): + result = subprocess.run( + ["systemctl", "is-active", "nvcurve"], + capture_output=True, text=True, + ) + active = result.stdout.strip() + pid_info = "" + if active == "active": + r2 = subprocess.run( + ["systemctl", "show", "nvcurve", "--property=MainPID"], + capture_output=True, text=True, + ) + pid = r2.stdout.strip().replace("MainPID=", "") + if pid and pid != "0": + pid_info = f" (PID {pid})" + print(f"systemd service: {active}{pid_info}") + else: + print("systemd service: not installed") + print(f" (no unit file at {unit_path})") + print() + print("Register with: nvcurve service install") + + # Show persistent config regardless of install state. + pcfg: dict = {} + try: + with open(_PERSISTENT_CONFIG_FILE) as f: + pcfg = json.load(f) + except FileNotFoundError: + pass + except Exception as e: + print(f" (could not read config: {e})", file=sys.stderr) + + auto_serve = pcfg.get("auto_serve", False) + host = pcfg.get("host", "127.0.0.1") + port = pcfg.get("port", 8042) + print() + print(f"web server auto-start: {'on' if auto_serve else 'off'}") + print(f"web server address: {host}:{port}") + print() + print("Change with: nvcurve service configure [--auto-serve|--no-auto-serve] [--host H] [--port P]") + + elif action == "configure": + require_root() + import subprocess + + os.makedirs("/etc/nvcurve", exist_ok=True) + pcfg: dict = {} + try: + with open(_PERSISTENT_CONFIG_FILE) as f: + pcfg = json.load(f) + except Exception: + pass + + if hasattr(args, "auto_serve") and args.auto_serve is not None: + pcfg["auto_serve"] = args.auto_serve + if hasattr(args, "host") and args.host is not None: + pcfg["host"] = args.host + if hasattr(args, "port") and args.port is not None: + pcfg["port"] = args.port + + with open(_PERSISTENT_CONFIG_FILE, "w") as f: + json.dump(pcfg, f, indent=2) + print(f"Config updated ({_PERSISTENT_CONFIG_FILE}):") + print(f" auto-serve: {'on' if pcfg.get('auto_serve', False) else 'off'}") + print(f" host: {pcfg.get('host', '127.0.0.1')}") + print(f" port: {pcfg.get('port', 8042)}") + + if os.path.exists(unit_path): + try: + subprocess.run(["systemctl", "restart", "nvcurve"], check=True) + print("Daemon restarted — new config is active.") + except subprocess.CalledProcessError as e: + print(f"systemctl restart failed: {e}", file=sys.stderr) + else: + print("(Service not installed — config will take effect on next install.)") + + +# ── Server management ───────────────────────────────────────────────────────── + +def _cmd_serve_start(args, cfg: Config, open_browser: bool = False) -> None: + """Start the web server — via daemon if available, otherwise directly (requires root).""" + host = getattr(args, "host", cfg.host) + port = getattr(args, "port", cfg.port) + + # --direct: skip daemon round-trip (used when the daemon itself spawns us). + if getattr(args, "direct", False): + require_root() + with open(_SERVER_INFO_FILE, "w") as f: + json.dump({"pid": os.getpid(), "host": host, "port": port}, f) + try: + from .server import run as server_run + server_run(host=host, port=port, + gpu_index=getattr(args, "gpu_index", 0), + config=cfg, open_browser=False) + finally: + if os.path.exists(_SERVER_INFO_FILE): + os.remove(_SERVER_INFO_FILE) + return + + # Prefer daemon socket: no root required, daemon manages the server process. + resp = _daemon_send({"cmd": "serve_start", "host": host, "port": port}) + if resp is not None: + if resp.get("ok"): + print(f"Web server starting (PID {resp['pid']}) at http://{host}:{port}") + if open_browser: + time.sleep(1.5) + _open_browser_as_user(f"http://{host}:{port}") + else: + print(f"Daemon: {resp.get('error')}", file=sys.stderr) + return + + # Daemon not running — fall back to direct start (requires root). + require_root() + + info = _read_server_info() + if info: + url = f"http://{info['host']}:{info['port']}" + print(f"Server is already running (PID {info['pid']}) at {url}.") + if open_browser: + _open_browser_as_user(url) + return + + if getattr(args, "detach", False): + import subprocess + cmd = [sys.executable, "-m", "nvcurve", "serve", "start", + "--host", host, "--port", str(port)] + if getattr(args, "gpu_index", 0): + cmd += ["--gpu", str(args.gpu_index)] + log_path = _log_file() + print("Starting nvcurve server in background...") + with open(log_path, "a") as lf: + p = subprocess.Popen(cmd, stdout=lf, stderr=lf, start_new_session=True) + print(f"Server starting (PID {p.pid}). Logs: {log_path}") + if open_browser: + time.sleep(1.5) + _open_browser_as_user(_discover_server_url(cfg)) + return + + # Foreground mode — write info file so clients can discover host:port. + with open(_SERVER_INFO_FILE, "w") as f: + json.dump({"pid": os.getpid(), "host": host, "port": port}, f) + try: + from .server import run as server_run + server_run( + host=host, + port=port, + gpu_index=getattr(args, "gpu_index", 0), + config=cfg, + open_browser=open_browser, + ) + finally: + if os.path.exists(_SERVER_INFO_FILE): + os.remove(_SERVER_INFO_FILE) + + +# ── Argument parser ─────────────────────────────────────────────────────────── + +def build_parser() -> argparse.ArgumentParser: + from importlib.metadata import version as pkg_version + try: + __version__ = pkg_version("nvcurve") + except Exception: + __version__ = "unknown" + + parser = argparse.ArgumentParser( + prog="nvcurve", + description="Read/Write NVIDIA GPU V/F curve via undocumented NvAPI (Linux)", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog="""\ +Examples: + %(prog)s Launch web UI (default) + %(prog)s read Condensed V/F curve + %(prog)s read --full All points + %(prog)s read --json JSON output + %(prog)s write --global --delta 50 +50 MHz to all points + %(prog)s write --point 80 --delta 100 +100 MHz to point 80 + %(prog)s write --reset Reset all offsets to 0 + %(prog)s verify --point 80 --delta 15 Write + verify cycle + %(prog)s snapshot save/restore/list Manage snapshots + %(prog)s profile save balanced Save current state as profile + %(prog)s profile apply balanced Apply saved profile (escalates to root) + %(prog)s profile default balanced Set profile to auto-load on daemon start + %(prog)s serve start --detach Start web server in background + %(prog)s serve stop Stop running web server + %(prog)s service install Register daemon as systemd service (recommended) + %(prog)s read --diag Probe all NvAPI functions (needs root) + %(prog)s inspect --point 80 Raw buffer fields for a point (needs root) +""", + ) + parser.add_argument("-v", "--version", action="version", version=f"nvcurve {__version__}") + parser.add_argument( + "--server", default=None, metavar="URL", + help="Server base URL (default: http://127.0.0.1:8042)", + ) + parser.add_argument( + "--gpu", type=int, default=0, dest="gpu_index", + help="GPU index to target (default: 0)", + ) + sub = parser.add_subparsers(dest="command") + + # read + p_read = sub.add_parser("read", help="Read V/F curve") + p_read.add_argument("--full", action="store_true", help="Show all points") + p_read.add_argument("--json", action="store_true", help="JSON output") + p_read.add_argument("--raw", action="store_true", + help="Raw hex dumps of hardware buffers (needs root)") + p_read.add_argument("--diag", action="store_true", + help="Probe all NvAPI functions (needs root)") + + # inspect + p_insp = sub.add_parser("inspect", + help="Show raw ClockBoostTable buffer fields (needs root)") + p_insp.add_argument("--point", type=int, help="Single point index") + p_insp.add_argument("--range", type=parse_range, help="Point range A-B") + + # write + p_write = sub.add_parser("write", help="Write frequency offsets") + tgt = p_write.add_mutually_exclusive_group() + tgt.add_argument("--point", type=int, help="Single point index") + tgt.add_argument("--range", type=parse_range, help="Point range A-B") + tgt.add_argument("--global", dest="glob", action="store_true", + help="All points (like global NVML offset)") + tgt.add_argument("--reset", action="store_true", help="Reset all offsets to 0") + p_write.add_argument("--delta", type=float, default=0.0, + help="Frequency offset in MHz (e.g. 15, -30)") + p_write.add_argument("--dry-run", action="store_true", + help="Preview changes without applying") + p_write.add_argument("--max-delta", type=float, default=None, + help="Override safety limit for this write (MHz)") + + # verify + p_ver = sub.add_parser("verify", help="Write-verify-read cycle") + p_ver.add_argument("--point", type=int, help="Single point index") + p_ver.add_argument("--range", type=parse_range, help="Point range A-B") + p_ver.add_argument("--delta", type=float, required=True, + help="Frequency offset in MHz") + + # setup + p_setup = sub.add_parser("setup", + help="Hardware compatibility check: diag → read → write-verify → restore (needs root)") + p_setup.add_argument("--point", type=int, default=None, + help="Point index to use for write-verify test (default: last GPU-domain point)") + p_setup.add_argument("--delta", type=float, default=5.0, + help="Offset in MHz to use for write-verify test (default: +5)") + p_setup.add_argument("--full-mask", action="store_true", + help="Use the full GetClockBoostMask instead of a sparse mask " + "(try this if writes fail on older GPUs such as Pascal)") + + # snapshot + p_snap = sub.add_parser("snapshot", + help="Save/restore/list ClockBoostTable snapshots") + p_snap.add_argument("action", choices=["save", "restore", "list"]) + p_snap.add_argument("--file", help="Snapshot file path (for restore)") + + # gpus + sub.add_parser("gpus", help="List detected NVIDIA GPUs (server-optional)") + + # profile + p_prof = sub.add_parser("profile", help="Manage saved clock/limit profiles (server-optional)") + p_prof.add_argument("action", choices=["save", "apply", "list", "default"]) + p_prof.add_argument("name", nargs="?", help="Profile name (for save/apply/default)") + p_prof.add_argument("--clear", action="store_true", help="Clear the default profile (for default action)") + + # daemon + sub.add_parser("daemon", help="Run the nvcurve daemon (apply auto-load profiles, requires root)") + + # autoload + sub.add_parser("autoload", help="Apply auto-load profiles from config (requires root)") + + # serve + p_srv = sub.add_parser("serve", help="Start or manage the web server") + s_srv = p_srv.add_subparsers(dest="action") + + p_start = s_srv.add_parser("start", help="Start the server (escalates to root)") + p_start.add_argument("--host", default="127.0.0.1", + help="Bind address (default 127.0.0.1)") + p_start.add_argument("--port", type=int, default=8042, + help="Port (default 8042)") + p_start.add_argument("--detach", "-d", action="store_true", + help="Run in background") + p_start.add_argument("--direct", action="store_true", + help=argparse.SUPPRESS) # internal: skip daemon check + + s_srv.add_parser("stop", help="Stop the running server") + s_srv.add_parser("status", help="Check server status") + + # service + p_svc = sub.add_parser("service", help="Manage the nvcurve systemd service") + s_svc = p_svc.add_subparsers(dest="action") + + p_install = s_svc.add_parser("install", + help="Register as systemd service (escalates to root)") + p_install.add_argument("--auto-serve", action="store_true", dest="auto_serve", + help="Auto-start web server on boot (default: off)") + p_install.add_argument("--host", default="127.0.0.1", + help="Default web server bind address (stored in config)") + p_install.add_argument("--port", type=int, default=8042, + help="Default web server port (stored in config)") + + p_configure = s_svc.add_parser("configure", + help="Update config and restart daemon (escalates to root)") + p_configure.add_argument("--auto-serve", dest="auto_serve", + action="store_true", default=None, + help="Auto-start web server on boot") + p_configure.add_argument("--no-auto-serve", dest="auto_serve", + action="store_false", + help="Do not auto-start web server on boot") + p_configure.add_argument("--host", default=None, + help="Web server bind address") + p_configure.add_argument("--port", type=int, default=None, + help="Web server port") + + s_svc.add_parser("uninstall", + help="Remove systemd service (escalates to root)") + s_svc.add_parser("start", + help="Start systemd service (escalates to root)") + s_svc.add_parser("stop", + help="Stop systemd service (escalates to root)") + s_svc.add_parser("restart", + help="Restart systemd service (escalates to root)") + s_svc.add_parser("status", help="Check systemd service status") + + return parser + + +def main(): + import sys + base_parser = argparse.ArgumentParser(add_help=False) + base_parser.add_argument("--server", default=None) + base_parser.add_argument("--gpu", type=int, default=0, dest="gpu_index") + + known_args, remaining_argv = base_parser.parse_known_args(sys.argv[1:]) + + parser = build_parser() + args = parser.parse_args(remaining_argv) + + if known_args.server is not None: + args.server = known_args.server + if known_args.gpu_index != 0: + args.gpu_index = known_args.gpu_index + + cfg = Config() + try: + with open(_PERSISTENT_CONFIG_FILE) as f: + data = json.load(f) + for key in ("max_delta_khz", "auto_snapshot", "max_snapshots", + "snapshot_dir", "profile_dir", "host", "port"): + if key in data: + setattr(cfg, key, data[key]) + if "auto_load_profiles" in data: + # Keys are stable GPU identifiers (UUID, "pci:XXXX", or "idx:N") + cfg.auto_load_profiles = dict(data["auto_load_profiles"]) + elif "auto_load_profile" in data: + # Migrate old single-string format — GPU 0, no UUID known at this point + cfg.auto_load_profiles = {"idx:0": data["auto_load_profile"]} + except Exception: + pass + + base_url = args.server or _discover_server_url(cfg) + client = NvCurveClient(base=base_url, gpu_index=getattr(args, "gpu_index", 0)) + + # Default — no subcommand: open the web UI. + # If the server is already running, just open a browser tab (no root needed). + # Otherwise start it. If we started it via the daemon (non-blocking), we + # block here and stop the server when the user hits Ctrl+C. + if args.command is None: + if client.ping(): + _open_browser_as_user(base_url) + return + + via_daemon = _daemon_send({"cmd": "ping"}) is not None + _cmd_serve_start(args, cfg, open_browser=True) + + if via_daemon: + print("Press Ctrl+C to stop.") + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + print() + finally: + _daemon_send({"cmd": "serve_stop"}) + return + + if args.command == "serve": + action = getattr(args, "action", None) or "start" + + if action == "start": + _cmd_serve_start(args, cfg, open_browser=False) + + elif action == "stop": + # Try daemon socket first. + resp = _daemon_send({"cmd": "serve_stop"}) + if resp is not None: + if resp.get("ok"): + print("Web server stopped.") + else: + print(f"Daemon: {resp.get('error')}", file=sys.stderr) + return + # Fallback: send shutdown via HTTP API. + try: + client.shutdown() + print("Server stopped.") + except ServerNotRunning: + print("Server is not running.") + if os.path.exists(_SERVER_INFO_FILE): + try: + os.remove(_SERVER_INFO_FILE) + except OSError: + pass + except ApiError as e: + print(f"Shutdown failed: {e.detail}", file=sys.stderr) + + elif action == "status": + # Try daemon socket first. + resp = _daemon_send({"cmd": "serve_status"}) + if resp is not None: + if resp.get("running"): + pid = resp.get("pid") + print(f"Daemon: web server running (PID {pid}) at {base_url}") + else: + print(f"Daemon: web server not running") + return + # Fallback: check HTTP directly. + if client.ping(): + try: + info = client.gpu() + print(f"Server is running at {base_url}") + print(f" GPU: {info.get('name', '?')}") + driver = info.get("driver_version") + if driver: + print(f" Driver: {driver}") + except Exception: + print(f"Server is running at {base_url}") + else: + print(f"Server is NOT running at {base_url}") + return + + if args.command == "daemon": + from .daemon import run as daemon_run + daemon_run() + return + + if args.command == "autoload": + from .profiles.apply import run_autoload + run_autoload() + return + + if args.command == "setup": + cmd_setup(args) + elif args.command == "read": + cmd_read(args) + elif args.command == "inspect": + cmd_inspect(args) + elif args.command == "write": + cmd_write(args) + elif args.command == "verify": + cmd_verify(args) + elif args.command == "snapshot": + cmd_snapshot(args) + elif args.command == "gpus": + cmd_gpus(args) + elif args.command == "profile": + cmd_profile(args) + elif args.command == "service": + cmd_service(args) diff --git a/nvcurve/client.py b/nvcurve/client.py new file mode 100644 index 0000000..cd3f243 --- /dev/null +++ b/nvcurve/client.py @@ -0,0 +1,148 @@ +"""HTTP client for communicating with a running nvcurve server.""" + +import httpx +from typing import Any + +DEFAULT_BASE = "http://127.0.0.1:8042" +_TIMEOUT = 5.0 + + +class ServerNotRunning(Exception): + """Raised when the nvcurve server cannot be reached.""" + + +class ApiError(Exception): + def __init__(self, status_code: int, detail: Any): + self.status_code = status_code + self.detail = detail + super().__init__(f"HTTP {status_code}: {detail}") + + +class NvCurveClient: + def __init__(self, base: str = DEFAULT_BASE, gpu_index: int = 0): + self._base = base.rstrip("/") + self.gpu_index = gpu_index + + def _url(self, path: str) -> str: + sep = "&" if "?" in path else "?" + return f"{self._base}{path}{sep}gpu_index={self.gpu_index}" + + def _raise(self, r: httpx.Response) -> None: + if r.is_error: + try: + detail = r.json().get("detail", r.text) + except Exception: + detail = r.text + raise ApiError(r.status_code, detail) + + def _get(self, path: str) -> Any: + try: + r = httpx.get(self._url(path), timeout=_TIMEOUT) + except httpx.ConnectError: + raise ServerNotRunning() + self._raise(r) + return r.json() + + def _post(self, path: str, body: Any = None) -> Any: + try: + r = httpx.post(self._url(path), json=body, timeout=_TIMEOUT) + except httpx.ConnectError: + raise ServerNotRunning() + self._raise(r) + return r.json() + + def _delete(self, path: str) -> Any: + try: + r = httpx.delete(self._url(path), timeout=_TIMEOUT) + except httpx.ConnectError: + raise ServerNotRunning() + self._raise(r) + return r.json() + + def ping(self) -> bool: + try: + httpx.get(self._url("/api/gpu"), timeout=1.0) + return True + except Exception: + return False + + def gpus(self) -> list: + return self._get("/api/gpus") + + # ── GPU ────────────────────────────────────────────────────────────────── + + def gpu(self) -> dict: + return self._get("/api/gpu") + + # ── Curve ──────────────────────────────────────────────────────────────── + + def curve(self) -> dict: + """Returns {gpu_name, timestamp, points: [{index, freq_khz, volt_uv, delta_khz, ...}]}""" + return self._get("/api/curve") + + def voltage(self) -> int | None: + """Returns current GPU voltage in µV, or None if unavailable.""" + try: + return self._get("/api/voltage")["voltage_uv"] + except Exception: + return None + + def write_curve( + self, + deltas: dict[int, int], + max_delta_khz: int | None = None, + ) -> dict: + body: dict = {"deltas": deltas} + if max_delta_khz is not None: + body["max_delta_khz"] = max_delta_khz + return self._post("/api/curve/write", body) + + def write_global(self, delta_khz: int, max_delta_khz: int | None = None) -> dict: + body: dict = {"delta_khz": delta_khz} + if max_delta_khz is not None: + body["max_delta_khz"] = max_delta_khz + return self._post("/api/curve/write/global", body) + + def reset_curve(self) -> dict: + return self._post("/api/curve/reset") + + def verify_write(self, deltas: dict[int, int]) -> dict: + return self._post("/api/curve/verify", {"deltas": deltas}) + + # ── Snapshots ──────────────────────────────────────────────────────────── + + def snapshot_save(self) -> dict: + return self._post("/api/snapshot/save") + + def snapshot_restore(self, filepath: str | None = None) -> dict: + return self._post("/api/snapshot/restore", {"filepath": filepath}) + + def snapshots(self) -> list: + return self._get("/api/snapshots") + + # ── Profiles ───────────────────────────────────────────────────────────── + + def profiles(self) -> dict: + return self._get("/api/profiles") + + def profile_save(self, name: str) -> dict: + return self._post("/api/profiles", {"name": name}) + + def profile_apply(self, name: str) -> dict: + return self._post(f"/api/profiles/{name}/apply") + + def profile_delete(self, name: str) -> dict: + return self._delete(f"/api/profiles/{name}") + + # ── Config ─────────────────────────────────────────────────────────────── + + def config_get(self) -> dict: + return self._get("/api/config") + + def config_update(self, auto_load_profile: str | None, gpu_index: int = 0) -> dict: + return self._post("/api/config", {"auto_load_profile": auto_load_profile, "gpu_index": gpu_index}) + + # ── Server control ─────────────────────────────────────────────────────── + + def shutdown(self) -> dict: + return self._post("/api/shutdown") diff --git a/nvcurve/config.py b/nvcurve/config.py new file mode 100644 index 0000000..a7ec634 --- /dev/null +++ b/nvcurve/config.py @@ -0,0 +1,31 @@ +"""User-configurable settings with sensible defaults.""" + +import os +from dataclasses import dataclass, field + + +@dataclass +class Config: + # Safety limits + max_delta_khz: int = 3000_000 # ±3000 MHz hard cap + auto_snapshot: bool = True # Save snapshot before every write + max_snapshots: int = 20 # Maximum snapshots to keep (0 = unlimited) + + # Monitoring + poll_interval_s: float = 1.0 # WebSocket monitor poll rate + + # API server + host: str = "127.0.0.1" + port: int = 8042 + + snapshot_dir: str = "/var/cache/nvcurve/snapshots" + profile_dir: str = "/etc/nvcurve/profiles" + + # Per-GPU default profiles: applied automatically on server startup. + # Key = stable GPU identifier (UUID string, "pci:{bus_id}", or "idx:{n}" fallback). + # Value = profile name (str). + auto_load_profiles: dict[str, str] = field(default_factory=dict) + + +# Module-level default config instance. +default_config = Config() diff --git a/nvcurve/daemon.py b/nvcurve/daemon.py new file mode 100644 index 0000000..7ca7cbd --- /dev/null +++ b/nvcurve/daemon.py @@ -0,0 +1,209 @@ +"""Minimal daemon: apply auto-load profiles on boot, manage web server via Unix socket. + +Run via: nvcurve daemon +Or via systemd: nvcurve service install + +Protocol: newline-delimited JSON, one request → one response, connection closed. + +Commands: + {"cmd": "ping"} + {"cmd": "serve_start", "host": "127.0.0.1", "port": 8042} + {"cmd": "serve_stop"} + {"cmd": "serve_status"} + +Requires root. +""" + +import asyncio +import json +import logging +import os +import signal +import subprocess +import sys + +log = logging.getLogger("nvcurve.daemon") + +SOCKET_PATH = "/run/nvcurve-daemon.sock" +_PERSISTENT_CONFIG_FILE = "/etc/nvcurve/config.json" + +# Global server subprocess — only touched from the asyncio event loop. +_server_proc: subprocess.Popen | None = None +_cfg = None # Config instance, set in run() + + +# ── Socket command handlers ──────────────────────────────────────────────────── + +async def _handle_serve_start(host: str, port: int) -> dict: + global _server_proc + if _server_proc is not None and _server_proc.poll() is None: + return {"ok": False, "error": "web server already running", "pid": _server_proc.pid} + + cmd = [sys.executable, "-m", "nvcurve", "serve", "start", + "--host", host, "--port", str(port), "--direct"] + log_path = "/var/log/nvcurve-server.log" + log.info("Starting web server on %s:%d (log: %s)", host, port, log_path) + with open(log_path, "a") as lf: + _server_proc = subprocess.Popen( + cmd, + stdout=lf, + stderr=lf, + env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"}, + ) + log.info("Web server started (PID %d)", _server_proc.pid) + return {"ok": True, "pid": _server_proc.pid} + + +async def _handle_serve_stop() -> dict: + global _server_proc + if _server_proc is None or _server_proc.poll() is not None: + _server_proc = None + return {"ok": False, "error": "web server is not running"} + log.info("Stopping web server (PID %d)…", _server_proc.pid) + _server_proc.terminate() + try: + await asyncio.get_running_loop().run_in_executor( + None, _server_proc.wait, 5 + ) + except Exception: + _server_proc.kill() + _server_proc = None + return {"ok": True} + + +async def _handle_serve_status() -> dict: + global _server_proc + if _server_proc is not None and _server_proc.poll() is None: + return {"ok": True, "running": True, "pid": _server_proc.pid} + _server_proc = None + return {"ok": True, "running": False} + + +async def _dispatch(req: dict) -> dict: + cmd = req.get("cmd") + if cmd == "ping": + return {"ok": True} + elif cmd == "serve_start": + host = req.get("host", _cfg.host) + port = req.get("port", _cfg.port) + return await _handle_serve_start(host, port) + elif cmd == "serve_stop": + return await _handle_serve_stop() + elif cmd == "serve_status": + return await _handle_serve_status() + else: + return {"ok": False, "error": f"unknown command: {cmd!r}"} + + +async def _handle_client( + reader: asyncio.StreamReader, writer: asyncio.StreamWriter +) -> None: + resp: dict = {"ok": False, "error": "internal error"} + try: + data = await asyncio.wait_for(reader.readline(), timeout=5.0) + req = json.loads(data) + resp = await _dispatch(req) + except asyncio.TimeoutError: + resp = {"ok": False, "error": "timeout reading request"} + except json.JSONDecodeError as exc: + resp = {"ok": False, "error": f"invalid JSON: {exc}"} + except Exception as exc: + resp = {"ok": False, "error": str(exc)} + finally: + try: + writer.write(json.dumps(resp).encode() + b"\n") + await writer.drain() + except Exception: + pass + writer.close() + try: + await writer.wait_closed() + except Exception: + pass + + +# ── Entrypoint ───────────────────────────────────────────────────────────────── + +def _load_persistent_config() -> dict: + try: + with open(_PERSISTENT_CONFIG_FILE) as f: + return json.load(f) + except Exception: + return {} + + +def run() -> None: + """Run the nvcurve daemon: apply auto-load profiles, then serve the Unix socket.""" + global _cfg + + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s %(name)s: %(message)s", + ) + + if os.geteuid() != 0: + print("nvcurve daemon: must run as root", file=sys.stderr) + sys.exit(1) + + cfg_data = _load_persistent_config() + + from .config import Config + _cfg = Config() + for key in ("max_delta_khz", "auto_snapshot", "max_snapshots", + "snapshot_dir", "profile_dir", "host", "port"): + if key in cfg_data: + setattr(_cfg, key, cfg_data[key]) + + # Apply auto-load profiles in a subprocess so the daemon process itself + # never loads NvAPI/NVML/HAL modules — keeps steady-state RSS low. + auto_load_profiles: dict = cfg_data.get("auto_load_profiles", {}) + if auto_load_profiles: + log.info("Applying auto-load profiles…") + subprocess.run( + [sys.executable, "-m", "nvcurve", "autoload"], + check=False, + ) + + auto_serve: bool = cfg_data.get("auto_serve", False) + asyncio.run(_serve_socket(auto_serve=auto_serve)) + + log.info("Daemon stopped.") + + +async def _serve_socket(auto_serve: bool = False) -> None: + global _server_proc + + # Clean up stale socket from a previous (unclean) run. + if os.path.exists(SOCKET_PATH): + os.unlink(SOCKET_PATH) + + server = await asyncio.start_unix_server(_handle_client, path=SOCKET_PATH) + os.chmod(SOCKET_PATH, 0o666) # allow non-root CLI to connect + log.info("Daemon listening on %s", SOCKET_PATH) + + if auto_serve: + log.info("auto_serve enabled — starting web server on boot") + await _handle_serve_start(_cfg.host, _cfg.port) + + stop_event = asyncio.Event() + loop = asyncio.get_running_loop() + loop.add_signal_handler(signal.SIGTERM, stop_event.set) + loop.add_signal_handler(signal.SIGINT, stop_event.set) + + async with server: + await stop_event.wait() + + # Gracefully stop the web server if it's running. + if _server_proc is not None and _server_proc.poll() is None: + log.info("Stopping web server (PID %d)…", _server_proc.pid) + _server_proc.terminate() + try: + await asyncio.wait_for( + asyncio.get_running_loop().run_in_executor(None, _server_proc.wait), + timeout=5, + ) + except asyncio.TimeoutError: + _server_proc.kill() + + if os.path.exists(SOCKET_PATH): + os.unlink(SOCKET_PATH) diff --git a/nvcurve/hal/__init__.py b/nvcurve/hal/__init__.py new file mode 100644 index 0000000..c7b3979 --- /dev/null +++ b/nvcurve/hal/__init__.py @@ -0,0 +1,13 @@ +from .gpu import get_gpu, discover_gpus +from .vfcurve import read_curve, read_clock_offsets, write_offsets, reset_offsets +from .monitoring import poll, read_voltage +from .ranges import get_clock_ranges +from .snapshot import save as snapshot_save, restore as snapshot_restore, list_snapshots + +__all__ = [ + "get_gpu", "discover_gpus", + "read_curve", "read_clock_offsets", "write_offsets", "reset_offsets", + "poll", "read_voltage", + "get_clock_ranges", + "snapshot_save", "snapshot_restore", "list_snapshots", +] diff --git a/nvcurve/hal/gpu.py b/nvcurve/hal/gpu.py new file mode 100644 index 0000000..5e9b1ca --- /dev/null +++ b/nvcurve/hal/gpu.py @@ -0,0 +1,93 @@ +"""GPU discovery and initialization.""" + +import ctypes +import sys + +from ..nvapi.bootstrap import query_interface +from ..nvapi.constants import FUNC +from ..nvapi.types import GpuInfo + + +def init_nvapi() -> None: + """Initialize NvAPI. Must be called before any GPU operations.""" + init_fn = query_interface(FUNC["Initialize"], nargs=0) + if not init_fn or init_fn() != 0: + print("NvAPI_Initialize failed") + sys.exit(1) + + +def enumerate_gpus() -> tuple[ctypes.Array, int]: + """Return (gpu_handles_array, count). Exits if no GPUs found.""" + gpus = (ctypes.c_void_p * 64)() + ngpu = ctypes.c_int32() + query_interface(FUNC["EnumPhysicalGPUs"])(ctypes.byref(gpus), ctypes.byref(ngpu)) + if ngpu.value == 0: + print("No NVIDIA GPUs found") + sys.exit(1) + return gpus, ngpu.value + + +def get_gpu_name(gpu) -> str: + """Return the full name string for a GPU handle.""" + name_buf = ctypes.create_string_buffer(256) + query_interface(FUNC["GetFullName"])(gpu, name_buf) + return name_buf.value.decode(errors="replace") + + +def discover_gpus() -> list[GpuInfo]: + """Initialize NvAPI and NVML, and return a list of GpuInfo for all physical GPUs.""" + init_nvapi() + gpus, count = enumerate_gpus() + infos = [] + + try: + import pynvml + pynvml.nvmlInit() + has_nvml = True + except Exception: + has_nvml = False + + for i in range(count): + name = get_gpu_name(gpus[i]) + uuid = None + pci_bus_id = None + if has_nvml: + try: + handle = pynvml.nvmlDeviceGetHandleByIndex(i) + uuid = pynvml.nvmlDeviceGetUUID(handle) + # NVML might return bytes + if isinstance(uuid, bytes): + uuid = uuid.decode('utf-8', errors='ignore') + pci_info = pynvml.nvmlDeviceGetPciInfo(handle) + # Parse something like "00000000:01:00.0" -> bus is 1 + if isinstance(pci_info.bus, bytes): + pci_bus_id = int(pci_info.bus.decode('utf-8', errors='ignore'), 16) + else: + pci_bus_id = pci_info.bus + except Exception: + pass + infos.append(GpuInfo(name=name, index=i, uuid=uuid, pci_bus_id=pci_bus_id)) + + if has_nvml: + try: + pynvml.nvmlShutdown() + except Exception: + pass + + return infos + + +def get_gpu(index: int = 0): + """Initialize NvAPI, enumerate GPUs, and return the handle for `index`. + + Also returns the GPU name as a convenience. + Returns (handle, name). + """ + init_nvapi() + gpus, count = enumerate_gpus() + if index >= count: + print(f"GPU index {index} out of range (found {count} GPU(s))") + sys.exit(1) + gpu = gpus[index] + name = get_gpu_name(gpu) + return gpu, name diff --git a/nvcurve/hal/limits.py b/nvcurve/hal/limits.py new file mode 100644 index 0000000..6971823 --- /dev/null +++ b/nvcurve/hal/limits.py @@ -0,0 +1,321 @@ +"""Hardware Abstraction Layer for Global Limits (Power, Clock Offsets). + +Uses NVML (via pynvml) for all operations. + +Clock offsets use nvmlDeviceSetClockOffsets / nvmlDeviceGetClockOffsets +(introduced in driver 555.85). The older per-domain functions +(nvmlDeviceSet/GetGpcClkVfOffset, nvmlDeviceSet/GetMemClkVfOffset) are used +as a fallback when the new API is unavailable or returns an error. +set_clock_offsets accepts Optional values and only touches the domains +that are explicitly specified, leaving others unchanged on hardware. +""" + +import ctypes +import subprocess +import logging +from typing import Optional + +try: + import pynvml + _NVML_AVAILABLE = True +except ImportError: + _NVML_AVAILABLE = False + +log = logging.getLogger("nvcurve.hal.limits") + +# ── NVML library / handle helpers ───────────────────────────────────────────── + +_nvml_lib: Optional[ctypes.CDLL] = None + + +def _nvml_cdll() -> ctypes.CDLL: + """Return a ctypes handle to libnvidia-ml, reusing pynvml's load if possible.""" + global _nvml_lib + if _nvml_lib is not None: + return _nvml_lib + # Prefer to reuse the library already loaded by pynvml to avoid dlopen races. + for attr in ("nvml", "_nvml"): # attribute name varies by pynvml version + mod = getattr(pynvml, attr, None) + lib = getattr(mod, "_lib", None) or getattr(mod, "_nvmlLib", None) + if lib is not None: + _nvml_lib = lib + return _nvml_lib + _nvml_lib = ctypes.CDLL("libnvidia-ml.so.1") + return _nvml_lib + + +def _get_handle(gpu_index: int): + """Return an NVML device handle, initialising pynvml if needed.""" + if not _NVML_AVAILABLE: + raise RuntimeError("NVML not available (install nvidia-ml-py)") + return pynvml.nvmlDeviceGetHandleByIndex(gpu_index) + + +# ── Power limit ─────────────────────────────────────────────────────────────── + +def get_power_limit(gpu_index: int = 0) -> dict: + """Return dict with power_limit_w, default_power_limit_w, min_power_limit_w, max_power_limit_w.""" + out = { + "power_limit_w": None, + "default_power_limit_w": None, + "min_power_limit_w": None, + "max_power_limit_w": None, + } + try: + handle = _get_handle(gpu_index) + limit = pynvml.nvmlDeviceGetPowerManagementLimit(handle) + constrs = pynvml.nvmlDeviceGetPowerManagementLimitConstraints(handle) + out["power_limit_w"] = limit // 1000 + out["min_power_limit_w"] = constrs[0] // 1000 + out["max_power_limit_w"] = constrs[1] // 1000 + try: + default = pynvml.nvmlDeviceGetPowerManagementDefaultLimit(handle) + out["default_power_limit_w"] = default // 1000 + except Exception: + pass + except Exception as exc: + log.warning("get_power_limit: %s", exc) + return out + + +def set_power_limit(limit_w: int, gpu_index: int = 0) -> tuple[bool, str]: + """Set the board power limit (Watts).""" + try: + handle = _get_handle(gpu_index) + pynvml.nvmlDeviceSetPowerManagementLimit(handle, limit_w * 1000) + return True, "OK" + except Exception as exc: + log.debug("NVML set_power_limit failed: %s — falling back to nvidia-smi", exc) + + ret = subprocess.run( + ["nvidia-smi", "-i", str(gpu_index), "-pl", str(limit_w)], + capture_output=True, text=True, + ) + if ret.returncode == 0: + return True, "OK" + return False, ret.stderr.strip() or ret.stdout.strip() + + +# ── Clock offsets (GPC + memory) ────────────────────────────────────────────── + +# The correct struct layout (per NVML docs and driver 590.x headers): +# +# typedef struct { +# unsigned int version; // nvmlClockOffset_v1 +# nvmlClockType_t type; // NVML_CLOCK_GRAPHICS (0) or NVML_CLOCK_MEM (2) +# nvmlPstates_t pstate; // NVML_PSTATE_0 (0) +# int clockOffsetMHz; +# } nvmlClockOffset_t; +# +# nvmlDeviceSet/GetClockOffsets are called ONCE PER CLOCK DOMAIN. +# pynvml (nvidia-ml-py ≥ 12) exposes c_nvmlClockOffset_t and nvmlClockOffset_v1 +# as ctypes objects; we use them when available and fall back to our own definition. + +class _ClockOffset(ctypes.Structure): + _fields_ = [ + ("version", ctypes.c_uint), + ("type", ctypes.c_uint), # nvmlClockType_t + ("pstate", ctypes.c_uint), # nvmlPstates_t + ("clockOffsetMHz", ctypes.c_int), + ] + +_CLOCK_OFFSET_VER = (1 << 24) | ctypes.sizeof(_ClockOffset) # = 0x01000010 (16 bytes) + +# NVML clock-type constants (same values as pynvml). +_NVML_CLOCK_GRAPHICS = 0 +_NVML_CLOCK_MEM = 2 + + +def _make_clock_offset(clock_type: int, pstate: int = 0, offset_mhz: int = 0) -> ctypes.Structure: + """Return a populated nvmlClockOffset_t struct, using pynvml's type when available.""" + if hasattr(pynvml, "c_nvmlClockOffset_t") and hasattr(pynvml, "nvmlClockOffset_v1"): + info = pynvml.c_nvmlClockOffset_t() + info.version = pynvml.nvmlClockOffset_v1 + info.type = clock_type + info.pstate = pstate + info.clockOffsetMHz = offset_mhz + return info + info = _ClockOffset() + info.version = _CLOCK_OFFSET_VER + info.type = clock_type + info.pstate = pstate + info.clockOffsetMHz = offset_mhz + return info + + +def _try_nvml_fn(name: str): + """Return a ctypes-callable for an NVML function, or None if not found.""" + lib = _nvml_cdll() + try: + return getattr(lib, name) + except AttributeError: + return None + + +def get_clock_offsets(gpu_index: int = 0) -> dict: + """Return GPC and memory clock offsets (MHz). + + Keys: gpc_offset_mhz, mem_offset_mhz (both int or None on failure). + Calls nvmlDeviceGetClockOffsets once per clock domain (GRAPHICS, MEM). + """ + out = {"gpc_offset_mhz": None, "mem_offset_mhz": None} + if not _NVML_AVAILABLE: + return out + try: + handle = _get_handle(gpu_index) + + # Try pynvml wrapper first (nvidia-ml-py ≥ 12 exposes it correctly). + # Fall back to ctypes-direct if pynvml doesn't have it. + _pynvml_get = getattr(pynvml, "nvmlDeviceGetClockOffsets", None) + fn_get = _try_nvml_fn("nvmlDeviceGetClockOffsets") if _pynvml_get is None else None + + used_new_api = False + for clock_type, key in ((_NVML_CLOCK_GRAPHICS, "gpc_offset_mhz"), + (_NVML_CLOCK_MEM, "mem_offset_mhz")): + info = _make_clock_offset(clock_type, pstate=0) + try: + if _pynvml_get is not None: + rc = _pynvml_get(handle, ctypes.byref(info)) + elif fn_get is not None: + rc = fn_get(handle, ctypes.byref(info)) + else: + break + if rc == 0: + out[key] = int(info.clockOffsetMHz) + used_new_api = True + else: + log.debug("nvmlDeviceGetClockOffsets(type=%d) returned %d", clock_type, rc) + except Exception as exc: + log.debug("nvmlDeviceGetClockOffsets(type=%d): %s", clock_type, exc) + + if used_new_api: + return out + + # Deprecated per-domain fallback. + if hasattr(pynvml, "nvmlDeviceGetGpcClkVfOffset"): + try: + out["gpc_offset_mhz"] = int(pynvml.nvmlDeviceGetGpcClkVfOffset(handle)) + except Exception as exc: + log.debug("nvmlDeviceGetGpcClkVfOffset: %s", exc) + if hasattr(pynvml, "nvmlDeviceGetMemClkVfOffset"): + try: + res = pynvml.nvmlDeviceGetMemClkVfOffset(handle) + out["mem_offset_mhz"] = int(res[0] if isinstance(res, (list, tuple)) else res) + except Exception as exc: + log.debug("nvmlDeviceGetMemClkVfOffset: %s", exc) + + except Exception as exc: + log.warning("get_clock_offsets: %s", exc) + return out + + +def set_clock_offsets( + gpc_offset_mhz: Optional[int] = None, + mem_offset_mhz: Optional[int] = None, + gpu_index: int = 0, +) -> tuple[bool, str]: + """Set clock offsets (MHz) for the specified domains only. + + Pass None for a domain to leave it untouched on hardware. + Calls nvmlDeviceSetClockOffsets once per requested domain (GRAPHICS, MEM). + Falls back to deprecated per-domain functions when the new API returns an + error (e.g. NVML_ERROR_DEPRECATED=25 on Blackwell with driver 590.x). + """ + if gpc_offset_mhz is None and mem_offset_mhz is None: + return True, "OK" + if not _NVML_AVAILABLE: + return False, "NVML not available (install nvidia-ml-py)" + try: + handle = _get_handle(gpu_index) + + domains = [] + if gpc_offset_mhz is not None: + domains.append((_NVML_CLOCK_GRAPHICS, gpc_offset_mhz)) + if mem_offset_mhz is not None: + domains.append((_NVML_CLOCK_MEM, mem_offset_mhz)) + + _pynvml_set = getattr(pynvml, "nvmlDeviceSetClockOffsets", None) + fn_set = _try_nvml_fn("nvmlDeviceSetClockOffsets") if _pynvml_set is None else None + + if _pynvml_set is not None or fn_set is not None: + all_ok = True + for clock_type, offset in domains: + info = _make_clock_offset(clock_type, pstate=0, offset_mhz=offset) + try: + rc = _pynvml_set(handle, ctypes.byref(info)) if _pynvml_set else fn_set(handle, ctypes.byref(info)) + if rc != 0: + log.debug("nvmlDeviceSetClockOffsets(type=%d) returned %d — trying fallback", clock_type, rc) + all_ok = False + break + except Exception as exc: + log.debug("nvmlDeviceSetClockOffsets(type=%d): %s — trying fallback", clock_type, exc) + all_ok = False + break + if all_ok: + return True, "OK" + # Non-zero rc (e.g. 25=DEPRECATED on Blackwell) — fall through to deprecated path. + + # Deprecated per-domain fallback (works on Blackwell/driver 590.x). + errs = [] + if gpc_offset_mhz is not None and hasattr(pynvml, "nvmlDeviceSetGpcClkVfOffset"): + try: + pynvml.nvmlDeviceSetGpcClkVfOffset(handle, gpc_offset_mhz) + except Exception as exc: + errs.append(f"GPC: {exc}") + if mem_offset_mhz is not None and hasattr(pynvml, "nvmlDeviceSetMemClkVfOffset"): + try: + pynvml.nvmlDeviceSetMemClkVfOffset(handle, mem_offset_mhz) + except Exception as exc: + errs.append(f"MEM: {exc}") + if errs: + return False, "; ".join(errs) + return True, "OK" + + except Exception as exc: + log.warning("set_clock_offsets: %s", exc) + return False, str(exc) + + +# ── Range queries ───────────────────────────────────────────────────────────── + +def get_mem_offset_range(gpu_index: int = 0) -> dict: + """Return the min/max allowed memory clock offset (MHz). + + Keys: min_mem_offset_mhz, max_mem_offset_mhz. + Uses nvmlDeviceGetMemClkMinMaxVfOffset; falls back to observed RTX values. + """ + # Observed RTX 5090 defaults (NvAPI GetClockBoostRanges says -1000/+3000). + out = {"min_mem_offset_mhz": -2000, "max_mem_offset_mhz": 3000} + if not _NVML_AVAILABLE: + return out + try: + handle = _get_handle(gpu_index) + + if hasattr(pynvml, "nvmlDeviceGetMemClkMinMaxVfOffset"): + result = pynvml.nvmlDeviceGetMemClkMinMaxVfOffset(handle) + if isinstance(result, (list, tuple)) and len(result) >= 2: + out["min_mem_offset_mhz"] = int(result[0]) + out["max_mem_offset_mhz"] = int(result[1]) + else: + min_v = getattr(result, "minOffset", None) + max_v = getattr(result, "maxOffset", None) + if min_v is not None: + out["min_mem_offset_mhz"] = int(min_v) + if max_v is not None: + out["max_mem_offset_mhz"] = int(max_v) + return out + + fn = _try_nvml_fn("nvmlDeviceGetMemClkMinMaxVfOffset") + if fn is not None: + min_v = ctypes.c_int(0) + max_v = ctypes.c_int(0) + rc = fn(handle, ctypes.byref(min_v), ctypes.byref(max_v)) + if rc == 0: + out["min_mem_offset_mhz"] = int(min_v.value) + out["max_mem_offset_mhz"] = int(max_v.value) + + except Exception as exc: + log.debug("get_mem_offset_range: %s", exc) + return out + + diff --git a/nvcurve/hal/monitoring.py b/nvcurve/hal/monitoring.py new file mode 100644 index 0000000..5738943 --- /dev/null +++ b/nvcurve/hal/monitoring.py @@ -0,0 +1,124 @@ +"""Live GPU monitoring. + +Voltage is read via NvAPI GetCurrentVoltage. +Clock, temperature, power draw, and fan speed are read via NVML (nvidia-ml-py). +""" + +import struct +import time +from typing import Optional + +from ..nvapi.bootstrap import nvcall +from ..nvapi.constants import FUNC, VOLT_SIZE +from ..nvapi.types import MonitoringSample + +try: + import pynvml as _pynvml + _NVML_AVAILABLE = True +except ImportError: + _pynvml = None + _NVML_AVAILABLE = False + +_nvml_initialized = False + + +def init_nvml() -> bool: + """Initialize NVML. Call once at startup. Returns True on success.""" + global _nvml_initialized + if not _NVML_AVAILABLE: + return False + try: + _pynvml.nvmlInit() + _nvml_initialized = True + return True + except _pynvml.NVMLError: + return False + + +def shutdown_nvml() -> None: + """Shut down NVML. Call at process exit.""" + global _nvml_initialized + if _NVML_AVAILABLE and _nvml_initialized: + try: + _pynvml.nvmlShutdown() + except _pynvml.NVMLError: + pass + _nvml_initialized = False + + +def get_driver_version() -> Optional[str]: + """Return the NVIDIA driver version string, or None if unavailable.""" + if not (_NVML_AVAILABLE and _nvml_initialized): + return None + try: + return _pynvml.nvmlSystemGetDriverVersion() + except _pynvml.NVMLError: + return None + + +def get_vram_total(gpu_index: int = 0) -> Optional[int]: + """Return total VRAM in bytes, or None if unavailable.""" + if not (_NVML_AVAILABLE and _nvml_initialized): + return None + try: + handle = _pynvml.nvmlDeviceGetHandleByIndex(gpu_index) + return _pynvml.nvmlDeviceGetMemoryInfo(handle).total + except _pynvml.NVMLError: + return None + + +def read_voltage(gpu) -> tuple[Optional[int], str]: + """Read current GPU core voltage in µV via NvAPI GetCurrentVoltage. + + Returns (voltage_uV, "OK") or (None, error). + """ + d, err = nvcall(FUNC["GetCurrentVoltage"], gpu, VOLT_SIZE, ver=1) + if not d: + return None, err + return struct.unpack_from(" dict: + """Read all NVML fields. Returns a dict with keys matching MonitoringSample fields.""" + out = { + "clock_mhz": None, "temp_c": None, "power_w": None, "fan_pct": None, + "pstate": None, "mem_used_bytes": None, "mem_total_bytes": None, + "gpu_util_pct": None, "mem_util_pct": None, "mem_clock_mhz": None, + } + if not (_NVML_AVAILABLE and _nvml_initialized): + return out + try: + handle = _pynvml.nvmlDeviceGetHandleByIndex(gpu_index) + + out["clock_mhz"] = float(_pynvml.nvmlDeviceGetClockInfo(handle, _pynvml.NVML_CLOCK_GRAPHICS)) + out["mem_clock_mhz"] = float(_pynvml.nvmlDeviceGetClockInfo(handle, _pynvml.NVML_CLOCK_MEM)) + out["temp_c"] = float(_pynvml.nvmlDeviceGetTemperature(handle, _pynvml.NVML_TEMPERATURE_GPU)) + out["power_w"] = _pynvml.nvmlDeviceGetPowerUsage(handle) / 1000.0 # mW → W + out["pstate"] = int(_pynvml.nvmlDeviceGetPerformanceState(handle)) + + mem = _pynvml.nvmlDeviceGetMemoryInfo(handle) + out["mem_used_bytes"] = mem.used + out["mem_total_bytes"] = mem.total + + util = _pynvml.nvmlDeviceGetUtilizationRates(handle) + out["gpu_util_pct"] = float(util.gpu) + out["mem_util_pct"] = float(util.memory) + + try: + out["fan_pct"] = float(_pynvml.nvmlDeviceGetFanSpeed(handle)) + except _pynvml.NVMLError: + pass + except _pynvml.NVMLError: + pass + return out + + +def poll(gpu, gpu_index: int = 0) -> MonitoringSample: + """Read all available monitoring data and return a MonitoringSample.""" + voltage_uv, _ = read_voltage(gpu) + nvml = _nvml_read(gpu_index) + return MonitoringSample( + timestamp=time.time(), + voltage_uv=voltage_uv, + **nvml, + ) diff --git a/nvcurve/hal/ranges.py b/nvcurve/hal/ranges.py new file mode 100644 index 0000000..83ef925 --- /dev/null +++ b/nvcurve/hal/ranges.py @@ -0,0 +1,31 @@ +"""Clock boost range queries.""" + +import struct +from typing import Optional + +from ..nvapi.bootstrap import nvcall +from ..nvapi.constants import FUNC, RANGES_SIZE + + +def get_clock_ranges(gpu) -> tuple[Optional[dict], str]: + """Read clock domain min/max offset ranges via GetClockBoostRanges. + + Returns ({"num_domains": int, "domains": [[int, ...], ...]}, "OK") + or (None, error). + + On RTX 5090: GPU core ±3000 MHz, memory -3000/+3000 MHz. + """ + d, err = nvcall(FUNC["GetClockBoostRanges"], gpu, RANGES_SIZE, ver=1) + if not d: + return None, err + + num = struct.unpack_from(" len(d): + break + words = [struct.unpack_from(" Optional[str]: + """Save the current ClockBoostTable to disk. + + Writes both a binary .bin file and a human-readable .json metadata file. + If max_snapshots > 0, deletes the oldest snapshots to stay within the limit. + Returns the binary filepath on success, or None on failure. + """ + raw, err = read_clock_table_raw(gpu) + if not raw: + print(f"Failed to read ClockBoostTable: {err}") + return None + + os.makedirs(snapshot_dir, exist_ok=True) + ts = datetime.now().strftime("%Y%m%d_%H%M%S") + bin_path = os.path.join(snapshot_dir, f"clock_boost_table_{ts}.bin") + meta_path = os.path.join(snapshot_dir, f"clock_boost_table_{ts}.json") + + with open(bin_path, "wb") as f: + f.write(raw) + + offsets = [] + max_entries = (len(raw) - CT_BASE) // CT_STRIDE + for i in range(max_entries): + off = CT_BASE + i * CT_STRIDE + CT_DELTA_OFF + delta = struct.unpack_from(" 0: + _prune_snapshots(snapshot_dir, max_snapshots) + + return bin_path + + +def _prune_snapshots(snapshot_dir: str, max_snapshots: int) -> None: + """Delete oldest snapshots (both .bin and .json) to stay within max_snapshots.""" + bins = sorted( + f for f in os.listdir(snapshot_dir) if f.endswith(".bin") + ) # oldest first (lexicographic = chronological for our timestamp format) + excess = len(bins) - max_snapshots + for fname in bins[:excess]: + stem = fname[:-4] # strip .bin + for ext in (".bin", ".json"): + try: + os.remove(os.path.join(snapshot_dir, stem + ext)) + except OSError: + pass + + +def restore(gpu, snapshot_dir: str, filepath: str = None) -> bool: + """Restore a ClockBoostTable snapshot from disk. + + If no filepath is given, uses the most recent snapshot in snapshot_dir. + Returns True on success. + """ + if filepath is None: + if not os.path.isdir(snapshot_dir): + print(f"No snapshots found in {snapshot_dir}") + return False + bins = sorted( + [f for f in os.listdir(snapshot_dir) if f.endswith(".bin")], + reverse=True, + ) + if not bins: + print(f"No snapshot .bin files in {snapshot_dir}") + return False + filepath = os.path.join(snapshot_dir, bins[0]) + + if not os.path.isfile(filepath): + print(f"Snapshot file not found: {filepath}") + return False + + with open(filepath, "rb") as f: + raw = f.read() + + if len(raw) != CT_SIZE: + print(f"Snapshot size mismatch: expected {CT_SIZE}, got {len(raw)}") + return False + + vw = struct.unpack_from(" list[SnapshotInfo]: + """Return metadata for all snapshots in snapshot_dir, newest first.""" + if not os.path.isdir(snapshot_dir): + return [] + + results = [] + for fname in sorted(os.listdir(snapshot_dir), reverse=True): + if not fname.endswith(".json"): + continue + meta_path = os.path.join(snapshot_dir, fname) + try: + with open(meta_path) as f: + meta = json.load(f) + bin_path = meta.get("file", meta_path.replace(".json", ".bin")) + results.append(SnapshotInfo( + filepath=bin_path, + timestamp=meta.get("timestamp", ""), + gpu=meta.get("gpu", ""), + nonzero_offsets=meta.get("nonzero_offsets", 0), + size=meta.get("size", 0), + )) + except (json.JSONDecodeError, KeyError): + continue + + return results diff --git a/nvcurve/hal/vfcurve.py b/nvcurve/hal/vfcurve.py new file mode 100644 index 0000000..c4e7275 --- /dev/null +++ b/nvcurve/hal/vfcurve.py @@ -0,0 +1,286 @@ +"""Read and write the GPU V/F curve via NvAPI.""" + +import ctypes +import struct +from typing import Optional + +from ..nvapi.bootstrap import nvcall, nvcall_raw +from ..nvapi.constants import ( + FUNC, + VFP_SIZE, VFP_BASE, VFP_STRIDE, + CT_SIZE, CT_BASE, CT_STRIDE, CT_DELTA_OFF, CT_POINTS, +) +from ..nvapi.types import VFPoint, CurveState + + +# ── Mask helpers ───────────────────────────────────────────────────────────── + +# The boost mask is a static property of the GPU/driver — it does not change +# at runtime. Cache it per GPU handle to avoid redundant GetClockBoostMask +# calls on every HAL operation. +_boost_mask_cache: dict[int, bytes] = {} + + +def get_boost_mask(gpu) -> tuple[Optional[bytes], str]: + """Read the canonical 32-byte clock boost mask from the driver. + + The result is cached per GPU handle: subsequent calls return the cached + value without hitting the driver again. + + Returns (mask_bytes, "OK") or (None, error). + """ + if gpu in _boost_mask_cache: + return _boost_mask_cache[gpu], "OK" + + from ..nvapi.constants import MASK_SIZE + def fill(b): + for i in range(4, 4 + 32): + b[i] = 0xFF + d, err = nvcall(FUNC["GetClockBoostMask"], gpu, MASK_SIZE, ver=1, pre_fill=fill) + if d and len(d) >= 36: + mask = d[4:36] + _boost_mask_cache[gpu] = mask + return mask, "OK" + return None, err + + +def set_mask_bit(buf, point: int, offset: int = 4) -> None: + """Set a single bit in the 256-bit mask for one point.""" + byte_idx = offset + (point // 8) + bit_idx = point % 8 + buf[byte_idx] = int.from_bytes(buf[byte_idx:byte_idx + 1], "little") | (1 << bit_idx) + + +def set_mask_bits(buf, points: set[int], offset: int = 4) -> None: + """Set mask bits for a set of points.""" + for p in points: + set_mask_bit(buf, p, offset) + + +# ── Readers ─────────────────────────────────────────────────────────────────── + +def read_vfp_curve(gpu) -> tuple[Optional[list[tuple[int, int]]], str]: + """Read the base V/F curve (frequency + voltage pairs). + + Returns ([(freq_kHz, volt_uV), ...], "OK") or (None, error). + """ + mask, mask_err = get_boost_mask(gpu) + if not mask: + return None, f"GetClockBoostMask failed: {mask_err}" + + def fill(buf): + for i in range(32): + buf[4 + i] = mask[i] + + d, err = nvcall(FUNC["GetVFPCurve"], gpu, VFP_SIZE, ver=1, pre_fill=fill) + if not d: + return None, err + + points = [] + max_entries = (len(d) - VFP_BASE) // VFP_STRIDE + for i in range(max_entries): + off = VFP_BASE + i * VFP_STRIDE + freq = struct.unpack_from(" tuple[Optional[bytes], str]: + """Read the raw ClockBoostTable buffer. + + Used for snapshots, inspection, and as the baseline for writes. + Returns (bytes, "OK") or (None, error). + """ + mask, mask_err = get_boost_mask(gpu) + if not mask: + return None, f"GetClockBoostMask failed: {mask_err}" + + def fill(buf): + for i in range(32): + buf[4 + i] = mask[i] + + return nvcall(FUNC["GetClockBoostTable"], gpu, CT_SIZE, ver=1, pre_fill=fill) + + +def read_clock_table_parsed(gpu) -> tuple[Optional[list[tuple[int, int]]], str]: + """Read per-point offsets and flags from the ClockBoostTable. + + Returns a list of (delta_kHz, flags) tuples, or (None, error). + """ + d, err = read_clock_table_raw(gpu) + if not d: + return None, err + + entries = [] + max_entries = (len(d) - CT_BASE) // CT_STRIDE + for i in range(max_entries): + base_off = CT_BASE + i * CT_STRIDE + flags = struct.unpack_from(" tuple[Optional[list[int]], str]: + """Read per-point frequency offsets (kHz, signed) from the ClockBoostTable. + + Returns a list of integers, or (None, error). + """ + parsed, err = read_clock_table_parsed(gpu) + if not parsed: + return None, err + + offsets = [delta for delta, flags in parsed] + return offsets, "OK" + + +def read_clock_entry_full(data: bytes, point: int) -> dict: + """Extract all 9 raw fields from a single ClockBoostTable entry. + + Useful for diagnostics and verifying unknown fields. + """ + base = CT_BASE + point * CT_STRIDE + fields = {} + for j in range(9): + off = base + j * 4 + if j == 5: # freqDelta is signed + fields[f"field_{j:02d}_0x{j * 4:02X}"] = struct.unpack_from(" tuple[Optional[CurveState], str]: + """Read both the VFP curve and ClockBoostTable and merge into CurveState. + + Returns (CurveState, "OK") or (None, error). + """ + import time + vfp_points, vfp_err = read_vfp_curve(gpu) + if not vfp_points: + return None, vfp_err + + ct_entries, ct_err = read_clock_table_parsed(gpu) + if not ct_entries: + return None, ct_err + + points = [] + in_memory = False + for i, (freq_khz, volt_uv) in enumerate(vfp_points): + if freq_khz == 0 and volt_uv == 0: + break # end of populated entries + + delta_khz = ct_entries[i][0] if i < len(ct_entries) else 0 + flags = ct_entries[i][1] if i < len(ct_entries) else 0 + + if flags == 1: + in_memory = True + + points.append(VFPoint( + index=i, + freq_khz=freq_khz, + volt_uv=volt_uv, + delta_khz=delta_khz, + domain="memory" if in_memory else "gpu", + )) + + return CurveState(points=points, timestamp=time.time(), gpu_name=gpu_name), "OK" + + +# ── Writers ─────────────────────────────────────────────────────────────────── + +def build_write_buffer( + gpu, + point_deltas: dict[int, int], + full_mask: bool = False, +) -> tuple[Optional[ctypes.Array], str]: + """Build a SetClockBoostTable buffer with specified per-point deltas. + + Strategy: read the current ClockBoostTable, modify only the targeted + entries' freqDelta fields, set only the targeted mask bits (single-bit per + write to avoid touching neighbouring points). + + Args: + full_mask: if True, copy the complete GetClockBoostMask into the write + buffer instead of the default sparse (per-point) mask. + Older GPUs (e.g. Pascal) may require this. + + Returns (mutable_buffer, "OK") or (None, error). + """ + current_raw, err = read_clock_table_raw(gpu) + if not current_raw: + return None, f"Cannot read current ClockBoostTable: {err}" + + buf = ctypes.create_string_buffer(CT_SIZE) + ctypes.memmove(buf, current_raw, CT_SIZE) + + # Rewrite version word explicitly + struct.pack_into(" tuple[int, str]: + """Write per-point frequency offsets via SetClockBoostTable. + + Args: + gpu: NvAPI GPU handle + point_deltas: {point_index: delta_kHz} — only these points are written + dry_run: if True, build the buffer but don't call the driver + full_mask: if True, use the full GetClockBoostMask (for older GPUs) + + Returns (return_code, description). + """ + buf, err = build_write_buffer(gpu, point_deltas, full_mask=full_mask) + if buf is None: + return -999, err + + if dry_run: + return 0, "DRY RUN — buffer built but not sent to driver" + + return nvcall_raw(FUNC["SetClockBoostTable"], gpu, buf) + + +def write_global_offset(gpu, delta_khz: int, dry_run: bool = False) -> tuple[int, str]: + """Apply a uniform frequency offset to all GPU core points.""" + curve, err = read_curve(gpu) + if not curve: + return -999, f"Failed to read curve: {err}" + + point_deltas = {p.index: delta_khz for p in curve.points if p.domain == "gpu"} + return write_offsets(gpu, point_deltas, dry_run=dry_run) + + +def reset_offsets(gpu, dry_run: bool = False) -> tuple[int, str]: + """Zero all GPU core frequency offsets.""" + curve, err = read_curve(gpu) + if not curve: + return -999, f"Failed to read curve: {err}" + + point_deltas = {p.index: 0 for p in curve.points if p.domain == "gpu"} + return write_offsets(gpu, point_deltas, dry_run=dry_run) diff --git a/nvcurve/nvapi/__init__.py b/nvcurve/nvapi/__init__.py new file mode 100644 index 0000000..5f17b32 --- /dev/null +++ b/nvcurve/nvapi/__init__.py @@ -0,0 +1,11 @@ +from .bootstrap import nvcall, nvcall_raw, query_interface +from .constants import FUNC +from .errors import NvAPIError, NVAPI_ERRORS +from .types import VFPoint, CurveState, MonitoringSample, GpuInfo, SnapshotInfo + +__all__ = [ + "nvcall", "nvcall_raw", "query_interface", + "FUNC", + "NvAPIError", "NVAPI_ERRORS", + "VFPoint", "CurveState", "MonitoringSample", "GpuInfo", "SnapshotInfo", +] diff --git a/nvcurve/nvapi/bootstrap.py b/nvcurve/nvapi/bootstrap.py new file mode 100644 index 0000000..f56174c --- /dev/null +++ b/nvcurve/nvapi/bootstrap.py @@ -0,0 +1,81 @@ +"""NvAPI bootstrap — library loading, function resolution, versioned struct calls.""" + +import ctypes +import struct +import sys + +from .errors import NVAPI_ERRORS + + +def load_nvapi() -> ctypes.CDLL: + """Load libnvidia-api.so from the NVIDIA driver.""" + for name in ("libnvidia-api.so", "libnvidia-api.so.1"): + try: + return ctypes.CDLL(name) + except OSError: + continue + print("Error: Cannot load libnvidia-api.so") + print("Ensure the NVIDIA proprietary driver is installed.") + sys.exit(1) + + +# Module-level library handle and QueryInterface function pointer. +_nvapi = load_nvapi() +_QI = _nvapi.nvapi_QueryInterface +_QI.restype = ctypes.c_void_p +_QI.argtypes = [ctypes.c_uint32] + + +def query_interface(fid: int, nargs: int = 2): + """Resolve an NvAPI function pointer by its 32-bit ID. + + Returns a callable ctypes function, or None if the driver doesn't expose it. + """ + ptr = _QI(fid) + if not ptr: + return None + return ctypes.CFUNCTYPE(ctypes.c_int32, *[ctypes.c_void_p] * nargs)(ptr) + + +def nvcall( + fid: int, + gpu, + size: int, + ver: int = 1, + pre_fill=None, +) -> tuple[bytes | None, str]: + """Call an NvAPI function with a versioned struct buffer. + + Allocates a buffer of `size` bytes, writes the version word + ``(ver << 16) | size`` at offset 0, optionally calls ``pre_fill(buf)`` + to populate request fields, then invokes the function. + + Returns ``(bytes, "OK")`` on success or ``(None, error_description)`` on + failure. + """ + func = query_interface(fid) + if not func: + return None, "function pointer not found (driver too old?)" + buf = ctypes.create_string_buffer(size) + struct.pack_into(" tuple[int, str]: + """Call an NvAPI function with a pre-built mutable buffer. + + Used for write operations where the caller needs full control over the + buffer contents (e.g. SetClockBoostTable). + + Returns ``(return_code, description)``. + """ + func = query_interface(fid) + if not func: + return -999, "function pointer not found (driver too old?)" + ret = func(gpu, buf) + return ret, NVAPI_ERRORS.get(ret, f"unknown ({ret})") diff --git a/nvcurve/nvapi/constants.py b/nvcurve/nvapi/constants.py new file mode 100644 index 0000000..7f00416 --- /dev/null +++ b/nvcurve/nvapi/constants.py @@ -0,0 +1,55 @@ +# NvAPI function IDs, struct sizes, offsets, and safety constants. +# All values verified by hex-dump analysis on RTX 5090 (GB202), driver 590.48.01. + +# ── Function IDs ──────────────────────────────────────────────────────────── + +FUNC = { + # Bootstrap + "Initialize": 0x0150E828, + "EnumPhysicalGPUs": 0xE5AC921F, + "GetFullName": 0xCEEE8E9F, + + # V/F curve (read) + "GetVFPCurve": 0x21537AD4, # ClkVfPointsGetStatus + "GetClockBoostMask": 0x507B4B59, # ClkVfPointsGetInfo + "GetClockBoostTable": 0x23F1B133, # ClkVfPointsGetControl + "GetCurrentVoltage": 0x465F9BCF, # ClientVoltRailsGetStatus + "GetClockBoostRanges": 0x64B43A6A, # ClkDomainsGetInfo + + # Additional read + "GetPerfLimits": 0xE440B867, # PerfClientLimitsGetStatus + "GetVoltBoostPercent": 0x9DF23CA1, # ClientVoltRailsGetControl + + # Write + "SetClockBoostTable": 0x0733E009, # ClkVfPointsSetControl +} + +# ── GetVFPCurve (0x21537AD4) ───────────────────────────────────────────────── +# 128 × 28-byte entries at offset 0x48; freq_kHz at +0x00, volt_uV at +0x04. + +VFP_SIZE = 0x1C28 # 7208 bytes +VFP_BASE = 0x48 +VFP_STRIDE = 0x1C # 28 bytes +VFP_POINTS = (VFP_SIZE - VFP_BASE) // VFP_STRIDE + +# ── Get/SetClockBoostTable (0x23F1B133 / 0x0733E009) ──────────────────────── +# 128 × 36-byte entries at offset 0x44; freqDelta (int32, kHz) at entry+0x14. +# Correct layout verified against LACT issue #936 (which had wrong stride 0x48). + +CT_SIZE = 0x2420 # 9248 bytes +CT_BASE = 0x44 +CT_STRIDE = 0x24 # 36 bytes +CT_DELTA_OFF = 0x14 # freqDelta offset within entry +CT_POINTS = (CT_SIZE - CT_BASE) // CT_STRIDE + +# ── Other struct sizes ─────────────────────────────────────────────────────── + +MASK_SIZE = 0x182C # GetClockBoostMask +VOLT_SIZE = 0x004C # GetCurrentVoltage (voltage µV at offset 0x28) +RANGES_SIZE = 0x0928 # GetClockBoostRanges +PERF_SIZE = 0x030C # GetPerfLimits (version 2, not 1) +VBOOST_SIZE = 0x0028 # GetVoltBoostPercent + +# ── Safety ─────────────────────────────────────────────────────────────────── + +MAX_DELTA_KHZ = 3_000_000 # ±3000 MHz hard cap (Blackwell driver limit) diff --git a/nvcurve/nvapi/errors.py b/nvcurve/nvapi/errors.py new file mode 100644 index 0000000..85128f1 --- /dev/null +++ b/nvcurve/nvapi/errors.py @@ -0,0 +1,27 @@ +# NvAPI error codes and exception class. +# Linux NvAPI uses negative integers, not the 0x80000000+ range from Windows. + +NVAPI_ERRORS: dict[int, str] = { + 0: "OK", + -1: "GENERIC_ERROR", + -5: "INVALID_ARGUMENT", + -6: "NVIDIA_DEVICE_NOT_FOUND", + -7: "END_ENUMERATION", + -8: "INVALID_HANDLE", + -9: "INCOMPATIBLE_STRUCT_VERSION", + -10: "HANDLE_INVALIDATED", + -14: "INVALID_POINTER", +} + + +class NvAPIError(Exception): + """Raised when an NvAPI call returns a non-zero error code.""" + + def __init__(self, code: int, context: str = ""): + self.code = code + self.name = NVAPI_ERRORS.get(code, f"unknown ({code})") + self.context = context + msg = f"NvAPI error {code} ({self.name})" + if context: + msg = f"{context}: {msg}" + super().__init__(msg) diff --git a/nvcurve/nvapi/types.py b/nvcurve/nvapi/types.py new file mode 100644 index 0000000..67e90e8 --- /dev/null +++ b/nvcurve/nvapi/types.py @@ -0,0 +1,76 @@ +"""Clean Python dataclasses for V/F curve data. + +Raw struct manipulation stays in the call layer (bootstrap.py + hal/). +Everything above HAL works with these types. +""" + +from dataclasses import dataclass, field + + +@dataclass +class VFPoint: + index: int + freq_khz: int # Base frequency from VFP curve + volt_uv: int # Voltage from VFP curve + delta_khz: int # Offset from ClockBoostTable (signed) + domain: str = "gpu" # "gpu" or "memory" + + @property + def effective_freq_khz(self) -> int: + return self.freq_khz + self.delta_khz + + @property + def freq_mhz(self) -> float: + return self.freq_khz / 1000.0 + + @property + def effective_freq_mhz(self) -> float: + return self.effective_freq_khz / 1000.0 + + @property + def volt_mv(self) -> float: + return self.volt_uv / 1000.0 + + @property + def delta_mhz(self) -> float: + return self.delta_khz / 1000.0 + + +@dataclass +class CurveState: + points: list[VFPoint] + timestamp: float + gpu_name: str + + +@dataclass +class MonitoringSample: + timestamp: float + voltage_uv: int | None + clock_mhz: float | None + temp_c: float | None + power_w: float | None + fan_pct: float | None + pstate: int | None # Performance state: 0 (P0, max) – 15 (P15, min) + mem_used_bytes: int | None # VRAM used (bytes) + mem_total_bytes: int | None # VRAM total (bytes) + gpu_util_pct: float | None # GPU core utilization (0–100) + mem_util_pct: float | None # Memory bus utilization (0–100) + mem_clock_mhz: float | None = None # Current memory clock (NVML_CLOCK_MEM) + + +@dataclass +class GpuInfo: + name: str + index: int + uuid: str | None = None + pci_bus_id: int | None = None + + +@dataclass +class SnapshotInfo: + filepath: str + timestamp: str + gpu: str + nonzero_offsets: int + size: int diff --git a/nvcurve/profiles/__init__.py b/nvcurve/profiles/__init__.py new file mode 100644 index 0000000..60dc80f --- /dev/null +++ b/nvcurve/profiles/__init__.py @@ -0,0 +1 @@ +# Make profiles a package diff --git a/nvcurve/profiles/apply.py b/nvcurve/profiles/apply.py new file mode 100644 index 0000000..f5c1031 --- /dev/null +++ b/nvcurve/profiles/apply.py @@ -0,0 +1,187 @@ +"""Apply saved profiles to hardware, with optional read-back verification.""" + +import json +import logging +import os +import sys +import time + +log = logging.getLogger("nvcurve.profiles.apply") + +_PERSISTENT_CONFIG_FILE = "/etc/nvcurve/config.json" + + +def _gpu_stable_key(info) -> str: + if info.uuid: + return info.uuid + if info.pci_bus_id is not None: + return f"pci:{info.pci_bus_id:04x}" + return f"idx:{info.index}" + + +def apply_profile(gpu_index: int, name: str, cfg) -> list[str]: + """Apply a named profile to the given GPU. Returns a list of error strings.""" + from .native import load_profile + from ..hal.gpu import get_gpu + from ..hal.limits import set_clock_offsets, set_power_limit + from ..hal.vfcurve import write_offsets, reset_offsets + from ..hal.snapshot import save as snapshot_save + from ..safety import validate_write + + safe_name = "".join(c for c in name if c.isalnum() or c in " _-()").strip() + filepath = os.path.join(cfg.profile_dir, f"{safe_name}.json") + + profile = load_profile(filepath) # raises FileNotFoundError if missing + gpu, gpu_name = get_gpu(index=gpu_index) + + errs: list[str] = [] + + # Apply mem offset first — driver may reset curve table as a side-effect. + if profile.mem_offset_mhz is not None: + ok, msg = set_clock_offsets(None, profile.mem_offset_mhz, gpu_index) + if not ok: + errs.append(f"Mem offset: {msg}") + + if profile.power_limit_w is not None: + ok, msg = set_power_limit(profile.power_limit_w, gpu_index) + if not ok: + errs.append(f"Power limit: {msg}") + + if profile.curve_deltas: + deltas = {int(k): v for k, v in profile.curve_deltas.items()} + errors = validate_write(deltas, cfg.max_delta_khz) + if errors: + errs.append("Curve: " + "; ".join(errors)) + else: + if cfg.auto_snapshot: + try: + snapshot_save(gpu, gpu_name, cfg.snapshot_dir, cfg.max_snapshots) + except Exception as exc: + log.warning("Auto-snapshot failed: %s", exc) + ret, desc = write_offsets(gpu, deltas) + if ret != 0: + errs.append(f"Curve write failed ({ret}): {desc}") + else: + reset_offsets(gpu) + + return errs + + +def apply_with_retry(gpu_index: int, name: str, cfg, max_retries: int = 3) -> bool: + """Apply a named profile with read-back verification, retrying on mismatch.""" + from .native import load_profile + from ..hal.gpu import get_gpu + from ..hal.vfcurve import read_clock_offsets + + safe_name = "".join(c for c in name if c.isalnum() or c in " _-()").strip() + filepath = os.path.join(cfg.profile_dir, f"{safe_name}.json") + + try: + profile = load_profile(filepath) + except FileNotFoundError: + log.warning("Auto-load profile %r not found — skipping GPU %d", name, gpu_index) + return False + + expected: dict[int, int] = ( + {int(k): v for k, v in profile.curve_deltas.items()} + if profile.curve_deltas else {} + ) + + for attempt in range(max_retries): + try: + errs = apply_profile(gpu_index, name, cfg) + except Exception as exc: + log.warning("Auto-load attempt %d/%d exception: %s", attempt + 1, max_retries, exc) + errs = [str(exc)] + + if errs: + log.warning("Auto-load attempt %d/%d errors: %s", + attempt + 1, max_retries, "; ".join(errs)) + elif expected: + gpu, _ = get_gpu(index=gpu_index) + offsets, err = read_clock_offsets(gpu) + if offsets is None: + log.warning("Auto-load attempt %d/%d: read-back failed: %s", + attempt + 1, max_retries, err) + else: + mismatches = [ + f"pt{idx}: expected {val/1000:+.0f}MHz got {offsets[idx]/1000:+.0f}MHz" + for idx, val in expected.items() + if idx < len(offsets) and offsets[idx] != val + ] + if not mismatches: + log.info("Auto-load profile %r verified on GPU %d (attempt %d/%d)", + name, gpu_index, attempt + 1, max_retries) + return True + log.warning("Auto-load attempt %d/%d: read-back mismatch — %s", + attempt + 1, max_retries, "; ".join(mismatches)) + else: + log.info("Auto-load profile %r applied on GPU %d (attempt %d/%d)", + name, gpu_index, attempt + 1, max_retries) + return True + + if attempt < max_retries - 1: + delay = 2 ** attempt # 1s, 2s, 4s + log.info("Retrying auto-load in %ds…", delay) + time.sleep(delay) + + log.warning("Auto-load profile %r failed after %d attempts on GPU %d", + name, max_retries, gpu_index) + return False + + +def run_autoload() -> None: + """Read config and apply all configured auto-load profiles. Requires root.""" + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s %(name)s: %(message)s", + ) + + if os.geteuid() != 0: + print("nvcurve autoload: must run as root", file=sys.stderr) + sys.exit(1) + + try: + with open(_PERSISTENT_CONFIG_FILE) as f: + cfg_data = json.load(f) + except Exception: + cfg_data = {} + + auto_load_profiles: dict = cfg_data.get("auto_load_profiles", {}) + if not auto_load_profiles: + log.info("No auto-load profiles configured.") + return + + from ..config import Config + cfg = Config() + for key in ("max_delta_khz", "auto_snapshot", "max_snapshots", + "snapshot_dir", "profile_dir"): + if key in cfg_data: + setattr(cfg, key, cfg_data[key]) + + from ..hal.gpu import init_nvapi, discover_gpus + from ..hal.monitoring import init_nvml, shutdown_nvml + + try: + init_nvapi() + except Exception as exc: + log.error("Failed to initialize NvAPI: %s", exc) + sys.exit(1) + + init_nvml() # best-effort + gpus = discover_gpus() + if not gpus: + log.warning("No GPUs discovered.") + + key_to_idx = {_gpu_stable_key(info): info.index for info in gpus} + for gpu_key, profile_name in auto_load_profiles.items(): + if not profile_name: + continue + gpu_idx = key_to_idx.get(gpu_key) + if gpu_idx is None: + log.warning("Auto-load: no GPU found with key %r — skipping", gpu_key) + continue + log.info("Auto-loading profile %r on GPU %d (%s)", profile_name, gpu_idx, gpu_key) + apply_with_retry(gpu_idx, profile_name, cfg) + + shutdown_nvml() diff --git a/nvcurve/profiles/native.py b/nvcurve/profiles/native.py new file mode 100644 index 0000000..534f54a --- /dev/null +++ b/nvcurve/profiles/native.py @@ -0,0 +1,93 @@ +"""Native profile storage and schema.""" + +import json +import os +import glob +from dataclasses import dataclass, asdict +from typing import Dict, Optional, List + + +@dataclass +class ProfileData: + name: str + gpu_name: str + curve_deltas: Dict[str, int] # { "index": delta_khz } + mem_offset_mhz: Optional[int] = None + power_limit_w: Optional[int] = None + + +def save_profile(profile_dir: str, data: ProfileData) -> str: + """Save profile to JSON, sanitising the filename.""" + os.makedirs(profile_dir, exist_ok=True) + safe_name = "".join(c for c in data.name if c.isalnum() or c in " _-()").strip() + if not safe_name: + safe_name = "Unnamed" + + filepath = os.path.join(profile_dir, f"{safe_name}.json") + with open(filepath, "w", encoding="utf-8") as f: + json.dump(asdict(data), f, indent=2) + return filepath + + +def load_profile(filepath: str) -> ProfileData: + """Load profile from JSON.""" + with open(filepath, "r", encoding="utf-8") as f: + data = json.load(f) + # Migrate old field names. + if "vram_p0_offset_mhz" in data and "mem_offset_mhz" not in data: + data["mem_offset_mhz"] = data.pop("vram_p0_offset_mhz") + # Drop removed fields so old profiles don't cause TypeError. + for obsolete in ("gpu_locked_min_mhz", "gpu_locked_max_mhz", "vram_p0_offset_mhz"): + data.pop(obsolete, None) + return ProfileData(**data) + + +def list_profiles(profile_dir: str) -> List[ProfileData]: + """Return a list of all safely readable profiles.""" + if not os.path.exists(profile_dir): + return [] + profiles = [] + for fp in glob.glob(os.path.join(profile_dir, "*.json")): + try: + profiles.append(load_profile(fp)) + except Exception as e: + # log warning ideally, but swallowing for robustness + pass + # Sort alphabetically by name + profiles.sort(key=lambda p: p.name.lower()) + return profiles + + +def rename_profile(profile_dir: str, old_name: str, new_name: str) -> bool: + """Rename a profile: update the name field and move the file.""" + old_safe = "".join(c for c in old_name if c.isalnum() or c in " _-()").strip() + new_safe = "".join(c for c in new_name if c.isalnum() or c in " _-()").strip() + if not new_safe: + return False + old_path = os.path.join(profile_dir, f"{old_safe}.json") + new_path = os.path.join(profile_dir, f"{new_safe}.json") + if not os.path.exists(old_path): + return False + try: + profile = load_profile(old_path) + profile.name = new_name + with open(new_path, "w", encoding="utf-8") as f: + json.dump(asdict(profile), f, indent=2) + if old_path != new_path: + os.remove(old_path) + return True + except Exception: + return False + + +def delete_profile(profile_dir: str, name: str) -> bool: + """Delete a profile by name.""" + safe_name = "".join(c for c in name if c.isalnum() or c in " _-()").strip() + filepath = os.path.join(profile_dir, f"{safe_name}.json") + if os.path.exists(filepath): + try: + os.remove(filepath) + return True + except Exception: + return False + return False diff --git a/nvcurve/safety.py b/nvcurve/safety.py new file mode 100644 index 0000000..7de2731 --- /dev/null +++ b/nvcurve/safety.py @@ -0,0 +1,72 @@ +"""Centralized safety validation for all write operations. + +Every write path calls validate_write() before touching hardware. +""" + +from .nvapi.constants import CT_POINTS + + +def check_negative_freq_warnings( + point_deltas: dict[int, int], + vfp_freqs_khz: list[int], + current_offsets_khz: list[int] | None = None, +) -> list[str]: + """Return warnings for deltas that would produce negative effective frequency. + + GetVFPCurve returns the *current effective* frequencies (already reflecting + any applied boost delta). The true hardware base for each point is therefore: + true_base = vfp_freq - current_offset + + The new effective with the proposed delta: + new_effective = true_base + new_delta = vfp_freq + (new_delta - current_offset) + + The driver clamps results, but users should be informed when their delta + would push a point's effective frequency below zero. + """ + warnings = [] + for point, new_delta_khz in point_deltas.items(): + if point < 0 or point >= len(vfp_freqs_khz): + continue + vfp_freq = vfp_freqs_khz[point] + if vfp_freq == 0: + continue # unused / placeholder point + current_delta = ( + current_offsets_khz[point] + if current_offsets_khz and point < len(current_offsets_khz) + else 0 + ) + new_effective = vfp_freq + (new_delta_khz - current_delta) + if new_effective < 0: + warnings.append( + f"Point {point}: delta {new_delta_khz / 1000:+.0f} MHz would produce " + f"effective frequency {new_effective / 1000:.0f} MHz — driver will clamp to 0." + ) + return warnings + + +def validate_write( + point_deltas: dict[int, int], + max_delta_khz: int, +) -> list[str]: + """Validate a proposed write request. + + Args: + point_deltas: {point_index: delta_kHz} + max_delta_khz: absolute delta limit (e.g. 300_000 for ±300 MHz) + + Returns a list of error message strings. Empty list means the request is safe. + """ + errors = [] + for point, delta_khz in point_deltas.items(): + if point < 0 or point >= CT_POINTS: + errors.append(f"Point {point} out of range (0–{CT_POINTS - 1})") + continue + + if abs(delta_khz) > max_delta_khz: + errors.append( + f"Delta {delta_khz / 1000:+.0f} MHz for point {point} exceeds " + f"safety limit of ±{max_delta_khz / 1000:.0f} MHz. " + "Use --max-delta to raise the limit if needed." + ) + + return errors diff --git a/nvcurve/server.py b/nvcurve/server.py new file mode 100644 index 0000000..1284d79 --- /dev/null +++ b/nvcurve/server.py @@ -0,0 +1,1245 @@ +"""FastAPI API server — REST + WebSocket. + +Run via: nvcurve serve [--host 127.0.0.1 --port 8042] +Or: uvicorn nvcurve.server:app + +Requires root (NvAPI needs it). +""" + +import asyncio +import json +import logging +from contextlib import asynccontextmanager +from typing import Any + +from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel + +from .config import Config, default_config +from .hal.gpu import get_gpu, discover_gpus +from .hal.monitoring import ( + get_driver_version, + get_vram_total, + init_nvml, + poll, + shutdown_nvml, +) +from .hal.ranges import get_clock_ranges +from .hal.snapshot import ( + list_snapshots, + restore as snapshot_restore, + save as snapshot_save, +) +from .hal.limits import ( + get_power_limit, + set_power_limit, + get_clock_offsets, + set_clock_offsets, + get_mem_offset_range, +) +from .profiles.native import ( + ProfileData, + save_profile, + load_profile, + list_profiles, + delete_profile, + rename_profile, +) +from .hal.vfcurve import ( + read_clock_offsets, + read_curve, + read_vfp_curve, + reset_offsets, + write_global_offset, + write_offsets, +) +from .safety import validate_write, check_negative_freq_warnings + +log = logging.getLogger("nvcurve.server") + + +def _open_browser_as_user(url: str) -> None: + """Open URL as the original (non-root) user when running under sudo.""" + import os + import subprocess + sudo_user = os.environ.get("SUDO_USER") + if sudo_user and os.geteuid() == 0: + try: + subprocess.Popen( + ["runuser", "-u", sudo_user, "--", "xdg-open", url], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return + except Exception: + pass + import webbrowser + webbrowser.open(url) + +# ── Shared app state ────────────────────────────────────────────────────────── + +_state: dict[str, Any] = { + "gpus": {}, # dict[int, dict] mapping gpu_index -> gpu state + "config": default_config, +} + +def _get_gpu_state(gpu_index: int) -> dict: + if gpu_index not in _state["gpus"]: + from fastapi import HTTPException + raise HTTPException(status_code=404, detail=f"GPU {gpu_index} not found") + return _state["gpus"][gpu_index] + + +# ── Serialization helpers ───────────────────────────────────────────────────── + +def _vfpoint_dict(p) -> dict: + return { + "index": p.index, + "freq_khz": p.freq_khz, + "freq_mhz": p.freq_mhz, + "volt_uv": p.volt_uv, + "volt_mv": p.volt_mv, + "delta_khz": p.delta_khz, + "delta_mhz": p.delta_mhz, + "effective_freq_khz": p.effective_freq_khz, + "effective_freq_mhz": p.effective_freq_mhz, + "domain": p.domain, + } + + +def _curve_state_dict(state) -> dict: + return { + "gpu_name": state.gpu_name, + "timestamp": state.timestamp, + "points": [_vfpoint_dict(p) for p in state.points], + } + + +def _sample_dict(s) -> dict: + return { + "timestamp": s.timestamp, + "voltage_uv": s.voltage_uv, + "voltage_mv": s.voltage_uv / 1000.0 if s.voltage_uv is not None else None, + "clock_mhz": s.clock_mhz, + "mem_clock_mhz": s.mem_clock_mhz, + "temp_c": s.temp_c, + "power_w": s.power_w, + "fan_pct": s.fan_pct, + "pstate": s.pstate, + "pstate_label": f"P{s.pstate}" if s.pstate is not None else None, + "mem_used_bytes": s.mem_used_bytes, + "mem_total_bytes": s.mem_total_bytes, + "mem_used_mib": round(s.mem_used_bytes / (1024 ** 2), 1) if s.mem_used_bytes is not None else None, + "mem_total_mib": round(s.mem_total_bytes / (1024 ** 2), 1) if s.mem_total_bytes is not None else None, + "gpu_util_pct": s.gpu_util_pct, + "mem_util_pct": s.mem_util_pct, + } + + +# ── WebSocket broadcast ─────────────────────────────────────────────────────── + +async def _broadcast(clients: set, payload: dict) -> None: + """Send JSON payload to all connected WebSocket clients, evict dead ones.""" + dead = set() + for ws in list(clients): + try: + await ws.send_json(payload) + except Exception: + dead.add(ws) + clients -= dead + + +# ── Background monitoring poller ───────────────────────────────────────────── + +async def _monitor_poller(gpu_index: int) -> None: + """Continuously poll GPU state and push to connected monitor WebSocket clients.""" + cfg: Config = _state["config"] + while True: + try: + g_state = _state["gpus"].get(gpu_index) + if g_state and g_state["gpu"] is not None and g_state["monitor_clients"]: + loop = asyncio.get_running_loop() + sample = await loop.run_in_executor( + None, poll, g_state["gpu"], gpu_index + ) + await _broadcast(g_state["monitor_clients"], _sample_dict(sample)) + except Exception as exc: + log.warning("Monitor poller error for GPU %d: %s", gpu_index, exc) + await asyncio.sleep(cfg.poll_interval_s) + + +# ── Lifespan ────────────────────────────────────────────────────────────────── + +@asynccontextmanager +async def lifespan(app: FastAPI): + loop = asyncio.get_running_loop() + + # Initialize NVML (best-effort) + await loop.run_in_executor(None, init_nvml) + + gpu_infos = await loop.run_in_executor(None, discover_gpus) + if not gpu_infos: + log.warning("No GPUs discovered.") + + poller_tasks = [] + + for info in gpu_infos: + idx = info.index + g_state = { + "gpu": None, + "gpu_name": info.name, + "uuid": info.uuid, + "pci_bus_id": info.pci_bus_id, + "write_lock": asyncio.Lock(), + "last_offsets": None, + "active_profile": None, + "monitor_clients": set(), + "curve_clients": set(), + } + _state["gpus"][idx] = g_state + + try: + gpu, name = await loop.run_in_executor(None, get_gpu, idx) + g_state["gpu"] = gpu + g_state["gpu_name"] = name + log.info("GPU %d: %s", idx, name) + + # Read initial offsets for reconciliation baseline + offsets, err = await loop.run_in_executor(None, read_clock_offsets, gpu) + if offsets: + g_state["last_offsets"] = offsets + + poller_tasks.append(asyncio.create_task(_monitor_poller(idx))) + except Exception as exc: + log.error("Failed to initialize GPU %d: %s", idx, exc) + + # ── Backward Compatibility Bridge ────────────────────────────────────────── + # NOTE: This auto-load path is for users running the server directly (e.g. + # via an old systemd unit file that lacks the new daemon mode). + # In the future, this will be removed and auto-loading will be the + # responsibility of daemon.py only. + cfg: Config = _state["config"] + if cfg.auto_load_profiles: + # Build a reverse map: stable_key → current gpu_index + key_to_idx = {_gpu_stable_key(idx): idx for idx in _state["gpus"]} + for gpu_key, profile_name in cfg.auto_load_profiles.items(): + if not profile_name: + continue + gpu_idx = key_to_idx.get(gpu_key) + if gpu_idx is None: + log.warning("Auto-load: no GPU found with key %r — skipping", gpu_key) + continue + log.info("Auto-loading profile %r on GPU %d (%s) [compat path]", + profile_name, gpu_idx, gpu_key) + try: + await _auto_apply_profile_with_retry(profile_name, gpu_idx) + except FileNotFoundError: + log.warning("Auto-load profile %r not found in %s — skipping GPU %d", + profile_name, cfg.profile_dir, gpu_idx) + except Exception as exc: + log.warning("Auto-load profile %r failed on GPU %d: %s — skipping", + profile_name, gpu_idx, exc) + # ────────────────────────────────────────────────────────────────────────── + + yield # server is running + + for task in poller_tasks: + task.cancel() + for task in poller_tasks: + try: + await task + except asyncio.CancelledError: + pass + + await loop.run_in_executor(None, shutdown_nvml) + + +# ── App ─────────────────────────────────────────────────────────────────────── + +app = FastAPI(title="nvcurve", version="0.5.0", lifespan=lifespan) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_methods=["*"], + allow_headers=["*"], +) + + +# ── Request models ──────────────────────────────────────────────────────────── + +class WriteRequest(BaseModel): + deltas: dict[int, int] # {point_index: delta_kHz} + max_delta_khz: int | None = None # per-request safety limit override + + +class GlobalOffsetRequest(BaseModel): + delta_khz: int + max_delta_khz: int | None = None # per-request safety limit override + + +class VerifyRequest(BaseModel): + deltas: dict[int, int] # {point_index: delta_kHz} — pre-expanded by CLI + + +class SnapshotRestoreRequest(BaseModel): + filepath: str | None = None + + +class LimitsRequest(BaseModel): + power_limit_w: int | None = None + mem_offset_mhz: int | None = None + + +class ProfileSaveRequest(BaseModel): + name: str + + +class ProfileRenameRequest(BaseModel): + new_name: str + + +class ConfigUpdateRequest(BaseModel): + auto_load_profile: str | None = None + gpu_index: int = 0 + + +# ── Helper: run blocking HAL call in thread pool ────────────────────────────── + +async def _run(fn, *args): + loop = asyncio.get_running_loop() + return await loop.run_in_executor(None, fn, *args) + + +def _require_gpu(gpu_index: int = 0): + g_state = _get_gpu_state(gpu_index) + gpu = g_state["gpu"] + if gpu is None: + raise HTTPException(status_code=503, detail=f"GPU {gpu_index} not initialized") + return gpu, g_state + + +# ── REST endpoints ──────────────────────────────────────────────────────────── + +@app.get("/api/gpus") +async def api_gpus(): + """List all discovered GPUs.""" + from .hal.gpu import discover_gpus + gpu_infos = await _run(discover_gpus) + return [ + { + "index": info.index, + "name": info.name, + "uuid": info.uuid, + "pci_bus_id": info.pci_bus_id, + } + for info in gpu_infos + ] + +@app.get("/api/gpu") +async def api_gpu(gpu_index: int = 0): + """GPU info: name, driver version, VRAM.""" + gpu, g_state = _require_gpu(gpu_index) + driver = get_driver_version() + vram = get_vram_total(gpu_index) + return { + "name": g_state["gpu_name"], + "index": gpu_index, + "driver_version": driver, + "vram_bytes": vram, + "vram_gib": round(vram / (1024 ** 3), 2) if vram else None, + } + + +@app.get("/api/curve") +async def api_curve(gpu_index: int = 0): + """Full CurveState: all V/F points with base freq, voltage, delta, effective freq.""" + gpu, g_state = _require_gpu(gpu_index) + state, err = await _run(read_curve, gpu, g_state["gpu_name"]) + if state is None: + raise HTTPException(status_code=500, detail=f"Failed to read curve: {err}") + + # Update reconciliation baseline + g_state["last_offsets"] = [p.delta_khz for p in state.points] + return _curve_state_dict(state) + + +@app.get("/api/curve/{point}") +async def api_curve_point(point: int, gpu_index: int = 0): + """Single V/F point detail.""" + gpu, g_state = _require_gpu(gpu_index) + state, err = await _run(read_curve, gpu, g_state["gpu_name"]) + if state is None: + raise HTTPException(status_code=500, detail=f"Failed to read curve: {err}") + if point < 0 or point >= len(state.points): + raise HTTPException(status_code=400, detail=f"Point index must be 0–{len(state.points)-1}") + return _vfpoint_dict(state.points[point]) + + +@app.get("/api/ranges") +async def api_ranges(gpu_index: int = 0): + """Clock boost domain ranges (min/max offset per domain).""" + gpu, g_state = _require_gpu(gpu_index) + ranges, err = await _run(get_clock_ranges, gpu) + if ranges is None: + raise HTTPException(status_code=500, detail=f"Failed to read ranges: {err}") + return ranges + + +@app.get("/api/voltage") +async def api_voltage(gpu_index: int = 0): + """Current GPU core voltage.""" + from .hal.monitoring import read_voltage + gpu, g_state = _require_gpu(gpu_index) + voltage_uv, err = await _run(read_voltage, gpu) + if voltage_uv is None: + raise HTTPException(status_code=500, detail=f"Failed to read voltage: {err}") + return {"voltage_uv": voltage_uv, "voltage_mv": voltage_uv / 1000.0} + + +@app.get("/api/monitor") +async def api_monitor(gpu_index: int = 0): + """One-shot monitoring snapshot: voltage, clock, temp, power, fan, p-state, VRAM, utilization.""" + gpu, g_state = _require_gpu(gpu_index) + sample = await _run(poll, gpu, gpu_index) + return _sample_dict(sample) + + +@app.get("/api/snapshots") +async def api_snapshots(): + """List saved ClockBoostTable snapshots.""" + cfg: Config = _state["config"] + snapshots = await _run(list_snapshots, cfg.snapshot_dir) + return [ + { + "filepath": s.filepath, + "timestamp": s.timestamp, + "gpu": s.gpu, + "nonzero_offsets": s.nonzero_offsets, + "size": s.size, + } + for s in snapshots + ] + + +def _persist_config_field(key: str, value) -> None: + """Write a single key into /etc/nvcurve/config.json if the file exists. + + The file is created by `service install`. If it doesn't exist (e.g. the + service was never installed), config changes are in-memory only for the + current server session. Silently ignores errors. + """ + import json as _json + import os as _os + config_path = "/etc/nvcurve/config.json" + if not _os.path.exists(config_path): + return + try: + with open(config_path) as f: + data = _json.load(f) + except Exception: + data = {} + if value is not None: + data[key] = value + else: + data.pop(key, None) + try: + with open(config_path, "w") as f: + _json.dump(data, f) + except Exception as exc: + log.warning("Failed to persist config field %r: %s", key, exc) + + +def _gpu_stable_key(gpu_index: int) -> str: + """Return a stable identifier for a GPU suitable for use as a config key. + + Preference order: NVML UUID → PCI bus ID → 'idx:{n}' fallback. + UUID is the most stable across reboots and GPU slot changes. + """ + g_state = _state["gpus"].get(gpu_index, {}) + uuid = g_state.get("uuid") + if uuid: + return uuid + pci = g_state.get("pci_bus_id") + if pci is not None: + return f"pci:{pci:04x}" + return f"idx:{gpu_index}" + + +def _persist_auto_load_profiles(profiles: dict[str, str]) -> None: + """Persist auto_load_profiles dict to config.json.""" + _persist_config_field("auto_load_profiles", profiles if profiles else None) + + +@app.get("/api/profiles") +async def api_profiles(gpu_index: int = 0): + """List saved native profiles, the active profile name, and the auto-load profile name.""" + cfg: Config = _state["config"] + profiles = await _run(list_profiles, cfg.profile_dir) + g_state = _state["gpus"].get(gpu_index) + active = g_state["active_profile"] if g_state else None + return { + "profiles": profiles, + "active": active, + "auto_load": cfg.auto_load_profiles.get(_gpu_stable_key(gpu_index)), + } + + +@app.post("/api/profiles") +async def api_profile_save(req: ProfileSaveRequest, gpu_index: int = 0): + """Save current GPU state (curve deltas + limits) as a named profile.""" + gpu, g_state = _require_gpu(gpu_index) + cfg: Config = _state["config"] + + state, err = await _run(read_curve, gpu, g_state["gpu_name"]) + if state is None: + raise HTTPException(status_code=500, detail=f"Failed to read curve: {err}") + + curve_deltas = {str(p.index): p.delta_khz for p in state.points if p.delta_khz != 0} + + try: + power_info = await _run(get_power_limit, gpu_index) + offsets = await _run(get_clock_offsets, gpu_index) + power_limit_w = power_info.get("power_limit_w") + mem_offset_mhz = offsets.get("mem_offset_mhz") + except Exception: + power_limit_w = None + mem_offset_mhz = None + + data = ProfileData( + name=req.name, + gpu_name=g_state["gpu_name"], + curve_deltas=curve_deltas, + mem_offset_mhz=mem_offset_mhz, + power_limit_w=power_limit_w, + ) + filepath = await _run(save_profile, cfg.profile_dir, data) + g_state["active_profile"] = req.name + return {"ok": True, "filepath": filepath} + + +async def _auto_apply_profile_with_retry( + name: str, gpu_index: int = 0, max_retries: int = 3 +) -> None: + """Apply profile on startup with read-back verification and exponential backoff retry. + + Raises FileNotFoundError if the profile does not exist (no point retrying). + Logs a warning and gives up after max_retries failed attempts. + """ + import os as _os + cfg: Config = _state["config"] + g_state = _get_gpu_state(gpu_index) + gpu = g_state["gpu"] + + safe_name = "".join(c for c in name if c.isalnum() or c in " _-()").strip() + filepath = _os.path.join(cfg.profile_dir, f"{safe_name}.json") + profile = await _run(load_profile, filepath) # raises FileNotFoundError if missing + + expected: dict[int, int] = ( + {int(k): v for k, v in profile.curve_deltas.items()} if profile.curve_deltas else {} + ) + + for attempt in range(max_retries): + errs = await _apply_profile(name, gpu_index) + + if errs: + log.warning("Auto-load attempt %d/%d had errors: %s", + attempt + 1, max_retries, "; ".join(errs)) + elif expected: + offsets, err = await _run(read_clock_offsets, gpu) + if offsets is None: + log.warning("Auto-load attempt %d/%d: read-back failed: %s", + attempt + 1, max_retries, err) + else: + mismatches = [ + f"pt{idx}: expected {val/1000:+.0f}MHz got {offsets[idx]/1000:+.0f}MHz" + for idx, val in expected.items() + if idx < len(offsets) and offsets[idx] != val + ] + if not mismatches: + log.info("Auto-load profile %r verified on GPU %d (attempt %d/%d)", + name, gpu_index, attempt + 1, max_retries) + return + log.warning("Auto-load attempt %d/%d: read-back mismatch — %s", + attempt + 1, max_retries, "; ".join(mismatches)) + else: + log.info("Auto-load profile %r applied on GPU %d (attempt %d/%d)", + name, gpu_index, attempt + 1, max_retries) + return + + if attempt < max_retries - 1: + delay = 2 ** attempt # 1 s, 2 s, 4 s + log.info("Retrying auto-load in %ds…", delay) + await asyncio.sleep(delay) + + log.warning("Auto-load profile %r failed after %d attempts — giving up", name, max_retries) + + +async def _apply_profile(name: str, gpu_index: int = 0) -> list[str]: + """Load and apply a saved profile to hardware. + + Returns a list of error strings. An empty list means success. + Raises FileNotFoundError if the profile file does not exist. + Sets g_state["active_profile"] on full success. + """ + import os as _os + g_state = _get_gpu_state(gpu_index) + gpu = g_state["gpu"] + cfg: Config = _state["config"] + + safe_name = "".join(c for c in name if c.isalnum() or c in " _-()").strip() + filepath = _os.path.join(cfg.profile_dir, f"{safe_name}.json") + + # Let FileNotFoundError propagate so callers can map it to 404 or a warning. + profile = await _run(load_profile, filepath) + + errs: list[str] = [] + + # Apply mem offset first — driver may reset curve table as a side-effect. + if profile.mem_offset_mhz is not None: + ok, msg = await _run(set_clock_offsets, None, profile.mem_offset_mhz, gpu_index) + if not ok: + errs.append(f"Mem offset: {msg}") + + if profile.power_limit_w is not None: + ok, msg = await _run(set_power_limit, profile.power_limit_w, gpu_index) + if not ok: + errs.append(f"Power limit: {msg}") + + # Apply curve deltas (after mem offset which may have wiped them). + async with g_state["write_lock"]: + if profile.curve_deltas: + deltas = {int(k): v for k, v in profile.curve_deltas.items()} + errors = validate_write(deltas, cfg.max_delta_khz) + if errors: + errs.append("Curve: " + "; ".join(errors)) + else: + if cfg.auto_snapshot: + await _run(snapshot_save, gpu, g_state["gpu_name"], cfg.snapshot_dir, cfg.max_snapshots) + ret, desc = await _run(write_offsets, gpu, deltas) + if ret != 0: + errs.append(f"Curve write failed ({ret}): {desc}") + else: + await _run(reset_offsets, gpu) + + await _update_offsets_and_broadcast(gpu_index) + + if not errs: + g_state["active_profile"] = name + return errs + + +@app.post("/api/profiles/{name}/apply") +async def api_profile_apply(name: str, gpu_index: int = 0): + """Apply a saved profile to hardware (curve deltas + limits).""" + _require_gpu(gpu_index) + try: + errs = await _apply_profile(name, gpu_index) + except FileNotFoundError: + raise HTTPException(status_code=404, detail=f"Profile '{name}' not found") + except Exception as e: + raise HTTPException(status_code=500, detail=f"Failed to load profile: {e}") + if errs: + raise HTTPException(status_code=500, detail="; ".join(errs)) + return {"ok": True} + + +@app.delete("/api/profiles/{name}") +async def api_profile_delete(name: str): + """Delete a saved profile by name.""" + cfg: Config = _state["config"] + ok = await _run(delete_profile, cfg.profile_dir, name) + if not ok: + raise HTTPException(status_code=404, detail=f"Profile '{name}' not found") + for g_state in _state["gpus"].values(): + if g_state["active_profile"] == name: + g_state["active_profile"] = None + changed = any(v == name for v in cfg.auto_load_profiles.values()) + if changed: + cfg.auto_load_profiles = {k: v for k, v in cfg.auto_load_profiles.items() if v != name} + _persist_auto_load_profiles(cfg.auto_load_profiles) + return {"ok": True} + + +@app.post("/api/profiles/{name}/rename") +async def api_profile_rename(name: str, req: ProfileRenameRequest): + """Rename a profile.""" + cfg: Config = _state["config"] + if not req.new_name.strip(): + raise HTTPException(status_code=400, detail="New name cannot be empty") + ok = await _run(rename_profile, cfg.profile_dir, name, req.new_name.strip()) + if not ok: + raise HTTPException(status_code=404, detail=f"Profile '{name}' not found") + for g_state in _state["gpus"].values(): + if g_state["active_profile"] == name: + g_state["active_profile"] = req.new_name.strip() + changed = any(v == name for v in cfg.auto_load_profiles.values()) + if changed: + cfg.auto_load_profiles = { + k: (req.new_name.strip() if v == name else v) + for k, v in cfg.auto_load_profiles.items() + } + _persist_auto_load_profiles(cfg.auto_load_profiles) + return {"ok": True} + + +@app.get("/api/config") +async def api_config_get(gpu_index: int = 0): + """Get mutable server configuration for a specific GPU.""" + cfg: Config = _state["config"] + return {"auto_load_profile": cfg.auto_load_profiles.get(_gpu_stable_key(gpu_index))} + + +@app.post("/api/config") +async def api_config_update(req: ConfigUpdateRequest): + """Update mutable server configuration. Changes persist to /etc/nvcurve/config.json if present.""" + if req.gpu_index not in _state["gpus"]: + raise HTTPException(status_code=404, detail=f"GPU {req.gpu_index} not found") + cfg: Config = _state["config"] + key = _gpu_stable_key(req.gpu_index) + if req.auto_load_profile: + cfg.auto_load_profiles[key] = req.auto_load_profile + else: + cfg.auto_load_profiles.pop(key, None) + _persist_auto_load_profiles(cfg.auto_load_profiles) + return {"ok": True, "auto_load_profile": cfg.auto_load_profiles.get(key)} + + +@app.get("/api/limits") +async def api_limits(gpu_index: int = 0): + """Current performance limits: power and clock offsets.""" + power = await _run(get_power_limit, gpu_index) + offsets = await _run(get_clock_offsets, gpu_index) + mem_off_range = await _run(get_mem_offset_range, gpu_index) + return { + **power, + **offsets, # gpc_offset_mhz, mem_offset_mhz + **mem_off_range, # min_mem_offset_mhz, max_mem_offset_mhz + } + + +@app.post("/api/limits") +async def api_limits_update(req: LimitsRequest, gpu_index: int = 0): + """Update performance limits.""" + g_state = _get_gpu_state(gpu_index) + errs = [] + + if req.power_limit_w is not None: + ok, msg = await _run(set_power_limit, req.power_limit_w, gpu_index) + if not ok: + errs.append(f"Power Limit: {msg}") + + if req.mem_offset_mhz is not None: + ok, msg = await _run(set_clock_offsets, None, req.mem_offset_mhz, gpu_index) + if not ok: + errs.append(f"Mem Offset: {msg}") + else: + # Setting mem offset may reset the GPC/curve table as a driver side-effect. + # Re-apply the last known curve offsets to restore them. + await _reapply_curve(gpu_index) + + if errs: + raise HTTPException(status_code=500, detail="; ".join(errs)) + + g_state["active_profile"] = None + + return {"ok": True} + + +async def _reapply_curve(gpu_index: int) -> None: + """Re-write the last known V/F curve offsets to hardware and notify WS clients.""" + g_state = _get_gpu_state(gpu_index) + gpu = g_state["gpu"] + last = g_state["last_offsets"] + if gpu is None or not last: + return + deltas = {i: off for i, off in enumerate(last) if off != 0} + if not deltas: + return + try: + await _run(write_offsets, gpu, deltas) + await _update_offsets_and_broadcast(gpu_index) + except Exception as exc: + log.warning("_reapply_curve: %s", exc) + + +async def _update_offsets_and_broadcast(gpu_index: int) -> None: + """Re-read curve offsets, update the reconciliation baseline, and push to WS clients. + + When curve WS clients are connected, a single read_curve call covers both + updating the baseline and the broadcast payload — avoiding a redundant + read_clock_offsets (ClockBoostTable) call that would otherwise happen first. + """ + g_state = _get_gpu_state(gpu_index) + gpu = g_state["gpu"] + if gpu is None: + return + if g_state["curve_clients"]: + state, _ = await _run(read_curve, gpu, g_state["gpu_name"]) + if state: + g_state["last_offsets"] = [p.delta_khz for p in state.points] + await _broadcast(g_state["curve_clients"], _curve_state_dict(state)) + else: + offsets, _ = await _run(read_clock_offsets, gpu) + g_state["last_offsets"] = offsets + + +@app.post("/api/limits/reset") +async def api_limits_reset(gpu_index: int = 0): + """Reset power limit to hardware default and memory clock offset to 0.""" + g_state = _get_gpu_state(gpu_index) + errs = [] + + power = await _run(get_power_limit, gpu_index) + default_w = power.get("default_power_limit_w") + if default_w is not None: + ok, msg = await _run(set_power_limit, default_w, gpu_index) + if not ok: + errs.append(f"Power Limit: {msg}") + + ok, msg = await _run(set_clock_offsets, None, 0, gpu_index) + if not ok: + errs.append(f"Mem Offset: {msg}") + else: + await _reapply_curve(gpu_index) + + if errs: + raise HTTPException(status_code=500, detail="; ".join(errs)) + + g_state["active_profile"] = None + + return {"ok": True} + + +# ── Write endpoints ──────────────────────────────────────────────────────────── + +async def _reconcile_check(gpu_index: int) -> dict | None: + """Re-read current offsets and return a warning dict if they differ from our last known state. + + Returns None if no external change detected (or no baseline). + """ + g_state = _get_gpu_state(gpu_index) + gpu = g_state["gpu"] + last = g_state["last_offsets"] + if last is None: + return None + + current, err = await _run(read_clock_offsets, gpu) + if current is None: + return None # Can't read — let the write attempt proceed + + changed = [i for i, (a, b) in enumerate(zip(last, current)) if a != b] + if not changed: + return None + + # External tool changed the curve — active profile is no longer current. + g_state["active_profile"] = None + + return { + "warning": "external_change_detected", + "message": ( + f"{len(changed)} point(s) changed since last read " + f"(e.g. by LACT, nvidia-smi, or another tool). " + "The write will proceed using the current hardware state." + ), + "changed_points": changed[:20], # cap list for readability + } + + +@app.post("/api/curve/write") +async def api_curve_write(req: WriteRequest, gpu_index: int = 0): + """Write per-point frequency offsets. {deltas: {point_index: delta_kHz}}""" + gpu, g_state = _require_gpu(gpu_index) + cfg: Config = _state["config"] + + vfp_state, _ = await _run(read_curve, gpu, g_state["gpu_name"]) + + effective_limit = req.max_delta_khz if req.max_delta_khz is not None else cfg.max_delta_khz + errors = validate_write(req.deltas, effective_limit) + if errors: + raise HTTPException(status_code=400, detail={"errors": errors}) + + # Check for negative-freq warnings before writing (best-effort, non-blocking) + freq_warnings: list[str] = [] + if vfp_state: + vfp_freqs = [p.freq_khz for p in vfp_state.points] + freq_warnings = check_negative_freq_warnings( + req.deltas, vfp_freqs, g_state["last_offsets"] or [] + ) + + async with g_state["write_lock"]: + warning = await _reconcile_check(gpu_index) + + if cfg.auto_snapshot: + await _run(snapshot_save, gpu, g_state["gpu_name"], cfg.snapshot_dir, cfg.max_snapshots) + + ret, desc = await _run(write_offsets, gpu, req.deltas) + if ret != 0: + raise HTTPException(status_code=500, detail=f"Write failed ({ret}): {desc}") + + # Update baseline and push curve update to WS clients + await _update_offsets_and_broadcast(gpu_index) + g_state["active_profile"] = None + + result = {"ok": True, "return_code": ret, "description": desc} + if warning: + result["warning"] = warning + if freq_warnings: + result["freq_warnings"] = freq_warnings + return result + + +@app.post("/api/curve/write/global") +async def api_curve_write_global(req: GlobalOffsetRequest, gpu_index: int = 0): + """Apply a uniform frequency offset to all curve points.""" + gpu, g_state = _require_gpu(gpu_index) + cfg: Config = _state["config"] + + vfp_state, _ = await _run(read_curve, gpu, g_state["gpu_name"]) + if not vfp_state: + raise HTTPException(status_code=500, detail="Failed to read curve") + + all_deltas = {p.index: req.delta_khz for p in vfp_state.points if p.domain == "gpu"} + effective_limit = req.max_delta_khz if req.max_delta_khz is not None else cfg.max_delta_khz + errors = validate_write(all_deltas, effective_limit) + if errors: + raise HTTPException(status_code=400, detail={"errors": errors}) + + freq_warnings: list[str] = [] + if vfp_state: + vfp_freqs = [p.freq_khz for p in vfp_state.points] + freq_warnings = check_negative_freq_warnings( + all_deltas, vfp_freqs, g_state["last_offsets"] or [] + ) + + async with g_state["write_lock"]: + warning = await _reconcile_check(gpu_index) + + if cfg.auto_snapshot: + await _run(snapshot_save, gpu, g_state["gpu_name"], cfg.snapshot_dir, cfg.max_snapshots) + + ret, desc = await _run(write_global_offset, gpu, req.delta_khz) + if ret != 0: + raise HTTPException(status_code=500, detail=f"Write failed ({ret}): {desc}") + + await _update_offsets_and_broadcast(gpu_index) + g_state["active_profile"] = None + + result = {"ok": True, "return_code": ret, "description": desc} + if warning: + result["warning"] = warning + if freq_warnings: + result["freq_warnings"] = freq_warnings + return result + + +@app.post("/api/curve/reset") +async def api_curve_reset(gpu_index: int = 0): + """Reset all frequency offsets to zero.""" + gpu, g_state = _require_gpu(gpu_index) + cfg: Config = _state["config"] + + async with g_state["write_lock"]: + warning = await _reconcile_check(gpu_index) + + if cfg.auto_snapshot: + await _run(snapshot_save, gpu, g_state["gpu_name"], cfg.snapshot_dir, cfg.max_snapshots) + + ret, desc = await _run(reset_offsets, gpu) + if ret != 0: + raise HTTPException(status_code=500, detail=f"Reset failed ({ret}): {desc}") + + await _update_offsets_and_broadcast(gpu_index) + g_state["active_profile"] = None + + result = {"ok": True, "return_code": ret, "description": desc} + if warning: + result["warning"] = warning + return result + + +@app.post("/api/curve/verify") +async def api_curve_verify(req: VerifyRequest, gpu_index: int = 0): + """Write-verify-read cycle. Returns per-point match results and collateral changes.""" + gpu, g_state = _require_gpu(gpu_index) + cfg: Config = _state["config"] + + errors = validate_write(req.deltas, cfg.max_delta_khz) + if errors: + raise HTTPException(status_code=400, detail={"errors": errors}) + + before_offsets, err = await _run(read_clock_offsets, gpu) + if before_offsets is None: + raise HTTPException(status_code=500, detail=f"Failed to read current state: {err}") + + # Always snapshot before verify — it's a testing operation + await _run(snapshot_save, gpu, g_state["gpu_name"], cfg.snapshot_dir, cfg.max_snapshots) + + async with g_state["write_lock"]: + ret, desc = await _run(write_offsets, gpu, req.deltas) + if ret != 0: + raise HTTPException(status_code=500, detail=f"Write failed ({ret}): {desc}") + + await asyncio.sleep(0.2) + + after_offsets, err = await _run(read_clock_offsets, gpu) + if after_offsets is None: + raise HTTPException(status_code=500, detail=f"Verification read failed: {err}") + + g_state["active_profile"] = None + await _update_offsets_and_broadcast(gpu_index) + + points_result = [] + all_matched = True + for point, expected in sorted(req.deltas.items()): + actual = after_offsets[point] + match = actual == expected + if not match: + all_matched = False + points_result.append({ + "point": point, + "expected_khz": expected, + "actual_khz": actual, + "match": match, + }) + + collateral = [ + {"point": i, "before_khz": before_offsets[i], "after_khz": after_offsets[i]} + for i in range(len(before_offsets)) + if i not in req.deltas and before_offsets[i] != after_offsets[i] + ] + + return { + "ok": all_matched and not collateral, + "all_matched": all_matched, + "no_side_effects": not collateral, + "return_code": ret, + "description": desc, + "points": points_result, + "collateral_changes": collateral, + } + + +@app.post("/api/shutdown") +async def api_shutdown(): + """Gracefully shut down the server process.""" + import os + import signal + loop = asyncio.get_running_loop() + loop.call_later(0.1, lambda: os.kill(os.getpid(), signal.SIGTERM)) + return {"ok": True} + + +@app.post("/api/snapshot/save") +async def api_snapshot_save(gpu_index: int = 0): + """Save a ClockBoostTable snapshot.""" + gpu, g_state = _require_gpu(gpu_index) + cfg: Config = _state["config"] + path = await _run(snapshot_save, gpu, g_state["gpu_name"], cfg.snapshot_dir, cfg.max_snapshots) + if path is None: + raise HTTPException(status_code=500, detail="Failed to save snapshot") + return {"ok": True, "filepath": path} + + +@app.post("/api/snapshot/restore") +async def api_snapshot_restore(req: SnapshotRestoreRequest, gpu_index: int = 0): + """Restore a ClockBoostTable snapshot. Uses most recent if filepath not specified.""" + gpu, g_state = _require_gpu(gpu_index) + cfg: Config = _state["config"] + + async with g_state["write_lock"]: + ok = await _run(snapshot_restore, gpu, cfg.snapshot_dir, req.filepath) + if not ok: + raise HTTPException(status_code=500, detail="Failed to restore snapshot") + + await _update_offsets_and_broadcast(gpu_index) + g_state["active_profile"] = None + + return {"ok": True} + + +# ── WebSocket endpoints ─────────────────────────────────────────────────────── + +@app.websocket("/ws/monitor") +async def ws_monitor(ws: WebSocket): + """Stream MonitoringSample at poll_interval_s. Clients receive JSON objects.""" + await ws.accept() + try: + data = await ws.receive_json() + if data.get("action") != "subscribe": + await ws.close() + return + gpu_index = data.get("gpu_index", 0) + except WebSocketDisconnect: + return + except Exception: + await ws.close() + return + + g_state = _state["gpus"].get(gpu_index) + if not g_state: + await ws.close() + return + + g_state["monitor_clients"].add(ws) + try: + gpu = g_state["gpu"] + if gpu is not None: + sample = await _run(poll, gpu, gpu_index) + await ws.send_json(_sample_dict(sample)) + + while True: + await ws.receive_text() + except WebSocketDisconnect: + pass + except Exception: + pass + finally: + g_state["monitor_clients"].discard(ws) + + +@app.websocket("/ws/curve") +async def ws_curve(ws: WebSocket): + """Push CurveState whenever the curve changes (after writes).""" + await ws.accept() + try: + data = await ws.receive_json() + if data.get("action") != "subscribe": + await ws.close() + return + gpu_index = data.get("gpu_index", 0) + except WebSocketDisconnect: + return + except Exception: + await ws.close() + return + + g_state = _state["gpus"].get(gpu_index) + if not g_state: + await ws.close() + return + + g_state["curve_clients"].add(ws) + try: + gpu = g_state["gpu"] + if gpu is not None: + state, _ = await _run(read_curve, gpu, g_state["gpu_name"]) + if state: + await ws.send_json(_curve_state_dict(state)) + + while True: + await ws.receive_text() + except WebSocketDisconnect: + pass + except Exception: + pass + finally: + g_state["curve_clients"].discard(ws) + + +# ── Frontend SPA ────────────────────────────────────────────────────────────── + +import os +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse +from pathlib import Path + +# When set, suppresses the auto-open browser behaviour so the dev can open +# the Vite dev server (pnpm dev) manually instead. +_DEV_PORT = os.environ.get("NVCURVE_DEV_PORT") + +# Robust asset resolution using importlib.resources +try: + from importlib.resources import files as _resource_files + # In a packaged installation, frontend/dist is inside the package + _dist_dir = _resource_files("nvcurve") / "frontend" / "dist" + + # Fallback for local development where frontend/dist might be at project root + if not _dist_dir.is_dir(): + _here = Path(__file__).parent + _dist_dir = _here.parent / "frontend" / "dist" +except (ImportError, TypeError): + # Legacy fallback for older Python or environments without importlib.resources.files + _here = Path(__file__).parent + _dist_dir = _here / "frontend" / "dist" + if not _dist_dir.is_dir(): + _dist_dir = _here.parent / "frontend" / "dist" + +_dist_dir = str(_dist_dir) + +if os.path.isdir(os.path.join(_dist_dir, "assets")): + app.mount("/assets", StaticFiles(directory=os.path.join(_dist_dir, "assets")), name="assets") + +@app.get("/{catchall:path}") +async def serve_spa(catchall: str): + if catchall.startswith("api/") or catchall.startswith("ws/"): + raise HTTPException(status_code=404, detail="Not Found") + + if not os.path.isdir(_dist_dir): + return {"error": "Frontend not built. Run pnpm build in frontend/."} + + path = os.path.join(_dist_dir, catchall) + if os.path.isfile(path) and catchall: + return FileResponse(path) + + index = os.path.join(_dist_dir, "index.html") + if os.path.isfile(index): + return FileResponse(index) + + raise HTTPException(status_code=404, detail="Not Found") + + +# ── Factory for configured app ──────────────────────────────────────────────── + +def create_app(config: Config = default_config) -> FastAPI: + """Create a server app with a custom config (e.g. different gpu_index).""" + _state["config"] = config + return app + + +def run( + host: str = "127.0.0.1", + port: int = 8042, + gpu_index: int = 0, + config: Config = default_config, + open_browser: bool = False, +) -> None: + """Start the uvicorn server. Blocking.""" + import socket + import threading + import uvicorn + + _state["config"] = config + + # Suppress noisy websockets keepalive ping-timeout tracebacks — these are + # normal disconnection events (browser tab closed, network hiccup) and + # logging them at ERROR level creates false alarm noise. + logging.getLogger("websockets").setLevel(logging.CRITICAL) + + # Fail fast if the port is already in use — silently shifting ports breaks + # client discovery. Users should configure a different port explicitly. + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + try: + s.bind((host, port)) + except OSError: + print(f"Error: port {port} is already in use.") + print(f"Use --port N to specify a different port, or free port {port} first.") + return + + url = f"http://{host}:{port}" + + # Print banner *before* uvicorn starts so it appears above uvicorn's own output. + # GPU name is populated by the lifespan; we omit it here since the server + # hasn't started yet, and the lifespan logs it via log.info. + print("\033[1;36m" + "─" * 60 + "\033[0m") + print("\033[1;32m" + " NVCurve".center(60) + "\033[0m") + print(f" {url}".center(60)) + print("\033[1;36m" + "─" * 60 + "\033[0m") + print(" Press Ctrl+C to stop.") + print() + + if open_browser and not _DEV_PORT: + threading.Timer(1.2, lambda: _open_browser_as_user(url)).start() + + uvicorn.run(app, host=host, port=port, log_level="warning", access_log=False) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c8566cb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "nvcurve" +version = "0.5.0" +description = "Linux GPU V/F curve editor for NVIDIA GPUs" +requires-python = ">=3.12" +dependencies = [ + "fastapi>=0.115", + "uvicorn[standard]>=0.30", + "nvidia-ml-py>=12.0", + "pydantic>=2.0", + "httpx>=0.27", +] + +[project.scripts] +nvcurve = "nvcurve.cli:main" + +[tool.hatch.build.targets.wheel] +packages = ["nvcurve"] + +[tool.hatch.build.targets.wheel.force-include] +"frontend/dist" = "nvcurve/frontend/dist" + +[tool.hatch.build.targets.sdist] +include = [ + "/nvcurve", + "/frontend/dist", + "/README.md", +] diff --git a/scripts/nv_vfcurve_rw.py b/scripts/nv_vfcurve_rw.py new file mode 100644 index 0000000..bec42e1 --- /dev/null +++ b/scripts/nv_vfcurve_rw.py @@ -0,0 +1,1481 @@ +#!/usr/bin/env python3 +"""nv_vfcurve_rw.py — Read/Write NVIDIA GPU V/F curve via undocumented NvAPI (Linux). + +Extended from nv_vfcurve.py with WRITE support for per-point frequency offsets. + +┌─────────────────────────────────────────────────────────────────────┐ +│ WARNING: Write operations modify GPU clocking behavior. │ +│ Excessive offsets can cause instability, crashes, or artifacts. │ +│ Use small deltas (+15 to +50 MHz) initially and verify. │ +│ The --dry-run flag lets you inspect what WOULD be written. │ +└─────────────────────────────────────────────────────────────────────┘ + +Write modes: + sudo python3 nv_vfcurve_rw.py write --point 80 --delta 15 + Apply +15 MHz offset to point 80 only. + + sudo python3 nv_vfcurve_rw.py write --range 60-90 --delta 30 + Apply +30 MHz offset to points 60–90. + + sudo python3 nv_vfcurve_rw.py write --global --delta 50 + Apply +50 MHz offset to all GPU core points. + + sudo python3 nv_vfcurve_rw.py write --reset + Reset all offsets to 0. + + Add --dry-run to any write command to preview without applying. + +Read modes: + sudo python3 nv_vfcurve_rw.py read # Condensed V/F curve + sudo python3 nv_vfcurve_rw.py read --full # All points (GPU + mem) + sudo python3 nv_vfcurve_rw.py read --json # JSON output + sudo python3 nv_vfcurve_rw.py read --raw # Hex dumps + sudo python3 nv_vfcurve_rw.py read --diag # Probe all functions + +Verify mode (read-verify-read cycle): + sudo python3 nv_vfcurve_rw.py verify --point 80 --delta 15 + Write +15 MHz to point 80, then immediately re-read and confirm. + +Snapshot mode (save/restore full ClockBoostTable): + sudo python3 nv_vfcurve_rw.py snapshot save + sudo python3 nv_vfcurve_rw.py snapshot restore + +Struct layouts verified by hex-dump analysis on RTX 5090 (GB202, Blackwell), +driver 590.48.01. Cross-referenced with nvapioc (Demion), nvapi-rs (arcnmx), +and ccminer (tpruvot/cbuchner1). + +Key findings: + - Mask must come from GetClockBoostMask (all-0xFF fails on Pascal) + - Table holds up to 255 entries: GPU core + memory + - Point 127 (on RTX 5090) is the start of the memory domain (field_00=1), not idle + - Points 128+ are memory V/F entries + - freqDelta scaling is generation-dependent (÷2 on Pascal, ÷1 on Blackwell) + - NVML SetGpcClkVfOffset and SetClockBoostTable share state (last writer wins) + +See NvAPI_VF_Curve_Documentation.md for full technical details. +""" + +import ctypes +import struct +import sys +import json +import os +import time +import argparse +from datetime import datetime +from typing import Optional, List, Tuple, Set, Dict + +# ═══════════════════════════════════════════════════════════════════════════ +# NvAPI bootstrap +# ═══════════════════════════════════════════════════════════════════════════ + +def load_nvapi(): + """Load libnvidia-api.so from the NVIDIA driver.""" + for name in ("libnvidia-api.so", "libnvidia-api.so.1"): + try: + return ctypes.CDLL(name) + except OSError: + continue + print("Error: Cannot load libnvidia-api.so") + print("Ensure the NVIDIA proprietary driver is installed.") + sys.exit(1) + + +nvapi = load_nvapi() +QI = nvapi.nvapi_QueryInterface +QI.restype = ctypes.c_void_p +QI.argtypes = [ctypes.c_uint32] + +# NvAPI error codes (Linux uses negative integers, not 0x80000000+ range) +NVAPI_ERRORS = { + 0: "OK", + -1: "GENERIC_ERROR", + -5: "INVALID_ARGUMENT", + -6: "NVIDIA_DEVICE_NOT_FOUND", + -7: "END_ENUMERATION", + -8: "INVALID_HANDLE", + -9: "INCOMPATIBLE_STRUCT_VERSION", + -10: "HANDLE_INVALIDATED", + -14: "INVALID_POINTER", +} + + +def nvfunc(fid: int, nargs: int = 2): + """Resolve an NvAPI function pointer by its ID.""" + ptr = QI(fid) + if not ptr: + return None + return ctypes.CFUNCTYPE(ctypes.c_int32, *[ctypes.c_void_p] * nargs)(ptr) + + +def nvcall(fid: int, gpu, size: int, ver: int = 1, pre_fill=None): + """Call an NvAPI function with a versioned struct buffer. + + Returns (bytes, error_string) tuple. + """ + func = nvfunc(fid) + if not func: + return None, "function pointer not found (driver too old?)" + buf = ctypes.create_string_buffer(size) + struct.pack_into(" tuple: + """Initialize NvAPI, enumerate GPUs, return (handle, name).""" + init_fn = nvfunc(FUNC["Initialize"], 0) + if not init_fn or init_fn() != 0: + print("NvAPI_Initialize failed") + sys.exit(1) + + gpus = (ctypes.c_void_p * 64)() + ngpu = ctypes.c_int32() + nvfunc(FUNC["EnumPhysicalGPUs"])(ctypes.byref(gpus), ctypes.byref(ngpu)) + if ngpu.value == 0: + print("No NVIDIA GPUs found") + sys.exit(1) + + gpu = gpus[0] + name_buf = ctypes.create_string_buffer(256) + nvfunc(FUNC["GetFullName"])(gpu, name_buf) + return gpu, name_buf.value.decode(errors="replace") + + +# ═══════════════════════════════════════════════════════════════════════════ +# Boost mask — the canonical source of which points are active +# +# Per nvapioc (Demion/nvapioc), the mask from GetClockBoostMask MUST be +# copied into GetVFPCurve and Get/SetClockBoostTable calls. Using all-0xFF +# works on Blackwell but fails on Pascal with GENERIC_ERROR (-1). +# +# The mask struct (0x182C bytes) contains per-entry enable/type info that +# also distinguishes GPU core vs memory clock domains. +# ═══════════════════════════════════════════════════════════════════════════ + +class BoostMask: + """Parsed GetClockBoostMask data. + + Provides the raw mask bytes for copying into other calls, plus + parsed per-entry enabled info for filtering. + """ + def __init__(self, raw: bytes): + self.raw = raw + self.size = len(raw) + + # The mask field at offset 0x04, 16 bytes — same position as in VFP/CT structs + self.mask_bytes = raw[MASK_OFFSET:MASK_OFFSET + MASK_BYTES] + + self.entries = [] + self._parse_entries() + + def _parse_entries(self): + """Parse which points have their mask bit set.""" + for i in range(min(CT_MAX_ENTRIES, MASK_BYTES * 8)): + byte_idx = i // 8 + bit_idx = i % 8 + enabled = bool(self.mask_bytes[byte_idx] & (1 << bit_idx)) + self.entries.append({"index": i, "enabled": enabled}) + + def get_enabled_indices(self) -> List[int]: + """Return list of point indices that are enabled in the mask.""" + return [e["index"] for e in self.entries if e["enabled"]] + + def count_enabled(self) -> int: + return sum(1 for e in self.entries if e["enabled"]) + + def copy_mask_into(self, buf, offset=MASK_OFFSET): + """Copy the canonical mask bytes into a target buffer.""" + for i in range(MASK_BYTES): + buf[offset + i] = self.mask_bytes[i] + + +def read_boost_mask(gpu) -> Tuple[Optional[BoostMask], str]: + """Read the clock boost mask — the canonical source of active point info. + + Per nvapioc, this mask must be copied into VFP and ClockBoostTable calls. + Using all-0xFF works on some GPUs (Blackwell) but fails on others (Pascal). + """ + def fill(buf): + for i in range(MASK_OFFSET, MASK_OFFSET + MASK_BYTES): + buf[i] = 0xFF + + d, err = nvcall(FUNC["GetClockBoostMask"], gpu, MASK_SIZE, ver=1, pre_fill=fill) + if not d: + return None, err + + return BoostMask(d), "OK" + + +# ═══════════════════════════════════════════════════════════════════════════ +# Point classification — GPU core vs memory +# ═══════════════════════════════════════════════════════════════════════════ + +class CurveInfo: + """Holds classified point information for the GPU's V/F curve. + + Combines data from GetClockBoostMask, GetVFPCurve, and GetClockBoostTable + to determine which points are GPU core and which are memory. + """ + def __init__(self): + self.gpu_points: List[int] = [] # GPU core V/F point indices + self.mem_points: List[int] = [] # Memory V/F point indices + self.total_points: int = 0 # Total populated entries + self.mask: Optional[BoostMask] = None + + @staticmethod + def build(gpu, mask: Optional[BoostMask] = None) -> 'CurveInfo': + """Classify all points by reading CT field_00 and VFP data. + + field_00 == 0: GPU core data point + field_00 == 1: start of memory domain (this point and subsequent are memory points) + """ + info = CurveInfo() + info.mask = mask + + # Read ClockBoostTable to check field_00 + ct_raw = _read_clock_table_raw_with_mask(gpu, mask) + if not ct_raw: + # Fallback: assume 128 GPU points + info.gpu_points = list(range(128)) + info.total_points = 128 + return info + + # Read VFP curve for frequency data + vfp_points = _read_vfp_with_mask(gpu, mask) + + # Scan all possible entries + in_memory_domain = False + for i in range(CT_MAX_ENTRIES): + off = CT_BASE + i * CT_STRIDE + if off + CT_STRIDE > len(ct_raw): + break + + field_00 = struct.unpack_from(" 0 or v > 0) + + has_ct_data = False + for j in range(9): + val = struct.unpack_from(" str: + parts = [f"{len(self.gpu_points)} GPU core points"] + if self.mem_points: + parts.append(f"{len(self.mem_points)} memory points") + parts.append(f"{self.total_points} total") + return ", ".join(parts) + + +# ═══════════════════════════════════════════════════════════════════════════ +# Data readers (mask-aware) +# ═══════════════════════════════════════════════════════════════════════════ + +def _fill_mask_from_boost(buf, mask: BoostMask): + """Copy boost mask into buffer.""" + mask.copy_mask_into(buf) + + +def _read_vfp_with_mask(gpu, mask: Optional[BoostMask]) -> Optional[List[Tuple[int, int]]]: + """Read VFP curve using the canonical boost mask.""" + def fill(buf): + _fill_mask_from_boost(buf, mask) + + d, err = nvcall(FUNC["GetVFPCurve"], gpu, VFP_SIZE, ver=1, pre_fill=fill) + if not d: + return None + + points = [] + for i in range(VFP_MAX_ENTRIES): + off = VFP_BASE + i * VFP_STRIDE + if off + 8 > len(d): + break + freq = struct.unpack_from(" Optional[bytes]: + """Read raw ClockBoostTable using the canonical boost mask.""" + def fill(buf): + _fill_mask_from_boost(buf, mask) + + d, err = nvcall(FUNC["GetClockBoostTable"], gpu, CT_SIZE, ver=1, pre_fill=fill) + return d if d else None + + +def read_vfp_curve(gpu, mask: Optional[BoostMask] = None, + curve_info: Optional[CurveInfo] = None + ) -> Tuple[Optional[List[Tuple[int, int]]], str]: + """Read V/F curve (frequency + voltage pairs). + + Returns up to 255 entries. Use curve_info to determine which are GPU/mem. + """ + def fill(buf): + _fill_mask_from_boost(buf, mask) + + d, err = nvcall(FUNC["GetVFPCurve"], gpu, VFP_SIZE, ver=1, pre_fill=fill) + if not d: + return None, err + + max_entries = VFP_MAX_ENTRIES + if curve_info and curve_info.total_points > 0: + max_entries = curve_info.total_points + + points = [] + for i in range(max_entries): + off = VFP_BASE + i * VFP_STRIDE + if off + 8 > len(d): + break + freq = struct.unpack_from(" Tuple[Optional[bytes], str]: + """Read the raw ClockBoostTable buffer.""" + def fill(buf): + _fill_mask_from_boost(buf, mask) + + return nvcall(FUNC["GetClockBoostTable"], gpu, CT_SIZE, ver=1, pre_fill=fill) + + +def read_clock_offsets(gpu, mask: Optional[BoostMask] = None, + curve_info: Optional[CurveInfo] = None + ) -> Tuple[Optional[List[int]], str]: + """Read per-point frequency offsets from the ClockBoostTable.""" + d, err = read_clock_table_raw(gpu, mask) + if not d: + return None, err + + max_entries = CT_MAX_ENTRIES + if curve_info and curve_info.total_points > 0: + max_entries = curve_info.total_points + + offsets = [] + actual_max = min(max_entries, (len(d) - CT_BASE) // CT_STRIDE) + for i in range(actual_max): + off = CT_BASE + i * CT_STRIDE + CT_DELTA_OFF + delta = struct.unpack_from(" dict: + """Extract all 9 fields from a single ClockBoostTable entry.""" + base = CT_BASE + point * CT_STRIDE + if base + CT_STRIDE > len(data): + return {} + fields = {} + for j in range(9): + off = base + j * 4 + if j == 5: + fields[f"field_{j:02d}_0x{j*4:02X}"] = struct.unpack_from(" Tuple[Optional[int], str]: + """Read current GPU core voltage in µV.""" + d, err = nvcall(FUNC["GetCurrentVoltage"], gpu, VOLT_SIZE, ver=1) + if not d: + return None, err + return struct.unpack_from(" Tuple[Optional[dict], str]: + """Read clock domain min/max offset ranges.""" + d, err = nvcall(FUNC["GetClockBoostRanges"], gpu, RANGES_SIZE, ver=1) + if not d: + return None, err + num = struct.unpack_from(" len(d): + break + words = [struct.unpack_from(" Tuple[Optional[ctypes.Array], str]: + """Build a SetClockBoostTable buffer with specified per-point deltas. + + Strategy: read the current ClockBoostTable (using canonical mask), + modify only the targeted entries' freqDelta fields, set only the + targeted mask bits for the write call. + + Returns (mutable_buffer, error_string). + """ + current_raw, err = read_clock_table_raw(gpu, mask) + if not current_raw: + return None, f"Cannot read current ClockBoostTable: {err}" + + buf = ctypes.create_string_buffer(CT_SIZE) + ctypes.memmove(buf, current_raw, CT_SIZE) + + # Rewrite version word + struct.pack_into(" Tuple[int, str]: + """Write per-point frequency offsets via SetClockBoostTable.""" + buf, err = build_write_buffer(gpu, point_deltas, mask) + if buf is None: + return -999, err + + if dry_run: + return 0, "DRY RUN — buffer built but not sent to driver" + + ret, desc = nvcall_raw(FUNC["SetClockBoostTable"], gpu, buf) + return ret, desc + + +# ═══════════════════════════════════════════════════════════════════════════ +# Safety checks +# ═══════════════════════════════════════════════════════════════════════════ + +def validate_write_request(point_deltas: dict, + curve_info: Optional[CurveInfo] = None + ) -> Optional[str]: + """Return an error message if the write request is unsafe, else None.""" + mem_points = set() + if curve_info: + mem_points = set(curve_info.mem_points) + + for point, delta_khz in point_deltas.items(): + if point < 0 or point >= CT_MAX_ENTRIES: + return f"Point {point} out of range (0–{CT_MAX_ENTRIES - 1})" + + if point in mem_points: + return (f"Point {point} is a memory clock entry. " + "Memory offsets use a different mechanism (NVML). " + "Use --force if you really mean it.") + + if abs(delta_khz) > MAX_DELTA_KHZ: + return (f"Delta {delta_khz/1000:+.0f} MHz for point {point} exceeds " + f"safety limit of ±{MAX_DELTA_KHZ/1000:.0f} MHz. " + "Use --max-delta to raise the limit if needed.") + + return None + + +# ═══════════════════════════════════════════════════════════════════════════ +# Hex dump utility +# ═══════════════════════════════════════════════════════════════════════════ + +def hexdump(data: bytes, start: int, length: int, cols: int = 16) -> str: + lines = [] + end = min(start + length, len(data)) + for off in range(start, end, cols): + chunk = data[off:off + cols] + hx = " ".join(f"{b:02x}" for b in chunk) + asc = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk) + lines.append(f" {off:04x}: {hx:<{cols * 3}} {asc}") + return "\n".join(lines) + + +# ═══════════════════════════════════════════════════════════════════════════ +# Snapshot save/restore +# ═══════════════════════════════════════════════════════════════════════════ + +def snapshot_save(gpu, gpu_name: str, mask: Optional[BoostMask] = None): + """Save the current ClockBoostTable to disk.""" + raw, err = read_clock_table_raw(gpu, mask) + if not raw: + print(f"Failed to read ClockBoostTable: {err}") + return False + + os.makedirs(SNAPSHOT_DIR, exist_ok=True) + ts = datetime.now().strftime("%Y%m%d_%H%M%S") + fname = os.path.join(SNAPSHOT_DIR, f"clock_boost_table_{ts}.bin") + meta_fname = os.path.join(SNAPSHOT_DIR, f"clock_boost_table_{ts}.json") + + with open(fname, "wb") as f: + f.write(raw) + + # Save human-readable metadata — scan all possible entries + offsets = [] + for i in range(CT_MAX_ENTRIES): + off = CT_BASE + i * CT_STRIDE + CT_DELTA_OFF + if off + 4 > len(raw): + break + delta = struct.unpack_from(" 0 and abs(v - voltage) < 10000: + current_idx = i + break + + gpu_set = set(curve_info.gpu_points) if curve_info else set() + mem_set = set(curve_info.mem_points) if curve_info else set() + + if full: + show = list(range(len(points))) + else: + show = [] + prev_freq = -1 + for i, (f, v) in enumerate(points): + if f == 0 and v == 0: + continue + if i in mem_set: + show.append(i) + elif f != prev_freq or i == len(points) - 1: + show.append(i) + prev_freq = f + + print(f"{'#':>3s} {'Freq':>8s} {'Voltage':>8s} {'Offset':>8s} {'Domain'}") + print("-" * 56) + + for i in show: + if i >= len(points): + break + f, v = points[i] + if f == 0 and v == 0: + continue + + freq_s = f"{f / 1000:.0f} MHz" + volt_s = f"{v / 1000:.0f} mV" + + offset_s = "" + if offsets and i < len(offsets) and offsets[i] != 0: + offset_s = f"{offsets[i] / 1000:+.0f} MHz" + + domain = "" + if i in mem_set: + domain = "memory" + elif i in gpu_set: + domain = "gpu" + + marker = "" + if current_idx is not None and i == current_idx: + marker = " <-- current" + + print(f"{i:3d} {freq_s:>8s} {volt_s:>8s} {offset_s:>8s} {domain}{marker}") + + # Summary + if curve_info and curve_info.gpu_points: + gpu_data = [(points[i][0], points[i][1]) for i in curve_info.gpu_points + if i < len(points) and points[i][0] > 0] + if gpu_data: + freqs = [f for f, v in gpu_data] + volts = [v for f, v in gpu_data] + print() + print(f"GPU core: {min(freqs)/1000:.0f} – {max(freqs)/1000:.0f} MHz, " + f"{min(volts)/1000:.0f} – {max(volts)/1000:.0f} mV " + f"({len(gpu_data)} points)") + + if curve_info and curve_info.mem_points: + mem_data = [(points[i][0], points[i][1]) for i in curve_info.mem_points + if i < len(points) and points[i][0] > 0] + if mem_data: + freqs = [f for f, v in mem_data] + volts = [v for f, v in mem_data] + print(f"Memory: {min(freqs)/1000:.0f} – {max(freqs)/1000:.0f} MHz, " + f"{min(volts)/1000:.0f} – {max(volts)/1000:.0f} mV " + f"({len(mem_data)} points)") + + if offsets: + gpu_indices = set(curve_info.gpu_points) if curve_info else set(range(len(offsets))) + gpu_offsets = [offsets[i] for i in gpu_indices + if i < len(offsets) and offsets[i] != 0] + if gpu_offsets: + vals = set(gpu_offsets) + if len(vals) == 1: + print(f"GPU offset: {next(iter(vals))/1000:+.0f} MHz " + f"(uniform across {len(gpu_offsets)} points)") + else: + print(f"GPU offsets: {len(gpu_offsets)} points active " + f"(range: {min(vals)/1000:+.0f} to {max(vals)/1000:+.0f} MHz)") + + +def output_json(gpu_name, points, offsets, voltage, + curve_info: Optional[CurveInfo] = None): + """Output JSON format.""" + data = { + "gpu": gpu_name, + "current_voltage_uV": voltage, + "layout": { + "vfp_curve": {"size": VFP_SIZE, "base": VFP_BASE, + "stride": VFP_STRIDE, "max_entries": VFP_MAX_ENTRIES}, + "clock_table": {"size": CT_SIZE, "base": CT_BASE, + "stride": CT_STRIDE, "delta_offset": CT_DELTA_OFF, + "max_entries": CT_MAX_ENTRIES}, + }, + "curve_info": { + "gpu_points": curve_info.gpu_points if curve_info else [], + "mem_points": curve_info.mem_points if curve_info else [], + "total_points": curve_info.total_points if curve_info else len(points), + }, + "vf_curve": [], + } + + gpu_set = set(curve_info.gpu_points) if curve_info else set() + mem_set = set(curve_info.mem_points) if curve_info else set() + + for i, (f, v) in enumerate(points): + if f > 0 or v > 0: + entry = {"index": i, "freq_kHz": f, "volt_uV": v} + if offsets and i < len(offsets): + entry["freq_offset_kHz"] = offsets[i] + if i in mem_set: + entry["domain"] = "memory" + elif i in gpu_set: + entry["domain"] = "gpu" + data["vf_curve"].append(entry) + + print(json.dumps(data, indent=2)) + + +# ═══════════════════════════════════════════════════════════════════════════ +# Write command handler +# ═══════════════════════════════════════════════════════════════════════════ + +def cmd_write(gpu, gpu_name, args, mask, curve_info): + """Handle write subcommand.""" + delta_khz = int(args.delta * 1000) + point_deltas = {} + + # Determine GPU-only points for --global and --reset + if curve_info and curve_info.gpu_points: + gpu_points = curve_info.gpu_points + else: + gpu_points = list(range(127)) + + if args.reset: + delta_khz = 0 + for i in gpu_points: + point_deltas[i] = 0 + print(f"Resetting offsets to 0 on {len(point_deltas)} GPU core points") + + elif args.point is not None: + point_deltas[args.point] = delta_khz + print(f"Target: point {args.point}, delta {args.delta:+.0f} MHz " + f"({delta_khz:+d} kHz)") + + elif args.range: + start, end = args.range + for i in range(start, end + 1): + point_deltas[i] = delta_khz + print(f"Target: points {start}–{end} ({len(point_deltas)} points), " + f"delta {args.delta:+.0f} MHz") + + elif args.glob: + for i in gpu_points: + point_deltas[i] = delta_khz + print(f"Target: all {len(point_deltas)} GPU core points, " + f"delta {args.delta:+.0f} MHz") + + else: + print("Error: specify --point N, --range A-B, --global, or --reset") + return + + # Safety check + if not args.force: + err = validate_write_request(point_deltas, curve_info) + if err: + print(f"\nSafety check FAILED: {err}") + return + + # Read current state for comparison + current_offsets, _ = read_clock_offsets(gpu, mask, curve_info) + + # Show what will change + print() + print("Changes to apply:") + changed = 0 + for point in sorted(point_deltas.keys()): + new = point_deltas[point] + old = current_offsets[point] if current_offsets and point < len(current_offsets) else 0 + if old != new: + changed += 1 + if changed <= 20: + print(f" Point {point:3d}: {old/1000:+8.0f} MHz → {new/1000:+8.0f} MHz") + if changed > 20: + print(f" ... and {changed - 20} more points") + if changed == 0: + print(" (no changes — offsets already match)") + return + + if args.dry_run: + print() + print("DRY RUN — no changes applied.") + + buf, err = build_write_buffer(gpu, point_deltas, mask) + if buf: + print() + print("Buffer header (first 0x44 bytes):") + print(hexdump(bytes(buf), 0x00, 0x44)) + + first_pt = min(point_deltas.keys()) + entry_off = CT_BASE + first_pt * CT_STRIDE + print(f"\nEntry for point {first_pt} (offset 0x{entry_off:04X}, " + f"stride 0x{CT_STRIDE:02X}):") + print(hexdump(bytes(buf), entry_off, CT_STRIDE)) + return + + # Auto-save snapshot before write + print() + print("Saving pre-write snapshot...") + snapshot_save(gpu, gpu_name, mask) + + # Execute write + print() + print("Writing to GPU...") + ret, desc = write_clock_offsets(gpu, point_deltas, mask) + print(f"SetClockBoostTable returned: {ret} ({desc})") + + if ret != 0: + print("\nWrite FAILED. GPU state unchanged.") + return + + # Verify by re-reading + print() + print("Verifying write...") + time.sleep(0.1) + new_offsets, err = read_clock_offsets(gpu, mask, curve_info) + if not new_offsets: + print(f"WARNING: Verification read failed: {err}") + return + + mismatches = 0 + for point, expected in point_deltas.items(): + actual = new_offsets[point] if point < len(new_offsets) else 0 + if actual != expected: + mismatches += 1 + print(f" MISMATCH point {point}: expected {expected/1000:+.0f} MHz, " + f"got {actual/1000:+.0f} MHz") + + if mismatches == 0: + print(f"Verified: all {len(point_deltas)} points match expected values.") + else: + print(f"\nWARNING: {mismatches} point(s) did not match!") + + +# ═══════════════════════════════════════════════════════════════════════════ +# Verify command handler +# ═══════════════════════════════════════════════════════════════════════════ + +def cmd_verify(gpu, gpu_name, args, mask, curve_info): + """Write-verify-read cycle for a single point or range.""" + delta_khz = int(args.delta * 1000) + + if args.point is not None: + points = [args.point] + elif args.range: + points = list(range(args.range[0], args.range[1] + 1)) + else: + print("Error: --point or --range required for verify mode") + return + + point_deltas = {p: delta_khz for p in points} + + err = validate_write_request(point_deltas, curve_info) + if err: + print(f"Safety check FAILED: {err}") + return + + print(f"=== Write-Verify Cycle ===") + print(f"GPU: {gpu_name}") + if curve_info: + print(f"Curve: {curve_info.describe()}") + print(f"Points: {points[0]}{'–' + str(points[-1]) if len(points) > 1 else ''}") + print(f"Delta: {args.delta:+.0f} MHz ({delta_khz:+d} kHz)") + print() + + # Step 1: Read BEFORE state + print("Step 1: Reading current state...") + before_offsets, err = read_clock_offsets(gpu, mask, curve_info) + if not before_offsets: + print(f" FAILED: {err}") + return + before_raw, _ = read_clock_table_raw(gpu, mask) + + for p in points[:5]: + entry = read_clock_entry_full(before_raw, p) if before_raw else {} + off_val = before_offsets[p] if p < len(before_offsets) else 0 + print(f" Point {p:3d}: freqDelta = {off_val/1000:+8.0f} MHz") + if entry: + print(f" All fields: {entry}") + + # Step 2: Save snapshot + print() + print("Step 2: Saving pre-write snapshot...") + snapshot_save(gpu, gpu_name, mask) + + # Step 3: Write + print() + print("Step 3: Writing offsets...") + ret, desc = write_clock_offsets(gpu, point_deltas, mask) + print(f" SetClockBoostTable returned: {ret} ({desc})") + if ret != 0: + print(" FAILED — aborting verify cycle.") + return + + # Step 4: Read AFTER state + print() + print("Step 4: Reading back (verification)...") + time.sleep(0.2) + after_offsets, err = read_clock_offsets(gpu, mask, curve_info) + if not after_offsets: + print(f" FAILED: {err}") + return + after_raw, _ = read_clock_table_raw(gpu, mask) + + all_ok = True + for p in points: + expected = delta_khz + actual = after_offsets[p] if p < len(after_offsets) else 0 + match = "OK" if actual == expected else "MISMATCH" + if actual != expected: + all_ok = False + print(f" Point {p:3d}: expected {expected/1000:+8.0f} MHz, " + f"got {actual/1000:+8.0f} MHz [{match}]") + + # Step 5: Check for collateral damage + print() + print("Step 5: Checking for unintended side effects...") + collateral = 0 + check_range = min(len(before_offsets), len(after_offsets)) + for i in range(check_range): + if i in point_deltas: + continue + if before_offsets[i] != after_offsets[i]: + collateral += 1 + print(f" WARNING: Point {i} changed unexpectedly: " + f"{before_offsets[i]/1000:+.0f} → {after_offsets[i]/1000:+.0f} MHz") + if collateral == 0: + print(" No unintended changes detected.") + + # Step 6: Check if unknown fields changed + if before_raw and after_raw: + print() + print("Step 6: Checking if driver modified any unknown fields...") + field_changes = 0 + for p in points[:5]: + before_entry = read_clock_entry_full(before_raw, p) + after_entry = read_clock_entry_full(after_raw, p) + for key in before_entry: + if key == "freqDelta_kHz": + continue + if before_entry[key] != after_entry[key]: + field_changes += 1 + print(f" Point {p}, {key}: {before_entry[key]} → {after_entry[key]}") + if field_changes == 0: + print(" No unknown fields changed.") + + # Step 7: Read voltage + voltage, _ = read_voltage(gpu) + if voltage: + print(f"\nCurrent voltage after write: {voltage/1000:.1f} mV") + + # Summary + print() + print("=" * 50) + if all_ok and collateral == 0: + print("RESULT: Write verified successfully.") + elif not all_ok: + print("RESULT: Write verification FAILED — offsets don't match.") + else: + print("RESULT: Write applied but with unexpected side effects.") + + print() + print("To undo this change, run:") + print(f" sudo python3 {sys.argv[0]} snapshot restore") + + +# ═══════════════════════════════════════════════════════════════════════════ +# Inspect command +# ═══════════════════════════════════════════════════════════════════════════ + +def cmd_inspect(gpu, gpu_name, args, mask, curve_info): + """Show detailed field-level data for specific points.""" + raw, err = read_clock_table_raw(gpu, mask) + if not raw: + print(f"Failed to read ClockBoostTable: {err}") + return + + vfp_points, _ = read_vfp_curve(gpu, mask, curve_info) + + max_point = curve_info.total_points if curve_info else CT_MAX_ENTRIES + + if args.point is not None: + indices = [args.point] + elif args.range: + indices = list(range(args.range[0], args.range[1] + 1)) + else: + # Default: show interesting points + defaults = [0, 1, 50, 51, 80, 126] + if curve_info: + if curve_info.mem_points: + mp = curve_info.mem_points[0] + defaults.extend([mp - 1, mp, mp + 1]) + defaults.append(curve_info.mem_points[-1]) + indices = sorted(set(defaults)) + + gpu_set = set(curve_info.gpu_points) if curve_info else set() + mem_set = set(curve_info.mem_points) if curve_info else set() + + print(f"GPU: {gpu_name}") + if curve_info: + print(f"Curve: {curve_info.describe()}") + print(f"ClockBoostTable entry detail (stride=0x{CT_STRIDE:02X}, " + f"9 fields × 4 bytes)") + print() + + for p in indices: + if p < 0 or p >= max_point: + continue + entry = read_clock_entry_full(raw, p) + if not entry: + continue + off = CT_BASE + p * CT_STRIDE + + domain = "" + if p in mem_set: + domain = " [MEMORY]" + elif p in gpu_set: + domain = " [GPU]" + + freq_str = "" + if vfp_points and p < len(vfp_points): + f, v = vfp_points[p] + freq_str = f" (VFP: {f/1000:.0f} MHz @ {v/1000:.0f} mV)" + + print(f"Point {p:3d} — buffer offset 0x{off:04X}{domain}{freq_str}") + for key, val in entry.items(): + if key == "freqDelta_kHz": + continue + marker = " ← freqDelta" if "0x14" in key else "" + if "0x14" in key: + print(f" {key}: {val:12d} (0x{val & 0xFFFFFFFF:08X})" + f" = {val/1000:+.0f} MHz{marker}") + else: + print(f" {key}: {val:12d} (0x{val:08X})") + print() + + +# ═══════════════════════════════════════════════════════════════════════════ +# Read command handler +# ═══════════════════════════════════════════════════════════════════════════ + +def cmd_read(gpu, gpu_name, args, mask, curve_info): + """Handle read subcommand.""" + if args.diag: + run_diagnostics(gpu, gpu_name, mask) + return + + points, vfp_err = read_vfp_curve(gpu, mask, curve_info) + offsets, ct_err = read_clock_offsets(gpu, mask, curve_info) + voltage, _ = read_voltage(gpu) + + if not points: + print(f"GPU: {gpu_name}") + print(f"Failed to read V/F curve: {vfp_err}") + print("Run with 'read --diag' to probe all functions.") + return + + if args.json: + output_json(gpu_name, points, offsets, voltage, curve_info) + return + + print(f"GPU: {gpu_name}") + + if args.raw: + def fill_vfp(buf): + _fill_mask_from_boost(buf, mask) + vfp_raw, _ = nvcall(FUNC["GetVFPCurve"], gpu, VFP_SIZE, + ver=1, pre_fill=fill_vfp) + ct_raw, _ = read_clock_table_raw(gpu, mask) + + if vfp_raw: + print() + print("=== VFP Curve (0x21537AD4) — header + first entries ===") + print(hexdump(vfp_raw, 0x00, 0x48)) + print(" --- data at 0x48, stride 0x1C ---") + print(hexdump(vfp_raw, 0x48, VFP_STRIDE * 5)) + + if ct_raw: + print() + print("=== ClockBoostTable (0x23F1B133) — header + first entries ===") + print(hexdump(ct_raw, 0x00, 0x44)) + print(" --- data at 0x44, stride 0x24, freqDelta at +0x14 ---") + print(hexdump(ct_raw, 0x44, CT_STRIDE * 5)) + + # Show around the memory domain if known + if curve_info and curve_info.mem_points: + mp = curve_info.mem_points[0] + if ct_raw: + mp_off = CT_BASE + (mp - 1) * CT_STRIDE + print(f"\n --- around memory transition (point {mp}) ---") + print(hexdump(ct_raw, mp_off, CT_STRIDE * 4)) + print() + + if not offsets: + print(f"(Clock offsets unavailable: {ct_err})") + + print_curve(points, offsets, voltage, curve_info, full=args.full) + + +# ═══════════════════════════════════════════════════════════════════════════ +# Argument parsing +# ═══════════════════════════════════════════════════════════════════════════ + +def parse_range(s: str) -> Tuple[int, int]: + """Parse 'A-B' into (A, B) tuple.""" + parts = s.split("-") + if len(parts) != 2: + raise argparse.ArgumentTypeError(f"Expected A-B format, got '{s}'") + try: + a, b = int(parts[0]), int(parts[1]) + except ValueError: + raise argparse.ArgumentTypeError(f"Non-integer in range: '{s}'") + if a > b: + raise argparse.ArgumentTypeError(f"Start > end in range: {a}-{b}") + if a < 0 or b >= CT_MAX_ENTRIES: + raise argparse.ArgumentTypeError(f"Range {a}-{b} outside 0–{CT_MAX_ENTRIES - 1}") + return (a, b) + + +def main(): + parser = argparse.ArgumentParser( + description="Read/Write NVIDIA GPU V/F curve via undocumented NvAPI (Linux)", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + %(prog)s read Condensed V/F curve (GPU + memory) + %(prog)s read --full All points including empty slots + %(prog)s read --json JSON output with domain classification + %(prog)s read --diag Probe all functions + mask comparison + %(prog)s inspect Show boundary and interesting points + %(prog)s inspect --point 80 Show all 9 fields for point 80 + %(prog)s inspect --range 125-132 Show GPU/boundary/memory transition + %(prog)s write --point 80 --delta 15 --dry-run + Preview writing +15 MHz to point 80 + %(prog)s verify --point 80 --delta 15 Write + verify cycle + %(prog)s write --global --delta 50 +50 MHz to all GPU core points + %(prog)s write --reset Reset all GPU core offsets to 0 + %(prog)s snapshot save Save current ClockBoostTable + %(prog)s snapshot restore Restore most recent snapshot +""", + ) + sub = parser.add_subparsers(dest="command") + + # --- read --- + p_read = sub.add_parser("read", help="Read V/F curve (default)") + p_read.add_argument("--full", action="store_true", + help="Show all points including empty slots") + p_read.add_argument("--json", action="store_true", + help="JSON output with domain classification") + p_read.add_argument("--raw", action="store_true", + help="Include hex dumps") + p_read.add_argument("--diag", action="store_true", + help="Probe all functions with mask comparison") + + # --- inspect --- + p_insp = sub.add_parser("inspect", help="Show detailed entry fields") + p_insp.add_argument("--point", type=int, help="Single point index") + p_insp.add_argument("--range", type=parse_range, help="Point range A-B") + + # --- write --- + p_write = sub.add_parser("write", help="Write frequency offsets") + tgt = p_write.add_mutually_exclusive_group() + tgt.add_argument("--point", type=int, help="Single point index") + tgt.add_argument("--range", type=parse_range, help="Point range A-B") + tgt.add_argument("--global", dest="glob", action="store_true", + help="All GPU core points") + tgt.add_argument("--reset", action="store_true", + help="Reset all GPU core offsets to 0") + p_write.add_argument("--delta", type=float, default=0.0, + help="Frequency offset in MHz (e.g. 15, -30)") + p_write.add_argument("--dry-run", action="store_true", + help="Preview changes without applying") + p_write.add_argument("--force", action="store_true", + help="Allow modifying memory points") + p_write.add_argument("--max-delta", type=float, default=300.0, + help="Override safety limit (MHz, default 300)") + + # --- verify --- + p_ver = sub.add_parser("verify", help="Write-verify-read cycle") + p_ver.add_argument("--point", type=int, help="Single point index") + p_ver.add_argument("--range", type=parse_range, help="Point range A-B") + p_ver.add_argument("--delta", type=float, required=True, + help="Frequency offset in MHz") + + # --- snapshot --- + p_snap = sub.add_parser("snapshot", help="Save/restore ClockBoostTable") + p_snap.add_argument("action", choices=["save", "restore"], + help="save or restore") + p_snap.add_argument("--file", help="Snapshot file path (for restore)") + + args = parser.parse_args() + + # Default to 'read' if no subcommand + if args.command is None: + args.command = "read" + args.full = False + args.json = False + args.raw = False + args.diag = False + + # Update safety limit if overridden + global MAX_DELTA_KHZ + if args.command == "write" and hasattr(args, "max_delta"): + MAX_DELTA_KHZ = int(args.max_delta * 1000) + + gpu, gpu_name = init_gpu() + + # Read boost mask first — this is the canonical source per nvapioc + mask, mask_err = read_boost_mask(gpu) + if not mask: + print(f"Error: Could not read boost mask ({mask_err}).") + print("This mask is required to safely read/write the V/F curve.") + sys.exit(1) + + # Classify points (GPU core vs memory) + curve_info = CurveInfo.build(gpu, mask) + + if args.command == "read": + cmd_read(gpu, gpu_name, args, mask, curve_info) + elif args.command == "inspect": + cmd_inspect(gpu, gpu_name, args, mask, curve_info) + elif args.command == "write": + cmd_write(gpu, gpu_name, args, mask, curve_info) + elif args.command == "verify": + cmd_verify(gpu, gpu_name, args, mask, curve_info) + elif args.command == "snapshot": + if args.action == "save": + snapshot_save(gpu, gpu_name, mask) + elif args.action == "restore": + snapshot_restore(gpu, mask, args.file) + + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..eec27f7 --- /dev/null +++ b/uv.lock @@ -0,0 +1,531 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "fastapi" +version = "0.135.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/7b/f8e0211e9380f7195ba3f3d40c292594fd81ba8ec4629e3854c353aaca45/fastapi-0.135.1.tar.gz", hash = "sha256:d04115b508d936d254cea545b7312ecaa58a7b3a0f84952535b4c9afae7668cd", size = 394962, upload-time = "2026-03-01T18:18:29.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/72/42e900510195b23a56bde950d26a51f8b723846bfcaa0286e90287f0422b/fastapi-0.135.1-py3-none-any.whl", hash = "sha256:46e2fc5745924b7c840f71ddd277382af29ce1cdb7d5eab5bf697e3fb9999c9e", size = 116999, upload-time = "2026-03-01T18:18:30.831Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" }, + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, + { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "nvcurve" +version = "0.5.0" +source = { editable = "." } +dependencies = [ + { name = "fastapi" }, + { name = "httpx" }, + { name = "nvidia-ml-py" }, + { name = "pydantic" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[package.metadata] +requires-dist = [ + { name = "fastapi", specifier = ">=0.115" }, + { name = "httpx", specifier = ">=0.27" }, + { name = "nvidia-ml-py", specifier = ">=12.0" }, + { name = "pydantic", specifier = ">=2.0" }, + { name = "uvicorn", extras = ["standard"], specifier = ">=0.30" }, +] + +[[package]] +name = "nvidia-ml-py" +version = "13.590.48" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/a0/f4fc18cf72f06821a9a665085435b901449986855519d5b3843532db35e9/nvidia_ml_py-13.590.48.tar.gz", hash = "sha256:8184d1be52914ac7f0991cd1c0d946c65dc88a840c754cd12c274b77b88760dd", size = 49732, upload-time = "2026-01-22T01:14:56.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/72/fb2af0d259a651affdce65fd6a495f0e07a685a0136baf585c5065204ee7/nvidia_ml_py-13.590.48-py3-none-any.whl", hash = "sha256:fd43d30ee9cd0b7940f5f9f9220b68d42722975e3992b6c21d14144c48760e43", size = 50680, upload-time = "2026-01-22T01:14:55.281Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "starlette" +version = "0.52.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.41.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +]