From 73cd8622f9fcd5e368060d1a1678e08bd3d0a794 Mon Sep 17 00:00:00 2001 From: Siddharth Balyan <52913345+alt-glitch@users.noreply.github.com> Date: Fri, 19 Jun 2026 01:53:32 +0530 Subject: [PATCH] =?UTF-8?q?feat(billing):=20/billing=20terminal=20billing?= =?UTF-8?q?=20=E2=80=94=20interactive=20TUI=20+=20CLI=20client=20(#45449)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(billing): nous_billing http client + BillingState core (phase 2b) Phase 2b terminal-billing client foundation: - hermes_cli/nous_billing.py: typed client for the 4 /api/billing/* endpoints (state/charge/poll/auto-top-up). Raises typed errors (BillingScopeRequired, BillingRateLimited, BillingAuthError) mapped from the live-verified contract; fail-open is the caller's job. Idempotency-Key enforced client-side. - agent/billing_view.py: surface-agnostic BillingState core + Decimal money parsing (server emits decimal strings, not 2dp), fail-open builder, idempotency-key gen, custom-amount validation. - 51 unit tests (decimal parse/format, payload tiering, error->exception matrix, fail-open, amount validation). Plan: docs/plans/2026-06-13-001-phase-2b-terminal-billing-tui-plan.md * feat(billing): billing:manage scope + lazy step-up re-auth (phase 2b) - NOUS_BILLING_MANAGE_SCOPE constant. - nous_token_has_billing_scope(): split-based scope check (no false-positive substring match). - step_up_nous_billing_scope(): re-runs the device flow requesting billing:manage, reusing the held credential's portal/inference URLs + client_id (so a preview stays a preview), persists like _login_nous but WITHOUT the model picker. Returns True iff the minted token carries the scope (False when NAS silently downscopes a non-admin / unticked grant). Lazy step-up (plan D-A): normal login path unchanged; 403 insufficient_scope from a billing call triggers this. 7 unit tests. * feat(billing): billing JSON-RPC methods for the TUI (phase 2b) billing.state / charge / charge_status / auto_reload / step_up in tui_gateway/server.py. Return STRUCTURED success envelopes (result.ok + result.error=) rather than JSON-RPC-level errors, so the Ink rpc() promise always resolves and the TUI branches on the typed billing error code (insufficient_scope, rate_limited, no_payment_method, …) to render the right affordance. Money serialized as decimal STRINGS + display strings. charge mints + echoes an idempotency_key for retry reuse. 16 unit tests. * feat(billing): /billing CLI handler + command registry (phase 2b) - CommandDef("billing", subcommands=buy|auto-reload|limit), added to _SLACK_VIA_HERMES_ONLY so it routes via /hermes on Slack (keeps the 50-cap parity test green, same as /credits). - cli.py::_show_billing + screen helpers: all 5 screens (overview, buy→confirm→ poll, auto-reload, monthly-limit read-only). Reuses _prompt_text_input_modal / _prompt_text_input (D-C). Non-interactive (_app is None) renders text + portal deep-link, never prompts (R7). Decimal money end-to-end. 2s/5-min cancellable poll loop; 429/503 = retry not failure; settled = ledger truth. Lazy step-up on 403 insufficient_scope. no_payment_method treated as mainline funnel-to-portal. - 6 CLI tests; 156 command tests (incl. Slack/Telegram parity) green. * feat(billing): /billing Ink TUI screens + tests (phase 2b) - ui-tui/src/app/slash/commands/billing.ts: /billing TUI command covering all 5 screens — overview (text), buy → ConfirmReq → charge → non-blocking 2s/ 5-min poll loop → settled/failed/timeout branches, auto-reload → ConfirmReq → PATCH, limit (read-only). Reuses the existing ConfirmReq overlay (D-C) — no bespoke component. Typed-error envelope branching: insufficient_scope arms the lazy step-up confirm; no_payment_method/rate_limited/cap funnel to portal. Client-side amount validation mirrors the server (bounds + 2dp). - gatewayTypes.ts: Billing* response interfaces. - registry.ts: register billingCommands. - billingCommand.test.ts: 12 vitest cases (overview/gating/buy-confirm-poll- settled/no_payment_method/step-up/limit/auto-reload/validation). TUI build green; 12/12 vitest pass; slash tests pass once @hermes/ink is built. * docs(billing): scrub private cross-repo references NAS is a private repo — remove all references to it from the public PR: - drop the cross-repo planning doc (planning scaffolding, not a deliverable; the PR description documents the design) - replace 'NAS' / 'PR #412 preview' mentions in code + test comments with generic 'the server' / 'a preview deployment' * docs(billing): scrub final NAS reference in step-up docstring * docs(billing): drop dangling plan-doc refs The phase-2b plan doc was removed in the cross-repo scrub (300afcc0b) but two module docstrings still pointed at it. Drop the dead refs. * feat(billing): interactive /billing overlay + step-up UX, portal-URL & token fixes Adds the interactive /billing TUI overlay and hardens the terminal-billing client across CLI and TUI. - TUI: full /billing overlay state machine (overview to buy to confirm, auto-reload, read-only monthly limit) reusing the existing confirm overlay. - Step-up: surface the verification link in-transcript and open the browser via the TUI's own opener (the device flow runs in the headless gateway, so a printed URL was being dropped); run the step-up handler off the main loop and emit the link as an out-of-band event so the gateway stays responsive. - Step-up copy is scope-accurate ("Billing permission granted") and re-checks /state so it never claims "enabled" when the org kill-switch is still off. - Portal deep-links resolve to absolute URLs against the active portal base (the server emits them relative) - fixes a bare "/billing?topup=open" link. - Billing calls refresh an expired access token via the stored refresh token instead of reporting a false "not logged in". - Optimistic funnel: advise "set up a saved card on the portal" up front when no card is on file (advisory, not a hard gate). - Token resolution is cached briefly so the 2s charge poll loop stops re-locking + re-reading the auth store on every tick; 401 re-resolves fresh. - Remove the temporary demo-mode shims. Validation: 87 Python billing tests, 88 TS tests (billing command + gateway event handler), tsc clean, ink + ui-tui builds green. * docs(billing): add /billing TUI screenshots for PR * fix(cli): guard _last_invalidate on bare instances; update stale prompt-fallback test The UI-invalidate throttle read self._last_invalidate unconditionally, which raised AttributeError on HermesCLI instances built without __init__ (the thread-safety test's object.__new__ shell). Guard the read with getattr. The off-main-thread branch of _prompt_text_input was changed (#23185) to cancel cleanly to None instead of falling back to a bare input() that would hang on the slash-worker thread; the test still asserted the old direct-input fallback. Update it to assert the current intended behavior: returns None, calls neither run_in_terminal nor input(), and does not hang. --- .../pr-screenshots/45449/billing-confirm.png | Bin 0 -> 141096 bytes .../pr-screenshots/45449/billing-overview.png | Bin 0 -> 151831 bytes agent/billing_view.py | 295 ++++++++ cli.py | 657 ++++++++++++++++- hermes_cli/auth.py | 97 +++ hermes_cli/commands.py | 4 +- hermes_cli/nous_billing.py | 406 +++++++++++ tests/agent/test_billing_view.py | 377 ++++++++++ .../test_prompt_text_input_thread_safety.py | 28 +- tests/hermes_cli/test_billing_cli.py | 136 ++++ tests/hermes_cli/test_billing_portal_url.py | 53 ++ tests/hermes_cli/test_billing_scope_stepup.py | 193 +++++ tests/tui_gateway/test_billing_rpc.py | 206 ++++++ tui_gateway/server.py | 216 ++++++ ui-tui/src/__tests__/billingCommand.test.ts | 301 ++++++++ .../createGatewayEventHandler.test.ts | 37 + ui-tui/src/app/createGatewayEventHandler.ts | 24 + ui-tui/src/app/interfaces.ts | 45 +- ui-tui/src/app/overlayStore.ts | 16 +- ui-tui/src/app/slash/commands/billing.ts | 332 +++++++++ ui-tui/src/app/slash/registry.ts | 2 + ui-tui/src/app/useInputHandlers.ts | 6 +- ui-tui/src/components/appOverlays.tsx | 16 + ui-tui/src/components/billingOverlay.tsx | 684 ++++++++++++++++++ ui-tui/src/gatewayTypes.ts | 94 +++ 25 files changed, 4203 insertions(+), 22 deletions(-) create mode 100644 .github/pr-screenshots/45449/billing-confirm.png create mode 100644 .github/pr-screenshots/45449/billing-overview.png create mode 100644 agent/billing_view.py create mode 100644 hermes_cli/nous_billing.py create mode 100644 tests/agent/test_billing_view.py create mode 100644 tests/hermes_cli/test_billing_cli.py create mode 100644 tests/hermes_cli/test_billing_portal_url.py create mode 100644 tests/hermes_cli/test_billing_scope_stepup.py create mode 100644 tests/tui_gateway/test_billing_rpc.py create mode 100644 ui-tui/src/__tests__/billingCommand.test.ts create mode 100644 ui-tui/src/app/slash/commands/billing.ts create mode 100644 ui-tui/src/components/billingOverlay.tsx diff --git a/.github/pr-screenshots/45449/billing-confirm.png b/.github/pr-screenshots/45449/billing-confirm.png new file mode 100644 index 0000000000000000000000000000000000000000..643f62a651db4f231edcba727f6d8aa6e46e2fe8 GIT binary patch literal 141096 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 z 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 YwNbBlL*f16A=ZzX(G|m5eb@N^0z!~%CjbBd literal 0 HcmV?d00001 diff --git a/.github/pr-screenshots/45449/billing-overview.png b/.github/pr-screenshots/45449/billing-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..6e2e319a9bd1cf088d6447705b3451eb02db1807 GIT binary patch literal 151831 zcmeFZcUY5I*Ebr+XLLpd#}NxnW^}{?f*6XS>L8%fq}PCg66q2M0cp;tC|w7o2UKdP zBF%)7Q7O_wO(;poC?ygk5rec4IQtH$^UV9a=e&P>-}(Oea$QqM-Fx3_uf2X{-BA}z z3`Dj_Y=OaGBInMYF^9o6jly8x{{G!2@Qi3tnhorm3nS;woVErnN%!A3&t|VGtI%Bfy+h2}*_r`pBAab`g4fCm1_BP!+`HJC<>EYh3G5BC5 zxMJSnPg^~#RR4T7UN}^;V~AhaPIEi|r{z#REyokK!G&_qK4hN^=FxYuM(Wf~oEYVA$h2$>drM%^CGS6u<$oBl9X~o)Ir-ZvkDkHr)WFhp z9IDu4*;S?@zX=R~?eS-HmB&N=5Kg10bZK=ZpHjrga;{%`vr1DW_tuElxc5;bRMoIk zBrJ#QJ3<&$UDW&hN!XvSKN=H?R#;hOPW5jbY|2&+MbtVz6R+_bbz|Q0UsX%H_Vuly zjJym!$+>opx7xF+=J7D$TUGBRRQkk3sUU=Ti~o$Y^~-x7-STY{czt5Wt>zEfp|_^_ zrK@T!j1ve$5nM^X^kw9iH^RaeBIy)L(E#`lUx$a)Q7aRlH$^oHHpZR&@}@^=KDs`m zinma&!$xBH`TBIyeB5(Ie(jexz>a<*Bo(Q#=eV95Vw~&(kGs`}(>rwb<^{ zCo9J%KYn@c>PVqgk&}JZrB@qQWmueL8R;)C-PlyJv|2j++>d9{nk1fQ3 zq1X@sp^N}e)($YO|F|<^`})PX(BkS$oQ^kk_E%iJhCBp=ZSDDT75kxUxRfovdHuZw zx80;*vj$%-?1io1vc5|IPp&;4?N0N9b<1=<{~7)6PZZ~`F=I8s+w0i5WeHMFerHB% z==GbldVi|)B2^@!tY})@m0*cvWb_1KSa!LkKICm4b+Txy3HQpdW;C97yr#DW?mZDZ zVRwbko-m@!7*UAeTF6@N zDm{uJgbM~%CFdz+D{1^b9YH3)mc$Ps`CLF;><;9PP~4wM>8$wD<;s>N!Egth_}CSc%|oK2spgC#uhIFtzzDPeeN zcwgB$1h2pPNNjak=|gR&bY+9vJ(~X2WeRb*#|fi&{Y&VnvGPtyT)@MdnL)AMXsM*`LI2UR}*Q0bIa&2Y45Rwn+#{5kkw&?$VnCDSm8(>fCyO| z3G&JN?%&c^tOr)|iOGf`E2$&ny`nP6pv7mhQ^R7CWgJa^Hd|sg$ha*P<;7>)PmWd{ zQSHi@E-K;#VW6QnF@mWj8w-QpJ-$QV)9~RDKaROjND_QYY9!2$@D1ikD`h%?s|yKS zK8?G0@ReRNewo1(Op_08-bEPpYO}5I$W3?hcFoqIG2Gp!a}ydoKI+efZb}i@mZU@+6+V zER|GayYmR8t~)irS->vCDCk~IQ0x`AbY6J5NBxstQq1B^!fM+mjh&|cDtJU=PIXZM zTQi1lRukibQG$kXGX=#UcK%%;Gdwg!dwdB*(u0o=%%g*n}?_p z!e7mbWk*kVqZv28M^dNUSymGsTo<{(55&k zKAs`ETh54b^SzrAMpnfXJ@)!jBN`L2I$Rvn*%ylzZBJ~#PGiGalB)rML+Kpu>WU1# zMJD1soXo&7!*JYhX!ZOa6W6zdU!(QS5b zSOhL@-xcK*u*nq{2&;i;QKiCZHEic&vSx{RzF@ZXP}YLdigWm6XgM)Ol{J*3%EDj4 zuw8~TKoWx$uFv3Aci)D<+pSu!OC@gG8A;!0D&q48+@zoWF{XUD_(jgdP27$L7JEFK zo2XV|MKP_Zx<)2QMzB5Zdw*H$J$)0J>HQO6S(+*l(6U%Usc$VGcK`08donl5&5yB3 z4}2m|&j(4Ca$!?N0@7~OwMrv2pk5;+JkHrXOXYC*Y&PlrnOKGP{O=^ih;`#&fqk)@ zRqiSuF~5EFLzXtFZd`Y}RzD$@>AT!)BbZivuCx4#wcNXCNeUNSkMMlV6fl^avGl8@ zjh6U6&B6|NerlgS@15z&SeJTob8J*+#W|}9I}EA61guuR6Kjh=p^nc936Jtu^PwMR z)PQ9wfKCK(O!(Y=_oHX@ASr$T-E^2`#2IH_f#dJyJ`Z zea(Dmx7W{)goK|XJj2LaKMb~JC}O5u&=c;nbat_c|DlDk#EP;FFzDo5J5D1rMW4zU zaZU$P4#%=#@;YFV{Mk27U;k7)pka(K+v&4vA-P;K0YcbZ_5FK*)e;rvn# z6s*cBUDYZoU4!9xhdqh#uDwNYJOQlCPhY>jVc;yY#x>wLlKE|wCu;uvHwbL_8|=QO zzx`M*_BDg+oc!^A#4&|AjknRoJ@-5=vgP zdE8QjQLrdK#r<0_<@STPiO=Xk>+K|Dv@b$On&QR+Xm8`dbA<{W6lvdX40Il=qsL;C zB60g`J8pf zx_|2mr3c(eD0RLzc@#Z1+(Gf)Cqg7QR=)sQuSGHd7v+8PX9J%f5b~zRYKMQRQb4{n zoynFFM=9mA-TxFAnDX9qx-v@E)=k?R2NeeN~0aF8I^EJd8a6U!URw&#?U^&LC z{@PmA=W!DS7aH0$y`2RD=ITxUSfVL?^&=_cgCR?JFowK)yuM%S7nN)BCfvhE)!JG- zf1lGM4cwb3C{Q=094@4aPrn+k)&WbW|LuA_-5Bo7K0QFO!q%N0*zl*zn6i2Ci~cik z@~s3lYZT#W>lOLr-aw>BW>eUwVb4{vBCZRqR84+xO*663FmB*h#=t$iMwcqPSM~I>sIBKbkg}n5Qv@g)xUw4(~rKCxiDDetuHk#*p@$njPm($$`B;+&ws#&PRTc;78c09`K7!5gboWNA7lpMcHyA z^D|!RpBYT{sdeoepttKd4tx*$^VhGys%pI;`TW%$kJk?8FQ5Hys`UR*)ou~)3HC%s zb5y4f)3t`tQSQ0oH1L!ve#|C^{nC*!6G@1dLV&75*8l1znX?9cv$JZU%Sq`O%VAh{ zzc}e%?XBqGm6StCiJk*1!>jMXCgW>2uzbgdYJ3yYQx^|7Ta+klE;EH)70n7;D_p*A zAuOt^Y{u~V>-(NZ^KWvuSX6y~Iq3;Gr&=+oNMqzUwW+%6-hly2(ayg%PF{Wc7w2QO zW2VkT)c6?1W_6Geu^Q$xP%7Q<*t!I-5BHgTKT`k7n;w)!s_IVnT`8%({)tHX7KRL+ z+W?iO8xH7;{B*n3`x}rTlAe^75=TW`rEiSi+@6yib+fjewl;}$qHz@wg9r-C!$%RZ zT$|;R+k+@r^uqg%Q1P=-KUP8;oFE@-i1BrGmtN$Nn*IeUVJ{&gZH#OR?=kih)#ma~$a{h%d0NKWzpO>j{_JW2e-osIa`U4?TuP z*J8=U15Dx@U(9WUE5_~Pq=+t3?Og_d9u;C30I0yFGCF$4O-$#WHm^|x3eB$Bjtr+M zlT2a@lRZQTqlQiW71!lYz)bwv&_y2`8R4HfY;7Z@I6gRw!M?$%aNfyH3F=D+mpft( zQEPd7M$5_yG1bVVKF4VJ%eeo~RQuntN3t`y>BiAsL=$0yjxeJ7iKf5ib)#VIH-?WHt?;3Z9qQ zYzf;=zkeX&9z6>$Z~@V9_-Odt2AKRAs3hTMiT&} z#qGBQtMo}`nT`wQ`^@*(gslGXld2AH_I?~EuI4SYOvAFb$CZ=!x98r%3_Iy1MsHRs zL-IXI0ov`i*;H|j;}ScIW`5Z?Xh$Dd!#{Ktyez7Mjh>9Q=5Bf}rGiP-xo5cm~7 zICpf!Zuh^fg#x`H15`=!oURK;Ycj?)w|m;Rw_gX#I-*SyH7?z)9YEZjP8Xy$_)T9ImE-YMk89-(Ck#`*!E zEmlC)jmx9>F8tiX;4q7=e%7@ec4HqjhbQe)+N&KR0E&I+xt*NVmpdsc{SMjSwQ9j= zKOpMBctpXmj`wfaYcluSY};nf(BeRY1jV{rPtpvdpDI-is)sFDH5Tailqpmj^}jCc zjqS@ljE(OK+~;eVeU{Rc=22+o_&gxFHr$m`he@+V-rwhHlIg)c@sLGEUXux%^<&)# zGkgEjgv*rdp(1JJILbrmoRZLb=$eC^Az3COcdwMu?ik^oo@Ln zs{b|B9Ev>2IxV-49#_6G{y?Hx_oAI#MH&d@%U~WSWDi^pD{fF4T$1JgYm*UbI9UII_5&u== zMCx$E;7SL6iZiv?6#{+l^%5SxJEG2U*rt5_g4rXv!F$QfY+@yMjw5(RqGS(5Ivp4` zy)J#{eHE?9Vf?^&e1u5@cXcalJWB=!(}JL2!{+n%mog6v59Zhg4WrEiQ=zQEpV-#d z$(9U7w+mkr5P7q?O0w9cn@92$Wb;j2k1WOQagemQGA;p$e5KAi3fN3+@FKKA?Iy;KkY0dBeE zTU#f!&T{pxm-X6Rdz_$690@KN4nH^DHOSp@qsMw~aRaPd7cOd>*MAQjJ$YcS=1mhH zn()tDLeIxQf2hN5Ci8x?>^!J9c(Dy1(~9r2jSe3LJG)qX&olL%@vh1Z`;!dK;+}Yb zjHh)s)nZSgMY4v|&}ptM@8`=HS;zdSxa4dfBoZ+?&gpsrfN~QtysO9j2sk)91wSTg zWY(81hFx{jq$xIi$v8_|HFBpd`0%-qe_gB`V!^@!FTh~CpZr^b`S;7Tq%s=){>P-9 z7U_a|g}AcsSod^Qq6;y4!<(|bEtK(`(;XRr3n9{H+C+P;wNBwQoZ^WX%y_H~md~a( zHL}|>Nx5R_C^H&J^w_#*f>y4s))jt5Rt7+qu^=mflg!K6izL?MGyv0g_!?=3k=`_Q zAZtDS@W-XG*AK@#08S?uOLCqYU^|vVkZZ(T+Ut_pV9_U_q)lvlEDp|tF?<#7`3JHD zR*<;Jw->JMGxe~$R%30Smp4knjv5=X@B&fu={Lf=z*J)AN#xSU1`KxDw!RJ|RHD?{ z%vwcV*e6iL_o<@8dzwUD?2+uhP=GsH%Qq7@6+)`LX;gA9z0|3mQ?YeC-X_OT0P0m| zlzgJaz%P1uo}IAL`}>S?vgd#{$I<6R z9$p|Gg50>*`^uMeqo9?47wKXG_(G_zNujszD%8KU+XApL_X|Du*YvUwch`Wk3qtvo z_PsRQo#((RrBwWfMe4gQ_J$bERvl$i$GiVg4g6y{)~f*`#`xKc(aAlayh?%-FnOGy za>x6b4i=#!=@~oZE^-~o$IV99dvA)iq>b>yx>*$WN_tQ z$A{arbzrdTSDQt4NMB&qvXV5EDyYc1x?#o1pAAY=`c4Pj#t%PGiowGR71L3Ph+__| zJg*9qgd^lWa8`9dkzv_c6yBMg+ULm_pRet@Ntx6w-k(giOs9wRG1-bzW#@5@ix=ZG zg4vm5P0I219X*Ct_a^@|D(SjK|VK=UTB)$bDYgJBZL2c&B=&obMMYYcCXETM`5tGwX+AE-H z_6ZH0s=GNbsoqzzy`>_!dCt%er~b}Kf2zk$ZS!{RkzmJ@`a3Jbp$ejsI5?LpR-@(f zCTNDp86|q9#*(?Zj9`h7&h~``TUNtFiQE02snW9vMdmixOuWF(5p~k(J{5kKnSCuY z&AAv)EfXKWu~x{gUPwKA?DJ{At@DR&^yHCdra~h(+%N<9@^5Jh@V+;zds#zn(l=hn zh5uAlQU9xb>36%P3e79d?a2Og#KkiE2NnFw3T8)RzuT&R!(d>lE7{*h&9cMM!xXeEB8u34JEo1*4j5TPo5tgU-^~Nz>M_ z^&!a4q5LudCovfGvvg2`biePfewm;MwzYZa9b)8xy9A0AEuagTWAfN5%2rb@ypEwP z=-rP`uk1TF6tpB=F~D&ds&2XEzX4|V9<>*?_gm%B8&}RV=ascpS-u$j;uEeYt;!#e zuj&vLc3QfNbQOX2ciH3_0AYb$V` z=^wBv094*>^Qmo9+Dq@QvqB=h6l4ScR4JBglP)`K4Sm!+KsJtS-kQsG>77s8)zTie zV&ugS*!_^arNVJ1zv5)qUI&y{-Mwn1zzd}Bn}R)x_X|e`W}8D5r_S&{{6V5vVKQZ{ zP=y>ILn#0OM&Ldb8@H#|Gvorv;0ZUDR^{2k;wtL%o_<}WzmW`2pA4q{LP%3jyo~65 z!LLsHZm;OFc4Y$d04zIxjhquIz4{jtlPfps?;;rhs#_F>j=c=$)S52)V^``uIqwoi zW`*MN#o2cnj{^vV4}aW(6~3AO4n|CV6fw~)g8kTUFEhVz5O(9qeXqY!EH|O=`uzAG z%cm#)=zQ*aXN6GvAz^^tBU3;5H)3WD>58cNUoQdsc^~OtunWrI?E_i-G!6er-T8hw zotZB>I7V|Wm&O^KRhj>|eE{wfY8VS0NP;bx*50M@GPq!Tt*GWBUF`Q$>$0P@vLOikH&VZHq78saGzu2y}8 z%0zPjrionI+9MNwjcm-RpCs3X)r5?e)Gq&_X^om~;wCd?rU%AnXMGp5kol&o9XNGN zGgHE3jf{{f(=;uDLy%d)sqU>Wpp-5m)~~eW{yx+2;i%SpHKK)Yht7@M@in#x;)-xL zr{FE6i{M!8KX#?GO*5{#>ndTt6`&V-G^c!=fj$9otuBad$y{wLV>&p8cFQJl3h(Rt z0Vs)#?Qg76ncC0F&EdWWiGkB)g09j71`XWNkQM5+;dS0fr^SO;sSZah@*k!Jg9Y3x zIyYaZz45S6j|MVS?UjF%q5huM2^c(K+tFmWsdxIzt9&CboLe`d)9jLR7m=21k({;_ z)Syz4?d0hSpI1v7xa~D^<>FYcC8Cg+1N8vlzZMNW>7PvX`2gNhN|73LUf1exzSpcg z(IRORg?aW5gV{6OI$owm__9?**%Y0ymBXe?t?UNFOiEPG+6cSY4=B2%BPy50WNVIO zOWbZPk=(Sd!}{)ZkKd9VOl34=0FsC~rL(XY?bUqj&b0keOq!!15VJ57%*j3-j1cw% z9Cu^9n9Kt6R}m6`NxwZDuC&L~D7Vy&OUHkQSjj)z*7;YpGXbqny&YKe^gX+sPQ*$P zo@F4yCKPJiUrEoui?{o%urm&ivpHR-`+?+Qj!>R@vzbMwZoW>c`5Box1M!k2E9i7< zs}|ZCU-_PeKV0f#VbstPt+x0oYW7VreSEky`Ftz6X<%F!@+WFOjCxH6*nCD3Q zj5hN@`Wgx4`Bo^j*yf7cVCtr6E%a({2c52I27ebsaRAZ-EMIAD&`dOm;_L%21u2Ub zSh5|g-_3PJ4a{W{q4)krfBVv(49HgrFUOopFMLrr|273cytF^IGYuE?nKZt;S-9W8 zr|-P$5f|m8{g%srEi+i9{7A~NtjXY|*ZT9#4wC~>6R664dZHAoq0)XwFOINGJsF2m zHqeuF3j376QYrj3F_FYI!Ksh>=32QoTS@y~Uy{@gpWo==w|xR`A2*_G#E(JY?_X-%2g zDSgk>`4ye&T6rf;b>khC?zp`(Ds8P-1MBZ*QZ^kDnUr=GpJ&CGd4H29>C2cN&{?g| zKK$v0ecXxkoBQ(b_5^vPh*OX&7vs_qm;}G7B+O0 z&0isnSC)o%p47@C2T{k=LC|6+{~)``6=gZwFzeFBWCNxrWXLcz`IN$#<{icUlc?OUP=dydGb{Ts-3(nbO4HgoxK|&HK z27jXK!Jw)wQXYk7L27s4Sg0rv=|ND~=FNtgj=K7recPb1IRVaIQNJ^F$Z_?z&xX=rQOCJR+^Xp-?VAIEQ<( zc6CK0H#o6xtk=$cbkvmt_I@rCzuVJ#NOL>Dk@>2iQb8I$43^Mx8ubXx<~Mmi4bGcG zaWp|@?G_cpg?Xt95I~8MhE9zeyOjmMRXW2J?xfj8KYjcV-jyMnJXTzE7+b6Ct9Nq# z2|c*_NPh4Gx%}3L2dKrsGf}LNN7h^ffJr;jOPnp4F^?7-XpiW1{)87evEFWo5SoqZs60Zk6U7OuQ5U`VCZ zKcpt4b3XKZ;=Er}2|u^}Zy;WrO6;%RzMgEye6ob7b#P7`Jr<2n&Ys+!BS^C*8jZ($ zSA^3Yw67<-Y@7Ue&(*<=rBXRNm^WdELJF$e3wGpx$8u7R2FPl~k5*m6S);V9NtYiZ zYXj^$_!BBco3y(23t4eF16_P`9Vw4RLPuT%4@jW3~VOd&1- z6r-L?8{J8HR^%WLw<9kjtxb@n>Qn3*q(09Y@I(?#ZjON6akBzXX9VvSdYrya8mez^ z3<2OkqF~0xF+TiP{*ZeKB>Q#qM??{d&@XZ`14`sTL)%-Ir*m)4(|$ZQ-z{ms*VXa< z@xqGIaN3nhAC+OF=i%+f9DavZ-4)fL*uFW*I8XlLBZ$syM}%={#*9m6VFu9=!IN(T z*nyE2G9XP_;qq4lG5*ehLN@B>y*_{(f^9O0NRUY{orA(hOz+j z3(*sHmQ~^?OHgpr1;UKgzX%pOJJL<+xJ$qXz^596~qDK#jatiKxtYys^Ih04Jg_ zB)E~4HHq9e12N79+?o8S`74W^G9w4OGhuuxa)&7+kHg_<>*RN7RxU~(}?RQXp zFZc4e%INWvYemm)5bo=?!XyzKL#_H?nC!mIFzbD|CN=E_3NzYnTsL`qIdc`d0;j(^ z3;uj|2}NqU#k_szVG}t#CN-mYz8(v{{OTs?3w?h{_e=0*zAas4IdGOkWEMlfOSTHL z(pX7q%W5bE+l5@s2}u&OPGZzzpYyp15%Z54LcW%IFC#;^0SyLAZJUb#NdOEb|LdU2 z|32*TPtjO%KXA89h?#YS~RfMcsj{fy!4t-pa{P`M6sHeHxu(K!yg|OQ zD=Q)oYuEJ3V2@L^Npht|z$5UNz!am{^4y%wF4S-;7KIJx>1X`zM*rYo1UE^>hYGQ+ zSTg>MMCWtJ%?CUD)kSuEfB#*J?pVq2829E4gsaWtP%NK9fL|55XrCC;{;90g(ETU= z=0LQ&^vWKBiy&hc-;zqp-JTb+Z?c|rtKaL0XS51b1F7KPIp9Yc)BMWS;Q;d1E{9i? zC2r(v`-5XyP;%R4bZFTH;W`y74SVWnL{@io!NlP5@wC`Jo~NaAXl9I;f>OEnVISv{ zndOe>ZIj_$L@0STx6~{-Y{i|NNn0!HD}0&znAaB3$|x_KL3}8anGQT8h^g2p%CW%A zJP$aTDxR2T2*h&FXPnu|EP_Qo8EnUL^+X9E>plE6;sB^SZzv0WjxxDt!$@L6b+-K= zYL;=;zqpYSU4@S1rqJI9GD%JU9(-ycF?zeX%2|$ti=X zS7~BNTU&qw@B1kTCKUhnBLquvvW8cM$UrU)UQM>OX#Ve}XVTdL(CvJ%LbcAZJ!{j0 zaNC+6?mcd%9N2x9C9t2Cjsi!i6k$BA??w1+v&14Ff3I$gY*pCU>yk#Pw8z}(CF%>w zhR5jSgj&HLxBLG!#Cz>M|2FCqXSs=kHLNxRY=98|H}A{v-|m}=8<0OoN!`fzA(m|K zSR{JHJ>H9p$|byekC&5a?8=Oe%inZwe@4iCl_c4`>)bb(mLi9I#8IGG0D{ATvlK_k z(jon*&Abxu(`DB42$St$y@(&4+%2DB`!E+{u#1Z&=!iMi)&ZF!IgeUk)qDt@-u~9T zxrW@OgIgz+Xw_Q%?nx;`V}+6;a*(}szE3$%S^X81J)xo*fb7{>EPbe=p6`MIzSCr? z6-x7%C((p|0rfLyxat+?m-v~eD-w)v5-e z`c55YE7XfT4!>+Rmc&3jvMg1z&=9Kf4ue@rG@UOq1hnD(B1rW_By? zf$9`GO^yK6mJD4pk2+hEDT_ubemvTiY>7lgLyEv)ke-M2q)}60GX6mZ%hKP{5|{5G zW;M0RyWIE>TZJmJCG^(wCP+Y6)7vdoRh*zQ5GZUssQ}Q5A!kfxX2FiLA^IA5!iz>l z%un3DoxJUUfzQElRqyAc`xr`7Yp#xo+0@!dB1nZ5KLSSrV|xw);}LoIW#nrYVB@f- zMWvub8P_ywm_9j#Co{VY5`dLdfsi7>O+6Iq6G|0KlBT`06($R&PhJmwb45xe=?Oas zsHps-cc5uxxb5nE-c&BzTG7>@%>6O;F)M2|k5(UIPVbm4?KSLFNNG?n}WtHw{Ra2%QsaO@Jgus*wo(&RDPw#=jBCY;5$Ni@F ziMRHxZEz)<`DgcEIT}Egz9uyJu{cKO-b6oM{KFzq~= zJ#E!^RbDUt8pz}?!yW>q$r5Q&ah_%XFV*(^W9+QTx&Go4Q2H?_L+mNk$fV?^or1^xk-z@yGl)yaboEk6HBIR^1k9R{;b%OL4&xY#eWJAWVn_Zrs4t)w<6xXQU# zH=cgJ&E3u?vE9-;KDBykHsvh@c9J|rI}7-~Dd(GX{-8JbgPj*nLZ`aeXy|%T_d@iV zh|pJ7-yh^*Ev{vLF1KiviaSq#RNiz>sen_O+CxElWfL`VNUicT|zZ4Xrj z&vYJ5?29c@|L3?%*>jXDqq)_JviSm3Clb9)A6|ALY6TDRZ`vGPj=O z7dN+DXTHgk9Xw9ma4Pl7781FyI+d0)v+IpiVK=sUdQf$0<$YI(ztt$J>p%@7=1?06 z`prT!;c)CH0dw0oAm~!SRRw+Q*IKdm-%b0a2ivT*^iGigLNW{Jj4CdW!0yb&o)>;} zZMe###NlE<#)*wBgVJ0`O~X!Y{ZFf~pZcz6Nvs^>L0D5zb^K)&YS=)v3%>sHjJ!{cZLNvPDXr{{Qx z0fFSJ|1|RFJ8om4KUrDm?od@7E~v<8ErBCCA3CPhK_ zv6zAzI13JtGJ!`;wTWxNJKzBW#@9opC75KU^K`Pjn53!5PY_9AyR!gFoI|{7fC(5i z;XWgpF_p(`v~8zX-s%*8nkOpMRWu`4e+egC)=HQ!nOd(P=FN9-&Ii2l^sxUK&zCvcZ?L7G3H*S<@syhSl7P03@h zv9Y<24mhoHjET{)P{nFt#Z*Y0L|E7YUFDv1|8c7pxinyviXbN!#>9bX?r0Z>`sI((n*3M2hyOk&> zQ#_dwe3_8ix3w$snWGV6cU=iT^KbQEA$5r7Mi|u8RnPWA`*K%x!HlBg zpxM+6vMzN8P+0S`GMDli2B^MD#*K2L);#{m96&PPKS zOz(ed{~i5@p_LCAv}z2wKKN2x?e(yS!jtRx#DA$CM)oY)D_9Mj5jF~}w=TVUuyMDc z^Ir|0enSXto=b;1>l%h_52aOw@l$Bk-bEq2P7=nZ%xT_mcUc_Jz+jK;{u7=X*$~#V z?y59QrOgj|tNFiABT;ru0MY~MrCY5qgxcg}(V*S025|iQt=3Nc<6}3Pu78}$3Vb_x zK$0QptHN9AT@o`c-Ps8|JCO-Y^k;%TS>8!<3TmXOSQpwN?}{#S|6x5-iv{w&QN{UB zO_(%Ue|w-jVWwi)G5$(puSNVtZRJ%u#f(+SmD+lVQ)eoMuVz%0n}AEN9J4NDp7K^t zA*Hxc9`)l;40YIEj7Qp$G_MTzuxQzVD7UJflb)_P@cyfxDT*7zvpA31z_|vCY5L-A zrdBRDS3+hgEZ@F7RpmQqqM|T;Tm#eFt35*_0#D##Ee@ERhWi9f@)^x7Ep&qa9tnQ9 znvf28wca)Z(IL8~aVNj+eBL8$qP@7V-f#!%L$=9%qS#vPWSCkMBHlt29yUZGB6}t> zI8*5AP)^mY=s#sH)JH6rf8qlaRI8Oh|CkEkHdH==>+v{a1}%B9S|Jf^Yk>G5d#cmw zi!MR$B4p)t5834%^ogPaG|TWNKoC)FkE-HI*A1&p+pgDjH-v@I*YXvfm@90!8ordZ zaki#PsF1Gpe`@;>Ul|P4RjbyIw zv#s>!7l%xA$C(HQlZLMP8zn!bFa7BlAhlBxEMim7=sc0PXW%(=aZmlc6ELnq@WoKY zhgMC|nrY0Dlwq=yvKcN>dC>O9D?scF0YBbNz18&!+?~* zh`xlfc1xc))t1OhnNTF$3CrtdQn##G_WP}C?5t`^jX^V}^2l-W&ObjwtRv(zssx@b zXeV4Evw>Iz(5fx4#x|2(gudEO`AVQ1aR=oHynF^UNnIj0vZIH6n(cUpsrOC<1`Z0n z-xxc{O>3oAFk=p2DJg0+(#im(ZA{QLV{Z0}ek-bjs zTY011x9OdX2mM9zpacUJ=qjIXbeaJG7E3g?)#cR!T$ex9Rj{@06T!N6Yj}qYUiy!R7IfWj?@U= zAh6e-VW|!f^9fo#e13xHPBQ>TfxP%>uSxiL+ zf6<-ryRaW;JjG(?BPJA=m(V1*i3;eRSz|=afPD=Nlj3N8D-I7?E!E?43)G$d%ci*v zb+p-nc@8G9$Bn^$oEnSH9u%(p&qxm z3M8bFDdXbOvoz0HTa5$dMpnIoe--T)%HDGl* zG%t8o`>_XjuHI{`>;o^cd&SWZ9ej*c8u~O6{#O4M`Ie-N46i1mHehlKsuBEB>~ZAJ zu4ry2S%+LP`pvlvr@51Lx%2txs z)sIZ=mBim5!;JN>qBs37GEqKguJJne3IuWg%;2|?v~q9AjP+7uO)+!02btAtL`SZz zISn{7hClECYZj28y=3Tv{x~nFckAp}zP~zzfueU|#zt`L(bRf-6l0AW-2;Wg@s7{M z?dwE-_)v=p^Ir>0y5t_656}iYz*>DyYiN?zn04kmATN<^aj6S)&f9Xa&S=oK-=C{F+`Gql3@9Zb^vkVmfdpq9- zWM3ci!;Aw?{{!9vwKl8;zJyRvxj0nND+RYo_mBm(?QY$;lX{Y!CwU+bH|$UMiy2Z7 z-96@BA$|ye^~sE%Yko}k^H9yy7Fg6ota2AaUU}NG&H+Q;jq%&0vJH?V1WOV@s(ws$ z=7N8}OW5t?WSTr;u~(2;1K6gR$NCvA&1abuM!ya}Xeq*_j>(JRB(7zJdN$Vuy8i&UbBRV5alLi0Dyo#0B&nhJQuZSO9xXXF*$EaCHWr|~616#jPS$@Zq4W;lUnm7@-v3Fd`l3;u;mgln3v zga=Pe{4FbANSOcZ8E>aREe5OsP*}3_&I4Z(yl!F2%4`gSE(>lr8H*!Lw40NEuJw&I0$1P;$R)nZxDIj9;`0P&)R7%jz zpUoo%3EZ3co98Iu=w&*3p;wYPQ25s=NBA4;KN^|xQZABly?@M1`Xbzl>7<5 zJtqkcdWz7X;4c`;{%)$R0G}=EUhx!axcjPKL^l42<08e5MR$Cw=`aE2~1V7Cy75{d__O^RZK z6|7g?gF(k)1yC&ptJ)>=u7ktI@BoYJDXc=k;beagKqzBLy}=K72OE83+$E3Z@w;(0 z{l2y4=xRHjO-}XCs=~&15&PmHvj?_oY7@h!WsmC8TB_~g zcK^iQgoAEC1Ue?xRM&N%Ord-Bcamz>z{ETZe2FLSL_aMx5_*xkq=;_@k2*o|m=H43J^@&z0Msw3rjNaSne7`~j zXs*G9Ld;Y=bl>A_ZkiwXd*A@xytw^d^s#=co-+5U!I|Q6vEJ)qN}!wg zd8b}AIST*L&5AUAzIDj0)65K$S!iZiBB|^d*zet(%SPj0B+_DwG%~$)ua=JvslO7K zKcI~POOUZK8IB^|)#A!llivmrpdInItryTR>c!HX2ZaWU$-nvndv=na+stw1f_4c) zzZ=wNi%P`6tdk%k=rAP5njm+9xQLPNk}w?JE93QD18i z91k+QsZzVFfHw*N`6oiHr!=*1Nt+Y`?r9>HF0E8P0kl#D&o=pLM3M{b(RKgH-iHzD zG!H;pdEwrK9|7?`9y^dH%Sx?qzTB550^Jt?_(aKRwCd@{WTLU3X1#R)mGR);+B=N6Om7%TBl>c9Q$hpW41|h zJxRkj&%uRfe)4<#K~^P?AML7RFjS#F2pKh{$R?z#2r#c+ zny$1TK%!1dL3xmX39twbGPw%9Qu}rZJah%*fZlHAhn75G20FC`Sk|i24Gia~)!Zzptl}DAZT^~US>ZV7sFV~FrE6puu*% zSO#s+oBF^#QI<0^<3r#MN)_Mef;&r0&7XEcu^_*hFQy5^Jnl&O3^W-pon~}1Rb0}y zJ0P+LQllu@@VI_hd4|L>aCK9o^Xn=tmcf z$0N|4a3w-6fgIkKdRZw32X^rP!`pj+HJNq&-a5{Rqk>}vq&PNIKu~GYEhCCFr3grg zz#tu@1f=UApj4G6p(;Hz0|-*1AfS{)YUqeS0trQ=6Uw*lfQrw&@A=O6UGF)$=JE2u zklbb8d+)W@|NmP!T}Y^n&A1;$6h4S1tMGVZbN z9h$|EYoGZ)Uc~xVk^2FW{5nvEn!z z-ev!*t9oBym2X7J3MnWp0Pe)GblEG89;deukA45VNXVvll#w0D#;^L62Ey@!ye$T1 ziYlHJ)i#A6KJ+@JD921Fe9YHGEg2O65@waUB4V?aw}C<3MQNq37H;Q{E&&vlguojm z_ysGz$QWb;Ns2W+{p-R2T3U)KjwVF?FS8i2?M&^Lm7?m;W;(%?x|)7WRT)IDg@;_Xq?u2?51LOPj@C^Py=k-T0^Jzh=`KG$_tmbu{D0`-F%R^h=OJuLxMH$=!3k z+vI}eZSXB3_G4U5^HhL;TxL#&jo_S)o?zTUR7MdG@GGKBq73_)`=vJWmsX1QBQjCM zE0;q|6cX;o;GTJ*#lsi=q#xhkdOMo%@$B6@o+)KD7Wci1`Fd6HDqo7Y_0X~S$`bv} zW~V1Ja#0URA~z5V0)xu7;oe3qOQ9^#E!S(+y)EKYaDVK+huH#t!ZMcSrm!UQmbPDm zzmk>|l=KX)Q^sH4GYP%j3AtwaixcJJ<6v5=?;`k)IrT13Jz+{iI6dUye%Unjf=2sf zs>f;U8uqW_{c|pKlBhPlR&0${>tJhYKdDT6*~>?smPlXV({pRy2-@VXQ6=d!^G z_gr<;BV}@gex8hd;ci8|j#DmV;CaM}Ue{ylk5;Qn?|?W@-2^+r%KnbFpAEFBt3%a8GN2j#3bPFoAY7-uy(R)ekyQ35o!@TmIM@vMRmMNRaTo4Bj{I_?g7Fd5YoaFBmH2*`}=mS;JlEbbb+p^@EZOOAc z904trc03UILZIEOdqJFj6ye`RViZE?ziDNBuN)2O-k3nra5pB9G#sLChBVyrKy#); z$FZ#;#VCZ`xanoi2+Z}=# z?6Hz-90zl}RKJ6B&KX@Fi4PTMN;>N{m(-{ro6q?j_tq%s9wp$yS3EPHV7R$zsgfXz~@7kwOWqpzoTGp^iJrbD(vh36IX z^JuR;D9Hiv$&e0lHaT)Z`f5vMz6vqs&@^Wy6^JiHm$Hh&XX*gDOB+p>(AR_WN|-j+ z4YX0E^M_6@1bj%#U3%IPhg*88#_Gzw`J^Z@XK5Gg73Ui#lZ}B7vrA1>|4Q~=?*2jbk&;IeawYi#QOqDrXcF+wY#}(^Z9dgsX5eAT1Yl zB*i*ycH1?LA4MGjs{)h7jbe;>)O;M(2=-Vj^1m~iLXV5iQxsdAF_q0fB9)*@(Yytb zmYS*b+#Lj)5_Ey5?xJfS&#XIqz!@FaF(s{$^Cy=0<|ZaG!43juCMPJ}T%swCy@m6u z8fZ?+x4C9VZto@;O|qQ22?^coB87;oxue^XVtvOXCgWr|1VxCVvnFG-qdb&9t$+nH z8c{^7h&R=l>T*EKX6YAAS)25n@b4}Y)i>!zS9u;#1^g$vBx^FxDN~1zBx_G?|C?K`VW5FMT(_nHb0zYYBl%s$tTXpXSQ47Q6S| zH7=Ln5S*6qH_L@jh&EBXL8W!fL^i|Ow$FGIUE4i=k4w6FYC>6T3KUDv8k?ze*ktlSnmXQm^n*UC>+s=)@R!MeBzHe4l{|(~IEr zi#PgGArtsxVQ@ko=X`CXNk9vi%wmjlZ|&~gSp{R0CP1`mCok8nJ7?8tXD!}hCz|V0 z(YT(3ky7?=Lefhyzx56SZ>hsA1}eDKw1t0Assj1F*fPTA`g}pRp4!!WN#*4bJQ}Uw zSDy=knAT6@JIp=#+F|958?>Org}^RFX`A6`ipqfbirH02`2c2bMGfOtRk2(kW%AWX zgO5QUXe|V(&Tx4AL}`E-v({6yLkXT1XEHN_Ab+OzWzMJ&&MLLo9rVK_1t1eep&>zO zyhooT8f#`46k+r=i+0P2~^Pn)Zi$xCr z--qDwgfVlR(@=5;e^^)mqJ=nGk#DU_5m@Yh^&3N6u4N~OzsRxdU)&ueXgM`o)RFZ) z>yOA1A`NPubAg$#0OU2rHr-tK*srzf=9~%B$1#61C4rMGi7<$9_>^(n@nbX9kSRCc z=72q@#n43Am3o444TAFUg6Ue(DT6LS6M~C4%BR{>GD#>#gf^h+U7gg>z+=Y)7v{%v zp)ktY)O~C+&i6$=%4CLfEq>0XAKg~zaR4p@mO$R+TAD~zwWAgp4HBE)%_ykd=%97P zc4D)04-G1D;CH}Rbqe;`?@3Qjhc)kxzzOlm0O|jH@_(o^Msm<;3LUUB3FkF&(5@_s zFv`$D(z_Oi;33gK=-w5f7bU~(AqhUVzuHvVkEi|oiQNG0lQcC)B3!{p;hmq#DOuW*E@0eY)bkY`3}j~QLjyrhUq z$PJXJjqEB4MG$_%xT54CXOw(4tq4F$f;=L`BF5s5@t3}gD=Mm~Quv+n6xNMs%JZx) z=c?v*!KZ2k&Gg?=dB3eX7yn}VG8-WK={BLXUjS9whd|j1n#CpW*kDCt>I|s?)LL@; z-+(>_zB6!ef=r;cPvz`*()4lb@TN>|8^-4OE0aZ6U7G7+DWvBRE{-N$M_Pl%y~dre znT}Yvm1!G*@w6$?@Ytuv<#>(y?h$EuIK!(g?q#~$&@I#PH{mRIhtFwf;*#le7ky`n zVPW{-rM(}YUJ%ry>(baCIrh3w>XnC8ui=g&Giiy=eqco|E!6J#+LAOq8-;M_o@UWx zWM&pyGA*vQcomDXvL84=>mn_;NCdjzzCI z$~T*J)c5?d%s@cIq|9~FRae;V@ik7Knd|i(D*;GOhFye!o^5q7z9Ui-Pg3xkaSfw9 zztZnCO(qSjh(+Zz!tPbI!Q`<~6YS;xgw6-|_|Z|yN{pN=8SsMA5Nw%SpHPKrtL^zF z1rfhcEE-q61H(e~X4;GXk^#h8$9ocqUiuREj0-)OveCB`SDto*?iJ($ovcQ*x|kFz zR0!--3_Je_xoN_v?ckOS3$H168xBvWZOSNQa>9@o}&d`rkZsNFH=lsVdgo80FbMs5L+PZUG=y}CV8~IBFmI`)t5IAG| zBeh}(*pK+o;K6{*++L|yHV$8M1HH<{Clzs42ny&%#N?v3Kq$5;a;rq~(TFmOtGz~A ziIh*@6uquXV2&+!#)wi?i5IVQwzbmgK&BajP9xRA>DyK1qJ1U@&|JN7$<}&T=(iu^ zcg#q&S(HDZSK1YJUfUC-$-^m_T)$%TwJ2zu^;X&suI{a&y-~KuQ#Scq9oVCvHi8aM zuW8@I$DExz=tV-)=iJ$(U@+YIQBi*LG1tplxW5srZ@n$mi^Tr1VtmsEIGf31-ndp#MAwk7OdIz@_8Lk2*jT#A>8BkIItn!Rel+9 z?tt2U+7=v?8;%C8A}ilOFqM^xcZ}eH4f1&$ThHo zLALe^8$O$kT(v>)Ha)2>bLfOhYxuY7H>0XP7MS-adsGgz4?!AQ0H1hyjdR8y z5gqxgqs#0U&Zsi-IQo?ygLej|Nz+2ZJ@%#~z_JuFW(v#GI(R8vJ;AnWnw)N^*<{Dy#vmp^+^Av1U57r1Z8 z6U@oD4@cZ*3u!UqY@9QmpI+*cAHDuLXT_X7c!p^37b3O@yZrszz`OF)0P^nkkZZsD z;gsEgoQMe!gd*1PFBy1JiyTxdZim{NRwM3_GIY0WvCOhVn53s|ApX_03F%sOcUgbu zZ&YhAkfLYi)I<2XvR8I>FNEOxQL1$qT`6Vj+8Wq&pbfX<_IRZT}n+U3(-VqYFd_vlFjHH zFsD2jH1u&VL0AEm;1%4^kWmnN1l2nta6rL=(NV@5mrp3H%h0n21Z;%#4fy<|Iv8ML;SisO+#4JuUai@di%=F^RgO5~!5s?>@={|DSe z$Fr1Ci&}4D98S0HWw2)A1WYeRIB@dZn_2gC?Bu%Dbp8dbv#g8hW^h+drfiDeny@L| zWf3l!7g&wfrm8E6Sb`G z**B@5q}O|( z#<0uJw9nTJmf63DI8)q@$P>6^m(3~kA%^MdhMQsom+Z{!l4tP(X}XG;(uF6~9mvjF z?iZ0%aplgl5!7p(ymmttZ(SHbN~bl>xs-xU7>g&p`Xa*Cs=)!~?Cr5Zx%(kBz6SWP zd(RW$OodK|k<`Jsrl>gBvhhuPs(6B&4*qsDMs(}$^xWK1#IlM8Ny5<5^_^uqlBrW# zm*(yQrCf{f0B8iO#V9|l`0gn1T|$-wpuMqT2}|jwQTy42ggl$gR39)WJjNU{uX$j? z8rI@%A_S4)h-LDZIvmo_=Mju9*n=Uzj>xH1(Y013&)Ot15lI427T$dNai2D_U6oKX zf=3^k8*EBl$V!aq&t(9lw&BxL5HYMDMyN9RW@?7`zVvNT=V$a&a$k>R6dXdptwvZ& zsRpCHef@z5Dr+|Zl2GA?L}SDGuw+F`DA<kHW(z3!@U+Zsi@5v+# zO(&sE5he|~Ln<9Xim*wopFi$x^b={hVBW(SrQgO0{9j?O3k-a)di&iM)x71^9EeOf znYyU@^HD-sd9$(7t7{;&xWOvgZ}$#!1UI3${vuNB(rTZmMbK}W+k?70Ui+un*=-Nb zOJ*&T{g1AciNn2S7U8I_b&YIl#eE%t!hFz`sA{BWV2H-uejKZL>FQibMBuTT!H3*R zK!**a+nz;gqn2N@u4*A?ACRmcW0n%zn6VA^D83U3{QneH~(RcR}j-(n;FLW6(TYZ%IewDz-0LfPHF(g!z z0C>LYV|YT7A9VPnZm@dfZTB@=jx$JyxqKqv%iufx^U)g!U5QBR0<9pa_iID3u;G=6 z39imFlH=}oC^Oo#bhv(;$sf5;>^OR7w%4<_gy#c@ z#Uy`dMuV%m3zAma}{Y*x@sMWqNB&ox{=KtDPtjIV6u-0Ou&Y4Ijrd?NCue8~)L z1w`msXThx~1?bs??Sz?jv%gyoIji%lsoLnTU_bG2N+0FX-ZC3e3zEVCn?znq&i zRC@Ke)C9_9r*K6t+0}2bHqxUHQQWUV^c%pP{xCCLDZl<;{q>!o4g+x*_;_RLjSbCT zFULJk2F1m`oZFY%LQkY`<2VjdYOTrlS^|Jr2Td-=W)ULfJQZHc0`3=SIh1?c?PStG z034i;*Sbk%?f!9hxbme@*a`R!}k zdRM@r0quYW{k}-6_U}Dl0uzj-o>h(&O9A7`Kf&e}Dk_GF_2;pKU`#@4Fy_y-aublt z51V;aCH7jg?Fs0JOg@tm(e383;=r%Gg8+g-jt*qoxtmu%up_e`)Axae0G$|*A3RA~ zdTrZz)3>FaPJAfx43W`P4&nTup~TTHGA9VBs4%#}GhBCx7}YTO!c<3F?c^Q{$IupU z=1?uY>(~<^HI)MaB%ky3LMZ~G5g-7EWX2>va?`|-HKs3q-QqZ$vwc-GUl&ndl9|Cme0p?rbK zy4_+Dy+*TBPkN$RYx4`|mK+-g7zf&}%ad$c^{C8Z6hb-4UaowzCLdF^s{NvsHzQ-0 zm4}^hl~qDeLRXp{O29WeE2^!#q0}JO5Nxe;j}lxlP}Uj#$B=`q%e~-3jvv2m>KM$Q z)m|_yoE=GIMO@lGUW>Im8Uq&pSf^6%J*U&EtL=EAQEHYkr*VZ~??v2-6%0jE)|LO_ zn#O>~M8p`>H4u|A@DLN!9FTaERh?ohwBYySxA?@Tit2 zUjms_Qvj?y83enfDj*TDgO;*YyCt3?9?+lku1Z0A{Pc_jwDf^Gwko(kZr%BhuUGho zyUnSom)B|P?)X=~qQn6qU+dSauphx;)qcui(poDA0X=LBa;x7sTky&l(ib^lRf;^OAQM@q7XlChEW- z3P!BVJkCGA0Q8I ziF+LmPPBRFnG2{tbZlqZl#+rr;D5Y+`q1j#?tRdsjUNJr&=P%@0vJCj% zM)6_`4?Q{}n9>^i>H2>Zrz!3Xk>IfUI5JZ3%m8!j^GHU{v*vbFuY6skqua~oZ*NB| zp_cT0PD`@Ab|A2Nk2Hh1M|jN9xobs0%$)*6$|93ZCq>*-dVHoQWKq6mu@9SOy`d4x zW>1TQ?tdp`OAaZ5sgYrKWK>jy9Bg2AnF%c>;vlC)K!Hh?I1*@<4 zGwY`~#?Su$M2ZNVCdAdI=v#dgQNDAuL<7`u^Ga?lqm{|_LMrIG;AJAJNsNGS8OyNEY_*&J*RR9g&V1K##kMt7#hb=;gXL7H!|$a4R{Lj)dc4UmJby5U!hCP0W-`MvloVuE1|Jcfb+X}p;P z2vwy0Sioo3;IxIZJ@#OuVRa*+Y-5riO{fMJ=-xFmFe88*SbFle zB2G-u@y`^vtZme(|3K@L-dS0Sq=P9Qu{YYBWEf@`SyvU?m@^M2_{F4`bif`HzHKiJ zP+~Y|@bum?{_!{gZ#6X$2uoGJF@^qM95`&41r9Dlb-~#3kgu+q*8&V#>W`PrK6{jA zKCW=uT=m2Su--mOs|=2CDf#XyWmOFXKUlB(m`jR*7^612t-7h~ny?uXMv=u*mE)Gv zI`J#Bi(lsp135e^SE9#}wm)K-WuI+M3Il&AexQ9>i7mrd>= z8VbSTh0)%{=2Che)wyufaM?gj%r*|a~3-PU^V64`vlr2wE zAHtE@o0UMyDFi=yp)7sUyZ)uR-biWNb~Pc`a*e-+o%`I;Ec<*C$P(#1?V!bO795sv zn?n_f^)Bknj^qlW30s?t9LUcImi`S!`Btu}O&`xFi1ff-t?TjuJtvr1W#OEYsCkGk zV#9`S(+DI4_&FTo+uyMaP&Z^F5tIm|2+U@B5_q1Jr#?zdCNc0qx+=C5`vVPSw^Dyum1w3^bc+Vfv-%g8Az>PN0HSqo``p=Sw^2uS9Bik&JPKAEkl zml;&f0T)$q7|m>4?_2tVl~9uaF=e_3O??=uKY^Brz{^0d0dZzWb#*eUXrWz{k&EI) zDfYPAat7!*701B8`wm*bxe(Rg^{A;ay5D>|q7lgCcUAgpy5;M>W=T(%)YsD7V-Yln z^t`C7`p^4*>r%r;eeZ_*x{@C)e;k3zhE!sYYx5`7XFM3A(`zGPNLp>DtBMlvx-?ip zzGtYEk_`U{<&G+Uk!nWI5rlC9lhKc1vFYWDA=j5x`BP)ctX@I_{L1vVt$ehfAHg))3j0$8ozwbYeLoWIS7>*KK z&B}G3kS4pJW&=9Q@NcwyApODA+=h5Y*;@5WmXj%&RfTIW;v@_f<7}HRwdC>5#KyX7e zIOCmxR_^Zj9P+`n)uYbDxUmCK)+Qwkdwhu0;=;nuS8R(uS&=+4N@ z%lUr4&By@UJQjJ(`ZXdn%k*~0N17ZEB`bDR40^Mc4RgCsML;$asr%~{O^uc0&ciRf zJhSeM;aUBJY;;A-d|B#qa(fLs=C~QA+hc2!X4n5vmqo-3sRtWeN#_|ZPh_+ruWQIg z6lrOW$U}R7*{~-NdJare%39~+Dt_v(sGiiHK^|12xGx4GyJJQ}8^$NB+pZzXTI6*t z@~7*A{Ak_2_VvN9$nK4{$)4f;oL0Fp&!Af<5Tc#w0tJ0NlGfGv0AJ@TKZ&vBCPR#7tTPMCLW4o`%OL(Mg+;C3q$BqKPDs{sbCtwI~TMiQxw^N8| zI`8V;@Rmb*8QB76^EqBa0cMx6r&e`uzgzLRDF=)kh&pz(Hm%9XuOES=N%%T?kF$gC z)b%aB;*|NIUBb{|dIIZFky-CkB9v{1ZQiLgpcyJX?)ZOZg>HaQIC)Hrb!PFZ9! zv11ZA+HbzB+lN2C#1ytij_Ggox#`sdX?0LtdwwH)5#B%3Er;PcZjnBQ%-6=*G>1K9 zR`F60Pu0cP!KtFW6(V+L3Y%Q5v8O-;?0WC@!MIqf9Wedrzr1&$ExgAm0id_zQXS$w zOPVcq$VwCI*K(2-T_Bjk@@D8}5EDMivTARym=Pj_8)d_HzrXuFH(EaeM5einV9Y0C z{Q!Ei;bgT}uM(t6x4TI3XOoWBzb5nl=a(Zaf1XPtPaW4%qlO-NRuAO_U_NF0oe{ZUMlJj)w=Y8}1sg=#Q{ObB5X9)_%Ujl}Mh(&| zAZ$1H6n$Q^3<2)(A8Rqkp@l=!2xD^nx^NP$%(M@g|FeffZ<@3KW*d{RI;9pvrWI|E zuu${~Qfjm#m#x$6O?|m6kuuA8SW}X<6YT58j~2K=t!Q7PYYoD^7bs~e-k!ycav`<5wV}hoelj6x_Xw452Jst1q(ft zit%$hES6>ny=2*?0$NvfTo!dvc@Je*!jc>KzRHFhU4uzlehJm%qZt&Z^W6 z{%*K`338t`77}adXNEp(v_WfY!h{|9_ghl~qWvS%e=$|=J?+wa{lU244HINtKUAlU zXuhYkR`aI$m%(N;e=@u^WeZ6=zrL?s5$KGtC#0y0Y~4*(!~!J`4PM);mIAPI16L2j z^;j7y?RgMkKEVDGxFCi!btKZlO<+QEktV=3KjPz!?_X>Zk@(|phnI3I7*{0z%(fsq z%%ZFdj;`sr1x;c%$f=BNM7!>|u9iCTsf4A6uYA*-i>!pG`{-m*n`jPNy2cT-Uxc zd1UqbN)Z1HT71&d&NKg{$$xwSjCIZIg@`W@<;^W2{2~Q{l>6Ua-^k}M2_hV=n!9q7 z!eEwesb>#_<}GWoDRHJR7iTyt16FOuv|f7`rZ4s^skC@J__bOJNl8B)qQRz1B48Lj zo(~$^D=ot-yE$5WBO^2eFZ2rC?zPfv&WwURt6I603N%pwd6@;~9aY-kyyYD~9- zj5<-s$rFQ}v{Vg4XzJJ21(SdJvnZaZd#A47lqN6U4^31UtBqxFl`2~x^m^HlP|cFB z0{gbvns(QREPi~3Q)LNTdQlx2wQx}4_NfY;>F?XgFYkkq3u2c}LG1NJDxiW>Z+;sK z=CSBm1!%A^GEb`p!L$G-;ZS(`At%qOfx29I(b$h#?-^tZ=C+x&a}tM!Z0*Tycf}`vJ>`P26i(HS-?F2zKs!PbLRRuM=r!>qxBlmQ zT$u`n*sK$M`6-Z)ZkD2rAzdWeCrK(3E5vWe#e!n71NGORU`&F#me`?cjWfO;1LcSU9iybmPd!lN7BVvy`Gr02kF zhtS+Ks)R5pI9+U(HLMv@;JV=u&%0>S7-SVW4fv3+Z})>KS{mfQF4i?H0iEC573zy;m#{ztM7h)f1rJLzAt z*DQ=+>X!I3ant$LZPeOX{fMxjVRn2KLoEV*1Ug#A|12#f4WGjw`dfUrjAuBIUFC>{ zhYFN~k+2A&pGj4FEq=uNT;FG(z!-iOPgCWdVEtFa-t4t=05||*--P?l3YAPO11yzg zdSb?;-2-~=h5hS@v$c^nn3EB$uW4}^t2iENG|L=RAJz4RfHI6NjGhp_ytV;d-D6 z751w;Ta&7qoYc*-7M8JG{RNcLYFN|8(7;&g6O9ODl@XhfeBoXSv_MFNnH&Y+(KLm& zH!x{Sb?tShl4R33Q@7GAXc4{7gJQ;jF%YHtP+pxyCgIjv@J$-2g|BU;E8flWYq`|M6V9QNMD!PMOH2uhDad5LiNY~q7xjL$)gh+_&p1m z@Y6jnwrB%NwS4xxYjE@Vy9nma0LjW+p7;y*5*&-)P6O5X1OLi}a?Jw!q{4ykemjLjJsX|J?+7lz5i zexp`;)i93m@x}Ft1AnU<8m}6WTqnG=lz)8RhUVnjvoiLnPVChQ)vWqs$`R!VFa+N+ z-CQ6x7dM?T1=*)1dsVyihNMlp8#Wl*T4@s+@_DbBCi?Zqz@vdjJ-cI z1!;f;FCXvqp%CLoKgH4rbi@4?LGP|f-_Z&NUyRXgF~6RH+;|60HQlg@y2t`olQ#g` z0Tq|_@BgaX02T9)%Fg0Fprl!?_5>A1*UDn$8j#)DZ9tZ+v;)T%X*>;H%35(op=RAP z3^`q&x3DMy8+TRYjD+)Ei(Xb!!$I>kp&{b_(d15!WZ zoYq~<4cntJ8<2HGM-OtjA$C;&{9GMyjCXE0p73J*##mukyuy zxbUGcl65Rh&Hg;&X4|HrpB@%7|JOC%sC6BhNWi(5e5>_g*KNgLzDS+OM<`P_NVT%M zly3_0addsOKFBcf9gvzqUgz|Fni-KF{lBP2|21Vmg6CVF26igHGBSP0=fH<6{BKTt zPocl$D7)tycelxW$(5eVKdW8G_36!eWiA!#vPoou?!rgu=^B+qM9ho4&S}7j=*ysc zs(S%5d~dZ=74i@NUr&e8hYI;c;KY3&7h->{Z*k%T`K53B^-#ND_u+g=QFrj$Z?%Vh zY>d8(?*}RO&I71F0@^ICd;qPUO&4GGA_qPZPtvD5A@<}VT@|Sb!S3f5R~n%p?@1xV zN>d`5n`XdZm$r1s%dLmjdh%%LWj>!__XWriIk|Ed4kDAJtCz7SQ`uq#ItIk2(13ZJ z0`#o;*%VXoqJQfb1bNk7i)l~0sqidz~! zWMq56GxeB;yWgp7J-5Kdp>WV7R-*s<6C-5SDl9o!Ni!~d4@i+WFMlkVn_gNDp?0b; zpa}4M=`08aZM>zP))!B%!InQd>9mM)Gp{+Bf4|Ykm)qt2(n9tMCQj$oyqQj?dDixe zO=(IPwGp&xA}Nq;h3OLX@_>8Kd^5~zQli$`vNi)Gz9ZvDGK({Q*9_h7dIG~Y;}l;t zO5)e$HGc_O(@A1&Jz-hx9kd>7142rE(BR)E^aU@?6rZuA+l!ZEZHzy#?rLqgxA#aZ zzqWwVLu^M^R+~pgdcAXNSgyNcYgd2pN&)CXl=V8HIb{*rE7?aI!03cKQO{~5U9SRZ zo;p$xjb^!MZ_H+fXOnjFW0Pj`Yo2`h)D+J@O*=|KGjB-!N!5&^4WlZdOZ+4M_nuU}wUT zp=y7-2X3Xn8z4(cjZVYEuAiYT0tsEFHm>6<5Tml$ufC@~ZqLulHDKYyehp?t%1=F-T;$h9Sr(Nl93B#IU1x53?( z$Nv7LCK9^X0N5k?V&vL9Va{?|++GaYvvdXbNd*ornAyjo_ZA3Nk$1_ArxmGZ9q)<* z_prLLrw_In+6xa@qVx)twv~GFVat5eA;3`(IeEeSJy#S#(#r;yheCR(K{K6vcY_$5 zZb-R%crFTQsDpSHs}jko1%x5z_s)7zMg5i3DbC~kAPe|{wgT<-U6-*v;Y~ApwFQo5 zz{cYQ_^?43e@@`1k+n}rcHZn5G^h1u`X%adlj%bd1v_fS=_j3`P)>XnE>x8Szy! z@W`jF2)Rhh6+^UmC#z5ZVns9m3f%|Z&POME@89T$Wokr3cJ24+T1|&Sn;gs`#^?}* zNb2Tm=E6=*%`oTWSYI9_Lsin|aXs*bwcb_B7)ATRh{S(PAYG6;;fRq9e~gt1J%?uL`!aru%=0 zya?V`aw_=fJ2gF5W>-GR%n$5lOfo15R0ebph!xeJ>)JCI6mgOM*90=2U1z(2CW=h4 z*NcJ`ZDTRS*32F5WdfJ7qL+37;MbOJ=IcsV!%RdXs|Hj{9CWg@U;Y%!@v!jRSsX)EWdDu|PB05lK_^tbFs zB<(PgLbr1=)sQH;0f}e6W#K`9dk8$Nr-4yTdc@O=T%Q)JUk1Thdk|bb*DYgl?izcL zOJow#uVFw7wWsuqbl&16s6|B8fi=2a@U&Iy-U6GC@UW<=GJP6-pH%&7y6P zHk!ulQ^G>;(PmL~I0dDwrhyS6Hk;NgWdu?9&U9!W)q->$1EQ0TggVy-;U5J> zkRtA|UD}u_Xd|$!paGJeX=Khx34{(@a?yw6fQh*~qal8Cd3&x6bVnNPSxODoo7~U5 zLpm`R<53UT(4dv{FO0dU#}%lUE{j>p3AbBhW1@Fj+!_i<6?E%E`sv-wZtVqboUQPz zddQLFt9mWYWMSisCwp5f2kM%tnj($IOJ{?DB!hDn2%o84i7Q>%n3G;IF1500*R83D zyn~x(d{aOCYB+vR%m-Cu{?FgKwuZYD@Tb#aj%+!hr(Jz_r|hw|yMfn5Ke0(|zNpQ1 z%%81a@yzL6N7^$#v43E3j6M_o{>D-7=-+axPKEIsvW}dNJ@5I)<_VLyS*tQ?ib|aQ zSYZJ@-x^hl(omtzlv0THvG!vv;>46V``)kNX^wqtTjp_p)JzYweL{JRYeYGwoq0e1 z%xCuZ7blk|J;!Y53k?>g4P+sy@lV0TWznzm*q0fQ>%8~?=wj}9zbBn=OWk^stQzHV zh8b;|V9j0{uXJ2#wDG|cYm?{&a~I*Jj;W5w;a3xn7G_4KU*8@e=MpszkThsU+*ey> zCZv)`qzf$*+IWP^_b>KsY;UD$uB?s2_Gn2u;ue{4fvn^{Xg!a=@fDU*bLg>o z<4GyMA~EkH8ZMF9B$t_-(RnwGj%J}&l3c;`G#`~6n_sF+Y;Qi=N@^C+ARCpn6%08I zpk!T`Phw;$=*&B@ki=gQ;s_Gk6iCyi+hGOd1!SmYUxk;(id?eY*q5A(iN~< zBb#OKb(7sR+bHNMdoOz)ldUu%f|`7F!t1V*z5ky zOh%r%cPZoTDdneB_A2?ON0|RO&v`V1EspvzO2O(MpDn6eG;8iFw^(Qpy^~4hx;`2j z9d#rdX?jd{jc$v5nT8drFg9T(##Y0y?2k^FEu}vDlnl<{4_;`2Ve?>%eOmoOn`n)$ zB?{lsoEun}am?z1nRx_n-9I_WmPju?EpcpcxYp+2>9zKmuO%YVoXnM{yGGDrRMB$l;=a*Iw?1fs~hWlqsty-(|uagbw3oXg9 zR&~bGX1kHAX25;5$<2x?9#M@B5rtzDxZ1RU4gY{VfgR@dB`*bPbThIu%;`tcG}k?F zuS+)P`>CDUPqmBU^8 z?o?-msEVJKmejJ}do!=Stld;ir6a3z<*mK8O2>o7W`VAnJMAB`Q$AW!(d6gq^{kz5 ztaypiuGThc+ONv4X%PRgavV(2OzcfgH@9fLWOft9FVriuZaiXIHCqI5!PtChb*s1| z(|#VUx7d!!PjBz{*>bEc6dPh0n=*&V{_W>Krxq~#l10y7)Of7A@BF3(y&EO_ioEw( zQT3DR-L6OI)7qzQKFr6uoD}PH@yVxIB=k;;)RU%ehKY`<8b6+V)$W6Jj8w315l6Lr zAV`JkQ}`@6vg(q~=hy1)uX4!|xSGwFcHN{@exG+UwhCqqznpdxl?qc439EiG1brQ= zzYT_{J7e@_p63>cNisr*iSCwW^=t>Z}x!m4G6ErH)NA7;Z z4bN#!Pd&F4dT-omLBnONsGqT6{`7Vwm@x4)f5$WlpPJgb0>O_grFjasisqkXmoRH! zJB$S;=1b7__7th6FYG;XWdRjBFM^Eebf^@k6r#Oy%@8*u_)yq?~>SGcZANf z9B|2`=#Kj4FX*A7<_$ZeIzrr4Z=jbOZo5Axx%d=53m$zg%w?kVqW;wT{-oad^$)@^ z`KQ9aY}rW`3fMM3b&%{&`udpforTh7X_T6Dt<`i(_OkEPh{MFqFs<7mZtl;`%95w2 z(@E1|ok>^ub4jDyDr8Vi`NZEOJ{DM8v-Ds1RY3#u&p#fNZIgSeL|TLbR05LXLbhBv zuGINQZ5<^l6Fvot+~PduC90FV#-y#K>gQM3(b`H|E*CviDkj)nInCwdyEE0tsv2#Y zDlIhNK`uJ3k^QP6uj~}-^!6pfF?p+>XBVw}OYMo>vJ*tVanBXml&?!ub&75W_9b{B zQ+?VD{pH+pc9KFw^5si6m6viZ$hbe8hp+r}NZ;bj&^(5!ZOvCs$hn&drz2lh_E8ws zw6M@ZTc{aTJsRFloAEY?%Qowzlz&aiW~F4gylQeLIOTSixyw(yQ}b`+6Pl2|UK^o~ zB1q#3mR(;g#EfGCef{hSF0bZ3FCAlLV&T%%$u4YECq8ES-R<(e!-1ImgW-`xF&$hT zj|R;GlWoSa&@#S&CHEIIv$yZdhS^yli1v}T0MqfWMxn{KPW+OFeGC`;l{f(-oGGYo zb-Bq+2|es2BX92i{Run*%YfPYKLoB}e({-a-D7&?^0x8aSDRcbjWnNb_WkK?@{RxO zyRQ?L9IyOT_}l)&NzupF$!f9v^P}-`!F;zR+*B0uiSjq!zYa9_;H&Ejx0)_+4LefL zvXo&^eOSfG?}%Bl(V2h{#=GE+Psv`$Opp8x{uBPw$9CkH5t+-BnQ6naq ze1E&}b^$4RvmwpLGm=|L+cYxBAo|CbH(omvz_D@N_g8#4W&JDcM!p>o^RbH8JMU!p z*qX%6-?W|Yh-gfR%paFZyGxDiZ%Syuk;^sxT39)igZ(~}i&d0mkIL^?Hi@0NiF2GD zSZR=Sic^=G^lDg;)y8ZyiF22;ogc)9a@Xg%(>485VqHX~pDq+QPtu!+FWQP{T`Tjg zQ1ZdIDWf*BDO$yjN-oXIY~3&WN-MD%DJhqmrd3ngf^xcxSIXL6S*W-q&gWa#*H*Hp zl--nqO`>6S*6t>uNkLiC@y^M)-zzJ6wWGODTGmFC=||hQrdJl4;BWGyzTR}X-PrAE zYqxNMIl^F0_i>^DY$tySYB?2j$D9JqHyac!#4?XNN|$_5DYK8tzKc_PY3Rnk)Ql4d zVjAhOFE8Pov=EfhwbYtb-Rgrnp{%`pb89K8=VKc`)SPt{!fI_db!RCJsF~AAfPF;d0!FL%?`fA)j=Uevf#s0%bwB zc=79O@|{mZ8O11zR^7)P^x39_$kwc-lNWuoKhN=u0Girf!n?O$s~2aaN$U4r$~9}s zcIXi-@qFeq-t%GJrf@LWj}j;QIC;M{eO5NqBYE;&cdtMXy=O<#<=kB7Nt@&|>;bQ& z}@b>YqKV|whiFf>785XA>8yo34 zUns|Ju-EvYE6Ll}e}~Ok_2|g*z?_LkxGA;$0u^5h|CFyFz>woiH_&zdTX(tGAX=_l zgNEX@o`1h>xZ#MLg6d{78nd9oKd&X=x|Y4BCMQ(}*~C8f;7}U$_mEq3`?^g5PIh@l zWm#cCd_JO3KcLy)(fsQdC*|X1cUL|P_cko(9x$GFtvqFY_7DE%-(7j_`c)dsY*^Vb)WEGb^TvX78rG=?p z23?6ZApV;}>?l=CGSX`Ee7dmtDk9XHMdm9D+ zQ%V%z%_?@6$BhSGztbfnqJQ|Y&LoY`?|QM4<@-%OU&fSV9S6$KvK|a!JzW1H=0(Cm zr!lOizj4~`Lu^!jPPjfxqMTcfZRuw#&6h;~Q++%xj4vYPkD;kOzm$qe>xU=vOKhx2 zR$Aea#$JApGpZhUlB@bHyfYsz-beWZT$4(DH5dM%Z?W=XssF3(;TnB1|ug;Xqx&$@81peezD5Yi(^(sR0uv*~?1r6WO+pG2WsvK@Q)ezl{uF5Q@T ze^@11)2B(-*Y-iLrL1X$N4~UNO?|R5PS3~I>j~5OS@%(;%5c~4M3WSbD&^V5F0Yq< zIoC4r14*9`bMUNuvFo4aGYL=(^=UZeA#kPfX`1~EuS4d zv`QDm_NR2?j9mWkZFk6;tv#hI9yEFRz!D0%9#4PCdu1W3?#wB~b>AUui_5c>#mN=) zF^AfnSFCzH>|H_$9~{ScTeI;3()*QhColJxYFD4Kp0nL%yH#Lm=tNjAT_PZ6Y`&1` zJg4zNQ$*$nBd`L%Llhpy4HZu!d z*XpmIqjtnS<~DmaIKH>ywfEs2>{-QQla(<}%VsXZyAy)g+<%q(I3anvsjx?|;_Tp9 z5zWcJy^9)h&v|xCR^TO?7Pt4n${ZI=j4HBAZ=T$V`sAW@$rLTz6NQEyvW^UI*0AG9 zMcZ&}oGXm*eOLoaR2oOHjyx5aECMdDIn{h6Ngj5)f%WeH3{3O>3req0j-()5S)b)2`Th;rM>zQEEi+ulf)G z>vNuU(8Y|;eSYh!!aw8YWNV~%J3&cuFrj})>v?k@{es7+v@A8x#IicJnw3>0S=5T- zbEeYhOjh&0+QvokU)pSvi2hjoAAZJjx;Fk2S^7&&umVU- zJF{p^4~lwqIp%&yR>F7uZZOBqcWTGM+R|AMbX969Z@`|eykX78MUy=N{9Og(=O%^{ zRFaJsQ}$SkW=mE({zaeNedKh9zsofyYuT&P=k#7H9ZvXvD0}lqsQ<5h{M8~NOQlo{ zNk!R9ma$WmvLzuq$vXC-v5c}L(Wr>BGqy%Zm|?;sYsg@1&Dg>i+mLM-WB5Gre%<%| zy}!SFK7W9xnKS2c&bh90U5{s#4)9)`u;0Ihn1Zo%wZdTT^9L^-10VJzmMRC#&w9A0 zdoJn|UpYk=TkeE3%NnLmE>?d5h8A~SKKU4QQIQFMnYBKnkc(6UXG))%>o2~8%bTlH zASSM>E+&+QI1R+fD<(}+tf0toqXmD8)t_`efrYeh-gV!re;|0Ls38*qx%{2Sf#1-T zsKiduPb=_OrW^&%FXdIDXJKFnUSRGZrO|^Gnk~p(0GSW4QM2^v6jbC>z0T|uVwzUk zV<3*(erC1r+9t0lQF6Y#9!86JC75&L-{V=7aGvEgza55qqx~EkHaPPfB{1)e4xy3vM`DaQ*(aSAbv96GVGFfp)A|C3ORvw3?wfdz0 zxFN!b2>L>;INB~5Ce>>n{4}#n;EMn{+KF?MXz~sr4~&#wx*8P2lkis8VLxmsfF%5M z;7IWOpa;_2$AXSd(BKnvgR1Vxzs?Pn?N=Kz<>w&Rf00c738ZjJ1Axx(n%`k_?!2*R zZw{!vfc-DDC3pTB$N*=`*htDt_x5@-m;|!q&fR=~5kD|tsvrgF%ZRk&@2^dSry}Gn zFs`s7rw5e^4j1|UoXBgVk+@FJNoAq^0k{7ItrAr2DE&&}67nbe3+oqd@NLvAZH$X&$Y$4*11U-d*f0R{ML=PfT#l4eXm z?VneCJ#`s$3w8t<>vZE6AF8hX;;6Gk=K7sJ-@znX9Q>9eYxtC*+O?9Bgw!rKN~ij5 zCo>=N`%FE2E`CP=-0Tb#>4$ zUSLX5%ho-t={`U^-CXIKj12`}U8XrUKEd#y*%{Y$nr~DNcxdC$Prlivj->Xgdn&X` zD$%*BQ1V4Z+NCWniYfL2nLdFdbmeqN>Gjm0*WnDz{9L4lDFt1(^eIze6M6lD0Ee+NAM^SpzzkQkL*ZTVv&BJ{+ zU}-V5nm>o6dHHP3Lz#NP0m{)Gq)3gVF7gZU%Q!?ngi!fvnux?;l&d8KU3Y=Dzzy+L8l`v7PoK!Jfoq z!5fb1MyST6CPs=Y+EY!YSS_2xcu$iUQ2N`)DG>NM8XqN2Skpa~)LmiNC5O8x=?~0q zcEZsU z*GQST)dSCmh`ig0l@?wYSGI14#*;>MZsN zqOUq|kd`}5BhK-Q6_=hr^thYqU2aQ9hmXAs$m4SF+5CN)C%<^4>Vj&DRA#2@pdgL& zfFN?*Tu0!#p=x=d;!e-|nQnjjH55^~v5qpBlPK$pI7nRStdJB)&62d>-jOeMEcqgZ zj+qdtE0@eTv{rDe&Czxwnf9kBUEUU$H^FoGCg4WSnP#V%!J@kJkAQi}L~4-EQCFon z?r?*fH%05RRN{&Ohg95#xQ2IGi>*O8#}qc}f)omsB!ty(u)PJJ$!Deq{Y}nX&*Qki z{pe&mPEb`cl&b_@y<*k3<(K^_{UG00Uhe~UuEZ|(Yt%_@{I^Qrrcd$88f$N-^$J}t=jy_A6 zVtkqjC@FuxXg1sFGc*S;4)*dLYGI_GFIo7u_MP@str2`2GE{^-)oi?O{cXFjoa%9X zWf^{e!I&>MG$heRI{lda?%Bu$6)>@A+Y8^Mba|&V!Y@nFaE!i|EaNa;x!N{4d{MFB zilbns_R*d1z}ap{TlXGm6f*Z2iSk_2gfG-rU)Vk3=%|%bop%dakx`24uU78rd@>zQ zflGDK;;h)0g44p@_dzV-3(W!Zce1%D*&e!S+Bu(8SLFYM+&~7DuN|G8W;4TEE}ne>R8ID~mi$mqpSB!;rj zHU{{9e#SI~*7VleR(EEBme<|;gnmDt%VInKi33_7IM0Dj02_ssi2u$<&0+1UXo>c?f>U*KvaOg^xn*CVl~{rJ#ks))$^6T842~1j)p*|!`WE* z*Ndl5B!8)L3*$V2v9m~)atW&D?DS}C;u*gXKq{aGN;y=W&yLK06T@+ueM4MJip$UH zK~s|mpBI_qX5%f>zGdybFGiM;^)72}I!*$!=~EB)<8N8|_dc*bKgEdRaNN$hE)}Ug zl3%vimLnCZL&oWGelBve=}O}5wLaFeKH3MJ{t_bPQAA^z-g@~@)xzLEYgx}YCPWFh zB*Ut0Jd?3&KE{HWPi~L6sckHN1T{_5)Rv`~niZ`GUFX$Z#wDmxl9-g&Nej$HtOe$G zPG9f2?ivQ!R7gkcpmQ8UQK~Q5g;&dP5IW!qG-`09n3&Go9cy0 zi*M-AE}z9(OzzBpfB3mN+-Cv7=b`O&B^*{$=UUES)Gtns^+!W6PV&p4ShqfD!K0~d zY`@PenGc-qXqd*kNYj{(@yvqGMLz3QL??JqO((jcL%N9nt9=$p)Y@udngTNCL6@9v$8I- zS4$SsB$9W845GK+D;NshNXjhr_I$XetB|=d#-SS?gI&ot=NA6aJZF~cJWHksrX`qQ ze^NC2l(;EiA1(_seD!J4@Dtm}5K9y~BGh+a>QH)D)LRR>Scz4gK&SB0OG+52%oZPn zTA_Pn4VP()nH_l6s4huM0Dsu0BQvw;ejqX2T`{pVLNPl3yt*!PIVm)~E5>Qt>U2ue zjhpZg8TCBRM`-tk#gg0cm$gw0bw)^#+{&Y9X0v>=Z~I_Ua8KolHPpAdmlH=nC(~X+ z4$i#<&RSW7i=md>NGyE|1~Lj9>Jj8kB&mFnKnL=)yuZkKXm{W=YN!hKy2}^l758qO zQTl}c$z=UalYa=};cse3H%e!Wv151bSuYJdsU6Obv2^N>QUjiJsu^l|WSM--!C&CH z{^8Hi>|@ZUtL=>YFIOHBDc5z;WJr|h;&xeEmyFP-zKS>$jcfQvm1ilQFI-9wHabPR z!xW3S`S-T!2I3aY)^o8u7CaZ9<*hc!*W4-%KN1CWMVUBn;aTIljTnw%RP0-?p|!2I zIJOX5c|$3j3kquFG10sB$V&?N+fhgN1Qv(2Y`nb51Ko@_NVNq7f&=izI&*feL`*JW08r* zR?&vZ03H3I8H9_<|;n?#$&( zR@mQnu{`;1mPdq%B{OMXC3Ko@q;pdaxG1e2CCktBFGTQAXoXm=*~{uJpOr^Dj;dA$odF9W9}nlCJW>h23tmo3{-p)&x1ayZ(XS zW((l1>Pr{e?5*nJLM9))2dEkIoVx-z!$Fi6fs@E@5+;@qok%R%o41}^^Kq}ldCPQ}02FsS@4 z+b1tKV>Z%X$bRQ&_PFvN7h7UAJ0tvU=;0uz3hBiJOHVqob3*fDEHeeCAR15Dj!^89 zVhSLu2c>3v7b&+q%%odPw=KGP8#@bvN6#?NJRw{zKc&eEq2^Amsr1Udau@|m|cHI+ifVeBPalV8IZV~YIlvX>2ZE{lnKGjw%QE9Dv!A1yDTz6XS(97Q}i+v+It?}Iay>Q-uBPzt`kVg7Vc(z)tc6<{$ZpTa4{ zCYC+gmBk0;1N$t!8(qF6RoKQS0&N#-FDN3Ss|v3I+Zjko2*~Q`@ehANXARYAT}Gr3 zIq9>1`C2nc=v?`sHq3Kr`+{xgM#d5LW?-p_pBXRBsIQx(6^z0|mf~mh`k22vkU-iHRTOBwbD*Cv)?lCpKD$&G>rN$ZY6x=+ zUXFl*;e5Gh;Mlfnkt`M8WF_9yEF`)k4NSSTwDFB%4{P&{}RKzf69)G zq@lDvs<=2!UVP3ys#@hso|HUh!HOiC)38w74ae>3cV66#_uWr>3zC`V7Jn6rXbNn= z8{)&Z5UYg>OhIHdFlLjX&ZA9?Tv8w#*M0chJ>VbsUD?^GTHtnR8y6_QQZr!{9=^Lr z@zYCFo@g^Hy{$gi1VCn?GT~9K&MRWW73tB`tgMBaCsMZ<^6BDJ%j&rf{9@4Yp;T8f zCkW}W(efHbRj@sWdFIlG(F;Jablc+{(i-dX6G@pAF*3z5wBi)YJzdx9k`A~B*6Yu^ zb8DQdf^VM$`d9R_dC(zV=H^O?ssO!N`aU`!u76Ln~pK3UchV;yzz!>uA)TaGWE0dR;f5q~>rn~7(cW%f&rJ{OoQ#1W>^xf(0RDFIGfvSi z(}-9IMqltA3jDh98quB&GXnhuvyg`JrrX>b<8;n=5F3-hMttzv3rw#h-=`6A7j zG1O-&{Pp^i_o(wwct^g1H;z#N9%A1(NE5}nfkh;L$a8`us zGYk!sTfl41KpzMY(;R)JH0w7S%{P@UO)cIcV7u_JTWW^ZY~dkT>`F>CFO>jE)waQT z9(b4*JRtck*@;t2aB!8>9};$!(gP)(QB=Fqs_V8Skf)-KH*UkXY>$6HKARDPCVI~FMHIVVnzhbYF3O3(} zx>P&u{E@D5STOQzyjoeI-D8VwP{48G1D50TYqR|%IZk^{7N92H)7Dg)28wB+qa_*g zr$0W<*UgcHNVxwI?vN1;BZH?>h$3OdGw5`>KUqT|tQhE^^JX;VJgz^&m?v>rvg@rU zV`HILo?c~jva18|m0mn{qDnkIBzEyY&B}-Z^at)5t0SlRUcNP^_wTuEw)#%Y(yZemR_cu zl&4E*vW*LexK6~>#)4b)r0R+|hf+<1_Axl=ass2gpwQZo7K@h%A8-PLYPD!4_+2i_p&Dq98sQo-T`XjS9hEJ&M_z(9 zGQ>Ck|ugb1v1$E#HrDmJdI=buCTaB$6&X1OoneUP5IqB^La=QzW73Mwcf9w~_ z4{g0ZI5`w+-!Mg7YY9FXhnhGuRK3CtZnqz?@e2Ehu{L4IDFG}Zo5|UuH33xRSkoc{ zL;O8m{u{N1x}0H$dBe8)?*2VNaZX$5O*irR?>uzQ<)mmsWUT2mR|fXdkwBvm`qBqF z@?0|WX}`#8a{=F0Jt-sfuTzee?aO`)_`oP6^5NJ4Z>PbzN_2qc|Im68R>IxF`+>);Xp^`2%&^LMAO?Ni@X z`QP*S=+{yYez1z0yzsK<9E|ruh46F`aNUOCRpMuiYswjB=ndkardLLihfOFm))Nmdo9&QeU9ikBJ!PWP|9%5nfFXe9m!LE zylT#LSbn_-(n_S6+q71B4`N|qL5bVt64uj=Q6hXiL(aGbEezE*cxrAuJno&lUm4*^ z%C#S206{l*cZ!AN@A`|c9{STU>Gd4S-)IuelcqzN6<9=hE>gd zncW$R<{2vQ_SFp}yc64lg&Om*&*pz>2mt^HQr`u$mq=^vtp$HJ@~5X`5x#O!_o}DQ zWa?9baLa#lA)zbXvitSyuhBvlet@)U?F|`-3JD#~r@r|9{LAw$;e)IP`NNUMx9m=d zjHGC#)Bp)osk*%aeINf?6Gbc)5EYO%9j~npTwL!7VCqm5f6a9kYUr?{-(x^?kB4ht z0oSVRPJ^(WwO4OlBhw-WdyiHe7rtaqE;|q2JlZre&`@oY>UXd6lGuJ(X{*01}`qlQQZ*@ij+< zC~?#KrSJK7(#V=E`pxe;nuONE{4S}{qDYl45L@0 zF)p7@f-V)J8mwM&J#;aqj&bOr#) z9>y|XVP~p7VL|4@7oh7xDcUH?(P&q1S#LVhLwE z7;_coQWij69m|HaCJn^DeW4H9g4L!Z{Vod0lxK}=y{L3H`rOtWmV+)k7NBZL(=;1RHBRcS?(QbB zx3$4L*MQBrnfm5qpYB^YQ3RF=WYnbj#Kxr#ku)m#bbeU}JmO0=+k{}ybi6XuD`6}nA3 zRnwJF=d`5zT%T0Goi#>OM(L*Sr3y~lF~~K$bT&c$&6<<$?6^;7!&Ws zq9~+=SVcAC|D+W(&u$NTo(O!VIqZ|LBWp_tN|_{CVg&Cznm#t6^IeN`VwrSo+1f1C z^E3CGsvCCJ|GWv_EX`aT0(+Zn8U|DI7k22n=Udm|dn=^tpzJ1*D~hjpK3|PHM#j%rg{gwX8z7GP1Z!JjChO&e^t1y`iJ+hCi z&@wt_JJEBn1aze}cVwD2qFsj%Pxv^@Z5_;>)e^-Oa+vao zbkm=svLqL?3R8rvB#ezJkJ%uzJkRzj)bVHQ!HrET5!F2Dm!Z&Ou+;Y?wNhsc7brac zx{k9nEtg2>nWYFS3HQhfMlR?< zF68)Skk?7oBc&?5bC-C3>K-v=ISXqE84ggT4L<&>om$7F;(F)`x~VO#SakiZ5N9Fb zE@CC_eZ~v|n>3f%9)ZLje}KFoG<=iMjZo4m1eP?@w>8>annG~o1AHRLGs33L*92jc zc;%gNC%M;8=lP%>k33VE#MP0>su;(#iD(TeKz89Xbe(%K9e^H%qv+VIw9SXL>!>fa zz10TW@M3U)4(Co-pmM3}HBx72=2)~JlXoP#(k=hrE^lCO#r-(&h zTC!h>*4YY|)O?yGviM3Nk&8x8_9Tv0zaA_96gpx?P=lrRrZ;e&BWWHOeH#EQeFus> zOon01WWtiZqQ!c&lSF3taYOAOI%(l!zs#(*@5*^U5(d5;Pa+blgWCKHd45G zK(&+-P>J_YN7P(WRdcaBzBrXT*OB5K^dQW-#W!W70X98AeWds9^woVbSaA}ALndUB zPUslja``?D(#eC2!W@dSp1WSLg9VN-@>gAYwj)}8u@x@=6KmwKLIu|EY==RCHalAE zXKf^DYTpV^%JfAA&;yJNwW>p1mH4dx>aojmohtZzyWW8P1;8Dbm9Uwbv#z~#m9k~{ z`rJi1M+y|OPq>uon(&;S%NA5N;ct{*0k8dGboS?sm7)awgt@tXC(WTH?%4N@cWw?8 zKC?M?iY!|2FzQ-X3maklqJ z1;xkpbhvrf!$hmzh>4eUpV%9`8-%mgSkX5SfA3$z>#>(p4n7Fw(jd*+YUMJbo*XH`oacVksYt%r_=Z`7gr z@AGI;*k=0^Y}2~`cn8nkgvqb~FZ6!-b^D!Ao~48f-+6bEPg zgvp#ms?>{;-`<5L@uKTeQu1319r(KHF8R22(&yO=ZysSlZ4%nYA_3`*}3?X#vtN!m&fj|;Ew9*GB|GMMx*{p4{3>R!;up-S$&9K6-P+7;X z0>oge@!E)M9WC#zOjU_=4m~_vgV*Z2wsA~l?lXa)#?W&c@G?xDT5caD{k}|0S{zVb ztl3dhRVFIx0-O`~vy?`d8G+8<`QjZCI+j4qu=WPxodeo_+PhW3_+BFd84QA^Z=)a9n0Z@b_299p_+8e(&Jm{(5;C&JW4&Vx5xb z-Z?7q?$bGdgq4DQHpe@{(Hr5;TT-qy98}oSUH4zi*Q5*rv6X(M^_5XBm++yy)O@n( zYa6!|HJNL%&&0LrP{-9x59mH!{n?cM_NEX2Pb`lF_mOt!dx z?3h)Tx4mgptcO%vRUxWKo3Qm95iM@=w~2*tzJhb&O?>}Vj5=Sqg4L4NtlUR-tvT=K zdOyhw^wAaLS)Rhs3wrqbo(+v#)vA-zTVw`}Y z0xOhRD9{V@c{#a2-I2$KyRv_|oY#7G6(;(WA1V?sbzdzf&m(ZwyOdp6Ubklbf6-`N^tspC9|Vd7n5Wvi7e?< z8Yy)jfES+p`}$hvM~4SPox`H1GPktW3Xzw6xPJR@H~$L*?xk^fZ}(HJ4go3bWQ_TW z!hYS}gSQEk;a<$_P}+*UjxRBuPfE%vIGm{&JZrIrPFE>CgGlfP`7-Bf2<4neoWt2joo@szTl&NFCE{B!V7Nh0g$rZ4 zgPxPAA%7z~Tl29z?j>I&&GO`b&1s~nq@dHO+4?A>MYa!Y$IjZf22k4VZRIN+?J_4< zzA`IB8E;@`Rdv6e@b|piVvd*yH-0LhTV#?xuE_4e(S8N5kmz<^;1^wc6-6Ls-fA z9q9R3U{6=t*7F$#6tf-pChoA}+Zx(qEw)zzlFW=*r54V(Hf^@^J?xD*|Ar%Ad)CLO zN=(}tNk8(SItakpL&?&&-w01l7u#^G4{gDn&5+;V@9&srg16ruih1n3_ zua%9+7mBV)+IlZ17j2yqTPvLFvd77WjmS2I%$v+UQ*16YR7rL`J0JYO`WWWcx4&jz zB&18ZT@n=g+Z55?YR#5iXOocC+8Sk^!zl*N{-#|GE^pG6MRluX9_4XqvK)A52?0-0 z4k95X*jY1Y4}SYw1Q#K6w8razn7>h6uqnq}%TxmxF-ZdfZMo)xH`g%4%zAuZgwY^;Zr!U-1S3$n9W%jx@Gg)hrhwmYxK3`v=K>YmBW@ zUDYLVksm2M8qXce7DB(HPuPS{ME$M$Rd%99OvD`tuK-qmkD?$Gs_iublgmfTRaJ6l#4O1CX0eBaIzoKL8F z_zL~F+Bh1!_7^^}hFS-ct_%Ga+ic7>;Tz|oHFUyT?YKNoCr@w35t2;mCf*cK9h;vP zHn;vq-lg}sQSq*>Z>ZMRX=PW4SXjzyNfFeqkOX7qY8Z!&zJ z?Ah98(_lpRCSo6uN&Eh^L#3*h?(~cS7KvwJJKKS$y_7?pmb%qQy#H^^b-S+M|NIBJwB2Zx z2{c27L(VfLUZsCCyc#eMmR0S^vfgzNby}3326m=}gTDB8s~uxa@C-Z@^&0@65=oM< zTE(Z+RpX9<5_j>(Mt03^A(PA6Gseb4|C%q6bj+?_j@So~K{M(dsmTSIDaFX8>YVy> zN$W*>AG$|I29Pr#1)4+0Bc%PowV9!w#!GeX5A&V#867g6i2-%Y>DaHyd|9p43G;yml2?z>D7++gNq^;*hyF z5YGSlSWw#!^AZC5P(F)?MDv`n-vAq2u3f+`IgP}^ZY}){JYAr>viQmtUba7~a~+w> zva6%bbzd9E6c-Q|u-(dT?gkqmSk}FMo29{Ki<%;W4J6cP)lM@Xugpj6^<&+2=GDFE z${pKF#1}sP{i8d-DFkFrRVb;ajf=r{7`P|G?KG&MeCBV)oWY8`gF2o9=6g3-Y6=>U zIKeo)%j7zHIlxCSiSjS<{4$@#?X$Hq4!9Fyp}X$0>qvJgb+Z>_#j{Yl(18b)I8iNJn4UTg+WT-3-|en7$_-%a5O#tAw~1 zc1$tVNKTWC?14?Q-&nea8>OLgl=3Q{$wMOe-|LOR|M^{q*P0)wew49y9PR_wrsFbh z_5$fI)&VI^S;$X!Yn=F%#V*{UyVq6Ejqe;yET0a&fi)nkMf3ha!O#8?;pc*a>sMgh zxiW9YW@Fbgt$rWSi&lw(ssp9~5zzGc)gW*D(WYWts0>t3&;Wl=%8T%;olwyS%J#pu z>z0X)HEDe}aLA*DN=kmvk4%*Z6;z|pPdAiBc?aNdi`_l4iLP5{ffDG;kWC`VVCEg0|(yRu% zTuJVf<8HA5&1z;#^`^>Sdi*5PAxqVBYrJRGYfZL0cYxWpss3g*VUZ6#k-xe<7Lec} zs@RE|HK{b*!_D$VsThOm6+r>ZJ?H9W+jubH$n*8VP*qE^PGK`o(tf0p+Rqb-lCgNb9HEJyK@*_-=EZItb+T zu7kdB-28QbgDpYrv-00Mx&yd*R!yN$dU0*b@5nF$FF&_2P!}0`@|}0uB)+_KB$RvwkoeChSek6K`fAY!?O>6Krl9)Xh#$*G?H8+EZG!?X z+w83Q-;)E)In~aa8FA~`Df5uDjLr%h`aeuIrJmeF!M|pwicL(TLsEAs7}3JkQA{;f zKAUrYapv4MU}DNWRq#whWJJ2cwL)~Tbbh@$1m}CN+2?^N1`K zO476=@ftNUGS;V)c%LMyxBHr|byB2pEx)JCZIoVWk39LWA2u&z%U*Ltz8ZxM?@Cp7 z$*L3HsX36Dm+JYk4y2Uyc1QR?Tl?4`!+dimiz`g=c+2GvLcqQYoqd#XK}|b#o9+Cl zbd7B(Zam5r@@`@cI}yvo@7$#ar$Z^EdRgMCP5WHWBK-Z{@lWC0J^p(30-y$nZj&%r zf956fZUW-x^km-3>(18c2PGUdwWWGKFv!m?-Txv%Q_p(i?$)%wsDF03bc;K0SA7e1 zMlr6_*P{mRR1ooI^QN<1wbbXiy9(6BNHzb+lTr(Vp$-Ltb|ZsHs35s&YQraOTKG?m zFP|oOJvdlkmgg5cbgiQ=rO(<`927a^2=9^~$Te^S4tD$ z4TPw5xvJl6k;b>wp-^{2y!gx?&Y#qg`HI-8`UHOX@x?)evFFc#U^l|xx{vZhe-6=h zV!+FY2~mV9qBMW~D2kX9N6p#q&H6QgO8r+LMg#khmP)UsAf(kkYF!wyJ^y`d!@-ix zQM%ZLZ}#4PS2C%~K%wp6dUiVlwPrz~IP~w*u+V{(bV%;77^IP+L04@IFw*F1Dm&id zTnDOH&*Q`#>l{T6Eys^VVwV^vgO3{6h<94Axj9?MJ&?kz~DnQcP)6_*DVyDGl_rAIaLYm zz`ucW79gn$mHvin%K^xYxT#6n&$!1Pv2+Ip>c_ZM9Y{dE#psbsEId5nk$`sU$1rJOM$G+7VbWQy zsTN)^RoSpQ{R-VuO#v(w=Q%!jct@J2(6kuird%j(f3-!FY5^}5-tqVpq~2VIB8iR- z=KQTTKJ~7d@hoUEGqC*B4r_&lXPwZ8x>@GAc5PG3FRyufYek76f>Th<(*OMnbRYc~ z*eV|b)LXYAaN}`8SIoViWYBX{(0Z%#=JfaWL(1TzSICOT=+lP>f~b>T^fj;qL`5nemb3edv_7&-4h z>0^+Ejx}&uSGBR(z}FC*JPYV^suoWIYt`oj`diEuysq`~gba7oEc(z=wW*D*R2Z&; z^S7UDSdBW&s|mtM5Aot}%-%l^4_IzOmjf2i_&FVguft0pdnycRL`^d}?Kz>QY`G2o_efxn8Os#xox5>Mm+aGh06-PPKFw69j| zvp+;{iu$I4pHU~!P$kd!X6kpMlnAl-*Q&>%{XyU&prJ`V-ku*SmJQK3*J5Vc)wC&A zIJfmny9?{;Te~n&b27#CimAgzPGZf@pwxc2Ms9vH7SiLVqaU#FO!uqxe%v3t^MO@3 z1Q;shsO4;BSdjMg!$m6_TnLk1mN=ly{d9Rc+TrBR{ zVjfZgh-WVHL5wRZDR_4nj~*3&UrNrI;N6#pfOo{mbjP6Cn@3IY6cE{8Yi0NbRO;!R z_$L#eC#tBM1)d}X;f%$ze5V%qM2wPdi|E{C@40+e>+E0PLyU;~8A*F8&ENCfdM_vP zhb(ORd8@vtvCpCt(8N8Nk6Oq#|1snM*V5L8+atBL+$>k@_lo@!UK>Np`*j6U0V&FN zGWj<@vBlB!eMMbSQ(090OxGOzHKgNE+?HyZ5T^J2eL-v9xtk`;56R)!v%ctA6dtC{1;-M1b@xD~_`aPg#DOBDd`<=772a&eH3+BEN0z=$Nw- zp@weJ(S*&xu#hk(=_=@i0(yI3)#a;Aq9<`aI^m!Nj+$Hss3e z&EYl>%}Lf8ma44=J{$*@2+YawnRWkElOlM-OLrRGDFC^1#jt&E%jf}&ueEWlFX=P$ zQlmfO*NlxY>GU$&0ejFg^HJXc+o&~@u8NTS?Eg%j!E39xL~ zSm-$%g*2#xVGeLo*=UFIikIap>H{8z3a;hU#C@j>ik3Y6(@J&r3$c-XR6?cs)R=Sy zOgg4A-xz9Ji)C+x-Lc#YiogEc+x=OFd)I`@`=e@iE!z2YMA?_L8$wf15+EaAJ}&y7 z5Q_ef5W;j7FrPvk7CVeZO;KR^L(rTo07>?8(&dwy{K*iVkw16D{sOu}(~@r`s&jJ1 z<#E*f!~*iJP|jSQ5fN_ls(oW4vz&C7&JR4Mi zRQ%=!7JxS{O(KJoN}N~190@^tq1?T_X^72y8c4=F4;LgB^7RW`@0s-75ddYHmXaN? zzUH@CZ{(?j-7`!V%bul&Fy_D#!QEaht|LqP`lpG0XMb8U=4=HE%kjexVgfV>Sgm`u zm7Ul64gW_9UTXAd2z>Y+$05Z>c%RJCud6v6fF9mqE)0awJ%sYufz$j+5ZG4lDb<2c zQguSbAd{q?2Q7Q&c^1*_IcE>wFQjfh9>+f?Mi9Fe;lCo-XL&SvFmF`?`-;;4Ny3{P zj5|ZN;MyEK!fvhu0+PBWj)(MUP0dAQ3Qu~4{Kh3 zH#Q3NfSzO=-^-gJku2PkhHQ~+ftGtFA6n#r1G7`~Z}@lwo6FkEhT5w&E&hK2jMN}! z6}^^E&zIFV74`A|2l;8GVu3L~ot2xI8Be}1s#h44!9_U})%PS(OToXQ=Xksg*x)x8 z`z+Xb1)GF93*0hWO0M$(hRweRc0Y-8Il7&Nr13wa=)vgS?xYT9+-V`_-DtTq+U~#O zoM{Lw3P5v!07QAkU>wWkK8f*|bHYtAK3|!S>Hg$`(zW}x4`hN@24>EjKjwlmu^^#e zt*bB+-no|_112bx>(kfar~&O>E=_gbHh@1I}mOh}bqjYQJ44 zuzP?I(KcjzPYkwsCrrQ)GFsG1LVBtLyR-g8tNB5d$RVl&H~M3`y-fa z?E3SB9waRXi9p=Yd;cMS=x40TSRtdGQA2nKH9DA{pSb)L>G;w0N;YXFZo6s~DekUa zFP}3}VO8L{Y|PgC`hF5?sA>C7BdhK7kYMSR?`_G?_atm*A@?*fGanK6ON4f%*}A0i z+C&~wI2Q3ZNmsLPsllDMw@-QTVu>ZU12EhuLob-$G@VYvad$t6e}fC$Jj$6f+|EGW z`8Sg7$;OrVF3As%Gw&PmotVtz*UyBM*#>ihc^L1(GAUPWkCdjvXWg4Hw6rcIL=hr& zOEnXp+8+NpKeP|O-ErfWp{2?9?OeGSnG^T!&fFp_5hB;%^l}DR*pD>!MZ#+l8+wgz zd0n$lR&W^)vaf;WLLIY4dhO#RbNi-ghO%}GKm zBIL;5-^V>i>vEUlAMHoDZ1-Zc-DTQAZXHNXX*>e`pS1NFf$2F5r5C^`XG^_y;r*N7 zysrp<9+S3q1lLWVw?pxSKYZ!qyRrr!$JxK_11Agd00~=w6)-z{p(B5S*Vdq3W9UQ) zO0v$Mux;S|Y*{`6ot5QQ!9u#@>=gH^VOQ4jTVk{%-|o=o%>TrGfSOQavIX>AzEn>O zHAv

AnIgVXsxeL;+r~9fR{sxlG?8L+o+QvVz3V5!86cfZaPUzw}wUyopDn_+u>k z^NV>FVKmW8Ewtv~jIIxsLOPb`y~H0BM~CViC2y7^TnH%q>o=uEZmPqL6wyU9M8lS< zE>mpj(J8AErkE*)+Lk819*)~e9d#FviBFh^{vY<7w)|C_SK} zU;{)%P&$az(2JA=r6>Xh3xa|Hk={W{=m6u(idFBU@0?BfuGHe{{-A-HM=@u*iQkAX^0VbTghQU+4Fs+34exl$VmEcy8C zmS|z`W(U%CQY&7xUa7NHG~Yw-PZVa%-zPv?FFjBRty$TcgkeArT{NnZjuM* ze3p_)?gP55NQzq7-9w|5Uj(6t4ssHh*I2<&P_x6jXuLMVHZPcQ%wzH(6jW$35bopO zIccC28@AH0?jB{W?Icz|f6jRE*#g&)2MxOkPp0E!Dk(=S;`Ig7fV|j|~@4hUNIvuo$q=pSnOZbiZv8#9YcDzt>$x`+|c0BA; zVB2ijYyROYjjUYuks{|bN6hc3r;Y)NVGl*0%5wxfD_aKZRg9 z?`1#t{Ol~YX!7vB9`<6IzE|v}V#tWK-i%G)`jv`^w3~1ve)Y0Fcwv^hbEB_linygx ze_KlXug5%kJ&7-6m#3f&)T0r@`C^ca29?SF(Az~>=@77OPL!+Gch`}dy4SoKY^m9}*1726a!=twR(I!ZoUFIdOqR{J7OyMy6#ocFLACswCjppXjtb;W^);5$Ol zL+NC7q02G~k^Vaz@f>M`a#ph(YeyRmTAr7!^T!MpvS!8VI6JUHLrt(3RD4HCXPx~P z{dN5W`qE?MdU4rvX@q@xlDsP0_-^y9@=DWX+p;3LMj@y0~LiB}nC@ zvYy#3s*bE*KXHoX=6*SXTyZ6T=K6BP>$IUk1olqUqVX+1R$k8Z{XX8A>uzXp5d(%& z{A3PJ;r0T=nu>N2rWKJ~! z-UZm}+Bq-$X}rd8fu99^*z{}~+}e%J`|j%B=e)Or+Nc$rZVLZy&IVe7#D1RG8WtvV zFV1LaDLR71l%z_E|2JY*$!!!#(`8sPE`E<{we1{FgMu{ZVefy3t-ONtf0C>I*ZwbP zYXAA`e;UY%*2FGGz>@pmH8(Tpw-F?1E1cyaT}BVe+^@Uz-nUNHxVpH4(Povba2A5Z z_@^R0?G`8Hsudt2p^ZEw$4!OmrPZ-|R#UM!A3LYtNz9q+F*M|qFyXlirMA6(ydD^i zkz++ieS8%s92)qXmQ{Op*c4D%RgKm_KogwPFlS>T{>H@6iclq=lh)R%5{*+eZ8@rP zF1voRmFFgz|25W0v}zgIh)D#5*hY6h`ru>3P*tnh)2Pq#?!Rv5TeqW>tcv+YBuPtK zcZm9+HGS*W_M=S&)&3MR(|nZJ{WtK~%e))Y+PE0*o-p2JsMA)uMNftv(yBwCm z531HiQ7EYw6&E|W(yeuHS*NVkP~CWBISvH$p`)c!aD&;w)!Tm`u&!=DWCO!91)&iR z8?5z9a+ga)sP0H<@LrYj$^rp`GDU);IcH*n%EHt2R9kfV&<-4vjd=&F_%y+(24wB)yHjt^feDZ5gI+iw|;HKn`e*M|#?aP}`0el`53xgcC zlN(-np@qE2)-Qf7a(^q6h~yau7CW}}92#v&$I4CXuEguU+$8g3NcekhO+=Js(vU*t zy(C=MC2zOEVoRY{5B$GtyI;QHP_b*+c$Z$mbm?M-NI1Xz=B%}<+Zp%Mzld#KRk7Tf z&j9-Y$-(D5J1=Y{5jc#Xp-$?t(_byl46OE%OlHbtV&+3dM}Re2>{j}&r${YL7giJ; zQ>T@<@9H||=`i94wOfzB5g{=3vg2j3N#*9}wu-bt9=$Xgk7Kj^Jl(Cgcr_6`qx4%C$CXGt&s)?9tMU?SZ@PDgIP?Kcw z8d{{189ZKiGsvNoM$%8iWwnRrM#PhIB`GfEiJ6IEQ6UW!nhCC<}vMAry?#o&}nM}~lhaQc|rTxy*=J2VPthoS}7 z=Hs)}>>>nOP_yIpfFA#S@z%@Np@qWGo3VMEgMivQY=ZmNR#G2vl7hdCy5(NBV&>kF z^&()9uMIWLIhVf)<>kWWa_y?s#Chg=n7E%RI)^prW!_6xoYk!9WJG!tpBPJHM4OGX zq2CWheK))!-IYbl-`1u$lSLkgK5avFV;*>*Tj(KRwdU-AKNk+H77qJK$m-#64+xW&1=|zG~d42#?0w}tqC;7JQnmAGvRb;72wPeKH zCked`ttxv}l2;MbrH_zgDIDzNMXG14hna3QTFiK;#s(BJPrnnUM9L)>+bG2>7H49r zs+?U-aaD6Xp}ssDjVm^AdRGR|caYWYkUAWPEEmOxEMB;6Lu4AvGhxd#X8J369)}Svj^=s-q*Wp zLyAx`6uIGRyh3NY0nM_ZQX+$8oFh8ww>wF!**7Q&y0}M-9rWMoz1p9?Hl@`1QB;&k zQk~X$O_8@FOzkTLiFu)8xSnM7M8`qNQx$e53ruV3h9BeqRJJ8q6&r_qAtmWp{@tdG zYW>#BB>omfub$>M>6id8D|--I*CczsRkzqbSvxDfH>^ zM6PJ0-(~A99{6^t)>gie$)Tr1$vivi6Vk5XY1lHNX^MvKBaW)#BA2nvQ3!!ZTnWj; zKS%zc39<;6vV6$SUv!)H{nCLOkqyp*{5G^lqI68-hqr5GM1IG#5MGN3dIg6pAqZTQFS z+%g-Kzu9p#Of1h1?fA1A7DHsSx%(WHb4z9+Br%A{9i_Xds}5BCQ4zz7UYNC`{PS$y z&t>`SOj4zI%sDT0TB~k-SP?p0C|pP!n%d=s#JGMxTP-F2>GZeex)Q(LfgqP4b$z47 z-4&rC;xK9c8ZWha3l#Y7e7m0e)2QouKQRN=x%eOx)2+e2h#+kR6TtUVD}$qlcpm#tu^_GJlowXpLh3Sn90y$Ok8$CQ2DD1kI5GwkZ?s^b z1HU5{_w&D_K0dnV6#O}BepAou<<|XjmxmvEzrPzr1p#z27EIh|`1(8l4{;yn<6b)LEz?TobXmRBn>tza__0e; zIU+;fqA@;f6Me;%7D!&_Vb^xc=}t4)LSXg%ER&rAV9~XS5&DS!P-kiYR$y3=!iQn` z?hTZ<*sTEDX+IuN>_EKrpfMfU`A84wE*9`Vl8TSY<{SX@Na)r(g7Yvt9Gz;GB& zE8>x0u3M~>BRPU|a@eU~Nh_rdZwtfP-V>Lj8^f#T)t`sf$>TlEW8$!uQCRrxVuugM zYhr3YZ1;xgnGXRgPG}1`f48!&Nj9MH?9HeooWR5-18y-W*3;Cw+u)rx48MNoy~n+$ ze!C_{uHU<_RhNAdW&Np_Yi`tEx#iR)JM8-qQds}qR`Cp~QWqtLbuB8 zx8aLC@QUzY=IRjsWJ^1B-TSNLw&L6=%B?Z^*+6q=Q)osv~>ro zG}V2{=n&*7YFN)_kUCfUl3=iv9>1AxG1h=qe9FWB4eW=vI(;{kXg=29nK(#YVIYZP z)Hu*&Q}}hW)c9;nFY{obwglbSMqH_VPN^&;2C)fSD?oYTU4L~Xj5dqaXH3OEY>-aS7MM6@kf2({IR{peUMA3jJ9FdQD3$TtTmi`>^Re?uqWB9 zdIy>KQ6+j-S)x*R=3i89DbCEZv`yeNqkT7&RA-WH4o*eFvMf_kVXezd+Iajso>*BfC@H ztV`4QT6q)e73`a{H@lo#!Q-8-u(OLd;%;ELp|bFGMaK!OL%LbM$TfbcicLHn*U|B2 zYNeyU`c1lvH10dJk40$XsD2;R-_wo3E>C|5(S6@Q6!^Ih83ntT8An%62`cihSilr6 z1AcLE*`aDF(YzNQ1{w88Q?G(?FPxkNKcLFaH>qN!8%~Mv34!m#V7SzUjS=js=ZuIXj;2hqyg-( zkte?D&#|**BYPg56Jra;Q+QzWSob%^^rX1`v#Ev!=(54Jup}%Xw zns5hYb__6iEC};VXi$5t1IOC5UO2I2bv|3PyQfqOJ23_IY2@L4a z+VP4uS=OH(c+}$7u1UBB5|%-x18|wcPc@f3GmZ^hRx$4JLKoR$-XDPu1~LQ^Zap2Z zVWss7z!2$w9!I^dn2Pfyi#lYLrj?9_0!oh7gSv11*eR{TBWYMoHV|#%?43-v{>5XS z1G!7I%g$>2e%5y$)9_L^aca=vW}!A#YW~elc!G`0QV!Hc|GAFaUO0A_soaaCxfm>Y ztG6JQyd83X&y3&RnR)I42KZh8hW*{EK9RI7v6wY0{;HgT$0k79VD~;?>0^m<6V9z` zn5pi;cvS~iqri-5aC~m(;g!b9zN6L=O87%9U;dk?x_o?W z#N+35{ZqBv7V5y}jkXeM*XE}jiM@}J4AxLbzV9oe*bAT1)OWAzyJ&$Z7JEO7Og3Eo+rwUAmnr^3j5l zaM<3; zuy`oua-CeCj=p2AUa{ou3A)#)=aDJ|T$9*vyf)gbd3o4~w z&`%wTj*H!exou{{3-!gQC|%{?i-c5hDnDxu+;GEsyWZhTxRM!=gH&&0a(6J{c?h2& zpH&jdplUp5Q1QpExT@*wEp-2f;^c~Ov;;7O7&&zSvjPE6{B~{2#pD}RP{ZOKwV3s3 zc5D>CLpUK&$@!P_v4!1r{->Omj8raX`=Q(EFJV8`mIMkv*(nvRj^RGhYn!V1a@+@> zURgh-N~-2$Prhp7KJ39W6Qr6T@%3~!zu)Qc7eu0pOn|&2-wUJxjGV^)~HTOz|Nxe`$JU3Gu1%6{mA!o*(p1SK!ZvTl(f!ZGu_&&ZCE` zH|o;~xuU4)VK4o@pX5`Z3q-wks|q0UTeEughgrAXQ?u*1yzWv(m>C1jS-?yeO!#mJ zJybkb1rnL9KgAq`0J&22s;)%3O84b!!&1TZdC8m#qLW=->d$eH?Z6~E?Sfow z#cgKp`Z=yc2l?8r{k+#5Om923O4mj`csbU}9gC2rIIq5oTx{4bEz1+DP8P7yuF4d@ zEPp;Yl%cRkoafw$jKKmio_u{)3R;Sq_J_AGOe^7tj+@sv-0mFh;nf=gHjfxHP>&+^Vo~*o$Ni zrGM^MXvU!j{Zp1(-s_KjlS1eifof7usqqB4pke*!^nvpAr~VGBFx&92ps^F5b3Etb zf%>M>4BikaLM@X-PfuW{LwABi$f2i<2la+G?>9t2BZ_E8DV`2p;Aoo(pC5~Qmmn;b zDDGIkX`Q0UV_$a#&dhmiS=+mg_it)#FjM;#L(5SzuA-OI z%cLBE43zG2};ftr;700^IB8fH9 zgE{^=^=o@cI-wk*aquARLHdIRsJzr;%gbQV9I)l<(+#hkJ-{Ay7l!L9t^V?2?+zp$ zttx>%QboTP)nQ#JgM=STf*d{l0TmBmfBP{3P4NDZ!av2twErh)l>eq0`8(z~bA3-M zYI|$Fy5KQ(S~M(TmxyC2kK2a(sQ>d8vsMKsVU;b>vHuSdGykXm@xSU>{a+}#{__O? zogmi#{(`-F`EOHuvxi&yzr70!TQel9d^-nQmzhHmEOa}mgw%giE!+H#V(K$-1$Cm| zB4<-ge+rUsl75ai{lD-R{>Sg*6*jLe(;YPz0&dsxU(X4d*Jo%g*FU9XV+a$rwL2dl zDsJj82I2p+#!2Sn$->^sovhJk9;(Ki8=XH#yOgRsiTZy{RU@8!4 z!`px)EraW%@XgSosrYxE_*jh7-ygG^Y4+U}*!k%DHyS33*Zs>$yME}eiv+)XBIKX8 z#(!13`=9SatUKd|p%KPp+nXq+;uh!L)W&H?u#nY<|17|Xvl{=XTiqq z-yixPzYwoXH=IbZqh+Sbs-R|yHdT4xw?o?Y?@A0N%RT?u8MW`P|5ab}U$*@}k1b0f z&B!L`00R-@pNsV}s`4t3m`amqZ?Y_RYBjNF#U=$3A*rQWuCzW2#PbB(mBZ<(9vkh6 zn2pYFAQ(s+|AHic?iNgL{f~2zm;9E`_9oPCG z-isom%|Z^$1dTRpOKL~N7%!qFJ#W0*8@ek8(AuxB=UR%g)tK>DYEokLndtl*FaCKx za%(X)!IKmPtN#F$Fgb3VP-7$GeA6p{JROKJ`|fyOfY|Xg?14AuGroI&9kaS=VsHFh zgwloSpS5szXKS#vVQ?KLqk4t7A}^SC0rkD1!Ns`PHLn0E^-*Z4^>v?OIZ87ozzEW8 zku>B`Te2Emao6u*9S!J=ks(evRQd0I=b7s}GU1qx(@f-LI&~<$ly+>A0w7K`Wd1om z;I{hs><+xViGyHrZ?@mV!sq@PJF>%!+Ts%XAWDzCs9ecDdwLcC zFWw)~?Q>#dU4GXCn=-lIqV2nc^ft^@dPPyD52QqBITfo)rstFE<#<&1cJ95rO#owc zuXd3tl4Mn=dKAWU*k|?WiXjR5$Edq514w+r@(a4E+gZjtclvZ(LI7#C4e?E%;_{Wp z8cphXloX|!D6w2lE_G-3)@N$dok>71?19)#H2wlL4cPk?j`LTAdhNMD0Vhq@A1I^x zxU^+_m&)psLaa((2CZHbQEs@Oi(U0q%;4L#DljD~ihZl&WLjqHZ+(R`Zm4|YnlstZ zgwMk+h&#F1t$#0D#r}=_#VJ9{j@R*UiQD@1Y$qQ1ENcIG((Lkd=eUjBjknZeU&ZNF z$w=+a$bXEee1eZI$>(S-*;00nXclB2qHZsC1l{XoIgbdy`YNmH+4zO&qvlJY7A#p0 zE6kTiUKo^tp{Y;SQpO#(7HUREX-u>t1ol9nf6o6)kf|R=bOchUi|%Z$in6%#vL=qw zra-+-TbnXLwbgjDQ&$AFgEn0AP`8A_j? zrqJ4Wm^^KKIFX5+0rk@hNMxj-59#m0O#bcXBmyFC%RR8=Yf?L2T^ek5x1)HXqufF4MiGtB3MKdYE$;!|X_}4Q&u)H0!^A zGu?#_rYFahD8!D9R8BXRAwt!HTqwOFMF?dJ&Mq@OR9$n=b`r3a!0OIRJaV8xLfNtL-B5i5v$>?q} z1ll^12M1gKE-9wj?%5|0p7?Tae=caVo_4T1mJ|b|8uwYX9E&|JRt;;`80572Sf1MZ ze%*e@V==wo&of0v^Zhd7L=4D^57%0=|_;hOpx%^cW)sUG$I~@zT9HgoF^e$tq z&~FRid?}SPx4wKqT3##VE)NP@tF^ZFasht}FkdOj*7-Z&eRa=wPsd4`^8S zbq16IX5-g^rPPBlgCZ&WKuzOaAHCt_ypwwWEaqLdhUv7J)-?Gm{gmpB##=vSA`8-S4hR9l3zk*QbRk;pl!?Bu3@i!@)~1}VxgN)uQIko%4qv$ zmrSMX)PG?*{l!=g?qa*?<&)Oa02D(FjQncQK++581Nb)j*(B6o^%kmH>{|t9Vy@9= zU>C5;eWd8@3-dB@E-4W>>_UZEcHkfnh-&{`4RARiVw4MQIU>P&XmorF5v7QGUM<7! zhxA>`6h07h+|P4fC4J-JvEV^aJZ|0}nkLN`O5<-#KYQ&GDWaEnAagvygx%xQI4I3( zvQe#FRoN6@A0Mf&uZxhBknnAf=yF>9XKldkIGskO2{<-R%i6AjN_&jEAc~Qx3Ff$Chky^ljD>9}}M z$F~{Ri=U1VC5rsx6}IkTrkg%aJ&ju&}}d6D*~@UFj6k1_w4q|mtD3|S5w3nOoR z|MGA>(hTur9tc@Vkoo&SRrrEN{h!@I90eiwB&Ye%VJFB(ZavE@G&_z9aC*6q5!9_bG@F+7Sq#VErB24lC;SoT(X|l`++<{`0mowmA-EF8A}H zjW4D<0o+8wdu11<5m2HLZ)7y9a6 zOZ$E5YAkRyV_`-55|Z5B7CJun{&DSIySNrJuRtp4C zs_kF0&%m)J`8x3Wv*qM^f(&@=vxtBvCsz^vyEkCYSuZn7ro>;(_iaCF9I>SvRc+!> zHmfnMQ$GDCFKK3PU~g!(XG2Za>MPFFtsw=OwIOamJxx~yc`#)1f#1Tg-}-lCSCFO4 zRPPS?lQVfAE8DqEeOvzeechGo?fNV-4#&qP6~ZmAoII>K1EMc#RiQ4U7j!BJ(Ho4H z3&A)NnO!S?t~anASaR~>`{>_$|1#y*Ta|lTAt%Ow()#hx{SVX@X3JH)0{L$-mteB1 zg-^E$_v8hx<=@+!)X4#p0{+EBS*O4!{snEpPiR zxESdi`TjS#M?gCTVOeVo91Y?NQuDtLpHWu%#>9%DPS&kz0ys8gDmBa(ZK0~AY{t+j z48dar4rIRv&lB)8CfBQ5Kl*RAd7{poaW3_nyIG$hV=^NaNJNdSrxb?QBl~Q|D%&OO zD(d-AYC|jbsv>RL9>>TMBKCP3|fyEy$>f5MhW@=?S zXhUG?98O@5ki|!b=JO4)Q8O0}Z&M8x*~UNfASfDT!8JtEcM^9|ev9LPo(!S7RHMNq zY(?}O_arDJ6lljrPFa-${Dvc5=bqkk&PICl!}|adc4&>NQ6Br}u$|KC6daBW3kSLq zhdCP$(a{*?rEjC78643m{;bKk3?1zo?`9lL zA7mxK00%h!%k-D%P$0%ktbC|P?IH_sZ+g@hI4G@^in(nZFi7D856E7k^Hz}hxnmCX z8wSS`$l{UCQaiGYTakCvxr&EpzHFWI1RsPdwDOv#r&aRziBQ};4s3_zAzS0GL^1_>Q%d*U7$U@-jYrL-vfw=QA*GEUp zDFYa0$5+kR^|L|fw5yQoUdtbFHZC^g3B}Gk<3Xm+i&ezom~8lE49!Xk9yJN_8ON$nOIvB2?=4tq1>7RM$+F z-Q*nuZBKTf*)v0ld%^raRt;686&^q_FVUeHhPPCFNL5V56)_ZDNU11fPKEwX`CXTJdMe&=;pH=olAP=p^yIo|^5Zio zQc7P@!98j$IVjZ_loM=Yo0mR??`WpKLD?$gjlKW$$#K{_wt)7AoC~LvcrvSZ?0K$b zD{o(^X6LGEx!b>e(0RGf5v9>8qadSOS8tL5>wf9zU&p5ac;Y#m$U-q0yf0Vxl~^J* z3WFcN)f2ZQ$i+#PSXt=xM_WRe)MK-K)`h1(lrrPu8`DY(;5np;sTy4rR0QoLInW&! zj=*lcB*k#IDnGOEKi9nQ`GjbfjLd{NcO!FhZ2xmIgmpw-x0L9t;G@2fM+tvH)rHe(d#uxQDOrDTJYnRPR#c%0u#KCCH5-sbH zY<@UFm>=l5b$y=eIIl7l?$Py*VvCsJ1|au zI+(A^M2PC|Gu-7IsxoP^6`}z;&>RPw{J0KK-e5YP{nl29cl%T11a3Nt%>I8EcNn#* zjf>>&d(ILh4IIs{Ai3eY!AhN;ex^g-1j7$;=_~;E{`&d(N*g;Ayq@Y(ZsI`m5)@w) z=f^sPZATuD8yqe96x2@}m)LEqWcSY^IIeaxPg5mYU!Gzk7G zV7MRk=;6a*1eLbf8kfA|ptxirtohQZ(F?WJ%aTZAwnixPeJ_qa=zZyIgd3j8 zPBSXqmUQn0L(pu5hM^-5fBn)9)>COvi`56IA{Q%r5S3ZsH;k??3p1VP)UXPFZ&+@N zFZmPxoQVEqIiDKw7-XT93{0(k2QQM9TogY%3rLxE4Pps$1%me70|HV3Hb83O+S7p* z2G@VIK3Z`KtaG}wUG%vL)4i`31wlYl2`wX$xecq2i3L6-k zx@F!(yxowH4Y+VaF5f66dBs8&dM9{-p@pF%+elU+<+0;>#vbd0EHGEXvCdt1cc%2Vkltr_@6N|sJ15rH%6TG_;i?yZf>YtrzrQ37Z*1LY( z2ee76%g9dU2<3eF!%%R%F$^ZNKsy-(`hwM}vsv*li2*SbWy(T85fR+Y23eIG z6I#>_P%(6ZrU{kuyOhjhXp3)5s5&6eNi0;%59b2%K4n#J;F)07Z2{lri;Jv{IXkU$ zAqs^*J_xnrayv_+g{;wF?>N5g=w2hPfvH|OZ~6O0rmk6;72AH*j9+wI3xD#j6K;~$ zJbfFL$1^z&51di4@nyfSVcgk$Bq9a+lWoqb^iM=aWQKj)S&5CHs4GdNZjS73^+T9|qPjinL!PGq_ z4jjBY$}4mA=_+rxo^>pI_PW&@Se!j5$nfjum|>LX;PIAn5UwJL6xeIi&%BrO-gvK7 z=KH_m@z+~|wN!8xlx7eU#f#_U)7^}mZ(UKM4>5^eV{SaW$#q@mw^uu*Ox|UtFD*`N z8t)4r!Hij`>lmB(G%q<#RLN}d@m_7}ljerQUzVJK*UiM{%_q0{fC1mZK>934c7*`W z=1=FLH`B1RSDpmHBZMj1Kv3Ml4jItYW7D|Jj7z>33_E#A40_!*5OyW~j$Q<(`N+KQ zT3>Iv-EM_=tV1P1Q`kL=N9PY3QL&61Cn;X)?36#ihHRitWvgf6hPk>}yZ0A!=_Y<@ zrWRpiE>zqxjN_rS4H7%|CXM!Iv)+tp49lB5_@q@5!zK0_zWG}TRPA~RD*DOv_Ik&R z^W?Ilr)@DdV@TnCLvp;=V<#54`7ypWsY1ofB%lUK7kB$xNa>{nZik!U@$!Yh817d+V z>}jE)ytU*uc-xE~bIQAX&2Y!@67kGqa+aGHru%=&OY$%VedbK%wESa`KdnIY-*+?= zv3Bs{fgt$IMdwQ;(y_$SqKC#MG8~JQCX|AavT1bK0ubrj2j&tlLXPxERPv6isjixh zz$ZD>9ne*Qja)sJmw$g_HwSqy6R5}3nfVJx%Nt4{7Ltj1e`4ycT5{TH%_et~($Rze zT}k~})Y);Ch^cC)iLXxQ{_Mb;XJO=&#e7I?QNU;XMLm*m4!a79YR%b3yFU3KA3*Zb z_6=7RmMo9F?fN^#;V#Cf@ijlDo{gNUJ*aqrXZ!Xi7j&Nu<@{;TYC+!EN6^;bT=&6+ zxv8o_c82F5NyC+TOn{8eV|SmL5IjgD=xbOihx;zzc!Wk4i|guhVeVHKdyE@L4)R{7 zsS!>UlWbEW2x1aSuD>=c_Bs?>9*GSCh;ZoA|X;H1?O{P65cyd&DL|^?;LI z@OmAXMW+cxD*3BIy7KzV*L~}Yp-Q-obi$yW zud;b%Jvz6S^q*g$Hab4`3^PBBlNi|=p-a|~flY||zY6%`#bn>Nd(Wz=$a~o-Wofs; zY8FSjz4<|_yG$(b)7)e%NeUvPD`r_A8rIeR;d7h^I%F(2J#PQeGWK^;@Vc5rE&Yl3 zlqVvWa~v-{z(}9IHd7p4#(N_F`0rFRDX~tf4ZO78JTnU7JH_pSZm9H)pW8OHT3U;+ zX2PQUlYg?jCD<4pG3O`~5B!uRlBFh=C}N}5UEs+67VsoFSppJi@~_@)|MNwhPXV67 znXg17;v_%QpfKY|1E|3P-Zk(J<@JqJ%5BU zfYS)$i9mKL{qZjG5FqT-X_rN(W&U2d6texCxXQ+gYytnQD7CsQLQ5mds5Gk|MKFU> zS;;{adCEV8Ssj0?84E8mKsjoHG1fNhp1!ONe8tzHCAmL$huiSeA%Lxg??|Y%KKp6F z&Qw?OQ;k}WcC2ie_6>ubCMvRJ-ECNUay06VPP-$3CKiO3`Os3tCr%-~w5)?nR7vg9 z{<^RBPsDj4@;<6DG+a$JF`lv*=*=d%rOLc{m2`~DJyy=Y>dV{waIBlL*YH-Vc-U(S z_sb?8J*i3&<)sr*xcQ;lFlQ+#9u#x=d|7)cjwNomoK%8wxvyT{?a@YiIk!>$7EUvE zqO~5L{on}lD{5$ope`Y$IB0=G4WMW=DaB54>%|AV9Pd4Rm%+7%E0GA#F>J_ZX;>uD zlQeP-*V|UG?ta|M{xFQSaD1iL2vJF9k}%=+er=LY*dTt-8tG z{t*6t8b7Lz487D;jknaRz1dsY4Ycx;)(Qy~z~4hy-uwzVycI+{kmrPZs#V?+-QU-3 zDG~9~5gj`R&&D5kG%mm|e~AA$%5kS)<(eDc8Vm(1r@#93^UsiijlJ!IaUD z5?t{d2HB2L)FgOKV)Mt=m7ph^<0t$O`bGT9QL1%EC5Zf70m^$enPfsX7_J^EM76AG z!6cXP2C;Po`*5SkZ3?AGTGIdJ!DSKwS*!L)!4}P6k$}Mj{Y!bQH&owvH51tpICT_b zV8YJjfJNcJAOn(CLvj2)!ixS%K4%B9;n!0U@HYE&%rXkY@Aw*(iOIi?WCx6O#&Du! zvOcxvZdS3H8(Zi1=Bfl^iLzgEp^4^mgBOsNfwD)op2AvHruxlr_g-8~!Db&TZDrre zIJSkbJfOK%l+L)3Qx0Ema_U?X=ip>(qJgR0&>L;haGedk`v}u(> zuqO^6pIx}=`M7^_kxa85c&Lk{R-Yr~y8m{vc4%;t=hK2*+&?d}PPrFf_<8S%2hu@v z>C?+LR0e|hE6K93Yp{q1Pw7%hoph0nmFr2V2G5SE685t7FuteyT(%pcOfSoj1nL+gIZK>4~PhCk|pOvl54 zp(BxB-&+P)sujqwZov%}?|*km{i$~GE^Bi=iQgk$KWK}RqzT+z8CE(5v1On>EPgie zkNXW6?t@+Z|G=HFyAI*biBg)E%vRa#Xh3%uPSI$taPEg|JJ5_pwM46O|J1sBlRV= znMaZQHLP5n?ku^6sL3XR9tEk*!Ga6di8FF}Ed3G0mZz)ENJ z(>-0+&+V_CLax7^*70BF$G_qkD;Qp{6H}GAU?l$xO==9HF2#?GF(h3O=SR#qqHPrC zE!k<#hTAVwT^JsZ$876dCDJ+4|2(y1`x&w{_0^iy-=@Z`V!uw`9Ywy(E1tV_UQ(ym zux9ap&5eBFZOvbEyoM8LvJ=wQn9)|3c4*1N1WxqT6&%W8M{aeWBV~GzBOg~ZoaZ&; zt2l(EPvG%4uZHh<0Ptd7POQ`JoR;7tgwL?|R6JL@?z{dr*^EKAoNqQ)1LP4W*3{fX zCy`AKqW#5R`lH}V&QliE>PUvw6hom9`ibU*iLMyzNr$p7zoY1C_2T_dj>IQO9c`Qt|#YSfD)?3%Fpjswb zyDsk9KTzlF8;wIyjf}WvrK+YLB4*cOGw6Rr+k$w z)*MJr_mK(YYaRVarE{4k{AYO0zH8iE-0}2lKaPCiE<#H&b2jNy;<=84*Wa^KD*_P{ z3=%4y=$4=DSKWGHFn;HD5#8sP_>%?u91} zaoatI@%?8USfi!(^CzA=Hdbu*O%`OvZdCJ{s)t{jBw6fgPcENPdA{rx{9c0Y7d@da zL$#J#tN>5Czvofi9msWij#4Q^n{@H}P#^V(@ z@@F?%k|q4w7T{FzWZ9w5#S$B#R`lJC(3CN^Go+6%rk9{rkXvXHpPvb#KR@#a%lGc5 z?P4mq>1qhiDbi7uunm5m{hMhoh6cxu!J*?Rz)O-kru!oh)ny1k4y5?(ooKX|;^4(p z2(C}BsNz@4756jhT(?(^N*(pM9_J5bOwpr}H@&%*X&Wv!@U z6Fyxrbh+j1aJ&6+4itNJIJQH*Q19LQYCE2Lkms($7VO?z=Qaai3`lgy^lg&{qY%Z( zRS9%Nd4ooLGe_m7Q2r4_>( zS4WPVqSYg0KGxQ+jHV}`$}G?0+Cdn)?t!wp+aq}ef+pn#r&-5Zb@#Z*S`0Xh;d@kv z2U0}*OdR_26krX?AHaZs_ksAMX6&6>L|1hCJJh>&l>j(JsnIU`6`RH>7Cx>B_Uv+S zS0jS__XFFSeIH#?@isg!SnFI|;#g*mW!`o7qzQwDXD6yL_usRpF&4(d~Wrqj<*jJeo`iQyZ=8(8rs#oLAiX(-b1c|H<7vN}Cgd15XcT zTd$ND>UPN-v8?BRd$dxzQ>FZ%obbfJ1qK6|ny-UuzQ?0#I1Kn>US$mlZ47)rQ^;4K zbR&`KO^wnO$3W}k@G{5^<$$L>;ZHQogbfl%9c;fG<8WIhRmM0)kG9gOdp+41^FGx{ zxH+ueRNR(QHIDERzYD{AN&J-0WQ^@-{1}}UZ@+jyfO2+mK-;436>ranAF$?ut{R2+%k^=m zay~7R9*DbV0ue{Ql(vaxtew}4r&0?$LVwW>Q+l1aX0tpUwK@<2F)6WmIiYA6^V|mdHEi*R4X!Dc`-hEPw~&Wap9XI_)Z3 zHknhG^Kr_(v4iZyK|Rvhw-ph7kP!P@Bi4TRgv)Yt3YAdZ6F%x37*8Crty+w*>}Scb z-&D3DXCI;`8=iibzN{cnOs#=`Z{YjmslUqF8CJ$el(1XspH=cBsDiYowF#Jn-~$ApQ~-en6fOIM>hZ&2#fC{ynZh0;OTd1z&pWuO~1m zDc1f>KAvqXC~>y@X7|&MAHKLP%+fp0?!&9_7b@T9?rSJ@#idp6-u52v6Q&mU@jiq@KQ76#R_tDNFf}#( zLCQ-97^A#bbxo45LejK1IwN*J&8~l`q2v@Dp~p_Ws7YCWAmUw{UnTuXg5sSKO7@(> z2f=gtHwtCmrWW!yKFN9a#F&jE|Hdm=Ly&`wU8>90g~43gEh9p|L_}#a6XC;*4(k~A z>KOGcc}C}gT!C9{sOw7{9R#_I02)n z&pQL8`Qd7aP4w5*2&`qJ?7oZrK#m79sK+d%&k1nwZ+}gDm99BLWzL>(zX=!iLPenG zy~D#U@q38+D?A%w>;l-!yC$=NJ%@u>6r5koz3(u@e?5y2S1#>OvaQY6bWpO3NAnFv z$UdToTzkpvwhEsUMdYT7kM8`k-{dsi?8?;xj2wo^;*YR_%rCNTIpF(Jb~JwY&44WW z!#A!RG21~$;re{{In_2uM}sl-L{9lU#%c)WnrB)&e^IrA$KxEaHG{EjLIsb)g?Tp5 z;gSfRf{A`P_8!xU)uWu@@lX$!U5`2bhSfBf`^&9<4Xgt9ssmOdygJ86B00?ovw9)S zClT(DA7M1LF%YlcA_r}R*A&J0GcGLihRvvoPV_&UT67a881Ai(Fgc&hZkD1wcPYw0 z6ULdZJ-+jK&iB?=m#y^o*DO7_4qm|oneB>+!TIACp5%pGFPq8PY82e%u13ZSZbsc0 z@yDLA3t4NqWFFh^*{u?N2v+;h{#IcZ&k7C8gKGJz6qOh2hs-(O=2ZyBU+6=b@;@0Z znmsfd*luE3vFIV-#4c*^i;d?jA~_wwclq)aj`ey<2;bSpP|owu($owM`ZV1Ga>$BO8bMxV-tcd!mPLm}~29{CZ)%ct=J z#dJBQfTJqcz1K~+-s6yQUvkdeXz$lTcGbj7hAyp}-Kp?I3jc^@5mUL61~;T*t(`*7 z#CE_XqxS!UwKsu=@_qluOM0WS6J>29$(F1!qT+3tL}ke`p|VaIvKz7#GAJY=Lza|1 zjNN3L2{DLhMz$f0#y&I582%6H^ZB0d`JMkczjJN(u_~W6DNI2?kr7&B-J0B%V7qo9cnn-oODOQKEj~?`O2|~6%Up-XI z{Nw_ugnm1Nlm=i*6fe3bWR%a1XI+1OuMI8MCBvFO5cw~fM>BKI6q>r zIuOyElFzDNU~KL*%w%H3tiC&FE0=y7;CFkM9L<9Unbut!C6 z>Z_Cwi_cO1;BQnNdY?oxZPJ23_SqXrn z8%TbG6=h=bUz`AmAUHIoNTheyT@D%hbL+8(tY&g(Nc|$M+><=P;?QCqo5do^hQ0ZE zo))T(aX(Wq`j2>l^%Zp7_CYN6~XUsE_ zpiUQQ-UQHOn!1|=m~z+*heERbU5H0vPxGP2d+ET+1t1-;p(@75T$TF9A#<`VxgKb+_&uAY{RN* zN=)6av03Nv1)#$KfA=I8IO%aTjn$_kj3dd_f+BZ0txiLP#p}&C;}RW(b}353_Yj zrr88!L08EQ7XY4MJ~_{HzaR zRN(vtR}k9qK77>$SH&YD<0BJ}VVjp(@ip=AjG%@5PskstU(_X>ov=Qb1Eoxjyas?!J4GNiyc0%Xhisx z%Ag_iv0d%z$M2K`s!uwI^2bO%Iw9cTbBy2GBzZZb!;eRt=j1&lLw3{*B{-fF>DQVo z!r(A@uCyO}&t5Dkz$D|^-Ik`zfL~GfC4^papbFW>q?abVTh{IQTMn?q4YTR)r%WuG z3_A>fQuG?Zz|k~t%KJPykcPB}!{iH&HZ#XWpA3%9Jf6kF_*m3bjWBnm9BxkNqL^{z z0cN-Rn}7d-1}M;^P>p@D4)!Ap^&7FXG%NxJE<5+!|w#H@PwDP{ZxJwOF!O*4tWpSIXTZ-?3Ulk^^XQ;X(lxS0k~R&j+|i zEHbMWBfP`Pd*)?)%DH=DHjSmM2Ubh!#gAnq_zb-4-;HI9w0_lYwc)B(abp*2LfR=A z^XA_aMxLNj+k8@;oFyv7hAvQX3Q|uXN5`F??V+X*{)DUicTHA!_bG-AM)JfBDw)&` zDNP8?4Y;;s1QN?>1Ywo1RfTKlp@IB+QQe zOzM*b&7(d1)NiW#2goZQOqu$FQ!&!wXQ9460jff!*>cv}SeQJm#*WKYt+vO7GQKJO zRnvcIX*__xN5>*%Yf5_CkGRq7^NT2fF7uXvfkmnkh9KLn9EGFjA#W6@z@ek3cbr1c zm}&5&r{8yPzV$bk%f1BsjC?hau?QyfsiRQtcWp1#*^fn1$Lo%f4~59zcuiN}*|iI& zcN4WAtu+Y4t1VAb@BK=Fo7{^_`%UyQ30LZ{;5N3ZYK$GZIcK7jQ`c9O&A(sg!s?C_Zs;AJIgW@ADJYEIA7n?C0UTE&K>h zmXs1IA|#7@HugOJo$DFH^kE5xBl34dNS+-XA??n>O%X~-lBJ0p6zio%_)iIVsCXi(b^C6#O)bdcj;LwMR_gRtwr z<_BOSns0#v6)hAnHUv`H`oaAy$b6-as+1RX?h?ygyg5`|o&jGjEGUh|@9N)BEUASO zg>)yN?CiEsW$xUJ@pW}Bi&?JHuvT}aq4=khff$u2O*vo4ZhSE}uq#;a9BiXwT2L{U zi|yh$T~&|tk**YcJ9^{V7nO7W&f^Vi4;YYh=8qp^s2}@V#<>{=I3nO6Uq*E?Q2p8Y6ubSoyMy9h4JWs0rx;*yO&IG*jp*pk zAKM(lPY=HU{&cFukfJCb5|9Nwczmd$(y5nPWkq{4lWjQwF-Y10FxT z9SKX!_2n&dp|~9SsJ~O4h&$BZivkz}d+0|}!rtSaF82EWy=de(H__gI^RCa_HEf}R zPFXqKa(jlpB52s}<~^4ubbBXYNp`*?XCFlFcB3um$l)IFMrO;5voQTT&IobZGf0J= zDWzY1wl>lW(F{KYSsb3WOxqg0KNv6r6|YS=z{YhubmMhta_Ltg%4Q)tFfWinzB2v8 zcF!07DLXxA+!`TFm*1(d{+h;3v;~TVd!cgTMU4``{rwLfYuf+jv2y$`qU-JjH7dt2 zZ$r0!k@%78gy|o_IvX9^c8yGG_KL~-@R1>E@m?r(+O>9v+A8NR?Dywgbb z7Ji1_(O&wzZP7?4rB8brC)}70V|yrzd)9Nw^PA+^X2$~}#qt4Plad>9R|<=>K<1GMVwbkC=37xK9u}6uuOrkgfD6scTV3&G-}~d=UK&UknPh`03SE-C3~#h$w`+ha^{Jn_h zrol9BVrI7dIbvvg)?mA7)hg|V&|1J)ULv-~&X4(!@^*0(ra|4V+AKatUP)l{d1p5q z|HAziBy`RgZ2?H8Cnr5>s6xR$0j0PQx&e78%h+1%s`yyx;GHVUI7lgof|_bAUp^LV z`P!Mhm~_S6;zhsdUr`ur-!WQ)Ko~zEjLh@dW}Hce1V)>0x z<#6A8M-9YB$p<`>{%}IhiDc3kbq?D?cY_9Mm+PquR{1#kVfX zq}8v=I_I(Qn$6%Apoj8P<)Jwzv^MRz+ro*(E_7} zrg!XM@khn@7ZJFrllydSKJ#Srp0G>`KK!|aM|LVOjvvGO^^qu)VBjB~uXqlvK=L_r z?^Nn(c0j;$3?ubj+?B)6p%^_=i%Fn7Zi5CO{Qj%2S?pCIXKb7<6)c>PP++gVB{ zs*;YLILhQB#)jx=FOm45dS!;|g5>5V;rGIVn7))gq2)T;(&2dc3@*5f_|7#b{HHKu zxhntiUs=*Wy-fFJM1i*W3GsbZZiJZS&OC;2C7h(CKQwZ$@n{2QJ1HZqi3)z}eP>rZj5vxyB)T z#?Z8E1?0-NkiUH-g;6_8^ZY4H<3>U3D?>@dspKwpsRi_Q_4Qz1V8doz0J|sPUv(mq z0Vw5xtLQ(!c)EHplj6)KyX=L$+A9QC zKlg7e?8=v*F1fLDxR0bh12l-8&-&FDQk#}4$>(n)bTQXXR3dFBETr36fcw|!KCdj| z1!U~>kZen$8M`_AEiWr`EbRvuILsJH6RQH-T%xqk6at=@_S(6d0YR|K)~XPVcAf)C zxM=al;X{l1r~-Wo7>O59))HU7^i*NfXxG_nkVbq<(c-+bymh=yVS}kf2q2D;NI`f1 z6Z{4Hx0U;j_O7}^g$<&wJ!zIZptBsajS9YhHatH6k?awO+L30(bGav{#w%KS)dr$p z8QWoBS}v;$ssTVbkE&a`=ocWgxOAOW1RZe8ACI=1fB_R5 zH10ay9*k2Py`9N9J!o7`Dv*CTBLwJb^rK?;JR53`1XGvevV6KF`m*E~=T6?GyXFp} zj0DM-ioZyO+hcK17S#RpI(8p_u~JXG6mfVM1?H;Pv&pJV_TNbiKlMk2-q}(u6vs>} zU3?hb-ozf!r&{8IWRK8z5U)*S{IO1f!=kf=vz9FmdXosJIMs*8_s~?s#N5H%=WK6L zXZgoyJSCX*2&X_jp&ao(a*aB&rgJG%H% zxBPp>SzRQ1Cxu-UdY#vPst*e}2|$5z)%q7=vhP=?2+d`t)J9{0Gr|yfv|%AU=IP&v zcF%cP39B7)z}xw0o9jhw=w9QJVVB+_mD4Y zYfZwfY6S@V`(X8Yk23DQ5`!dU6^W0Q*;N8H7KTTJ=++Zj@emtDlNHp8c%dB6#%j&R zC3c1kI{p?g0b+oP-DE z!Hg`3H{|itfWNZyYKJ{o4X~2+6nNxs;Cak>*4mui?z7%yg+)FtMQ{C!EdGVFS{?0k^$;- z?QVx#xf!{0*}JP#Dl!8o!L>G*_ci5|>8$fImUwWa>fs|EZP0^r;~Z2UA4~ zIadAOiE&I6V9!=M+(aTO{*IgJ@7zhSY`!Z#{J?vw%q==ABJ1gA*V+gPo=$+U@yq-f zW%kh5lq-#(q8Cor2GxhzWGBmd;YI%tBi|$mkUNC!TrPQ?zbhfD)brO35O)%~OCMU* zT?JT*26_I|Sp8jUFKK?;|KGt#vK>I&2#_b=U;Iz!Ucb>!0`q^F64t1QW5`S)wn9_; zfJ%DRB{j=X>gG8Vn3@a94@lS%Lp_Ql<&-J?7u9 z=KdcuP$2o2i?O@J^$NR@88A^qnw!C$$6XhD4GAo&V;(l>toRtX9whSdHN?&`)3x@e z_v)p=YeJ%>Tk8yGOP%2%X7>BSJlsOf+`m3QunDi6y&G5Pb#606k+SM-&Z$4n2)yK4D#bLZ#V7D za2(jd21cGo!IZM&UuC>#%CBslel1=s&Pej^9J^S8UT>Xf&yA<%W*DJ>)rw4D1ETyo z%9eK_T%qJ^ZAA+F!93ewwdbWvl@mvw7T5{QR+NaWDOD@K2@5^t6 z%sj3HvWD}uD+RAAUw}Szg^53GL~mQo*lt(hm<?>>gK_y zz};PRCGSdVpQ5~}$5Y%W{qSJ6-*s7B3N8Wy(n>Ax)(f6UE$en%Ns)~AIyYfOTtBL* zQQZEcV)e4m@>2Zt-xRUb5*2b;;PCLhP z9>44~FzDxfV*B;$?y$)^SdB^UcE8-r>rw>j4&$pk?tHjWcvX5R<>yNE$H2)S6>MQ_ zviXD%-Bb7k!Nee1sX}(=vFVLRReX>cRCqe*P*@F@S=u8;uLy{?y5dn|^pb2J0=4^q zV~hBFky@UH>G6{M_muzf_of#fiU75>D|wzXTc0p%UvCBcK02deA3#RvB7X8H# z4cI6r+Ax+9KSqux{t5w@wO>gc=G%d2zAx~qNH;j+m1+#+h^0F0FY8x=hdT46ZTnu^J8WaU!rR-Skp zVT>GWI?tz{k7>04dQ@bNr%lqoL&oopC5-uHX^E7C*KPHz{(AZ^tA3EUrlM8twOQ{j zX8Mdr5|(SR-RR>@v9NDHA;IrOgZz*qjGuASxL>#LSGsdevg0_=0MYxlczt_7&pZe+ zuSTcziADXx2+CnCA5t6nm_kF|LSq8DiZ7ry7(Zp@_srPcKfyNteH@vd4oIT-Xd?{3 zT0QHP9q2zY3++;jOp1!9fei$vXc>;L=ByW~k36OY6zg98=|--o3L&4k_2aglAPc|V z>-P}#3dcb{_TgDIJli+ro71r1Wn&2!5~G2o5c=x&DE7mawZo=IJdZ4`0-I3i*Wfa?ffL+z`Ephm~oGY%#`ToN)k()mb>2Mu#Rc(`b!+)!+AuY0?rq z=!f88A}NU!EqDaV?;O@)vt%=8GOX(GZsAjcgviMC!lpnedx=!rdy_9E%Ur#-Cl;YD zn}Oq2A;l(#?AOYP5o1#cOf5SS&BU0Qa!2&$z^e`usdJ@2G&5XStA(6Y%m`C5No7NJ zl#nX&N19Rk5liJV(s=g;-3{!tj~(2+I#n@#t7e1l*EO}cyb^51Z6W_VbPl1*td{~= z_bty|$gfT@9FxoAoE`dX#P#q#w&s}1oRLl0otYKm`0fv_ezYSAXQZCjOXXC|cTULx zEQ;gAN}_qRQ?|BK-eG7n8Si3>F-})ca8d42!Dv<&$D;1*RL<4F8*UUuPBLrdCT2t| zS}P}lzrcNGzqm#yS?K79%L0gA*=>YSnR#rQGSdMMV43z!pvPNxhd={ z$TM?lF`}mFykLpVz}u~R(;_pf&~O*-cyF6pd6k0~;7i8h0vDMc9bsR0v_*8XhG^xz z-hY(q?YVc_?31%V1`TgfkE3Rrvd;>;_vu?dR47r}JQ5F=q<)j?@t%6&Va%~#1N#Cu z=bvyMb2Q&Mzms@lVMwGDURnodT$=vj&+J^Dfk=@Un!B#2(sws!w-Odgf||5oa_LAN zI(597oG$hK6G+$8WK#v~g`HOJI%l{Rf`BI8t!b{$CPxp(I3X}uaLL|^IFLO%a~z1J>d zsMSxL*i|xtiyQxr0}F%9C1~~dgDZ8BFcGi2H%dO9}w<-#0OG_VduWkb28$u)l}TWI!qVOPie?>+|TLrj74& z5sJT`xrc((k4`ydB)jmB#%cmKkr2y!Qr zIUQtm&2apZQKiC+4YR>R`8xVL<8)6K46jh)ZA(OjGDlWk<-KGmC+Of=;RL5-?BXS> zQFs6D1Rkka)of1Us?mo7%oeiXse*ViX09+#ps-s?EEo&BP;xe!nhLnm(D^7>8bOm9 zX2t-vY6=w(`}ghHXvp|PM)BCFW_-lF-{xkV=J{*rPj2cVGEM`_d&j%)s>r{>uca}o z=dx2hu5OWOb*WlTdhz|IrGC;)%Hk@ZU(jZ_`KFaG`5OEQ(T{j3hRbMzaJe9(CZ(trd-)mutu`nU2#S99%96G%s>_HFV;(2i; zQdKENG>v+RBl5Mt#VSis3To+W59d_b$tgIZ{?k3%+HFQ<5T+*Doh!{A$;ebP?pPV_ z;NP@4}L3gV@D zNscRs#hYnur|pm&C>V-!L)a@T)d&1sp~{87a_~L4!S|M!lOPN9`WzqQ-lfj7wZ(N( z%p9(w0XG{lHKT#zIsa3a&+u&K;f-Q>2wAw(=lQp|M}7SjG5)3&_u5mDx$FXU78l#6 zfC}G|x?1ni7WpAEo;~aA7yd=~)D0=qUqjb;`UGmO+VJ>di&xXy)@Q$om*YT$XCQ#c ze0f0)pJ1*NDx4FnDrpsWd_6GoHn@4raXQfJ?f7QR%rA#=e6=?I22JOj?nzlq4dfio zCF%wC;G(In?Wq;37>wP)OU65Az!k1m8s1Fz?I?ungU@1=W@5gonn}>3E*#pw z|DfG|%gnQxJ253OmgquCD)7sUEVcQ1sCYWD6^uvr-@CPKVB-^4IGq1M`?Wb27naP> zf?c<{kyhEr)_gtE+|EHs7@Hh8K6q~gH6J8e3t1KJhTzcbFD*BwZm6OmT$GeU6B1}Sk|hUfA@53eBPF2(n4WckVbP~NQ}LfLmnHAQ zc=YZnZBdD3CzmIs6ErkzY~Dm=O$Wl^j6X3qU4}S zXr*k7yA0SdQQow!>Xls?~0`NE3+6Q2{ zpoOhQ^ETK9F|2Wiqt8(EhZ&Y6lx3G2uzVz=4+XtqUA#35C6pwCk$(D zrL|dM3#hTE9`e{)tQ?dfI#{|fb1xV=qSS+1Was>$b8S~CKg8|iZvZFCWTE}zy9Sn1 zQ?Epz{BcSFm?@z=LzDjLn0i&BhsZmw9Q!2c+V?wwM3I)mFm@*_@iaA1lt40M(E2c5p$axo6>dRP)Xa zG7#+zreh$YZI-0xYlu#KzPs&F?7(2tF`pdCq0)H8GkR+DWWJLFAXA+mi^h)urkZpiT9 zGvC^vUJ!O>z%Ox@zr`FMA^2(Y7^c?872&hHjJ{wffSA#buYgw9akAD#)yg%QgW$zM zHu5KTZ1al8mWI@Bm~?9wYx(V9TXc5*)HXHnXp*x@h}nM)m|2ur>D(b=YINKprO&r0 znqJ14ryxa@5uWkML5Wp^`q&DpZ+;iVOm~<`#RZI6Z6vh^4Kn1>nM3AjQ)H5lmE84q zMfuV+F+s0E3R$`5;Gq@Y4N9aWSk^Zkfmg+s6vCBB`nn|$JFZi+H;tlw5j2D#DDo)l zl}Jz6VPieba3l(XNM*&d4#fG+_$C$gkXDWN5&wZjFH4Za*iU#54^;m2dS~kQ)2Co< zsBaXDx&?%ib30qH?KG%nB>C@I1LP+C=c-O@sUg5*d~Y22ptkrJO#pM@V4pO&5I-3N zkcWSiBDw)IBE^}ER^Nlo1a@H66cvOMz(S2?JU5&;FNI5ek#jL71`Xo(0K|vhB!v>8 z{P2neta{p1Z$257by(>kJoR4Q-5sq^brLy5a<~dmE59Srs|^7}sjHHr(;wz|EXPxUmgdR=yXI?NNgO6x5bB?39CbV}ZeQl7)Gt9i@shJ`?1BMDZiVQIB z$xPRIX(czUsL%K$OWDaJ<7Pfkgt9wJ_h3JtmO(6N* zQoXpa!Na&bYMK=M7DrU08Hy7dbk4*=dOn|~tDsdE_sBCJfWe`F;durN@G5d`w~6L( zxCk!7A+%7-YfBNoqo z%O)*WkB%*_8yu?!d;1RzTEGYm`Gmx4{}L#f8axm2@)#G(FUJXzrUKO+{9VF+{eIbw z?KqLV;*!#;+h4H#7qds+OE`(_u4>2KBK;S575%g+?{acoSjm!Bd{Q}#X&~!fD6o#z-tcbcCzHP$k1kvOxBA(Z#ylVCgr@^=8BA&8kSfb z^hZnd!OxeS)8oy2kpjQchR&zUYS9re8|OhV&&6<5$`Rd;0+R z&o53fs@KMx5<2eC!)}y;YK>fUb-zuyvGO!DeUNx_`GJR`YeK%_sVJYyQ)jxexzaEY zJ5kHK$YjHu;EYd&%4o9T(Y7b!Vj=4%lPz z+zHyXW^#xwbY4%Je4eohdkzTJ!|Gx3T)6|&lv+?f>x3;7>2hB$re86*E@D#R3m$l? zRgPs{Q@(GWrZcwb68>^Y#&q(aRGW{*LTjY_IK-v$vDdw%SbR3lp3D|~OqAf>5Yx>_q&*+Ie zt&O!NcovLq{+Jug_XeG3Y-QDDN6IO=j;Y9G5W+Rf9C_ActJj=PD>lc1pf9l(yq1&< zSw69Fhkd`-@!OQiH|@lBh%$N6U~tqH(pWdT7R-;Bu9WizDhVj}kFeioq+%)K=^ zVg&OYH24JV%*^Ju>5&JBf2ddPcGu#Dvk`o{JhM5i(|Exe-vP#b8M+Ev!k5BuFPm(f zFeG{tlu;T)E0(BaqWC5~&tbp#tk?0SKI+!Jsl76sqf@ism#v-N(P>(G$O z-3YOaelcxGt;;tY@1c61P+W$0bJ(mAGqCcO-to3d*?<3Q`YGx%~civ=0?5 zFGP^f4L$nYX+z%?$1XIlE{;zMREa6enb&#_?fqVAd?u=)@>E`e=w|N>6si~Wh*^*j zgOH5f@;EL51V9KqiK@72)N;E!K7PNbfhBr{&d#s?o+SIq=^F2@>+2^rNRCL2#L(RI zF8eJyiHIbeNoWMkavLR;%nOn=+-x(>)jaE$=H*e#)}Bp5@ti|?!Ykue^JbIsh7H7^ zZMb8Y2R4uP3dPC0#>GY$8);>-Pz8SWU1@?-sv#|ev4y!H)8kpAe5@;SrRjCgSpui! zN5x}bh6whA$e0iottI;9R1Ao~BWacpE3XdnHzlpCxEHOdSNMGL`f$yLH|-+WjoJ?L zKF?|(Al2b|CEj^EcD}HYF;xAJ1Na^I&8G8Ho0_~c!~08Xvm-w==sX>iU*0m+y5#rN zjz2z(nx!M`y~gGgahrGGEzgsfTa{HpgtCNUOHnaa@$v*=NHBL~@o#Sxq7}-HvXupK z<)?>Hr)mrab$VddTS%oWvKga37fbIoC{lL}&7Q~em%5THL&6*e6!tyZbkSscXX^SI6#_A`l~5m2 z55Wr}x|=sF;y;kB-etm7#N72m`!ffgs!lil$gEpCCiI?<9=RGZm|1JOUW=(JD>mhs zF@XH8E{5lr795^&TXPt4Cpk(O50=7WjY@ZOH$>XQKjx z^W3!%gw>#^k7#(uGO5SYvnZ3^ED6fH*CZ|DX9Gb`gW?17pVofL-# zHEx0r!?3K~ z5?4S?nb+qpQBdDpaJ^se10Uv(i+2ea(aY6x=TPy=cT7WR<>zzG$W+R>M6TdH3}Y%J{_H!qt% zuq^t?7|!=1t5;}=1TJ+&6}qDuAer6Vn7Yk&39e9efZ6(h@@nkIMFm8wvnTT@1|l2{ zi{G`;v66B9Lb{@7mhKoyG{pZoV$Svcu4M}nt|pUc#=?FzS|ZOLRga)I+9Fl?7ZMiK zAD>KSkWQt~6hAhr7Zq;UII7!Tep@j3+P~##q}-${_RH3gS1DL+*(p#^*T(b@o@T*= z&GIzt@C|E$A(Kk7PH#b5yn+{C>`2A6U;*A9xeB3Fi>GBvn;mLL$<8kai znX8ESrtYQw-4`tRq>hYD6pHMfaP zLPM{|o!4OH+NDGl6Z^%Vp(b#o?=ff`^ zO0&Oos5CEYo?`mSeZQ=eS!u+cMaZ4rr3@E2*aC@AeY;4iM}8LcQj=-SpiHFTSQB|k z64A+SzYyf%L@dUqe%_{fiplenTVqx~yEoClK39ClBB%wM&d;i(+Ze)SCxi6k&bT59(pJVEZ;prC& zCAejuHd)cjL8=8quNTI&-Xpzc`vra5oS*XsnqR;i*-0!SXfvtoK^1TBd+7X7L@CoL zR0UBPO4=p;X7zeny+{`__OOg){jBN5;(Sg(rNxF9vs+Owo7h6Ikotuft202cHd@_fqJ#Q6=O*1{1s|fzMzjCvWzu=-MLJ z^9RZ$4n3@6G<;%a^E()f=*uq!8evYux#Io1yGl8)a7vYPgoMNF#3dbw^nzV*+6*76_Dy5OWd_H zBI}`bYmJumsLIRT4LMxBB(i8A54c0SY0XmPR9}*rbT+>_7C^`6fwG7x79W*sWsJ@8M0M$A>;w}gwM|^ z;@yDn+WL%Vuu7S1`9+Dv?sA}_@_RIJdlQuv5_*fCyd`jdoe#O z1SlDqf{3fR2IUg?V?}m*Q)=!5M;V{?u}n>rZd#4>$3$K{QwcUvJCPe}GgOXuVGQBq zaKAp9emC8bM&H^VNnww=#$TtWI{ftZ8%0azh(Ekv3&Epe$Fo1^tT3t@Gn?LJxk{{c zWC96(%X<`PZWZ(edqo)b?dJ?CzN&5{!OrO%{uL_!o~ZOeR#&7ad#9Jb#!l6JrgO~G z=0`H&RFq-T)qVcm-)UVH6VC6+!NFGoRIU5IgNaf(-7;U`LeMu535Zh>ugfo|nB^p_ zmAKHF+L;nwisHrpzVT{}eki`_#bJtS z&{0tQ;)Sk!nrm0x@KtsujfI7W@9Lw00mz1M9sTrBRk7)cDl2bt(`@S=)P@Zg7Wti? zs3b4raw)}Zk)MGJyOA_Imal5(R@>vS1EO8;DS#ybgsfg^fh83q@%r+_$d-o8@ZjIUaVd7c(ebWH8|)Fwk09aUO&crireXeyM6ao;z;Io5JgR;{wS*mjJ38l@3*i(gOQS7~M>lud*%W zL7J+|jPxsm2+#qgey*Ljm|BAGJRKK9q389wN$sZg6bgs!36Y5w^xaTCxw@XdQx2#o zkT}R%3?`}-dIB13SJ2g3ul6`4ZiU^Aw8+J}I&9x?ec-OiqOAp%PwX4odccV{d%NYrq7 zG)~Trr=$ZjwLbbY7`nf6w+H9DqCA_t9e*E`f9x24zre0-+p3f5s%Rn<=QfaL zcCvqymLcA(M+EKgw>5&0qu3vUzP;ZC1_{Uo~F zu){_A5T4pc0X3vtDA~0I0RwM)&xL<9j72>>u67KnuiFTAk_diV-t4<3@^nT3G_yQC z^!@GZ>)ngnly-?E>0>iWqbD;EXs2Cin}naulCO=2FWmurH;#5TD}FEYIc+Gr+m2@4 zsxB2Hi>7F!DY^VEYqJ_wBpeOA^`qE7v2&iU=N6Vr0PnpFA$e|wfB);4Gn?U zyemJ=h{J=*NX4c3R0PrEPzMY7nr@?<7rXhkJ?cH{$r1DNIry*|P_APk+~`HwiE;yQ z+UY2HsM9IWD`&5C0Fn=lv^UDx%i`h)bKh(JqtI z^S(9O=TDE@{|Kp0KM*{G92z(+)wBG=CNx`V*ikPnoE}L#8{pU|lc854yX%lIp|*SC zuS6JzSbPblyadPbPaDgG+6zwX%j0x7kcQXB*Nb1iSE+K4g8DMy3cqGPu3S7>K~5W$ zwh#)tOq}C;e(}d`y#M^G)^f8_gJ_~IK@~cSou#1yr)(nneyOAiRH&r7R%2qyc+5$*kug7&Hg#2slMJ&wI0&=(>t_pEcrYYz1U6} zbe;AZRNdLv0Y)P?5U>x;*Q&^2!%Ftu+%tgvQ-5&RDADALV7{3rF)_>Z+`zlLuj?mK zp7?Z75gkISU1iasf;nbaA{$WsL0Ms?zbQqi2^_J zjWGr1aTy8R9$7r)`K>a62OALx3I)(X*aLDOf4^(Zbui6}t1J%eptw2`zgejVD2U zkFu3mskMtOXd}90v+P4C2-0^pRV4ef)W>5!L)m;^I6YNpg9fSqXSBLWD?Ne3b+-yi z^am^uKwZ#$X13VebT(V|{GD?!0=D)3%J%Dr8bEuaouw7;#BIyB8o-cp{7Gt9pyI!u zjh+bZzeZq85$r%f7MPb=v0o4F@%SKbtvuLA>!9hx58poAMSxh3_WN)CYU{+%w zDKbIt0$?~VRvl#Eus9Nmsql*0FlFTf#2dyonf7Am$N^>?+=S66r<0X{@sLnJqe>$E zB`j==g!vro8@GI5S}q^w&rMLq$k1EcKK( zJ~{A4LD&7fZVo41v9s^bwOC;+GZKnGr!pD}5N})#^r~sNa3PA-Avd_+>%(|N8%nG{qC( z-IA=R1pnsm9D8o5&(;Cn0OWZ@?=5}CxXM9q``zJq?v_NYmpUAFvOn=!RjZdjj4dgX zLFpFU*ZkXIGcnsc>R8J~;~#6-SojcJWDL?405gHn})C1LrtX=Yq|kjNa`P}7<%!uJ zU0&G%wQR~2xlBqbTBO7Oks6>aK*ro#QeT9RAI_1=HHZm(GH6p&qZ_G7;aDDg^2()1 z_&_1HF1)LknzkMYqSnFi>4J8=|MvQp!+!%#pT(!H#zRDxh0)=&(BiHk-WH_5%J>o3 z-lsdOFIm5sr+mFZXN(B6Gz_U4!s6|09j4D*Kk$mpRxp5|AHwUobh0=TwcYs8}VpJV=cs9p=Xp;^iN_mdm_WYBXB<;qX?;J+lgpeH=qsF%h zo@XI{cK5U)X>YV|$-zRGU3M3xUhF?0n)%xu)FF+FXthC|0zf655gpq1R8|;}4nL|2 zXkBh5^I8(*&i!TA_jl|ze1M~1q03RN{QEo`s!9SYSiM?t!OwA4cP!!4FTviRia(E) zc>2TcsURw1m|c(xWecmUn+OI(@jp@9GxJc>FedB(7;DmD8CcFzy9xcT|aCNW*+!|*y#=M?B`BbQ92KQ zl(XD)1i--rW=H7xgr6%^(~xbQ+fkz%Sz3Ro`{2Y~EOo>{)zcR8bEy^$wCqqg)GQ(Z z`t)Kl)|X))To^b_iTFq1y(E3>7o@R4tMKF8n{Cg4-zn8-%GS2KqAI36!0YF2iPFqq zs?IC)-&NG#VV)}^7+;7qyPI|)a{xv82J(@U%6hEMre~*9^%Voq*@=!&kSmKmC8@Ov zL{#xazffFIwoxcrXxi8|gN~Lp4Ke=SZ4V>;XF+CvE2>b(KC@fXy5$)=kxN43PU9v% z44~K7$ejRimCzDj&{on;JGy8I&sK$^*T!j`$ah(V0$=iaXo~Y~N~b$pAsI6E|1tNTVNGsbx3C2%7K(_Xf`El0 z-9je_DhNmuDbf)Y6aoaK6Od{FgVGfwbdZje(2*L7RB0wi3y3t45=v;_x)a#@ectz+ z@4C+Ub*}wy?`*Pi-*c@s=a^%RX(&r7^0rqAM6B!6u>+d5oDi@XP%(L46FA+5K>=aj zj{ACx=j;Vs>h=#u|7)P=Z`yGC70{2Z(ixM`XIZmGZwP0$3QOAkm9+G9KDY8`=P9x7 zUfGZ|&focMhi-X}CmgR+kjX1)V!QRDs9U6;%U-&RBWi2gY*?dpxC}m2QdKtU=$%=y zJ@`ypl3H_zM>SrhJmfHTOk$c;h}6HvA!iG8^e)--!qDWcX(XNdGF->n)n2NSj`)y+GHVP%xth& zLM8O}Gn{`C88TXhWig(XRqh`CgHP0Gg*di{yshB5;NRNiB`%B|Y3K8fnVHaIpjRjz z@HS)zzsI(6XJ`6od*=#HtWXDOb4b^6$e856FGsC^ZSPfwMUao2V}!36m+B8Eu)zug z&St8iMNl2V<*EK$C9C<~avky9{!%i*wfw;6L2i!7%v)5iHa{&WM3)zNqC3ViF*HEm!rXfdyt~!{b9Vjal|^eB zRaPg(&-mMZe~)jNY(Dsmtp+&6!<|Bw(caMuN;H_06P0eV{LA{rifhfQUH571v+~Q{ zu7s@4Nv*syJ%W27wtq`cY3t)k^Tu`;5`C`cI_8l6f{WHVjGz92=z!SvE{-SA= zH$>*mYox1}I3K{zzmPX9$;aJd#j1S%RPKE*uI>D0$tbS+I<3|X{q7Ua^#}}-!`PfH z4!6#rYVW8{XYsM_(l|2D7o}^nQnLWPgU2uyE}^{!#b+1ZVn;Ze-AVz2xh?=4@Tu zR!2_ZWmbO+Gx!$fM7X^Yn_w$}A-zm&Ghe#wynN4f`6DS=Xx>G#u4J>UIAU*n-}4M| z?Ytl&#CRv`Ij^{FN&cBzta=RH`jQRs9f@IeFBWL&p|IaVE5Q9YIqtam z8uY(gmNg(;<}R!n8sf(pr!qq}%bC$BiXu%JlDa{H&J6}pM%Vyj92q)M<}`>I6^qU z)g*Z7nn8QSRi>|IgL&pDfb+!2|6Q|s?jf5?Kg^-psi;j`ddJ+U>(J!4(%Tj$`H?r% zQX*$c-sSPleCJkk7-?XAa!H><8a#XExo(JrWsE%T@Uv_M|737A^TLTI0(tJCq5wJ> z3=Pw9q$Fpd&o7fTS5Cm=Lv;M=7wA|r=;NdZOW-oEQ~7*B2mRaEuj#}hs zEm#;QG+{QfX7D^*^B5R4smUU}V}r;-U^w!5Z!BRpm?aO=@^g*Zfs8`8_W1?)AEVL& z(wBwl52sAfj%S;c-;7w^w>pve{p)m+QS~4X99wBQDVHU*{kzW>j`yd14JtabB9|Qv znvGPnocCn)2Um`r^`xD2T;XVNjmPlw)uF|RfY$EPQ|$ZjiUdk1S6QY-+FdI+3+k%+2EfpF%~B=2N~29bMxZdY@DB zy7ZR{SdSRb;kZKrv@!|l*xRRUct=P7E{!Qv$Yrw7Y0OOC`ExaRI*gIFAUG2*%eVwEnz5(tB<5 z&j<)yoYj9tt4CWI_c_2Ss~vaGH$j5?S^zthf`;regKBlGD6O}1B;(A>4R0yhOqelh z3U&M|n`i;9Q%G-9x^wvH6XpX7 zwG!hCc`m~mGzpv=hi79t7e5}qCA&WBEkuCZnnfLy)m@j}tlY6r%n=WFdohoNT9b1QSPDH-_$9+!Mbb()_Adlle zP%1=KO%$OoJvKv|Lwm*|#Q+W4?}LL9zQ*FGnM!>+X~q3YhR$5Gl@r_E(zO4c?qs*% z)H(?T-zt5X{GM@4;|p7=8NP(jDG z<_^11^>_vI+?h2QS>4nHOZ;Um6Yq@`jaUh41)w>tyk7E&7lUC-(>MTktef1{ok}u| z)y}Qjx;6YIF=VCLN<+oirn-pAfynrdFuLXv8DElpZ1044M7H}%tt(OH3^znE+J@3v z=SAE5YYOY)7Cu<=|K#?6{Zdt;G?W-`Ojk{-gw3YOz2rG_*k9u$+mX4>;>){LXsG%n zXcq+{KE9efw>42S`9g46EM)f(y!h!bC1o!hU(6`Ug)PogW6JhCo}LO)LkKnjp?L1l9ie+3j|q@m>s_I_sHIq0AOrd~{6ja_;-dW(+>fhwzOF=8F+tW5)rVme5>kN#Fa zd3R;_Zz(#;@+P{Hcq($7)ponrg@()m%|}DR=G#wpRA({`y~@2`VGN8m0q_s86+xH# zY6ef7Y^LYqxv`4%=cOhh^f}dpiba;UGhMmt8O25m@@``N&F)^o$0v6zydB!(cgtbx z_vBO=*u60GCb3C5kjz*HT61&wy`?2*f4Ie$t@-|l5MPBAf2IlwGJUV$p_La|Taqp( zf8eoT(heW1<@2E==#VvK zoY>afI4z10c3SBtNA>&-h!msxuHVj=bh&1^wgJmMYZPM?EE8h%AXW`>!W;SPb9C7gtm(HmEaiyAW=v& zJrR9<_rV-Egv}Z(NAl00NR5|E5p}m@18Ed?jDh}ar<@u>TR?Z&ZeBSH61ijxwv*S+wl;HNW$7Av97SB4Ws^bk;c}CGpA2Wb zTskB_?U(5fGUdnGt4VIVd%r-+Xf^g-osjAdD2}J^w?S5K!uxzwg#2#GH_#7m)eEqYC z(90Sh!T?|z*I4ck6mRMG+WO;owhj%0-)2;z$WK^u=Yv*~uYC>?woUmM;wS2CN>^QW zAUTB*d+SwcHB}n|CH@&hLyLC)>13t8PqBJDzaby$V^lWY?n%oG`N(K9V90? z^^EMVFkaSPIUA*#ba;VG6)W15Po8Ctp~?lo&VKt|GR^>IStnpoVhdq@$p-)>#tDI}+iZt$dxoS6)B&Cq2e@J!h zw#Pjek*aE~#$!%54E4V)Xqh&i#1sc_@7t)ZWTWY4u2;dAdA{{@U2T+?_FYhx20ovL zTDu#psEK@`wQa80XnAsH5qwCTDBlj!HJ|7+ssGlUH+Ty-sFo ze9~2+&!65xSp^jrA3)x$N#8b?KCB2oyEtHT*7Q|}_#>ymbi)iG#X`E@8Y-s#+D4~n zS;~EfXj{v0vbT^Vn;=giv4BTh(=SrL=7i|O@jW~)gU=u3Niib~2g?Rm3$IdCVaf(U zlq}>~$#pZ&VjcY458W7<1rr8kS`A*SkJmbwf2sMXzv|ovnk}^++9K0VNZ;IaB}3Qu zH+?@{+I0JJX>$rJHu50=nbdjlSxH6yehK?%!jY&}rjX1nIfyHt|8^w~hwX~wM@7zUeB#$a z=fGMNvjdNoRs|cXu;V+$`)%00#EdBYaeiGoREEp)*?Em!^uF&r$W1_Y5;D&6@Lw`! zw%6Tsaw9zxN@T1xrvWAP?}i%lq;cl-6>yc76=xEzlY`~;25j^Yy!Nn?t%p8#P73Y0 zGJEeAMmr4V^pQ%T2gKMhg}$$%mNDX<5+#TDKKgzDhvC0lAu#=5naS15kKTCwPMRWe zfp`a{{8nR^&Z%HS4jkH7YXc=_F?jEbXXU}s@ff={<099tLs-?*aW{!oLCBB{7aQ!x ze~(?*sxqV zZhb9qG0PA-!{pgAGuH+chA>r$Ol|+^`aNgA@ht4jR97rSft&=f9ted_`B-W7HiA<(k^yZ zgA7PY|0|YdvLW3a>s+y`kKEk}eV(i?S7;Ce1xsI>3|XHLJ8CSxeDcldSbhWc!0UCP z*%)7~+?lHg4y^QLDk}xkVc_>tG?mo*glP6f87(DvU7VUz_xi;Fb+#>guEZcRE;6M7g1&*uyAt)_?w^pu`*F+04^E72(H`e*<)Yy;cKQaUPdXP$Qv^@I zB@8f{%uG)J1!A7;A!9-&|Jnl;M8%QQa-t{8+KUjfYZG;Ty)nYo?ut&}E{n{4KI75O zEQ1w3-Gfy;CvFV?>C~=NskLL$M2L2Lxy#GVzD-W+#DXpEdGPs39LNDNRtR|cyvV_F{jIoi3S70-9rHq9or`{y#fGa z$*s*#lWjkQ=au9Q<$j62qMS$QAUKyb{T49V**%gB$XPfrq&nbBJGS;M86sVKIw2fH z0!i7U++5s)TEOPlV)2tNY23{u;yHMP5h6A#OL?>^An*8HzhTzn@wkEq?3Sn*t=tPW zT`nr<29;06A^~0ltKFv^QCuE>i;t(<%$anB-FChC7l9P)iB8rJC~_J8(5rXuL$%7^ zxR_x+pQgO*8=dma4u97GojgiZQv7b#yv2Qw@vXO4tCEg&ojCK$y)YmIjE3nJ^C}ae zmYk%uqzkSlmB*YpDB1o&BWmXIg%qzWrz20lv-v|}E^rQ0$F6S#)2T#O4==~{$k9|< zl?ln{6u&MmA^NmAw!ig!d&ucO5}v`2M+pxDR85iGe=I`fmfhMOG9InZNX@}ZQFCMQ zy1WaOO0D&h%rpM#RJ6iT??Ojr%B~UI5|1PJ(jYadSfn$aNP~I*k7~naX4FKr`U$7W_=6xhN0M@D?t%@ zEOQXc}zW{&Y@2j=f6c}-7LNwVABW=0(tS=Zj=&fI>>B3@vxlE7_F?6J*Ue2uSA@LYL7?7> zCB$nC92`RT%4S`8*sGPbIm)_zE^NaXsbGvTfqK&xSn7>^8>2)dJ_GgF4{3bAHq+{+ zz;kb8S4Y;hvC5hZ8Qm*gJg-)67*>ya$vz)1eCGXBL)SDvWbGUdIO!v~fbEu1@CUI% zF8*~ZITHqS;9qTsl=o($E)i7!O`eC?&+~mmf9`HEE zrRp~(4J(RAS&NNz+6z=$OYb#aFAFTvCpA)L832?V z>JA%%Q6`ZZgU7>PnDN14_L{TFVUVa6{KNgMqx~QYny#@WI$-GJE>kcxncRzm{`0Su zIUq5}-Ewmi+Z-${#t-hbizywEtHIwxM;w#L^ntd?fB68`P%jVs6M4&!72^Vo;3`Al zS@e~T2J11DH20#iHD%y*Y-Bx9z34`P@$NEbrNEN|Dw+NW%RP`Q+nB5b@)%w4>rFYu zo?Er~wQ<^X3q8}SxA%ua3-6nF|Cw9V&;B9sr0pdlXAcm#sQi&l@Bao_0t42QB&zE~k57);Z5=l45&{B|q-5!r!EO8aznJ)JuXS4$yrkkUG&ll{Z5PJoyD@$dh z*F|*2q81QWP%nGm{I!hDyVS@;_;5w2Q7ENZg5Y-jRdKrEH_4UX{q&zoY_lGD)aMKS z(w8t5E*n|XC+u3)e!NHqz0Ni^B-=#+aH`zhbv1Btc6?zZib1d6#;qFo&QJYWihOkM zBd4D)f_o~npExW%E_*sx&> zNQ8?vukwBYico|l;q7GaixGjd+gEB9Zhx|vUAj+8kTIonxC+leh+y`*$e%V$-!a`< zCjIqry~!nSrf>K`bq@@t9#` z83$|YiQubodPxDrPfv1fk6uXAt9}mDI{f?=`TapD=XU*Py6^4L2Hy5p>2Z7Zix0WK z>xjYusrKuLtExq1Hr=oSqTe(gb4;&IM6`oy3j)Wcy=~W9#u$w>?Z3P{MX-19ekyoP z!eH9VOhDWIOUPtE@ziIl&)CimT8UNB5*;IS;S3xn#`bxaUH+AT?&7 zV)9qrM5d0@)~8AFKoo(qB!1)(gK*>M_;$B}m3?8))>r-1kw+XIN8sPJyY7WBQ+DQ4 z@+_;FNR4I}N z+n#a5_sHS0+8`i%6AWovsqT?&94nIB`Z1L~Itt7hGsQkFnLDlh{7y}rcKF#luXoDS zK5J~L1`y&NSKDQ0dKpc374M|qbX~TlX|>RS?tZ^}t3d0MadSRaH%{=Agd%P~T4?h3 zL=;>0N<`ko2jEX>`dNj8UC@-nOKgNem1E8$za0K!@^OmBj}FMQqe|AI(<_PO-+$XU zvCZm2Bkf#^m)F3T+7k;TdjLU9SSz)9pta^~)?^wtVGm>aY|w{W_L}*w$5dD6%NSNV zAgf7Dzks5yPY)8*QHdzg_~6n--oKtRc45D~aXOIpwD~zSp@*m9RcEqt{|sa9C7Xn6 zQ_M1q^n=JHX+O@KW7wV(nu-+UPKnx_@W$feSI*8Rtb946OhX2i5jixza{R?l*SQYR zyfX@_T2zuJ2E)rJQ0#6CICvix?H&Yrqoh5-!^LsWi{W`k86PBAQ7`O*Oi3 zyZi-gjac51UA1NSa?(hZ))aCMs6a%nMi3<%J~Yv)amm^$BEN3xK|LL*?tltF^(=Ny z&mTGH1^_^=-u@ORwVB&BIa;j=kicFbz zGArWTukwBERaxgyR$}6@A2jo7thK8*J^{ne3bze2qwZfmlfgWsz=$ZPtS8T;`XAbK zp39Q5U^$5lvTnI9&^Q3;;J!ZIE=ea;#GCb$P-=`a?Uya$2f@jyVwFv4WL#op!*#4AwFuS}d1gdin@JmqgS~vebjg+hf4@Un-zw%w;h{rGIKL&b z*}{)OSO@2c_R6NQ@gtFGL46AYT(djNF3zI()c; zl_`X0E91!rP5Z6Tn)ZAGg=$xEzP?&>@9|P0k|rAR^nvSVQouKO2#Bgn$n9mT&!FC` zC2JISQHG3voJaON zLVBVNLHXO2@ulInc3R3wtfr}Qk8aeOJH4WVeb31+=U*zV130Gpq%T+T)0r@vSa^X% zV~)jZxYb#eQ;io7B{e=iNwhm_iZImFWon#mP!5uMi%&fbm#;}*Y3dJ_d9rx9ja-}7 zd+{NsF#{H-EsyTzEv@8P8X&#O5*TxQbAY*q>O|J^ezdvMyhU5sPUdZ@eObSP@Mow| zmue$Z^WG{u9G;dpIT<$I#5|XjXGeJl?mbEHFEE-gru08MA(pZFDOYn~P6p2q!4({r z>Mx5`Dbi_S*@DNvCVS0v3E1@);`pb#`-Wb@aR)L4isx{qVG@d{nC|2bF&|N7GA=*M zgVrEM*h%h#KWkNfuop=eLOyu5HJPFz)z8h@sO~f`5J8B>-Ogm85d>HQn5LiGdz&e& zEWG!Xh^VdhK;bi98Y*Ihu32cTuZ)jL6#ie(@57z>s}DF5bXFEKE$>4lZ3w-~*OgxR z{QX{##rPI)!rTm4dE-PUuXm+T2*ZkU^O$ToPP(`z9fpFJuk>F;y(ejXT>il=!p+C3 zGa0-zNA{$7-PeKH{NS)I_BO1zQ>@ME$YI4ICBxr+D^4q_Q`*%H=UOE!Zcs8|10hN$ zSTOpzWy5LyaZ=XW13t;;Tvq4pGavytHJYyfIUDk(825L+-?$u9za|8=a(8d2_Z2AE z;(O=Yj!fJTahZ1!kCdCl=onM>`cTMmT-7hjy}VXjDR{j>8wdWZ6lDH0Jj z_@ZiVEv4N@UJRhvPcvh&pNC1;VM)2?y47$B_-jdmpgnL7o?wXY{~=5XcS7*~-BhHM za!)$F>M*Qx$a#KRDaV3?XgDLPtS%pSBR5Fd;W=CnTgAspEQ2vmX@yjU5wkYO#FC-5 zP~mqPKNhu-laZJqIT8^$UDz|m>L#aRwc*|)`qFlNseG}2MyPsOpY9VwxMdrb^*V32?6?4 zdy&G8;P0VzA{f-T>F*3SDjWWn5GU``p8uavVJ}gen24hHi;*cE49uGZM!T7ieb1My z#MVqi*@07=ZglzjERBL^?8-KPQD%aS&(Mj+7lbz*DRq>iQ`r@4Tic#no;gZ*v7~Xa z)={09i0Hu(=aJor_mnBkUil+eKx&z{0p`E0XvauXj@`(AxKX0Ub^)vQwA(V~9Txc= z5fW<0pqgHu>0jYrUm^ThQm|01z*DAw*v$(RP~tQjO>l8Yn)wn})xNF)qF;XsZ;9vr zZ^4w^EP^sT<*X)lK|_7@1M9CoCsD2rOXYlnCp{UN4R3lSSg-q>3mZ(g!=LOq1oNI- znVS!{yy=#RJi$E*#ThM&RrSogjGak*vls90?>uS_)qVH`<*XOehYKSO3J;nexKxOg zJ@Cps{RBQ~N}q0aMdrDg$P>Ko^9EQMWkaX#@Ul+!|! zZ*XHyKx#{C7#Pvu+GAi29)$smTz1mCKTF+PyR1>uO#1N2?nuz{6oMFE>bZ&H7Uen@X>r9>DP-h=<`DTHOa+s_sr7uE$T0(bU4?IxKn?IR=0k-eKy;n0Rx*oOyr2RHl znBB7=c*^ojW8rS?G)O+eZCFo@*6`k))>izi@l8W2_`)?$$Qt1R0PDjB)e_{Xn9Z)l z(b>bSQF*^0dDlurXXv}*yTR~#sZ2lRRP?(v`go4$ziIP0?v)v2>kI?CVtC842(j{O?%!v$%#AAHu3IeI>jW&pen$c9WF0#i`m=7?cOT= ztS!?PkRt7eN^CCW)bHlqr8HPL(en?VzhR}YuVH+NVdZ)*{b!5wB%lRn@V!_Ymn-w- zX<}HFTP+E+zW(TRu=)1bF*T`t2I$=fW?o;GM}uV!c%6B4iOyr_!~*-8RDo)KjuV}x z!!1pnYR__pRgD6wvD)l+jJV0hl4Nd&EBls|`miPsY7{cS#?N2mUcE4w7#H5TLZnT= zO)E4xK$4%vCqL5-uQhsf+p5jI9iT>%qVp9w9NOz=4q$J81~;QeI5)ER^99W$yBYbD zxoN@2Gn-kHb_?#81hY+Tbe0C13gci9bDPN^b83hceM%NzXKYJFdBih{zgg^NP%OG<$)U**(9WFL3Z`qkE}yX$@i zQP9AGF4d@%fH>gmcmh3&XTVR)_E$&`c0PSld{tJh>&uX-vKB0WXaXke~Z7$LOX4lqb(O3=x zCRxa|mjM?fpmaf<*<-k6hEqzo#ka$rX=Q&hR$B@l@Z4l|S!)7TZO0Eo);C1ZdWp#W zcHYd>wtqNwfSL*czTqZkF?1LQ_ZGCCZR`c=m7Beh{4kL{yi=EtlD&k~kCXUwKWVW` zIpEA3&XS94r#B)fy!!F;Rh!eNd(~XfhE-RrK~|`@I_SApFUI*Q>5f5kiOE-yU83H- z-9#v4mriEk_nHWed#D)~Bg-cNf0eD~4EhK*dwLp^5o~zh3*u~H?9O^Mvf;Ha3IcxL zZp@QMj~*o)t`$F=iG|or8h35U>h2tM$zXz5IAC^Jwr5{n61?m^6}dENqtTqhM;Q5- zJ+B8r!eyJ?KRS}7?wgcXQyl}^s@e9hqo^3vec-Az5PuBqwf#+AqDiGf@KWK3W08Il zfSO5e^D$b(8d2_^ss1mcjNNS*QGwz^2IA>hm(|#-JT^9U8{P{f7Y()}*o8+=PoexQ zQf&W;wVzL@gaCtkk_WYa!^FrGOd6SjoCr19-6sndBbpP{sGUm2tUhEv#(-^G=qxUr8bZ#rMnARlOZjS<=_8gjwA|X~AhUeT$u)(o zF5QA|nIFqY@bj8*+Ax($af5G^hz~L0yM$al+GdbprkgEmcPG!s5^aa#wFWa*nl%pY3 z7e!8<7zok9mJM1aS|`KsWC!R>ZF)&w0K?haXST%rf#p&O%%~40UfDJohTXbx!%8WB zVbOYq8ej12RfL0h_`ZuczkaRh#5UT``qyubkW;XC6NS@bK+ftL9wfw?^E`;ulOGb) zQk)f!-_erQKcvKaTb8!-AqNqUpw?Ayn3GuFl$$Qg?FrcGJ~?}#FyK^H+{5d@ELcKF zH$9(WTK>iJEv5Z`F4kvj%d@8o^S4?ZsjXGx2)F~C-lFx1vX1D@!ivbP3a#z&d6#gQ zc||;&LzS!1n3KxF2F&C(tnU7GVjMO~4Avl>3?43KdHPB$K~Px0!guBIAoHCoL##YT z4EjirM|=D!P7wysJid zuzSQ(3?jap4+7L%JX+Jz!5gwae^56_QH2-@C9{0CQTt4oq43116OCMR;&-nx&Yq%O9$_byh)ASJcQQwo-07ETK4ZGj>LmC*RLi^52spVdf&Rfpe*YOgXFCpvx z+N9ArZY84f_xvvmjkGwF6JLGFP3_@HFE|NC9MlGSTN@;)Xy)pmL(Gegdz&>?L@=`@ z*e_?vC%=rG#BOX&4+xj7usdCx-I|4_**p)Liy`gKQ0#6Yx62C5*}}%W@7EsXUYiM< z5-oiTWoRW*x0~cyZ;eLNhSFMYul*>{pmJm~=*4i*WQGDy)3CuO@XVO=orZU<80UdV zpKaRSGrY{|!nL^zxo@nc>Fb@52g4Az)0^hB*DI>ImuU_`KvT9R2Z5{D{4pz6|3&aa z3}YHEt9!2WnMN}g-);&{CEWVcpJOJ9I<-E?+OJSo0&`%*7r(g{Wby3GKXFesW&*vW za~0Wlx9yPCh%p#9MiiluQ9?KTT=92>z%?2 za9?}HqN%pQ8J!sa88r&=7Wi8}t}t=we_#E=)<`x6Nnt_uvan95X3V7q%$jS2j|8a;kx0;s6`;gjzg#eU=Zc}sU@h{jonRd&do`#-lQ=;)83b(FHG zGje-gLlFf`etP*7=oMHGFJ4Y`7wIcRIzvqbyh>-o;j6wCVru*43m@ol0>}PrW4dTs z{F;pztUOZ{$-&fH^FoxTDg#&N2vI?!IQCHkZ#@xRcJ}#WjsTfonkY zVdWJWmE`qe1QmPor%r}*(P$D4l^PNDRmIuIZ_&?@4;D}4>UEWzc?A;Maqg<&P;PPf zR(5!=n4L@lSE*{N+|X z7d|acb}eGMJ|6J3D#*FPSuC$z?%+{{$8by?<@rK_DbC5&I}^)(y?D$9t-16Z9P{{S zR~pem9*=keE_=$;Kjrl3o7Ts1izjDQMhX^KV~nx;QGUL~c=MH~VK4_IcFH?P4w(5} zlZb=?(hRmF1` zX7`y|9@3U=GDCOSf(MQUOQld=BW~W(6`UKaOu&_UZ^e?5tciyG+XVwTCVC3Z`tKW8 zr?J5Gzw+b-3=tUV=xWG#=(_a@kitp7$Unhf@86$bv-=ZX8Rj~((@4-C*0pBESYs0w zaigr41iTl26ioIy4kCqnMR(W#5gKG(0@UGeEv~CSU!{}$k~-(;@%)gSKii3Ghp3PT zdoZjum#>2?M0MS>-YrRFE-$okJbALXVqSg~-#*^!Ns~i9rDH!Qw=?NUr(~DW|(isL~(i9CowZ@;T_tvThdhx)E{lW+*jPu1f+Dx;Iq*Q z^VYu^0Lkx&s?%=P9Lik>k-k9SBEi)UMZ1OE;E;p0+s~>ieB0ly40d0EHed@caPwkJ z77Qjh=0J!CqgetU=PR+9!&JZ!98atf=E#BA=ayz+bw|mO`M~Y=1N>#6xK(p%ORzBX z5O|s9d@fz6Y-GUf`T)@-uAlYB#JdbW&)^kXsrDPcZ^~E7MyUi#7ZyETmc#IFdCs{y zqKmSJX1}lg21~bBmm$;7N+-3WWV~Y96Y|oQk%QbA>vBeHkJF!B1loXXM-hlP_HZ|+ zEo5UI8N!OGZC?F#ew>pwKDfIONl%OOMS0kIrLhf*#4BjuV1)OvIMj=g(}4n@@3JaW zyEKZ;DW^ZC3kx8@-VG-W(GbC#h=9jkuDQT}fey?j08ZFuychPBrN+vs!Hf0XWS~ao zg#ND_l!Xo(P(|Iq6(1MCB~D2k1gNRDhfn~k3$R+EdK~iYSc^nt4vW_-`^C2oS=A;7 z(%jK9u7-3=#Z{(p*F!ow)6__B2+$0%#ki!^2G`eL^QMXuoA&ct9Z`v@IA1t3@L%21 zz!snuH4mxUf7|O=neL=O1nt4-thC_nV&vdQCeKgyqlKYPHd2#y(_pHP$$@ePYHVH0 zrMzR8f(Y}6C876ro#qOa(Ckh`f8Vibdo4Oq+F*Ub@R_a?xO}vd>Ydxs)U_P8qGeC( zUS7&tH_KzF<@fzmT=vV}FXE|2WZqjbx(XV8PC9SgO8$xSFAiYkd0n8%RjX-Wtdu4L z`^`cZJ@y|AhlsETmqq{R6BVlq*^dlcKGyr*Qp%>@xaL}5gDG9+mls9){WyimC3NwA{ zVKRZvyZmE}b3dJ8gC89JNP5IKj=)am(dJ!2O+b9_BYFi|1ivb?L*~d{qdxFD6TMk2 zXLFUcJsdV;mEeRooG9m4TPxX?{jTr~%Jh-Y%bEMU!;TNR4+te>iID5H7l**xIh>nV`LwPW##NFm*xAPu6ah$x zI=E{?QLsm`Z>1u?&VXdsuzqIPb!J(TP^gL)JFPbxwxCd{fFF2RK1dy_(S_DZTRUfk zfzgs^rPjklIdIdv!*nT*I;R_-w!H?GM^06(hN|#OP|i}a0|Qmc3ocB}yF68D?5!QA z(+e(?cEi!M6ie1!HQG}_C^rLSBfiNpAnO9ol1Ktvp)`UQeHxp+A4pi;-r}vE{-Yo> zXr=tk=Pj@QzU*Kk6qFp``q^;msNDBf9YiY+$&Yi-^i|nD-Xup>_)=eZ>&!1xBu0iF?>!u3 zV!4ahWDEYkU}D@BhyX6F2IrJE{#7%DVIeYyP&u$70y8LEh67hBOR8`?-MDE&K*_q^ zFnc&RbiMAW5RnO)RhL2R7U&TkyNLmQ;3h5h5o}lmEjcR36RdkU zNoXV4Fab-^9+vkZ_`sczt0C-tmZ4@ivf7bM7;N?G1H`d2cEQF* zCN87G=W@rc{vC3)HYO6jgj6|=ZV7L!<<~3#@ty(kiQCT-r~mx|{vifIMjjN);I*wg zznwNtq3)9 zY_`Kw0GfXay39#>OyG5pE&ldghsXv-wxB1iMjIUGwhab>&ZB>_%4BmR*r=>9n=8K- zZ9IYFiDj18MorZZP9gGa+By6%UkA=E;Iug#?N{2e#j3@_RRHg}Uz>NewV;%?8{|JD zw-{Id{(0NWT*P8yL-w{h@I6&TsG4Co?a{hE@cjKH>13n8sN}PUa$S6CL}|~mmt>D+ zGuW6E`LJ=}$TEZmkb2!}YM2H&8$?Yvk_@&!jHm62yDWc}fBc{3ndzsX&LH1ZP9Y0# zqL;bOPhijoNxa{cK|<9Zo(KW|9OKO88%SPqBC}gpT_i%+3g$^&36y*RZ=l&4!J`Dn zzO~sx$hZ8@fY&3&GYR}X51xWAJyH^R*yqHdT?n&dNb0y`FxeM#sX+{0GaA>;1ChDN zl2kJ7{ILws`y>N@8ad&Pk29>hbT{_P9c7q<*t?)g+1=TpJ!Dw{IA)CXornNy((tSr z$X-QzoX;~<<7R4Py0f_MW|+%HZSB)xNYIV6dKN;9EAuTJNPT+Sz@)b^`w}1FjYzKG z>=8CX+{+;_vo_=sMBj!CAa_yO&>l1eFAV~bWD2Gh10mh$XlF9h85lWjY#2@r?R{uk zvHkP-8+JkOnZ6Tdw>$fELI$XbX8bfkNLXvO@}PUT;nT$r4#wwg*qo&A`bv-q97(%J zwel7v(4Ah{r}3K^JXHnVd>LP4Z=INK*n*^W^mEq!!W~$mV55AK%F~;>qQE_^^)7T? zLeKQ)`Jub~@Kp@GZj|vAqqgFAd9Qk%z+hNSpeypU4{8Q|n$HVYdmPOAPeg(h_Uh&z zn|5k5AAG3Sh-g)ZG312+2fNAz*Mnda{?7yWC_(C0$#)i!>_VDq&yunyts4^$9Pfy! z3^NAbc;lP8`Z_*4Gy5Th>GPoUT-N=hVE&b1otFs2H*zhewD-nHl*`n*#xfnbQG9da zM{QjK=uKr=nn+}le$CqnbdWAsC6eEOSWx_tFkrki35la{_7(2ypfv}eG>qqC8R%G| z#&mlU0Fgd7s-FYGNP8Qa=kU)5_X2dcX6L^-V(;ldFwPnH z#e+G&deR)jnsI6-^uf^01+iS+5}2^$LlsawY}M}o*M4jUz@0SXbS`Zgm?DWT;%#&@ zu0J_`y4e5y;E*uo9LZGZRHR2~fl8c`AJt}HXU#$pBp_Gtdui*0#X#%fvpqZ;pd7tmw$6r7g zcFJ2+WW3#gde-5fttEGrlkhSyT!u(6i{RRSo>&T1wUNhVx|J&AQc&@8U<`3atB|C@ zwwO$uxa!Uv$k>mkvvbV)^#=XUu~b?%7REM=)T- z;}D_=<8kZzbiT4hz#xPfE2NqK)m?o;m#TkVFZa~`K9J{LE2i_gIQP{W5}^`eWEt2LWRNqk?|;t z>(4%ZR2*&WG*&()zw9Uxxqt&k0&H2V*4E|V^yn8i-!S6vtw9QZ>MGljQcxsCf9XW_ zXr)O|=RM4|Guv|%p$q~73DTZ1 zbAy_)8%e|o{(t_6!Da8F$s2B2eK!ANraX!t!~LoWjChnKk1%&L#t6})xo}~k^9bASe!JazsX>;YxPxj-F=mAVSXAhZwYLE!}mzOCc+5@BarQJ0!957Te=C!5^ zdwfD*lIrxla*adPe6nZ)nS5d6h6Ng?`DfvH;5H4=XbDLw|KT5q^s?3rK6}rsqhL&D)K1P!qm#r=IseX2Q6aG2Z1Nxu?JL2p z33VV>o42b8;j0Y`Lw+F$Z`-^2NdE}2;LCFu!DGOE|3p+-`9mSAhNu?q7bN_D4BtwX z-g2lbs>5IY1IlG^C9JGGP`T@V&;GL7H&IbU|n0`C1B`jr0I5Awfo) zqfqdW$w83wQEb#*fMFjPd&jy-UBUIO_Li%|7Yy2bkP)3w0HwS9G-45Q@@6+a6ly<~ z`J@sZp7ix~X+x_y!{mv_*)8yVgZ6bJ9_+^F2@0k)c6cp}T`q+8FPpJD_?qHe5OIYL z&_+=&6+sQ#qjV74FCER*BmUNl*m^N4-Lc z3ivdXrwJ;{Ha0vO1uhNHd=vZLdn3C4AA4{9mvsL94L2?0*yc=5nYq>ElI4RZm1w|zpc;rdtKN4xUa{3 z{Q(#F0|>m{a9-z}*IAzD9R28J0H+stc-OLN18D|qJ$jUlETrKX{}@UN}ryL znKX-R6RAfH!)Fl%EGAO>1lf&ne0SQ6XjeOozs2>`T2_!hb#;gL8q*V{HBsfC!^x@G zZHl3TL!^PUTbtMLIcO3d2F?)xrZ*x*d_H=bAjogsrvw+DO9 zggo@YoWwkofJA^DC~TLl=w6FpEOb3@U3DJ6JBVux!aKQl{?`M3qQQ-M6Pil^2tM7@ zQ~@)ROwZ{Aei+10nT4-^?iSwI!qQO9agd5E;iY*1i&@FA+X8nxsE927&cthPxA8TE zXoaOJEf0nxu0yIJk3gW6B?tO=sRHHz^uwEA(56?N9M}%+ynO{I1Z{__ey@EUI5aG? zz46uaFL3flrm477$O_0$TZN6s?hP4p>g;%rBsPLpK+(!D*Vqz*4n?yw?HJ1(+VJaN zojP6ZA#TGV|H5mlPrb&z<*zt$ni)B9o7d~wwRx`^Zp1QrsM}f)3UNCesmsx*3UENu zM9jO|Nm-nFS4;zcwMn81imGep(wiQU@9GCexvwnNTstxAjB|Q~1$UxX0 zX-vE899>+eNLu-uJBQpXc+}_5+3vv^!tFU;)gen4I(0j{U>%PoF!0aA@kw zrs@5V$$a%+pj0=B@;k{KGqcH+fyGKqb2mBLPaBuZ%q#BChTO!^&f`GtKWZ@ zvjWK33q*pI7r$`TD zj))Sd080Y-xfU=mKQ$Xg*dMsR(Jz^H=sK^U@+8viu9l15e;QAdsbH2ufdCjEJH5@M z>Q30le$?LHP2YJ2({;b+rNn=KhW+aj;658zjJ#~0IC02eehsv%v+aHP+nr-ui!A>v zE%96km9z`w@Ev9IpYn_A@Ee%`KA4+jfLk0B@vz)2U)pg#8S}|63x@r5u^&XP(i_el zESx&($0U!U{zWQ4S3u?s{5y+TRk41HU51o(CTVN4XV>xG^}+SCM;;r@lG};zi#2tQ ze-ZmLen==}M=u3QbM|Te0&)39s0u`&ktoxby79UY0CCw8)c;tZpYNx55(8~s{MZ0= zwul*J9QY;*L;EY|N2OOqZj5&U<2=e0ECwJ!0On?}^hYTFdb>8i)(P40Eo=X&7nGG1 znR=jQj~(>KFdq<$=LYmbA*BEaP8Tyj={TVB)AkZsw!=HL>7kesY#VU556&mSFp} z4QwsNSQ-}h=MFjoDbkXFS6LjwC~qYM9i7Q2yqomWZCcB|RE7)8x0?XWW}R}Q-7zNK zz(h8SrC2^aJY)Es`vBq$zd!upDXZZX>)f%PlZFFR`nx425t{IzN0#^X)!Lc>IL2zU z_KhvS2UxTKoHuZ)PD!qF-vKrGEsf4ULtd_!Hs4lvHT{`yQpEu6$2}#;`}Y6cqD4jr zB*9nq#M7a4{b|gI4$Hx-hFP{LM0#2roI!j0mXFV+v>9>1w~&FMO2jly)+vTysS z_PPEEi)tV zcaF2u*QJ?86JOr6PN6&t^*seIn1z1Xj6VW{t>moq5ifK0o4cj(d)@vLS0mNpS&)#pyEi*er?3?GQE@3Gb|3R%uFU?zj z*16KA4ZWRCwCntR{=!K}Jn^LJt~*)YHzwbyGbTd2+#l7cNP7Wu?cS(V?c6ti{G`YZ z_a)M_d)^1ACiPSNRtxPG$j{G4%vhN3M%q9&Y3_LX|0r|Wgde1QM_L~g z;In7pRg4*Mh6{^u?T;+^-`5KD1$e)#mKkubn(^b^innEjxbKDKw?ekT6hEc_KwF9U zcYOZrH1s)g*^oa(Tudx}6N6Wl7i3MHjl%=7<22y*1eBQ6<%3?+caPQo1#mJRJ&*?pY0?*JAir z=d0;F`U=Pdxi&3<+wQ-6KacSvfo7ku!}m2B+W|&Mrm{1mrh@ucYbyhIor!|VFPBf0 zUI=-Z^#wtBB6@hj(G|&k-2`^*)JQ;<=`#z^4pC2+>V_b;>Ib@=vmSo zDjG(oKKkd^2%I@du?qj^2VE7Ie9+{yzH6@<;;jElx3W~aPthM;=D%FXS@g5tE!>-X z4IxMTnE4e6yW>_9{L%tyGT;!a7Jj=bL09fj1;xA7M~ z=_9hAlrzC^EmDmy!6X_O;P!hH0BcO=Q8u)I;jM7=8hmN(n$^D_j+%=`&|aaLQIg5W zM7{DC#?lWZ-&Tw04lgisj8~8k{_VhCQEU$5UJ~l#A;SbFqYLaslGlc0Vu`AGsb?5)1jR^Ytb zw1MJMI?~i^QJu{Y%}nmx(3DRd!0=97z+9K9J1$4yK5T|{;m-c<)?hwG1v)KBg6etA z&yaA7)JHi!4wSO^9sbFZqo#8F$}b^RA$U?OnsE!%UmUCm$5lesxa(deTib(Z94M%j z)8g5UYS$L{zhqd$a4oolU~=eFLR^x=$qO zlGC1HyN>@Nwk2?|`l_`^J-Z_rk3{(i*r({bBY}1#8K(r!u{6OMKhnKjf9LX#+<4+e zgy2&3v8U9Gc}=+$^(!BGB4u<0G5e z@^WO2M-wTjZxqVk@ADYUxHONxv0-e&$uU;#*E3ty$|?|Pk{?;qE(Qd9qwr_`J)MFf z{zKZ`u|b|?Lh4y_0DBM)vGx00t!4hs;wp$;wb`3ou&WxYopLtUWjs<^1b0X%&E2!1 z>fQzM&u2)AKhoWJ??|djo!IU^W!v{rF2*$X=LVvW>^RBFCsG!FpWzI$Yvplpl07nI zC*Z{kES&H0qAK;@6%A~ua*$pf&q+C8s04MR9x6k_0^1{jaiQs^QdL<$0q}v9#Y#qR z^H8z&X3dD_w1V|iit;R*mUUI!4!H+5xVo*oNM%C-xw=&ElDvia72J-7w?<5?uo|{7 zQA0X!aJV;acqlov@3%nZhd;88b&fv(AU98@(*+OBcY!u5*S~z8UB3!{o&sz~n$OIc zYm-+_cgZe)udTab@!LFFBE(5%@2!JRyWTs2JKztw=<9hcjdToAIf1#gB+3tl2rME% zC**IRi#wj|0!?tUT$Po-VSdhRuz_jzcgJUMnYv)4aGq{I&|}IxQq|h*{GBc|&_LGj z{n`KYhG|IviMrDK6N*lfB!vz{vk#JDzFZ!_rmR*Eji$&CCqDz-bEp&H7Q`PT>HVPj z3f*mb?GZW!C;Itbe)%saE?9hfhDpm`(o<1$b< z7A+M3gD)C8+}h@e=}Ob|7qT*pd}Dt&%@;@ol`^ZEP?+>*ZauIQ+Xj@y5m_7pRJ4Aa zRJsF;fbTLw;Cm(``AwHW+J&*Nr!21c1F)@=hhgZz@;;e#xD^R5{@#4ult+C}cnP`t z$_97H0Kx!_bq+e9uH$|11#*jDGcY|Se1=`W1D;<{FXpg?LyEs<)2aOdp)K-!_<*g* ze~XXa0x&Udy8GI19%r_Q(jJWh(XN2kK|cLk3ErJ>N8xz@#oXJ84_q`AC%q4q4hy<~ zmN9}-BP7>z&z&q{UHeox&=vhV7~?U!Z_Cd6nNVY{j@>zz&IKp-b#XDKC{D+q6uYQ# zBFI4ds7YtzjsKy;0`-@Hjs*FzlowyM&zUzY$2XgVoms68J1`t^zs5r1H3g*BY4qZ- z3jkADIrMw7!j^bIaWa>aZPhwqsV*;?FSFZkx2>s z!D@}GT-A|Tj&{(;!Obnk{f;m1L4CL!ja^ilCq49GxzSV%sOa&_wJ__}rrAy3a=YXg z*rS2DJtzYAM?%8T4}$y?X6#Ei{?933euOi^M{NGofkyMrj#svP^FApB^{VzLLWEJn z7&^dJqWqqCSW4>OdbdAhpu5L_K}Vb7ZYiN}>s|(bU`)q%DV!7#nx9{$`(tr795h-APaN}u#7$Z9=?u_=i zrx)X2)@(TI=n>L@f>YJb+ZBP1ANXvk#(^9fRC(QTx46zc zYu7L|mg-zVHX8t8@`z*pgOI=*R~xKV%PzfHOb z8E8KqpsA$k(B*tp05m7ra{b&Z5au7}_~@m&%SFN)g>Nk+u5&+tdjQ-t4YO`E`3YkV zN|W5O3^*91hwKS*dYl{JvI-(|^aBjRR^(Yf3frzrwfpn@ITg0EeK%LNs3=^ZZE<76 z>pu#n+`?d0b6v5>w1xUUG6c^Ww?)Yxf=bQkrv5MFoz^u5)JoCEMQ_;nYskbQKS6&y z?B2v3+pAc%l8w_Bz(79F^tsH2ZbBGm0&K`!*xcsne1x=m)0iK>>0uxbL=# z3bTw2+!1>H45(*8)SNJ<6DZJY=;N<;Kd%R%lju1bY@G7=hqs)cz(Ko^F%WL0M@Yi$C+`in?;D|z8t$2!xKb$mA}X#LTBAr9FK#G z|EbeGF%KnyYVBQKckD=x|MU@;lsrE+o|;A5f7{O4OziSZ!{oOr`mLhOakuE ziSwj-X1Khv{HXYIzj;S7V2XHM{TVj{SZT4>eXNDvtRG;Grw0-@wtH~#%+)JY^lO2cXrbEp#KtLv_=X=R zW)C#s>|&jDvUtg}Xsnvo)lAtbl+R!+cBkN&wX?JQ%Ym?@ht3hcmk+>(&whe_QAvqs zTsQlsto6O(0v;_AZJt)?B3f-o(@j&VVP&%KN2A>4(Aq6xcy;{3GDT| z1;2gcWmB;7qQs)n?KYop&(S`IHoQ2lpc9KX@vB>mCUow>Ag4O>@PwqzflHvoU1oOq z3m60+MtOv|{pG~p2l`Dxn0l|4U8)ib0|6jwuFq4SGe}WK)Y&dEQZCwOWH(RN$+_75 zP`4k%e1pliP;{A@-?`f5_%Gv;)xTr%yBkRU9NzoCaQQ_|#cn_Kqn!^aI=^+Ycbn+y z`^Y#oRvNwdzW7R~n++&T!=t-cvZG<4Ns=#k%hZ%LG*KY+VFiMZ?<%NmP2+lp^ z2tEmLtx-qNOeMxOu;DQk;c?@DV~E@4-_Qs0XCG$gOpOd)@%?a3qUqoHxSNlnK>rFg zm0f@PAzm)L?U-rLZ=p7^CqR;Zw|9aL%jq1H^V;V{UR;)X(fRDwu>Y~R@YCVl{Vza)QIH*g*Q6+(Yy;X zW1L$rZhkrKanof%Lg(HsCs7!c-#{KooL%bOH&hjZ8MR= zDXB`yUJ$vDF-h*+Qiw&BPg|u#fEUwi4NQ50*%{+81x(!awA2)j%sfZX+NQ69y17Hw z)GBRLRXwC$S=^~J-6P`G%k?{>(?Qg9V#MQZ7JW@I@gHY$&{}1}fdi1S(PX=p*$Z*O zj?ZKE4vTBb#gH>Yl*58O#_^n-kKEgO>~~alIt=T6YOurs6l6C+P>>(xM3x7rCz#-R z(_tBA0Wqba*U}RA@2g#w)QN0p4SomPI-Ffok@Sz6-Lp08FPqodGQi=Myzw6JN=RyT z(KiH{>tQ|T2@HS0G0CU@b^eZogH|nK)U!6veUdtr1`V-v>H^gY^XlAnL!01C)~Oyf zQ}nnvh`TA%%po{Ie;-dj>=#zNZYapCJL8d>g>s|ioFB?PwvgS+P)AEGQ*<>A+=7?>Kp9{!`;M0PCLfj=_jk{;mU0u*cXwo{&~H+Lm+ctt{5<9( zG`Q4fq~*Y#C(q@@6~x0FxYf}{D5k45*inCYDl|IjlQO)w6byDn$m>3+&`bV2kYp_BYbeqo;?qY!Mx9vWWp z6ek}4c3eBVeKiuRB%DO9DwMHI^AN}J$u#B&K4^1MweGF`B>r;9wqt#738ZtB2l}A- zz;|o^cgy<~xx700(>iRIsOAw8@`7L}FFzvrnseIgd`-jxst^)7`)!)N=FWq8q~=d^ zz3@TFHA-uZ&&rLr`VznDA76*6jjXDVk*aqvgK&9XZcR_qhM;?(ojH#Wt%2tbj#d+W zuco!Fw|zkur(?|=(x!*RQmv&eDQ)>4Q6}JZq_KphZ7H;O4DDZLMzN>Hk4$1hd?P!D zt07=#2sN=FmO}J%VDcb?iVSzSMret{IDsF6bEmAaNt0R)W^9hf!_?t) zz400m$FvP44+nHMm^8X=<9GAEQdpFT6mEmCe4f$YTwGUR#uVAJNBg&3?IDyGPY@oJ zsET;ongx5|HF+F^RQNQ4pe$>LjW!3T$?eIxpo5C;H&i$v%xa3_R@Ie;-Ijft*0lO> zDYj~VZ31+zrLx>IIE3{@c8@uq%Z}j-T#RPg9p=T;Dr*{7a8|CS8|cm4@FRdltuX-#+CC z9|D)71HDW~2k2kJN4NJ&h<+_A{}Sb(5Oh4E!rQZ6ksSppVv-MHiMDif)h{6>md-em?3bkG`zJu<`4lbLV=!^<7?U|W(LBT}lVOiG2q(g;hUF>DmqtA~??HcREy zi1e)HS`xxJavnF2B-0w`At1&>#N|%+u{y!4c~HaU=+~&F$9cZ>1x%y#d1JrW;5?1T zVN!ect1cFuTu5{-!h6anCt#tLePw@wLnjEtMeY@!dDMawZD59Fet90B;Fg% zr6L*i9hjNn96qx~>eoGAm(J{J7qT2}_21O!dHX6e=(dE4KWooYLasb*taZO}S5n{} z;Pyy9F+Mq7uQDmHajgI=bS^>jkCp@MWcP`=o1MSxzK(wLpV#*UC&g1F@!Gqe>OZk7 zD!>W}Z~j027fvEhP+%%2WOl|x9V4QnI-K)h%eW!8sAWRSuA)|fi@v7^y6O_ktUtF zm5nFy+CRV0o+slsJB@++?swj^r}vurE#Q9i$CJDA|M{{%o@wm<-sE_|S^#btFWqy1 zyX222AI)}mR+jeT$({#)0R&X|ugi($wu0#}l8)mwJpom94Wpe@{G%mEo6=i|z*@7M@vM78ey@wU6*$sopd z9DF4!j$B>~FvD5bM|8tD_{J-gQkA!0UPb5UYN4ve4GB_u0f9U>at!p79?_?$ZA! z?RUxebI0}og#StOCjk9^5@na-KUdn_qo0p10;K-=2-uvT8}omc`@gjNzl`#KrQ)Yr z_}^%hwtpF@Rt(Qs3C0Ukv7ca%2U9L+*S0JjfH<|Rm!VYrTj;`ZBCw?urd0p_lA-9< z?=^8^V*2ziGi|Eykd-1L4BHx9L&#f;X9rg|%=fD*VM7IW0*CZz;(W6cWf!&b#Q?MN^HQnVIo)M%U!pbTx%ml@*X1s}R+pH%9I`%J|IN zOIa1C_C>HYaM*Cb@qCCn5l6x@-?5W{3V)Go1>jdjofQ{4%U=r+xNtMSw&!LF&Vl^Y`G7ZflKitf#bodke07x8OQPd zGB+1JyOBFgXS*(Yy@JJyl?nC16$r(lo;m03iCD*(`S7TE`&~zmH*0;nt9?#*&z_G{ zH#^(@JqWtl8CY!cNnTDk`_KNvnI($AUrCKgLxo9<^(VTu1=(7PL2fKVc+WTKc9O+y zV!fahxo#n5nYNln>dV8{{|AeCV!uW>(ev~`xUJ7o0l-10B%Hc6%k@7MopLwEVtc6z zRBsal_*%m0tniVz6JiklaOJdB8a*(U3hRrrMb|FcllpB!to2AEiJHZSOLff{Zp_~A ziiBdb3)yGI!@SFA_ty6B*>g8QmUj2r0U24Eww2tyM*4y6Ep<|eDN&rC(? z?b@XqxLbEE_umq0bP7mq2Cr#7593tlxlfdqB4YbQ_w0Gnni%h9E9M%yGP7Eat!#Bv zT5h1Lp4fy>=dl;pZ?_5Iwfn7&#|mZdC4b61vvx^t?UF}wP>H4VA?*i~v^|XKr)u*B zLX4FdhIFEK5x_*Y0R6<>LWb)Q^|gx`gZcQis~u28#T_d1NbTe*t(HMnMmOuroR$%6X%614F93NUk5{HAes5 zN~5ZNRqNiny%$_p`{ICdD|p~qCA_(TSzBV!vTSCvMyqO;v5)@l7KRSHC88SDrg6fe zdHorC(VfNT38r9qAXnaVDj7CsKO4uipIv6Z;;kFl!G>lW(jn2dn<#h5+On?>@dvtY z*&L646R)Qs=Wr)9@)OQUwPmI)^bJrJa9sH-FaktG6%@AF7}xCh=Ibai2NN!cMK&~S z@`K4GnhMZ2z^3#RLy4&(D%dq05tXWF*(X6h*+O097Qn3RVl}EB8;h7e4)H-13%R+j zB%%EK(lc~>OV9m>(C72jLxm>xg;6!OU<*JN2><3(m;GSDVn)&N#&exQi@eYdjFO5EFSN}=Om*3RXs2kGJIh6= zOC^7ac^%A_2=e5kz{Hn$yj zfvj#Y+dTk&2)2;9EHcI8V(Nn;<&IehIZ$gS8#kW{iP_;+&I+L?b#5jF_MU(GZa?w9 zl6GiW+1KZyjT%G|Od)}RYQ(jY(~@~9I<54P%>cQX&I#^E+KB+@<_qSNogH8>tL&4E zBrl8cLK|Kn#gpjN!nRZK)}v1<%vRypQ|M&)2dQCXbNCOJqyjyWHx$uI)HlcLn~xWo zyst0`tr%o!bS9m-jHoU&8mTTjlDfM+a??q(Jt=?|aR{mK@r)n2Go(8TXf6}IuEqDE zw@Tf1cWnG(rJHv{HE1j#LUt(?#eWL$=Vml}EzXU#-QLEUiCd>*qN&H@)>Ol1X4-_X zHCu$V7KufaIU`x>hl8$FfY=-bE@mc$orq@8!~!C_k^$Nonp5TH@ulw}pxOZQdjV7Y zChiHuKGr?2 zKf=e*QC%YX<3a5q)aGzb(RhiJ+2Hg-y32*spjOZX5_=EwCu~&Vt2BYCjAI1^6ttj76t2bwk;hv z8-*(&(wO6KN{#+z%AFwz^}<#rE&MmY*w#~;x>whw50Qb+1K5we7MULP?UdE$h$CJZ!2@&8&EORwrClomIvj;hw_)wc&(>zVuV<*&;TDt@JBD&*A?sz9x zUV_MaRYQ}Zyetjf%2N;+$Jx+ll4x;^DQ+pdf1xyO?F~R&-17W#LoKt)9ByYEmdFCT>3xeCdCFGqN_LG zB{&NN^>&n}M7#P~H>!-6g-M~gqTWU@7$&f_RNX)|U#MbnrxseKN(7dJVT##xL+;K= zdxl@SMbzEukW{zWwcsMwD*Pli-n6@-zYJZbtzQj z0AO(FhtwfV^R|aOFu2w07bv@8F<%B84~*U8!CI<#qZ-g(Y95XyWOu-s#4;34%tOBa zQ~3`ic4{kIK|g8L$#oT{jOAs)r(FHub}TrrPf*fIX0KmB)^C-dnj?K0EU>KN%8m6I z0^dWvbZ;3gdpf zX-^5(#8UFHde!o)C_${C>!}=+^Q6UnBD3fR*}H|vn&-kSq-Qn1y}j9~b^)(_OKuxc z1A$JqGCywapsk>mdf)Y?ruuG$V)r=9_;zbs#tg5fpMkkHk! zkJZqdBh}}HEkoiBB0rD~Y>k)%Tj0F?Z^J=twEz7NyFs$tt*7pmji0y#31*z6j;}pw zn%BCO4tsn7e?lgjiHo@8u;~Kce(pq7+bm(V0LQ8u1`~#6dV-g(<8b;v`MqAy$5!{i zZHVRju0G4=S@LS}5uY+q7KN>gePSY0Ax*asA(o$3g>zKS8wb9@H-LoMWm@n$zvmd@ zORdU)*#AL25!`GxrTQzTzJdB&d3n=_nOh6I`b*=kahnd=@MDgIewAOH3a*0jXFi## z`s4g0nzNeIKlTneJ(8pH5-()0(RmTh11YNCi8j;1z0T|$0wo}>ayqzUo#(f;-(&Q3 zbKw!;a2v`{436`?y5j(IbAR}Kov&$MiABV1E_37^G!*J2nga0e)+gaBg^kwVr%yjy z6`qh){J^sZLbZBv*RlJi(PnMy!>#OOX9k4N8FTyH$ILZ7ZZId7+mZ8Z=B6Pw2Sm9!ZhL_doA|3$UFR z7SZe4WgHdGXQvG#u(L6Dt(yRjY{&WFq~c-lh~40Nu*PDzDus~38 WjdK~&Tuo9 zz$#5|nGzI`Y*25CTZuW-)C3T?Ib$5WkNj=`~mb_eX zPC+44r(fAle?Ff*#b#|ZtWMpQL6x~ZoXo8`+*Z0&q#E8i6IHcUjP4to!cLO*rbRrS zi;-rU(Y) zq~6%VpKEJCKB0=&A174_O8)t zP0Ji2^0vaz9yAsPwiBeMAfU1P9~81`%+e-~C-Yy5ps_cj(ke}3Wb@O#7P#8b27l}g zYThY}DQk*>yfe9&@$`g9 zgI}G2jh$+HVP1%OURDfQvryTArM_ID>Oh?u>O|ryE!&wP(AQRH#MAxmj*g@o41zBk zEKno@wzCHo9By@bP^w3?{30!lVu)-{->pFrF>!P!yh6FakRhn4ka0LsYew3tIfB1r z19ey)tyf?eqv7KZu9I&=FJ3~NaTxWUd>`nUkz&*S+8C;}Lyn`A>-+Bz6_i^R_I$qG>Q<-2Se@%Q}7kbHb=I#@$j;n=(g!ZG0#4ExvKS773AzBFY>ET|Iwn zXBz_9CSu4qCF9n*R_XLQ$w#JK$oi;^AK=UaX9QS@ziKF`Z#`w3VEd~2LffX(0h!|c zx7b$CevL@EpS0i5AH?43P{q!FLZ3A#;f21-DRkWxUGL^eoqI%ML5&TytKPY5tj0_3uYats@jLz;W9n z6N$GwVFymiDc3uBmT8jt*Ol7Ru#W3#FZXx4uA$?|x`E5KkiySp^crM|@M&d~n>f1< zg~jE%LtA-h<*;qPMeZJrBZ`@PK*a9~hG%c6T*ZTy0Q*qA35n;cFE`TG667T!ZH(9v z1Zsj!83HCVOyHjrtBVK3-(~rdC18kYpKLQ_W&f$cpt>?@G|*B!~^Y*JwX?BtiCStUIhEejy$2 z8e^P!-VaJhn56RI?|GU_!G7ECX>UCi(nAQHA*|*{D|``y{MIasVmHkEIUNbUM*;~iUrZ( z-?aQLMqQitYgWTTqqq#dahzhb6=2UT#sXkx)W*H6x za3Z#sak4YEjm+9E3@6w(@7jhNIdYNNpHNf zP-aj@&nBU)cl}6ToJJ~eEF14BO$sztvH45o=^NGPNIi+1yC%=Fw(s)~7N+dKb?hNN z*Ef-M0G~=Uej@i_U0JoU=0G*t?n^YX`}Xf;&x*q)Iq6PU-pA{yJCp6gmgUZDRztR1 z+wMAGQS`xif7EcPf^kjV^nxACRDvJDoMbjL@9S)dZk#KMr=1PpSF-y*96VjLd{DEm z@|j4sexeg-!D8mys0EU$33&8X4;8*P)@UV}5yJeG8L>pqph0Az(Je$U-EXmbq(Uup zkV>ldCYQs5-sRLNDG(GB_gi6?Kz4hKs=WKcR|RE(OX=|!hOEQ>mb-bIb)Ae z&(RmojK`4_kez!?g1dK&@pUD=eo79uVT2L4SDc7im6IewUE$|A2zT?=;`|w5C6AoM zdsg>Hg2pkbgke=I_tW*NAb9&_kvf5lKg%Cdgj^tSH|+b(`zU6HCY{I{<9Z05@2+)) zw?y%#Cd+<$BP(5lY~pIbg9kx!WrzuSGIa1wlg~MBe?HHR{j@PS%0$YIS)n^xuhF^q z-b)Gyq^K1E5h-gupgs-oCuzPJqiU(LghzxfUv6nIj~rG7o_ zDd=gcru)=`?N`oq?3!}yC_+qHnn}~W_3AM4(9<9VhklWuE4PakN0r^866q|CPXeGt z2Z1j1Mx~)Q1W{?5^te>@Tmm5iY_^UQ=}X9uoVHHC=Dhsp`#nK>Q?z870i>XeH-nN) z4*lkY;V0}Lja|OcJ^8BudmzEeX!%kk{7}*yqA8qBk3C`G11Gv1Z=`sbe1Kz)eZivY z_1c#9XLB`WBND#`$bJ+Qhw;+^0SgEpStFzQGH5#;obD7F9_BxitUD-1eJD`RRXcHW zIXS}kQ6XZTa9u116B28DptmaqbvJzPd}2h3u73b;vx1;Hb?`R0apbsRLbTaiy|APd zCKXvX5;I(@v|Ftb@+9t?eJsk|R8r>^M%r33zPpCmvZ7h)*0)B7;;Xxd*(LR*Mo%{l(_Zro>{J3^t7&qfftbAR?pc` zs;MeMEE40-3g?7)x4x{F9dHJqL4T(zIXRSgZ--B(T2LI9J2~3WKgKobx7vK8R&~RY zT?RPKXyd>Q39x#@#OQj!=KM-!;PpAbIfqV}MnksIRCKT&ViJ_+V7uYIRR?aT3wH8W z9z>-yKSqws4c!KNj+fDOhZ5to)qTl{IFH77U!&Ti>x6J);76YgaTo7i9oY0JM22PE zE-&=pOfMdpDvJ67B)o6#l}L>@eWqyVr_h~PXs>!D$yppLIkA|0*U-4inVHK;{?^ni zw+Z5)nSYz9LRR6VN=(23x0qR0h@BG|E%|n#BwyrUDxak`+z*q~s&}7QT{kMGdyoVS z_W6Wbee{puE^5xME!O;3gP> zWX?{pQu&3pV0DAg61!)TYV$9Hs^t4=zYV3qBDo7@T&uaHzk8d>E#`0O_J^Eh)grjL z^Jpq=AhbfHKKJg?gVNGDkl>zhD|Mz~umoexF^#ir#j13KMP?E=P7Y48-8g^A*(!5+ zkJtT{_dN%1^v98(_OoA3Q{b!E&?hVPE*PH@^&^Tv#Lk@Tqh3V~n5|m*pCVT8Pm)!y z8_Wg#A84D)6ZSGov67;5N73K9)>Nq*qE2F<4VFz*T1ow}FhUwQJy7nK^!=^~!{m=S z+&|KI=#3w6ng_%D2WBdT+ofTk?LxO2MGg(E|5wz=%B_005$s=GqQ-w#m3iW9nl&$5 zB%Lcil)`~7)ads2UiSww5`%cg3|WSjU+ffMpHQpV|_Y3HIgNON##~$qaJPI>%IL?;|Oiv z*tT7tQ;<-23XZg~0`03=drnu&72aw2RwEAzp|H{{e>CKL=ArgFXGSPRsAJM#e{<1RkS`bNa5}= zzgDbj4BBOzD+FUk+4&m+C4ui%{~B`Ssd1e zD(V6_N59^tT;pp?!A7h-A!GtwG zv*NRI#lMcEd2FTF*Dq}j4Zu~}i$DJaVnwKWEY>aAbfzX@&BhDKbE-n@lEux}KSJwc zeAJ@-NDLWsgqeOHoTRy%9qY80x?H(?02kK*DJJjvp8?&RBSX5yF%pdDyxY*W1>Y?hRAB}vs0w|WXac*GLl3S%4aV5apCbh2Bpu^b58ra% zN*SA76%ICjr2Ta0(6Gr6<60+;{`<;sDaE^^o{Vq1G(K*lf^f zfGDg?&Nf}t+G@Dvb@l!`&<7*v$XxQWtk-67*iJf-59Gjdn#@u_#+sT$4)L&`V2V2` zhW6XYuk>KpTv+}}VbbKebZ-n{ZsA+JMQ1DLfM3DpGf#|NRPb!N;4N#D!n%WVY3e*JRx9Ax5H zLPWGB)^6@BnCryM?Rzh}JehZT*;+>XZ=eZdxK?pKzF@!BzLMH{1KZHuIPH{3RanuvWMle>`dGffo2jIY-9cI*PO z6lPvP?sueLF(e)cC2C6)hnPtPYzSBL(%7VGH*nsmFb;0CIZ}00?nH2+9pXapA9e^v zP)n*&;rt5CP|UHr6va3l4bSl!?gJ+QN3O4YOO5M=dM1Smf&Ky|`C3SdNZ4yVs#xcbe zR?PTG=X1j-QAS#R)?BNk(CdW+YG@RqMyX3taX`kDc`e4`WeAPd8%)=5+QZ6Xd8615 za5Vj>0io-$NxrQI_lq2`xbR(uv8eN~ytv@m7va4kawp%3%&GttK3ZK#YDLE~4BM8y zjpF5wbDBT6?qq?zS@GqZn9h3P%lwnsdFBiA)ZxE(nq>V^K(-CVU142%d7I!*UB+U? zaO?zfHJx33bJ>wILgFPG3$KRwxsE0TM@XGBHiOM_SOKU`u*P-swG>@%-TTRe@sw*! z-OIVFqi;_^>X(%bT4kDIt1gxq1` zwWL5%=H?YgncLg5T)yF<$Q=^fJL_Pme8ffxYm3M3ZxE{^-jJztNZVt-*!ZK;*Q|v>oW)cfO`-__K zj5k)-H$s#Im3F-3z(OU4MYrh39-CiEc@3J%HHgeu%u_;RrS8`1mvVaQtr3MR6C%w$Qxf|l}z#$R* zDiOwH7+7am8Y3BXO{QG73v&5!DkIGa@%rHi<4&O0M!Oj}uPR**hHG)()-4cD3 zN2pP?cnNukZ)moaU0cl{C8`ZBk|uJthTSUI^PbHU84 zbK~hyNL#O#^I8kKU&fpAPV{-nfRI+Qq{$-e+PpGwuS)x8SgWqxVhm{Gd+gZoug!HnS`5~dl2 zVo|C$V~2Odeb#EQ%QkqtH>+%6g1~92C_X!C9*nOWzQ~y6QfmC1Y=;_$X1!6189`m- zrtK6~kr|v#m2jvz0>Qj4U->)?Qvz&n={pg9#JmO<#>lz~k<^>uh+f4w_Q$##nBqa6 zlLv%b>4EDsaB}VnzM%vX)HdTKhHUx58m=ZHR3A4F2w8U~S-YJdYZQfkDVDvt@yrKO zo$Bl4sq-Q0^!S&}G^K3aTq=B0F#haHtmEl#s$zl#DI9fb+&1hN`IU_3J+XU3gG>LT zeRw{1I5FAjYN@MFvz5 zBR5*rtU%2s|7FAfz^+VavSEv6<38Nto(9Xobj56IZV4UQnKAL=(nj@DH}0D{r_nSs5{WfEaRnL1G?hK{8` zwtA&1`x7v@-T+^T$S*;jQ3l=@DZ?otH4v2hp#7(Z4lxlxT);gNg+ri zbfUC^iS&XbbdqSP1c-tXLJyH15)!&3KtkYdz&hi;JKlNc-Jf@iJ4Sx(Wb9<`ti8Uq z=9+VUM7_NgSR^kw2^KXiQqdF6`OJo%?YCA{Q69?tVF60{T1~7( z+`R1qBydj31N*N?4RVYtRG|J zz!ZK*`P26GkPJuz&A#uzb+cD5D|o|c`Xj?Eu~91;?R6F7_i70T_1f}{fs(nTX>e`K z$B>QM34SyUEcRPbQfoq_+-E11g(hG~Jv@ph!A3I~bD0f(d;~Cw4*zRmh1Z@S$%!&C z@BRqTJ$^5j+*BM92{@GEbV=#&xY$6j5>Sz>W@^=PB1KiSiD?mZOc1c&j+EcNglzwuV6iy#k;#seMbo4jt>)JfFKh`U1ECV!OEYvHgrtz3@-6!NrB7mSTR4GX^FvNT-8F zUWZB#Jdmq#bF)JPYpOhG=0I|DW~tg0neJTt#{(V6;Q2b$L$hmLK?aeYXxr}Mu_nuo zRA4(}(%vS1IBn?GR*=RXwZ&F4J`>M-=kVE$aF%o`GSaLlxj0^xyNhF-t+aQAV_ZP8IjB}^336DNT?%wP^Bk$~(g(bLnTxf?nsQM{V!d&F-u z|Nb4loP@feib@JlZ48~utU2NRuqG&SAXB8b_rxK@&9<;^0OZ9PsXk$??hfiBg7gN4 zVqF2+o7RLj)K9-#+%?ZNci2cKvys`kuIljUjx%X3%0lUN>5i*OqR*QR4qN!XOF_R` zt?dVa9O=}9x_EuQh+AbdZFlH%>&fz;3@mfn4K!x9z!F9kIed~ai{dww4uDGIbIfys z=XfF^IP8xiu+A(=Ah%k_Nhw~DsaCu9byJ9exqeL)3#3<7j8|meUSh+6+h)NE*dGbu zC6jckN0v*0mg3jDa$>6J0n(`xCzX~oy`}t!901>{l#Wj&?Vd)9B3u|S();}f%p)D>r!0tf--XH}Du zQQ?xYDTC#RKYpu~`#Io1{8QpUj5oL6#?HN9+pVJfeV$aK(x)~*Z?{DXEc+ZxKL-xC_}Li~?Hjlb1x z_JKU)6h2@z;#j>zlqOI%i!3h=McT%VAmeVkkKQmthB5{k)FNMtA&y(NHP~-q)XKnKWjnD1*3&RW5$OanwM& zixaJOIoa;Qni%KVpeX4yIGk545-Zt~0H>~Q@f>#M?Pe2w7EFUyKC_p{Sj(s$qbg_k zUrhs2A9C7Sz0ArQ9!Al`LgZUq4ivRS*xXzlCjyOkzo0`=I;C0)GO3h+Axk_$>TP(? z)&`2Je22?_`mhZx+(2q+2(z?mPye7QVNa7T_iZ#E4 zzE%9Z_~CamKL?lwzj4B$7)qJo;n<2Zi)nF(>2A%0g5Bjoj)Y>>QqCcR@+h-+R^ur> zdVyxY2HFZRHe}mvx>za355CiyUR#=+&g{K2bm9mV2ddA<@f9c++w_HFvYF^w+;nD> zJy|J~a`v)W8Dn`9N3iiUn+@*=o{F*~^4a_Oimlo2nCt(@2o(jB;~Jabh$_3E_0ipL zqQ)2ek;f4A$?HKNq{xlR{wg(TVrr!B3!)wu(Kya-shX1>_&rw6()}Pp`_q%AE_+JE zKLewP2!grqdclD&2RJ*h+&!#s(&%R3T>HFJ$Y1@2&duS>My*+-^IM_CeRO^?2hK56%^>LXHXxSq<&u_6!dg z3BFyIy++N|DLOrX{_xexs(0$kvOO?0x-4l*M_y(&9=uw=+Pws%INDg+O}`xgJ;804 zv}zVF&9Y?$&?th{E*vb4bHpF5jIyIy6}hWc`N?r8?SHESaqVfv?}$yng1Pzs7xVU- z0PQ>Bxq<^^3(23(yeX3o`WeqbQ`^G zf8Z-?`~DI4UF-~cXXtKMWRGj5#;u1y_~kI36%b1!UsH|ximL*qjVZiyN?5m*YXYnj zUSgowY<9NCD5ykV+2GH}PWd3LCVi11P~pBygO(^>V1kgR`f9D*ub$%;??)~Mw^hF` z3mWSRX}f#YQ^)lEp-@qy6RZ-&UN^MpGs#cv`>#w8RUDMOIhMO~qaeiox)J)% zXxmb#E>`5dfB>{5fUh_xK6YJ#+xKLjBV~ymyFhU@27polWwG9m(re(65fb~YhluD{ zI?sm(+AG|-qCU1MvlYIPjYhQqFx2-|(qt(H2(i2mp4|Fs>P)YuQ0GQ%YmTb0%CGSi zBj^Pw6hWK9cAdvszd;8u1*_-IHQ~h#c{7@X3Fa6pM`*uv3gOM>fEKKL02yXcoY~o5 zR-AAZNmdH80Dq9zHn-4Hm%*&O__=q!UXp(~$grXATPI6uC?-&EqZDBlmyyJW1Jbq)ch!^mysC;;IT-^~kCht@vYW#D2|GiZ`))1LvoRGZ-$KhXe z*yo-y1m>x{$DJ;nt7}1|zTpKN!XxBTy}C#!=0bTY3lNz$#b8NP-z!RPI_ddN-J7vm zZG~m>^N5Jjp_I8S!AFE=ug4?ilryoBZ8;YM1E$_0uAR8ZSsTS~H3oB2k9YjG#S_#j zKj3uOg~)$PD|u@R-Uyfab{Ipvl3>q7g4x?md-Je$j_Na)ZtaRK@_9+Vu*8e3)(6Nj zLr%|oj!BzG*~ymnUrk7@`fmkq>w>dkHwb;ob6umBfpa>aLtz9qOz@V+27L2oyLxrP ztYoHo2}XPgdSM6Cek>M6AbG6dY*oWJ^#n0^GGQkDY{6&UGB6b}!g@Xv))h z{E_D5CV&!~F}THuWgufKC7+jSL@>RHK-{qV^s(({^d@B*_NX>zd^D=M5dII_)-ZC( z*xXP*^ZOW~Yuz(r#CG&phV@D083VOr9>!hMKNKIBe#G}LzOlHcRX%dbbbxOlcg0Ep zda^a8QN{uAak=Mc_-lZsQSi_*A)q<6zeap_FqOw~X8)8xM44d=*=;TE8kNFybZtOK z_^4I}Q~D>ME}Vq7<|fUxmRC%yzl_xCYVJh)xQ`ZW%m${4M%(Y?*;8xE_{U2s5e>5v z#oUo**L7@;V8b~Y??I0TW(?i9*gjRHpp<26-zXU2^Z66kXNAX+yIxdATX-&)bT~%e z8Rq&$N}C7XaxX3Nwa9W5j$I;48i!5`ptl>pQheh1ckDgWU7F)F<{D0@Be?4=&#Yr- zeI(m@HaJVJBso8Tq35dM@W>}82`AZOf=pR`iZqtHcG!eE&_RYDO7M!=WZmmjNYxgU zcAc;ZBI4Df=PpF{fovhovX70Nv<{wgc_D9l}Cbxu1#BIA6alZKmv^vom+k zr+WMO8vv-)_*F$AP%s+_5N8p_1Jk)ZI(~GgO|gOc@O%q|?R4>^%Ho!SIO-Ez{d&ll zI-1U(-CG$EypSPJBFVloGH@~FAVQT!IJrl1yY@@U_k{0~!0Q;ix_}eMkbk^vMX-&c zC5v3tT4Nz{8_&lmUY+MV8qMF{q4*x_?}9(8wVnp&-)P%P@gZ3Vv3#Yw|CmWd)UI2c zQ9?D2y2`t}A&R=F)z>o?o(t4>CZ$`Rr8gEct$#V+i;5_2Um@D~CeTy3Xo+H^?K6*ePj|`j8*9$D$ z`x0i5Irk@Jfu0WL{oqQ5tqLvH+Ut%omFk0+s+L>uu2Gh$`AMg4rCb#__Q zr^V{0yxHi^Wc%&Mvj{!f^7ZqlRBvuetKw@|%+H%dNmGH3g{;aY{&}*a9?vZ9{L;%w zmKE>w`8);kOdokza1nGe_EK_RCKr+JeHiAy@zu2h#aU?#>e#&1AeFYNn@SvoF^gYy zsFJ|2P)R!`Bc;CG751qnGc?Ux&kB+-M9>2Y;H&g^SjBwTcl4KcBSfziN)$Qd>oQ&| zCEPayrUj3`-F{I1?biBwPI)`fN+#(H<7&J&B>Edw+9F8BqL=9OIIN7u`^-$jTgH)! zRRY1#5Cu@{0pF9g+0U#YVGsRs>Ni$&U|o5@AwtJ%zRuwCTay5RPVO%ZCgjX20$?8k zqLD>4<8ei(RtzRKh3K+h(NyWj69JIFj*lgZpet9paW$+dT|8@0yMJD#CuRLp%%?LZ z0ZB%B=gwJ2WyPOIYg@{APQr^D+b8W1g^JE+czeEh(ZX6a)A?Lget<21^nUGN-#Q8R2o-%K%1V&wt=^2S z?f)54f-N#knZS4B(|{fswXgeZ5&dAG){SU2K!0=WfZfBY{IB|vWl%{!vFtMKU+hZW z_B;{sSX?QL3LCcs_(G|1`dP_q$d}>=_Lv2$U@d&&j3M*sRi!9xF- zyZVfO%(&ukyO>|jn`IkQ_VnSslZx}HBAvajAegQLWU-%$IQ;N3%=N#NE7@#r05G2K zhP*ARc&xcqMmti1=Nfp!@78CPj-^F5YTT{ztd1$#)F>27VALsEyr%~o(LSqg_%%!W zokUfjo1MmKtwtAsxk=S(eLH=gTPlw|N9|jw)?arqy5))^ zT`kSz>XD&B*@#VyqrEtp+YM3hY5PM+mu2*1Gr}#HC-PpokMYct+Ssr z2XKXHCT5;FOcE#=2pY9>1eW8XkA zAEG`F7!9}A0vjF4}RGm5;d@FBSMEG*G;pvBwb$c678TMrz&?aLG-T zE-B={&Woq;?=JU-arj>Esta^xYdO(FGij@5l=gTCf2K8`E~RZ;ql9ks*$WT)QW`6e zv5D#X=5{ypLsgdW{rIw=jb5MTuBgFH45T)CR(KXN`DWzllsFFpCQdUw)?F+6U6(2{ zxp>LD(CTbpN?vaXV|}b#E#$HN>h~_ISFX3ei*m%n!5p#Xvrx)p2~iE7gr=P(45`&O z&QC%S>M$Rdq@fpY0u5}-7>R?s9W_lWE)M&?EgEOnbcHTVWa1aQc)_sm=S6wn~O+bPD ztzMy#w*+e6_C34)fvSP5ldUr>NGI1?8z}F5JorcEsj$iztBUaGx+cLB3v-6ETwtmn z|K{z(&0YZ<|l5TZmgtKGb0tX->< z9u-GE_J+05dZ3baQM?J2_QbP7txio)LJ%+04^0*ADtd+2?1mMZ30NI zC7xiC%;z_<91Vj)I&$(nV@8{F@dzv4P>)fy#A?n6pmtd3b2Y-_Nk?qpF*QmjAB5e z|0A|ec3^7dMJ6;WX7|DzVa5eME0vXT+J{29Lra+mR6)KzlEoX!E<9ZdEqns=&?>4d z1heBT&F|_XlG8-WLl0e&JXasRDc_11n2ad_yiY1J2GzBGxobHedS~U}@5t_n_q?&T9OV5AvL}p+3HTM>D|stqwJZ;2?RYOjDsXAK{Xhbp=i59EH+_ zG&g(>n+BP5gkb=?7>M9;TAnnr541U9dDz1?Ap_)2VuWR7W9YB62x&Bkq6jMX!}n2$bF+8 z%)UesxQ*mDbG<9JnH@2KVD>{$s&_87D9A>~7uQ;+D1|lB37%y$fFcf%Y)Chl8EN#y-b?C-)hIt=XZEfZe&|GuzP5DFIK>Q6b@v`-5Q9WGSmZ#<2QwMt??D(>rm| zhPgu$_%bee29!Ql;PwmRF~zU{4ELyL?=z8_r6+YFRtUA7bP;QW_H1mcyM+mzOmNI1 z0O~Oz%YIazdSBpcEg22uAnSn#mV1xp`ATMY3~8JVcWj?&jv11lAxoRt7J;#bPXx|c1ffjxM|aAtD*eLnlseMK3v>WjFbg#z96$$LL$sI9!p z?$S@^-YcUP4;@WX^tZvhg6PSP2NDKvNNdKj=yyP|JM+E&R`|7|H{recz^8*%LnP0E zHT%}bX0U7q*n_trU8f*YZAP^S89oR_E(&3+1Z9v5ILrs)!vE z6$J)bbK4~Ew|zzQ2kh~<2Tcu|G|W&Et|o%UXk2Kw!-QmO!R!%4PYv{^LCAG~jRP^j z2AeL(W6Ur=-Q6d|o}~d-V6{84$VrZ#ZmK|op3u8C>1#IW+oo*42n8rvh6BRA8ch)k z-+u6vY0?IXrM8?%izmJ=VO-f2Ji9Bo1bT8ts=jn>ZEx-c7gM$zJ=UKd0jPc9EH`@q z|8WPn!bv6XYmB-9;WgHRS#7o>IVEYJXU;wh0CM7nglO+bb6fy!>qlW03qLh)oemJJ4D3 z%y;3J4YY-KVL$%EFnyo$4PeaLI3EQ^P-Sb^K3>OQd6wHE@~VoQvks<~%uR)}D(h%z zVZYwC>fGmjO}A_?6ca4R_@SGLzOmI6!PH)EYUS@*mgWZ#6Q`Zb>u5Pa)^~PJ%C8fH zhRr^FRGyo@K=nuAXsfPtuAOjiwhVxy*0&s|Z)99@^M@UI+PIJlU;UUkFjwcpY+P#p z4HI(or3hYa-hQ2vqkGy2G0WpIuG`O#gqivMpCS5B0|b3YwIW(PNl)I;387maeQEUH zcVVA!|0NW4%6GLwVLl(94X=8-3_`Vyz3x(5(*mXezIjJ`e^IY-O48UxrMir7uH(rG zA}tRpZh`k~2i~dz_doj>?dO|s=;8m1TL2Zp6NA)!aMCKm=rAS0eR*Z(b)T|oivM4n zbBD=)IuYExe-&}9OZI5U0MPv)kR$g#?hdM7DvS9b^VSa1GSPTEv$miwe7KiT07bv@ zGQxzM0kS5}R%?H_!uI3vcU)v!5#%qgyaDL7Vg`D}QUhr1HxZ`_w9u4f1;%mq*)j# z*Z{d~TT$)cb*Q*#u~kvkPBCMD?A^ASxh|g*x}+Ds-*94s->geqC}in=eNZamKI|So{p&hI5Li;xoP2%^XW{%LE+y*_ijkR_}hqdb^MkmW} zG@8pnnj`I$w$C@437l@K7C1h-QI($5IF3ODx4xH<-^gPpe>cr)YIP4mM-LXD=3i(E zJ2U|&vC%ID@4<9T2)O6(vp05OLP)`DD|F%7^T=~DhYq=it#kkzPJjJr`)gi>=G83^ zlLTbehPsO4m_dc)_VMtkZ1zGLt7r4zD8^hvK$ZZnalt8%zkjzcK`LY_wj-0s`2!q8 zGGY{YxsC6yuS5w~@=hC6e?Lk0>WBI%IlqKDGW*X{_gm{=(_cpNJWn44&-`r{cHtWP z!JUn!v6TgqVD;ml>8*7w?l1{(%pB-oVvw7<>b6nX+rB`M7@}PqxY)~Jy)a$3pa883 zM5~f@J%1$r5Ts3bKO?)*YvgRk_HWN#HTv4==jjKj1EU7V4x$!cXd)LL@P-vPz^$_U VJRCOX(dGvsm#r>U{CYj^KLE2_2(|zK literal 0 HcmV?d00001 diff --git a/agent/billing_view.py b/agent/billing_view.py new file mode 100644 index 00000000000..ef97c8d0d64 --- /dev/null +++ b/agent/billing_view.py @@ -0,0 +1,295 @@ +"""Surface-agnostic core for the Phase 2b terminal-billing screens. + +One fetch/parse per concern, consumed identically by the CLI handler +(``cli.py::_show_billing``), the TUI JSON-RPC methods +(``tui_gateway/server.py``), and any other surface. Mirrors the proven +``agent/account_usage.py::build_credits_view`` pattern: parse the server payload +into a frozen dataclass; **fail open** — when not logged in or the portal is +unreachable, return a struct with ``logged_in=False`` and let the surface degrade +gracefully (never crash). + +Money discipline: the server emits decimal STRINGS (``"142.5"``, not fixed 2dp). +We keep them as :class:`decimal.Decimal` end-to-end and only format for display. +""" + +from __future__ import annotations + +import logging +import uuid +from dataclasses import dataclass, field +from decimal import Decimal, InvalidOperation +from typing import Any, Optional + +logger = logging.getLogger(__name__) + + +# ============================================================================= +# Decimal money helpers +# ============================================================================= + + +def parse_money(value: Any) -> Optional[Decimal]: + """Parse a server money value (decimal string) into :class:`Decimal`. + + Returns None for missing/invalid input. Never raises. Accepts str/int (and, + defensively, float — though the server always sends strings). + """ + if value is None: + return None + try: + # Decimal(str(...)) avoids binary-float artifacts if a float ever sneaks in. + return Decimal(str(value).strip()) + except (InvalidOperation, ValueError, TypeError): + return None + + +def format_money(value: Optional[Decimal]) -> str: + """Format a Decimal as ``$X`` / ``$X.YY`` for display. + + Whole dollars show no decimals; any fractional amount shows exactly 2dp: + ``Decimal("142.5")`` → ``"$142.50"``, ``Decimal("100")`` → ``"$100"``, + ``Decimal("0.01")`` → ``"$0.01"``. + """ + if value is None: + return "—" + if value == value.to_integral_value(): + # Whole dollars — no decimal point. format(..., "f") avoids 1E+3 for 1000. + return f"${format(value.to_integral_value(), 'f')}" + # Fractional — always show 2dp. + return f"${format(value.quantize(Decimal('0.01')), 'f')}" + + +# ============================================================================= +# Parsed sub-structures +# ============================================================================= + + +@dataclass(frozen=True) +class CardInfo: + brand: str + last4: str + + @property + def masked(self) -> str: + return f"{self.brand} ····{self.last4}" + + +@dataclass(frozen=True) +class MonthlyCap: + limit_usd: Optional[Decimal] = None + spent_this_month_usd: Optional[Decimal] = None + is_default_ceiling: bool = False + + +@dataclass(frozen=True) +class AutoReload: + enabled: bool = False + threshold_usd: Optional[Decimal] = None + reload_to_usd: Optional[Decimal] = None + + +@dataclass(frozen=True) +class BillingState: + """Parsed ``GET /api/billing/state`` — the overview screen's data. + + Fail-open: ``logged_in=False`` (and empty fields) when not logged in or the + portal is unreachable. + """ + + logged_in: bool + org_id: Optional[str] = None + org_slug: Optional[str] = None + org_name: Optional[str] = None + role: Optional[str] = None # "OWNER" | "ADMIN" | "MEMBER" + balance_usd: Optional[Decimal] = None + cli_billing_enabled: bool = False + charge_presets: tuple[Decimal, ...] = () + min_usd: Optional[Decimal] = None + max_usd: Optional[Decimal] = None + card: Optional[CardInfo] = None + monthly_cap: Optional[MonthlyCap] = None + auto_reload: Optional[AutoReload] = None + portal_url: Optional[str] = None + # When the fetch failed (vs cleanly not-logged-in), the message for the surface. + error: Optional[str] = None + + @property + def is_admin(self) -> bool: + """True for OWNER/ADMIN — the roles that can manage billing.""" + return (self.role or "").upper() in ("OWNER", "ADMIN") + + @property + def can_charge(self) -> bool: + """True when the UI should offer charge/auto-reload actions. + + Admin role AND the per-org kill-switch on. (The server still enforces; + this is just for graying out actions the user can't take.) + """ + return self.is_admin and self.cli_billing_enabled + + +def _parse_card(raw: Any) -> Optional[CardInfo]: + if not isinstance(raw, dict): + return None + brand = raw.get("brand") + last4 = raw.get("last4") + if isinstance(brand, str) and isinstance(last4, str): + return CardInfo(brand=brand, last4=last4) + return None + + +def _parse_monthly_cap(raw: Any) -> Optional[MonthlyCap]: + if not isinstance(raw, dict): + return None + return MonthlyCap( + limit_usd=parse_money(raw.get("limitUsd")), + spent_this_month_usd=parse_money(raw.get("spentThisMonthUsd")), + is_default_ceiling=bool(raw.get("isDefaultCeiling")), + ) + + +def _parse_auto_reload(raw: Any) -> Optional[AutoReload]: + if not isinstance(raw, dict): + return None + return AutoReload( + enabled=bool(raw.get("enabled")), + threshold_usd=parse_money(raw.get("thresholdUsd")), + reload_to_usd=parse_money(raw.get("reloadToUsd")), + ) + + +def billing_state_from_payload( + payload: dict[str, Any], *, portal_url: Optional[str] = None +) -> BillingState: + """Map a raw ``/api/billing/state`` JSON dict into :class:`BillingState`.""" + raw_org = payload.get("org") + org: dict[str, Any] = raw_org if isinstance(raw_org, dict) else {} + raw_bounds = payload.get("bounds") + bounds: dict[str, Any] = raw_bounds if isinstance(raw_bounds, dict) else {} + + presets: list[Decimal] = [] + for item in payload.get("chargePresets") or (): + parsed = parse_money(item) + if parsed is not None: + presets.append(parsed) + + return BillingState( + logged_in=True, + org_id=org.get("id"), + org_slug=org.get("slug"), + org_name=org.get("name"), + role=org.get("role"), + balance_usd=parse_money(payload.get("balanceUsd")), + cli_billing_enabled=bool(payload.get("cliBillingEnabled")), + charge_presets=tuple(presets), + min_usd=parse_money(bounds.get("minUsd")), + max_usd=parse_money(bounds.get("maxUsd")), + card=_parse_card(payload.get("card")), + monthly_cap=_parse_monthly_cap(payload.get("monthlyCap")), + auto_reload=_parse_auto_reload(payload.get("autoReload")), + portal_url=portal_url, + ) + + +# ============================================================================= +# Fail-open builders (the surface front doors) +# ============================================================================= + + +def build_billing_state(*, timeout: float = 15.0) -> BillingState: + """Fetch + parse ``/api/billing/state``. Fail-open. + + Returns ``BillingState(logged_in=False)`` when not logged in. On a portal/HTTP + failure, returns ``logged_in=False`` with ``error`` set so the surface can show + a clear message rather than crashing. + """ + try: + from hermes_cli.nous_billing import ( + BillingAuthError, + BillingError, + _absolutize_portal_url, + get_billing_state, + resolve_portal_base_url, + ) + except Exception: + return BillingState(logged_in=False, error="billing client unavailable") + + try: + payload = get_billing_state(timeout=timeout) + except BillingAuthError: + return BillingState(logged_in=False) + except BillingError as exc: + logger.debug("billing ▸ /state fetch failed (fail-open)", exc_info=True) + return BillingState(logged_in=False, error=str(exc)) + except Exception: + logger.debug("billing ▸ /state unexpected error (fail-open)", exc_info=True) + return BillingState(logged_in=False, error="could not load billing state") + + # Prefer a server-supplied portalUrl if present (resolved to absolute in case + # it's relative); else build the standard one. + raw_portal = payload.get("portalUrl") if isinstance(payload, dict) else None + portal_url = _absolutize_portal_url(raw_portal) if raw_portal else None + if not portal_url: + try: + portal_url = _fallback_portal_url(resolve_portal_base_url()) + except Exception: + portal_url = None + + return billing_state_from_payload(payload, portal_url=portal_url) + + +def _fallback_portal_url(base: str) -> str: + """Standard billing deep-link when the server omits ``portalUrl``.""" + return f"{base.rstrip('/')}/billing?topup=open" + + +# ============================================================================= +# Idempotency +# ============================================================================= + + +def new_idempotency_key() -> str: + """Fresh UUID for a user-confirmed purchase (reuse on retry of the SAME buy). + + The ``Idempotency-Key`` header is mandatory on ``POST /charge``; generate one + per confirmed purchase and reuse it across retries so a double-submit collapses + to a single charge. Never reuse a key across different amounts (the server + returns 409 idempotency_conflict). + """ + return str(uuid.uuid4()) + + +# ============================================================================= +# Amount validation (Screen 3 custom input) +# ============================================================================= + + +@dataclass(frozen=True) +class AmountValidation: + ok: bool + amount: Optional[Decimal] = None + error: Optional[str] = None + + +def validate_charge_amount( + raw: str, *, min_usd: Optional[Decimal], max_usd: Optional[Decimal] +) -> AmountValidation: + """Validate a custom charge amount against bounds + 2dp (multipleOf 0.01). + + Mirrors the server's accept/reject so the UI can give instant feedback rather + than round-tripping a sure-to-fail charge. The server is still authoritative. + """ + cleaned = (raw or "").strip().lstrip("$").strip() + amount = parse_money(cleaned) + if amount is None: + return AmountValidation(ok=False, error="Enter a dollar amount, e.g. 100") + if amount <= 0: + return AmountValidation(ok=False, error="Amount must be greater than $0") + # multipleOf 0.01 — reject sub-cent precision. + if amount != amount.quantize(Decimal("0.01")): + return AmountValidation(ok=False, error="Amount can't be smaller than a cent") + if min_usd is not None and amount < min_usd: + return AmountValidation(ok=False, error=f"Minimum is {format_money(min_usd)}") + if max_usd is not None and amount > max_usd: + return AmountValidation(ok=False, error=f"Maximum is {format_money(max_usd)}") + return AmountValidation(ok=True, amount=amount) diff --git a/cli.py b/cli.py index 4ca07fa0bf5..07fa2b72513 100644 --- a/cli.py +++ b/cli.py @@ -1984,6 +1984,24 @@ _ACCENT = _SkinAwareAnsi("response_border", "#FFD700", bold=True) _DIM = "\x1b[2;3m" +def _b(s: str) -> str: + """Bold if stdout is a real TTY; plain text otherwise (slash-worker safe).""" + import sys as _sys + try: + return f"\x1b[1m{s}\x1b[0m" if _sys.stdout.isatty() else str(s) + except Exception: + return str(s) + + +def _d(s: str) -> str: + """Dim-italic if stdout is a real TTY; plain text otherwise.""" + import sys as _sys + try: + return f"\x1b[2;3m{s}\x1b[0m" if _sys.stdout.isatty() else str(s) + except Exception: + return str(s) + + def _accent_hex() -> str: """Return the active skin accent color for legacy CLI output lines.""" try: @@ -3664,7 +3682,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin): if getattr(self, "_resize_recovery_pending", False): return now = time.monotonic() - if hasattr(self, "_app") and self._app and (now - self._last_invalidate) >= min_interval: + if hasattr(self, "_app") and self._app and (now - getattr(self, "_last_invalidate", 0.0)) >= min_interval: self._last_invalidate = now self._app.invalidate() @@ -6359,6 +6377,17 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin): in_main_thread = threading.current_thread() is threading.main_thread() + # Slash-worker guard (#23185 / billing auto-reload hang): when a + # prompt_toolkit app is running but we're on a non-main thread (the + # process_loop / TUI slash-worker daemon thread), stdin is owned by the + # event loop / JSON-RPC pipe. A bare input() there blocks forever until + # the worker's 45s timeout fires. We cannot safely prompt off the main + # thread, so cancel cleanly (None) instead of hanging — mirrors the + # _stdin_fallback discipline in _prompt_text_input_modal. + if self._app and not in_main_thread: + self._invalidate() + return None + if self._app and in_main_thread: from prompt_toolkit.application import run_in_terminal was_visible = self._status_bar_visible @@ -7506,6 +7535,8 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin): self._show_usage() elif canonical == "credits": self._show_credits() + elif canonical == "billing": + self._show_billing(cmd_original) elif canonical == "insights": self._show_insights(cmd_original) elif canonical == "copy": @@ -8425,7 +8456,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin): if not view.logged_in: print() - print(f" 💳 {_DIM}Not logged into Nous Portal.{_RST}") + _cprint(f" 💳 {_d('Not logged into Nous Portal.')}") print(" Run `hermes portal` to log in, then /credits.") return @@ -8487,6 +8518,628 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin): else: print(" 🟡 Cancelled. No credits added.") + # ------------------------------------------------------------------ + # /billing — Phase 2b terminal billing (CLI surface, all 5 screens) + # ------------------------------------------------------------------ + + def _show_billing(self, command: str = "/billing"): + """`/billing` — terminal billing for Nous (one interactive modal). + + ZERO sub-commands: any argument is ignored. Bare ``/billing`` always + opens the Overview (Screen 1), whose numbered menu is the *only* way to + reach the Buy / Auto-reload / Monthly-limit sub-screens. (Per the unified + UX spec §0.4 — ``/billing buy`` etc. are gone; we don't error on a stray + arg, we just open the menu.) + + Interactive CLI uses the prompt_toolkit modal; non-interactive contexts + (TUI slash-worker / no live app) render text + the portal deep-link, never + prompting (the URL is the affordance), same discipline as ``_show_credits``. + All money is Decimal end-to-end; the terminal never collects card details. + """ + from agent.billing_view import build_billing_state + + state = build_billing_state() + if not state.logged_in: + print() + if state.error: + _msg = f"Couldn't load billing: {state.error}" + _cprint(f" 💳 {_d(_msg)}") + else: + _cprint(f" 💳 {_d('Not logged into Nous Portal.')}") + print(" Run `hermes portal` to log in, then /billing.") + return + + # Any sub-arg is intentionally ignored — always open the menu. + self._billing_overview(state) + + def _billing_portal_hint(self, state, *, reason: str = "") -> None: + """Print a portal deep-link line (the funnel for portal-only actions).""" + url = getattr(state, "portal_url", None) + if not url: + return + if reason: + print(f" {reason}") + print(f" Manage on portal: {url}") + + def _billing_overview(self, state): + """Screen 1 — overview: balance, spend bar, role-gated action menu.""" + from agent.billing_view import format_money + + print() + _cprint(f" 💳 {_b('Usage credits')}") + print(f" {'─' * 41}") + + cap = state.monthly_cap + if cap is not None and cap.limit_usd is not None: + spent = format_money(cap.spent_this_month_usd) + limit = format_money(cap.limit_usd) + ceiling = " (default ceiling)" if cap.is_default_ceiling else "" + bar, pct = self._billing_spend_bar( + cap.spent_this_month_usd, cap.limit_usd + ) + print(f" {spent} of {limit} used{ceiling} {bar} {pct}%") + + print(f" Balance: {format_money(state.balance_usd)}") + + ar = state.auto_reload + if ar is not None: + if ar.enabled: + print( + f" Auto-reload: on — below {format_money(ar.threshold_usd)} " + f"→ reload to {format_money(ar.reload_to_usd)}" + ) + else: + print(" Auto-reload: off") + + if state.org_name: + role = (state.role or "").title() + _org_line = f"Org: {state.org_name}{f' · {role}' if role else ''}" + _cprint(f" {_d(_org_line)}") + print(f" {'─' * 41}") + + # Action gating: admin + kill-switch for charge/auto-reload; everyone gets portal. + if not state.is_admin: + _cprint(f" {_d('Billing actions require an org admin/owner.')}") + self._billing_portal_hint(state) + return + if not state.cli_billing_enabled: + _cprint(f" {_d('Terminal billing is turned off for this org.')}") + self._billing_portal_hint(state, reason="Enable it on the portal to buy credits here.") + return + + # Optimistic funnel: no card on file → a charge will 403 no_payment_method. + # Surface that up front (with the portal link) but DON'T hide Buy — /state.card + # can't fully prove CLI-chargeability, so we advise rather than gate. + if state.card is None: + _cprint( + f" {_d('No saved card for terminal charges yet — set one up on the portal first.')}" + ) + self._billing_portal_hint(state) + + # Non-interactive (slash-worker / no live app): no modal, no sub-command + # advertising — just the portal funnel (the URL is the affordance). + if not getattr(self, "_app", None): + self._billing_portal_hint(state) + return + + choices = [ + ("buy", "Buy credits", "purchase a one-time credit top-up"), + ("auto", "Adjust auto-reload", "configure automatic top-ups"), + ("limit", "Adjust monthly limit", "show the monthly spend cap (read-only)"), + ("portal", "Manage on portal", "open the billing page in your browser"), + ("cancel", "Cancel", "do nothing"), + ] + # The overview summary is already printed above; the modal only needs to + # present the action menu — repeating the title/balance reads as a dupe. + raw = self._prompt_text_input_modal( + title="💳 Choose an action", detail="", + choices=choices, + ) + choice = self._normalize_slash_confirm_choice(raw, choices) + if choice == "buy": + self._billing_buy_flow(state) + elif choice == "auto": + self._billing_auto_reload_flow(state) + elif choice == "limit": + self._billing_limit_screen(state) + elif choice == "portal": + self._billing_open_portal(state) + else: + print(" 🟡 Cancelled.") + + def _billing_spend_bar(self, spent, limit, *, cells: int = 10): + """Render a 10-cell `█`/`░` spend bar + integer percent from spent/limit. + + Returns ``(bar, pct)`` where ``bar`` is like ``[████░░░░░░]`` and ``pct`` + is the spent/limit percentage clamped to 0..100. Box-drawing glyphs are + not SGR codes, so this is leak-safe even without ``_b()``/``_d()``. + """ + from decimal import Decimal + + try: + s = Decimal(str(spent)) if spent is not None else Decimal("0") + l = Decimal(str(limit)) if limit is not None else Decimal("0") + except Exception: + s, l = Decimal("0"), Decimal("0") + if l <= 0: + pct = 0 + else: + pct = int((s / l) * 100) + pct = max(0, min(100, pct)) + filled = int(round(pct / 100 * cells)) + filled = max(0, min(cells, filled)) + bar = ("█" * filled) + ("░" * (cells - filled)) + return bar, pct + + def _billing_open_portal(self, state): + url = getattr(state, "portal_url", None) + if not url: + print(" No portal URL available.") + return + opened = False + try: + import webbrowser + + opened = webbrowser.open(url) + except Exception: + opened = False + if not opened: + print(f" Open this URL: {url}") + print(" Complete billing changes in the browser.") + + def _billing_require_admin(self, state) -> bool: + """Guard charge/auto-reload entry points; print + return False if blocked.""" + if not state.is_admin: + print() + _cprint(f" 💳 {_d('Billing actions require an org admin/owner.')}") + self._billing_portal_hint(state) + return False + if not state.cli_billing_enabled: + print() + _cprint(f" 💳 {_d('Terminal billing is turned off for this org.')}") + self._billing_portal_hint(state, reason="Enable it on the portal first.") + return False + return True + + def _billing_buy_flow(self, state): + """Screen 2 (preset select) → Screen 3 (confirm + charge + poll).""" + from agent.billing_view import format_money, validate_charge_amount + + if not self._billing_require_admin(state): + return + + # Screen 3 — preset selection. + if not getattr(self, "_app", None): + presets = ", ".join(format_money(p) for p in state.charge_presets) + print() + _cprint(f" 💳 {_b('Buy usage credits')}") + print(f" Presets: {presets}") + print(" Run this in the interactive CLI to complete a purchase.") + self._billing_portal_hint(state) + return + + preset_choices = [] + for p in state.charge_presets: + preset_choices.append((str(p), format_money(p), "one-time credit purchase")) + preset_choices.append(("custom", "Custom amount…", "enter your own amount")) + preset_choices.append(("cancel", "Cancel", "do nothing")) + + card = state.card + detail = f"Payment: {card.masked}" if card else "No saved card on file" + raw = self._prompt_text_input_modal( + title="💳 Buy usage credits", detail=detail, choices=preset_choices, + ) + choice = self._normalize_slash_confirm_choice(raw, preset_choices) + if not choice or choice == "cancel": + print(" 🟡 Cancelled. No credits added.") + return + + from decimal import Decimal + + if choice == "custom": + entered = self._prompt_text_input(" Amount (USD): ") + if entered is None: + # None = cancelled (e.g. slash-worker can't prompt off-thread). + print(" 🟡 Cancelled. No credits added.") + return + v = validate_charge_amount( + entered or "", min_usd=state.min_usd, max_usd=state.max_usd + ) + if not v.ok: + print(f" 🔴 {v.error}") + return + amount = v.amount + else: + try: + amount = Decimal(choice) + except Exception: + print(" 🔴 Invalid selection.") + return + + self._billing_confirm_and_charge(state, amount) + + def _billing_confirm_and_charge(self, state, amount): + """Screen 3 — confirm total + consent, charge, then poll to settlement.""" + from agent.billing_view import format_money, new_idempotency_key + + card = state.card + print() + _cprint(f" 💳 {_b('Confirm purchase')}") + print(f" {'─' * 41}") + print(f" Total: {format_money(amount)}") + if card: + print(f" Payment: {card.masked}") + print(f" {'─' * 41}") + _consent = ( + "By confirming, you allow Nous Research to charge your card." + ) + _cprint(f" {_d(_consent)}") + + confirm_choices = [ + ("pay", f"Pay {format_money(amount)} now", "submit the charge"), + ("cancel", "Go back", "do not charge"), + ] + if not getattr(self, "_app", None): + print(" Run in the interactive CLI to confirm a purchase.") + return + raw = self._prompt_text_input_modal( + title=f"💳 Pay {format_money(amount)}?", + detail=(card.masked if card else "no saved card"), + choices=confirm_choices, + ) + choice = self._normalize_slash_confirm_choice(raw, confirm_choices) + if choice != "pay": + print(" 🟡 Cancelled. No credits added.") + return + + # Submit the charge with a fresh idempotency key (reused on retry). + from hermes_cli.nous_billing import ( + BillingError, + BillingScopeRequired, + post_charge, + ) + + key = new_idempotency_key() + try: + result = post_charge(amount_usd=amount, idempotency_key=key) + except BillingScopeRequired: + self._billing_handle_scope_required(state) + return + except BillingError as exc: + self._billing_render_charge_error(state, exc) + return + + charge_id = result.get("chargeId") + if not charge_id: + print(" 🔴 No charge id returned; please check the portal.") + return + _cprint(f" {_d('Charge submitted — confirming settlement…')}") + self._billing_poll_charge(state, charge_id, amount) + + def _billing_poll_charge(self, state, charge_id, amount): + """Poll loop: 2s interval, 5-min cap, cancellable. settled = ledger truth.""" + import time as _time + + from agent.billing_view import format_money + from hermes_cli.nous_billing import ( + BillingError, + BillingRateLimited, + get_charge_status, + ) + + deadline = _time.time() + 300 # 5-minute cap + interval = 2.0 + while _time.time() < deadline: + try: + status = get_charge_status(charge_id) + except BillingRateLimited as exc: + # Retry-after, NOT a failure — back off and keep polling. + wait = exc.retry_after or 5 + _time.sleep(min(wait, 30)) + continue + except BillingError as exc: + print(f" 🔴 Could not check the charge: {exc}") + return + + state_str = status.get("status") + if state_str == "settled": + amt = status.get("amountUsd") + from agent.billing_view import parse_money + + shown = format_money(parse_money(amt)) if amt else format_money(amount) + print(f" ✅ {shown} in credits added.") + return + if state_str == "failed": + self._billing_render_charge_failed(state, status.get("reason")) + return + # pending → wait and poll again + _time.sleep(interval) + + # Past the cap with no terminal state = timeout (not an error). + print(f" 🟡 Still processing after 5 minutes — this is a timeout, not a " + f"failure. Check /billing or the portal shortly.") + self._billing_portal_hint(state) + + def _billing_render_charge_failed(self, state, reason): + """Branch the poll `failed` reasons to the right copy + portal funnel.""" + reason = (reason or "").strip() + if reason == "authentication_required": + print(" 🔴 Your bank requires verification (3DS). Complete it on the " + "portal to finish this purchase.") + elif reason == "payment_method_expired": + print(" 🔴 Your card has expired. Update it on the portal.") + elif reason == "card_declined": + print(" 🔴 Your card was declined. Try another card on the portal.") + else: + print(f" 🔴 The charge didn't go through ({reason or 'processing_error'}).") + self._billing_portal_hint(state) + + def _billing_render_charge_error(self, state, exc): + """Render a typed BillingError at submit time (pre-poll).""" + from hermes_cli.nous_billing import BillingRateLimited + + code = getattr(exc, "error", None) + portal_url = getattr(exc, "portal_url", None) or getattr(state, "portal_url", None) + if code == "no_payment_method": + print(" 💳 No saved card for terminal charges yet. Set one up on the " + "portal (one-time credit buys don't save a reusable card).") + elif code == "cli_billing_disabled": + print(" 🔴 Terminal billing is turned off for this org — an admin must enable it on the portal.") + elif code == "monthly_cap_exceeded": + remaining = (getattr(exc, "payload", {}) or {}).get("remainingUsd") + if remaining is not None: + print(f" 🔴 Monthly spend cap reached — ${remaining} headroom left.") + else: + print(" 🔴 Monthly spend cap reached.") + elif isinstance(exc, BillingRateLimited): + wait = getattr(exc, "retry_after", None) + mins = f" (try again in ~{max(1, round(wait / 60))} min)" if wait else "" + print(f" 🟡 Too many charges right now{mins}. This isn't a payment failure.") + else: + print(f" 🔴 {exc}") + if portal_url: + print(f" Portal: {portal_url}") + + def _billing_handle_scope_required(self, state): + """403 insufficient_scope → lazy step-up re-auth (plan D-A).""" + print() + print(" 💳 Terminal billing needs an extra permission (billing:manage).") + _scope_msg = ( + "An org admin/owner must tick \"Allow terminal billing\" during " + "login." + ) + _cprint(f" {_d(_scope_msg)}") + if not getattr(self, "_app", None): + print(" Run `hermes portal` and approve terminal billing, then retry.") + return + confirm_choices = [ + ("yes", "Re-authorize now", "open the portal to grant billing access"), + ("no", "Not now", "cancel"), + ] + raw = self._prompt_text_input_modal( + title="💳 Grant terminal billing access?", + detail="Opens the portal device-authorization page.", + choices=confirm_choices, + ) + choice = self._normalize_slash_confirm_choice(raw, confirm_choices) + if choice != "yes": + print(" 🟡 Cancelled.") + return + try: + from hermes_cli.auth import step_up_nous_billing_scope + + granted = step_up_nous_billing_scope(open_browser=True) + except Exception as exc: + print(f" 🔴 Re-authorization failed: {exc}") + return + if granted: + print(" ✅ Billing permission granted.") + # Step-up only grants the billing:manage TOKEN scope; the ORG + # kill-switch (cli_billing_enabled) is a separate gate. Re-fetch + # /state so we don't over-promise when a charge would still hit + # cli_billing_disabled. + from agent.billing_view import build_billing_state + + fresh = build_billing_state() + if fresh.logged_in and fresh.cli_billing_enabled: + print(" Run /billing buy again to continue.") + else: + print(" 🟡 Permission granted, but terminal billing is still turned " + "off for this org. Enable it in the portal, then run /billing again.") + self._billing_portal_hint(fresh) + else: + print(" 🟡 Terminal billing was not granted (an admin must tick the box).") + + def _billing_auto_reload_flow(self, state): + """Screen 4 — auto-reload config: threshold + reload-to → PATCH. + + Prefills the current values from ``state.auto_reload``. Validates both + amounts (2dp, within bounds, ``reload_to > threshold``). When auto-reload + is already on, offers a "Turn off" path (PATCH ``enabled:false``). + """ + from agent.billing_view import format_money, validate_charge_amount + + if not self._billing_require_admin(state): + return + + card = state.card + ar = state.auto_reload + currently_on = bool(ar and ar.enabled) + + print() + _cprint(f" 💳 {_b('Auto-reload')}") + print(f" {'─' * 41}") + _cprint(f" {_d('Automatically buy more credits when your balance is low.')}") + if card: + print(f" Card on file: {card.masked}") + else: + print(" No saved card — set one up on the portal first.") + self._billing_portal_hint(state) + return + if currently_on: + print( + f" Currently: below {format_money(ar.threshold_usd)} → " + f"reload to {format_money(ar.reload_to_usd)}" + ) + + if not getattr(self, "_app", None): + print(" Run in the interactive CLI to configure auto-reload.") + self._billing_portal_hint(state) + return + + # When already enabled, let the user turn it off without re-entering values. + if currently_on: + top_choices = [ + ("edit", "Edit thresholds", "change when / how much to reload"), + ("off", "Turn off", "disable auto-reload"), + ("cancel", "Cancel", "do nothing"), + ] + raw = self._prompt_text_input_modal( + title="💳 Auto-reload", + detail=( + f"On — below {format_money(ar.threshold_usd)} → " + f"reload to {format_money(ar.reload_to_usd)}" + ), + choices=top_choices, + ) + top = self._normalize_slash_confirm_choice(raw, top_choices) + if top == "off": + self._billing_auto_reload_disable(state) + return + if top != "edit": + print(" 🟡 Cancelled.") + return + + # Field 1 — threshold (prefilled when editing an existing config). + cur_thr = format_money(ar.threshold_usd) if currently_on else None + thr_prompt = " When balance falls below (USD)" + thr_prompt += f" [{cur_thr}]: " if cur_thr else ": " + threshold_raw = self._prompt_text_input(thr_prompt) + if threshold_raw is None: + # None = cancelled (e.g. slash-worker can't prompt off-thread). + print(" 🟡 Cancelled.") + return + if not (threshold_raw or "").strip() and currently_on: + threshold_amt = ar.threshold_usd # keep current value on empty input + else: + tv = validate_charge_amount( + threshold_raw or "", min_usd=state.min_usd, max_usd=state.max_usd + ) + if not tv.ok or tv.amount is None: + print(f" 🔴 {tv.error}") + return + threshold_amt = tv.amount + + # Field 2 — reload-to (prefilled when editing an existing config). + cur_rel = format_money(ar.reload_to_usd) if currently_on else None + rel_prompt = " Reload balance to (USD)" + rel_prompt += f" [{cur_rel}]: " if cur_rel else ": " + reload_raw = self._prompt_text_input(rel_prompt) + if reload_raw is None: + print(" 🟡 Cancelled.") + return + if not (reload_raw or "").strip() and currently_on: + reload_amt = ar.reload_to_usd # keep current value on empty input + else: + rv = validate_charge_amount( + reload_raw or "", min_usd=state.min_usd, max_usd=state.max_usd + ) + if not rv.ok or rv.amount is None: + print(f" 🔴 {rv.error}") + return + reload_amt = rv.amount + + if reload_amt is None or threshold_amt is None or reload_amt <= threshold_amt: + print(" 🔴 Reload-to amount must be greater than the threshold.") + return + + print() + _ar_consent = ( + f"By confirming, you authorize Nous Research to charge {card.masked} " + f"whenever your balance reaches {format_money(threshold_amt)}. " + f"Turn off any time here or on the portal." + ) + _cprint(f" {_d(_ar_consent)}") + confirm_choices = [ + ("agree", "Agree and turn on", "enable auto-reload"), + ("cancel", "Cancel", "do nothing"), + ] + raw = self._prompt_text_input_modal( + title="💳 Turn on auto-reload?", + detail=f"Below {format_money(threshold_amt)} → reload to {format_money(reload_amt)}", + choices=confirm_choices, + ) + choice = self._normalize_slash_confirm_choice(raw, confirm_choices) + if choice != "agree": + print(" 🟡 Cancelled.") + return + + from hermes_cli.nous_billing import ( + BillingError, + BillingScopeRequired, + patch_auto_top_up, + ) + + try: + patch_auto_top_up( + enabled=True, threshold=float(threshold_amt), top_up_amount=float(reload_amt) + ) + except BillingScopeRequired: + self._billing_handle_scope_required(state) + return + except BillingError as exc: + self._billing_render_charge_error(state, exc) + return + print(f" ✅ Auto-reload on: below {format_money(threshold_amt)} → " + f"reload to {format_money(reload_amt)}.") + + def _billing_auto_reload_disable(self, state): + """Turn off auto-reload (PATCH ``enabled:false``). + + The endpoint requires ``threshold``/``topUpAmount`` in the body even when + disabling, so we echo back the current values (falling back to 0). + """ + from hermes_cli.nous_billing import ( + BillingError, + BillingScopeRequired, + patch_auto_top_up, + ) + + ar = state.auto_reload + thr = float(ar.threshold_usd) if ar and ar.threshold_usd is not None else 0.0 + rel = float(ar.reload_to_usd) if ar and ar.reload_to_usd is not None else 0.0 + try: + patch_auto_top_up(enabled=False, threshold=thr, top_up_amount=rel) + except BillingScopeRequired: + self._billing_handle_scope_required(state) + return + except BillingError as exc: + self._billing_render_charge_error(state, exc) + return + print(" ✅ Auto-reload turned off.") + + def _billing_limit_screen(self, state): + """Screen 5 — monthly spend limit (read-only; cap is portal-only).""" + from agent.billing_view import format_money + + print() + _cprint(f" 💳 {_b('Monthly spend limit')}") + print(f" {'─' * 41}") + cap = state.monthly_cap + if cap is None or cap.limit_usd is None: + _cprint(f" {_d('No monthly cap visible (managed on the portal).')}") + else: + spent = format_money(cap.spent_this_month_usd) + limit = format_money(cap.limit_usd) + ceiling = " (default ceiling)" if cap.is_default_ceiling else "" + print(f" {spent} of {limit} used this month{ceiling}") + _limit_note = ( + "The monthly limit is set on the portal — the terminal shows " + "it read-only." + ) + _cprint(f" {_d(_limit_note)}") + self._billing_portal_hint(state) + def _show_insights(self, command: str = "/insights"): """Show usage insights and analytics from session history.""" # Parse optional --days flag diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 61c2bbed786..d0c70a48def 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -71,6 +71,7 @@ DEFAULT_NOUS_PORTAL_URL = "https://portal.nousresearch.com" DEFAULT_NOUS_INFERENCE_URL = "https://inference-api.nousresearch.com/v1" DEFAULT_NOUS_CLIENT_ID = "hermes-cli" NOUS_INFERENCE_INVOKE_SCOPE = "inference:invoke" +NOUS_BILLING_MANAGE_SCOPE = "billing:manage" DEFAULT_NOUS_SCOPE = NOUS_INFERENCE_INVOKE_SCOPE NOUS_DEVICE_CODE_SOURCE = "device_code" NOUS_AUTH_PATH_INVOKE_JWT = "invoke_jwt" @@ -7865,6 +7866,7 @@ def _nous_device_code_login( timeout_seconds: float = 15.0, insecure: bool = False, ca_bundle: Optional[str] = None, + on_verification: Optional[Callable[[str, str], None]] = None, ) -> Dict[str, Any]: """Run the Nous device-code flow and return full OAuth state without persisting.""" pconfig = PROVIDER_REGISTRY["nous"] @@ -7919,6 +7921,16 @@ def _nous_device_code_login( else: print(" Could not open browser automatically — use the URL above.") + # Surface the verification URL/code to an out-of-band consumer (e.g. the + # TUI gateway, whose stdout is a JSON-RPC pipe — a plain print() there is + # dropped). Fired AFTER the print/browser block and BEFORE polling blocks, + # so the consumer can render the link while we wait. Best-effort. + if on_verification is not None: + try: + on_verification(verification_url, user_code) + except Exception: + pass + effective_interval = max(1, min(interval, DEVICE_AUTH_POLL_INTERVAL_CAP_SECONDS)) print(f"Waiting for approval (polling every {effective_interval}s)...") @@ -7984,6 +7996,91 @@ def _nous_device_code_login( raise +def nous_token_has_billing_scope() -> bool: + """Return True if the currently-held Nous token carries ``billing:manage``. + + Reads the persisted ``scope`` string saved at login (``_save_provider_state`` + stores ``token_data.get("scope") or scope``). A space-delimited match. Used by + the lazy step-up: if False, the first billing call will 403 ``insufficient_scope`` + anyway, but checking up front lets a surface skip a doomed round-trip. + """ + try: + state = get_provider_auth_state("nous") or {} + except Exception: + return False + scope = state.get("scope") + if not isinstance(scope, str): + return False + return NOUS_BILLING_MANAGE_SCOPE in scope.split() + + +def step_up_nous_billing_scope( + *, + open_browser: bool = True, + timeout_seconds: float = 15.0, + on_verification: Optional[Callable[[str, str], None]] = None, +) -> bool: + """Re-run the device flow requesting ``billing:manage`` and persist the result. + + The lazy step-up (plan D-A): triggered when a billing endpoint returns + ``403 insufficient_scope``. Runs a fresh device-connect with + ``inference:invoke tool:invoke billing:manage`` on the scope. The user must be + an ADMIN/OWNER and tick "Allow terminal billing" in the portal for the minted + token to actually carry the scope; otherwise the server silently downscopes and this + returns False. + + Reuses the held credential's portal/inference URLs + client_id so the step-up + targets the same deployment (incl. a preview via ``HERMES_PORTAL_BASE_URL`` set + at the original login). Persists to the auth store + shared store + pool, exactly + like ``_login_nous`` — but WITHOUT the model picker (this is a scope upgrade, not + a fresh login). + + Returns True iff the new token carries ``billing:manage``. + """ + prior = get_provider_auth_state("nous") or {} + pconfig = PROVIDER_REGISTRY["nous"] + + # Build the step-up scope: existing scopes (if any) + billing:manage, deduped, + # order-stable. Fall back to the standard inference+tool+billing set. + _raw_scope = prior.get("scope") + prior_scope = _raw_scope if isinstance(_raw_scope, str) else "" + requested: list[str] = [] + for tok in (prior_scope.split() or [NOUS_INFERENCE_INVOKE_SCOPE, "tool:invoke"]): + if tok and tok not in requested: + requested.append(tok) + if NOUS_BILLING_MANAGE_SCOPE not in requested: + requested.append(NOUS_BILLING_MANAGE_SCOPE) + scope = " ".join(requested) + + auth_state = _nous_device_code_login( + portal_base_url=prior.get("portal_base_url") or None, + inference_base_url=prior.get("inference_base_url") or None, + client_id=prior.get("client_id") or pconfig.client_id, + scope=scope, + open_browser=open_browser, + timeout_seconds=timeout_seconds, + on_verification=on_verification, + ) + + with _auth_store_lock(): + auth_store = _load_auth_store() + _save_provider_state(auth_store, "nous", auth_state) + _save_auth_store(auth_store) + + # Mirror to shared store + reseed the pool (best-effort), same as _login_nous. + try: + _write_shared_nous_state(auth_state) + except Exception: + pass + try: + _sync_nous_pool_from_auth_store() + except Exception: + pass + + granted = auth_state.get("scope") + return isinstance(granted, str) and NOUS_BILLING_MANAGE_SCOPE in granted.split() + + def _login_nous(args, pconfig: ProviderConfig) -> None: """Nous Portal device authorization flow.""" timeout_seconds = getattr(args, "timeout", None) or 15.0 diff --git a/hermes_cli/commands.py b/hermes_cli/commands.py index f81d50eace9..514e7f659b3 100644 --- a/hermes_cli/commands.py +++ b/hermes_cli/commands.py @@ -215,6 +215,7 @@ COMMAND_REGISTRY: list[CommandDef] = [ gateway_only=True), CommandDef("usage", "Show token usage and rate limits for the current session", "Info"), CommandDef("credits", "Show Nous credit balance and top up", "Info"), + CommandDef("billing", "Manage Nous terminal billing — buy credits, auto-reload, limits", "Info"), CommandDef("insights", "Show usage insights and analytics", "Info", args_hint="[days]"), CommandDef("platforms", "Show gateway/messaging platform status", "Info", @@ -1053,8 +1054,9 @@ _SLACK_PRIORITY_ALIASES = ("btw", "bg") # the telegram-parity test reads it so an entry here is a deliberate # "Slack-via-/hermes" decision, not a silent clamp. # - credits: the billing/top-up surface; reached via /hermes credits on Slack. +# - billing: the terminal-billing surface (buy/auto-reload/limit); /hermes billing. # - debug: the log/report upload surface; reached via /hermes debug on Slack. -_SLACK_VIA_HERMES_ONLY = frozenset({"credits", "debug"}) +_SLACK_VIA_HERMES_ONLY = frozenset({"credits", "billing", "debug"}) def _sanitize_slack_name(raw: str) -> str: diff --git a/hermes_cli/nous_billing.py b/hermes_cli/nous_billing.py new file mode 100644 index 00000000000..8bca89ed36c --- /dev/null +++ b/hermes_cli/nous_billing.py @@ -0,0 +1,406 @@ +"""Nous Portal terminal-billing HTTP client (Phase 2b). + +Thin, fail-loud client for the four ``/api/billing/*`` endpoints the terminal +billing screens drive. Companion to ``hermes_cli/nous_account.py`` (which owns +read-only entitlement/balance) — this module owns the *write* side: buy credits, +poll a charge, configure auto-reload. + +Design rules: + +- **Money is decimal, never float.** The server emits decimal STRINGS + (``"142.5"`` — not fixed 2dp). We parse with :class:`decimal.Decimal` and never + round-trip through float. +- **This client raises typed exceptions; it does NOT fail open.** Fail-open is the + *caller's* job (the ``agent/billing_view.py`` builders) so each surface can + decide how to degrade. A raw network/HTTP error here surfaces as + :class:`BillingError` (or a subclass) carrying the parsed server ``error`` code, + HTTP status, ``portalUrl`` deep-link, and ``retry_after``. +- **Auth** = the OAuth bearer JWT Hermes already holds for inference + (``get_provider_auth_state("nous")["access_token"]``). No API-key auth on these. +- **Portal base URL** resolves with the same precedence as the device-flow login + (``auth.py``): ``HERMES_PORTAL_BASE_URL`` → ``NOUS_PORTAL_BASE_URL`` → the + stored auth-state ``portal_base_url`` → the registry default. This is how the + E2E run points the client at a preview deployment with zero code change. +""" + +from __future__ import annotations + +import json +import os +import urllib.error +import urllib.parse +import urllib.request +from typing import Any, Optional + +DEFAULT_PORTAL_BASE_URL = "https://portal.nousresearch.com" + +# Default HTTP timeout (seconds). Charge/poll calls are quick; keep this tight so +# a hung portal doesn't freeze the TUI. +DEFAULT_TIMEOUT = 15.0 + +# Scope the privileged billing endpoints require. Mirrored from +# hermes_cli.auth.NOUS_BILLING_MANAGE_SCOPE (kept here too so this module has no +# import-time dependency on the much heavier auth module). +BILLING_MANAGE_SCOPE = "billing:manage" + + +# ============================================================================= +# Typed errors +# ============================================================================= + + +class BillingError(Exception): + """A billing HTTP call failed. + + Carries everything a surface needs to render the right message + affordance: + the server ``error`` code, HTTP ``status``, an optional human ``message``, the + ``portalUrl`` deep-link (present on every gate denial), and ``retry_after`` + seconds (429/503). ``payload`` is the full parsed JSON body when available. + """ + + def __init__( + self, + message: str, + *, + status: Optional[int] = None, + error: Optional[str] = None, + portal_url: Optional[str] = None, + retry_after: Optional[int] = None, + payload: Optional[dict[str, Any]] = None, + ) -> None: + super().__init__(message) + self.status = status + self.error = error + self.portal_url = portal_url + self.retry_after = retry_after + self.payload = payload or {} + + +class BillingScopeRequired(BillingError): + """``403 insufficient_scope`` — the held token lacks ``billing:manage``. + + The lazy step-up trigger: catching this kicks off a fresh device-connect that + requests ``billing:manage`` (and tells the user an ADMIN must tick "Allow + terminal billing"). Also fires mid-session if the scope is stripped on refresh + after the user loses ADMIN. + """ + + +class BillingRateLimited(BillingError): + """``429 rate_limited`` or ``503 temporarily_unavailable``. + + NOT a payment failure. Carries ``retry_after`` (seconds) — back off and tell + the user "try again in N min"; never auto-retry-spam (the limiter is + 5/org/hr + 5/token/hr and easy to dig deeper into). + """ + + +class BillingAuthError(BillingError): + """``401`` — missing/invalid bearer token (not logged in / expired).""" + + +# ============================================================================= +# Base-URL + auth resolution +# ============================================================================= + + +def resolve_portal_base_url(state: Optional[dict[str, Any]] = None) -> str: + """Resolve the portal base URL with login-time precedence. + + ``HERMES_PORTAL_BASE_URL`` → ``NOUS_PORTAL_BASE_URL`` → stored auth-state + ``portal_base_url`` → registry default. Trailing slash stripped. + """ + env = os.getenv("HERMES_PORTAL_BASE_URL") or os.getenv("NOUS_PORTAL_BASE_URL") + if env and env.strip(): + return env.strip().rstrip("/") + if state: + stored = state.get("portal_base_url") + if isinstance(stored, str) and stored.strip(): + return stored.strip().rstrip("/") + return DEFAULT_PORTAL_BASE_URL + + +def _absolutize_portal_url(portal_url: Optional[str]) -> Optional[str]: + """Resolve a (possibly relative) server portalUrl to an absolute URL. + + The server emits ``portalUrl`` relative by design (e.g. ``/billing?topup=open``) + — it doesn't know which deployment the client points at. Resolve it against the + client's portal base (preview / staging / prod) so deep-links are clickable. + Idempotent: an already-absolute URL is returned unchanged (urljoin keeps it). + """ + if not (isinstance(portal_url, str) and portal_url.strip()): + return portal_url + base = resolve_portal_base_url() + # urljoin needs a trailing slash on the base to treat it as a directory and + # join an absolute path like "/billing?..." against the host. An already- + # absolute portal_url (with its own scheme/host) is returned as-is. + return urllib.parse.urljoin(base.rstrip("/") + "/", portal_url) + + +# Short-lived cache for the resolved (token, base). `resolve_nous_access_token` +# acquires two cross-process file locks + reads two files on every call (even on +# its fast path), which is wasteful when the 2s/5-min charge poll loop calls a +# billing endpoint ~150x per purchase. Cache the result briefly: the resolver +# only ever returns a token with >=120s of life (its refresh skew), so a 30s +# cache can never hand back an about-to-expire token. A 401 still surfaces +# normally (the cache holds a valid token, not the HTTP outcome). +_TOKEN_CACHE_TTL_SECONDS = 30.0 +_token_cache: tuple[float, str, str] | None = None # (cached_at, token, base) + + +def _billing_not_logged_in(exc: Optional[BaseException] = None) -> "BillingAuthError": + """Build the canonical 'not logged in' BillingAuthError (single source).""" + err = BillingAuthError( + "Not logged into Nous Portal — run `hermes portal` to log in.", + status=401, + error="invalid_token", + ) + if exc is not None: + err.__cause__ = exc + return err + + +def _resolve_token_and_base(*, use_cache: bool = True) -> tuple[str, str]: + """Return ``(access_token, portal_base_url)`` for billing calls. + + Uses the same refresh-aware resolver the inference path uses + (``resolve_nous_access_token``), so a short-lived (~15 min) access token that + has expired is transparently refreshed via the stored ``refresh_token`` + instead of failing as "not logged in". Raises :class:`BillingAuthError` only + when there is no usable Nous session at all. + + The result is cached for ``_TOKEN_CACHE_TTL_SECONDS`` to keep the charge poll + loop from re-locking + re-reading the auth store on every 2s tick. Pass + ``use_cache=False`` to force a fresh resolution (e.g. after a 401). + """ + global _token_cache + import time as _time + + if use_cache and _token_cache is not None: + cached_at, token, base = _token_cache + if (_time.time() - cached_at) < _TOKEN_CACHE_TTL_SECONDS: + return token, base + + try: + from hermes_cli.auth import get_provider_auth_state + + state = get_provider_auth_state("nous") or {} + except Exception: + state = {} + + base = resolve_portal_base_url(state) + + try: + from hermes_cli.auth import AuthError, resolve_nous_access_token + except ImportError: + # auth module unavailable — fall back to the raw stored token. + token = state.get("access_token") + if isinstance(token, str) and token.strip(): + resolved = (token.strip(), base) + _token_cache = (_time.time(), *resolved) + return resolved + raise _billing_not_logged_in() + + try: + token = resolve_nous_access_token() + except AuthError as exc: + raise _billing_not_logged_in(exc) from exc + resolved = (token.strip(), base) + _token_cache = (_time.time(), *resolved) + return resolved + + +# ============================================================================= +# HTTP plumbing +# ============================================================================= + + +def _retry_after_seconds(headers: Any) -> Optional[int]: + """Parse a ``Retry-After`` header (integer seconds) — None if absent/bad.""" + if headers is None: + return None + try: + raw = headers.get("Retry-After") + except Exception: + raw = None + if raw is None: + return None + try: + return int(str(raw).strip()) + except (TypeError, ValueError): + return None + + +def _raise_for_error( + status: int, payload: dict[str, Any], headers: Any = None +) -> None: + """Map an HTTP error response to the right typed :class:`BillingError`.""" + error = payload.get("error") if isinstance(payload, dict) else None + message = payload.get("message") if isinstance(payload, dict) else None + portal_url = _absolutize_portal_url( + payload.get("portalUrl") if isinstance(payload, dict) else None + ) + retry_after = _retry_after_seconds(headers) + + common = { + "status": status, + "error": error, + "portal_url": portal_url, + "retry_after": retry_after, + "payload": payload if isinstance(payload, dict) else None, + } + + if status == 401: + raise BillingAuthError(message or "Authentication required.", **common) + if status == 403 and error == "insufficient_scope": + raise BillingScopeRequired( + message or "This action needs the billing:manage scope.", **common + ) + if status in (429, 503): + raise BillingRateLimited( + message or "Rate limited — try again shortly.", **common + ) + raise BillingError(message or error or f"Billing request failed ({status}).", **common) + + +def _request( + method: str, + path: str, + *, + body: Optional[dict[str, Any]] = None, + extra_headers: Optional[dict[str, str]] = None, + timeout: float = DEFAULT_TIMEOUT, + _retried_auth: bool = False, +) -> dict[str, Any]: + """Make an authenticated billing request; return the parsed JSON dict. + + Raises a typed :class:`BillingError` on any non-2xx response (or transport + failure). 2xx with an empty body returns ``{}``. A 401 triggers exactly one + retry with a freshly-resolved token (bypassing the short token cache) so a + cached-but-just-expired token self-heals instead of failing the call. + """ + token, base = _resolve_token_and_base(use_cache=not _retried_auth) + url = f"{base}{path}" + headers = { + "Authorization": f"Bearer {token}", + "Accept": "application/json", + } + if body is not None: + headers["Content-Type"] = "application/json" + if extra_headers: + headers.update(extra_headers) + + data = json.dumps(body).encode("utf-8") if body is not None else None + req = urllib.request.Request(url, data=data, headers=headers, method=method) + + try: + with urllib.request.urlopen(req, timeout=timeout) as resp: + raw = resp.read().decode("utf-8") + return json.loads(raw) if raw.strip() else {} + except urllib.error.HTTPError as exc: + # A 401 on a cached token → drop the cache and retry once with a fresh + # (refresh-aware) resolve before surfacing the auth error. + if exc.code == 401 and not _retried_auth: + global _token_cache + _token_cache = None + return _request( + method, + path, + body=body, + extra_headers=extra_headers, + timeout=timeout, + _retried_auth=True, + ) + raw = "" + try: + raw = exc.read().decode("utf-8") + except Exception: + raw = "" + try: + payload = json.loads(raw) if raw.strip() else {} + except json.JSONDecodeError: + payload = {} + _raise_for_error(exc.code, payload, getattr(exc, "headers", None)) + raise # unreachable; _raise_for_error always raises + except urllib.error.URLError as exc: + raise BillingError( + f"Could not reach Nous Portal: {exc.reason}", error="network_error" + ) from exc + + +# ============================================================================= +# The four endpoints +# ============================================================================= + + +def get_billing_state(*, timeout: float = DEFAULT_TIMEOUT) -> dict[str, Any]: + """``GET /api/billing/state`` — role-tiered overview (no scope required).""" + return _request("GET", "/api/billing/state", timeout=timeout) + + +def patch_auto_top_up( + *, + enabled: bool, + threshold: float | str, + top_up_amount: float | str, + timeout: float = DEFAULT_TIMEOUT, +) -> dict[str, Any]: + """``PATCH /api/billing/auto-top-up`` — configure auto-reload (scope required). + + Body is strict server-side: extra keys (``maxMonthlySpend``, a payment method) + are rejected with 400. Numbers are sent as JSON numbers per the contract. + """ + return _request( + "PATCH", + "/api/billing/auto-top-up", + body={ + "enabled": bool(enabled), + "threshold": float(threshold), + "topUpAmount": float(top_up_amount), + }, + timeout=timeout, + ) + + +def post_charge( + *, + amount_usd: float | str, + idempotency_key: str, + timeout: float = DEFAULT_TIMEOUT, +) -> dict[str, Any]: + """``POST /api/billing/charge`` — buy credits (scope required). + + ``Idempotency-Key`` header is MANDATORY (a missing header is a server 400, not + a default): generate a UUID per user-confirmed purchase and reuse it on retry. + Returns ``202 {chargeId}`` — money is NOT confirmed yet; poll with + :func:`get_charge_status`. + """ + if not (isinstance(idempotency_key, str) and idempotency_key.strip()): + raise BillingError( + "Idempotency-Key is required for a charge.", + error="idempotency_key_required", + ) + return _request( + "POST", + "/api/billing/charge", + body={"amountUsd": float(amount_usd)}, + extra_headers={"Idempotency-Key": idempotency_key.strip()}, + timeout=timeout, + ) + + +def get_charge_status( + charge_id: str, *, timeout: float = DEFAULT_TIMEOUT +) -> dict[str, Any]: + """``GET /api/billing/charge/{id}`` — poll a charge (scope required). + + Returns ``{status: "pending"|"settled"|"failed", ...}``. An unknown or foreign + id returns ``{status:"pending"}`` (never 404, never another org's data) — so a + ``pending`` that never resolves past the 5-min cap is a *timeout*, not an error. + """ + if not (isinstance(charge_id, str) and charge_id.strip()): + raise BillingError("A charge id is required.", error="invalid_charge_id") + # urllib does not need manual quoting for the opaque ids the server mints, but + # guard against a stray slash that would change the path shape. + safe_id = urllib.parse.quote(charge_id.strip(), safe="") + return _request("GET", f"/api/billing/charge/{safe_id}", timeout=timeout) diff --git a/tests/agent/test_billing_view.py b/tests/agent/test_billing_view.py new file mode 100644 index 00000000000..288c125e417 --- /dev/null +++ b/tests/agent/test_billing_view.py @@ -0,0 +1,377 @@ +"""Unit tests for the Phase 2b terminal-billing core + HTTP client. + +Covers: +- Decimal money parsing/formatting (server emits decimal strings, not 2dp). +- BillingState payload parsing (role tiering, presets, bounds, sub-structs). +- Error-code → typed-exception mapping (the live-verified contract matrix). +- Fail-open builder behavior. +- Idempotency key generation. +- Custom-amount validation against bounds + multipleOf 0.01. + +No network: HTTP-layer tests drive _raise_for_error directly and monkeypatch the +request function for the builder. +""" + +from __future__ import annotations + +from decimal import Decimal + +import pytest + +import agent.billing_view as bv +from agent.billing_view import ( + AutoReload, + BillingState, + CardInfo, + MonthlyCap, + billing_state_from_payload, + build_billing_state, + format_money, + new_idempotency_key, + parse_money, + validate_charge_amount, +) +import hermes_cli.nous_billing as nb +from hermes_cli.nous_billing import ( + BillingAuthError, + BillingError, + BillingRateLimited, + BillingScopeRequired, + _raise_for_error, + resolve_portal_base_url, +) + + +# --------------------------------------------------------------------------- +# Decimal money +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize( + "raw,expected", + [ + ("142.5", Decimal("142.5")), # decimal string, NOT 2dp — the headline case + ("100", Decimal("100")), + ("10000", Decimal("10000")), + ("0.01", Decimal("0.01")), + (250, Decimal("250")), + (" 50 ", Decimal("50")), + ], +) +def test_parse_money_valid(raw, expected): + assert parse_money(raw) == expected + + +@pytest.mark.parametrize("raw", [None, "", "abc", "1.2.3", "$5", {}]) +def test_parse_money_invalid_returns_none(raw): + assert parse_money(raw) is None + + +def test_parse_money_never_uses_binary_float(): + # If a float ever sneaks through, we still get an exact decimal, not 0.1+0.2 junk. + assert parse_money(0.1) == Decimal("0.1") + + +@pytest.mark.parametrize( + "value,expected", + [ + (Decimal("142.5"), "$142.50"), + (Decimal("100"), "$100"), + (Decimal("0.01"), "$0.01"), + (Decimal("1000"), "$1000"), + (None, "—"), + ], +) +def test_format_money(value, expected): + assert format_money(value) == expected + + +# --------------------------------------------------------------------------- +# BillingState payload parsing +# --------------------------------------------------------------------------- + + +def _member_payload() -> dict: + return { + "org": {"id": "o1", "slug": "acme", "name": "Acme", "role": "MEMBER"}, + "balanceUsd": "142.5", + "cliBillingEnabled": True, + "chargePresets": ["100", "250", "500"], + "bounds": {"minUsd": "10", "maxUsd": "10000"}, + "card": None, + "monthlyCap": None, + "autoReload": None, + } + + +def _owner_payload() -> dict: + p = _member_payload() + p["org"]["role"] = "OWNER" + p["card"] = {"brand": "visa", "last4": "4242"} + p["monthlyCap"] = { + "limitUsd": "1000", + "spentThisMonthUsd": "180", + "isDefaultCeiling": True, + } + p["autoReload"] = {"enabled": True, "thresholdUsd": "20", "reloadToUsd": "100"} + return p + + +def test_state_member_tier_parse(): + s = billing_state_from_payload(_member_payload()) + assert s.logged_in + assert s.role == "MEMBER" + assert s.balance_usd == Decimal("142.5") + assert s.cli_billing_enabled is True + assert s.charge_presets == (Decimal("100"), Decimal("250"), Decimal("500")) + assert s.min_usd == Decimal("10") and s.max_usd == Decimal("10000") + assert s.card is None and s.monthly_cap is None and s.auto_reload is None + assert s.is_admin is False + assert s.can_charge is False # not admin + + +def test_state_owner_tier_parse(): + s = billing_state_from_payload(_owner_payload()) + assert s.is_admin is True + assert s.can_charge is True # admin + kill-switch on + assert s.card == CardInfo(brand="visa", last4="4242") + assert s.card is not None and s.card.masked == "visa ····4242" + assert s.monthly_cap == MonthlyCap( + limit_usd=Decimal("1000"), + spent_this_month_usd=Decimal("180"), + is_default_ceiling=True, + ) + assert s.auto_reload == AutoReload( + enabled=True, threshold_usd=Decimal("20"), reload_to_usd=Decimal("100") + ) + + +def test_state_can_charge_false_when_killswitch_off(): + p = _owner_payload() + p["cliBillingEnabled"] = False + s = billing_state_from_payload(p) + assert s.is_admin is True + assert s.can_charge is False # kill-switch off gates the action + + +def test_state_handles_garbage_substructs(): + p = _member_payload() + p["card"] = "not-a-dict" + p["monthlyCap"] = 42 + p["chargePresets"] = ["100", "bad", "250"] # bad preset dropped, not crash + s = billing_state_from_payload(p) + assert s.card is None and s.monthly_cap is None + assert s.charge_presets == (Decimal("100"), Decimal("250")) + + +# --------------------------------------------------------------------------- +# Error-code → typed-exception mapping (live-verified contract) +# --------------------------------------------------------------------------- + + +class _Headers: + def __init__(self, d): + self._d = d + + def get(self, k): + return self._d.get(k) + + +def test_401_maps_to_auth_error(): + with pytest.raises(BillingAuthError) as ei: + _raise_for_error(401, {"error": "invalid_token"}) + assert ei.value.status == 401 + + +def test_403_insufficient_scope_maps_to_scope_required(): + with pytest.raises(BillingScopeRequired) as ei: + _raise_for_error(403, {"error": "insufficient_scope", "portalUrl": "/billing"}) + assert ei.value.error == "insufficient_scope" + # portalUrl is resolved to an absolute URL (relative-by-design from the server). + assert (ei.value.portal_url or "").startswith("http") + assert (ei.value.portal_url or "").endswith("/billing") + + +@pytest.mark.parametrize("status", [429, 503]) +def test_rate_limited_maps_with_retry_after(status): + with pytest.raises(BillingRateLimited) as ei: + _raise_for_error( + status, + {"error": "rate_limited"}, + _Headers({"Retry-After": "60"}), + ) + assert ei.value.retry_after == 60 + # Critically: a rate limit is NOT a generic BillingError-only — surfaces branch on type. + assert isinstance(ei.value, BillingRateLimited) + + +@pytest.mark.parametrize( + "error", + [ + "no_payment_method", + "cli_billing_disabled", + "role_required", + "monthly_cap_exceeded", + "org_access_denied", + ], +) +def test_other_403s_map_to_base_error_with_portal_url(error): + with pytest.raises(BillingError) as ei: + _raise_for_error(403, {"error": error, "portalUrl": "/billing?topup=open"}) + # Not a scope/auth/rate subclass — the generic gate-denial path. + assert not isinstance(ei.value, (BillingScopeRequired, BillingAuthError, BillingRateLimited)) + assert ei.value.error == error + # portalUrl resolved to an absolute deep-link (server sends it relative). + assert (ei.value.portal_url or "").startswith("http") + assert (ei.value.portal_url or "").endswith("/billing?topup=open") + + +def test_monthly_cap_exceeded_carries_remaining_in_payload(): + with pytest.raises(BillingError) as ei: + _raise_for_error( + 403, + { + "error": "monthly_cap_exceeded", + "remainingUsd": "12.50", + "isDefaultCeiling": True, + "portalUrl": "/billing", + }, + ) + assert ei.value.payload["remainingUsd"] == "12.50" + assert ei.value.payload["isDefaultCeiling"] is True + + +def test_400_amount_out_of_bounds_is_base_error(): + with pytest.raises(BillingError) as ei: + _raise_for_error(400, {"error": "amount_out_of_bounds", "message": "too big"}) + assert ei.value.status == 400 + assert "too big" in str(ei.value) + + +# --------------------------------------------------------------------------- +# post_charge requires idempotency key (client-side guard) +# --------------------------------------------------------------------------- + + +def test_post_charge_requires_idempotency_key(): + with pytest.raises(BillingError) as ei: + nb.post_charge(amount_usd=50, idempotency_key="") + assert ei.value.error == "idempotency_key_required" + + +def test_get_charge_status_requires_id(): + with pytest.raises(BillingError) as ei: + nb.get_charge_status("") + assert ei.value.error == "invalid_charge_id" + + +# --------------------------------------------------------------------------- +# Base-URL resolution precedence +# --------------------------------------------------------------------------- + + +def test_portal_base_url_env_override(monkeypatch): + monkeypatch.setenv("HERMES_PORTAL_BASE_URL", "https://preview.example.com/") + assert resolve_portal_base_url() == "https://preview.example.com" + + +def test_portal_base_url_falls_back_to_state(monkeypatch): + monkeypatch.delenv("HERMES_PORTAL_BASE_URL", raising=False) + monkeypatch.delenv("NOUS_PORTAL_BASE_URL", raising=False) + assert ( + resolve_portal_base_url({"portal_base_url": "https://stored.example.com/"}) + == "https://stored.example.com" + ) + + +def test_portal_base_url_default(monkeypatch): + monkeypatch.delenv("HERMES_PORTAL_BASE_URL", raising=False) + monkeypatch.delenv("NOUS_PORTAL_BASE_URL", raising=False) + assert resolve_portal_base_url() == nb.DEFAULT_PORTAL_BASE_URL + + +# --------------------------------------------------------------------------- +# Fail-open builder +# --------------------------------------------------------------------------- + + +def test_build_billing_state_logged_out_on_auth_error(monkeypatch): + def _auth(*a, **kw): + raise BillingAuthError("nope", status=401) + + monkeypatch.setattr(nb, "get_billing_state", _auth) + s = build_billing_state() + assert s.logged_in is False + assert s.error is None # cleanly logged out, not an error + + +def test_build_billing_state_fail_open_on_http_error(monkeypatch): + def _boom(*a, **kw): + raise BillingError("portal exploded", status=500) + + monkeypatch.setattr(nb, "get_billing_state", _boom) + s = build_billing_state() + assert s.logged_in is False + assert "portal exploded" in (s.error or "") + + +def test_build_billing_state_parses_and_prefers_server_portal_url(monkeypatch): + payload = _owner_payload() + payload["portalUrl"] = "https://portal.example.com/billing?topup=open" + monkeypatch.setattr(nb, "get_billing_state", lambda *a, **kw: payload) + s = build_billing_state() + assert s.logged_in is True + assert s.portal_url == "https://portal.example.com/billing?topup=open" + assert s.balance_usd == Decimal("142.5") + + +def test_build_billing_state_builds_fallback_portal_url(monkeypatch): + payload = _member_payload() # no portalUrl key + monkeypatch.setattr(nb, "get_billing_state", lambda *a, **kw: payload) + monkeypatch.setattr(bv, "_fallback_portal_url", lambda base: "FALLBACK") + # resolve_portal_base_url is imported into bv via local import; patch nb's. + s = build_billing_state() + assert s.portal_url == "FALLBACK" + + +# --------------------------------------------------------------------------- +# Idempotency +# --------------------------------------------------------------------------- + + +def test_new_idempotency_key_unique_and_uuid_shaped(): + a, b = new_idempotency_key(), new_idempotency_key() + assert a != b + assert len(a) == 36 and a.count("-") == 4 + + +# --------------------------------------------------------------------------- +# Amount validation (Screen 3 custom input) +# --------------------------------------------------------------------------- + + +def test_validate_amount_ok(): + v = validate_charge_amount("100", min_usd=Decimal("10"), max_usd=Decimal("10000")) + assert v.ok and v.amount == Decimal("100") + + +def test_validate_amount_strips_dollar_sign(): + v = validate_charge_amount("$250", min_usd=Decimal("10"), max_usd=Decimal("10000")) + assert v.ok and v.amount == Decimal("250") + + +@pytest.mark.parametrize( + "raw,err_substr", + [ + ("", "dollar amount"), + ("0", "greater than"), + ("-5", "greater than"), + ("10.005", "cent"), # multipleOf 0.01 — sub-cent rejected + ("5", "Minimum"), # below bounds.minUsd + ("99999", "Maximum"), # above bounds.maxUsd + ], +) +def test_validate_amount_rejections(raw, err_substr): + v = validate_charge_amount(raw, min_usd=Decimal("10"), max_usd=Decimal("10000")) + assert not v.ok + assert err_substr.lower() in (v.error or "").lower() diff --git a/tests/cli/test_prompt_text_input_thread_safety.py b/tests/cli/test_prompt_text_input_thread_safety.py index fb27a95b312..cd495f392fb 100644 --- a/tests/cli/test_prompt_text_input_thread_safety.py +++ b/tests/cli/test_prompt_text_input_thread_safety.py @@ -34,37 +34,35 @@ class TestPromptTextInputThreadSafety: # not the orphaned-coroutine result. assert mock_rit.called - def test_background_thread_falls_back_to_direct_input(self): - """On a daemon thread, skip run_in_terminal and call input() directly. + def test_background_thread_cancels_instead_of_hanging(self): + """On a daemon thread with an active app, cancel cleanly (return None). - This preserves the fallback for any prompt that still runs off the main - UI thread: run_in_terminal's coroutine would otherwise be orphaned. + stdin is owned by the prompt_toolkit event loop / JSON-RPC pipe on the + non-main (process_loop / slash-worker) thread, so a bare input() there + would block until the worker's timeout (#23185 / billing auto-reload + hang). The guard cancels to None instead of hanging — it must NOT call + run_in_terminal (orphaned coroutine) and must NOT call input(). """ cli = _make_cli() - captured = {} - - def fake_input(prompt): - captured["prompt"] = prompt - return "1" result_holder = {} def run_on_daemon(): with patch("prompt_toolkit.application.run_in_terminal") as mock_rit, \ - patch("builtins.input", side_effect=fake_input): + patch("builtins.input", side_effect=AssertionError("input() must not be called off-main-thread")) as mock_input: result_holder["value"] = cli._prompt_text_input("Choice [1/2/3]: ") result_holder["rit_called"] = mock_rit.called + result_holder["input_called"] = mock_input.called t = threading.Thread(target=run_on_daemon, daemon=True) t.start() t.join(timeout=2.0) - assert not t.is_alive(), "daemon thread hung — input() was not driven" + assert not t.is_alive(), "daemon thread hung — guard did not cancel cleanly" - # run_in_terminal was bypassed entirely on the background thread. + # Cancelled cleanly: None returned, neither run_in_terminal nor input() called. + assert result_holder["value"] is None assert result_holder["rit_called"] is False - # input() was invoked with the prompt and its return value was captured. - assert captured.get("prompt") == "Choice [1/2/3]: " - assert result_holder["value"] == "1" + assert result_holder["input_called"] is False def test_no_app_uses_direct_input(self): """Without an active prompt_toolkit app, always call input() directly.""" diff --git a/tests/hermes_cli/test_billing_cli.py b/tests/hermes_cli/test_billing_cli.py new file mode 100644 index 00000000000..31c645760c5 --- /dev/null +++ b/tests/hermes_cli/test_billing_cli.py @@ -0,0 +1,136 @@ +"""Tests for the /billing CLI handler (cli.py::_show_billing). + +Focus on the non-interactive (no live prompt_toolkit app) path — the same +discipline as the /credits non-interactive test: it must render text, never +invoke the modal (which would read the slash-worker's JSON-RPC stdin and hang). +Plus role/kill-switch gating and logged-out handling. +""" + +from __future__ import annotations + +from decimal import Decimal + +import pytest + +import agent.billing_view as bv +from agent.billing_view import BillingState, CardInfo, MonthlyCap +from cli import HermesCLI + + +@pytest.fixture +def cli(): + obj = HermesCLI.__new__(HermesCLI) # bypass __init__ (no full app needed) + obj._app = None # non-interactive: forces the text path + return obj + + +def _boom_modal(*a, **kw): + raise AssertionError("modal must NOT be called in non-interactive mode") + + +def test_billing_logged_out(cli, monkeypatch, capsys): + monkeypatch.setattr(bv, "build_billing_state", lambda *a, **kw: BillingState(logged_in=False)) + cli._show_billing("/billing") + out = capsys.readouterr().out + assert "Not logged into Nous Portal" in out + assert "hermes portal" in out + + +def test_billing_overview_non_interactive_renders_text_not_modal(cli, monkeypatch, capsys): + monkeypatch.setattr(HermesCLI, "_prompt_text_input_modal", _boom_modal, raising=False) + state = BillingState( + logged_in=True, + org_name="Acme", + role="OWNER", + balance_usd=Decimal("142.5"), + cli_billing_enabled=True, + charge_presets=(Decimal("100"),), + monthly_cap=MonthlyCap(limit_usd=Decimal("1000"), spent_this_month_usd=Decimal("180"), + is_default_ceiling=True), + portal_url="https://portal/billing?topup=open", + ) + monkeypatch.setattr(bv, "build_billing_state", lambda *a, **kw: state) + cli._show_billing("/billing") + out = capsys.readouterr().out + assert "Usage credits" in out + assert "$142.50" in out + assert "$180 of $1000 used (default ceiling)" in out + # New design: a spend bar with a percentage on the overview. + assert "%" in out and ("█" in out or "░" in out) + # ZERO sub-commands: no /billing buy|auto-reload|limit advertising. + assert "/billing buy" not in out + assert "Actions:" not in out + # Non-interactive funnels to the portal (the URL is the affordance). + assert "Manage on portal:" in out + + +def test_billing_member_cannot_charge(cli, monkeypatch, capsys): + state = BillingState( + logged_in=True, role="MEMBER", balance_usd=Decimal("10"), + cli_billing_enabled=True, portal_url="https://portal/billing", + ) + monkeypatch.setattr(bv, "build_billing_state", lambda *a, **kw: state) + cli._show_billing("/billing") + out = capsys.readouterr().out + assert "require an org admin/owner" in out + + +def test_billing_killswitch_off_blocks(cli, monkeypatch, capsys): + state = BillingState( + logged_in=True, role="OWNER", balance_usd=Decimal("10"), + cli_billing_enabled=False, portal_url="https://portal/billing", + ) + monkeypatch.setattr(bv, "build_billing_state", lambda *a, **kw: state) + cli._show_billing("/billing") + out = capsys.readouterr().out + assert "turned off for this org" in out + + +def test_billing_limit_screen_readonly(cli, monkeypatch, capsys): + state = BillingState( + logged_in=True, role="OWNER", cli_billing_enabled=True, + monthly_cap=MonthlyCap(limit_usd=Decimal("1000"), spent_this_month_usd=Decimal("250"), + is_default_ceiling=True), + portal_url="https://portal/billing", + ) + monkeypatch.setattr(bv, "build_billing_state", lambda *a, **kw: state) + # ZERO sub-commands: the limit screen is reached via the menu, never a + # sub-command — call it directly the way the overview menu would. + cli._billing_limit_screen(state) + out = capsys.readouterr().out + assert "Monthly spend limit" in out + assert "$250 of $1000 used" in out + assert "read-only" in out + + +def test_billing_sub_arg_ignored_opens_overview(cli, monkeypatch, capsys): + # A stray sub-arg must NOT error and must NOT dispatch to a sub-screen — + # it just opens the overview (spec §0.4: zero sub-commands). + monkeypatch.setattr(HermesCLI, "_prompt_text_input_modal", _boom_modal, raising=False) + state = BillingState( + logged_in=True, role="OWNER", balance_usd=Decimal("142.5"), + cli_billing_enabled=True, charge_presets=(Decimal("25"),), + portal_url="https://portal/billing", + ) + monkeypatch.setattr(bv, "build_billing_state", lambda *a, **kw: state) + cli._show_billing("/billing buy") # arg is ignored + out = capsys.readouterr().out + assert "Usage credits" in out # overview, NOT the buy screen + assert "Buy usage credits" not in out + + +def test_billing_buy_non_interactive_defers_to_portal(cli, monkeypatch, capsys): + monkeypatch.setattr(HermesCLI, "_prompt_text_input_modal", _boom_modal, raising=False) + state = BillingState( + logged_in=True, role="OWNER", cli_billing_enabled=True, + charge_presets=(Decimal("25"), Decimal("50"), Decimal("100")), + card=CardInfo(brand="visa", last4="4242"), + portal_url="https://portal/billing", + ) + monkeypatch.setattr(bv, "build_billing_state", lambda *a, **kw: state) + # Reached via the menu in real use; non-interactively it defers to the portal. + cli._billing_buy_flow(state) + out = capsys.readouterr().out + assert "Buy usage credits" in out + assert "$25" in out and "$50" in out and "$100" in out + assert "interactive CLI" in out # defers; no charge attempted non-interactively diff --git a/tests/hermes_cli/test_billing_portal_url.py b/tests/hermes_cli/test_billing_portal_url.py new file mode 100644 index 00000000000..fa8616e1028 --- /dev/null +++ b/tests/hermes_cli/test_billing_portal_url.py @@ -0,0 +1,53 @@ +"""Portal-URL resolution for Phase 2b billing errors (nous_billing). + +The server emits ``portalUrl`` relative by design (``/billing?topup=open``); the +client must resolve it against the active portal base so deep-links are clickable +on whatever deployment (preview / staging / prod) the user is pointed at. +""" + +from __future__ import annotations + +import pytest + +from hermes_cli.nous_billing import ( + BillingError, + _absolutize_portal_url, + _raise_for_error, +) + + +@pytest.fixture +def _preview(monkeypatch): + monkeypatch.setenv("HERMES_PORTAL_BASE_URL", "https://nas-pr-412.nousresearch.wtf") + + +def test_absolutize_resolves_relative(_preview): + assert ( + _absolutize_portal_url("/billing?topup=open") + == "https://nas-pr-412.nousresearch.wtf/billing?topup=open" + ) + + +def test_absolutize_leaves_absolute_unchanged(_preview): + # Idempotent: an already-absolute URL must NOT be double-prefixed. + url = "https://other.example/billing?topup=open" + assert _absolutize_portal_url(url) == url + + +def test_absolutize_passthrough_empty(_preview): + assert _absolutize_portal_url(None) is None + assert _absolutize_portal_url("") == "" + + +def test_raise_for_error_attaches_absolute_portal_url(_preview): + # The 403 no_payment_method envelope carries a RELATIVE portalUrl; the raised + # BillingError must expose it as ABSOLUTE so CLI + TUI render a clickable link. + with pytest.raises(BillingError) as exc_info: + _raise_for_error( + 403, + {"error": "no_payment_method", "portalUrl": "/billing?topup=open"}, + ) + assert ( + exc_info.value.portal_url + == "https://nas-pr-412.nousresearch.wtf/billing?topup=open" + ) diff --git a/tests/hermes_cli/test_billing_scope_stepup.py b/tests/hermes_cli/test_billing_scope_stepup.py new file mode 100644 index 00000000000..193aa62a8fd --- /dev/null +++ b/tests/hermes_cli/test_billing_scope_stepup.py @@ -0,0 +1,193 @@ +"""Tests for the Phase 2b billing:manage scope step-up (auth.py).""" + +from __future__ import annotations + +import pytest + +import hermes_cli.auth as auth +from hermes_cli.auth import ( + NOUS_BILLING_MANAGE_SCOPE, + nous_token_has_billing_scope, + step_up_nous_billing_scope, +) + + +# --------------------------------------------------------------------------- +# nous_token_has_billing_scope +# --------------------------------------------------------------------------- + + +def test_has_scope_true_when_present(monkeypatch): + monkeypatch.setattr( + auth, + "get_provider_auth_state", + lambda p: {"scope": "inference:invoke tool:invoke billing:manage"}, + ) + assert nous_token_has_billing_scope() is True + + +def test_has_scope_false_when_absent(monkeypatch): + monkeypatch.setattr( + auth, "get_provider_auth_state", lambda p: {"scope": "inference:invoke tool:invoke"} + ) + assert nous_token_has_billing_scope() is False + + +def test_has_scope_false_when_no_state(monkeypatch): + monkeypatch.setattr(auth, "get_provider_auth_state", lambda p: None) + assert nous_token_has_billing_scope() is False + + +def test_has_scope_no_substring_false_positive(monkeypatch): + # "billing:manage-lite" must NOT match billing:manage (split-based, not substring). + monkeypatch.setattr( + auth, "get_provider_auth_state", lambda p: {"scope": "billing:manage-lite"} + ) + assert nous_token_has_billing_scope() is False + + +# --------------------------------------------------------------------------- +# step_up_nous_billing_scope +# --------------------------------------------------------------------------- + + +@pytest.fixture +def _stub_persist(monkeypatch): + """Neutralize the persistence side-effects so step-up tests are pure.""" + monkeypatch.setattr(auth, "_auth_store_lock", lambda: _NullCtx()) + monkeypatch.setattr(auth, "_load_auth_store", lambda: {}) + monkeypatch.setattr(auth, "_save_provider_state", lambda *a, **kw: None) + monkeypatch.setattr(auth, "_save_auth_store", lambda *a, **kw: "auth.json") + monkeypatch.setattr(auth, "_write_shared_nous_state", lambda *a, **kw: None) + monkeypatch.setattr(auth, "_sync_nous_pool_from_auth_store", lambda: None) + + +class _NullCtx: + def __enter__(self): + return self + + def __exit__(self, *a): + return False + + +def test_step_up_requests_billing_scope_and_reuses_prior_urls(monkeypatch, _stub_persist): + monkeypatch.setattr( + auth, + "get_provider_auth_state", + lambda p: { + "scope": "inference:invoke tool:invoke", + "portal_base_url": "https://preview.example.com", + "inference_base_url": "https://inf.example.com", + "client_id": "hermes-cli", + }, + ) + captured = {} + + def _fake_login(**kw): + captured.update(kw) + # Simulate the admin ticking the box → token comes back WITH the scope. + return {"scope": "inference:invoke tool:invoke billing:manage", "access_token": "t"} + + monkeypatch.setattr(auth, "_nous_device_code_login", _fake_login) + + granted = step_up_nous_billing_scope() + assert granted is True + # Requested scope must include billing:manage, preserving prior scopes. + assert NOUS_BILLING_MANAGE_SCOPE in captured["scope"].split() + assert "inference:invoke" in captured["scope"].split() + # Reuses the prior credential's deployment URLs (so a preview stays a preview). + assert captured["portal_base_url"] == "https://preview.example.com" + assert captured["client_id"] == "hermes-cli" + + +def test_step_up_returns_false_when_downscoped(monkeypatch, _stub_persist): + # Non-admin / unticked → the server silently downscopes; token comes back WITHOUT scope. + monkeypatch.setattr(auth, "get_provider_auth_state", lambda p: {"scope": "inference:invoke"}) + monkeypatch.setattr( + auth, + "_nous_device_code_login", + lambda **kw: {"scope": "inference:invoke", "access_token": "t"}, + ) + assert step_up_nous_billing_scope() is False + + +def test_step_up_falls_back_to_standard_scope_when_no_prior(monkeypatch, _stub_persist): + monkeypatch.setattr(auth, "get_provider_auth_state", lambda p: {}) + captured = {} + + def _fake_login(**kw): + captured.update(kw) + return {"scope": "inference:invoke tool:invoke billing:manage"} + + monkeypatch.setattr(auth, "_nous_device_code_login", _fake_login) + step_up_nous_billing_scope() + requested = captured["scope"].split() + assert "inference:invoke" in requested + assert "tool:invoke" in requested + assert NOUS_BILLING_MANAGE_SCOPE in requested + + +# --------------------------------------------------------------------------- +# on_verification callback plumbing (TUI surfaces the device-flow URL via this) +# --------------------------------------------------------------------------- + + +def test_step_up_forwards_on_verification_callback(monkeypatch, _stub_persist): + monkeypatch.setattr(auth, "get_provider_auth_state", lambda p: {}) + captured = {} + + def _fake_login(**kw): + captured.update(kw) + return {"scope": "inference:invoke tool:invoke billing:manage"} + + monkeypatch.setattr(auth, "_nous_device_code_login", _fake_login) + + def _cb(url, code): + pass + + step_up_nous_billing_scope(on_verification=_cb) + # The callback must be threaded straight through to the device-code login. + assert captured["on_verification"] is _cb + + +def test_device_login_fires_on_verification_before_polling(monkeypatch): + """on_verification(url, code) must fire BEFORE _poll_for_token (so the TUI + can render the link while the flow blocks waiting for approval).""" + order: list[str] = [] + + monkeypatch.setattr( + auth, + "_request_device_code", + lambda **kw: { + "verification_uri_complete": "https://portal.example/device?code=ABCD", + "user_code": "ABCD-1234", + "device_code": "dev", + "expires_in": 600, + "interval": 5, + }, + ) + + def _fake_poll(**kw): + order.append("poll") + return {"access_token": "t", "scope": "inference:invoke", "expires_in": 3600} + + monkeypatch.setattr(auth, "_poll_for_token", _fake_poll) + + seen = {} + + def _cb(url, code): + order.append("verify") + seen["url"] = url + seen["code"] = code + + # We only assert the callback fires before polling. Post-poll token + # validation (JWT usability checks) is out of scope and may raise on the + # synthetic token — swallow it; the ordering assertion is what matters. + try: + auth._nous_device_code_login(open_browser=False, on_verification=_cb) + except Exception: + pass + + assert order[:2] == ["verify", "poll"], "callback must fire before polling" + assert seen["url"] == "https://portal.example/device?code=ABCD" + assert seen["code"] == "ABCD-1234" diff --git a/tests/tui_gateway/test_billing_rpc.py b/tests/tui_gateway/test_billing_rpc.py new file mode 100644 index 00000000000..3d29993bfda --- /dev/null +++ b/tests/tui_gateway/test_billing_rpc.py @@ -0,0 +1,206 @@ +"""Tests for the Phase 2b billing JSON-RPC methods (tui_gateway/server.py). + +Verifies the structured envelope contract the Ink side branches on: +- billing.state serializes BillingState (Decimals → strings) + fails open. +- billing.charge / charge_status / auto_reload return typed error envelopes + (result.ok=false, result.error=) instead of JSON-RPC errors. +- billing.charge mints + echoes an idempotency_key for retry reuse. +""" + +from __future__ import annotations + +from decimal import Decimal + +import pytest + +import tui_gateway.server as srv +import hermes_cli.nous_billing as nb +import agent.billing_view as bv +from agent.billing_view import BillingState, CardInfo, MonthlyCap + + +def _call(method: str, params: dict) -> dict: + """Invoke a registered RPC method and return its result dict.""" + envelope = srv._methods[method](1, params) + return envelope["result"] + + +# --------------------------------------------------------------------------- +# billing.state +# --------------------------------------------------------------------------- + + +def test_billing_state_serializes_decimals_as_strings(monkeypatch): + state = BillingState( + logged_in=True, + org_name="Acme", + role="OWNER", + balance_usd=Decimal("142.5"), + cli_billing_enabled=True, + charge_presets=(Decimal("100"), Decimal("250")), + min_usd=Decimal("10"), + max_usd=Decimal("10000"), + card=CardInfo(brand="visa", last4="4242"), + monthly_cap=MonthlyCap( + limit_usd=Decimal("1000"), spent_this_month_usd=Decimal("180"), is_default_ceiling=True + ), + portal_url="https://portal/billing?topup=open", + ) + monkeypatch.setattr(bv, "build_billing_state", lambda *a, **kw: state) + res = _call("billing.state", {}) + assert res["ok"] is True and res["logged_in"] is True + # Money on the wire is STRING, not float/number. + assert res["balance_usd"] == "142.5" + assert res["balance_display"] == "$142.50" + assert res["charge_presets"] == ["100", "250"] + assert res["card"]["masked"] == "visa ····4242" + assert res["monthly_cap"]["is_default_ceiling"] is True + assert res["is_admin"] is True and res["can_charge"] is True + + +def test_billing_state_fail_open(monkeypatch): + def _boom(*a, **kw): + raise RuntimeError("portal down") + + monkeypatch.setattr(bv, "build_billing_state", _boom) + res = _call("billing.state", {}) + assert res["ok"] is True and res["logged_in"] is False + + +# --------------------------------------------------------------------------- +# billing.charge — typed error envelopes +# --------------------------------------------------------------------------- + + +def test_billing_charge_success_echoes_charge_id(monkeypatch): + monkeypatch.setattr(nb, "post_charge", lambda **kw: {"chargeId": "ch_123"}) + res = _call("billing.charge", {"amount_usd": "100", "idempotency_key": "key-1"}) + assert res["ok"] is True + assert res["charge_id"] == "ch_123" + assert res["idempotency_key"] == "key-1" + + +def test_billing_charge_mints_key_when_absent(monkeypatch): + seen = {} + + def _post(**kw): + seen["key"] = kw["idempotency_key"] + return {"chargeId": "ch_x"} + + monkeypatch.setattr(nb, "post_charge", _post) + res = _call("billing.charge", {"amount_usd": "50"}) + assert res["ok"] is True + assert res["idempotency_key"] == seen["key"] # minted key echoed back + assert len(res["idempotency_key"]) == 36 + + +def test_billing_charge_insufficient_scope_envelope(monkeypatch): + def _post(**kw): + raise nb.BillingScopeRequired("need scope", status=403, error="insufficient_scope") + + monkeypatch.setattr(nb, "post_charge", _post) + res = _call("billing.charge", {"amount_usd": "100", "idempotency_key": "k"}) + assert res["ok"] is False + assert res["error"] == "insufficient_scope" + assert res["idempotency_key"] == "k" # preserved for reuse post-stepup + + +def test_billing_charge_no_payment_method_envelope(monkeypatch): + def _post(**kw): + raise nb.BillingError( + "no reusable card", status=403, error="no_payment_method", + portal_url="/billing?topup=open", + ) + + monkeypatch.setattr(nb, "post_charge", _post) + res = _call("billing.charge", {"amount_usd": "100", "idempotency_key": "k"}) + assert res["ok"] is False + assert res["error"] == "no_payment_method" + assert res["portal_url"] == "/billing?topup=open" + + +def test_billing_charge_rate_limited_envelope(monkeypatch): + def _post(**kw): + raise nb.BillingRateLimited("slow down", status=429, error="rate_limited", retry_after=60) + + monkeypatch.setattr(nb, "post_charge", _post) + res = _call("billing.charge", {"amount_usd": "100", "idempotency_key": "k"}) + assert res["error"] == "rate_limited" + assert res["retry_after"] == 60 + + +# --------------------------------------------------------------------------- +# billing.charge_status — the poll +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize( + "server_resp,expected", + [ + ({"status": "pending"}, {"status": "pending"}), + ( + {"status": "settled", "amountUsd": "50", "settledAt": "2026-06-13T00:00:00Z"}, + {"status": "settled", "amount_usd": "50"}, + ), + ({"status": "failed", "reason": "card_declined"}, {"status": "failed", "reason": "card_declined"}), + ], +) +def test_billing_charge_status_maps_fields(monkeypatch, server_resp, expected): + monkeypatch.setattr(nb, "get_charge_status", lambda cid, **kw: server_resp) + res = _call("billing.charge_status", {"charge_id": "ch_1"}) + assert res["ok"] is True + for k, v in expected.items(): + assert res[k] == v + + +def test_billing_charge_status_requires_id(): + res = _call("billing.charge_status", {}) + assert res["ok"] is False and res["error"] == "invalid_charge_id" + + +# --------------------------------------------------------------------------- +# billing.auto_reload +# --------------------------------------------------------------------------- + + +def test_billing_auto_reload_success(monkeypatch): + seen = {} + monkeypatch.setattr(nb, "patch_auto_top_up", lambda **kw: seen.update(kw) or {"ok": True}) + res = _call("billing.auto_reload", {"enabled": True, "threshold": 20, "top_up_amount": 100}) + assert res["ok"] is True + assert seen == {"enabled": True, "threshold": 20, "top_up_amount": 100} + + +def test_billing_auto_reload_validation_error_envelope(monkeypatch): + def _patch(**kw): + raise nb.BillingError("bad", status=400, error="validation_failed") + + monkeypatch.setattr(nb, "patch_auto_top_up", _patch) + res = _call("billing.auto_reload", {"enabled": True, "threshold": 20, "top_up_amount": 100}) + assert res["ok"] is False and res["error"] == "validation_failed" + + +def test_billing_auto_reload_requires_fields(): + res = _call("billing.auto_reload", {"enabled": True}) + assert res["ok"] is False and res["error"] == "invalid_request" + + +# --------------------------------------------------------------------------- +# billing.step_up +# --------------------------------------------------------------------------- + + +def test_billing_step_up_granted(monkeypatch): + import hermes_cli.auth as auth + + monkeypatch.setattr(auth, "step_up_nous_billing_scope", lambda **kw: True) + res = _call("billing.step_up", {}) + assert res["ok"] is True and res["granted"] is True + + +def test_billing_step_up_downscoped(monkeypatch): + import hermes_cli.auth as auth + + monkeypatch.setattr(auth, "step_up_nous_billing_scope", lambda **kw: False) + res = _call("billing.step_up", {}) + assert res["ok"] is True and res["granted"] is False diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 782a8ba457b..d2436b86d08 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -174,6 +174,7 @@ _DETAIL_MODES = frozenset({"hidden", "collapsed", "expanded"}) # response writes are safe. _LONG_HANDLERS = frozenset( { + "billing.step_up", "browser.manage", "cli.exec", "plugins.manage", @@ -5190,6 +5191,221 @@ def _(rid, params: dict) -> dict: return _ok(rid, {"logged_in": False, "balance_lines": [], "identity_line": None, "topup_url": None, "depleted": False}) +# =========================================================================== +# Phase 2b terminal billing RPC methods +# =========================================================================== +# +# These return STRUCTURED success envelopes (result.ok / result.error) rather +# than JSON-RPC-level errors, so the TUI's rpc() promise always resolves and the +# Ink side can branch on the typed billing error code (insufficient_scope, +# rate_limited, no_payment_method, …) to render the right affordance instead of +# landing in a generic catch. The data-building lives in the shared core +# (agent/billing_view.py + hermes_cli/nous_billing.py) — same as /credits. + + +def _serialize_billing_error(exc) -> dict: + """Map a BillingError into the result.error envelope the TUI branches on.""" + from hermes_cli.nous_billing import ( + BillingRateLimited, + BillingScopeRequired, + ) + + kind = "error" + if isinstance(exc, BillingScopeRequired): + kind = "insufficient_scope" + elif isinstance(exc, BillingRateLimited): + kind = "rate_limited" + elif getattr(exc, "error", None): + kind = str(exc.error) + return { + "ok": False, + "error": kind, + "message": str(exc), + "portal_url": getattr(exc, "portal_url", None), + "retry_after": getattr(exc, "retry_after", None), + "payload": getattr(exc, "payload", {}) or {}, + } + + +def _serialize_billing_state(state) -> dict: + """Serialize a BillingState for the wire (Decimals → strings, money-safe).""" + from agent.billing_view import format_money + + def _s(value): + return None if value is None else str(value) + + card = None + if state.card is not None: + card = {"brand": state.card.brand, "last4": state.card.last4, "masked": state.card.masked} + monthly_cap = None + if state.monthly_cap is not None: + mc = state.monthly_cap + monthly_cap = { + "limit_usd": _s(mc.limit_usd), + "limit_display": format_money(mc.limit_usd), + "spent_this_month_usd": _s(mc.spent_this_month_usd), + "spent_display": format_money(mc.spent_this_month_usd), + "is_default_ceiling": mc.is_default_ceiling, + } + auto_reload = None + if state.auto_reload is not None: + ar = state.auto_reload + auto_reload = { + "enabled": ar.enabled, + "threshold_usd": _s(ar.threshold_usd), + "threshold_display": format_money(ar.threshold_usd), + "reload_to_usd": _s(ar.reload_to_usd), + "reload_to_display": format_money(ar.reload_to_usd), + } + return { + "ok": True, + "logged_in": state.logged_in, + "org_name": state.org_name, + "org_slug": state.org_slug, + "role": state.role, + "is_admin": state.is_admin, + "can_charge": state.can_charge, + "balance_usd": _s(state.balance_usd), + "balance_display": format_money(state.balance_usd), + "cli_billing_enabled": state.cli_billing_enabled, + "charge_presets": [_s(p) for p in state.charge_presets], + "charge_presets_display": [format_money(p) for p in state.charge_presets], + "min_usd": _s(state.min_usd), + "max_usd": _s(state.max_usd), + "card": card, + "monthly_cap": monthly_cap, + "auto_reload": auto_reload, + "portal_url": state.portal_url, + "error": state.error, + } + + +@method("billing.state") +def _(rid, params: dict) -> dict: + """GET /api/billing/state → serialized BillingState (Screen 1 + 5). + + Fail-open like credits.view: a logged-out / unreachable portal yields + {ok:true, logged_in:false}. No scope required for this endpoint. + """ + try: + from agent.billing_view import build_billing_state + + state = build_billing_state() + return _ok(rid, _serialize_billing_state(state)) + except Exception: + return _ok(rid, {"ok": True, "logged_in": False, "error": "could not load billing state"}) + + +@method("billing.charge") +def _(rid, params: dict) -> dict: + """POST /api/billing/charge → {ok, chargeId} or a typed error envelope. + + params: {amount_usd: str|number, idempotency_key?: str}. If no key is + supplied, the server-side core mints a fresh one and returns it so the TUI can + reuse it on retry of the SAME purchase. + """ + from hermes_cli.nous_billing import BillingError, post_charge + from agent.billing_view import new_idempotency_key + + amount = params.get("amount_usd") + if amount is None: + return _ok(rid, {"ok": False, "error": "invalid_request", "message": "amount_usd is required"}) + key = params.get("idempotency_key") or new_idempotency_key() + try: + result = post_charge(amount_usd=amount, idempotency_key=key) + return _ok(rid, {"ok": True, "charge_id": result.get("chargeId"), "idempotency_key": key}) + except BillingError as exc: + env = _serialize_billing_error(exc) + env["idempotency_key"] = key # so the TUI can reuse on retry + return _ok(rid, env) + except Exception as exc: + return _ok(rid, {"ok": False, "error": "error", "message": str(exc), "idempotency_key": key}) + + +@method("billing.charge_status") +def _(rid, params: dict) -> dict: + """GET /api/billing/charge/{id} → {ok, status, ...} or typed error. + + The poll. Caller drives the 2s/5-min cadence; this is a single status read. + """ + from hermes_cli.nous_billing import BillingError, get_charge_status + + charge_id = params.get("charge_id") + if not charge_id: + return _ok(rid, {"ok": False, "error": "invalid_charge_id", "message": "charge_id is required"}) + try: + result = get_charge_status(charge_id) + return _ok( + rid, + { + "ok": True, + "status": result.get("status"), + "amount_usd": result.get("amountUsd"), + "settled_at": result.get("settledAt"), + "reason": result.get("reason"), + }, + ) + except BillingError as exc: + return _ok(rid, _serialize_billing_error(exc)) + except Exception as exc: + return _ok(rid, {"ok": False, "error": "error", "message": str(exc)}) + + +@method("billing.auto_reload") +def _(rid, params: dict) -> dict: + """PATCH /api/billing/auto-top-up → {ok:true} or typed error (Screen 2). + + params: {enabled: bool, threshold: number, top_up_amount: number}. + """ + from hermes_cli.nous_billing import BillingError, patch_auto_top_up + + try: + enabled = bool(params.get("enabled")) + threshold = params.get("threshold") + top_up_amount = params.get("top_up_amount") + if threshold is None or top_up_amount is None: + return _ok(rid, {"ok": False, "error": "invalid_request", "message": "threshold and top_up_amount are required"}) + patch_auto_top_up(enabled=enabled, threshold=threshold, top_up_amount=top_up_amount) + return _ok(rid, {"ok": True}) + except BillingError as exc: + return _ok(rid, _serialize_billing_error(exc)) + except Exception as exc: + return _ok(rid, {"ok": False, "error": "error", "message": str(exc)}) + + +@method("billing.step_up") +def _(rid, params: dict) -> dict: + """Run the lazy billing:manage step-up device flow → {ok, granted}. + + Triggered by the TUI after a billing call returns error=insufficient_scope. + Returns granted:false when the server silently downscopes (non-admin / unticked). + + Runs on the thread pool (in _LONG_HANDLERS): the device flow blocks for the + whole device-code lifetime (minutes), so it must not stall the main stdin loop. + The verification URL/code reach the TUI via an out-of-band ``billing.step_up. + verification`` event (a plain print would be dropped by the JSON-RPC stdout + pipe), and the browser is opened TUI-side via openExternalUrl — never with the + gateway's headless webbrowser.open (hence open_browser=False). + """ + sid = params.get("session_id") or "" + try: + from hermes_cli.auth import step_up_nous_billing_scope + + def _on_verification(url: str, code: str) -> None: + _emit( + "billing.step_up.verification", + sid, + {"verification_url": url, "user_code": code}, + ) + + granted = step_up_nous_billing_scope( + open_browser=False, on_verification=_on_verification + ) + return _ok(rid, {"ok": True, "granted": bool(granted)}) + except Exception as exc: + return _ok(rid, {"ok": False, "error": "error", "message": str(exc), "granted": False}) + + @method("session.status") def _(rid, params: dict) -> dict: session, err = _sess_nowait(params, rid) diff --git a/ui-tui/src/__tests__/billingCommand.test.ts b/ui-tui/src/__tests__/billingCommand.test.ts new file mode 100644 index 00000000000..f27f474e561 --- /dev/null +++ b/ui-tui/src/__tests__/billingCommand.test.ts @@ -0,0 +1,301 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { getOverlayState, resetOverlayState } from '../app/overlayStore.js' +import { billingCommands } from '../app/slash/commands/billing.js' +import type { BillingStateResponse } from '../gatewayTypes.js' + +vi.mock('../lib/openExternalUrl.js', () => ({ + openExternalUrl: vi.fn(() => true) +})) + +const billingCommand = billingCommands.find(cmd => cmd.name === 'billing')! + +const ownerState = (overrides: Partial = {}): BillingStateResponse => ({ + auto_reload: { + enabled: false, + reload_to_display: '—', + reload_to_usd: null, + threshold_display: '—', + threshold_usd: null + }, + balance_display: '$142.50', + balance_usd: '142.5', + can_charge: true, + card: { brand: 'visa', last4: '4242', masked: 'visa ····4242' }, + charge_presets: ['25', '50', '100'], + charge_presets_display: ['$25', '$50', '$100'], + cli_billing_enabled: true, + is_admin: true, + logged_in: true, + max_usd: '10000', + min_usd: '10', + monthly_cap: { + is_default_ceiling: true, + limit_display: '$1000', + limit_usd: '1000', + spent_display: '$180', + spent_this_month_usd: '180' + }, + ok: true, + org_name: 'Acme', + portal_url: 'https://portal/billing?topup=open', + role: 'OWNER', + ...overrides +}) + +const guarded = + (fn: (r: T) => void) => + (r: null | T) => { + if (r) { + fn(r) + } + } + +/** Build a ctx whose rpc routes by method name to a supplied map of results. */ +const buildCtx = (results: Record) => { + const sys = vi.fn() + const calls: Array<{ method: string; params: unknown }> = [] + + const rpc = vi.fn((method: string, params: unknown) => { + calls.push({ method, params }) + + return Promise.resolve(results[method]) + }) + + const ctx = { + gateway: { rpc }, + guarded, + guardedErr: vi.fn(), + sid: 'sid-1', + stale: () => false, + transcript: { page: vi.fn(), panel: vi.fn(), sys } + } + + const run = async (arg: string) => { + billingCommand.run(arg, ctx as any, 'billing') + await rpc.mock.results[0]?.value + await Promise.resolve() + await Promise.resolve() + } + + return { calls, ctx, rpc, run, sys } +} + +const printed = (sys: ReturnType) => sys.mock.calls.map(c => c[0]).join('\n') + +describe('/billing slash command (overlay-driven)', () => { + beforeEach(() => { + resetOverlayState() + }) + + it('not logged in → prompts to log in, no overlay', async () => { + const { run, sys } = buildCtx({ 'billing.state': { ...ownerState(), logged_in: false, ok: true } }) + await run('') + expect(printed(sys)).toContain('Not logged into Nous Portal') + expect(getOverlayState().billing).toBeNull() + }) + + it('bare /billing opens the overlay on the overview screen with state', async () => { + const { run, rpc } = buildCtx({ 'billing.state': ownerState() }) + await run('') + expect(rpc).toHaveBeenCalledWith('billing.state', {}) + const billing = getOverlayState().billing + expect(billing).toBeTruthy() + expect(billing?.screen).toBe('overview') + expect(billing?.state.balance_display).toBe('$142.50') + expect(billing?.state.charge_presets_display).toEqual(['$25', '$50', '$100']) + }) + + it('any sub-command arg is ignored — still opens the overview overlay', async () => { + const { run } = buildCtx({ 'billing.state': ownerState() }) + await run('buy 100') + const billing = getOverlayState().billing + expect(billing?.screen).toBe('overview') + // No confirm overlay armed directly by the command anymore. + expect(getOverlayState().confirm).toBeNull() + }) + + it('member overview carries the non-admin state for component-side gating', async () => { + const { run } = buildCtx({ + 'billing.state': ownerState({ + is_admin: false, + can_charge: false, + role: 'MEMBER', + card: null, + monthly_cap: null, + auto_reload: null + }) + }) + + await run('') + const billing = getOverlayState().billing + expect(billing?.state.is_admin).toBe(false) + expect(billing?.screen).toBe('overview') + }) + + // ── Overlay ctx behaviors (RPC + error mapping live in billing.ts) ── + + it('ctx.validate rejects out-of-bounds and sub-cent amounts, accepts valid', async () => { + const { run } = buildCtx({ 'billing.state': ownerState() }) + await run('') + const ctx = getOverlayState().billing!.ctx + expect(ctx.validate('5').error).toContain('Minimum is $10') + expect(ctx.validate('10.005').error).toContain('2 decimal places') + expect(ctx.validate('100').amount).toBe('100') + expect(ctx.validate('$50').amount).toBe('50') + }) + + it('ctx.charge → poll → settled', async () => { + vi.useFakeTimers() + + try { + const { run, sys } = buildCtx({ + 'billing.state': ownerState(), + 'billing.charge': { ok: true, charge_id: 'ch_1', idempotency_key: 'k' }, + 'billing.charge_status': { ok: true, status: 'settled', amount_usd: '100' } + }) + + await run('') + const ctx = getOverlayState().billing!.ctx + ctx.charge('100') + await vi.runAllTimersAsync() + const out = printed(sys) + expect(out).toContain('Charge submitted') + expect(out).toContain('✅ $100 added.') + } finally { + vi.useRealTimers() + } + }) + + it('ctx.charge → poll → failed adds the portal funnel line', async () => { + vi.useFakeTimers() + + try { + const { run, sys } = buildCtx({ + 'billing.state': ownerState(), + 'billing.charge': { ok: true, charge_id: 'ch_1', idempotency_key: 'k' }, + 'billing.charge_status': { ok: true, status: 'failed', reason: 'card_declined' } + }) + + await run('') + getOverlayState().billing!.ctx.charge('100') + await vi.runAllTimersAsync() + const out = printed(sys) + expect(out).toContain('Your card was declined') + // Parity with the CLI: a failed poll funnels to the portal (from state.portal_url). + expect(out).toContain('Portal: https://portal/billing?topup=open') + } finally { + vi.useRealTimers() + } + }) + + it('ctx.charge monthly_cap_exceeded surfaces remaining headroom', async () => { + const { run, sys } = buildCtx({ + 'billing.state': ownerState(), + 'billing.charge': { + ok: false, + error: 'monthly_cap_exceeded', + message: 'Monthly spend cap reached.', + payload: { remainingUsd: '42.50' }, + portal_url: '/billing?topup=open', + idempotency_key: 'k' + } + }) + + await run('') + getOverlayState().billing!.ctx.charge('100') + await Promise.resolve() + await Promise.resolve() + const out = printed(sys) + expect(out).toContain('Monthly spend cap reached — $42.50 headroom left.') + expect(out).toContain('Portal: /billing?topup=open') + }) + + it('ctx.charge no_payment_method → portal funnel copy', async () => { + const { run, sys } = buildCtx({ + 'billing.state': ownerState(), + 'billing.charge': { + ok: false, + error: 'no_payment_method', + portal_url: '/billing?topup=open', + idempotency_key: 'k' + } + }) + + await run('') + getOverlayState().billing!.ctx.charge('100') + await Promise.resolve() + await Promise.resolve() + const out = printed(sys) + expect(out).toContain('No saved card for terminal charges') + expect(out).toContain('Portal: /billing?topup=open') + }) + + it('ctx.charge insufficient_scope → arms step-up confirm', async () => { + const { run } = buildCtx({ + 'billing.state': ownerState(), + 'billing.charge': { ok: false, error: 'insufficient_scope', idempotency_key: 'k' } + }) + + await run('') + getOverlayState().billing!.ctx.charge('100') + await Promise.resolve() + await Promise.resolve() + // The charge failed with insufficient_scope → a NEW confirm (step-up) is armed. + const stepUp = getOverlayState().confirm + expect(stepUp?.title).toBe('Grant terminal billing access?') + }) + + it('ctx.applyAutoReload(true, …) → billing.auto_reload RPC, resolves true', async () => { + const { run, calls } = buildCtx({ + 'billing.state': ownerState(), + 'billing.auto_reload': { ok: true } + }) + + await run('') + const ok = await getOverlayState().billing!.ctx.applyAutoReload(true, 20, 100) + expect(ok).toBe(true) + const ar = calls.find(c => c.method === 'billing.auto_reload') + expect(ar?.params).toEqual({ enabled: true, threshold: 20, top_up_amount: 100 }) + }) + + it('ctx.applyAutoReload(false) → disables (enabled:false, no amounts)', async () => { + const { run, calls } = buildCtx({ + 'billing.state': ownerState({ + auto_reload: { + enabled: true, + reload_to_display: '$100', + reload_to_usd: '100', + threshold_display: '$20', + threshold_usd: '20' + } + }), + 'billing.auto_reload': { ok: true } + }) + + await run('') + const ok = await getOverlayState().billing!.ctx.applyAutoReload(false) + expect(ok).toBe(true) + const ar = calls.find(c => c.method === 'billing.auto_reload') + expect(ar?.params).toEqual({ enabled: false }) + }) + + it('ctx.applyAutoReload error → resolves false + maps the error', async () => { + const { run, sys } = buildCtx({ + 'billing.state': ownerState(), + 'billing.auto_reload': { ok: false, error: 'monthly_cap_exceeded', message: 'Monthly spend cap reached.' } + }) + + await run('') + const ok = await getOverlayState().billing!.ctx.applyAutoReload(true, 20, 100) + expect(ok).toBe(false) + expect(printed(sys)).toContain('Monthly spend cap reached.') + }) + + it('ctx.openPortal opens the URL + echoes a transcript line', async () => { + const { run, sys } = buildCtx({ 'billing.state': ownerState() }) + await run('') + getOverlayState().billing!.ctx.openPortal('https://portal/x') + expect(printed(sys)).toContain('Opening portal: https://portal/x') + }) +}) diff --git a/ui-tui/src/__tests__/createGatewayEventHandler.test.ts b/ui-tui/src/__tests__/createGatewayEventHandler.test.ts index 43be458caa0..7e6c7a891ae 100644 --- a/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +++ b/ui-tui/src/__tests__/createGatewayEventHandler.test.ts @@ -8,6 +8,13 @@ import { getUiState, patchUiState, resetUiState } from '../app/uiStore.js' import { estimateTokensRough } from '../lib/text.js' import type { Msg } from '../types.js' +// Mock the external-URL opener so the billing.step_up.verification test can +// assert it's invoked without spawning a real browser process. +const openExternalUrlMock = vi.fn((_url: string) => true) +vi.mock('../lib/openExternalUrl.js', () => ({ + openExternalUrl: (url: string) => openExternalUrlMock(url) +})) + const ref = (current: T) => ({ current }) const buildCtx = (appended: Msg[]) => @@ -1561,4 +1568,34 @@ describe('createGatewayEventHandler', () => { expect(getUiState().notice).toBeNull() }) }) + + describe('billing.step_up.verification', () => { + beforeEach(() => { + openExternalUrlMock.mockClear() + }) + + it('renders the verification link + code and opens the browser', () => { + const ctx = buildCtx([]) + const onEvent = createGatewayEventHandler(ctx) + + onEvent({ + payload: { user_code: 'WXYZ-9999', verification_url: 'https://portal.example/device?code=WXYZ' }, + type: 'billing.step_up.verification' + } as any) + + const printed = (ctx.system.sys as ReturnType).mock.calls.map(c => c[0]).join('\n') + expect(printed).toContain('https://portal.example/device?code=WXYZ') + expect(printed).toContain('WXYZ-9999') + expect(openExternalUrlMock).toHaveBeenCalledWith('https://portal.example/device?code=WXYZ') + }) + + it('no-ops on a missing verification_url (never opens a browser)', () => { + const ctx = buildCtx([]) + const onEvent = createGatewayEventHandler(ctx) + + onEvent({ payload: { verification_url: '' }, type: 'billing.step_up.verification' } as any) + + expect(openExternalUrlMock).not.toHaveBeenCalled() + }) + }) }) diff --git a/ui-tui/src/app/createGatewayEventHandler.ts b/ui-tui/src/app/createGatewayEventHandler.ts index f7f4293e16d..de2f774f149 100644 --- a/ui-tui/src/app/createGatewayEventHandler.ts +++ b/ui-tui/src/app/createGatewayEventHandler.ts @@ -10,6 +10,7 @@ import type { SessionMostRecentResponse } from '../gatewayTypes.js' import { rpcErrorMessage } from '../lib/rpc.js' +import { openExternalUrl } from '../lib/openExternalUrl.js' import { topLevelSubagents } from '../lib/subagentTree.js' import { formatAbandonedClarify, formatToolCall, stripAnsi } from '../lib/text.js' import { fromSkin } from '../theme.js' @@ -533,6 +534,29 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev: turnController.clearNotice(ev.payload?.key) return + case 'billing.step_up.verification': { + // The billing step-up device flow runs in the headless gateway, so it + // can't open a browser or print the URL where the user sees it. Surface + // the link here (clickable/copyable in the transcript) and best-effort + // open it via the TUI process's own opener. This event arrives while the + // billing.step_up RPC is still polling (and may even outlive the RPC's + // 120s timeout), so the link — not the RPC result — is the source of truth. + const url = ev.payload.verification_url + const code = ev.payload.user_code + + if (!url) { + return + } + + sys('💳 Open this link to grant terminal billing access:') + sys(url) + if (code) { + sys(`If prompted, enter code: ${code}`) + } + void openExternalUrl(url) + + return + } case 'gateway.stderr': { const line = String(ev.payload.line).slice(0, 120) diff --git a/ui-tui/src/app/interfaces.ts b/ui-tui/src/app/interfaces.ts index f7297c151da..f570cf2b6ab 100644 --- a/ui-tui/src/app/interfaces.ts +++ b/ui-tui/src/app/interfaces.ts @@ -3,7 +3,7 @@ import type { MutableRefObject, ReactNode, RefObject, SetStateAction } from 'rea import type { PasteEvent } from '../components/textInput.js' import type { GatewayClient } from '../gatewayClient.js' -import type { ImageAttachResponse, SessionCloseResponse } from '../gatewayTypes.js' +import type { BillingStateResponse, ImageAttachResponse, SessionCloseResponse } from '../gatewayTypes.js' import type { ParsedVoiceRecordKey } from '../lib/platform.js' import type { RpcResult } from '../lib/rpc.js' import type { Theme } from '../theme.js' @@ -85,10 +85,53 @@ export interface GatewayProviderProps { value: GatewayServices } +// ── Billing overlay (Phase 2b: full-modal TUI parity) ──────────────── +// The /billing command no longer parses sub-commands; bare `/billing` +// fetches `billing.state` and opens this overlay. The overlay is a small +// state machine (overview → buy|autoreload|limit → confirm) that performs +// the SAME RPCs as the old slash flows (billing.charge / charge_status / +// auto_reload / step_up). Backend is unchanged & shared with the CLI. + +export type BillingScreen = 'autoreload' | 'buy' | 'confirm' | 'limit' | 'overview' + +/** + * The functions the overlay needs to talk to the gateway and emit + * transcript lines. Built once in `billing.ts` (closing over the live + * SlashRunCtx) and stashed in the overlay slot, mirroring how a ConfirmReq + * stashes its `onConfirm` closure. Keeps all RPC + error-mapping logic in + * billing.ts (single source of truth) — the overlay only renders + routes. + */ +export interface BillingOverlayCtx { + /** Run `billing.auto_reload` (enabled/threshold/top_up) → resolve ok/false. */ + applyAutoReload: (enabled: boolean, threshold?: number, topUp?: number) => Promise + /** Submit `billing.charge` for `amount` and poll to settlement (non-blocking). */ + charge: (amount: string) => void + /** Open the portal in the browser + echo a transcript line. */ + openPortal: (url: string) => void + /** Emit a transcript system line. */ + sys: (text: string) => void + /** Validate a custom amount against state bounds + 2dp (mirrors the server). */ + validate: (raw: string) => { amount?: string; error?: string } +} + +/** Pending confirm built when leaving the buy/autoreload screen. */ +export interface BillingPendingCharge { + amount: string +} + +export interface BillingOverlayState { + ctx: BillingOverlayCtx + /** Set when on the 'confirm' screen for a buy. */ + pendingCharge?: BillingPendingCharge | null + screen: BillingScreen + state: BillingStateResponse +} + export interface OverlayState { agents: boolean agentsInitialHistoryIndex: number approval: ApprovalReq | null + billing: BillingOverlayState | null clarify: ClarifyReq | null confirm: ConfirmReq | null modelPicker: boolean diff --git a/ui-tui/src/app/overlayStore.ts b/ui-tui/src/app/overlayStore.ts index 82c1629ab08..c0290d71cab 100644 --- a/ui-tui/src/app/overlayStore.ts +++ b/ui-tui/src/app/overlayStore.ts @@ -6,6 +6,7 @@ const buildOverlayState = (): OverlayState => ({ agents: false, agentsInitialHistoryIndex: 0, approval: null, + billing: null, clarify: null, confirm: null, modelPicker: false, @@ -21,9 +22,20 @@ export const $overlayState = atom(buildOverlayState()) export const $isBlocked = computed( $overlayState, - ({ agents, approval, clarify, confirm, modelPicker, pager, pluginsHub, secret, sessions, skillsHub, sudo }) => + ({ agents, approval, billing, clarify, confirm, modelPicker, pager, pluginsHub, secret, sessions, skillsHub, sudo }) => Boolean( - agents || approval || clarify || confirm || modelPicker || pager || pluginsHub || secret || sessions || skillsHub || sudo + agents || + approval || + billing || + clarify || + confirm || + modelPicker || + pager || + pluginsHub || + secret || + sessions || + skillsHub || + sudo ) ) diff --git a/ui-tui/src/app/slash/commands/billing.ts b/ui-tui/src/app/slash/commands/billing.ts new file mode 100644 index 00000000000..6c3ddec0845 --- /dev/null +++ b/ui-tui/src/app/slash/commands/billing.ts @@ -0,0 +1,332 @@ +import type { + BillingChargeResponse, + BillingChargeStatusResponse, + BillingErrorPayload, + BillingMutationResponse, + BillingStateResponse +} from '../../../gatewayTypes.js' +import { openExternalUrl } from '../../../lib/openExternalUrl.js' +import type { BillingOverlayCtx } from '../../interfaces.js' +import { patchOverlayState } from '../../overlayStore.js' +import type { SlashCommand, SlashRunCtx } from '../types.js' + +// Poll cadence (plan §5, frozen): 2s interval, 5-minute cap. +const POLL_INTERVAL_MS = 2000 +const POLL_CAP_MS = 5 * 60 * 1000 + +type Sys = (text: string) => void + +/** Map a typed billing error envelope to user-facing copy + portal funnel. */ +const renderBillingError = ( + sys: Sys, + ctx: SlashRunCtx, + env: { + error?: string + message?: string + payload?: BillingErrorPayload + portal_url?: string | null + retry_after?: number | null + } +): void => { + const portal = env.portal_url + + switch (env.error) { + case 'insufficient_scope': + armStepUp(sys, ctx) + + return + + case 'no_payment_method': + sys( + '💳 No saved card for terminal charges yet. Set one up on the portal ' + + "(one-time credit buys don't save a reusable card)." + ) + + break + + case 'cli_billing_disabled': + sys('🔴 Terminal billing is turned off for this org — an admin must enable it on the portal.') + + break + + case 'monthly_cap_exceeded': { + // Surface the remaining headroom the server attaches (parity with the CLI). + const remaining = env.payload?.remainingUsd + sys(remaining != null ? `🔴 Monthly spend cap reached — $${remaining} headroom left.` : '🔴 Monthly spend cap reached.') + + break + } + case 'rate_limited': { + const mins = env.retry_after ? ` (try again in ~${Math.max(1, Math.round(env.retry_after / 60))} min)` : '' + sys(`🟡 Too many charges right now${mins}. This isn't a payment failure.`) + + break + } + + default: + sys(`🔴 ${env.message || env.error || 'Billing request failed.'}`) + } + + if (portal) { + sys(`Portal: ${portal}`) + } +} + +/** 403 insufficient_scope → arm a ConfirmReq that runs the lazy step-up. */ +const armStepUp = (sys: Sys, ctx: SlashRunCtx): void => { + sys('💳 Terminal billing needs an extra permission (billing:manage).') + patchOverlayState({ + confirm: { + cancelLabel: 'Not now', + confirmLabel: 'Re-authorize', + detail: 'An org admin/owner must tick "Allow terminal billing" in the portal.', + onConfirm: () => { + // session_id lets the gateway route the billing.step_up.verification + // event (the verification link) back to this session — the device flow + // runs headless in the gateway, so the link can't be printed there. + ctx.gateway + .rpc('billing.step_up', { session_id: ctx.sid ?? undefined }) + .then( + ctx.guarded(r => { + if (r.ok && r.granted) { + // Step-up only grants the billing:manage TOKEN scope — the ORG + // kill-switch (cli_billing_enabled) is a separate gate. Re-fetch + // /state so we don't over-promise "enabled" when a charge would + // still hit cli_billing_disabled. + sys('✅ Billing permission granted.') + ctx.gateway + .rpc('billing.state', {}) + .then( + ctx.guarded(s => { + if (s.cli_billing_enabled) { + sys('Run /billing again to continue.') + } else { + sys( + '🟡 Permission granted, but terminal billing is still turned off ' + + 'for this org. Enable it in the portal, then run /billing again.' + ) + if (s.portal_url) { + sys(`Portal: ${s.portal_url}`) + } + } + }) + ) + .catch(() => { + sys('Run /billing again to continue.') + }) + } else { + sys('🟡 Terminal billing was not granted (an admin must tick the box).') + } + }) + ) + .catch(() => { + // The device flow can outlive the RPC's 120s timeout while the user + // is still authorizing in the browser. A reject here is NOT a hard + // failure — the grant (if it lands) is persisted gateway-side; tell + // the user to re-run /billing rather than reporting an error. + sys('🟡 Still waiting on approval — finish in the browser, then run /billing again.') + }) + }, + title: 'Grant terminal billing access?' + } + }) +} + +/** Poll a charge to a terminal state (settled/failed/timeout). Non-blocking. */ +const pollCharge = (sys: Sys, ctx: SlashRunCtx, chargeId: string, portalUrl?: string | null): void => { + const start = Date.now() + + const tick = (): void => { + if (ctx.stale()) { + return + } + + ctx.gateway + .rpc('billing.charge_status', { charge_id: chargeId }) + .then( + ctx.guarded(r => { + if (!r.ok) { + // 429/503 while polling = retry-after, NOT a failure. Back off + continue. + if (r.error === 'rate_limited') { + const wait = (r.retry_after ?? 5) * 1000 + setTimeout(tick, Math.min(wait, 30000)) + + return + } + + sys(`🔴 Could not check the charge: ${r.message || r.error || 'error'}`) + + return + } + + if (r.status === 'settled') { + sys(`✅ ${r.amount_usd ? `$${r.amount_usd}` : 'Credits'} added.`) + + return + } + + if (r.status === 'failed') { + renderChargeFailed(sys, r.reason, portalUrl) + + return + } + + // pending → keep polling until the 5-min cap, then call it a timeout. + if (Date.now() - start >= POLL_CAP_MS) { + sys( + '🟡 Still processing after 5 minutes — this is a timeout, not a failure. ' + + 'Check /billing or the portal shortly.' + ) + if (portalUrl) { + sys(`Portal: ${portalUrl}`) + } + + return + } + + setTimeout(tick, POLL_INTERVAL_MS) + }) + ) + .catch(ctx.guardedErr) + } + + tick() +} + +const renderChargeFailed = (sys: Sys, reason?: string | null, portalUrl?: string | null): void => { + switch ((reason || '').trim()) { + case 'authentication_required': + sys('🔴 Your bank requires verification (3DS). Complete it on the portal to finish this purchase.') + + break + + case 'payment_method_expired': + sys('🔴 Your card has expired. Update it on the portal.') + + break + + case 'card_declined': + sys('🔴 Your card was declined. Try another card on the portal.') + + break + + default: + sys(`🔴 The charge didn't go through (${reason || 'processing_error'}).`) + } + + // Funnel to the portal after any failure (parity with cli.py _billing_portal_hint). + if (portalUrl) { + sys(`Portal: ${portalUrl}`) + } +} + +/** Validate a custom amount against state bounds + 2dp, mirroring the server. */ +const validateAmount = (raw: string, s: BillingStateResponse): { amount?: string; error?: string } => { + const cleaned = raw.trim().replace(/^\$/, '').trim() + + if (!cleaned || !/^\d+(\.\d{1,2})?$/.test(cleaned)) { + return { error: 'Enter a dollar amount, e.g. 100 (max 2 decimal places).' } + } + + const value = Number(cleaned) + + if (!(value > 0)) { + return { error: 'Amount must be greater than $0.' } + } + + if (s.min_usd != null && value < Number(s.min_usd)) { + return { error: `Minimum is $${s.min_usd}.` } + } + + if (s.max_usd != null && value > Number(s.max_usd)) { + return { error: `Maximum is $${s.max_usd}.` } + } + + return { amount: cleaned } +} + +/** + * Build the closure bundle the BillingOverlay needs to talk to the gateway + * and emit transcript lines. Keeps ALL RPC + error-mapping logic here + * (single source of truth) — the overlay only renders + routes keys. + */ +const buildOverlayCtx = (ctx: SlashRunCtx, sys: Sys, s: BillingStateResponse): BillingOverlayCtx => ({ + applyAutoReload: (enabled, threshold, topUp) => + ctx.gateway + .rpc('billing.auto_reload', { + enabled, + ...(threshold != null ? { threshold } : {}), + ...(topUp != null ? { top_up_amount: topUp } : {}) + }) + .then(r => { + if (r && r.ok) { + return true + } + + if (r) { + renderBillingError(sys, ctx, r) + } + + return false + }) + .catch(e => { + ctx.guardedErr(e) + + return false + }), + charge: (amount: string) => { + sys('💳 Charge submitted — confirming settlement…') + ctx.gateway + .rpc('billing.charge', { amount_usd: amount }) + .then( + ctx.guarded(r => { + if (r.ok && r.charge_id) { + pollCharge(sys, ctx, r.charge_id, s.portal_url) + } else { + renderBillingError(sys, ctx, r) + } + }) + ) + .catch(ctx.guardedErr) + }, + openPortal: (url: string) => { + openExternalUrl(url) + sys(`Opening portal: ${url}`) + }, + sys, + validate: (raw: string) => validateAmount(raw, s) +}) + +export const billingCommands: SlashCommand[] = [ + { + help: 'Manage Nous terminal billing — buy credits, auto-reload, limits', + name: 'billing', + // ZERO sub-commands (plan §0.4): any arg is ignored. Bare `/billing` + // fetches state and opens the interactive overlay (CLI/TUI parity). + run: (_arg, ctx) => { + const sys: Sys = ctx.transcript.sys + + ctx.gateway + .rpc('billing.state', {}) + .then( + ctx.guarded(s => { + if (!s.logged_in) { + sys('💳 Not logged into Nous Portal — run /portal to log in, then /billing.') + + return + } + + patchOverlayState({ + billing: { + ctx: buildOverlayCtx(ctx, sys, s), + pendingCharge: null, + screen: 'overview', + state: s + } + }) + }) + ) + .catch(ctx.guardedErr) + } + } +] diff --git a/ui-tui/src/app/slash/registry.ts b/ui-tui/src/app/slash/registry.ts index 7f2d95195f4..c9192f5d56d 100644 --- a/ui-tui/src/app/slash/registry.ts +++ b/ui-tui/src/app/slash/registry.ts @@ -1,4 +1,5 @@ import { coreCommands } from './commands/core.js' +import { billingCommands } from './commands/billing.js' import { creditsCommands } from './commands/credits.js' import { debugCommands } from './commands/debug.js' import { opsCommands } from './commands/ops.js' @@ -8,6 +9,7 @@ import type { SlashCommand } from './types.js' export const SLASH_COMMANDS: SlashCommand[] = [ ...coreCommands, + ...billingCommands, ...creditsCommands, ...sessionCommands, ...opsCommands, diff --git a/ui-tui/src/app/useInputHandlers.ts b/ui-tui/src/app/useInputHandlers.ts index 4e8dac7e3c2..20d3493f547 100644 --- a/ui-tui/src/app/useInputHandlers.ts +++ b/ui-tui/src/app/useInputHandlers.ts @@ -147,6 +147,10 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult { return patchOverlayState({ modelPicker: false }) } + if (overlay.billing) { + return patchOverlayState({ billing: null }) + } + if (overlay.skillsHub) { return patchOverlayState({ skillsHub: false }) } @@ -272,7 +276,7 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult { // answering felt like the prompt had locked the entire UI. Explicitly // skip the prompt-overlay early-return for scroll keys so they fall // through to the wheel / PageUp / Shift+arrow handlers below. - const promptOverlay = overlay.approval || overlay.clarify || overlay.confirm + const promptOverlay = overlay.approval || overlay.billing || overlay.clarify || overlay.confirm const fallThroughForScroll = promptOverlay && shouldFallThroughForScroll(key) if (promptOverlay && !fallThroughForScroll) { diff --git a/ui-tui/src/components/appOverlays.tsx b/ui-tui/src/components/appOverlays.tsx index 94dab304621..a7336d08f33 100644 --- a/ui-tui/src/components/appOverlays.tsx +++ b/ui-tui/src/components/appOverlays.tsx @@ -8,6 +8,7 @@ import { $uiSessionId, $uiTheme } from '../app/uiStore.js' import { ActiveSessionSwitcher } from './activeSessionSwitcher.js' import { FloatBox } from './appChrome.js' +import { BillingOverlay } from './billingOverlay.js' import { MaskedPrompt } from './maskedPrompt.js' import { ModelPicker } from './modelPicker.js' import { OverlayHint } from './overlayControls.js' @@ -35,6 +36,21 @@ export function PromptZone({ ) } + if (overlay.billing) { + const current = overlay.billing + + const onPatch = (next: Partial) => + patchOverlayState(prev => (prev.billing ? { ...prev, billing: { ...prev.billing, ...next } } : prev)) + + const onClose = () => patchOverlayState({ billing: null }) + + return ( + + + + ) + } + if (overlay.confirm) { const req = overlay.confirm diff --git a/ui-tui/src/components/billingOverlay.tsx b/ui-tui/src/components/billingOverlay.tsx new file mode 100644 index 00000000000..6fbe9cddc5f --- /dev/null +++ b/ui-tui/src/components/billingOverlay.tsx @@ -0,0 +1,684 @@ +import { Box, Text, useInput } from '@hermes/ink' +import { useState } from 'react' + +import type { BillingOverlayState } from '../app/interfaces.js' +import type { BillingStateResponse } from '../gatewayTypes.js' +import type { Theme } from '../theme.js' + +import { TextInput } from './textInput.js' + +const SPEND_BAR_CELLS = 10 + +interface BillingOverlayProps { + /** Replace the overlay slot (screen transitions + pending data). */ + onPatch: (next: Partial) => void + /** Close the overlay entirely. */ + onClose: () => void + overlay: BillingOverlayState + t: Theme +} + +/** A numbered menu row with the ▸ cursor (mirrors ClarifyPrompt). */ +function MenuRow({ active, index, label, t }: { active: boolean; index: number; label: string; t: Theme }) { + return ( + + + {active ? '▸ ' : ' '} + {index}. {label} + + + ) +} + +/** Plain (non-numbered) action row with the ▸ cursor (confirm screens). */ +function ActionRow({ active, label, color, t }: { active: boolean; label: string; color?: string; t: Theme }) { + return ( + + {active ? '▸ ' : ' '} + + {label} + + + ) +} + +/** 10-cell spend bar + percent (omit entirely when there's no usable cap). */ +function spendBar(s: BillingStateResponse): null | string { + const cap = s.monthly_cap + + if (!cap || cap.limit_usd == null) { + return null + } + + const limit = Number(cap.limit_usd) + const spent = Number(cap.spent_this_month_usd ?? '0') + + if (!(limit > 0) || Number.isNaN(spent)) { + return null + } + + const ratio = Math.max(0, Math.min(1, spent / limit)) + const filled = Math.round(ratio * SPEND_BAR_CELLS) + const bar = '█'.repeat(filled) + '░'.repeat(SPEND_BAR_CELLS - filled) + const pct = Math.round(ratio * 100) + const ceiling = cap.is_default_ceiling ? ' (default ceiling)' : '' + + return `${cap.spent_display} of ${cap.limit_display} used ${bar} ${pct}%${ceiling}` +} + +function autoReloadLine(s: BillingStateResponse): null | string { + if (!s.auto_reload) { + return null + } + + return s.auto_reload.enabled + ? `Auto-reload: on (below ${s.auto_reload.threshold_display} → ${s.auto_reload.reload_to_display})` + : 'Auto-reload: off' +} + +const footer = (extra: string, t: Theme) => {extra} + +/** + * The /billing modal. A self-contained state machine: + * overview → buy | autoreload | limit (and buy → confirm). + * Esc from a sub-screen returns to overview; Esc from overview closes. + * All RPCs + error mapping live in billing.ts and are reached through + * `overlay.ctx` — this component only renders + routes keys. + */ +export function BillingOverlay({ onClose, onPatch, overlay, t }: BillingOverlayProps) { + const { ctx, screen, state: s } = overlay + + return ( + + {screen === 'overview' && } + {screen === 'buy' && } + {screen === 'confirm' && ( + onPatch({ pendingCharge: null, screen: 'buy' })} + onClose={onClose} + s={s} + t={t} + /> + )} + {screen === 'autoreload' && } + {screen === 'limit' && } + + ) +} + +// ── Screen 1: Overview ──────────────────────────────────────────────── + +interface ScreenProps { + ctx: BillingOverlayState['ctx'] + onClose: () => void + onPatch: (next: Partial) => void + s: BillingStateResponse + t: Theme +} + +function OverviewScreen({ ctx, onClose, onPatch, s, t }: ScreenProps) { + // Gate: full menu only for an admin with the kill-switch on. Otherwise the + // menu collapses to Manage-on-portal / Cancel + a one-line note. + const full = s.is_admin && s.cli_billing_enabled + + const note = !s.is_admin + ? 'Billing actions need an org admin/owner.' + : !s.cli_billing_enabled + ? 'Terminal billing is off for this org — enable it on the portal.' + : null + + // Optimistic funnel: admin + kill-switch on but no saved card → a charge will + // 403 no_payment_method. Advise up front (Buy stays available — /state.card + // can't fully prove CLI-chargeability, so we hint rather than hide). + const cardHint = full && !s.card ? 'No saved card for terminal charges yet — set one up on the portal first.' : null + + const items = full + ? ['Buy credits', 'Adjust auto-reload', 'Adjust monthly limit', 'Manage on portal', 'Cancel'] + : ['Manage on portal', 'Cancel'] + + const [sel, setSel] = useState(0) + + const choose = (i: number) => { + if (full) { + if (i === 0) { + onPatch({ screen: 'buy' }) + } else if (i === 1) { + onPatch({ screen: 'autoreload' }) + } else if (i === 2) { + onPatch({ screen: 'limit' }) + } else if (i === 3) { + if (s.portal_url) { + ctx.openPortal(s.portal_url) + } + + onClose() + } else { + onClose() + } + } else { + if (i === 0 && s.portal_url) { + ctx.openPortal(s.portal_url) + } + + onClose() + } + } + + useInput((ch, key) => { + if (key.escape) { + return onClose() + } + + if (key.upArrow && sel > 0) { + setSel(v => v - 1) + } + + if (key.downArrow && sel < items.length - 1) { + setSel(v => v + 1) + } + + if (key.return) { + return choose(sel) + } + + const n = parseInt(ch, 10) + + if (n >= 1 && n <= items.length) { + return choose(n - 1) + } + }) + + const bar = spendBar(s) + const auto = autoReloadLine(s) + + return ( + + + Usage credits + + {bar && {bar}} + Balance: {s.balance_display} + {auto && {auto}} + {s.org_name && ( + + Org: {s.org_name} + {s.role ? ` · ${s.role}` : ''} + + )} + {note && ( + + {note} + + )} + {cardHint && ( + + {cardHint} + + )} + {cardHint && s.portal_url && Portal: {s.portal_url}} + + + {items.map((label, i) => ( + + ))} + + + {footer(`↑/↓ select · 1-${items.length} quick pick · Enter confirm · Esc close`, t)} + + ) +} + +// ── Screen 2: Buy credits ───────────────────────────────────────────── + +function BuyScreen({ ctx, onPatch, s, t }: ScreenProps) { + const presets = s.charge_presets_display + const rawPresets = s.charge_presets + // rows: [...presets, 'Custom amount…', 'Cancel'] + const rows = [...presets, 'Custom amount…', 'Cancel'] + const customIdx = presets.length + + const [sel, setSel] = useState(0) + const [typing, setTyping] = useState(false) + const [custom, setCustom] = useState('') + const [error, setError] = useState(null) + + const toConfirm = (amount: string) => { + onPatch({ pendingCharge: { amount }, screen: 'confirm' }) + } + + const pickPreset = (i: number) => { + // Prefer the raw (numeric) preset for the amount; fall back to stripping $. + const raw = (rawPresets[i] ?? presets[i] ?? '').replace(/^\$/, '').trim() + const v = ctx.validate(raw) + + if (v.error || !v.amount) { + setError(v.error ?? 'Invalid preset.') + + return + } + + toConfirm(v.amount) + } + + const submitCustom = (raw: string) => { + const v = ctx.validate(raw) + + if (v.error || !v.amount) { + setError(v.error ?? 'Invalid amount.') + + return + } + + toConfirm(v.amount) + } + + const choose = (i: number) => { + if (i < presets.length) { + pickPreset(i) + } else if (i === customIdx) { + setError(null) + setTyping(true) + } else { + onPatch({ screen: 'overview' }) + } + } + + useInput((ch, key) => { + if (key.escape) { + return typing ? (setTyping(false), setError(null)) : onPatch({ screen: 'overview' }) + } + + if (typing) { + return + } + + if (key.upArrow && sel > 0) { + setSel(v => v - 1) + } + + if (key.downArrow && sel < rows.length - 1) { + setSel(v => v + 1) + } + + if (key.return) { + return choose(sel) + } + + const n = parseInt(ch, 10) + + if (n >= 1 && n <= rows.length) { + return choose(n - 1) + } + }) + + const payLine = s.card ? `Payment: ${s.card.masked}` : 'No saved card on file' + + if (typing) { + return ( + + + Buy usage credits + + {payLine} + + Enter a custom amount: + + {'$'} + + + {error && {error}} + + {footer('Enter confirm · Esc back', t)} + + ) + } + + return ( + + + Buy usage credits + + {payLine} + + {rows.map((label, i) => ( + + ))} + {error && {error}} + + {footer(`↑/↓ select · 1-${rows.length} quick pick · Enter confirm · Esc back`, t)} + + ) +} + +// ── Screen 3: Confirm purchase ──────────────────────────────────────── + +function ConfirmScreen({ + amount, + ctx, + onBack, + onClose, + s, + t +}: { + amount: string + ctx: BillingOverlayState['ctx'] + onBack: () => void + onClose: () => void + s: BillingStateResponse + t: Theme +}) { + // rows: Pay $X now / Cancel + const [sel, setSel] = useState(0) + + const pay = () => { + ctx.charge(amount) + // Settlement is reported via transcript lines; close the overlay now. + onClose() + } + + const back = () => onBack() + + useInput((ch, key) => { + if (key.escape) { + return back() + } + + const lower = ch.toLowerCase() + + if (lower === 'y') { + return pay() + } + + if (lower === 'n') { + return back() + } + + if (key.upArrow) { + setSel(0) + } + + if (key.downArrow) { + setSel(1) + } + + if (key.return) { + return sel === 0 ? pay() : back() + } + }) + + const payLine = s.card ? `Payment: ${s.card.masked}` : 'No saved card on file' + + return ( + + + Confirm purchase + + Total: ${amount} + {payLine} + By confirming, you allow Nous Research to charge your card. + + + + + {footer('↑/↓ select · Enter confirm · Y/N quick · Esc back', t)} + + ) +} + +// ── Screen 4: Auto-reload (the 2-field form) ────────────────────────── + +function AutoReloadScreen({ ctx, onClose, onPatch, s, t }: ScreenProps) { + const ar = s.auto_reload + const enabled = Boolean(ar?.enabled) + + // Prefill from state (strip the $ from the *_usd raw fields if present). + const prefill = (raw?: null | string) => (raw == null ? '' : String(raw).replace(/^\$/, '').trim()) + const [threshold, setThreshold] = useState(prefill(ar?.threshold_usd)) + const [reloadTo, setReloadTo] = useState(prefill(ar?.reload_to_usd)) + const [field, setField] = useState<'reloadTo' | 'threshold'>('threshold') + const [error, setError] = useState(null) + // focusRow: 0=threshold field, 1=reloadTo field, 2=Agree, 3=Turn off (if enabled), last=Cancel + const actionRows = enabled ? ['Agree and turn on', 'Turn off', 'Cancel'] : ['Agree and turn on', 'Cancel'] + const FIELD_ROWS = 2 + const [row, setRow] = useState(0) + + const noCard = !s.card + + const validatePair = (): null | { reloadTo: string; threshold: string } => { + const tv = ctx.validate(threshold) + + if (tv.error || !tv.amount) { + setError(`Threshold: ${tv.error ?? 'invalid'}`) + + return null + } + + const rv = ctx.validate(reloadTo) + + if (rv.error || !rv.amount) { + setError(`Reload-to: ${rv.error ?? 'invalid'}`) + + return null + } + + if (Number(rv.amount) <= Number(tv.amount)) { + setError('Reload-to amount must be greater than the threshold.') + + return null + } + + setError(null) + + return { reloadTo: rv.amount, threshold: tv.amount } + } + + const turnOn = () => { + if (noCard) { + ctx.sys('🔴 No saved card — set one up on the portal first.') + + if (s.portal_url) { + ctx.openPortal(s.portal_url) + } + + onClose() + + return + } + + const pair = validatePair() + + if (!pair) { + return + } + + void ctx.applyAutoReload(true, Number(pair.threshold), Number(pair.reloadTo)).then(ok => { + if (ok) { + ctx.sys(`✅ Auto-reload on: below $${pair.threshold} → reload to $${pair.reloadTo}.`) + } + }) + onClose() + } + + const turnOff = () => { + void ctx.applyAutoReload(false).then(ok => { + if (ok) { + ctx.sys('✅ Auto-reload turned off.') + } + }) + onClose() + } + + const onAction = (label: string) => { + if (label === 'Agree and turn on') { + turnOn() + } else if (label === 'Turn off') { + turnOff() + } else { + onPatch({ screen: 'overview' }) + } + } + + const editingField = row < FIELD_ROWS + + useInput((ch, key) => { + if (key.escape) { + return onPatch({ screen: 'overview' }) + } + + if (key.upArrow && row > 0) { + setRow(v => v - 1) + setField(row - 1 === 0 ? 'threshold' : 'reloadTo') + } + + if (key.downArrow && row < FIELD_ROWS + actionRows.length - 1) { + setRow(v => v + 1) + setField(row + 1 === 0 ? 'threshold' : 'reloadTo') + } + + // Tab cycles between the two fields when focused on a field. + if (key.tab && editingField) { + const next = field === 'threshold' ? 'reloadTo' : 'threshold' + setField(next) + setRow(next === 'threshold' ? 0 : 1) + } + + if (key.return && !editingField) { + const idx = row - FIELD_ROWS + + return onAction(actionRows[idx] ?? 'Cancel') + } + + // a number quick-picks an action row (1..actionRows.length) + if (!editingField) { + const n = parseInt(ch, 10) + + if (n >= 1 && n <= actionRows.length) { + return onAction(actionRows[n - 1]!) + } + } + }) + + const cardLine = s.card ? `Card on file: ${s.card.masked}` : 'No saved card on file' + + const fieldBox = (label: string, value: string, onChange: (v: string) => void, focused: boolean, key: string) => ( + + {label} + + {'$'} + { + // Enter inside the threshold field jumps to reload-to; inside + // reload-to jumps to the Agree action. + if (key === 'threshold') { + setField('reloadTo') + setRow(1) + } else { + setRow(FIELD_ROWS) + } + }} + value={value} + /> + + + ) + + return ( + + + Auto-reload + + Automatically buy more credits when your balance is low. + {cardLine} + + {fieldBox('When balance falls below:', threshold, setThreshold, row === 0, 'threshold')} + {fieldBox('Reload balance to:', reloadTo, setReloadTo, row === 1, 'reloadTo')} + + + By confirming, you authorize Nous Research to charge {s.card ? s.card.masked : 'your card'} whenever your + balance falls below the threshold. Turn off any time here or on the portal. + + {error && {error}} + + {actionRows.map((label, i) => ( + + ))} + + {footer('↑/↓ move · Tab switch field · Enter next/confirm · Esc back', t)} + + ) +} + +// ── Screen 5: Monthly spend limit (read-only) ───────────────────────── + +function LimitScreen({ ctx, onClose, onPatch, s, t }: ScreenProps) { + const rows = ['Manage on portal', 'Cancel'] + const [sel, setSel] = useState(0) + + const choose = (i: number) => { + if (i === 0 && s.portal_url) { + ctx.openPortal(s.portal_url) + + return onClose() + } + + onPatch({ screen: 'overview' }) + } + + useInput((ch, key) => { + if (key.escape) { + return onPatch({ screen: 'overview' }) + } + + if (key.upArrow && sel > 0) { + setSel(v => v - 1) + } + + if (key.downArrow && sel < rows.length - 1) { + setSel(v => v + 1) + } + + if (key.return) { + return choose(sel) + } + + const n = parseInt(ch, 10) + + if (n >= 1 && n <= rows.length) { + return choose(n - 1) + } + }) + + const cap = s.monthly_cap + + const usageLine = + cap && cap.limit_usd != null + ? `${cap.spent_display} of ${cap.limit_display} used this month${cap.is_default_ceiling ? ' (default ceiling)' : ''}` + : 'No monthly cap visible (managed on the portal).' + + return ( + + + Monthly spend limit + + {usageLine} + The monthly limit is set on the portal — shown here read-only. + + {rows.map((label, i) => ( + + ))} + + {footer(`↑/↓ select · 1-${rows.length} quick pick · Enter confirm · Esc back`, t)} + + ) +} diff --git a/ui-tui/src/gatewayTypes.ts b/ui-tui/src/gatewayTypes.ts index 00a3b458911..016171008c1 100644 --- a/ui-tui/src/gatewayTypes.ts +++ b/ui-tui/src/gatewayTypes.ts @@ -53,6 +53,95 @@ export interface CreditsViewResponse { topup_url: string | null } +// ── Terminal billing (Phase 2b) ────────────────────────────────────── + +export interface BillingCardInfo { + brand: string + last4: string + masked: string +} + +export interface BillingMonthlyCap { + is_default_ceiling: boolean + limit_display: string + limit_usd: string | null + spent_display: string + spent_this_month_usd: string | null +} + +export interface BillingAutoReload { + enabled: boolean + reload_to_display: string + reload_to_usd: string | null + threshold_display: string + threshold_usd: string | null +} + +export interface BillingStateResponse { + auto_reload: BillingAutoReload | null + balance_display: string + balance_usd: string | null + can_charge: boolean + card: BillingCardInfo | null + charge_presets: string[] + charge_presets_display: string[] + cli_billing_enabled: boolean + error?: string | null + is_admin: boolean + logged_in: boolean + max_usd: string | null + min_usd: string | null + monthly_cap: BillingMonthlyCap | null + ok: boolean + org_name: string | null + portal_url: string | null + role: string | null +} + +/** + * Raw error payload echoed from the server (`_serialize_billing_error`). Carries + * the extra fields a few error codes attach — notably `remainingUsd` on + * `monthly_cap_exceeded` — so the client can render the same detail the CLI does. + */ +export interface BillingErrorPayload { + isDefaultCeiling?: boolean + remainingUsd?: string +} + +export interface BillingChargeResponse { + charge_id?: string + error?: string + idempotency_key?: string + message?: string + ok: boolean + payload?: BillingErrorPayload + portal_url?: string | null + retry_after?: number | null +} + +export interface BillingChargeStatusResponse { + amount_usd?: string | null + error?: string + message?: string + ok: boolean + payload?: BillingErrorPayload + portal_url?: string | null + reason?: string | null + retry_after?: number | null + settled_at?: string | null + status?: string +} + +export interface BillingMutationResponse { + error?: string + granted?: boolean + message?: string + ok: boolean + payload?: BillingErrorPayload + portal_url?: string | null + retry_after?: number | null +} + export type CommandDispatchResponse = | { output?: string; type: 'exec' | 'plugin' } | { target: string; type: 'alias' } @@ -538,6 +627,11 @@ export type GatewayEvent = type: 'notification.show' } | { payload?: { key?: string }; session_id?: string; type: 'notification.clear' } + | { + payload: { user_code?: string; verification_url: string } + session_id?: string + type: 'billing.step_up.verification' + } | { payload?: { state?: 'idle' | 'listening' | 'transcribing' }; session_id?: string; type: 'voice.status' } | { payload?: { no_speech_limit?: boolean; text?: string }; session_id?: string; type: 'voice.transcript' } | { payload: { line: string }; session_id?: string; type: 'gateway.stderr' }