From dcadefe01e3448feb12c4a8f10736aeb046f665d Mon Sep 17 00:00:00 2001 From: Antonio Maiolo Date: Wed, 17 Dec 2025 12:26:33 +0100 Subject: [PATCH 1/8] feat(app): add Windows build support - Add Windows icon (icon.ico) for NSIS installer - Configure electron-builder with NSIS settings - Update PyInstaller spec for cross-platform sqlite-vec detection - Create stub_win.spec for Windows native messaging stub - Add cross-platform build scripts (build-backend.js, build-stub.js) - Add Windows build job to GitHub Actions release workflow - Mark alpha/beta releases as prerelease automatically --- .github/workflows/release.yml | 84 ++++++++++++++++++++++++++- app/build/icon.ico | Bin 0 -> 285478 bytes app/package.json | 14 ++++- backend/native_host/stub_win.spec | 46 +++++++++++++++ backend/think.spec | 25 ++++---- package.json | 4 +- pnpm-lock.yaml | 92 +++++++++++++++++++++++------- scripts/build-backend.js | 55 ++++++++++++++++++ scripts/build-stub.js | 90 +++++++++++++++++++++++++++++ 9 files changed, 375 insertions(+), 35 deletions(-) create mode 100644 app/build/icon.ico create mode 100644 backend/native_host/stub_win.spec create mode 100644 scripts/build-backend.js create mode 100644 scripts/build-stub.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2a9122..f0b2377 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,6 +20,7 @@ jobs: with: generate_release_notes: true draft: true + prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -141,10 +142,90 @@ jobs: path: app/release/*.dmg retention-days: 1 + build-windows: + name: Build Windows App + runs-on: windows-latest + needs: create-release + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: '1.8.0' + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Install pnpm dependencies + run: pnpm install --frozen-lockfile + + - name: Install backend dependencies + working-directory: backend + run: poetry install + + - name: Download MSVC-compiled sqlite-vec + shell: pwsh + run: | + # Download MSVC-compiled vec0.dll from sqlite-vec releases + $vec0Url = "https://github.com/asg017/sqlite-vec/releases/download/v0.1.6/sqlite-vec-0.1.6-loadable-windows-x86_64.zip" + $zipPath = "$env:RUNNER_TEMP\sqlite-vec.zip" + $extractPath = "$env:RUNNER_TEMP\sqlite-vec" + + Invoke-WebRequest -Uri $vec0Url -OutFile $zipPath + Expand-Archive -Path $zipPath -DestinationPath $extractPath + + # Find where sqlite_vec package is installed and copy vec0.dll there + $sitePackages = poetry run python -c "import site; print(site.getsitepackages()[0])" + $sqliteVecDir = Join-Path $sitePackages "sqlite_vec" + + if (Test-Path $sqliteVecDir) { + Copy-Item "$extractPath\vec0.dll" -Destination $sqliteVecDir -Force + Write-Host "Copied vec0.dll to $sqliteVecDir" + } else { + Write-Host "Warning: sqlite_vec directory not found at $sqliteVecDir" + } + working-directory: backend + + - name: Build backend + run: pnpm build:backend + + - name: Build native stub + run: pnpm build:stub + + - name: Build app (frontend) + run: pnpm --filter think-app build + + - name: Build Electron app (NSIS installer) + run: pnpm --filter think-app electron:build + + - name: Upload Windows artifact + uses: actions/upload-artifact@v4 + with: + name: electron-app-windows + path: app/release/*.exe + retention-days: 1 + upload-release: name: Upload Release Artifacts runs-on: ubuntu-latest - needs: [create-release, build-extension, build-macos] + needs: [create-release, build-extension, build-macos, build-windows] permissions: contents: write steps: @@ -162,6 +243,7 @@ jobs: files: | artifacts/chrome-extension/*.zip artifacts/electron-app-macos/*.dmg + artifacts/electron-app-windows/*.exe draft: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/app/build/icon.ico b/app/build/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..757a15b52fa8e799c317ea20d2c6745300bb85e3 GIT binary patch literal 285478 zcmeFa2e?+%nKn$?B%BunvCzAUf>>j3*omTIHz*=13W^GL5fuyehJb~ps1#|UqOq6Q z3)oAdCPvfe%S>kGpZv*8&RYL{KV_}8-*YgT?+;04=A7$V*V=onr`*r|JZtU!?){!~ zTDAI0tFO1(VvAPjd$n4)VXIdAv})C=S1&tn->TI=VXQrR&X4|jt5&ye3jzG_eB)kU zZxwcI)oNp$!$hm&Y#iPH`k!^b`OU9%Yxq{JPuqr$-D|CTc4__1ew`b>wQ1+p-`X5) zOSG-5ZH<0w%t;S9^lWlY8Om;fHn2;>w>C!W1DjoXw{2K!op1ibSN~E(1LImfYON1P zyA|!Fo^2X_f%ZGJKca;mZCax>1Rm?Tj_G;p%D9iId{xhM8!`8L9RCJ(-|pF_^}RSB zO`iU%@${%Q)S!Dc+P|PBoOIp}S%#{AZco9omQCCyxzR-n}?nvtW5R`P_-2U)QdMti()w zGS)rCP98i4@7-?0Z;+c^zbuAswT5p1|MRpbt=6DDY!Yv3GG0GNorbVw|Nf1)KK^2y z^TJzk>_wM{lh2zNrhV_ZIQP}}Pt zeVv}-DSUF?W!koBB(9NXoLykD91$PnJf3~OGbGG^>(jVV*REOLWD5TYe=(M9Bldfn z!GA4%bkX?1kNV^oT4On9GBU>TefK-CapAi^jvI99`(`dH+E`h9eS^~UgJhRdNgj(wM*!VTz1Bo z>RE5G6mkBhUEOuMbFK9=z(Y*huvU4zA#5Huc6_*g@e1Vq{O}90 zyYQVK#amW99mh)@XSay}bwT2m2qz>5n`Uj~R1X z*k#wZ0ZA9O=;|@E8aaUX$79g%`7A_Bq-urP}@a7NW#kb7~`yX^j=+=HM z*sFy>gZ2pLUNx!_VWM`|PWJnNQARZvBRDRjy;5 z+cbQRM(lz8miy_#3a7JvSUb{%rNDD{K=<_G2Z69ZkdZrv>-K0;S@W6*3#}m#xJ1lr@RpfEmmVfvO z;?VX$(Jy`H!#L{fbHl)lU09KVNinMZ%w^qa#5 z-MckTnSWQf=cz~HW&<`6th?^AQ+NcrgLdDwz)gILu|wE^ymsa-p=ZbS!ej5gEay7M z`B&qUAG{t1ZoFZ*;L1zF<2d(r-JDy+XJ=fuCth<|SpLe>@xY<`hsEE2G>$pzw7@z@ zn6Pg3xPkmv@MHYf#ALG6X{7FY&>45Z8R6;oUx?H0xixm}(4OOzYaBmee0b`;7oxZC zt$aGpTs#M3FQzd)=J|{9-tXNPAAbEwIet=PUW_--Rs8FNE83iJ#f9Oi_g{)P%$gFB zFN~?LI$WWif6N2F^1oh@htBQS3Nshp79M`-p}6A32jjL|4fGhpnG;SA&%XOiOzpjA z;xjm(dgqjQ-K|&0XWx4k=g-DFm(Gb7Uv*)SbB+~l%G_Im_yZ>!_3RezU2$)G_~l39 z(r1>14SIA1kKj=8XdWO(PS>uhAISWga_fNy>2bU+_`l?ev%;cB?u@;=bP5+v8Xq2i z<^H(Oo;!-%xN}bq&%gU<)b<$q$Kp#LJQ26tv|re`Pml1@2T#h_0Yml-9Z?@ozWyN2 zAFs5VW?Uuywi>u`So-)qaq6t=LeEa?hg)V{7iY}7C1Ni~coHsPTf44)9RIpS4Eqn> zCA|Fh^0@uhn~Dv@6HYw&$nerz%j4LwM~5+|9^LrzI}gUPz4rdYalnQ>vDQ0=*WQ0b z=DxGf&S5>=Cp`J`eK>z8zErfUZ@eHJGIX!->}yNorB|LE)^8_x8~b+a-1yiF3*)I{ zk7Rc_XgZTsZ-z|Lq)p_yh7w(F$y}cN1 zNu^DibV@kqoa2Iwzq2IXGJQfAdE7za^>>#+ZfShAXivRxH_n&D(W8$Pzv9RIefOyS z!t<}+7yESWh&jb&^Tc1-|1qxk83WpP_S!bQ_QqYNt7D}Na(vg^NpaQN^Wv(v=Earl zQ=5y4z#};5t+XD~X9CzM!VCKmD6T^B~zYjigOFVV-VP$M`+`exAhy1VB zfsVJ!FFH0%yJ;->CilX>&(Su=w%@8><2!H8iYs5cJ-+kS?D#Gk`zGzpIT%Cp9>;I4 zoE2BSF)JQ@#2$i&wrSV;M)dI65Y8BTSh#oY#JCRDC4I^`j=_KB{sH5^q;3bb(Rk-= zW5Zde9msRhSqHW_Pkrxuc3P+LqqnBVJ$4@0IQ9CI!iR58kKR7OF&oc6c=OiOK78x8 zxZtjH;&xkZ*tqJYo8ulk4Xm@9{9wEx9JJqd;rXYojo{mGD8_cJTW?-ozkumyhD+?t zy!rTW$vH#aw%97?dE5bO;ngQEjfV}{2FJCq`wsoXeREEWAFsN>+KuRQdu-|X@$dt- z6Pug$>mELR>*m-y%yaeJ>ghaIwGc=q8-;@S;)oN)pFrX_yx{296THhTCjVddlJ z#U33y*ch&P#k?QiJAGul{M-X1PaIntG3~Kqzwr9w7sSt2PL}r3OOxWTgSVAA)oTdD z58W=j`sn%5&(Sy9vGvF`_mH`##2Y3ZQTmqob`7oj)#3kv>T3+0I;<6*UotvAx9Fs> z^H#kA@>gd=E9^r}kqvFQB)(ayK`*yhNhQq_BFPT9=Yctdmxj{9_7C)|6((D3Wm#>M9s3=dN;+9ymLvr{hw_Cq(;_-+ZG^VcfA>hubdMGu(g6knqSI2Zg0mhJ-sN z?HwkL-XVPQ*ob)H=g@RV!F%-!|NPn*=$L`oav`;cV*TyinX;fP_-c;@vwQqg%D<%GRwc4ciz_gkMdzh=~d^_+`%6)r4 z=kV{Zj)>cD+9k=9^W|~Z_1cABJv}TAAJVU412XDEG2~deHqn z<7p^`&FufrI+Ux$9BJuiYa0=LCb`r$FI8@TsfcJn^r z$B!Hthwk1t#lFMlUBm114vKFq92$Fe=}>S`D{y-(982-huE`{SS3BPNzHOUYtF_v; zxrJES+JYZHwsRR@3--}4=S}4)(;jtw^|@q^`u#oxVlT%32+9-(W; z_NGsHw~>69FO1`}LiU^FIJOe4vTrttp;r4kWEjIA%|~IYO=C5tdOyI0+GvaC7aR3j zH=KCrfH3CBfnjUxPZ%@IDOT8G{3({gPifcUtF?I^HY(SwdhCAQrlFw~?Cyp31n~X= zjk%CUe|}uwf7x8Ute^i0oj*eEUEm(QR@=7UaD4xjzD=7p__)?)DB2{X?(R0V+T$2o z3A;Z)`w805(0+;bE3{w#nT@j4`FY#6Z9Yf;32eRvAJ5=;5&CP(qY* zdyDLvXCTT?t5)%|R;|7nTeS*wc}c5Qzh{p-)Zemi*Q(WL?7x}&XLyq|#v@*q9<5x%?!Z-3-Gcm%b=YvS8@=S!GgXoJxB{jL8lKiz7rTj4u31JF*z zcUGpMEyed!p2T-rUcm7yXs_b@3Qc_FE>BG6Ab=bmzbcj{~7`wYKt z?)w$>-*Y(j)Ny!!cy9X6gU=uG-QnSk%df4D!D!Oli%zZKZ1<1%&8Ovgyw9gR z{PBHoZ@hcP`)7H#eaO)x!pL#w=ywqC{LSS|xA9=Shd$xV^YPxWb?CLuI*q;8Ur*lG z>vtAtPjLA6C}t%8wboZ)&fcZB4m<9!W0*AK?l2AS_piI}L239-LA>Yn58_3)&Iv;f zIXvKd7U|u4PrTdNcmD&!+1K1G-({Hk@G}wLYlzn_d?508^4*I9-z#Sy{60bJ<=KD3 z6n_stXpcR^4NF$UZ*Q@s#INJv4DSo3u6Qc$jPF8>I_vy!^YTaK`vm9RI1S@Z%J&*} zhwe4_e!#}Pdr7S1k6iHk4wLU%7!Nw)yE+bjG53`F$2;oV5bx1HlQ#X4C*uK!9v(X5xzdSeofp1y;84}0K6GiP zcgOV`r!4J4YO+_qwog$grcs*;ij1uA8?o_`Bn3 z|7o1Md__EU;>F>F@n?w-vEg>m-@4*4eP7J6TEi>Ze{HtE<2>i8tHPk&2kCoF+#7Se z3)cL_Q>Wsc@JDzjtZm7gZ^g@J%#4@aen(uia#b4VJLBu;&yPLUUB};zO21kEO~RFP z?#yGg+H39~?;U5`JIKauw%#gSfA<`|+mm;r8{>NmQx?vP_dfS@dcQgI;fLZ@TWlVV zA3G+Tc>36|$|DLo3FFqe{TfQuAvFQM{gZuso*Iyg<`Oe_tonhg>4g7-> zeHYsh&b|ENFl@xIu+5g6g;@_Sio5K*L%3r4P2sWk--wTW@J8Houie9}2NvP|=1OVz zJpNGJX~*rtV|;&!v0InS!*?nMh8d6}w6lKW^k_^WBTLe!aSfyO+<7cP^O`2k*O^ zjNf?2HQ~8;pNi7ndpeFf<+!l?xux-mm5;??#~y+Abx-4c9osW;?(#b$ZSAz(mVw82 z-9HEKg*Qp|%}-PR;DvLy&ABq{zU#K37v9Itxcj;|YSau)?OJpbyw@wK-WMasYa z&SK2*ozvp@;u{O%4L6?`y05=ZvNLYn@GxTd0qGpJlYcz3pY6}^r`~j0*mjGJM9#<9 zPdNAJ@aD?d@$I+ntZR4SJhxRV?~FIyaGE^3_n6rC?bb2OyZ4evTQ>fxuRn~^oDyc6U26L$-vkKQq? zxNBrwF=s@aaLR7-ZeXkaT?2JQuiLs|#KBvI=N6BSgSOicKGO60C!K#h&%LyKrrR6O zg7~cYfm?@$=jR<6pPzSBTye*t;kCs_$I0V&4Fm8F0MEtKb6`FL-JturVf=C1g?Ao0 zE-t_I(D2;6Vd4AphsB5PI6O`|ZC6|q*H7~1YpMH({%G1Ln?8r;Gr~N6_}&|asTb@l zcKD20pCM{bn-`q0RXAhVW}=hz)rQAMdpt%z&Hdxq+HU?l^2_=Tc<254{DZKUbui!h z43j$Ruf0~_bF{AOwNG=fn~v-4Vs32qUu(cQp8qv{mRdh{^#1+AJ$UAY@$wm6xCYNC z&pB=jj(hWY?@Jww)dskK_BfyF&-c_udahUM%d@H(m+TsDJZDFVxdZC#eDMCjE&GJE zv2Lq2%5$+tzr^tjo@bZtTj5y{9`Ut#htG}l`IVzeL-p;@nQ~Y^OciRJ4CZTkZ3(G>3NpcotO3@i}05 zULOA*vE7H(t2y807;H4Ot+m+=wnyXn)1^4S3GFttnP{`{9BMY65u&lLHarWP37y+9 zJ_YRxwA0WALuc*M$KT5_9~a=6OuNtU6y{kxaX|aZq&3vc{?|T(FZk?9-+WHd9<3MJ zpYxmo&js*1K&=PibNw*%C!meOXL~gEa?G)#@%eu*^mtN|fAHCJe|)z6C&;JIslD)Q z{;;Eu4Mz+g8G7S$T{(x()BgtfV`u@Np}vmKalhbaxAHUPuu-Rkjk|XX8{&KC!^fPV zbEQnJ;ol(N4xe`irq6ho=kF}^@7+5LIry;j*>LZD_X}GN7$9>=7Fcn-Cq4)5&Cf!E z4>%|sb;8N`JaBBdV9M=b*h!~^LxBI_qmB;WJ#lonaOw=1JL>q8t# zMUEPIf`0zM@$fOHhLiAp(*FIO_Nl?K_+eC*Z#IK;9o8 zd-6$gpS;r!+lRZKeI_nlwJMIk^wKbH(xkBXjW^=_mtKs!?S}8>VO(+#Y-E7RF6DGi=zcUm>plhcdO1kAK&Yla9Vio?MLGS&)gRe9=cCB zdF;qAXqRpLes2`+V-Dnf&q*g7DEB{`_wO4XdGr?fj?I%#PmfPNb*p@r=830oi{IXS zW05)OgrR<~Gz#|yLwHX#X2kC49%A%}J;KOgyN6evyez))>=p6kK_-`TZaIAi!WVeGK2!pzD0hqF)IDI7X@vvAPfn}+ctcMNl`J}{g*Z0m5^ z@NL6^dkyew_Ap#8``2H?cpW_PkpAf!cl3V!LjOMNrSn355cd7AVc-9(xIW2s>Xail z4O?v73EwMNFN`~8a~Z3X`6uYg#R7ZhE@+=&KTG>6&cji7Z+Og*jRUU>Jmmb!*mxX|XYY;Y zKIXm40au#yG4Ea0^`(b=IbScsIhj|vaW0$24J*z$cLwx5hSPC=8qV1mCu3&pV{l9^ zPR8*l^yG+KjllVE^uy2}i8u~JKNM{U#=e7oZ?r*ZJELunv8~Y$MB5mxFIo@ibjJ8P zIBthl!|^|${~Fp?dbX|pKFHrk6}F&Q6AU2lUq$;ST6?rEX#IM%sbT%rSVKqPob`7; z+BIl*pv^~n0PRV%7tmfudk5_YkpB_-&(Z%3{V&n}5$&IF{#)4l7aad9+W&+0?`ZrV zGyg9d{~ydBiuNBkX8TXnDE|k|f6B4`Kbk*s4ECg9obxh$@4#>{U5YnLH_6Hzd-vj+Q(?`qpiaDD~RJ696yY<2yG6=ZbE+v8uz2&XxvYB zMjMFM6K!3z2DGmO*P1BD7Mp8`0p$9ts6pnA`#$rz56+KA&wS5Bdl2nq$bEo1_yYZJ zaL%>LdXpMPAFZYT`-JoII6|*H_hahexaRX*Lq5me=1W_(0DR4iH@FSahn-}*v_t#e z%y;Pz#})tOocr{?oTE({r){-cVwU52J>sn&<6b}v5iwTm6fq|qk0aYDb>uk7)?wm! zbN=bmbrPQz6R~mc;lA=)v|pfog0>Rv3E-QD{#rEd5eK7f2R+>X)!Jk38#rG>72IO> z&t(94XoY(?=3;&HTcAG#Z35a{wCB)%fW~VxbBuhK^_1$_>!VViDqAm~&ZXX9vrdP# zmSkiO7+uEvq;nttGyAMp*fiZV7r{fFz18h6B1gQ2>rcm0#P%gRP3+X+@p;GTJ;xK4 z)!C=aYV3X4p50W(I5a;^<~i;dcwNB$Q2Pn`H_+}!yMy)g!a!E=bm0)Bc99pah?}*sSdD?Fpj@7-PD%L zq*~1~b!(5mSFQTZ$fh~Sr0c)icD?%kOXib}dL4-)#R&fD@us;-tgDTs&@W?<<0`%! zKXmHsReV9WB3sEx#b5JS6OLwhR=>^YH6xqs2;OFNh=noAXB^%$evac8(a%O3i?%D` z=n9Viul~NM*LX``WdM8iSHNutwC&M&KgQ4I?}Gc^f^Xil=bA2#i<**L7Uz`r{GzX= z`b#o(^_s@Beb={|Pz*h^K9W7d=Ca<`&sS{J>D0@L4ab9?=gBT(p}x--bA?PjU)6Q% z{uvAHG_j?7&1&qqE$>CYIawJiY}e%^`%N-=k893esbBILB`3(I#FFai@ygy)8l^< zfUn)a{Z;7kw~ysnuh$22T3>(o+r`MU?eWmD97!#w{4kfC^FG_rYeO;T_PsY7Qb)y{I#cUW@<7|~0^y`({^*P#6TN%H^;~3Fbg>^f5eIooF-W}=pEuXl^AfYmG&iPm zuB$zKC|2bO&t7QLWymjia4bHKbK_P0WF2C5+$Dx`+%c*j*F_JTr9HLdWAshg=Ion{ z>jQI&qY-+)!u$i!J07h&&RgyWu+w2b{0H!|4fx=5)elhz|LOeVysi#}cjvdL53iLn z*Q#6Oppb`LWo?vtY5z}tXoI2c+COZn9`*iXJiCV!4K zTzQ=req^81^NcIkt@?9$>2(j4wMIFK!E%6ksb}ZGu~Lt77`OS=$5FLcVxkVt!K2!0 zVk6~3P1rJ9q%SK`vwb@rt;RvQN}l(Lzha$IA~HKB8b|2DOMgr6lCKOC(C zV*X!uy=TCP9oPI?SLBn=PWc@5|26(?9aQtcIp!WZMJ;GPybfT)bVasuozHW2&5$)) zx2~XH*stW9`t(!QpW2kMW-ygl>aOz=hg@fy(Q8_l@JTGvln05=_pr*kl=)oav`@W~ z7dy^1ndg1|Rp+u#-?!8r$Ax=YxAi(rxfMTYuPkkqIVo*hoU*rw{kmAvd=a0H{CU2#=Q}IqH;?N&EOqqQxwd>%>YR3%1J!j~Zd-fSjL*9*V&hn8Q;!Xw+T=PD zf2CX*SGGqv_g}TE@>M%kJ--I8J{Ff}EZI*rPWWkN9hGrbF=RV=T;nX`@Z)M;GM;ig z+o5f@rF|7E#u$g?rOBM-*ljxo^tR7v{#4KHdtP|%by3DxFISP5dT@X2Wvv?y@mu6q z<;0KFxaUM;#^3Aa_Zy!>u12Eo@V{a0gRi!T^%(Teqw#y6bvg9&eBIJ~NN!V2S97Fu z&a=!h|8hSob5dhjhhv%x*TFe)a9zmS$g+}S!{~YQKFuXuMLg-c>^5~ye4GDf>e8;= zlug(ehs2`Xq;ah&!>01tN7^e4hx6(^V<_X0SS$|VqKsKMA!ki_Q5)p1>6~J4yyz__ z;w$t_d%K;>nk_LYSE6TpXnY<+?%hWjzvVRXQ`OD&rE(Ip#9y_g*f>W0G?(hDI<9ig zU&)Eu=f0e5m`(WSyF)zhu5F0+KlZr*;_is}&O`q(dhppa*PbKD)bS*;#b?^gdubPDDT1(O5MP;HAWn@3kh%c?L#1-cpH+0Kb8dqOm9wUFRp>{X)wu6oTaqkEa zQxCMO(eOJn9y5DA#<(2k95$^r%n7G1tG(*+*zM-yY`?Huk#ifatJm3FZ|YOltK`<@ z#fPr*H10M^|J1MORAR4URyn`MW{%Wu=BeHf>(q3m=RCi^$g%CV+$x@Y&V}*j2cC$F zKl~yt`{Y+~=?{Mqm;B)8aVeVgKh*yIPk$X3z5jWCfjDzTWZjJXcSw3YaEEX(9M zAM?IBU9T_44q4g5Oy~E|S%J1EUbl%33tm~*gZDcz>#~j#r;m_->l!xE6$0yu{uja`+<#7j%^uq zuV4Id9CFa1u?POwjpKCR%6-!~R4!pN9wbiVMR9(4EY6F`CO$P!%1LQcaTtfplP$Sr(Pp6|c#zq}g;qy4wv0}xL) zv>Or2Kcjb!>$z*xcU)dG!kx>aFUOM&oA){A73a!n!M)4W>r$s2D~}6%lH1kVfFGCj zKGg>2iuq)N*otw-;qzuE$3gkk#9P)_EgW>%kzvL&ugSXi^)5ZH_x0=C&Rx#;1fKht z^w&NV~US*HR_u4VXJpljH@4xS70mQHl@Lz`h zHyW$QTyoIdaV_Hi5L%tNPRVyUC-X(#$f@%$$MU}qEB>Vx-G<6wo;6{1(pWRK>T@M7 zx0S{U?B$%#yPxLfV84RTW2u*QTZ-q~n+^=u-uF=D+U{$(T-SLIDDVIMUI4wXeedh` zh|A|J44d}pW4zF}^WnYY^ttB7OPMb{W@H<(jRpdftm1LNDb+ zY!q1BezNU*U7;^BWh^$=#ID6B^0*JE;j=((yYBxDp9y09wg&doz}rvJV+~e#6E3*M zy+Ot}#h6|zY~6y3#JzJ(J=)~C)CcVer{<^RQO31)M842bd#aQ1sof;2@`g!cQ9Z?_ z`$iE*_J@1KjMMFzuHhvvi=z$~@n}rBciC(M_TzU?924(*@3YEh`pVk3YrpRU-n)$V zX>Tajei|$GfqAPwjt3ukH0}lTIblvXKpozb#ua3Ifi4ai-HTy1wNBt%<^J+ zcUjhhTthT3!eyE>*@Ab^jr%CEc`TW8#aGJuT(-l!P%it=GMNLx^=0*i@4(jh-Qf-Y zJ3R?N>|X`uq3GZ7{1_K?`OTbgUYqh}@;(M0mG8`*%d_|WrQlQKbBuOgrMv5Ac7CmY4S;kLb}{Mpw`iM`fthd7)+ z=f?TtW>=bAZHzQKX*@vbYpfg_bwU9Ks;XK-8O*K0)cUvet8Z7n;t9`>G2<8h=l?|QrkpZf5#vTn;YSzYV;nV)0# zz5kPV`J4rD_~@~5_uU4C0lj*KK6uyHcij$gAZ+ci`=0T*vE$=a_b$bIf}f^+#P@>a zqi&D6ZqbU^uWJ{{hvz26SmCUi6OY5!kenCtmItmA;UMLO{0K)@8&(TnmJ_Wf<+_qn z)`;07j*Q7X|acyIjHNhMxFUl2<)sCGjcUi~s=eEc(xUS?(^KTd`dv1f| zNqnX~oPMkpl5fk8>WF>czYujE+lJ8wlStbfg=#ji5DP9!JHo8?Gy=s8gNDsIaMV-Y)M zlQD@6w?jG1r7@L9?sDD>pSgzAx6}dW(JUX*drlH2nKzx{7&_UH9jhIcCDvpkVYm9i zoYkRn)TV~-2{vro-1YYFWel<4zQ1+^`1+A?Zr3k~N#bGr8gtHB;#q3abHjPbC*oJ0 zvR=+dnk%?O9*wg)ABAtlQD8w0x-Hg$`ZE8P3y!&-%kbRmChIeI7#Ev9kFkm!m1&}1 z#G!LlpWAGwC9vr?GI2(Z8&<>^sM#V;T#OYoYIY z>xNNhogWvz`{Rla_vbx*-hp=x1N&}ZToAKxr#+5g*Z9q~03TK#8Y6wz^Gh7gtLH>` zwS7_7P`0W1b+H)lVy7-A&=*|hqfz7coM{~>M;epkajYs2f8qo3l0Vh=SR{`!uCb`D z9aHa1b3!?>_j~xB0xW;G-xEfheGvb0;FR}ylIC+E(Ma#7(hbL0G1bFDr~ zyS|QHPICf(vM0E`j7u?cUd9sUqBad*)}ig}Q^(75$*INR`NMq5vyM^Ea;x@G({5MC zbuVvro;t#fr0|K1D|6oPV9yHBmQK2SUGwO3yM`MI5+ z;XPKbp&YZus^^j;+Y_BD_I{js;aJahZNsS-URt@&@O5AI(;xl5eAY-l#9sLsUgMW} z>QvTr@?~5k896U)$uXa&ErHMOe=qm^jsv*+1J|=28+p;#oHygj`0}_@ zFZ-;@q%q5>*_3_N=VVOs?`uTzL>bvi>ymOh5^0JJI~sSE@g7lb>j8omWQ6kns0iq?`v=VTc5;z_uY?pJZ7s& z^Jkb9r(xn+z_{Y{d`U0QZ3UBE|8y^D;P(mxpC!^S_aOK0=N!9c!SdLv{aU4eiyi(F zKE;)|HZCjkc})3b3><4F7Jer8aly^l3Xap>AU0A?prdlmcbP}kH=pX4+zP)Yr|%&B z-qP#9ah9=4ECnWuA!DxQM8*w2eHFNVjaZJy{NK&*2ms61f$vPTe+YVFKZrYgd)u@}Ii~C<$Ki7cmvdd@&2dN^ z92dTFUiaK{@4#mQvgYgd0mpadl{eTr5q)55CMV>`cyb@`oz@p|IEE5$8JCZ%Y*YDU z!~K*vim{Rx^$A-x?>1y#udgqa(S4w>S6@FKNA^QLGbhzJtOm$^v3?4h4+2{czxMvE zd&IE;usx|cww&^M2R<||i^sTfxjd&j7*91Uob#En#83Uwmf2%{WFN9#vh9k`#I?#d zwdHU~2_scPrbNzhYBo>Q7buv!dOK=@>8+=yoIFCK$^uRS$HoxZbe&F6W zK8o9K{q3|y)dp=<{V=W^i_hD7vHdY)*LlZ~ZF<}dd|#OM0LSCKV?E@kVVvNj!G#_RG~-{zXBFPCMX<=v)_5oc5T!f9oX$}-BW<{`&Y zA1CDN?5ZrsJ%^bO#ftM^VeV*P`9=FT0ui}Ajfuk07 z-hRip=!4Jmbr#oJUt{cf4c=*ooeCZG1#B2o+m4gHA3IOg^AeXI>v~r?kHaxHgnb6@ zAIk3v__}9Ld<)+G5q^ho(~^TSW*mD?#HL^toRUlTQ^BJ9Ns7f~w6}PS51&*06l<~1 zR`Z_ySPq>-vsayWew9nkkt_Gbzk|A3H9d*B|rT z$E;_5oA`8HldqrWIU4hsaw;;8LGjZ{vDV|Qh%jzVA3DjV0lA&M2$$iJQZZJwfhI9GC9_e$L-P?1|q;Onz6FC-;#sI~U$7rfkR0 zG4HW7(KVSm%r4)2PN*Y3o8%(Y z85eCR?mT932!?trCZF&L27X5<@8e}G8gs5u%|XEaAI`Y^x`fYhN^iJNJo9|Umhd&t z7qJ?KCbAZX#OHQuO=EA86Sr9xyT{zLz6>XPWLr(JXMUTsBt{^t4#ntd^e$ti@CA*DmhgSd@l3ov2*?7*lW%6?fHVO ztb?^L<4M=*%VaR;vf|HVbJqCMU{4 z)B4IW$^MglRddpGZ-NiCDSDpQrW`lTiTWsG@niJaW~w2|CtO8N;Oi;K_xxMk0{~NJ z@w#}Bv z5R;!nro`s4sBg1p^(kX!Q!&5}&t;4@)tA`t=a@FnYo^;adE*D~+3|fnzC-8r)9;~u zJ#lRI3vb2^J9kR<=_lcnSgckIo6O_b{i&?;sywCmod?=Uaab&@JE=X5i}P-a{ic2U zhI?22FyZ$50LOmaqK8sFC)))^$DlUr^N{O5acx*B?;MB?!PU^1$C<+;`{v6lI=Tp8~kn_^YHq%U@I+_dBJ?$2$DKf|K>Zc9GH7=~IHaq9Ts>z>c| z^Rey+yY05S)r{lRxJ%#FW5wsOIzC{qJ;!;=aVU13*ZU0cBWp-7+q%#i)wnQ@`62kb zg1&EgpYi`Z;(b6`i@+$?ZpSM5O8n%QoU7(~j%h3F%Xwk%7=297y}s0^%Smj?ZMwFY zd^5NtZsEjzxNKnuddfHFblfo)d9WB1W5!(7(VY00`N03mfW66>`>X$-LFD_Zh~aGD z{k`SDb0HimUc^+nZj^aNELKy{m-{y7guTU^?{g*B&ZpSLoN^|zuvNv8I7<7fa;u-E zw60y>WShyC;n4iq{!BX_pU<;*{2mwgb7BHlver~j#|#TO;GE2F(yLeG_jvr8S^pjX z(=WYR?HLzh8-D9!N#mRo*&JUwcWlHba+KAk{M-IuylN~aBV!(K_R$q_Oq@Qu$)4qV z!JRL?6F2C%zWC#QVSdDK#z215X08LrSjJ&~Q%>sVFkj3$N1iA3=hv6IoJc*|{s$~3 zTi`5wF%I)%a=Nb>Z<-6ryN>(vbH-uuT24HFVz1zuHY9&$Tm9A^2bW#`+V=p!yf*ON z=iE3pp6i(R!0dQ!&SlV}?mbVGFF9eXZo@h9p1Cny>N_tgpPtv~IBX;iRaWo`*J|JK zWINfn&q;ha4!bYOcylan6LXLkUlpuACO$NN&f_z|IoD5XvUdFXIrF(UVsCs8(D4wf z@MSgQQ%;)n3L^#*z7^PwEka^umY7ndGd#d>p;3RXtZ5jz5igZJ}2?C6~Ze zzgAmE1VTo*(NG zhws%Mm)pvAwN^M@Hjz)hbH1P_xu?Fi%A8?!n;EQc&n=Q5si}B3Qua$o7mwN%M8@y{z_uXa8dChoywC6m#KR?gh6?;=%tr=F!Ny>}F zPCFip`^&w?(-00DK2qYSkA*Su?-UK#fWIfG*qk@fty`PUlgs*fnG>JO`-$Tdtod31 z{E9v2#O-n1kJ;DRFV=g>qwkGwSLXyzvG%jAV%~f>cJ&FrhB4>QWAPrGkt4|k#{}!| zpg$I4f7R~-0sEow`ld&>E7+4DHpVpT@wQZ3t@JWJCWH=A4+mgfq`DQ}L^ z{fb_?C)eDU`0C|z%{NoCMcfjH<;HDzE;W|I7cmZ8je6XS~nRXP`OX z;#+jH-Hfl`t%%d}qH@_*v7WUqz)=~q$LsOb$E{dWuA9`C%4^MK-R$4iXv&w%)c205 zz?=Baa|s96NxBBOEy(fn#w@hI+CLIRd|v~WNx=P|o&)Wb17dH|QVx8Mvhs{oKF713 zvn`j=*cq$MS^T+1=qKfq{Lr_p3Dv9G$YUy}etBGCuiF1pv4~Ceo$yt33Ovk z?kvyzczyJ>@Av%+alPDP<4qEu)Mvao4#Q3U5{J&$pNOMLo=o0(te+PjZqs|M$$C6H zZNHo^oaq&BYTl61bF~E*-FE&yfl9RNDYo5y-VZOvCvFBPaTe@~>KXGGvc1}6P zF196qK9=fA{0OI&`f~p+pMB7#!;5*ls_|SRf zTm3lq&Y9ZrImPL=E3pv2@a#CV?<_|d<-_f39x+~@lfoWq^>g^#<*)S&0C8*pe=CWb zu{zeWSKQ8@_gP-Ka88VW=Lg)ot!zg*P_CRq>dCnBmTKGey)R_QZ((2bC59~PzP;C+ z7)H@mS&d&Yl-Dq|Y1nAn{56RMbNv3kuM1!I-p|AP^nP8tHsRQ7Q0@71P{Sm#C|1Ys zy>ssMnR8dqr*lysXL-(Jaw%&j?R(-`{H(mH}K6P$&HdJ15`?2D4{p^SH$YGIxk2&jeUVZc0 ze+B+lPvu!(@x7lD#-CfMBl^j9%45gnw$*-pTsYTjVTxViFdu5eVzJz1d+N6ux5nnY zRrD>!THx;im-jTjx3NFxx~a*Ja9PC#F5R~FIUe`L-j9_J$C-6qCa+1yow@Wlv#qLr z6^G8bY<+LC={n)I>tm?OX>4lC@s+Vv@n|d_D|&w4=~}eE%HIctAM7;^|6bvth_^Cc z$syw_Tgj`9OARwj6Vc7df@=T@_P z=I_^jStGbE`uW|je;E1qy)&1u>Jwg>d~Z`rwP$AAi?K-;l{|C3~mi^8vPns_h$5JU25c`#fu?(@=1%`(@j z<8$s0=Ou>Z&*ak_^}Lxb=eYh;@3tGlxi{RJ*1NBJt_vQYg#X!Dtta!9uxC3d&H|Uk zQsl^S6@9{O`%^kM8L@|BAJ^Vvu~;q6f;;u?*V~40=2bUV_Bq+>EY2|(UfPTu@tOA# z_1iQS$5p8xyN@jAD)Yu8W221ZhVgnFek^iLYgO!;jTCdKOMP!XPJU&L6n=cpWxdCE z$|J@EqtAIyUDMZj=Hzv>-fMdOBepicw*Zax8TszY*A0&auf}gN6LS%Jx)yp2j8~4O zu4qf_NPLBi$D4eJp4Ld>%f}Rx+EN>WQ7~uw>PO>1Tq#FY|2aP9RqbRyIbIn{xf8i2 zu_zbhqhF66fxj1%p84BzKdz1WZ+#m3b?XW|^q2j+O^(BF9bS&5SgbxQR^gYv)xX-w zcHLHrk6e3Bgm>pm^|kht*Hnilmu#uN;Mii5{?+d`*!P;b*YS4-df|Ib8Jomm@i8X_ zPQ!0w7Qe;hcCDF;`d|PN-Y5dOlZQLt; zpW#aDgmdsCbtrO%Nyhcq{adX4__%B)HXRI$1*1J&XMC&pM?+MvzlY&lN_rp#7b`Sx|jNZ z4c39zhtH!o|7qP)mt1+C^tcv|AAeruou6M5xhBS4aUJ8(n1!3NZk;<~6x+&)%BfBo zmpCLA@z0tvd#-DE=}+vXSaU2XevL)>U>xL=c3cNOG$%Gz_=p$Zc2~OB(Z6Gu-*X&3 z;soW_ZI%1EjHNgvmeQyCNLsFMxbnmCrukR@YF9D1FRf?R5$$N4?pNy_KD=%u zKRFhYlUU4-)o*F5JAPkw&Wmpq>p}m%7xw_RE&BJD{oi@eIFzR{7N1vM6>E;g{cDWQ zh1(L$TAva_wq-Gr+k#7-&pg7X&^I%>jG+DD4mx%LI8W7524Uh+8Q)TZhyuX#TC zOLfe-5>x5RG310LO0d@k@={Ys_QQz0TwIcr<2k#Osf(cZ(A> zhB~sf=JGmc{G_#%ImSHg2yb?5wWDL|FJto@Rqb0I;hX+MM`}opIVM;=mLy}coY(wh zT#TcvQR=8(%8_6mJmet#n|^lBUq1i8;;x0_H|x1Bap|%8NVTFql*49XAtvWVv8rvg z=Q%04ks1?TvTu(?_8MJF=8H0tTXHDz;k=mRUa7vOJoJ?8Z`=obFJQk6|6d&ML9O=1 zmf4Y9Xl|7EWJ_>ZE+lu#wcXzoajqsOo;$BS$0)IA9D*TpC449U&X@XD4t&hH!nxFT zvay=_O7j*E&grKTOEo7}2O6{2SR=6G9YAf3z5@VXhr`Fed5&>R|BcS4?)hG;&Wq%O ze0qIo%}BhEK|S#rq1>wu?W(-mvh!36=GSV*)`DD@wcdPQZMrS33mKDr+;GuopMf!nPOeyWas4Bjrm>eoXAL&y;wDKa0_7 z%rT>{)U4=py_!DG(>_+tF(-cRHEuRpmw9a%kF522pYFqBIehrY;Cr3N>-)gj*WRMF zq4s!OjbGw2OcrCtXLyhWDHH~OK^Ekte3Usb~GpRU}KHl0N@_26=FRd?LW{`4R~DjeaQ#rQclSiWhEv( zHd{Vk?a80ZdapTzoaB)5bvk7(Fvon9_=v%9q&OUx%lUaFj-vKePJNq<)kd-{KGcr- z-?(Q_d@uH=>7Bo<2l>1HKaaDXUlqHr)rPUi-tYb$kMBvViv{u(9#)S*wpW)E$R^I}V{to` z`qKSH>nPd)+nl0RY!%zWci}$B}Re{}@NCk_*d&*A1|eN8vQ( zN_gkJEU*2Xb3U}!`onP=cfXPcThBh0baO3eej!Jh=HxI&+jaXhv5HTR#bUFX(>R)3 zgBgeTu>0nONASy>_!>Uubld~u_xw=%a_zUC$D_`^Kw>T9%XWk(i-Y#6JeIL&tR^G( zRXHcL$9co5IT7roz18Q$Y)ifbr_`dX3*PI>I?0&DSA}1H7ntIgy+HXGdgRf*R=@*o z8Rv2=@idzgt0Borl^5l#zP>aD=2(0f=Z0OmkTIQC9La8_zJSm34qHivxOMF^9*N(4 z8t>c_BsTMtbCTAH=xhAK1G$yPF~wy36r9yo!tNUTJpjb?O~f&qv3YKCZ#AX4lK7kt z?JJm7UpdmatS)JfTsdEeHRZzXV9sL}J2I}^K(@#iai;Z{$CI66KjN5bLt-{;FX_>Ak875G0svi|w^{j@FpA?yEVao~m<3I?r#k^{!!*Ad2{En~*H z<;4A2EXI?_OHP#EDn{o_FpHgRFVE2iV^v#zoc%S;3Fi9;o|( z#?Svfo}UAH9*p1YTQKC<6DG@Ffj#A<#9p=Q_&rX+j6FM#Yh74QiuDRR%n9qx^1;5u zC7;;^x7pBRu_5;J-kh*o-ppnZdy2*575~snvC-})Xam>W^}n_j;$4n7;mhk&{R2nA zjq+eMBCzb`^TK^qjgy8bMg!L-g(Wf|AzH}<2hh3UWG5uPl+YvPB>Ez zr4E|lXAUYka;}Y!wC{0Tnyj;&6X(e5#Pg$el{dw!c{eWAC+oGqm2|{cUe6k*)dF=K z3wq*o9xN7%N8`%5JL%jD(=|VT2GG6WkYUFr9K5bLR{EkHVnNL0-Q?7-%q3o`xK!8V znqY>_O6<-X@#?x!{IppgzsssWl^1-*i_S|e7kC8^aEv_tth)7|_rqbM#*p92{=(RG z%($q;!Z_r5o%mC{rX$<|XI)M#m*k*`Pmhgj8@E_Y-?^~ZlqL z^{l$E!F^4*m3$R(DF@BfAp9FgcAq9T&0orAw&!>|H?$5t*XaLmQFjVtb69Iu>zKU+)rHE{qr9`zz<)bQ`{#b8Q?w zVNx7^${F#{VaJBOzcVCk_w8-MCVl$EZfmuPog2PY$z8_Z5N>vH|)^-k-a ze-o!~r;f@e*SWRKuh)aszjJ7DyIhq=iC=j4I&nE$3pppn`X*Q8H`{U^z0X*ZJ?A{- z5weD@xmfTyN7lbS{~!m-1Mi2fU;J?Fg5QhEywR`t7H;|7iT-^z2-|P7ZP;i31H+-m zj0ndfhodK48pq>%Q^--c==QnsGW=c@b7y&smm{al?Sxxq#51qDA&j~3GURYv9D3xi zFbMz4Y;$}^vB%o&0&4{RyuQS@^QS%IPndA5d?dV{8SxE}gS&6_P<-~2oQ9k(xW zix2pgoaDTyU-xhQ@6ZolQ|lkT@!g-=59@p;d^+x!wI^QVO+G8-*bKkajB{hU!U1?l za~`YAE2pXUUDy2Bco%%u-mpvOu-87{iTM8$;o{rxiZh;m1%HeFGpje$oIHomy{<>E zNA^FJds3~>Jg(R7d@g^d7q;%h|KpnR^s905+(mKRonv+4vG`ubc+s>r$TK)Gj=V;2&e{;}(_G@KxQ^CBm!zZnNK<>Nmut-Y z+nyl#U`{kYimjY;`@SEbm)vVk@P1ljA=c{`ugH0j_3xbgJPyF$66fCs89d~`fPFTc zdG$^4Dy-$1&%G{xKg{>dGB4#`;`#A0f6hX^eBb52Yem02{+Zam_3;-ZA0y8=JMNDE zbJ_=guavb!>_sfR=fJt}Ww~>%7-O1~+(~YfC)#07>U2up$!}pxIOSNgZMD znBRG)UBc+|FA7s0cp@&w|IX7I;rlk^8EaJEz45$g+z0HM%<*NMlf8j5s;Ba-6(8fV z&-u8{sSlf{40I9}v0KjlEDkyNFz{({r1%+YF($DhZpx7Zkx$3WN3zjHZBXvNm=nuu zB`5CFYu03vf5*^FPNdEmgE!hzd$`ZH_n`W@U-P8AkSpiV*K#?}sgFN4%8>_=TEtCQkebyH}X18>&(`xtV3IOSnIOJ%eCx#fb9c1@A9z0akp6>C%g3H za@2FbHZF5ECO-6d8Qy~Y>P(2nfY{JxnSOE30$?tW~}{Bpe8T5ZF&_+Q0GjvN&y&6q3u zXI_uamvS!azvMgdT;!xO=6gHyWHMl}KND|M1z&K0;Ye#cWU$5H1@2m?3T zD94W2ghS~Ozt=bOVthC+skil<=9}>AT%xC}*fCDsX5w7OVT(Dr30S_iCT;*=`vBnh z7sjXYN*!b#fG5R6j9!z*M|DmxYg|?%x-V$`Xl>Qm(|S`*p|c0>&#t_Ck*v#Ql0)W- zxyCiT$fq1Lr#3FKz8)!ObK2`Ub=;=f(mLRnkNbLGMt#`RajiYkH`_^G&TZc9IBuJ} z=e76aq&w!uqfa^|Y_ru?;INJEm!5Os!0N5MPpag^>cr{>yn|xKL)hjlqgYem*T%C)Q^)A3`;VUic$InT`T9Qa%#{|^K?{x1CC8H29NR6DFg zG>uhal>8%>V$5(`ZD^dV8?8&_4m`=Z_By6|KF@e}-E}uSCweGd!@2(adhW+*O(KUr zr)yMm$+~b^-51n`j3f8;I&M#7y@tdu?dYD6WT?+EZ}cT&tRHQ9&blR@qLr~gZ|*Da z#tWy;kY|nk`}V1V0JNZ%wUx+cl5=t+&GJ;C#(_A5E@bxm2-W!|;rd@=v@ z%j@D%BS(cx@Vw>TH$Fsc79+UuW97YwUHEejIBrdNGj7R|*r1%wr+wAVT~F7%$qT=b zH+|;B^;7RTNPe>qk&*mlOleGV^|N^8J&WV^+ivT;>iv`Lna<5soA;&b!rq%88HH z@#|~L_Sn2Ga!x$=m|KM7HSz!e_(#G1AJBWOUXQ-cr3SpdQmqxZ6(?}(o~73z)Rm9B zE`5|^9LMj>%UY1NgPdspMOs@tU;2aUxvn#p<+`+H`b$3eNnH5$$B~~Mrak<0yaCUp zu37j%oQ(SlH2K?{*W&!9W#5ap;{Wp9`NAs5e-an-IU=qB<$jTTv7V$h3crGl_-w3{ zv16GtJ>M6SP0{;VoQCfYjk(}r`F*->xW2Ncz;lI1U#H;D^CDc5PrrA|p9j3=WGz!Z z*&^rOXiM^5iYizxj+u_f0JfEzcNn$-m{V`=8S(=buym&vt5nz8edz4ADXN~JC^m&v2qQ55vhCSJ&WS07hM+ldnkD46F2YQ zKlE9D{m=>Td^+L1U1z*^>e8ll1ur?3c<>CE^OU1J-y`CkAMVk&#yi0M;}Ii{k0+gX zQJ8Sc9f5nrJYM^Je@JWki#q(d)+G+FHPPdo#!>D8YLl`4EZ+U|Ym3F0>rD1ry$)f{_g<+#yC;+Sl>LOy#1A;+@HlbW%s7A5N45^j zSa57HOCGp)@!Xr8>l(LQ6*97Ch`z2X-bdVsXK;M~I_lhs;jm+li+k<+ov_W8TS*Rj zuCuo04|&lXN-bF&T8mO!h~IOFUf#KGj`s|B&L8nN2;ymzu9Wu-^r!n5G5bEFdw01< zSLT41xVg9Ny6Yg>M_D_SrZwNRzI0F3ajqj@E6hX6iH^&<(lL`0A8Aj4zh{xlb^g-p zA2sn6*u4Ya4X1muIc z;QC?hmTPdC$4fT2K3v;goZUgE{jU5?4)F-8FA{ke9f=JD6vR978B!3`x#^8cyrC>3NCn0a?!1`!jPedhF+4tgl`(xqXukCrga*Zfnj-@?7 z;?@{7K9|eA?{%`ij3=z~uo3S0I^lnxfKOtQI(KYRUj30G6u$F957C8IksfW zi&tY{?MZxw(=q$rzkTz%@s{#hds?3hQLh)=G(GOM&)|s92&oP&mIANVobj!( zq#DD$J^mN@eg_^LFTQnlT=br<`6@1Aq0e$(Rt%0&_ph8Q;=CWR^8TC8a3)Th6|zm& z)&AI1&yboxUaY<(FP5(&CsKoXy)ci=OEWpi`QyCp2f~Z2ud*i99&Po&z20}g!=#z_ zge4#TME3%#MUUNck@AEXPrKv_aP7FHF6=s~almi+4h49%zDgaed(|oOB63z=IJPzC zb=R;8argL3uYX|u8tUZc5}Vge#!$C5Y+X6evc8xr<2uI+?2O&wr#!z)H(?6bK5K{9 z#au&rZr4i8@g8&({$6Iko;^}+Bzv+xflGbrJaM6=SPGoJZqqs!J;s%CCN{LjWM8)R zr1m^U`s4AG2OhV2@!Hln=Dz$k^4&)6M|#ymd2I=RtNs}W!r=)#AFzP<1d@v_G7p@xcKM8ik-|fC0&+B;~ z%>7ES(-(30dA=XB*eW@4xi2J^1#kT@@^72)`={YEs0)s<@w#AN_{Dow2s{uWn>?-Sae+Sr1dQLye<uey%7re*C2cUEIIrq|abul$NWZS!-w@2%|X8@cCRe8y#0#SQR1 zKkCpI?fEg+xYvNLcjiFyEaUE5n^Dm0GJoJREH|Ie2LtlHkFWBD5x|7=DUSV>! zCV9Uxb;VPW->=`GOBdzHV&P|_O?vkV$6;;W{=^HBvc9(nUbE@gH5cv!>l{9GE@NEs z;ph0?%){}7GtY}V?X+{~hIgL_8z_G?*vHJ6i|PuebW zLQYdHDZetW^{F*!^HvX<1D#L#U`{MAhMPX;s7UGE=#p6)N}*Z=KLJRkYJd(|ap-i#mdo4L$# ziW?j;2PqcEpmx+o)*<(TSz?iWKw=SF>6roLO-J^N!FVo5+;UB0O%(gsp?HSXJZ8Zq z@sKZmAAO@9-T6IrdEUlrGVh6*Z>yKwEUj4(_T;r(>ciuvUBk>cw8mte6r3TJYE8Ot!O44uO8vpk zk755W{yV_1+ZuKklvs$FoLOJQhVjIitr1%rI!+!$x1vKk8~5rN=DqQOuKyok{jzb6~yDD5q}zR7XUzx!hEIq+lcZQr&1z~>;Vd9irva3`OIyx8Y? zEu1>>QsB233_oL8jfJ zS?rfM=UBNf)Sut~L##J8mlYejPe~mY`pxY(_v5>ZcRcxGJmtbmaZkTf=!)kx{H{D> zXD-uvOy}UGTmuqkE%5)J%AV#Np?Ckdc3OSWuJP|Vks8YRmU-vS=8}$Ku$r+vSM33_ zttHPV@oFrozT{l7czyNl+$mgr?_zMB_eAI2_s~0Ed^7gxgx@6)d|DqFulex!q=%1s z*~)yvScwHOe1W$08o2(~fOjeQ%XLe9mHo*ur5t;lj+HTKT;Q(2O+MA`MR=zqYlheU zq8>QLJ%IQ7B`=bLlsDRuW*Eh;kI{!7E4~~Db(HHuFWE93kEIy*_*1+#XPn%)1m98e zID9SP{Lbg!z_ross9lZ2*12HudFQF%Fx5dVY`5)p`2EF80}IHbeFR$DZP`SjJy*O*IzSOE|?|lQ;=4+=K9S%^4#yEbU(25ZTm!3mU`M~ zYu2-`#FNjP$ThG25~jM?b=-2|e3%U9G}fZmX8_kOyuXUoFwK2MpOYqjJ-0*= zj_^f}#GmnE7~F5A?p)S!vaiTDjl=zGoSJvD&HJCr=iHlktMHE+Sn|PV{9RDuOF2`2 zw#Lo2tP|kiJrw34E4^Y%aU|^7mdgJM@_+Ha0RkNDVE0kQ;&>Q`#p1k3U1FYCIF@2E z9l@wNzK)#p%V*tV*C?!a=HD7?_U8M)7ws8RW4)f}+x8g1oIwdBq@yeWM%O)F|9oS-H(uzzeQkgB z)|_k2s@nTIvVHqWj8UViR;^lj&Aq?x934EL)ED}5JZMWRCxwUpHF6?9=$FOUeYy7T z`|am|m43zzKRftu>3jR{-^z>KZPgd!m)|K}M;+UG4V`&UKg@>eIh)T~eMfrOoAX7U z@Ws}}h=n74><~ zyKX-FX@~j@bYHjew#JGN?RO`wKa>3~PO&Z9p!$m#qc%_@w6k&6@&!HTcffDIvyp8< zpTI8KW-&YK*B1)&JSTpyArCT2{EA%0wdSW?Rg8J&c4irknl)bbeE4Gg4Ze`)`EwhN zIr^A1+J62)Jd+=Rtt0Jo&OSTu1Y6w~*c~?XR$nF8@-JrI^Sjv|m-Bzv{^y&z|4rsr zHs-2Mi2-Iqts8MtFNw}NsA~eTWelqv+5Vc_?~dMg3=kGzzVW_?yr!tHj(^jC=|pT{ zU$zx?AzoEpwZ}}mqkq7o+Vy9X-=#d-?Q-N7`9rR+{rtWG_PYzjvVv#8wBbGL(@yp` zY})s*=U}bHn(tQd1vbF%mhbe_?ZfY@;dg#MGOQf^rR(%}4_jLgaqyYyYw)3lvP+!W4>x$YiI&vXwbzkBgz;bZG z6<536ZCt5x?=`5L=X#*N@S}PL!S`&-|7o_Ivg!N3&o&n8`N}QqD{+#2$hkd-@;iWP zH{t>taWDM#__^J$Oq{`IK6+%#`aj3|&-Uy$yxesc)|=kj>R7W!Bk#<;KeO5Y;3Kc= z_2*>6Za-%Z%d_6=AZ)DjpnSWY@WpfxXP_hM0q1Yb__XSOz1RHL^z5+F_WP{%?+h|O zV@92%g)h|W&j`jj`+}YqkLknre-96OH+{VB&!!K33SHLYHTGB!`RD!U{ovy}7SD=( z8FvFS8hhTe_pvn_?B5*>NWh%RE{`>O68(kvMXaMvSQqLS_dma1b+)sAS21X&{q8}w z4K_QDJ^#^o%UvUvl1)0}A39lIw87(A<-}{k^4zdF+gbBBa{@o;6XZsn$PdVJf87`5 z2C?ycc&nOVFQQT9dU@7fNhG~7V-Je`8{9o3HvShGruwG{*`?{*6MaF`yUwaXZ!y~zSYQA zxf}N*H_#n02w&v+V`TlfAAgm;H%%?)BcuU z!7$XY#-HuG`iJRHZ4>W>4(Se^>~-p+{G!8tE}kBL`vb;kbjC~Rspo>F#g6*Cxt1)= z%{w8BeQB6snDg-KZ>J*0CfrZ2n~1ah`yP8{62FQ~z@HH-zAW!d zkrT>6??Sfgjl7o_fyd7<#~N{#owN%z;AhH7^Bnv!c-Qe)a>I}EEDn4D#y>Y=toeh! zH-CVqwuXNrSN$pBcZ$RATz}a{=%Y==*2Y=(#cPi?fZuER|2Da2Z|dj2>DbZ6V%8<& z$C$ClVH^6$;|4oqzwC<{Ji+I&_If$h{?6N&_2~b9W$%y8^Pkf%fAwp}_$=RhP4Sm5 z%5%NUMa0Y3$VtSL{K_ZapFQ`Ghd9!gYR!9)OPc&~|7u#fZ@&{_zgHs+(+oVo`iygb z6?)tc=1zL+xKStcx_!_E+w6PA^yIrkELM=`Sc%#3#xb*AxAoU&-rIhsTSk%TXX?o| zp4i@S;L$hVP5AeCj63t}F=IUAnHll&UO_%E;2(Y6qI2=JH&~w(y*2jWXN`&h`FXd> z2l4|sRegC*oQHZ8%eY(X76z!U+Pb)RzZy8>I(x6L-@qT{#$!huus!Buk3H7?hb^!> zYEF99XHNG%Q7uH=JdSbCV}clL@&6d#uQqxAZ`o%jv*`w-Rx{GDx8M^S6E2l_m=<=wNY zO`h4gY9oB|T!2nqx4ZZmzPen>$63~A>r1Y?!TKLqRtB(b+*kiK9kT!ak?YDA$f7*> z<#DmSV^@5}Kl-SnI#0hpERFqAJ>HCy`W$nOpT2kODHw~pkR!bH^CF{7FMG+r{aUv4 zJ}~f&_!*)5#9E~<*)OjRd`=YpMo#d1!sEWO??}zZw(PTheSyU&YA9ypO*t#^hhEQz zpW}}0>wH%E;xm(SdaW(*O!K3@H``J=cMT)XiP54itf2jztyUPrR2cRyTz)G%bg zfBLq?f4B8-HhKTs|5wanxm>m(t{NVrDhE|;a~x|wX=};F6K}uQ8EZ8&Zas!QCvC=l z;JC>Ne{aKMBOhayJYo@I6|ujps+icYnGEU6N%p*Yv}Fj%R;c1h_b6?Kjb9wXD)_V{>1FhPcI< z#ZP*>`jlst4EC|)i{9Rc{M_@so$Id8qgH*DZh7d@w3q$+EY$&H4PW7B#8~m)&i<{# zTeIiWU*VYJ)4!%C-}#U|)_YDO_lTup>pK4Cdz?!j>NI+q{8ioZZY2vcf+y=Wo_s&n zp8rxWs*`Q4cHdvP)ch%ZFAToho)LQft9}%lqN~{E@q!Q8K8uHZv6=qThW}x5Hu?Vp zz;x|ya#za65(BqUd-ut01g}Q+iSK>&oh>@g4iu~|7?0)3fJ2vF_S2FE)z(%cDmoZE|2;YA9nEmc@ zr33mRpI+Oh6aNYMg1 z$a;!*qdtGjpGANgT`7+{0{H<+@9s$i6)HX1m!{ z;#1!>pDng8jvQk(jptl*Jo*06^1U5%;5C9f{@m1Fv(vLJ#)sG_KD<}SY5G#z6>HBA z>i{yl^RGK)#_xBsSJ*G8VPF~j+F(ETy)tB>wsa@z%>LfNuP(l%^TEO;Xnmg{w_(osN2Dfp8{rj2i{PR=0H@fO8%Vm8DGn9YTgJ#h^Cw|xK z#($@(q6c31a{%|6GD0u=a|`}V^OcFyn7{lEM!ejR9=4xrxem`ibV!!Z9uK!o{y2~L zq`%%9?SI<1Vw3m(mVNd#8`ro!-F@aU0=?K;#f^1ub`tY4Z}dR_p(EHMsP%E{{$vBQ z{e76BBjayBMU0qtlkfR=Y=Az|0~gxzv(4;%#k)P%cEgXBPTCqgcILi-2efkc; zuxHs*(WmIUFE^y)?e7+M+p_w=3pXLo=bCP(i>!V=LPSJ21K%N_(j z?!{g9eAS1P$Gz?ahJ;S(a~Z%d#{qx-+uHUz!`d+d`Eok-1#a-1w&`p`8vo_Lrssxz zguh2p#Z7VS(>eW|b3+GY!>`b1>tpOY&^p%5*efS>J|c&uE}~}I^w>9Yj#@Q(qrOZw zc_QE9&+;Khwa}-7_y0lD=h6rF!+eR@JUH-muZ3D(++{xprg^Uwf84p&K!3;8MlUvufYK2+6@Y*7aU8gC24&yWz%;_a}ToUqo-3Py9V5=F9Kw zZ+}DL!IxjPo#v;UC<;$HU=NDrarTC^t9k_#JD?ojM+4*ZwKp@zgU3zxU_0;5qog_rI6Me`VhRL3XyyWZ3?k3omNn z=Rc7X%USeO(NpJ~>pHKtW{#r&RS$o!9~&}?P3Zq&iTrYToU0f2iAI7g+W0Cim*i(Eoc|p4K@g$~K!#|Mm?(+!oL>-R$08apYWL1e@=) zI$+pwHR{k z{+&-fn|<*B(EVF-4YX64+%3E%;?+NTPzWGVqiy3R-pkspi-g|kx^#q3Z zHO{JD&VA^-<*daA9qjj@xB86Okk8e>MW)*;8_L|U3$|o?AfwiWHE7?Y;Y*hL=OX*w z1To%qo2@#dSASXVR&uVt>z-Wm$YsuFofGr}b6Rr3I;iuEwfHb=EuOsZuCGSVW-W9&f1hhNb^n{4`jQDbx~$PV~5Ao%)^cK)`_*!iMpX0~&i>b1gr5lRQOc?(w)QJ(Me&&BPMS(W6<9XcaHc zvVE03q~k2Gsg|L$;*T)Hb>q&!aTEFf(Aeg`{O8U)bLYF9ut)bE9QbOEzvWOl$@Nj^ zO>51lNvxSqtXadYXT0hlYAblMeC60>Mo&U!RcESE@E(5Xp&h=<1lD=zv=tG&pzhUmx=-A+E>L^_=q#|6dRj9DAqQg zarQY@Q_=V9zOvlgi2X^YoLuRNm^{peTWn9olPem6ZZV4$6$f4p~v4?gP-dIERQOHM0$(A2Mz;r!#s z>*obO<32Gp&gy7ah)%%S(~ao%(33ydWX66yi;DOi>NPuUS+J&^EdAO zo%$-h4Bm4ty13(cj5sl;wzm0%6HX{LMGdy`rr}G}9I-~e*x%x3{n?)JATDfS{om(? zP2K;dXJ6C(74*x7h({gIjD_TpU$7m08FtdAF7eK>o$cf#=+#__dUj~S?_%ty-UB? zFQ{YSn*5Bt_~19!Uq%1p+=kBUwRmyXK94ivft`(`PUjoP*v7h&=h$I%&}?9@z9p7` z7H65K;;m^}mh8eTd75!I*0Pg+1TXO?ufgj*Gji#2`+czJ-ztB~3Fbyk>RC|Lfb%pk z!0%9A)V9k}|990F^(ptd7rIr8sP{Xbel~I#ILiL_Snj#w_W3~KBc zfxOUB%Zt9|S>t~2gIE02Tgi~!S#D)Z=mVDRZU1(@t|6_F`^bAf&-H~|M(#4dX#UJB zcUyeIebuSz*ydcjp!x7R1MjwmQ-f!J!hHC9Pr=o7ugpy+FtR|ZuO+iILCN=(3@7{UwR*`v)RUY8{^il zOMCCKv&rYX8Tc(eT*ouq?<*dXXLbs|9XF68=n6Zrmd#v;9lg=%v-mNKH}Ys{S1HLgZBe`^F3#r=YDV=zvJ}w8htOBjWd%`;|S|tPf zHa5FYVXN_CFTO03G{-h#nDw}L9LGX#^!Egu69>v2_dBq_9u*NJqsp$sn1Up&Y7=wKhr@sXw;!u z_oG*9UAkkl%X1xWi{Hz+p)TkHZ>ifQK(CjXA8@9Qwn$P7m=`I%-bK03_dMz*) zrM~dFp0%bNVMc95eKCh|rg~(KjizTj>I-M0M_yfS93pY!JLA?tfr+IQNXW5hmU zf*9NQKxa=NRo^|Nk25 zt^B}VHgOKWYC7*Yow5*T{96(9|HK#*)?tmjuTL)yAJM_T12TEUXUVde?@?Kg?8rLH zXT@w2&WtbHP{l9H3EQN{=#|fYS2FT!e{=7?7hYulIgSOEu@B)#4ECJPD!j{de1A~! zM-2Ra#oxt)9<_Uv)4pHP?L5l|Z_QurGmpu{x@#YD90LMh!Y0Qn*bu!QbuT%@B+{HG z)1fEJ#hG*$Ik!IaaCd!0K6Cy`ej9m1ovEjl+vs7*%=H*~?X|vmw~|G@QAetWH>XXv zm_^?Q<{=(qR8^(q-&9 z#oTqeomqdClXC98N?Ylp_J41NU2zWo8G~xpSzDg*J@MvSo&D_p6H(`x@5*(akZ;=~ zkL(rgMbkd!%nSZ%1ik&-$+~M^t&ctK*fjCW_zXh~2>dkLpBns*ddBUmZH{%@UiEhE zi*&VVX768ZY?l5vxqSX3ChDcCkLg!?->_59(TFeP*tuh_(JsIKdVl_}TW2!{YQ$#S z-}cZ$&3AvuP`oO64clD4_kLgd{SxB~ojHGXY_m_{pLlBjKQAAI_0;D}>Q_6xd9Z^oO-?o6*~VUz9)C-i2aT{`u$WjI+VUeMJiVOO0Kw=3)p8@kUcJ1gJt{J(WmKmW~!18ng5uXr|kCv0(B<8whp16};i z<9o&oakGK{f9JSWtE_+4+6W8?Ecm!*S=xQOZQFg@j%C)dgY~=gP>1Q)Oj~MuLcjR) zd3^^wxD#WaU3RkXEZ-nD!B*hZ1hZ9mCHuKY{lkwquX%7M->>*`uT;GN8u=k7le0=ADKmL(e(ruIbM7v-+?Bjq> zzNX)Cme=fyxGP`451*L#3fJVnX7af%=(zX!=hAN5ZjFYQ}fS}q!KD)FfO(z;#e zwa>wW_kut4@821}c1_L;Y!H@V7S=UvrG4t>(({Ue-_c$84_@c<9x60qOP1$%a36l- z-v&jV|89Nd+LVtzH}5DeQQ!7H;W%Kt?6q>h6KlW&rQ};&K8uGhx zQq^U*e$_Q*d>^^#`+v(m-v5f5dPaNAZo6YaZKu|LuV97vao^U*A9rkLvgLGA^bY1p z>(+2T>Wjtx4zMvF z?Ph<_#Y2CLMtVL&orBh#g?-@FS)rjHARl*pj9a}bU3>fQ)6U=dcDEnyi+ZEPy{2az zuih0}F+(gd?B{RxUBP(A0QREW&YCx0)Yrs7?rWO$Y=g&r(H(#Odt60xx$?Cs%g?~S zE{$KkI$hDPU)sU`9X0BRLq4{A2@G&P@Q6>d^LORHeJ|=O>$C49(%4liiGu|L(fjaw zT{`sOgG&9?{oS??`;GNU#Y_EebGE_m{nf^8oBI9V{vFN%Cik1|+&1`;u?+u4C&w_( zjn<5x+g;nqJH6c&luDktrR)zyw9!Q+_b{788J8-S5sO?y?K;D~JDzyl zG1>eoIA^w*olm{}7VC&|D!F#A&?DYix9p6WwpDq74VmULk>9@>ZIk!E$!$LWQA=&R zD%)U}Y~$>_x&MhH;*lJi!F%c%r*$S`A{kUq_y ziSR>tu41TovL+CVuKL2=DxO$>|G)z}6V@Q7ZU_AhKY&xm9Q~uNJGckE)Dia7x~L;~ za((KK&H4Esca0x;#P|Q3y#LL{1I;!*|836wBHLlN=`D3c`-*>l);ISo{K4I@8T1_Q z?d^M~H^z^#zFNgGX|2r|*xP(>|EI|xx~qHM|BRcib{aHcTzYTj^z_!`iRq=`L(>B< zJeRKQcS8qtx&yyIU_KmA5gW+y9NW5YuGycS__NNq6+P@l`e3#>#<`!~qoPGG+jj^~ zuV?Vo`+D6xn+!amo|?zjq&scG>Xqq|YpzyJdVNJrM(*hoYAx!>XRi^&k-oDoN-RCT zlHQeaNwM`y!@Qt%z5E=vZ>};M)Yt0!Rq4%1kGK){#Rr=Bbl%%69-Chr_JQ^Ow>{tV#c~248Q+|r&Q8X2{-wW3 zuYNo-O=c{CKcSC0fIGI&KCVtvzFd<=ESjHw|L}w9`v)Dse{Z9ln2r)_<<91lPd%kG zaSih1IOcQ6ZO}ZH!iSpQF|oeyYw=m0J%<=C|GbE`d)A5LDC>m{MGo`G zv#>3nX;0l@XWTpNkb~0uGpDkL9dCd=(2bsU4C1_gRl57h$I{OB|N6e-#_KD53Lf#} z4)LI(*Jr>sjF3$|)jsPq+WWJnrQLVf-u;rF?hEUav9cz>B20YiZ*feGW)LY@NBQ*9&uz&(5uhA|F%8*gek@5 zCayq7@Syi&WFFgNjq&l4h4#BP^p)|pSb5wm&dMcdPrdm%<>($|0dFcj^%>VVtIml} z+H1C+mMrjIa`lxFgDpB2UVdqsuyPqO#qrF2aXsbU$VKz)ccA=dgv>SSwdDutRsChG z3qOP3Ys~L=Zn^Is>Mpnke;wl{tm;Vz@3)WlrD_s0o`o(m*Clk5hPw3OXXJMpc|tF9 z4;grVNtXMA_y0}a|EA+Wvui_dHuU0)_a*!Y`vce9w?Yg5%R6hxA|~K->wV8Wk*2Qg zu^KTPsK>hHX~Ocw_WuRnpEc-q;XPQG>V3%^RxyiMWz1o$5kJ#)*|k@7Ca+m$7z3;U z?zqk=u@~*FiKEl^4%jdI0v&ff{&2n{KH3WzxF4g)M*ra~FfC@CO9pU+cG&$J@48*N zEK(lo>mcfU3=5zLa_A6)} zYxsimYKGjscG@9*{MjeO0N}thjCDQSWBtlV`~O1V`zpzn{87Z=||x34)$~X zyPtfd1Nl=SJLKmXbn0E69(e94yziU5h(p-?$V<<;jd6Dh?+8O66IkMMYFu!AIIGTS zgVQL-?hgBOCO*f)k6wMj-_0DoWPZpgcmv!Kra%X9MKs}x>u%9u4{eqm*sn;pJ@5zQ zi#8}s!o5H5wJ#=5G6aQS<*?s$M)6;`q zb>8aURO@{V#{>8O#yfw&d#cN*FMOvkc*^)IJu-SQS3m zI(&+{mF@Dy&nqmce3y*#F8Ec)dw!Yqe;HF_ecf&SGG0r5;4Ad^l7on!`Lf$~+ot}5 zU$+`@xtP6Aiyq+2~1B@znDcAbhX@Otl>0T3Kc-PJX zM}&75UwK)hW)&O6Z!h~@;V=94mB`I*i`tZ)#tLuXcu33x!0d! zP}Ski))yn@&o&*vjnE~1c@Hcp`igBq$6ejWo#=befM54M{g~%F@<2=^CiU%Hb4x$_ z%m}^C%Q@ufoeH!EU+kao{iyQC9+EuF(Vy*l(*BM|9_YeRVryW}Nk2akvQc}D+GM?l z{8~rQfdP7s=l*A|*+1;1j2lH(`MH|$;`e_x+xvgmOrP}bQQN2Z=p76EHt1J50}Z}p zY-rR|$kRE`o*VpHn!a`^$26PkF=gfAblfpNf{xzgMEsj)uxr0Pc1v%L9g(K3Y04}8 zYjpcOFmm3^bnMYb!jH(Q$Gi6DPti4O?%kC#VxUn z=TQC3ppHx@@5!h9+%cxME%X3;?(hE;Y~eu76!~07FLc+VV%XxZd!?%e?2gHEyb4{L z<^A7kkMIBCSFd)2-OcYO;lIa#c5ckKnhu}1uW-Y9wKI0{C)ST&7zSXVXTPukO!y)V zn>{)0ZGTI?DJSbLbhpR;R@XtDp?1gbhb+T^=^Bu0>(@tpnD#f__+BCRUtoIJ$ls%R zYure`3%h_P7_`kX%k6E+2XF9jhVB*(a_)ZI|LvpnWgpFs)6O{6?pW{WF7(DAJ>;<; zbym~jF7$iPx=m%RzO3oX(!k;GN53(~^}2J=zI%73u10Qj7xU6yF$Fv|)@WVkat|~N zd)I2`6S-;FfbWIgoieUvLv4?*2TvNE_P5`SKuqgiMK9(!+ZoU8_a5+j6r&c+HMx#s zZhyhp;h)Y(_$;Ek4LQ*Vc7}8B6Q_A?)$7oi<etDqdwfHF7zt&*c>t?t6&4L2zs5z`PAdUA=vDA zM8AL?S*Fw7uE?Kkvs`Us-=z*3J3REeOxU79tkYgPU`GtMyScCL4zTBTBM0rev7F!; zg8!c|-Ww%%WApG&rllWz?_lrozf+I+yzt71_j{4&STfao`Pt7-sQbEUXUHZdAYN^| zd)tREeD?UABD2USw9WMS-}K}6f2{xUJ!te++g`_@VvF%^TWqvmuY4A7Hw}Ds40D{e zweM{oeC`Qi0B}G9{ZCvl+Ksp0ocXhzm5!57IjJ*o**vR##|+9~|JU*sr|WOKvFT^2 zizeM-u(kF7sQJ^=jIcv8^UNL$%%a`sQ|SA`?x0WI7SRemEJ>4BEK2xH-H2Vp{g|VU zNHf=YueuDE>%L(P*|iSedq0I+`oY4EhqfCEN*>h+frazdQ0VvM@J<(}KK{w$OD z5jKR3;%h~>GhwE1En|#?(G%CC!eYO2vh%--hS&E6t#r?C+IioXmISZrneKjDi{ianJ)$=%pI9X4lMfC)iC^dWENpmh^2f>Exg2*8o6y~elWNpv&@Z|2($2Kih;`shp5YttY)P89 zbbdPNq@UC^_cz?d_|4_Nv7QfJ@Ylm+z4p-uX$SlNI+^e61I7fl)dM#5+9%!l&G+bs z`bfP3pL4$e8)}`KrT@3JpZ}T84b&ArRq^)TZ{@-6@G2zeuW?Qxk_;^emS;h z9g3sH@38M3lqN0xB+Xp&xsA`Pjxm3p=Sj;xNk<*=gB*L$-$k7nt3NAzYmMIqWni{3 z?(bv#s?qqzB{>RK4sMM(7%ui`=E;hwo+f5k;nQr?03G{8{Z=@ zJn6Y^#T+_c8~LuuY~n@H1sOVnZtd%}&f`otl44I4##B?)~(q$J=w`Gh(3IRQQ31qZiFe_+Ef@0$r>X*DqSO&*G$a z7MyXQ-m3c29q>_Bd=4g$-6^tg&v|sm&NgfRQxEKw9k5%U(+!8hx4-e+Yr?T1e2sgc zkuIMY2gn4yp5vbD^}He`XPOY@cmH?a)Tj(I-`X5fhxhzC&M}pR%%y@RiSf_t#~U{#WDO^gsWz&;O=l zL-xnxZ2f|shyCSQU-Q^`i2?m*kE74fn|=>;_|1W}?}-Dv<7dnx^u+tZ?;p6MGi%MF zgfX)+9@xQk?kG8+-k$ZEk zD_?jfop8djW?v=K`WbuR?F^GC+~sW8knxCTH8!B{{T+{3VRG=E#{RcBpjNV8?Y)+W z{dzw@O)`JRkDea+$t1@d*qE^ix?J~k!+=Y#y0kNV_5?rAc;r07(yRJiLp@&eQK!(a zXSV5Ot+;;TQe8)W&ujx9Yg_#4Czqu=;`1JHpClt{ax?XR-viBt4UD(NP&zRi9W`L~ z$LA27GY6P`5Wf@)KPTSt{RJ|71O_q|ohxTXN5I(hOr1L$- zc&iy}@J?AaHyyO^Uai{b)9H76zb@>PY~lj2p`{=F2Ogc*{95}g+;SU=9ATMc+uua^ zVvhY?n*%I=z-6ypd=7qf*juIx{a^I?y2b>Z*K>0%{B&7T+qnx4N!|H?i-s{)6LKkftf zIUnukEdBP}z#sTm`N~+DK7ZGUT;d$h!68#Wvff#cX0O3mm}aB@Y?So^o|A`s;8~Df z9Wuy%|8E=Ajrrj<#GILJkj+u@o8MKO?)gQX?Y8~4of#|VS-rbn=*gHAw!j|HU>ofc zujZg<8KT=fV&<51$|)x%{J*jH-(%O#t#{qhk-UbU>P~$=6ZHBFxIlZVSv;K4_wI}1 zspCMQY47v+&t{}+`d!ue*-wt^oOk}&owrB7Z})>I>+yZT62k@J-9lnS=GVOnH*}`E zQ|$Ylc>a4%y6Ou(BOF&AQCrBF?QNiz_Stpk&bt$aW&Oe@>1)oyPRGDEKYri7`~0rg zJ^C(2sSWSRptHVk=FiFQ`l3xOKcXh%*-9KJJ)|1VbJ$>i|HJfbp8mJ@f5s2J5wWCy zT-QFaH@b6^? zXs>+uI`Zf7ls`T9C(lpmfA2A?H{>&(FZlm(zB_J6 z*anP(oxmo;B@FF>OME9=m_C{_KJB;ru7M{}kE{jhs_MjbdB0fQ)VgVV)EDZgH(#vw z{5dE&tWlF+^yOz2?wg#=)c^keFMFsT`jiLQQm}~9uDZ2PSNT)&NDyH(AROlo^zkucMQCTe#QVV?!>^pO>4*%pU=V(mu0lT8py*L zM@1`og4fQ3W4harDZ3yGywxmOqETn?)cPHBn);^o%(b8YtJgMKU!~?EkLY>jxvoiI zz&rLikiKO6L5zR}G1In%Y4R7d(+}-u8&yquZ9s=c)GGH;OVZ2!SA98OEvvgHqW>i; zbf8YX?;%5a-OtU}|9t+JK4)*Z&eHp_kNCnL_es9`d8Q#Q5gXz{>@yZtXFSh3Ydxj| zeAnD~MQ6^MdDiC}A7kfD$j^Pr<{a{{)}Rl{6`mI@U*bNW|LFfYtBc;*T%$kdHHO>f zSOh$(@AzJ{z$@@*gnY>MK8HT=N8j6wy|52+z6-kK#H_u`in}_iWyRgzu!;J*&e5-~ zFVEITbF7DGYt3)8hFsih-xV!*r}xZB`1yy`eOFG@ADkn1ktg<9b{}N z&Nf^Bo9qqnL-x}@&w=c%dpXk(U)`zKqG{Hd&Ftrz4w}wI*Nn4+_Sv&De*OfjuTRt5 zHR#1^RQtKskM}-$ca|@i<`?3a;}6-)nb{5=j<)YkPkZ9|zw#67wa}><{YpDxZs3yf z8sBW3i{4nHx8sa*AR`9&v94#Wd*N{`@;ktztS{EYlbUWa$QL?@VW8ErrLVf@veEa= z`?9mv<2_z%@-tvr@e4e{GvOGpVtUVPfBr{ZDTmBi&{P}sEIyla{&Bv&{rmAJ2R!Mz z0-K~aV`R`r%ov5gFUKj$hZxDzrmo=^tvLRK9o+uPsjOG76ONpn|!@=uLVtgr5U z^sZue(N}1pOTJ{AD*Ze&yY1PLX0No*9;UxNWE1H1TKF{AdLDG|^@3fb2aW*Gu-Bc& zjI-9c@UNBu`J~${U63Q*#u_|vmd}Hhb+i9v6Z$yTMt41;m3|fuP`~&wR~S|*k=%KB! z1^d9bjDbOiO^?6$Sg8TkNRi!Joo8BCeW{0voZkI_eDT=K`YbvXkInM)KhxV=|HIDg zS69EnR@p1tokstFXS`F-8^6<(Wp{A@zDMuue8QfdojzG*V|6{y>o!QAlRgz6d3^uW zi;pBc1G@ZxO|nIEZ+6-leug-E#Z1>l9ncxNslQt5TyxDQ+D2wzm+;GHmy7k_QE%FJ z(!01HmJ|H{3Ev$4I&E@10dK*_jjiXd6888!XxM9M?_G8@xqPkt*}Y>)t-fC zq#OVBw_ME{j2dtD0rfTP=X7W5?|}qi26a8Nfcj0~~-{n}Izw{weZDU6^gu*hQY8x6gXx18wqWlhFUA1}!hG`fB!CGnXBF-~YytSIv$l zR=NJbNbB#%o_i?Guy?AOug0!9=I<$|{;U}v*CpHPoVZObTN<&&Sugb!G(U^`+f;JG zclE{QdH&=5f6Kn#z?NDc?SRc@57ybPpwYj$)8K8%sp7ymRXS3AkNQo|F-QHNGpA>& z)$~k`c{bN$_OdDI_+x%Ztbg>Gd+iMUZ@e18&-+Gu#(aAKWe|SJ>etXA7;ZA z!xY#cnGI~p_#@nD%^8ncbYfIRYvJ8I>*e!)b$ZJ23j0+vXPT*-;cPZwNxmdUL!4QpW>@g?>_>*5tp+{^E68^cHN#hr=s$MX5#$Vucv z^?`Wgm{>klU-tV`>4UL@%~$gmx`;iPV@-SKq~G1$k3OG%#z~#Ina}a7#;mEIPqx1W zyN~)^J;+{d)d_P%yLzcF*(dv%Kk@bMi{lCW_H&oNxt{;t{~H?iOE=<+wcD$j$+2J_ zU?cc6>bpV1c^+{dG8=UKynWp4;c5Qr8EM|C89rkj`+2LTr*qCZh54sW`Q_0A?5l&BG8b zRcq1md7R)g7OC54;+@KP!!yHG;qY#~i>W<)xh~ zv%*3yr@uWgu}ZoBgaubq&6#nrzttbiRA-f3_1kn!*0-$6GpnDLkX zuurte5&I$vKTY-S^1v zi(J^$%W$kKxaK_d-Z98=12TXQu%Xa}d&DWDkC^;E`@fMBug6>?s3X-5_)a+P$j+37 zV<|u5Y2cr9LH>0&Ugou9cPzi&qbBRYC+-UDg6+Tp*y&h?{bT*_Pg~pXBq&DI4I2=v zx{s?^);wCX{^OqNtKAdXZw#8 zG-ux>%jf()f%6;sUDjE!dTPR$kFm<<1vX>!fB9kXD~Gy^S|+XEEx#ppEC6;0Lm&fd zj;U$J=VQ|;zc`-t58F882j+;oe8Im(nKFNr*%O!~S<<0B=H7TjtNGhB?tv+?+wtYh zU!LSOT5OW<+0I{{aiZPR-M}TCt zoz`(ioV9hvTiVmqtuta`@M;Yl)c9cZAoN=M{Wtqd`@&Ax`|Dqy-syTjBgQz^0h2ym zIXzu`>ABuR;j{dNulP4jgFbwTHW0&VJPq3}xbRHIp7jzjQjOHIf~MN^ctWQ}b(gY? zuDEA;!3#Qb10K6)W3zn!XL9ZPfBgHOz(nYR?y$ACowmlA)||oHqSJQCfXv{L?C=SC z(y>1}tTTVbB(9lG#uQO3^vAZ&6RB1Q!!37hf^{Ho4?VcO?)@Z>!(3N&G!?LS*L zBYkiGy%`(YV&DHM&e?yn_xlIzoffV^uQ^^qm)03WNdvY_O$*mfPlxQcSI%uaC&&e8 zhaR|3XVJQuW|QA5cp4bh#yyk0Ks399d-WdoNsF^A5Av(=*3g$6pc_@klkpDX*_ugdq} z-A1)q-w7YGY<#|OpE~%rI%B52n-;E|oEEOKv2qgU1z6jd{rSh~u!Hx@n2PVhb+_2( z@dy3%(|*#KyL4QZ0Xcb?4#|D=sXO`q*)1|!a!t?H_Os~W;|66}u!mR@wz}LY`Fw$V z$U1S>#3AfOtJgI~Ei^3HmH&Rxb5O;tpKCl5=9O8z1)p%HJDLX$em2(y^MV?QI#Hf>v41Q1 z!mE#EysU9kG9hoqXJhPdJ{_6+(&c%M>2I!O{+!#sQ$D=&$#Oh{n)oFQe0St4@$=*= zCs{_&Jg(GfJ?u5BJ}^I^SAJ-%S#;GmZEW7L{@?VU|4lDG|8Ia^*pQ!#TD#4q|7xF` zy+|I}o&AZJP;U5xHFeqkfk$q!o}ZW&t-_eZvC!7Q0qlSMt5Y(s?l5MsH}qF~*3WUr z{Gc;!-tZz1m=f|Jb772khP{~f-EF6a{IJ{JK`j>cZ&BVE_DAyTo@U|{v8u@HrUAno z&p2m(;hfGK>xh5GKX3U2_9^|U;wS%=s~znB6Yh8XzOb#PaShzFF>m?Aggc7A7Aq>S{dg{4(j(!)P){c8M=G8a|9C^+^48KJVl@P<_$oToZjeJAL=tozK@y zG5v-kz_*Mqu@9WnIe2&c?zgj_ika7Kc{c63-PWB&tI_+wCzpjeV_%D&d8og-r@Jk? z@UFL@A;az%?eVAWcejOi^wV#vf9TWa4%sVP3e4>O)aW2U^BK3g@u zjFq;=FxvPTgY6x7>ntneP;M=UaznQ4Bd_V$bEoY(gNHt6@+M@tpBc_@1n%H$LC@Iq z+uxom8@)b=!+kr?y>ws4?;AehZrmeo0kdL2CTKd>Uid~CajrAbC_{Hm&XRRg?C(zQ zmVK9BUaP1(kMV(f?`Hi!*=?X*fkm{X;F9oXvGE?Tr~mtQ$*5zAI!4|O+GqF9VzYtw z0w;-aS_Ah2yChq%C|$tKg#J*2Hw*gE!^R)pfnxeCy?*FYW}^WxT`Q@Z{w| zkK=nHuQS!4*9PUPKfTTo!>HA&e)ijAm(GlZBOUA7xMe!Y>~&8^bQf%r;ULa4{sDi8LD1{;8V572I>Y_jZpY7eQG0#2Q0;GvDdU-G8{Y1Dx{VJjAMePiKx>#220g-S7r9;T-4I zUT4I^IQ!l0SL9moILLqG06zBV-1fW6yJSn2bk;NE-+HV496a);{30hFSC8H8cU)=q z0`tOlUiO=Y;B}o&)3LyXq*{En;h9z-teipN4yeJ2mzJhh{7o#=kF@??}Gg z^LniM1ZVM_%yF;yL4(b_SJAP@yT9ieGB!{DTdeK#f8TFtTczJ^4gC=l`oVS7ATc3) zuI~!}A{Q=;KG2`%Ubs6gUOCzZdfw($W4OjVbaU6B51da-yK)>k1)0x-nufMRfXvG-oyWoRP ztYa@Zvla|ZC!TPm`;%*~nfHQgM)#U2_@^4uUCl<{Z}*)$a~F>^y@ox|FCSum&%M`$ z-jEwP@myIAa<4gTGP&sMt#iG~ra0Ff+J!U!Ia%0kzyG%xKL7Lkf9rqfhF-I6>x?_1 z+5ZbmvB5c?jkGzQ8_{#((fvkD$lp2ohX-3feQf>yaas~{-me;+&OYNOxefyZXxG-X z4R(Z0iV^oNJM)KL@D_gX^z9t}z5O~9ro9pP0}SeYEU}(^{>8h~_1B%hQJ%$S@5b); zMkm$*OKP3${n6H2&9TIK=cmUVp6duXseQqBmavi76qq6Ga9s5>(J+&D_1JXmk%xrb zR@^M6Cmj2O&XU#WJI5i-z2YBir~S?=9^L7hi3d%&g=cKKe*WZ4ulW`L`BMgH* z!V{ZEjvZ)!8)o;YS=vl4)KOUazbRuk~CrxxS*7ZGYtz=knbT7{>wqVZ*}Z zqwPDIT|JJti<;MH_d&CdS%=gMzNpovonc%3{Ih@mZ#)SH3VF^_j;8a$kS*p|oVhDC>P2nz6R==HN$xsl;cuBV@Wf(sPWn4)8di z+i3RUOn%@@y~9}dwSU*YO?qvR*}?vG90C?;WNcdTar&%hWTxx9o-=k;_v`Z|CPVuy zcVoO29^%f>k`5=S z-FnM!IB-;4d@Pu!fg{g)~y zwtn={er{)PcX7j>jKz#qkI9gm#>rXs&EAO;1t;Q{_CA;41-r~T# zybn9XD>`Jf*EOxpzo_S7&-T$8qEed*cCebkE9aR)Vb z(oc@G_aXFuH~xLLe3bo8!XDArF1M~ruft}|W_xL$=hg3&I_5q0R@ADm|7!Hx{?jJ@ z&#;9*|973Wt?Vo(| z=J*arIW!FK!g1&*xp!MUhSF^|`{y#+8#Xi6mF<-;KmW-QY4)dsX@ldE+e5p;Hn-6* zz;MEcc*-6)!ZTnS@dP|Nw;Ai!;2on01G9{frSsvVo=;a_bx!BX-=5VOGU8dv!5!+s zeelOjS?v9g+n^Ud;P3}SpH7f@^_AyzMwkv_Gi=Xy3@<1fa}3B1Y;u0vQ(rH0@O7Qn zvNm?NrW^m{8E>RxjyObk@BPobmij`!vJQf-T6LP&w$`jI)R$z~9JoQ6Y8Ep1u9&g3 ziCK?6c2hiWN_~}QmC^7nLA(6i>lACRbM{AV1Nw+P=5_5oz`OYOf1AGl?eo9+nBV_t zJ8VWgXk)~e@u}h)XKnk@f3(?S8}aSa`N6?^cfMFL-1>in^}o$4ZRUP>`h4k-wD0aa z8L!6(GV^dfjxnGkHesLgTkg^B$RBtiCw$>uzfa#n2YlRjw;el!-+3f(3HAY>Mp%Dg z?E`D=zsP6S<3Z0f;9KAZFJ*Xon#71+qLsLQYow5ac}qg~^$*FOJSJ#U`=-?Gm)*#_B`9+C zYmblTz;m15Nf2)i<98dSqtpQCc>le*-vyl|Jwwel;FFJKn1NFjEw6i_FAGe|Ji4EG zfhUzbVvP0$Z(OJCbp1!@>R5Gx z8DlHE^U7=YWz25l+$}d<#Jbfz^$KerIv|I-yavgqp5Z&L*CO6+OTB{^^qA5Ao8kFy z`VKH1{QY0qPa9-&J=>c0giqe95j(TB*bY349N3KC$y)H)VC(y#*8es%+xm;157U8r z@9O!1uaeDpDQ-SPU$(K*C!0K0hLZTDTEIBf%&)}9nzUJWq{*U;1e;c zS`)i~Wx_B!6P9I~&M*hBaIfakT0H1`=hG~_w7qOF`~WUFHbK7Q2lrUFFi5=4-{OtC zl#%a2r{oKpLT=6fkJcIU{*eq|isSdW$+`#I+|>QptroR%7C z)mN_72A#T4<909WHQkVT^bZf}EL{Z*3!F3jBR1N8%8b|SI~x02Eu~lZ`%Bqg$U{Fs zR)@$uZul8z zMsq&%;o|du-sxHKVJc%ttcRo*U$`xv{fKk)IbubfIZrumv=e7}C|Ay_7_$x-^N2m| za{06u_EQh;Nrs>AvdwopPd;%Yv8pFymDz0To)trkjuBW^&zaZws&(eY{hHq1OBU!@ zSG?_eS*G#z0AGL�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.4.0'} + png-to-ico@3.0.1: + resolution: {integrity: sha512-S8BOAoaGd9gT5uaemQ62arIY3Jzco7Uc7LwUTqRyqJDTsKqOAiyfyN4dSdT0D+Zf8XvgztgpRbM5wnQd7EgYwg==} + engines: {node: '>=20'} + hasBin: true + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -3315,7 +3330,7 @@ snapshots: dependencies: '@changesets/types': 6.1.0 - '@changesets/cli@2.29.8(@types/node@20.19.25)': + '@changesets/cli@2.29.8(@types/node@22.19.3)': dependencies: '@changesets/apply-release-plan': 7.0.14 '@changesets/assemble-release-plan': 6.0.9 @@ -3331,7 +3346,7 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.3(@types/node@20.19.25) + '@inquirer/external-editor': 1.0.3(@types/node@22.19.3) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 ci-info: 3.9.0 @@ -3430,11 +3445,11 @@ snapshots: human-id: 4.1.3 prettier: 2.8.8 - '@commitlint/cli@20.2.0(@types/node@20.19.25)(typescript@5.9.3)': + '@commitlint/cli@20.2.0(@types/node@22.19.3)(typescript@5.9.3)': dependencies: '@commitlint/format': 20.2.0 '@commitlint/lint': 20.2.0 - '@commitlint/load': 20.2.0(@types/node@20.19.25)(typescript@5.9.3) + '@commitlint/load': 20.2.0(@types/node@22.19.3)(typescript@5.9.3) '@commitlint/read': 20.2.0 '@commitlint/types': 20.2.0 tinyexec: 1.0.2 @@ -3481,7 +3496,7 @@ snapshots: '@commitlint/rules': 20.2.0 '@commitlint/types': 20.2.0 - '@commitlint/load@20.2.0(@types/node@20.19.25)(typescript@5.9.3)': + '@commitlint/load@20.2.0(@types/node@22.19.3)(typescript@5.9.3)': dependencies: '@commitlint/config-validator': 20.2.0 '@commitlint/execute-rule': 20.0.0 @@ -3489,7 +3504,7 @@ snapshots: '@commitlint/types': 20.2.0 chalk: 5.6.2 cosmiconfig: 9.0.0(typescript@5.9.3) - cosmiconfig-typescript-loader: 6.2.0(@types/node@20.19.25)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.2.0(@types/node@22.19.3)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -3719,12 +3734,12 @@ snapshots: dependencies: '@hapi/hoek': 11.0.7 - '@inquirer/external-editor@1.0.3(@types/node@20.19.25)': + '@inquirer/external-editor@1.0.3(@types/node@22.19.3)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.0 optionalDependencies: - '@types/node': 20.19.25 + '@types/node': 22.19.3 '@isaacs/balanced-match@4.0.1': {} @@ -3979,6 +3994,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@22.19.3': + dependencies: + undici-types: 6.21.0 + '@types/plist@3.0.5': dependencies: '@types/node': 20.19.25 @@ -4014,7 +4033,19 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7))': + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.3)(jiti@1.21.7))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.1(@types/node@22.19.3)(jiti@1.21.7) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -4022,7 +4053,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.4.1(@types/node@20.19.25)(jiti@1.21.7) + vite: 6.4.1(@types/node@22.19.3)(jiti@2.6.1) transitivePeerDependencies: - supports-color @@ -4494,9 +4525,9 @@ snapshots: core-util-is@1.0.3: {} - cosmiconfig-typescript-loader@6.2.0(@types/node@20.19.25)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): + cosmiconfig-typescript-loader@6.2.0(@types/node@22.19.3)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): dependencies: - '@types/node': 20.19.25 + '@types/node': 22.19.3 cosmiconfig: 9.0.0(typescript@5.9.3) jiti: 2.6.1 typescript: 5.9.3 @@ -5892,6 +5923,14 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + png-to-ico@3.0.1: + dependencies: + '@types/node': 22.19.3 + minimist: 1.2.8 + pngjs: 7.0.0 + + pngjs@7.0.0: {} + postcss-import@15.1.0(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -6501,7 +6540,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7): + vite@6.4.1(@types/node@22.19.3)(jiti@1.21.7): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -6510,10 +6549,23 @@ snapshots: rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 20.19.25 + '@types/node': 22.19.3 fsevents: 2.3.3 jiti: 1.21.7 + vite@6.4.1(@types/node@22.19.3)(jiti@2.6.1): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.19.3 + fsevents: 2.3.3 + jiti: 2.6.1 + wait-on@8.0.5: dependencies: axios: 1.13.2 diff --git a/scripts/build-backend.js b/scripts/build-backend.js new file mode 100644 index 0000000..01f73f5 --- /dev/null +++ b/scripts/build-backend.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node +/** + * Cross-platform backend build script. + * Handles PyInstaller build and code signing for macOS. + */ + +const { execSync } = require('child_process'); +const os = require('os'); +const path = require('path'); + +const platform = os.platform(); +const backendDir = path.join(__dirname, '..', 'backend'); + +console.log(`Building backend for ${platform}...`); + +// Run PyInstaller +console.log('Running PyInstaller...'); +try { + execSync('poetry run pyinstaller think.spec --clean', { + cwd: backendDir, + stdio: 'inherit', + shell: true, + }); +} catch (error) { + console.error('PyInstaller failed:', error.message); + process.exit(1); +} + +// Code signing on macOS +if (platform === 'darwin') { + const codesignIdentity = process.env.CODESIGN_IDENTITY; + + if (codesignIdentity) { + console.log('Signing binaries...'); + try { + // Sign all dylibs and so files in the dist directory + execSync( + `find dist/think-backend \\( -name '*.dylib' -o -name '*.so' \\) -exec codesign --force --sign "${codesignIdentity}" --timestamp --options runtime {} \\;`, + { + cwd: backendDir, + stdio: 'inherit', + shell: true, + } + ); + console.log('Code signing complete.'); + } catch (error) { + console.error('Code signing failed:', error.message); + process.exit(1); + } + } else { + console.log('CODESIGN_IDENTITY not set, skipping code signing.'); + } +} + +console.log('Backend build complete!'); diff --git a/scripts/build-stub.js b/scripts/build-stub.js new file mode 100644 index 0000000..582a59a --- /dev/null +++ b/scripts/build-stub.js @@ -0,0 +1,90 @@ +#!/usr/bin/env node +/** + * Cross-platform native messaging stub build script. + * - macOS/Linux: Compiles C stub with clang/gcc + * - Windows: Builds Python stub with PyInstaller + */ + +const { execSync } = require('child_process'); +const os = require('os'); +const path = require('path'); +const fs = require('fs'); + +const platform = os.platform(); +const nativeHostDir = path.join(__dirname, '..', 'backend', 'native_host'); + +console.log(`Building native stub for ${platform}...`); + +if (platform === 'win32') { + // Windows: Build Python stub with PyInstaller + console.log('Building Python stub with PyInstaller...'); + try { + execSync('poetry run pyinstaller stub_win.spec --clean', { + cwd: nativeHostDir, + stdio: 'inherit', + shell: true, + }); + + // Move the built exe to the native_host directory for bundling + const distExe = path.join(nativeHostDir, 'dist', 'think-native-stub.exe'); + const targetExe = path.join(nativeHostDir, 'think-native-stub.exe'); + if (fs.existsSync(distExe)) { + fs.copyFileSync(distExe, targetExe); + console.log(`Copied stub to ${targetExe}`); + } + } catch (error) { + console.error('PyInstaller failed:', error.message); + process.exit(1); + } +} else { + // macOS/Linux: Compile C stub + const compiler = platform === 'darwin' ? 'clang' : 'gcc'; + const stubSource = path.join(nativeHostDir, 'stub.c'); + const stubOutput = path.join(nativeHostDir, 'think-native-stub'); + + console.log(`Compiling C stub with ${compiler}...`); + try { + execSync(`${compiler} -O2 -o think-native-stub stub.c`, { + cwd: nativeHostDir, + stdio: 'inherit', + }); + } catch (error) { + console.error('Compilation failed:', error.message); + process.exit(1); + } + + // Code signing on macOS + if (platform === 'darwin') { + const codesignIdentity = process.env.CODESIGN_IDENTITY; + + if (codesignIdentity) { + console.log('Signing stub binary...'); + try { + execSync( + `codesign --force --sign "${codesignIdentity}" --timestamp --options runtime think-native-stub`, + { + cwd: nativeHostDir, + stdio: 'inherit', + } + ); + console.log('Code signing complete.'); + } catch (error) { + console.error('Code signing failed:', error.message); + process.exit(1); + } + } else { + // Ad-hoc signing for local development + console.log('Ad-hoc signing for local development...'); + try { + execSync('codesign --sign - --force think-native-stub', { + cwd: nativeHostDir, + stdio: 'inherit', + }); + } catch (error) { + console.warn('Ad-hoc signing failed (non-fatal):', error.message); + } + } + } +} + +console.log('Native stub build complete!'); From 2f261bb57148d64b6c50524f983c6e991901f589 Mon Sep 17 00:00:00 2001 From: Antonio Maiolo Date: Wed, 17 Dec 2025 12:31:04 +0100 Subject: [PATCH 2/8] fix(release): use pip to install poetry on Windows The snok/install-poetry action doesn't add Poetry to PATH correctly on Windows. Using pip install instead. --- .github/workflows/release.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f0b2377..95c4c99 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -167,11 +167,11 @@ jobs: python-version: '3.12' - name: Install Poetry - uses: snok/install-poetry@v1 - with: - version: '1.8.0' - virtualenvs-create: true - virtualenvs-in-project: true + run: pip install poetry==1.8.0 + + - name: Configure Poetry + run: poetry config virtualenvs.in-project true + working-directory: backend - name: Install pnpm dependencies run: pnpm install --frozen-lockfile From 5e5c0aae827c93d8e43fcded700b4e65147d81da Mon Sep 17 00:00:00 2001 From: Antonio Maiolo Date: Wed, 17 Dec 2025 12:42:35 +0100 Subject: [PATCH 3/8] fix(release): correct sqlite-vec download URL to .tar.gz The sqlite-vec Windows release is distributed as .tar.gz, not .zip. --- .github/workflows/release.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 95c4c99..ca6e7bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -184,12 +184,13 @@ jobs: shell: pwsh run: | # Download MSVC-compiled vec0.dll from sqlite-vec releases - $vec0Url = "https://github.com/asg017/sqlite-vec/releases/download/v0.1.6/sqlite-vec-0.1.6-loadable-windows-x86_64.zip" - $zipPath = "$env:RUNNER_TEMP\sqlite-vec.zip" + $vec0Url = "https://github.com/asg017/sqlite-vec/releases/download/v0.1.6/sqlite-vec-0.1.6-loadable-windows-x86_64.tar.gz" + $tarPath = "$env:RUNNER_TEMP\sqlite-vec.tar.gz" $extractPath = "$env:RUNNER_TEMP\sqlite-vec" - Invoke-WebRequest -Uri $vec0Url -OutFile $zipPath - Expand-Archive -Path $zipPath -DestinationPath $extractPath + Invoke-WebRequest -Uri $vec0Url -OutFile $tarPath + New-Item -ItemType Directory -Path $extractPath -Force + tar -xzf $tarPath -C $extractPath # Find where sqlite_vec package is installed and copy vec0.dll there $sitePackages = poetry run python -c "import site; print(site.getsitepackages()[0])" From 2c21b4da5a194578a2ba62815e9ca27b73a8c918 Mon Sep 17 00:00:00 2001 From: Antonio Maiolo Date: Wed, 17 Dec 2025 14:52:28 +0100 Subject: [PATCH 4/8] fix(backend): use binary mode for salt file to fix Windows password bug On Windows, read_text()/write_text() can add UTF-8 BOM which causes the derived encryption key to differ between setup and unlock. Using binary mode (read_bytes/write_bytes) avoids encoding issues entirely since the salt is just ASCII hex characters. --- backend/app/services/secrets.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/app/services/secrets.py b/backend/app/services/secrets.py index ff4dd25..870ee3d 100644 --- a/backend/app/services/secrets.py +++ b/backend/app/services/secrets.py @@ -42,12 +42,18 @@ async def delete_api_key(provider: str) -> None: def get_or_create_salt() -> str: - """Get existing salt or create a new one.""" + """Get existing salt or create a new one. + + Uses binary mode to avoid Windows UTF-8 BOM issues that can cause + the derived key to differ between setup and unlock. + """ salt_path = _get_salt_path() if salt_path.exists(): - return salt_path.read_text().strip() + # Read as bytes to avoid encoding issues (BOM on Windows) + return salt_path.read_bytes().decode('ascii').strip() salt = stdlib_secrets.token_hex(16) - salt_path.write_text(salt) + # Write as bytes to avoid BOM being added on Windows + salt_path.write_bytes(salt.encode('ascii')) return salt From 62b7249fd6f4019a11b29a08b3dd9f5859aa23ee Mon Sep 17 00:00:00 2001 From: Antonio Maiolo Date: Wed, 17 Dec 2025 16:26:36 +0100 Subject: [PATCH 5/8] fix(backend): handle missing FTS5 module on Windows rotki-pysqlcipher3 on Windows doesn't include FTS5 support. Migration 8 now checks if FTS5 is available before creating the virtual table. Search falls back to vector-only when FTS5 is unavailable. Also improved decrypt_db.py with cross-platform support and better CLI arguments. --- backend/app/db/migrations.py | 91 ++++++++++++++++++++--------------- backend/scripts/decrypt_db.py | 59 ++++++++++++++++++++--- 2 files changed, 102 insertions(+), 48 deletions(-) diff --git a/backend/app/db/migrations.py b/backend/app/db/migrations.py index 840fff1..312eda6 100644 --- a/backend/app/db/migrations.py +++ b/backend/app/db/migrations.py @@ -208,47 +208,58 @@ def migration_008(conn: Connection) -> None: "SELECT name FROM sqlite_master WHERE type='table' AND name='memories_fts'" )).fetchone() - if not result: - # Create FTS5 virtual table - conn.execute(text(""" - CREATE VIRTUAL TABLE memories_fts USING fts5( - title, - content, - content='memories', - content_rowid='id' - ) - """)) - - # Populate with existing data - conn.execute(text(""" + if result: + return # Already exists + + # Check if FTS5 module is available (not compiled into all SQLite builds, + # e.g., rotki-pysqlcipher3 on Windows doesn't include FTS5) + try: + conn.execute(text("CREATE VIRTUAL TABLE _fts5_test USING fts5(test)")) + conn.execute(text("DROP TABLE _fts5_test")) + except Exception: + print("WARNING: FTS5 module not available - full-text search will be disabled", flush=True) + return + + # Create FTS5 virtual table + conn.execute(text(""" + CREATE VIRTUAL TABLE memories_fts USING fts5( + title, + content, + content='memories', + content_rowid='id' + ) + """)) + + # Populate with existing data + conn.execute(text(""" + INSERT INTO memories_fts(rowid, title, content) + SELECT id, COALESCE(title, ''), COALESCE(content, '') + FROM memories + """)) + + # Create triggers to keep FTS in sync + conn.execute(text(""" + CREATE TRIGGER memories_fts_ai AFTER INSERT ON memories BEGIN INSERT INTO memories_fts(rowid, title, content) - SELECT id, COALESCE(title, ''), COALESCE(content, '') - FROM memories - """)) - - # Create triggers to keep FTS in sync - conn.execute(text(""" - CREATE TRIGGER memories_fts_ai AFTER INSERT ON memories BEGIN - INSERT INTO memories_fts(rowid, title, content) - VALUES (new.id, COALESCE(new.title, ''), COALESCE(new.content, '')); - END - """)) - - conn.execute(text(""" - CREATE TRIGGER memories_fts_ad AFTER DELETE ON memories BEGIN - INSERT INTO memories_fts(memories_fts, rowid, title, content) - VALUES ('delete', old.id, COALESCE(old.title, ''), COALESCE(old.content, '')); - END - """)) - - conn.execute(text(""" - CREATE TRIGGER memories_fts_au AFTER UPDATE ON memories BEGIN - INSERT INTO memories_fts(memories_fts, rowid, title, content) - VALUES ('delete', old.id, COALESCE(old.title, ''), COALESCE(old.content, '')); - INSERT INTO memories_fts(rowid, title, content) - VALUES (new.id, COALESCE(new.title, ''), COALESCE(new.content, '')); - END - """)) + VALUES (new.id, COALESCE(new.title, ''), COALESCE(new.content, '')); + END + """)) + + conn.execute(text(""" + CREATE TRIGGER memories_fts_ad AFTER DELETE ON memories BEGIN + INSERT INTO memories_fts(memories_fts, rowid, title, content) + VALUES ('delete', old.id, COALESCE(old.title, ''), COALESCE(old.content, '')); + END + """)) + + conn.execute(text(""" + CREATE TRIGGER memories_fts_au AFTER UPDATE ON memories BEGIN + INSERT INTO memories_fts(memories_fts, rowid, title, content) + VALUES ('delete', old.id, COALESCE(old.title, ''), COALESCE(old.content, '')); + INSERT INTO memories_fts(rowid, title, content) + VALUES (new.id, COALESCE(new.title, ''), COALESCE(new.content, '')); + END + """)) @migration(9, "Add token usage columns to messages") diff --git a/backend/scripts/decrypt_db.py b/backend/scripts/decrypt_db.py index 46c937e..cef7765 100755 --- a/backend/scripts/decrypt_db.py +++ b/backend/scripts/decrypt_db.py @@ -1,31 +1,74 @@ #!/usr/bin/env python3 -"""Derive the database encryption key from master password.""" +"""Derive the database encryption key from master password. +Usage: + python decrypt_db.py # Uses default salt path for current OS + python decrypt_db.py --salt-file /path/.salt # Uses custom salt file + python decrypt_db.py --salt abc123def456 # Uses salt value directly +""" + +import argparse import hashlib +import os +import platform import sys from pathlib import Path from getpass import getpass def get_data_dir() -> Path: - return Path.home() / "Library" / "Application Support" / "Think" + """Get the app data directory for the current platform.""" + system = platform.system() + if system == "Darwin": + return Path.home() / "Library" / "Application Support" / "Think" + elif system == "Windows": + return Path(os.environ.get("LOCALAPPDATA", Path.home())) / "Think" + else: + return Path.home() / ".local" / "share" / "Think" def main(): - salt_path = get_data_dir() / ".salt" + parser = argparse.ArgumentParser( + description="Derive the database encryption key from master password" + ) + parser.add_argument( + "--salt-file", + type=Path, + help="Path to .salt file" + ) + parser.add_argument( + "--salt", + type=str, + help="Salt value directly (32 hex characters)" + ) + args = parser.parse_args() - if not salt_path.exists(): - print(f"Salt file not found: {salt_path}", file=sys.stderr) - sys.exit(1) + # Determine salt value + if args.salt: + salt = args.salt.strip() + print(f"Using provided salt: {salt}") + elif args.salt_file: + if not args.salt_file.exists(): + print(f"Salt file not found: {args.salt_file}", file=sys.stderr) + sys.exit(1) + salt = args.salt_file.read_bytes().decode('ascii').strip() + print(f"Salt from file {args.salt_file}: {salt}") + else: + salt_path = get_data_dir() / ".salt" + print(f"Using default salt path: {salt_path}") + if not salt_path.exists(): + print(f"Salt file not found: {salt_path}", file=sys.stderr) + sys.exit(1) + salt = salt_path.read_bytes().decode('ascii').strip() + print(f"Salt value: {salt}") - salt = salt_path.read_text().strip() password = getpass("Master password: ") key = hashlib.pbkdf2_hmac( 'sha256', password.encode(), salt.encode(), 100000 ).hex() - print(key) + print(f"\nDerived key (use this in DB Browser for SQLite):\n{key}") if __name__ == "__main__": From f279a594353223cafa58491218c9589e3217ca4d Mon Sep 17 00:00:00 2001 From: Antonio Maiolo Date: Wed, 17 Dec 2025 16:54:37 +0100 Subject: [PATCH 6/8] fix(app): windows fixes for ollama download and native messaging - Replace blocking curl download with async https download - Show real download progress (5-80%) instead of hanging at 10% - Replace winreg dependency with built-in reg.exe commands - Remove unused winreg dependency from package.json --- app/electron/install-native-host.js | 100 +++++++++------------------- app/electron/main.js | 79 +++++++++++++++++++--- app/package.json | 3 +- 3 files changed, 100 insertions(+), 82 deletions(-) diff --git a/app/electron/install-native-host.js b/app/electron/install-native-host.js index ff4ffc7..ba90658 100644 --- a/app/electron/install-native-host.js +++ b/app/electron/install-native-host.js @@ -11,6 +11,7 @@ const fs = require('fs'); const path = require('path'); const os = require('os'); +const { execSync } = require('child_process'); // Extension IDs - update these when publishing to browser stores // During development, Chrome assigns a dynamic ID based on the extension path @@ -128,76 +129,40 @@ function installManifest(directory, manifest) { } /** - * Install Windows registry entries. + * Install Windows registry entries using reg.exe (no external dependencies). */ function installWindowsRegistry(stubPath, extensionIds) { - // Windows requires registry entries instead of manifest files - // This uses the 'winreg' module - install with: npm install winreg + // Windows requires registry entries that point to manifest files + const manifestDir = path.dirname(stubPath); - let Registry; - try { - Registry = require('winreg'); - } catch (e) { - console.error('[Native Host] winreg module not found. Install with: npm install winreg'); - console.error('[Native Host] Skipping Windows registry installation'); - return; - } + // Chrome/Edge manifest + const chromeManifestPath = path.join(manifestDir, `${NATIVE_HOST_NAME}.json`); + const chromeManifest = createChromeManifest(stubPath, extensionIds); + fs.writeFileSync(chromeManifestPath, JSON.stringify(chromeManifest, null, 2)); + + // Firefox manifest + const firefoxManifestPath = path.join(manifestDir, `${NATIVE_HOST_NAME}.firefox.json`); + const firefoxManifest = createFirefoxManifest(stubPath); + fs.writeFileSync(firefoxManifestPath, JSON.stringify(firefoxManifest, null, 2)); const browsers = [ - { name: 'Chrome', key: '\\Software\\Google\\Chrome\\NativeMessagingHosts\\' + NATIVE_HOST_NAME }, - { name: 'Edge', key: '\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\' + NATIVE_HOST_NAME }, + { name: 'Chrome', key: `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`, manifest: chromeManifestPath }, + { name: 'Edge', key: `HKCU\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`, manifest: chromeManifestPath }, + { name: 'Firefox', key: `HKCU\\Software\\Mozilla\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`, manifest: firefoxManifestPath }, ]; for (const browser of browsers) { try { - const regKey = new Registry({ - hive: Registry.HKCU, - key: browser.key, - }); - - // Create manifest file in app directory - const manifestDir = path.dirname(stubPath); - const manifestPath = path.join(manifestDir, `${NATIVE_HOST_NAME}.json`); - - const manifest = createChromeManifest(stubPath, extensionIds); - fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)); - - // Point registry to manifest file - regKey.set('', Registry.REG_SZ, manifestPath, (err) => { - if (err) { - console.error(`[Native Host] Failed to set registry for ${browser.name}:`, err); - } else { - console.log(`[Native Host] Installed registry entry for ${browser.name}`); - } + // Use reg.exe to add registry key (no external dependencies needed) + execSync(`reg add "${browser.key}" /ve /t REG_SZ /d "${browser.manifest}" /f`, { + stdio: 'pipe', + windowsHide: true, }); + console.log(`[Native Host] Installed registry entry for ${browser.name}`); } catch (error) { console.error(`[Native Host] Failed to install registry for ${browser.name}:`, error.message); } } - - // Firefox on Windows also uses registry - try { - const firefoxKey = new Registry({ - hive: Registry.HKCU, - key: '\\Software\\Mozilla\\NativeMessagingHosts\\' + NATIVE_HOST_NAME, - }); - - const manifestDir = path.dirname(stubPath); - const manifestPath = path.join(manifestDir, `${NATIVE_HOST_NAME}.firefox.json`); - - const manifest = createFirefoxManifest(stubPath); - fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)); - - firefoxKey.set('', Registry.REG_SZ, manifestPath, (err) => { - if (err) { - console.error('[Native Host] Failed to set registry for Firefox:', err); - } else { - console.log('[Native Host] Installed registry entry for Firefox'); - } - }); - } catch (error) { - console.error('[Native Host] Failed to install registry for Firefox:', error.message); - } } /** @@ -272,31 +237,26 @@ function installNativeHost(resourcesPath, extensionIds = CHROME_EXTENSION_IDS, i /** * Uninstall native messaging host manifests. - * @param {string} resourcesPath - Path to Electron app resources directory */ -function uninstallNativeHost(resourcesPath) { +function uninstallNativeHost() { console.log('[Native Host] Uninstalling native messaging host manifests...'); if (process.platform === 'win32') { - // Remove Windows registry entries - const Registry = require('winreg'); - + // Remove Windows registry entries using reg.exe const keys = [ - '\\Software\\Google\\Chrome\\NativeMessagingHosts\\' + NATIVE_HOST_NAME, - '\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\' + NATIVE_HOST_NAME, - '\\Software\\Mozilla\\NativeMessagingHosts\\' + NATIVE_HOST_NAME, + `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`, + `HKCU\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`, + `HKCU\\Software\\Mozilla\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`, ]; for (const key of keys) { try { - const regKey = new Registry({ hive: Registry.HKCU, key }); - regKey.destroy((err) => { - if (err && err.code !== 2) { // Ignore "key not found" errors - console.error(`[Native Host] Failed to remove registry key ${key}:`, err); - } + execSync(`reg delete "${key}" /f`, { + stdio: 'pipe', + windowsHide: true, }); } catch (error) { - // Ignore errors + // Ignore errors (key might not exist) } } diff --git a/app/electron/main.js b/app/electron/main.js index ebe3689..d42a8d1 100644 --- a/app/electron/main.js +++ b/app/electron/main.js @@ -6,6 +6,58 @@ const fs = require('fs'); const https = require('https'); const { installNativeHost } = require('./install-native-host'); +/** + * Download a file with progress reporting. + * Uses native https module to avoid blocking the main process. + */ +function downloadFile(url, destPath, onProgress) { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(destPath); + + const makeRequest = (urlString) => { + https.get(urlString, (response) => { + // Handle redirects + if (response.statusCode === 301 || response.statusCode === 302) { + makeRequest(response.headers.location); + return; + } + + if (response.statusCode !== 200) { + reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`)); + return; + } + + const totalSize = parseInt(response.headers['content-length'], 10); + let downloadedSize = 0; + + response.on('data', (chunk) => { + downloadedSize += chunk.length; + if (onProgress && totalSize) { + onProgress(Math.round((downloadedSize / totalSize) * 100)); + } + }); + + response.pipe(file); + + file.on('finish', () => { + file.close(); + resolve(); + }); + + file.on('error', (err) => { + fs.unlink(destPath, () => {}); // Delete partial file + reject(err); + }); + }).on('error', (err) => { + fs.unlink(destPath, () => {}); // Delete partial file + reject(err); + }); + }; + + makeRequest(url); + }); +} + let mainWindow; let pythonProcess; let backendReady = false; @@ -277,12 +329,14 @@ ipcMain.handle('download-ollama', async (_event) => { const zipPath = path.join(tempDir, 'Ollama-darwin.zip'); const downloadUrl = 'https://ollama.com/download/Ollama-darwin.zip'; - mainWindow.webContents.send('ollama-download-progress', { progress: 10, stage: 'downloading' }); + mainWindow.webContents.send('ollama-download-progress', { progress: 5, stage: 'downloading' }); - // Use curl for reliable redirect handling - execSync(`curl -L "${downloadUrl}" -o "${zipPath}"`, { stdio: 'pipe' }); - - mainWindow.webContents.send('ollama-download-progress', { progress: 80, stage: 'downloading' }); + // Async download with progress reporting + await downloadFile(downloadUrl, zipPath, (percent) => { + // Map 0-100% download progress to 5-80% overall progress + const overallProgress = 5 + Math.round(percent * 0.75); + mainWindow.webContents.send('ollama-download-progress', { progress: overallProgress, stage: 'downloading' }); + }); mainWindow.webContents.send('ollama-download-progress', { progress: 100, stage: 'installing' }); @@ -315,17 +369,22 @@ ipcMain.handle('download-ollama', async (_event) => { const exePath = path.join(tempDir, 'OllamaSetup.exe'); const downloadUrl = 'https://ollama.com/download/OllamaSetup.exe'; - mainWindow.webContents.send('ollama-download-progress', { progress: 10, stage: 'downloading' }); + mainWindow.webContents.send('ollama-download-progress', { progress: 5, stage: 'downloading' }); - // Use curl for reliable redirect handling - execSync(`curl -L "${downloadUrl}" -o "${exePath}"`, { stdio: 'pipe' }); + // Async download with progress reporting + await downloadFile(downloadUrl, exePath, (percent) => { + // Map 0-100% download progress to 5-80% overall progress + const overallProgress = 5 + Math.round(percent * 0.75); + mainWindow.webContents.send('ollama-download-progress', { progress: overallProgress, stage: 'downloading' }); + }); - mainWindow.webContents.send('ollama-download-progress', { progress: 80, stage: 'downloading' }); - mainWindow.webContents.send('ollama-download-progress', { progress: 100, stage: 'installing' }); + mainWindow.webContents.send('ollama-download-progress', { progress: 85, stage: 'installing' }); // Run silent install execSync(`"${exePath}" /VERYSILENT /NORESTART`, { stdio: 'ignore' }); + mainWindow.webContents.send('ollama-download-progress', { progress: 100, stage: 'starting' }); + // Launch Ollama spawn(ollamaPath, ['serve'], { detached: true, stdio: 'ignore' }); diff --git a/app/package.json b/app/package.json index 416b5a2..207d12b 100644 --- a/app/package.json +++ b/app/package.json @@ -84,7 +84,6 @@ "tailwindcss": "^3.4.15", "typescript": "^5.6.3", "vite": "^6.0.1", - "wait-on": "^8.0.1", - "winreg": "^1.2.5" + "wait-on": "^8.0.1" } } From f4940ae73bcd60412d2cd976fcc1d1e8763f9d6f Mon Sep 17 00:00:00 2001 From: Antonio Maiolo Date: Wed, 17 Dec 2025 17:07:50 +0100 Subject: [PATCH 7/8] fix: update pnpm lock --- pnpm-lock.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc3de0a..f59cbb7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,9 +99,6 @@ importers: wait-on: specifier: ^8.0.1 version: 8.0.5 - winreg: - specifier: ^1.2.5 - version: 1.2.5 extension: dependencies: @@ -3127,9 +3124,6 @@ packages: wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - winreg@1.2.5: - resolution: {integrity: sha512-uf7tHf+tw0B1y+x+mKTLHkykBgK2KMs3g+KlzmyMbLvICSHQyB/xOFjTT8qZ3oeTFyU7Bbj4FzXitGG6jvKhYw==} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -6588,8 +6582,6 @@ snapshots: dependencies: string-width: 4.2.3 - winreg@1.2.5: {} - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 From 8fd3dff500ad0442b2ba3cff566461ea030136fb Mon Sep 17 00:00:00 2001 From: Antonio Maiolo Date: Wed, 17 Dec 2025 17:28:03 +0100 Subject: [PATCH 8/8] chore: add changeset for windows support --- .changeset/windows-support.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .changeset/windows-support.md diff --git a/.changeset/windows-support.md b/.changeset/windows-support.md new file mode 100644 index 0000000..17989ae --- /dev/null +++ b/.changeset/windows-support.md @@ -0,0 +1,12 @@ +--- +"think-app": minor +--- + +Add Windows support + +- Windows NSIS installer with desktop/start menu shortcuts +- Native messaging host registration via `reg.exe` (removed `winreg` dependency) +- Cross-platform Ollama download using native `https` module +- Graceful FTS5 fallback when module unavailable +- Binary mode for salt file to fix Windows password unlock +- Cross-platform build scripts for backend and native stub