From 27eea9f8cbee9ab80d015205e868cb318d617ab6 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Tue, 6 Oct 2026 15:23:51 +0000 Subject: [PATCH 1/7] hqterm desktop: our own terminal window with tabs, split panes, layout restore, images and HD emoji (0.2.0) - desktop/: Electron + xterm.js (webgl, image addon with iTerm2 inline images and sixel, unicode11, web links, fit) on node-pty; OpenEmoji colour font bundled; tabs, split panes with draggable dividers, OpenIcon tab bar, a + menu listing ~/.ssh/config hosts via the TUI's own host code - layout (tabs, splits, each remote pane's host + session) saved to ~/.config/hqterm/desktop-layout.json and reattached on the next launch - panes run with TERM_PROGRAM=hqterm HQTUI_IMAGES=iterm QC_HD=iterm - hqterm desktop [--host H] [--session S] finds and launches the app - install.sh --desktop / --no-desktop; default installs the AppImage plus a launcher entry on Linux with a display - release builds hqterm-desktop-linux-{x86_64,aarch64}.AppImage into SHA256SUMS Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 31 +- .github/workflows/release.yml | 50 + README.md | 43 +- desktop/.gitignore | 4 + desktop/build/icon.png | Bin 0 -> 56479 bytes desktop/main.js | 254 +++ desktop/package-lock.json | 3665 ++++++++++++++++++++++++++++++++ desktop/package.json | 58 + desktop/preload.js | 19 + desktop/scripts/prepare.mjs | 44 + desktop/src/app.js | 567 +++++ desktop/src/icon.png | Bin 0 -> 56479 bytes desktop/src/icons/add.svg | 1 + desktop/src/icons/server.svg | 1 + desktop/src/icons/terminal.svg | 1 + desktop/src/icons/x.svg | 1 + desktop/src/index.html | 31 + desktop/src/layout.js | 177 ++ desktop/src/style.css | 248 +++ desktop/test/layout-tests.js | 92 + install.sh | 157 +- package.json | 2 +- site/index.html | 14 + src/desktop.ts | 117 + src/hostlist.ts | 12 + src/main.ts | 7 + src/sshconfig.ts | 25 +- test/desktop.test.ts | 111 + 28 files changed, 5699 insertions(+), 33 deletions(-) create mode 100644 desktop/.gitignore create mode 100644 desktop/build/icon.png create mode 100644 desktop/main.js create mode 100644 desktop/package-lock.json create mode 100644 desktop/package.json create mode 100644 desktop/preload.js create mode 100644 desktop/scripts/prepare.mjs create mode 100644 desktop/src/app.js create mode 100644 desktop/src/icon.png create mode 100644 desktop/src/icons/add.svg create mode 100644 desktop/src/icons/server.svg create mode 100644 desktop/src/icons/terminal.svg create mode 100644 desktop/src/icons/x.svg create mode 100644 desktop/src/index.html create mode 100644 desktop/src/layout.js create mode 100644 desktop/src/style.css create mode 100644 desktop/test/layout-tests.js create mode 100644 src/desktop.ts create mode 100644 src/hostlist.ts create mode 100644 test/desktop.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b22b2d..f4fb115 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,5 +16,34 @@ jobs: - run: bun install --frozen-lockfile - run: bun test - run: bunx tsc -p . - - run: sh -n install.sh + - run: sh -n install.sh && bash -n install.sh - run: bun build --compile src/main.ts --outfile dist/hqterm && ./dist/hqterm --version + + desktop: + runs-on: ubuntu-latest + defaults: + run: + working-directory: desktop + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: npm ci + - run: npm test + - name: Build AppImage + run: npm run dist + - name: Smoke test under Xvfb (window + a PTY come up) + run: | + ls -la dist + app="$(ls dist/*.AppImage | head -n1)" + HQTERM_DESKTOP_SMOKE=8000 XDG_CONFIG_HOME="$RUNNER_TEMP/cfg" xvfb-run -a "$app" --appimage-extract-and-run --no-sandbox 2>&1 | tee smoke.log + grep -q "hqterm-desktop smoke: ok" smoke.log + - uses: actions/upload-artifact@v4 + with: + name: hqterm-desktop-linux-x86_64 + path: desktop/dist/*.AppImage + retention-days: 7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a393555..301b6f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,49 @@ permissions: contents: write jobs: + desktop: + strategy: + matrix: + include: + - runner: ubuntu-latest + arch: x86_64 + - runner: ubuntu-24.04-arm + arch: aarch64 + runs-on: ${{ matrix.runner }} + defaults: + run: + working-directory: desktop + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Tag matches desktop/package.json + run: | + want="v$(node -p 'require("./package.json").version')" + [ "$want" = "$GITHUB_REF_NAME" ] || { echo "tag $GITHUB_REF_NAME but desktop/package.json says $want"; exit 1; } + - run: npm ci + - run: npm test + - run: npm run dist + - name: Name the AppImage + run: | + app="$(ls dist/*.AppImage | head -n1)" + mv "$app" "dist/hqterm-desktop-linux-${{ matrix.arch }}.AppImage" + ls -la dist + - name: Smoke test under Xvfb + run: | + HQTERM_DESKTOP_SMOKE=8000 XDG_CONFIG_HOME="$RUNNER_TEMP/cfg" xvfb-run -a "dist/hqterm-desktop-linux-${{ matrix.arch }}.AppImage" --appimage-extract-and-run --no-sandbox 2>&1 | tee smoke.log + grep -q "hqterm-desktop smoke: ok" smoke.log + - uses: actions/upload-artifact@v4 + with: + name: desktop-${{ matrix.arch }} + path: desktop/dist/hqterm-desktop-linux-${{ matrix.arch }}.AppImage + release: + needs: desktop runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -28,6 +70,14 @@ jobs: target="bun-${t%%:*}"; name="hqterm-${t##*:}" bun build --compile --minify --target="$target" src/main.ts --outfile "dist/$name" done + - uses: actions/download-artifact@v4 + with: + pattern: desktop-* + path: dist + merge-multiple: true + - name: Checksums + run: | + cp desktop/build/icon.png dist/hqterm-desktop.png cd dist && sha256sum hqterm-* > SHA256SUMS && cat SHA256SUMS - name: Smoke test run: ./dist/hqterm-linux-amd64 --version diff --git a/README.md b/README.md index 3c9673c..a3b19a4 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,46 @@ In the manager: Enter or one click attaches, `n` new session, `a` add host, `i` install hqsh on the host, `r` refresh, `q` quit. Inside a session, `Ctrl-^ .` detaches and returns to hqterm. +## Desktop app + +`hqterm desktop [--host H] [--session S]` opens hqterm's own terminal window +(Electron + xterm.js): tabs, split panes, and hqtui/qc images and HD emoji drawn +via iTerm2 inline images and sixel, plus the OpenEmoji colour font, so it works +where Konsole and friends cannot. Every pane gets `TERM_PROGRAM=hqterm`, +`HQTUI_IMAGES=iterm` and `QC_HD=iterm`. + +```sh +curl -fsSL https://hqterm.sh/install | sh -s -- --desktop +hqterm desktop +``` + +The installer puts the AppImage in `~/.local/opt/hqterm-desktop/` and adds a +launcher entry (`~/.local/share/applications/hqterm.desktop`). A plain install +does this too on Linux when `$DISPLAY` or `$WAYLAND_DISPLAY` is set; +`--no-desktop` skips it. + +| Keys | | +| --- | --- | +| Ctrl+Shift+T / W | new tab / close tab; Ctrl+Tab, Ctrl+PgUp/PgDn switch | +| Ctrl+Shift+D / E | split right / split down | +| Ctrl+Shift+Arrows | move between panes (or one click); drag dividers to resize | +| Ctrl+Shift+X | close pane | +| Ctrl+Shift+C / V | copy / paste | +| Ctrl+= / Ctrl+- / Ctrl+0 | zoom | + +The `+` button opens a shell, the hqterm session manager, or any host from +`~/.ssh/config` and `hosts.json` (as `hqsh --session main`). Splitting a +remote pane opens a new session on the same host (`main-2`, ...). + +The layout (tabs, splits, each remote pane's host and session) is saved to +`~/.config/hqterm/desktop-layout.json`, and the next launch reattaches every +remote pane to its hqsh session; local panes come back as fresh shells. +Settings: `~/.config/hqterm/desktop.json`, e.g. +`{"fontFamily": "JetBrains Mono", "fontSize": 14, "theme": {"background": "#000"}, "restore": true}`. + +Develop: `cd desktop && npm install && npm start` (needs bun and a C++ +toolchain for node-pty); `npm run dist` builds the AppImage. + ## Develop ```sh @@ -36,7 +76,8 @@ bun run build # single binary in dist/ ``` Releases: push a `v*` tag matching package.json; CI builds -`hqterm-{linux,darwin}-{amd64,arm64}` with `SHA256SUMS`. +`hqterm-{linux,darwin}-{amd64,arm64}`, `hqterm-desktop-linux-{x86_64,aarch64}.AppImage` +and `SHA256SUMS` (bump `desktop/package.json` too). `site/index.html` is the hqterm.sh landing page; `install.sh` is served at https://hqterm.sh/install. diff --git a/desktop/.gitignore b/desktop/.gitignore new file mode 100644 index 0000000..e1e6194 --- /dev/null +++ b/desktop/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +lib/ +fonts/ diff --git a/desktop/build/icon.png b/desktop/build/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..126853897ab73a5a47f93cefd7e9baa169e7b437 GIT binary patch literal 56479 zcmbTcc|4Tw_dkBm41>gkP_|JCQ7C)Gj8}__q9l4*MypCS3Q?GQq|%}&SxOqMDv465 zEcc)+MI%edGGiyp492YY^7Hzp%>$9*36eXetz^E~I8_bzv} z1$qkr08rb$ZSx)gz|Sx7fHGlzs(x-#2LJ@X+jH+0&*Nu|mf58}{Cf2+G%`Aq^|Sm60L?{)pMm5v&F_+(Rx@*(N) z;N+oyqb5fFZvIEEbqUrC6kFT4XrF)=ZyobmFDClW=)}NNld}gc$KaV!{ZzR4UF}{Q z0ldO*y~CI?9kzFoID5dM;df2S5c$gE1iaE}3v1&XM0H~~Z0UaX+?`GKvxwk{ax8o= z=L;O{&bUz;cr4x8VQ6l0{Lxc3`&0E<%RxJT*!7>l6|WKToESM_k4OYa9VOUqX7$?M z4`rQ;Scu%Ybzv&<=4F}R{wU=B3b}2CRVa+SH1{&^6^-?8vl>fC{dVi(4K8<_apA^7 zHA7_T;#vn!vV{jCuF&5qLB3ccvNrjKq^g>3*~YPD`(Wp_YY%iINZfkZ@df-REhVFb z;_MP0`y}w-F;$>B?8+J(rqLXmv;%R}|Zleb)- zGK8g{U1M-HM!zVwA1>Cv(_t9zwQVfb7- z@L}7~t}Q3NE(%Cqwetn_Ot+i`KRACR@af8D0DuD`_w4ckkpB$yzi#-yW&|Prhy8!Y ze+&Pc{{PJLyXAkz|5^So`hNufv-mIJ{PdqE%@6dy=Ko9mN0I-v?k|1J8zTK*TM z|Nn9ko32MXheEXZim3Z$diPQdza?jgG%_e{Sg45 zs`;NA*WAN^0l*m8zS(VW%%6#J)yRzY^Zcca{~B(;cIoS#uLmOCSBx_kDJop!6~G0ZT%BF%a?WjSn0Ca(bgSL9eaY4z z9uf5y*u7D6jaqwd!megnRC6ci6hD;an(&NsJXD7xXXw^vJx381Hame#MME@MbHOL= zlKy?&*8|R2p8od#pMWjD3Hp{Y)YfJ}=VEH0C}YqbQx#~S`PJ2!dn28xOn4P z2$bynmZ^y;Y63jUJuxTA3`^|2&W%~Fju2S8F=#p)43k0`dlmT5#c!LJ_`$4iH+iL8CoejLySmIi{f!`GqDwtkx zP&i{#O=^u$~S?^Cl^E}hhD~#@Uo~8`+L|%fK!1qZ^5bXFH$kNt z-M6piEIl}R^H7QN?l)gr?Ge&jMk&s*C_I0Iwd zq&7+lXt?b(Z5hue9=Z9|3mx||PhJ?@Nz#)H(O;>Ti7V=73@;gVYG}Y-$WXT0;9||- zxQOXBx5#t)U_n0=4o{OYUCDHcZ4LGulq6D^C6o+D=`&qS15X*XgsD&z=8q;|p@S^U z?TXEIt3qS831dda>i8EwjlZGC5iW(dC%=2J6qImxT1&oLhW$2(6OCF|)GZhE z8b$-?vn=@yb;EUokF4Ei*Z~Pg-|I|;g~w#5Kc8AEf0B?F@%l>piP^X7Z@p@L+if5z zNMoq4$fOzM)Q*0&MkjCLcQA)#Y576D$4BM?G@H=;|nR_QYtm5aVas91?AnXSV zjD_AZ%R}(A?iildHt;K5p=h*ed0lM1BsKoa9m>qv<^Krsdk!3QHk#QAz5I7du%fTc z@y657uR7t-##28plV%H}zC-tJVCVCNUZ1bJ_ww=r`K)GgT@>iw+YJT!J_93c`;h53 zudHBSKVRQWY4(>_gN5I)H9^AA4WZq!?GZsipzu^G`@a1PW*==an1*(bS_dn@?OP@3 zFE>mS_FR=d*_`cWotMTPbw_(Q??By(U0w;aP4}MmjW}(qYE18|y1ePt`k;N9 zhhfeigF9ijY*x+qs$7^6$D=R4w}HhPZoWd@w{{IlQZBbm+Fj0xauJtsrnq2uU=bw30Z+I4jM_a!o3XLly$uSwCL@@41NiC$e@ zUwbxZAa39G=Z==MM?~QZ_d78TJUoA9R|}<*vgh)9scxgm(qEVM)ZJVrYu!gKJ-%@C)Nl5?a5p^G_M4e~x$E#j$Hm)uF#G`CH%zHcRt@b%kqI7leB@@6kwk-$Nx_mAT`hcP3;$P5(o z%cKD_(~{By&R3KFL{y~+OYpJ+#v>A;$?rhLp=-fwdV|v~gpFvZ?OXbr<*+%f8UHvF zyT7gA^NGGHigczOn(ppbU2{V@<3>-Z1=lmZ$Kj;!=YC+cy>UIPZIV@W{!b2f=mY;KA10 z6vmtwolXAvX59*==lQONz@&vQZhACXuX-Eotyz>}m%F7YeTE>nP1aoRhRA23ufVP8y)R1N*_0?_em#)kU*Ve<0*&%{J<6UiB=~;u~mBn%I{{%n)zz{Moi? zJn+rT-Cw~092eZWEj~8*PbI|`W_ig5>`rVE)W9`C?&xEogA#?i}QKu)nzf0^f3weKa_O@~1^vIu9&fJ)fYcW|8UuyJuiLwZM_ryIC35O}S3^4$3A+9wNxKlk-v@kc6%4Zl?E0R`$Qz@IHRsmC^nco{vt zEQvLa(MCm>)Ev91P}V8^Szx=b`qwqdm_Av!Z)iMs$2GTWkBfJVKs#lv?*q~uP05e- zoU({Ev%tV6!V*o;{phB3$Ee$L8_a0K6?d~XD&g){(tLI9stx_E3qHQg+buKgs)bfR z@SM0rF^+kPoe^ISHDutgu%2n~_SnuqNmlY0*coGC34ejjgaGpS%|Qu$9#%00N9-Tu zNG=|j`1{l5)y3)={-tZXozCCd*VylDD+oQb$CQ>1EfS2x-}*PWzw-ib=GK?>w>3%d z_p{saD##flg})SN za#!pQQ~$_Z8ZfjY``WIG)}vP6-x7~TnpQWHK7 zaz79Slz9DH+T{gKVVI@_#FRMtD2E*T@J$oBbi5-dg^kn9?YN%lJo#-Og^GiJ-D~~F zsv`QW#TRd4&@Sbivr(I)FZS+@-=pX2>&WfO9IQd7W;QxHPV_%{wKLTt@|Aq3_aV$# z{de8wl$TuJ-_N3W+J<2`)+5D}Q;6?51wpUX3T79bc0ChmOQ$tp&{rXb&R$~agc`_u zS8!to-;_>W%v7<&MJL1(D^_l1{=+V|^@)6$Vs&S$u6eH3b$kcTSJ@8`u+;zl^}NQ7 zO@Hb4rdYNPiilqG5FxBwP_U(-KGS*7!ZY4X$yl6}@@5@GVq=C|fs?rQO8h%JO@m$9 z>;`PYZaix81PMYM7XV5zsFd$Q_Z#k3Ou8A%|Khk*W!cln+nSq+g`eEQwd;DY$qHzv^5+N7%as`3&Qh*1kQosixV?P7qgh=@z0p zmcq6pqYoJq{UGxwuReFRu6BrmpmX;joKmqbp$Okyu|(&TC7g|4515t@?s!c5Q?gr+%m=!gfO1om?Fm*`HsW#$eO0Abm@}YkzTCU)o9v%5n($ zO9?ogdDyp;Nx-T}{ai03{wzBsC`bez(8apjj)0eF0`mCZcj?soZj0wPAG%YiI{)=}z*2FXf#Xhl6=rHn%-*EyIRI%hH5@Gxzag@uLb_>!Ef2i%gc z9y+$@vX$wsiR1j~gPxov*PcH&o!w{kYx(W~50;^-E08i_(Q=bnfIDgsE(qDDb7f(k zPKfwTYjfd9<0n2TxaS5>{eH;l@KGly{#%B9EIFcEtQl2#x{@3T)?e3_aos!CPN~60 z)x_Rmm#UoQ!ML?cZyC!HT>58oA7-uEVJMq=I5oAlXVK2^(PJl142~X~@Lv0FfUt*| zqcM8NK|oldWYF;au<-}LBl_W73r&I{rLiSW7aX`Hh2G2k-wwa^(r%ljxP$_wn{)8JgD>2Tr7re>2)E-p#4 zvaaH?>W?qqTMZtc#;YHFrW!}j(_o*v5uJ0>kFgxH5BTjAehWL-mp2N%DyP^atPvIN zfBOwt`4hL9aA-_?;qjX5#)aJh$4iGOgD+UmAgP5|#3I{Jn7A z(kNsUl3Dd^l3HEK09J+U^hZgj_dg1|wufq}+Of!)eKH$npq%<#W_C4smVB%>1U|3;hLVIn~`>UOT%RwL~*XJu6bHbnSyk}N9IWtW?sT+F0$(wW-|v)kY2#C z73DoL_I*EAxMQO00cmU_>lfrsanp5t^NtwO{xGhXyuQ0V_wtdbH} zM|p_uR?GDR6j0H^7FYc}>Zw##Bz8Ud8$}PUoc?Tzi<9B7gDh;x7*?|lH);$UDns2% zC{9dUrs6KVD(|KuZwH_Z_$kpZ{%}jwW?Ojyz5QH%fpc9C*R2gHP-(=*8A^gI2jJEG zWimrEFm@ivGzaw8QV9%&q6Zdd3R8 z(-RR(8f}h~?4)KW*aWH=mx)I@-WC>2A3bFr+}0r-7}vlCVfsP9cnFkBJ%zPbdT6ht z{7{*7E=gbpRt!TF^Z=@x{p3qeHmpDJ0o#f|)S?kmk#jSuXUTO3l-rw;U zj&~yUTiQ0@!nBxl%jF_o5DRX{3e9G)o)|szW5DbCd*?=uvl$ZlwrK2?RJ9gNmE6Jp z#Xm{YVmt1_&~d1ciBH2O88Ue_baoXI0t`Q0MGZnX=tMffyMPYmvuZK$G$+KuCJZmh z4*Lysf9JSY5Wj9D#H{{_vax|xz%c6(mM5a@q#VJnGB$O<9Cam>821=W*<}wp8lj;N z06?S%l4qhNE2ZDCc)8guUm&9l0jqF#x8Roy1{Nvv9)=wR9I%@JBUbgu>m1qn_8;+G z%70%rz&34v3p@d`H`_HG@jgF(nxnlZrdrOo4Ff4*vVR1B#UPQ;E`6PSK$(DE;+ zmOKXUv}xL_Pf4*sL88_F7MqCSWqv{(Wi>*dVsNQGb2H*6<+e=DK-XqzpF#H(jk3+v zkU!o@dz7QV*6w@Kd4}nA&TtRjTMABeltbl5((cP?Y33-c1m>zK0b$)U35mymKA22X zXz-W&BCO-;PoV>%OKP{}9MM>QK2(9P8lJacHIEqZIm}w34Gh!OLXw>|&?WWp4nPBA zi7egm)fC-r0;vFmHo2;bMfHKCpZ9?m_?Qw#D&Vm8WBIQ-5>%!?>oo-ZyXi3z>}s(D z9Ey~W{z;Tm7QAB;o%Dwf-xsMAgTfI=n3;vl<$%j;~X)pf+{Q zSWki-i1Z9qh00NU2RMX!0A4{p+SJo%sknr6dc+dTz}owgC$OG{1Ke9W;E0RB~pI{ZO?<{sj$`W7f z7=g)spjsO}yax?XWVbP22dWR=NGQ1tzyZ*$9(j@{3!$9ADyD)Ie!fRNO0aOnPCe=? zaklgC2evpDQ`B=3EiNE<+Q1`JRA1@Xw#8U|p9G7DMqmD>%nHZ?Tks6PrU%#H?$-#S z$d1yMgdSFaq&Vd1eYx*vuLU;vQHx*p%s0ftJ^&9$z->IJAn<$e<~in*R&iR8c^fjq z$ezzAv*KyaQ^z9ay)Qt+Ru%6lFSeKniRl7~dl&@6N9xIyDQ%!hUrHkeEM?=)F|9iP+2_pstoL3;A@$}E;6Z~-4cTiuYi4ir z6M*nBjwsPfa+$je%@ep+W7;o&fN#Yd6Il&#RPS%(JNb>dd2laDIB$@-#S{XDX2kaZ zj8ymm`Vg0uH9ACD&wMTUXX>N0`X2ZiSrH^Hq&4lP7q#D*wM6e3@Xlzk1*=ypdE+Gs zrE;<&KrHI-v92Ys%_8NQzp(PK?Yn?WaM^ROVbTIXw3#fR8>6_?{n^H=v{0-l`vfA(mgZ!A1aq+W!crAOrf2TAXOyGRDu8N4`Z7~J(U{~B ze||$aU|^j0m8H6O-o;ciw*i#0=do;eZ=4BpavhFZ!_R|iDN$0dHp~Wz7l+Q~$v9&i zJfPwkE-`Dk*CQta4rUrUVx^bq;jhL@tbkGRhI0d~Yeg6grFzvJrP~u2eM?TTop| z+bd6)4?0-An`SBNWCVC<&2Z5eZPyX-X<4!S&{X@tST|US8v;H%_t{VtZ0dX;)!@!m zods3^4nPN%b`JbJ-U|h}tzduc=d^y~3W$s767na^6`D(&$ebcwEC#DCb)Ge5+yUCb zO!<9Aj?6GRjV*rl!0WU9OtYbDu1!L}$WI0riW$3uGVzJw%p7PK# z)*tM`YxK8h<(#uO;JjPKCaj9M2Iw%}qss0At}6;&Q6$(q8bZ5ih8>nf+q}ZuWPBeH zr+~EsRnen#QXbKd9ZUU7G5hk@PpR;i&8&~^s*v~nor@{lIzN5@@;;h%Ronz z-0~GAeIqw>1to3smi;IR@0}ufD{NmeCsT;^!zTl0Tj$?Fcw`ek#$5#M+}Mk#uBKGj%5} zZv1{2Zn8RH8zAZ>1)*9VAl$~i3oDg>z;s+pq2UxWd-UjUIzA8j0DR@Zoj8l`J6`8N zCWl`Co3aZ#5Im3~a6deXUWBdp%|TaS@QVgu4;)fvINzhpnnOyz@3}?RiX%L3{jdN|h4jkT+^`4nxu9P)hGQ#1Q zJ$52;Kd|JhBerG073`>e!dMMxEe0bT>=areXng|y6QNiS1Viez*FLAJyRXJa@^8!*GA5|W=*`!k(yQ-oI2f0dhuYiYTHAI1)3xGcdY{ePG&9z0( zb$caqd*pAo-r=gDj+e(MJ|9p95Z8cp2FmqAmUJ|+S2CrX>FF-GWQ;%Rr-l8}z;;lu zMvXp_WL(4eovxxX*g_EeCJ+B1H=qRhADvy`A$T)O{VN1a>x+npKD~S{eDVBG&9gM! zuekNk@PkE!$fY;~Mcl%v8ekEyMtQjs0Kdb=gN&K>L~*I$eUJWO?EWz=l~ri=NBYMx zh@%Z0L7avY6Z-~23GCA7ondzjF@~K*|qJ^4l?R}5_{ZBf1xMO>)a$1 z{(+$g1B4HOfByDVzoIYKXUDpxn%^W0?#?8 z3@oY6^VO91FNA)QQ&Uh54$`IQv%2b2vaW1W5bW1c6s%Wa{tGYw<{9R_03)9TO^(PG z*cl-ml+hng(OZsoNs`o{S;jxsEsTG->d4{2kze!r7>h||r7rgeP8bLjRoEM` zG!~Y0hTj(-w&-&&Z21@Ql@ZXLrv)WrW%@IdiC-Z4NpHECXYLs&M-rBMo{|LN?n75> zq?Vt6^5=-`-iMbxd48^d70@0OR*x2U=eAk z7kbaS4K6D(n+F>oO2yi`x@}Z~jiEku9k3Rg`F97n7Vwqf=D(+)x`ca_AehdpR?f9B ztJT51!li7$E#%w>1R6#AaJK4n;vZa`i?F4QyBJJlHIP-AIiuXn8PJ23#LzOmD(397 z1uLYs@n1QOWIybLp46A*(@0x+#I+sWaRc0Z{POzFPHTGV$CGHV$p?Enn%pB0eZT!w* z5|sRlj~3ptKOiFS9pgypqx7xvyl$?eLz|>=ZxpKwRk^kCrc57(%}|51+fb!-eacwj z6$a7u!B55JNVHafeDvS;B0@O#2F(xxXCew<<&ek#UmN z5M>OGuEOP=FMh(*wAVeUKn<&6*w?vKHQb>t0!jdDAB1+w5HKm)T1~Q`3_%?5vSZbDHIzl~a@PWSkd~gtHhO=9kW-8t@KGWiE=)K((x*#l2q)U}LN_P6o zv2bQ!G7#OAf_b&izYY|DXwM}6hVIRAxb*jA@?0n5uBPF`AuKamz1avWw{)lkAL-Y za%SW~2cVM(w5o;H=A*fSq5~66_o;Y?kxn-4)e9ejCJ-jb!k$OFfTv9NdZ0*w38-tz{r(xx98{7KG1IS5 zXD6nxp(}APwAWl(JrRA6);C0%PSTo=z`e+*pv{o4gDZ=oHNDkBcDAD0;e-%`@(Vej zJs!ASP)nO-g#2=(JE?83Yk~0Ke znnW#pr$c40&`szQdAk#=rTSW~yI9EwYemlxctha%&lGX~Eaf9;$rN6t`9vq_0K$uZ zdy6U;F#RrjU6{%H4KSfIfR^o-ZNOFVN2OmJd>6fobgf}{UI-G0UXf@CzhV08)Mcy0 z?jC>h@3Fq0m_!5Ro8Sz_%Sh1vfY_{uqK?}HUvU)zSHRK}!1vm4x!1fOXa(>XEwmpi zN}@dV$}=!#8t4Eq)crb!JQq#&k9k*#2jU_D4dxO49q{rbY$A8OG2&C1^1Xs5ej`$x zu(Sy5_q!yyUEa#D7l)cKmuW%i{&E%Q1m;m7CSb~tJ<~~kL!MP}9Juu+g6Mr(nkVo| zoZ)xPLq&bDQxuLFU0rJ{a6Tt8VA~g^x~j*#Oc$k?)@VSDc89#_4p=H+rM{f7M77{Q z?s{v4^C8fL#%;T4N95zK^!%+7<(Im7h~Mb zsMo9{Ob%)-G>&K|zY{Ag>eb&Gopf~nJQ(a(Xo@?zU~`e71!g*lX4}eLyL)dRgYQ)s zNeXYw&>Cp^Y@4{GiIEzb;LzPFkL`e3?(@AcWOEFi93(xXq#$M&=ExQleX3h4Z~>u} zzrsEuVg%cH7IRdC9r>OD&JuGbd?)TR#p#oalPX@aOqnUTfnQ&8ZrI?~Sul+yGnG2> zv+v>Jtsg6i%9qy00Y*1KJ=X+3b@oqEueJ3wcwaKD&(w?S+x@5?8sb( z>A#;(!yNy4F{;fenwq4iW@&Y4pdRIHi4Il0 ziDAd%K9-gcrR3irnMD{_}VKtf{-s zJWJ+!_!5i>)Etz7CbdBHiuz8zpz95G^x_Wcu3e1z>sG0)i9UYGF8n9aI~wj48(2T9 z9xJS#D1r6g59K|-pcY>z4Gs{hd@1=^UCa6zpPv9!h#38xASh?<_LP?|O_B*J^d>yC$iH>oSuHto|q32PxQn5iui3UKze*>tv=W70vZqepW zUB!llf6+_mo9RDpNlu_EK3I>s@g*4aRPVXlX2Ywwt2{T z?f-PnJkK2oMHkstPX{w0-M&NIcFPH=e^NucGd%fg?%|H8=R`20qJ2J`wVF>tw&PE? zrJO*ARyRwzetQp(VArodH&p_5<7{AB{}AOmMF&X2c5TOQ1`7A%XJ0QIE@-CtIpgjQ zMyMK(zE)YOB$0;9S904EivXjbU4~TcbYKy?*lo@XmOFUWwN8y|Gt`_a`B&so z`v~JztDwyT?xRP#bw7besI0{yxAcr80hr@6=y{RpD14(Qor`ywvEEZj47dBF5SV`y z_?tLKCKVVa$6mtKU^7VvW(HURLnXTiS6nIx~6#y{;bW-6Nwc%Gg?mn^_P%Pq) z7IRL>eqy#O8Ho`L`ong3@emk$ww1Q@rZhh-9cpz1YrOb(5XIlJxehK%zG}}fZSTYm zN)tV7d{S)&>K+EemSZk(7P^`BjvE;tyRWfJS+dMw=Jsxde*vjf>b!^W{hc}U)W5)w zX#oDQ1&}{nsp37tSzEj;M?6*GsyE|NtGiRYJboK?tg3L&ads}h-fq@sX%t3%Xh8i= z5hF8WH<2f(mp%BK@aNC(IY+{L;Y>}vOm0>WdewnO)ljFgQeEZ&W`O3U5+zbFP--^0 z701L;MQOOb1wR#W2KHsF9wkF623wYWgxm@Q17Y#ngYbA{PoW1#42ZsePyRr8&q&AY zn{gdC7|no0=qoGjrSVFI^f0f5z=_0skP7PU1Wcy6^%6%tVXXPcJ*4I)`Ot$zHvLD& z5gqESK2qqdQUj+L+(I+lM(KQX6Unq#PX(}(3L#aC@pq`EsJ6qjFXpGq5njocSAS;E zf7X`GNhGm%S{Pqx`hXL#n6U%2pYiblB^EO$z_*7u>#~f7!GZuR{|$C8W}vT>n*sa5 zoxt#9XYuuUTX4DBTSSS@80g-=0*Iq{>`;zmKVcf^E@$eBNg@%?>tlgE-svLVe`+aH z69@yovpbm3PWa08kXF_e>kN{%aS*aoBxbuGS-f$)U2CWO?ha|me4Z3JCeDYlk>Vim zF%lL=K*y_4mL&fd>lz^gN}`Q!lKD*O8yd?~Qj8_Z8po(t63#n2KP1En`cWiNo4H@} zQ|z=4`Lj{#Vzzqtd-01j!0ys@%wT&QcWzGB>y4jWS}5H2Wj`zAr&4;zi$mjFpZi%N zRe_H*Q=3+UUvNB{h-|1L#ptW0#@r41N7Z+@Lsjoa}sd za|i2L;%Z>v>*s+H-e!%HLPKMU*W%PPU);n|T!<22#53THYz4~qDKstGSW0S_czSDw zPn3UWm3^pwTc|o}W<7UiSnQ3s%Ulxp11|~s?2CFIcqKOMLM80mh3~cbSp3chSPPxW zA_7-wqqdB{XeGRpx1rfx7|NOI{(R2t%3}PpgaOmL#Ld9@^Ao_z{W|RN)xZfL!_PjA zknD)JIkTAd8lRXEijFkBLJ$9Smz4V&O@A=*mA>WkcaL!r(U4Kvz)iSEx0=fv`3Tim zqU%JK<`=6)=(mf(mzsXJ*Jv@vg+S5DwP-C$|IVGNq|4Nm7T8ei+$#W;%a8x5Rfpx*Q3pqLvCvW92|4LTC$@Cdm#OBUijjXlBTgL zdqiZvS@Ps10~#vyxG5`N1sU;A(DzDm_?f>Yohc%k_mdXN2{hz}b>S@EvN?xF3!stB zL;mjGfb)K{4^S8YR|E8WMC04oK~z6pHVip_o$6_z^Z{m`29z@oHUpaB>Zz>4Smx?( zBiMBTFNs`lo;t%ay+{SDMOCZTJMB=|`QlVy8k}YcuL~S)>n${cFYI_G@fqhDgHEJL9v_O^1!XD8-us_g<^ac>m+;mW4iyWRIq zybqAM={DH)_2u{c-U;MBu5B;F5FIduGL!ZZGMz#exx6h@xxe^oR~WHhZft;B6Vimd{v!Gx@OGSf%0Kz#RzhIQ=GTwZ2pX`|{=KtY6n1 zz9Z<;8%aBI_NQ46Y`+fC6{1g>4lc8Pfrl-b<$@MdIFCHJvjE^Bq(9QT~7fU zY~Tej4*2>5xk^P|DUBI~iG6q`9Ez@H>h_JsYg}S4vM3;W<12_3e?VoJU6ISjsU%|RTJdqB(+&okyALRbDAItZW~ls6_b)31>LIWL@DA%v16;I;gBi>D zYbCl~|4u9fW?5SkY=~4PCl4ufr-f$6Q#Gd0q7p>IY&YeQPg>0PKo#`)yE9Os_F?g8 zMnebDbUewa-T{qTC23pwM%3wyeZ5rZI7HS5cj7X!uXQPMu+epiByDb!ObZ+9Q)C`P zFx+W?d`^TtZIkRGQtb-QzE#xf{aNUR@Jbo21L_cHRKM+)#)Y(ote(R`|9NDAHK%jWgq)ftMoi+*jtrn)bH@`dTDTgRHe=dg0qz&HfDWL{FaumF(?HF1t$ks$&qSu+ z5WJp^ zZFr%3Z!M+cN17r*zf|=e+EC1Q=|pYu3*jIUJ+~vKqj^!+70FZgL>ok{m}sqUbn*;Gc~`m zLir0TeIFS!kwgu)EnkB7w<4x?dE!6)l!E1{g&)*Eqyvrv7hUdeO;g*;gPR^KI0A@p zr+47S^kdr|c=6D6L)p&Km$K^a`9_)#^&ODf2)Z0A8M8)cS|33MGJ>O8>Hs|edm%4- zDU1!6sbMRa;LE6aeUg4pb-$fekC=G8fU3@xa*S-g83>4)pBxxvPSoHEeWjy)Xt@7k zPkadB*mE15)PN6*40-DZ!PsR;wAGr`QJOCfD=sAH8msJWxMv&da6Dukz7rV%k04@0mEFvpVcz}r*0GRS$aV%9}Lqua`mGP{I_z-Z^AL|wKECBF3o z(5mP#auIN~S8qCfE9xvgM`}&C^ETI8(q{*JnqE4ky%nE}V_aH6sjELdM1D>=!Z-qY zxGV-w0Zs!c>y&ll&if$`kdn(EAT{Yg9cdEs8-bz~Uz|I97dxuRJ$Nt-R4INpaPg)3^6s;PBU|v(ain$7@Tc zwgVs4b5)lNn&Y0~Vy@)JGu@+}{rTC*m&*5#R7arC&}2%`O#a)ja+g_pVsQa82|aZV zrX99xsu}$tEk?R#C@#T3ubwC1O&A@aIAUH?4v8DKVO@8z2$X;s9s`)*d<90q4hY|Y zu+}LCFB$}Fv>#Y6NX;Xv{R_;u_{<|3FD-FDIss1-8~>@|&0^G&=7|3keAThD+EjiL zzd~?)V|w*#)nrqZ!%fVdUzrZVyZWV;mL6IJ$VS|Df&3oP+b__v15X_Tjt-Mdu>}|H zDH*PL6lbdbYjiwFcL@4}@4f|`0k)~l`_dt(01ZpY5<>A~lP1h(zm&t1LV>PaT#>pl zk=e;1Qq{ey)=+~P4uAcq>R z=@55E?(JsCMGq_ShtY+`4uAzVZ&^?4`LU;Mh4dB_+P2V`Opd#!(WT48=|$u;m;iYMS}A#!pvg7 z5|%E1Oj`z({QObP5q=h1ZwnTlO7vS>_UF1b9Ky>>Vmz05$jn5*_0zcYrMU`V3o_~Y zE@GtQG<0h0)Egj1N|$oqj;mV%s}mw}=et~95x1!2<_oIW8bh{M0mkg;r5H`86^-32h9-jhX`Eq^6bB4#A3(m^fUlG|Z7gb}z7b z16N9gGu1=7c8uXdbF%C&FJLb=2UY`L^s-Vc@iC>xESWU*J%Cr|y^!r-#ADz&4PxZ% z^Zixed$~P_*rd4Ql%??kfk}vc(ShoTPVFZLY)1~1r9-iIZ`=WTI%t1;`o|iPB83G5 zVXg*|M)pG!&tHl*+vqwSGKauNJWdKRFrB+WzF(V zws4N6Xr)qwj8wE!lHCj{ga}!(%*Y-w)>+J)^PSHR-@o9T$9*UKv?2c#Z}UtVFLEb3cuXMr{YxE%@F!ra(?T$eDLJ|yC_N39 zV44MT20)f}3rn@?Q6!b#Ih`e@1!kddDGmNiNKXh>FE!1G7|n2|nI}ENini60WR$jO ztOe}!t$n4xFsED2mN>4@pD|z@Atyo~TI1_hJaL*NJt~;HP=I`4 zS10uQ>$G8$Whkc%D&{}qIq-gOzp@}mJV;DFx-ySM@+nuoPP zC8Bu#N{mE%5u;STMvtZdYf6hc>M#P~YEZgfSi0VirQvRv_*4O!aV@LNY>z$r@65Bs zKAb)E+#KJ#ODcsQ&k1>;=T&i8!sILYHM9eb7R|2)LP;0lk=wr^H>6{p*L&&gdPp&# zjuD!({B#+=SiVuiMDRiAPXIl*J)+I!Z}yaKf%j>`$=HmuQ!2XR4f`2Y^&!`>HR4yd z**qwd&HH)B2A%E{{v>Rk6#%=0sf^lm`RFdpO#~1(?B>`KOo1892joX87y4_xvJn8*{&YapH6)PD%GS1xnoI?lD$Az1UZ5n0v%nZ`z;gLIe(yPBJZ-0m&0qoLx8CJf$6AQtVAkMfzt(f zxi}N%mC-uD^1A7iyk{B{VHc3cBGgtV7mc4h*8q`fAlQH6No zk2i&`{}w8Z?C$)w{+S<3OP293>S2mTTRim74ju-YAbVO5DoMzaOxrGCVac!|7)$8dKl2XTT-jxN$L`E#FmDIHft)`?8%$-rcBS<{5bTUM?)vFE7-TdPovos z^QxtkJLDWg{!MFbbmiH)eEe7XAXelAFs)C+`w34w|3dzWa&)ev-_duV!A~F3D?tAP z=8L4sSCRZ^k$3n8-9=oSl;yUE0+Kp(#300 zXZ=3yOlbKn1GY2t2x;e%Y@D4%qD5Dv3_W_{GUta$8 z&MrxTwr&7B{Ik}G0GKBpTE%;V|FX^o*#I0DdBi^2#H$GtY;AciD_BTdUoKSEOCnz; z%cL<$rlQos=2y$$(7Qs~rFr$SB3Z8m)@GjQAfDIVw~3n!J(&zCMrQ>*Q!qlAjH&4^ z6a3*X5M~Dl6~yXP0e33OMvZdNry$NrV>juB8O-j@w-fWaCju|gl}MLcq~!r~_!zbs z`vufi2oNuMlD|RN+u83+$zC0rm|$G%2=5IYd6|Ds$qF7}l#`-K1(=FkkdP&-`_ad^ zB&vVRf^DII9Q_WtqaVPaD`Qk_fUxd&wCt7{QoNlXx|nfim`s0?h^v#vB@-sUMI7$f5<3wB0}t5Pf%dWJ2<{vIMj5 zLhPmd9HsqoN|V(jNEXyRw&(^_#Ju%_<(zfGeC5BVCp4| zFyuoERiHgfz?yo3OkIXs7!dz4xPI>iuww6*AL3~x%C73JhT5(=Vp)gFWzI{3J=l!; zHsLX0`&|?M5=zC`XP5QVyF?B@#A38MZJzXdvVW|Pigc54uDA5zS$k~r7ijAF*8_+* zzX(HGB|f4Vyav)yZwUY#?>e0SR_y>wc)|+7i_*ta-%zN$jjh(((=?vyqw-&J@4zCk_d<%>#aW~{-o07 z-~7Bkxlerkcb3E@HD1+P#4UP<0PAa}mTmk<{j~`Y>Hy|%aEwpYcy#b*=E+OspG_DO zVlV)s2D$jW`+8tyELlXKCh7(C;ur_K9D`ffs!6C@9}X6IiSIlE-2N5%$N44@P1^ci zZj-jU#+!X@JM1z@*Vwe&3i*mt8evF{+aCL*%3qxZoBs;yOs^FbH5rg+h32O^Lg?}F z6z`Dbx*tE&*eMfK1$NrXI@C&*K-JK%vC9rs>*Wab3mcVvjVyl!daE~+s7_u+9k{91 z*RC_5@D`v{FZSQ5lhWg5u>HYiupeNaJQ%eU1lJki+{z;Mcb?sJPN;te?HbKMvB*>8 z_@?w$+~v6}jb8Yu9rU3gnPbwi{cqX0S&S6*NBk_}ig@z~lf*V)L3D|b$x_e$^@K493Y0eFW$kC3^j%|=Yu7Oh^f`~HlRUfD`^P#=(e#w&71b6?%cYi z;lRb}&I7qWv|M&Xl(=ARJ@ja?pc4F7AnX+&tFrYxvWw+TlkgfXfypXe6kLO9^y>2 zGCfIGcBSJ=Ae0s&@e#w6$WKveZ^lK>X@Mcg##5#VN>`f`teSy?Kd7BOdAwk#3w;5&qxYJn6zwtT1bhV; z6#9tK`ylcB4rc$Dz1^xynJns5} zDot6i-_pinIPlm+i$LAVNx*L4jg+`xK%6Ag@)7U?Y$v?$Tz>Fa1NFvGK_}kPZ5V{YIGCygB&| zhp<#d?MG{Q7QYDcCXuyf@ai}av5)i}e7$s?S^G4av$am}?9mTT0i}3RRV~=oOvHd$ zm~eV+nV&Q41q?G#Qf*E|?A#{d6l=777hr8Hl1JF%lJWPe?;)1C2ybTC$;ruQ$CvQc z3ssG8@~s$aX*;72p{8fJR_}(1KTz>r^b&`x&3gi>?#|fEUr1f1_|*ObzX*N8ip1)G zvq0>wW-)kPLWOY$WX`e6o?<|&TD_>&5b5?ZlFP}GF;@log2te2s zBJmMQy7`$Op~OVGsW3ao-6HSE3geZu=buMZ^6CLFU@~PU5YdJGUxUYZtAHz8MTmLN zgYCUIxI)qA5Hpture4~KeS_Mxvoe;O`*3k!iBNxB!^coxM_s@ZIQSN*IjIqs#%_z;k1E-%s_PT#k>D?O4cv#BR^- zEERVl_SyMc`LQuxXxWVs`a@_DeNch_@?<2Bo{m^=t_Y2H)KN;8MfN{d@xvfF<(4ANYkEH`2m0gC#VDDu#S{)BHKarWe%Z3sDnX z2GUl-mX8!dJ@t`xvZ3Rmbu9ZrT^8@`!#Gdwzc$KO0fv%=)(*C!7lhGoV}YAOH2^Bd zQ?`(rrry%=!XGSfeh9q~4^NOQjRl+kjh>XJt!u#hP5|L9fHQ7#>K1^(=1-EfIwkNW zMWynUEI%ZfFzv3-omB?VBTW9<_PbGX6Vz^6R-rZ>b9$fOQQzp)Jp6ih+4A}=gf~Db z;R{ak^UVY;k_1Dx5pqa`3V4_KQ!9#UjN)FEFJvd&+=l-WO&MBD87op9)sAE?R5BC^ z$kXsbaeu)#VMt`3G?YQ|PisMf_oSHtobqM6mA+R)#J@M|7%qD&IrxdtyZ(B{_#P+t z^Q+j~Q0QY;U|lPJd(mbztk=bb|LYVr|}47WR~TeF~5vQ7Gsq1M;#j{^gu7B0_#4h+w>| z^;EibY;(Q)&*^Ub8dEum+@A1go4#{D4z67aQBJc z(iO5j!!wx}n4jBxC?RC&U@hSody)B;{+P(5^zL?Qx^_qyeNxXv$1XJaAdfF1$5$P= z9>8tQ-lq4&n`$Hj4HImS;E*O831|5_>;kP1VU%o5z_}G6hN^}x_!d_71%?ek<@GBp z8mRNfLg#eun?gCo0BPtU_YSgMSPFTi`l7R^>)aPF=T3}Bg7eXYn!U1oM!9~S%bStn z6Jrjd6*B}~bUE=Sx+4!f`yXD^E2U~~>NVuSGlZ6JZ5_P$BA{*=lB$*H1jJ#5RYHG$Kz6@G^AMGje!($Ok(_R&601~;9j^zh#= zn}45V! zwBv;Oq>7NHs8ZAnX5{55hp+VW#Axa@mlgktri`N9+!JE#kwl%>`7-qu_k=q^cFB%( zV_sn1SlIMxy2~Tp-mipy{4=6E;vR;yZphyWcOscz+>y!4Q<)7!y&vmGHZg7iOP3^4 z)fkFc1$aP@`^wM8QPh!y%g;eEP1OA#>4mn*tH_l{#GfJAyN}*o{L+`YpN$A7^?6sy)SsW!PW%aSd&Hz$se_A| z-zb5Qlo@gtGB%Ift32xR@JlA$?8R}KI!V|o4k%EVet2X2tiXuJYhOq8Ej`5f<-Xa@~V4!;GaKMMd$V@E*m zpf&jNPw`@8;KG7`$ijzPD~o%iIw?2v+w19V;-y4?P!qi9;%k_I?`H@(K8Y8a8@=)V z)2pjHPEA-2D=R<(i;fwnD2I3QTGNiZms2cTZIDf`skF=yy|_JHPo; z|Ce%xXMmh6{F!)!{bIG4r3X|}{~;eR2A+k!aHcCh)Ztqt0evkX+`nrf9XSI1{Swb9 zQP>bJI|$g%?e&@l%!G#T+LlA{+;Ucb8TT%7>>lZvsyVA@%?He4Wgv!a6oy6bO)$zq zlkej5vwSWv={K1m$$zrm@!*fKKthk5UQBC3>aP$VAZ|N8Ax7WoXCxQ!yzM`+?7?J- z;FMHW=q?(IcvA=SN_6hcqWPrjD%C*|j<^qhrAo%ce~)vHN4lmveS1l_^s#DDH$`?= z$cBIFujx3?lZ1xY<=obI5LJoX62AqG?%@}I93%Dt_koC^d#dY$PpM;6TV3Qc=d!F4 z>sQzb+}h=g*i&|?*ygW-4B@@hcQA2P<2%rXNQ*13{gR)4N_L7YTr%bv@=cyghz!%- zVAMhzekKwGq*LwZu=5Uvs)lSyw!?pa)1DV!w09W#C1a~})pT5ODzle7)-+eNA~n;v zfhp4>5acbCSj6sYjUO_jzT`SwwjTSFG;;WM!@}5G>2H;?>4M7rFH?h}n&lzuS1f!3 z5IgEa{7QYYPK_AKwLeMK1MgtY%6Iy`v4hEV+0Pnj_>D?JpThIO@N_UMWQ%8du|9^6FB-Z6}JhBlu`E(c@4#+ zGC(-WSEN| zM}3FF0nSb4sJ|^H&(=Gz>36RdKc;HR_tYCQSClG4vmPf(4Wbt!Rv_zfm$^u-CgeN9 zUs0SVOFW=k?OH!>>0Tmdw@Yq3z^R#~z`fVb^l zJb+_2_<@_ky?#gb>_8>Mffyg=O7C~yKofe$Z0#u<mZrwL za+O1A6CP{5I|#zF;L%~~B-J)|B~y$h@uZVb zG*}nU@PPrg|K{k|qL#PIg)b>H+7lYFpdS_hZ##hDcm?UxJUi~CZS30wRl!84XS%rY zO58aA&ZD{{7f+ynO5U<^&uQL?iS>DD-hR>Myny3v(MPu!~`jCn_DB?m{cR^R51T5(MZjv-M5w}h5vKt05);W4%6v5Wp`Yj z(tBU7)B4e9Y0~cL)Ww`cc!8V!I7tMDiVTxXEG zfHHlF(Gn+JbzvPFO3fL?Zrz;%hh0;Sdb~c|O)cCwwaGa?5x55ZJYuezZ$o@NQ zZ;P|}sKYhl)N||k1L!a#O`gOH3sk%S9-6yOI8^hLI5Kx{4YFzt)Q7}uWPCSH%R&3W zPPwESZ+Iik7;glZ5jYmY51Fq+w*XYMZ$9KcWzBo)(t!9}-Q1kqtX$G(jytn!BV zEuAijZ*4a|RqE=c4ar#F^IelfYpi6DyyMHd>)ZKfIE3<`p^CATp?OWbL%r&~3azVCv*WZ1&mBcHew<$Kp2?_)LX;%@vksq$d*jW`$xA z^2^#ExdJNTIWD}&pBU%_a8~*@r?}{9?WFUKLFgCf&OuMa@(^$H3k))#$0!O`QnIA^ zelm*n!bIV5p*Vu_w4q2_ha!lx0ro|LM~p)&i`9SR;{j_4`TG{`v#M9{WJyKi&2Bd> zYNGVT?(p8FOq46v;p%l3%4dpa(TWS^s2Q@ApMkEbLpF<)&FhB2t?1^jwjzDFhBax< zfDA44v|LpL%gq`119G!IJJ35q)eo?=pL@=+#T z%rJrJz}sXij~-W5kHZo+mOL6Hp6;pKJ5kpTJw$Hs`brT|Hb05jSnLXL{?0R90jlzJ zjI=ylu4zCqrER5AuyE7%#iWxrM+3K?B%K!e-7`Tf&0P+^?#M_BfK?MI9P0R1s&PU} z;|kSnHQoa-fC$tCW8Ojui?vsnfh#%7UcW`$|7wQP%zwyQU(`59TxQsZW_8C}jaiZM z)NsG?%>tn~Exvan%aJ{u)UIq}r@Xuu)o;2S*=XUbeaSIr0kEa|Z1h<)5_%#~-mV{m= zDCS~uVy<7&JC^H@sYd+gaQS>4hF#1aAiDTMxyaaawEI>~EMmvkK}r+X$fJaZdOR>T z--V#W=C>uuGNq#QJa)nE7~!Zu=^(kvh{!zv=L5j>u3=lBSXYf$KV5fj^}O=|$T_ac zWp-s|={fdGBfJ-(c0yc-3Gyb|#s^3wegN9S9dlMKaH!T|c1SruU!eK7w6xf5Xa#o)(BkN1OEq)fZ|>PxO_cKw7ICd@f{E_ezJ}2I)9FcQ~L}fc| zwD75xSY1O^X716J@Z-J5u2`X+zy_{0?IPy!B9QtRspk+?k&ZMqFNF7! zj|bbnccM+~rfZ*FVmtaR-e3%Ia-Z?3Cz3btCP|mKOACd*BY>{*B7F08lZY?HTp;Ed z==H32S9btoWd@nKj7@(|u%xN*%>f3_4^ih^q8-yWh*s>K%`j?IURoLecFim-vhe7$ z9op3M@zShTyQw4=rIM~Y8czngFyk3Qtm_iv0nE>3c|dlpEDiIrKDo(v>HdZck>?`v z)%cw%yZ{%;1ISW^Fiw-9(0P;b;yJcPMjoVs6^VFXbmKTQ@gv$@_A6lrdiXrr`t45Q zEX4FOeu6IVWKQwOlx$ufFR73o5uw)(ngK<|zR9*Cqwzh7Dnb?78seD#uNaSi3sMh= zU&QhYD~eO!Qe8@o^#YcKv44;|F+N5EPr!v%+kX$?s1501I3-DNq}harU1MIHY24mX z7Jp=wys^uon`jkD^ND-NFBNK&GUC4xJhmAcM(_j1PIVL(X1C;LtPJVy1o_0HeV~>e z5G>n!W~dK8E(%dn^D9!-I}O#~+er3~p*Q4PDxX$}lChi9X@@p70d>MgwHJOI!cnGk zym;E{^}rs)S7W`SB7w@VXr!t!GemYi|9=+1PbcQR%YBp=&xU?ckV8xESLm1HY!@v2 zU5eLTIJ-+ErQhN;zzr+)VOnrsgGy4AI!y%Nl!}PNQ$BT4}Nsnrj`Bqygw_LrQN*-iwYW^i9V6SNvy9F|FT_0+?gRDmoXOi$4BH zB%zF?M=|n6+Pgs0&iU0lVhQ*ZC zrOrlCG0S1DscNWj67W1tLg4?=miZvc1hLRs=a9JWBbrOk!E1z0w?*R4w8dwv;-kV| zd{niN8u!|Geqpl{Ef!iLensE>oS=s(qxN?g-s zm|sDo8(>yWy@>hOTg5wBf!J8t`fd+l!An`5r77;F9S8@ko$JOW*P!oL(}6J^ zY(@CiFUBL#_gwjY z^}+!z#j`qz*^RB8d2D;RUKE3zUF-Odmt+;+bmrG(TXjDESx;p1PFx`Af2u%d=Tkt( zhM0Xy3(}8>TJrD`V5VUz%gtEOXz(^jlz&6LPHH@J6j?uYG#F) z#6iL5**`o=Y2;Y5BEDNDf0JdrD}Q9wKKy}4#cvn4xbJ<*u+o| zLgqj$mZ!53$s}RI_pT(M7p!mC!cF5A%qj3{81zQ4PJfX{TbbQA(w6$Ko5AJWj1=B; z_AHMoHUpV`k8h_JbJW@Rql^g$8K4J_T{s1ZGNz}4YxDZ0 z$?6r~g~)bv*YccigRx7{V|}TWLNPDG;>%%3L0qE^YL3%I-IID@hO};mRJ!lk1LtaOo{*&PUTnb)Qtd~5!(mIAKq^uG-qn*_kP=`Eh(q2>bUS{ zVybs7cfY%fbl;{AeSf+yv}GIPelIXejh(pPiC;O*KZ5ZsVPBQCy!_3ec&AM%Z18vj;xWo$TA?O!-=16=xNB!!)VWCIx>3j-hOCw2O z8Yw7LRuVNn!h)>{*0MaV13d^@8r(CuzKg3C&N8QeI$#m(^vsi0=>5B~+FMe{OF7Y9 zA#~>XA?P2FIYSaXt$<|llG@d^6LFrP20EH=VXTW9XJehcLYxnsT_j#;r?3Mh43fqzWKD#DgBo0`h&-E| zO~ee~Ae3g?U;z^T&ePxI1Hs4!^v&+|xCJUPX*p%l>GEk)W7fZ%{lM4}V3(l16(p+B zc(X*X0@jkrD@}%kunu=aaAa&_@cNsHYOB$;+Fqg9;^5l3pWUz3+p!(Kn-M)D_V$0M!I%`IJL&W_>ec4*gV0|=QhqqTq2m|1 z583dL9ZsM5lbx8A=^KLatgQd`k$4_5XdlY3=M;%=d073f538@l5-N<&;`8mJq{ zwD^I=w6wQd1-=slXSiW;YW5{+;aQ>RnuV4?Y)Z z+9yv^Hv=ps4O&|s0XqytRqqo-;VR`Ds@( zx9Ewy=xKR&I`W^_WVgx%;`6#6NGInfyF&wVMAJG>vV9|;?3*Gs&ADS@qW+tWu`Lsd znoncD09C&Vd&!f80eiHWCuc*()E>G*G^#4z4^&PA5yxMik-=g5DQaNFVA37z_-z8= z3gDJg!zc6AC>L@Zp3;AMcdCdQytJ4~?8TnKtZ_D6M}?N-WhI}xv_Ol0zCK7D$;HrP zPk&pX-hKsD61gXYuJfB5?ux9J`gZ)=U4BQhAf!LU^M*35iPey>1$-?S;QEFMlI&EJ zto9W@NuA~IlvtmCa6p}4t7DVWUB8bHN~U}MSt7UNZUXUUu3(KehM9@e#sQZF<3P8Z zLT9JBl_XDV11|{!fTy-9z9Bs77r{grU2eF&~nl@}+?5yQrc%7<^+(JKHmlm8NS&DLXY3ro^o38Llz zl%UEeP|ZS*0Y)n8)Rz(rc{OLujW9x~!^+eTFj{OB3ZB>{PEK)zH;~?Q0#IMXy%LM# zeIXx*LOLn+>+3HTnj$}8$NO*4a#amsC#>B6?2GMdZh`lJLi4XGwBP)AjpcUHaZasI z!i`=b8Um|!xA|*@k{J5j70IC%wNHg+!nBlcAx=T#JnIjUD$23nnJ%o zwKJaU*!MwV?)~Ae;^5nikzn5N_xe-Qsd8iXut)B*C8Lo*mY&52k^^|!(xm+lUdU^S zT#?G&!scsH5AyCbsCz?mPl?`lAL_KNAB<#=kA4^~bp-!_x1kr#@vU@5CW!@^97b=q z6kn}d*JRRv(x^TS>vl14uAjUK32ZK4ltpdRIIR%8H(v2Ft1m{TW1Jp`aKv|Ds z&xD8Z>kd4Jli2*wnPTIWqU$_6DxtD&!bK-ZXKQSeBzE~irfxc{K%0Wj0&Jrm;^@FeudBkHlc(kL)@kj4z!bfiQP?KESfh<^TT%E z-4dzQv7mjLOM&sF#e4JhH=gc<_euFW67X<3^6VJ0UQ2I33GHA}pAk=|tM-FEckFs_ z=T8F2*`LuT02!zZ)lmY}Mbzu^MGPS8zX{ggu3@*wH=%oIfbN_$yvj9LN}uk~q3uSV#V=$!|sl{zH+ z{=*>S6~j7D%;@JcI6;iDNuK6*01vMMny_X7BF4;p{TcYy{Sp{0H`HLSAlpXCp^PTH zq@{QTEY_S%6u6755G=1ySL3g{gz?74Vl~k*U?iV4ZH3R;iqCZuna#++!|I9i9lQg@ zyrzNTX@?hRUHrgKHe5*XR)D_%@E})baMkEBT=gJOzyKZx&M;&vJ#u#%DEkG zD`-shy7^5Yj7v7r7c=G)8h8$({UbzwWCo;1?pdH7$!e|Nd2mHx`-OPHO-K0{#;OLg zH$5(@VhyTBcaGwj3h{%9LO(FpT=R8N9;gY?QWj1EG>hpYm=Y7&IYJw~-eE0^bqWWZ zUDlbNzr&!Ibh@PCE>;?sR)}k{^?koeGNvHYrK=jHEC(h?^r3E#xh*bm@G+jN{h#u1 z6njeUJ4PfMxGZ(OAKPjYW952^#ixnms1u)6` z*+t=h2`o1hq`+-nNHqsu4$fo6m+e()XXKRc2BH`1FmCG2d;( zr4o9;Htr7*Z*p&MepZc1JYKH>`&vlt1!DGjFaQnsv>(O<9gLbKe~*tc?3B#c2fh~7 ztsi0)3A~(D8CH||0FU*!TUD5v0G<4q+2$goRp;YUYA1;0w5)iS z$Nx@X@orQ>54>Y&SFJwq1|>B8^g2Zu1mOxTX391^t<~5MJWr*>0(N->OLCxs|R z$O1o4IbJG9Y2F+_M~DqpvH#NX!fY$ zN@>CTJMqJZnAF#spB~~{Em~ZzyI8j%QGi~((#fHtzsL_Sb(Cvg|BbQhI2)V+P%K3l zCK#c?r@GUZQ$ge(KLFFw`sJyAN7Gmx4 zQU_G9wl-lb4JVp^BhnARsGkgjr!C6trtFSLE6iZ5s~t|r<)z-I|Ljd*A5yl2ZXH*s z62-`e*>#?*7A|qX9Bp)<~)P zg&5oWcWCOzo_8F+NqFyYV!d)TPUV1uLrU0tqfO_Arnmjwa>f3PL$9a1%wIpX6PfGc z%1B5{xuM)lD5lQ^)_gBwH5lh@lGKCjLx;;_I+Kl>4P~RD~Zm`t=T*?2U^~ zC<+>@RgkE98Gp3I$!3m7t0w)q7jkC!sK$Cj&<9&1T~}eRTkq6Kfk5`A#P#d%q#!Hs zu!PrToYS^Ww-oX7V!KdIbahys^U;zrja!`*KYcWMpz7tF?`rh#ISQ8sU(*$W%<7`` zp9R!1iGh6Cn@_(fR&`j9#PI)cgw7K0QOG5U%%Z%#E#+=ro}d%EP)H+M!qM=Xlq)Zj z)=_>TDSWqwi^M4cXylLK-B_HeA{h&L<(U6yJ^p@)PJR~TIdOT4_;5kv`{#Ej;Zyf+ z20!|-3JY#>Qr5!BJ^RCt*%hyQ6 z;t_>Fji z4&b?o|8DO4r5gLd7jYskyKkeC|X&((@dXLy%H!6Co!3dm8JHU%#?oMq>35nRPPQKp-|vPKLoSZv9R=}hsOjag@lH5wNc zmb19AQbPHm+nQOOUD}UN%)RFr38jaydXm9Y!yczQlOp&AWJjBUN#~fTgPy4loMTJv zac6oH-4JTM$ONFP=qkrxUpFNMzR?7vaPa|Cu#NrOZC;-H3i4@#_NLXvgAS);MC_@0 zWx@8tKdxh}^p|Y-dV{px^c8(4{d|eqGoItK9bz@t6FHu9yUDB|{V4mZbyJ>4D2AUy z`m*D19Yr5CcHo*LUqWu633zwGbD{U%G0D!~J#j2bFx`BZ_DeV2a6e9_e?i0%J zmX7ivtHI9J;1r21`5|Q|16vzCJ)0X=*3{~%XQ6K+r|JxEmcJuOs`a$`+6ILwU&kyW z6~#Gc?PuaYdmTlT4KfrH465BUZbb&W4ac`W!l=<7Km(Iof(8m~fQ0N> zYe%R2js1lRNv-8+>#L9IwGkye2BR8Xv)fc>7yZYd1%kd`)&cZ?4|EvzpmT!FNMcqL^!d~nZguWr(%m^l zdA}lW{mH1$U3Bqo8@(Af4P(91b5}!6?FHI3wWt{gU8BAL@@%YD2iV@4w5nFH zj$N>;C){BtJmH>q{~G?(NKsQSC3t=2gmYI-+O98`h-UZAm$-G#nTY(3LQncs?2s7L z8U!&C&3J9Qsb+)Jm2Y)@IUB`d9%_wz-0Hd3{}a%O!`Sw!bXBD{#`HzD{nPs&y4%1E zD%oiyyk$_EnhtH9b-&De51}Z&CFOcY_At`l(|yP?>n!Z<{gUq+Pyit98+TJL0n6SsHlG^c~#j z{W_!7VDf%>r)uBf?SU$NZsDAX%+gXfi@YIgE_+&V`;&z|+}nF|xc$|!>ER45@!Z2T z2R0G-W2R=A&m9kv zvdiW!o(y%osB}u0aHi9>$}wrhm+PLP;HPUg#>#G6O&2l0cWO6Dwu>!ljFY?Pm8k5fmF0x#XL|kA zxw~wZ+Mx(vJeZ|EYGQlR9n0NW*}9p#N8K#8!TykC2Qp$L!8`h@gZcdF=HibZ>+iQ* zKk(=(D;;|p^46a9>2gh)8jlUwrX^C3q7C6c_CtQU%xJ#QdPDouWz@9%oE{ehT9 zws_#fd}~qgt$VQk0k5qJLXUS0oRkZf`IgcFTKStsHe~NrFpL$pQB6F2@`%7x#!tjW zz)`if!3VTywpz5l*%3=6GlA?~p!7xyhn<9ZVmu)n`6=atl1B5+6$~Vyemnk({~hdA zSKQJ^&ED8^SPR*e9x?rAE^NHKsE1&D8d=|)P|`8mQ&i7-{8#gB&$TL8@6``-U^!a{9WR8rLQ=Hcg%IdABWP zH_RFsnH*p!x;?#=yj$G!3BjeI2R(u{EfLO=mn#XuUQN*ig`eOYuisaO-hm2xg*KE5 zOY4wSU?VI)UM>!(`}P8xS)lU4TlM5gDcrH9zAD;Z*0-Ge z9MJqi{^@J5+h~fR1mg>RYHl(5^ZSt6DqV&v<~f*!{e!dKY#eHjt5}gg3QA(0ZxHHC z_JOsZp~l{5pEAs&-EPO2aJBIz%Y7MDHE?Q#&>{D~JZI*X|Mrd>R7;3Nc}ADAW}OM9 z+2z|z1dAkpX5?J|G29>Ps`c8AIo=l-IW4Y*|WaWL7AUn=Tx(U>vv&5ZWKfdt>Y zuYC*H>Ik#~O8-aGdH+-S|9||t&ayXUJ2sIKvN^U;Bq734$%shyJoYLhG7^qW$jpd1 z$mmUW2yu`t4jCuwe0_fSUVp%KdtJBJb$eZ}>-D@pA9r@lVFx=@geJLG7T}}~QP zhSkMZzjzHXzX4c%Q-0f_16kYnz@1mTe|@{>PwI1?hYooB_?1pMD}uvafSVth{iDe_ zR7i9BnLbt(We$>ei~+_%51`vKxYgQRn0_%eWcn2#3S?hKK|IHngHKdMj`9|L>7aZF zDi_tj4rKMvpPTA+pS#zJ&tE_1md!8Cz^+gta?Te@slz0(wRF>ng!Q$bbEKL9GJ?Ot zc{~USnw~v%mvROEVVuAq7uK2=kRUCXJ{v3`=gljPvq;Kb)NOjPRzWDKWXTQZvac%<~;i{jBs#h}$&Bi(Q>QNa4Ri7eg^ z)jWo=^FD1;aUk!SS)&USxty8E`vTk*b?1pX7Y zdn8}c;bC$}Je8CDZA|t5vj87RvEvjTSr}|SBHX-9_|eqa&+}l%wI*DXHv#MIG%T6< zLHOpa;+>v0SKBfy^^Ybdg`8Av8D1q>OdKyAg1OD{UGOv?}>pI5YT;297R* zq{l+8A<$Hibyie{Dus#7jAV^PARGivO^X4>kNrv9i+Y167v{WO4+qT7s!d}+BSCZ)CNNv-au3`<=|Kl-fZHV+xUm{)h}=bZ zuy=+WOo|C*ON%WAUAPFsd?mwu{P z0<8x9;qCDD+ka z>@mT2&i-~a`ngYUAK0JXOP5VPxKK$7B7C_`4b5Rk$zaRBeGQE(1fHJ{Q)1bJaV|38 zLn|Ab%`8|W1XWxKxcs_?X$_5x4l<=lt`b6`srC>?x(a+DorgZbb~xuFLCE?(M7-hk z;-%iv>TXBP7Zv3DHMt|Y*+(6ZAlpQ1N<6nB$Uy@Oo>3)g=*ZBcE`K(|gi0aF3L|4R z)=)DUXxhc@6hVwM+)e7!#?F$AW2sQaux9F08d|@oQ)qe~PtBVDpUw^zfpzDz;B=Oe zYbc&X0Sx}Sw8l7@7cBU4A>Kaq;U7W2jY3J2FWP0T-DNC@4ojwLZkvAy7*W?TpjbzK zZ=5v|vq?1QD%h2NGF(psaQZK_Rr(|MVQz$Mb`-e>r#blp$J^Kl zARhdFy9>AtvO*=oMBwPZz=u7Ixm%ZAl~TJo@M-R}iX45t4{)hsl=?xUiIL>hMKYv~ zZ!>hDk?5oYF`-JSiN7tU9m7wFuSb&f5F@Kl7cs0BpjJJd)F!QqdB~34d4Rwcx~M5MfLCX z2Nn_YTnpB45xWJ9I^F@W{E_haxa#3ShGP<`fy ze^bLNWyJURgAqKkq7X}J92s_aGgLmCB-jKmTNQ@4*ONC};V(b*fx%T(Br1vta%6uB ziVI{M9}ZSWZ(uO|s}&#NuL^R}X!!YVImE0brb=67U}RVXa$jhniSop!|A#)HK`YOi z6)JO5kzGbIsDhM{PJhFYNd|LF%O+nLmCDDJvH0 zUGOB0A~L6~A7dg5P}<2!>qu1ii5lt+*5W%w@flNNe)~N(7*s3~2ufTGK^0u6<;83s zi-44;=Idwvy4!vAn_}Bl=YJ=@0!J4P08#?rIVOw{A0z3nF5@Rz@SIo13b$^(f+H0n+FdlaaRddaS%0Wpz*7FuHGl#X@>OyrQa0-rAa3tg6$Oz znt&5oCP`?#X9?IZ`;~QfACO~+g;CEWSvt3>&jPy6dox3H-|5pSmiS0!>TO%W2&^vqz zN|P^$V&zN_7E<_`zj-INT5(%GD0qMn^Zf(eoSkPr`QFV3&@4#VP-iP zjRkjNL(5IU^z)1eag?)9JJ zf%g0>97q3D3RRCF9V#||zZ9_IEv$@*@tWvGvOVA@QZgem$X-1|Qm47VEAH9Wj42$eg5rZlo1SDq$Kgtt3t5`JV7`4^f?KYgD^8K+awo;{;J4B4M zzxum}0?p-7Z7OydIR2OtQ`m`xd_~kS0PEIZD1FeoPF)1Rg3DCE#HugN3vPz$Gvnwr z)g!4#%4Ep2`EC+ewsSSb$wlxiWZDcP6^)dhHeq>$MSJRXxBFkztUdc2LsK2n9Tiq> z@Yy$MLTAPfpO(m9$CujVs2NYF`1))d_)`Y7QsU>0>3*Dx(`IrZX)`(qXv`B$GcT*I%Oh$AGiJ9Q97R+u<^hl>-jVdnfEW#olqn7kzAt493HoNpH0ccTARX15rsasvG_5*e25yH{ zLw4WN(4-{Akr8wzGy_6(+8Ybxc;n7JibAyk$je%G?xK3!mpoja#xC79X_{4)P;MpH z$uk)v`ZgLx_v0xOyaQ#oRRuhX19+w%QLA@op_wk3`bt^QW^Yl@?v+|{2)DIP)0XhW zUulQg>jaH|V!y}38(sFVCaU7UTl}uDBfPtDz<)GIv{jK^%9qF69I6f zv*C_V_Xx-{_#<8Y3QVdQH-wn?P@Fu}nY<)>eA^SyFhXuGR!Ls@yI3#TJA9yf=LZw-b6Y-+>IkB=;rKI2 z$?Jre>q3#^&UgNxldclk_8XptD^*AGyYW`51hqCcMeMvm%qluz{Oy0a^F`K0WoSONflz>6}-I`Q&Rvn3c+JUbo8Z*BE2vri}EZ$M0M@e zT4{7_?M?Jmb^-DbNGR%#EdDEI8s++HiRtTiUwf4xX>Xhs+YYPPQRdE!^!AYR-92&g zdZ&QT>3wv)55T<1;5@07r#XAda{uuYOK)G#v*kIV`AbwWjxj+klhi{yr0Go9Ko6)T z8J01VVcpp3%K}xf@I{>gr@Yz(+r7?vzz*pBbv-A5%~FPCtg&8JE?|WzUyGf`D{2^+ zVzuXD!oi7B(CZegK{Tobd3VGFC5>ZcyFW@zsi4!|#^tT*eKxhy}uzjTmos< zaK;X-$tS44g2jR?(X>(l>S90@7;W@tH>_h7$ghq?y~#lc@iLzYDL#6xRP>nLDHRfA zV~K61XHt`zw(sR}!t8c?>0p}#jO{2&@2@!$|8mmU52fzh5x^(-|F=pt?_3O))T8Y- zDB5zM$Hb^d82K_1^bAXqk5k{k>~!u-#+(0v&_jpN9Lh36)Fv)yecEF3x=PmapSV7Z zjdJCKN4@rCG{u7Yldf`Oi7$EMp3+G3RhoslXwr<{<;M*o>bYw2;nw;~7TZZAM5_xovGn(NBQb&bEi7WXLH5JW0>~R2es5MIjqmWC{QV24FDLyXtu5Hhs9Z?t!3F_W z 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 literal 0 HcmV?d00001 diff --git a/desktop/main.js b/desktop/main.js new file mode 100644 index 0000000..33f539b --- /dev/null +++ b/desktop/main.js @@ -0,0 +1,254 @@ +// hqterm desktop: Electron main process. Owns the PTYs, the config and the +// saved layout; the renderer (src/app.js) draws tabs and panes with xterm.js. +"use strict"; +const { app, BrowserWindow, clipboard, ipcMain, Menu, shell } = require("electron"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); + +// Chromium's setuid sandbox is not configured inside an AppImage. +if (process.platform === "linux") app.commandLine.appendSwitch("no-sandbox"); +app.setName("hqterm"); + +const VERSION = require("./package.json").version; +const HOST_RE = /^[A-Za-z0-9._@:\-\[\]]+$/; +const SESSION_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/; + +const configDir = () => path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "hqterm"); +const configFile = () => path.join(configDir(), "desktop.json"); +const layoutFile = () => path.join(configDir(), "desktop-layout.json"); + +function readJson(file) { + try { + return JSON.parse(fs.readFileSync(file, "utf8")); + } catch { + return undefined; + } +} + +function loadConfig() { + const c = readJson(configFile()) || {}; + return { + fontFamily: typeof c.fontFamily === "string" && c.fontFamily.trim() ? c.fontFamily : "monospace", + fontSize: Number.isFinite(c.fontSize) && c.fontSize >= 6 && c.fontSize <= 72 ? c.fontSize : 14, + theme: c.theme && typeof c.theme === "object" ? c.theme : undefined, + restore: c.restore !== false, + }; +} + +/** `--host H --session S` (or --host=H) anywhere in argv. */ +function parseArgv(argv) { + const out = {}; + for (let i = 0; i < argv.length; i++) { + const m = /^--(host|session)(?:=(.*))?$/.exec(argv[i]); + if (!m) continue; + const v = m[2] !== undefined ? m[2] : argv[++i]; + if (m[1] === "host" && v && HOST_RE.test(v) && !v.startsWith("-")) out.host = v; + if (m[1] === "session" && v && SESSION_RE.test(v)) out.session = v; + } + return out.host ? { kind: "remote", host: out.host, session: out.session || "main" } : undefined; +} + +let hostsModule; +function listHosts() { + try { + hostsModule = hostsModule || require("./lib/hosts.cjs"); + return hostsModule.listHosts(); + } catch (err) { + console.error("hqterm: host list unavailable:", err && err.message); + return []; + } +} + +function loginShell() { + let sh = process.env.SHELL; + if (!sh) { + try { + sh = os.userInfo().shell; + } catch {} + } + return sh && fs.existsSync(sh) ? sh : "/bin/sh"; +} + +/** The environment every pane starts with. */ +function paneEnv() { + const env = { ...process.env }; + for (const k of Object.keys(env)) if (k.startsWith("ELECTRON_") || k.startsWith("CHROME_")) delete env[k]; + // AppImage runtime variables would leak into every child. + for (const k of ["APPDIR", "APPIMAGE", "ARGV0", "OWD", "NO_AT_BRIDGE"]) delete env[k]; + if (env.LD_LIBRARY_PATH && process.env.APPDIR && env.LD_LIBRARY_PATH.includes(process.env.APPDIR)) delete env.LD_LIBRARY_PATH; + const localBin = path.join(os.homedir(), ".local", "bin"); + const parts = (env.PATH || "/usr/local/bin:/usr/bin:/bin").split(":"); + if (!parts.includes(localBin)) env.PATH = [localBin, ...parts].join(":"); + Object.assign(env, { + TERM: "xterm-256color", + COLORTERM: "truecolor", + TERM_PROGRAM: "hqterm", + TERM_PROGRAM_VERSION: VERSION, + HQTUI_IMAGES: "iterm", + QC_HD: "iterm", + }); + return env; +} + +function startDir() { + const owd = process.env.OWD; + if (owd && owd !== "/" && fs.existsSync(owd)) return owd; + const cwd = process.cwd(); + return cwd && cwd !== "/" ? cwd : os.homedir(); +} + +/** [file, args] for a pane spec; host and session are checked before they reach a shell. */ +function command(spec) { + if (spec && spec.kind === "remote" && HOST_RE.test(spec.host || "") && !spec.host.startsWith("-")) { + const session = SESSION_RE.test(spec.session || "") ? spec.session : "main"; + return ["/bin/sh", ["-lc", 'exec hqsh "$0" --session "$1"', spec.host, session]]; + } + if (spec && spec.kind === "hqterm") return ["/bin/sh", ["-lc", "exec hqterm"]]; + return [loginShell(), ["-l"]]; +} + +let pty; +const ptys = new Map(); +let nextPty = 1; +let win; +let pendingSpec = parseArgv(process.argv.slice(1)); + +function createWindow() { + win = new BrowserWindow({ + width: 1100, + height: 720, + backgroundColor: "#0f1115", + title: "hqterm", + icon: path.join(__dirname, "src", "icon.png"), + autoHideMenuBar: true, + webPreferences: { + preload: path.join(__dirname, "preload.js"), + contextIsolation: true, + nodeIntegration: false, + sandbox: true, + spellcheck: false, + }, + }); + win.loadFile(path.join(__dirname, "src", "index.html")); + // Nothing navigates away from the terminal; links open in the browser. + win.webContents.on("will-navigate", (e) => e.preventDefault()); + win.webContents.setWindowOpenHandler(({ url }) => { + if (/^https?:\/\//.test(url)) shell.openExternal(url); + return { action: "deny" }; + }); + win.on("closed", () => { + win = undefined; + for (const p of ptys.values()) { + try { + p.kill(); + } catch {} + } + ptys.clear(); + }); +} + +ipcMain.handle("app:init", () => { + const config = loadConfig(); + const layout = config.restore ? readJson(layoutFile()) : undefined; + const open = pendingSpec; + pendingSpec = undefined; + return { config, layout, open, version: VERSION, platform: process.platform }; +}); + +ipcMain.handle("hosts:list", () => listHosts()); + +ipcMain.handle("layout:save", (_e, data) => { + try { + fs.mkdirSync(configDir(), { recursive: true }); + const tmp = `${layoutFile()}.tmp`; + fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", { mode: 0o600 }); + fs.renameSync(tmp, layoutFile()); + return true; + } catch (err) { + console.error("hqterm: could not save layout:", err && err.message); + return false; + } +}); + +ipcMain.handle("pty:spawn", (e, { spec, cols, rows }) => { + pty = pty || require("node-pty"); + const [file, args] = command(spec); + const id = nextPty++; + const p = pty.spawn(file, args, { + name: "xterm-256color", + cols: Math.max(2, cols | 0) || 80, + rows: Math.max(2, rows | 0) || 24, + cwd: spec && spec.kind === "shell" ? startDir() : os.homedir(), + env: paneEnv(), + }); + ptys.set(id, p); + const sender = e.sender; + p.onData((data) => { + if (!sender.isDestroyed()) sender.send("pty:data", id, data); + }); + p.onExit(({ exitCode, signal }) => { + ptys.delete(id); + if (!sender.isDestroyed()) sender.send("pty:exit", id, exitCode, signal); + }); + return id; +}); + +ipcMain.on("pty:write", (_e, id, data) => { + const p = ptys.get(id); + if (p && typeof data === "string") p.write(data); +}); + +ipcMain.on("pty:resize", (_e, id, cols, rows) => { + const p = ptys.get(id); + if (p && cols > 1 && rows > 1) { + try { + p.resize(cols | 0, rows | 0); + } catch {} + } +}); + +ipcMain.on("pty:kill", (_e, id) => { + const p = ptys.get(id); + if (p) { + ptys.delete(id); + try { + p.kill(); + } catch {} + } +}); + +ipcMain.handle("clipboard:read", () => clipboard.readText()); +ipcMain.on("clipboard:write", (_e, text) => { + if (typeof text === "string") clipboard.writeText(text); +}); +ipcMain.on("open:url", (_e, url) => { + if (typeof url === "string" && /^https?:\/\//.test(url)) shell.openExternal(url); +}); + +if (!app.requestSingleInstanceLock()) { + // An hqterm window is already open: it gets our --host/--session. + app.quit(); +} else { + app.on("second-instance", (_e, argv) => { + const spec = parseArgv(argv); + if (win) { + if (win.isMinimized()) win.restore(); + win.focus(); + win.webContents.send("app:open", spec || { kind: "shell" }); + } + }); + app.whenReady().then(() => { + // No menu: Ctrl+W, Ctrl+R and friends belong to the shell. + Menu.setApplicationMenu(null); + createWindow(); + if (process.env.HQTERM_DESKTOP_SMOKE) { + // CI: prove the window and a PTY come up, then exit. + setTimeout(() => { + console.log(`hqterm-desktop smoke: ok (ptys=${ptys.size})`); + app.exit(ptys.size > 0 ? 0 : 3); + }, Number(process.env.HQTERM_DESKTOP_SMOKE) || 8000); + } + }); + app.on("window-all-closed", () => app.quit()); +} diff --git a/desktop/package-lock.json b/desktop/package-lock.json new file mode 100644 index 0000000..16065f3 --- /dev/null +++ b/desktop/package-lock.json @@ -0,0 +1,3665 @@ +{ + "name": "hqterm-desktop", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hqterm-desktop", + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "@xterm/addon-fit": "^0.11.0", + "@xterm/addon-image": "^0.9.0", + "@xterm/addon-unicode11": "^0.9.0", + "@xterm/addon-web-links": "^0.12.0", + "@xterm/addon-webgl": "^0.19.0", + "@xterm/xterm": "^6.0.0", + "node-pty": "^1.1.0" + }, + "devDependencies": { + "electron": "^44.5.1", + "electron-builder": "^26.15.3" + } + }, + "node_modules/@electron-internal/extract-zip": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.5.tgz", + "integrity": "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/get": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.1.0.tgz", + "integrity": "sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", + "progress": "^2.0.3", + "semver": "^7.6.3", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=22.12.0" + }, + "optionalDependencies": { + "undici": "^7.24.4" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.0.tgz", + "integrity": "sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.10.0.tgz", + "integrity": "sha512-GhokD41lV4gQrrLYm3wCkHfBOnJrnhDMgt4XeMW8gzfE1UdJqIuSwsE+ggf82XBjUXRJylcm+KIGQFa4utIVLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "tslib": "^2.8.1", + "webcrypto-core": "^1.9.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.1.tgz", + "integrity": "sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz", + "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@xterm/addon-fit": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", + "integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==", + "license": "MIT" + }, + "node_modules/@xterm/addon-image": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0.tgz", + "integrity": "sha512-oYWA8/QAr5/Emwl1xL7WCoOqeG3IZfpzEz/OVq1j4Oi9934TQmHiyubClikRf0D/jL3JNiNuz/Lsqx0kXQ02BA==", + "license": "MIT" + }, + "node_modules/@xterm/addon-unicode11": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0.tgz", + "integrity": "sha512-FxDnYcyuXhNl+XSqGZL/t0U9eiNb/q3EWT5rYkQT/zuig8Gz/VagnQANKHdDWFM2lTMk9ly0EFQxxxtZUoRetw==", + "license": "MIT" + }, + "node_modules/@xterm/addon-web-links": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-web-links/-/addon-web-links-0.12.0.tgz", + "integrity": "sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw==", + "license": "MIT" + }, + "node_modules/@xterm/addon-webgl": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0.tgz", + "integrity": "sha512-b3fMOsyLVuCeNJWxolACEUED0vm7qC0cy4wRvf3oURSzDTYVQiGPhTnhWZwIHdvC48Y+oLhvYXnY4XDXPoJo6A==", + "license": "MIT" + }, + "node_modules/@xterm/xterm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz", + "integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==", + "license": "MIT", + "workspaces": [ + "addons/*" + ] + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-lib": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", + "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "3.4.1", + "@electron/fuses": "^1.8.0", + "@electron/get": "^3.0.0", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.3", + "@electron/rebuild": "^4.0.4", + "@electron/universal": "2.0.3", + "@malept/flatpak-bundler": "^0.4.0", + "@noble/hashes": "^2.2.0", + "@peculiar/webcrypto": "^1.7.1", + "@types/fs-extra": "9.0.13", + "ajv": "^8.18.0", + "asn1js": "^3.0.10", + "async-exit-hook": "^2.0.1", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chromium-pickle-js": "^0.2.0", + "ci-info": "4.3.1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "26.15.3", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "isbinaryfile": "^5.0.0", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.2.5", + "pkijs": "^3.4.0", + "plist": "3.1.0", + "proper-lockfile": "^4.1.2", + "resedit": "^1.7.0", + "semver": "~7.7.3", + "tar": "^7.5.7", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0", + "unzipper": "^0.12.3", + "which": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "26.15.3", + "electron-builder-squirrel-windows": "26.15.3" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.3.tgz", + "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz", + "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz", + "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "44.5.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-44.5.1.tgz", + "integrity": "sha512-lx7AYoFIiyvEvgvpyWO9kOC60xtNPpiPZAth6Fmg9NutzpCAMLwpUlaBY2EXfZyIgUdzEmLMj9KvK0Vrokl1yQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-internal/extract-zip": "^1.0.1", + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0" + }, + "bin": { + "electron": "cli.js", + "install-electron": "install.js" + }, + "engines": { + "node": ">= 22.12.0" + } + }, + "node_modules/electron-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz", + "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "dmg-builder": "26.15.3", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", + "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "electron-winstaller": "5.4.0" + } + }, + "node_modules/electron-publish": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", + "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "aws4": "^1.13.2", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "form-data": "^4.0.5", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/electron-winstaller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-winstaller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.36.0.tgz", + "integrity": "sha512-zrvE1Qy5ElbZVqmJFSiNfwzNZzUCepasgp/4dw0Z0fsn40LJkAk9FA9oMh2SKMeNFRRe/3IfeOKvWwWr8u4SmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/undici": { + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", + "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-pty": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz", + "integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^7.1.0" + } + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkijs": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.1.tgz", + "integrity": "sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "website" + ], + "dependencies": { + "@noble/hashes": "1.8.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", + "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/webcrypto-core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz", + "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/desktop/package.json b/desktop/package.json new file mode 100644 index 0000000..c4d2d81 --- /dev/null +++ b/desktop/package.json @@ -0,0 +1,58 @@ +{ + "name": "hqterm-desktop", + "productName": "hqterm", + "version": "0.2.0", + "description": "hqterm desktop: a terminal window that shows hqtui/qc images and HD emoji (xterm.js + node-pty).", + "license": "MIT", + "author": "Profullstack, Inc. ", + "homepage": "https://hqterm.sh", + "private": true, + "main": "main.js", + "scripts": { + "prepare-assets": "node scripts/prepare.mjs", + "start": "npm run prepare-assets && electron .", + "test": "node --test test/layout-tests.js", + "dist": "npm run prepare-assets && electron-builder --linux AppImage --publish never" + }, + "dependencies": { + "@xterm/addon-fit": "^0.11.0", + "@xterm/addon-image": "^0.9.0", + "@xterm/addon-unicode11": "^0.9.0", + "@xterm/addon-web-links": "^0.12.0", + "@xterm/addon-webgl": "^0.19.0", + "@xterm/xterm": "^6.0.0", + "node-pty": "^1.1.0" + }, + "devDependencies": { + "electron": "^44.5.1", + "electron-builder": "^26.15.3" + }, + "build": { + "appId": "sh.hqterm.desktop", + "productName": "hqterm", + "executableName": "hqterm-desktop", + "files": [ + "main.js", + "preload.js", + "lib/**", + "src/**", + "fonts/**", + "package.json" + ], + "asarUnpack": [ + "**/node_modules/node-pty/**" + ], + "linux": { + "target": "AppImage", + "category": "System;TerminalEmulator", + "icon": "build/icon.png", + "artifactName": "hqterm-desktop-linux-${arch}.${ext}", + "executableArgs": ["--no-sandbox"] + }, + "mac": { + "target": "zip", + "icon": "build/icon.png", + "identity": null + } + } +} diff --git a/desktop/preload.js b/desktop/preload.js new file mode 100644 index 0000000..7d46ba3 --- /dev/null +++ b/desktop/preload.js @@ -0,0 +1,19 @@ +// The renderer's only door to the main process. +"use strict"; +const { contextBridge, ipcRenderer } = require("electron"); + +contextBridge.exposeInMainWorld("hq", { + init: () => ipcRenderer.invoke("app:init"), + hosts: () => ipcRenderer.invoke("hosts:list"), + saveLayout: (data) => ipcRenderer.invoke("layout:save", data), + spawn: (spec, cols, rows) => ipcRenderer.invoke("pty:spawn", { spec, cols, rows }), + write: (id, data) => ipcRenderer.send("pty:write", id, data), + resize: (id, cols, rows) => ipcRenderer.send("pty:resize", id, cols, rows), + kill: (id) => ipcRenderer.send("pty:kill", id), + onData: (fn) => ipcRenderer.on("pty:data", (_e, id, data) => fn(id, data)), + onExit: (fn) => ipcRenderer.on("pty:exit", (_e, id, code, signal) => fn(id, code, signal)), + onOpen: (fn) => ipcRenderer.on("app:open", (_e, spec) => fn(spec)), + readClipboard: () => ipcRenderer.invoke("clipboard:read"), + writeClipboard: (text) => ipcRenderer.send("clipboard:write", text), + openUrl: (url) => ipcRenderer.send("open:url", url), +}); diff --git a/desktop/scripts/prepare.mjs b/desktop/scripts/prepare.mjs new file mode 100644 index 0000000..314707e --- /dev/null +++ b/desktop/scripts/prepare.mjs @@ -0,0 +1,44 @@ +// Build-time assets for the desktop app: +// lib/hosts.cjs the TUI's host-list code (../src), bundled for Node by bun +// fonts/OpenEmoji-CBDT.ttf the OpenEmoji colour font (skip: HQTERM_SKIP_FONT=1) +import { spawnSync } from "node:child_process"; +import { existsSync, mkdirSync, renameSync, statSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const here = dirname(dirname(fileURLToPath(import.meta.url))); +const FONT_URL = "https://raw.githubusercontent.com/profullstack/openemoji/main/font/OpenEmoji-CBDT.ttf"; + +mkdirSync(join(here, "lib"), { recursive: true }); +const r = spawnSync( + "bun", + ["build", join(here, "..", "src", "hostlist.ts"), "--target=node", "--format=cjs", "--outfile", join(here, "lib", "hosts.cjs")], + { stdio: "inherit" }, +); +if (r.status !== 0) { + console.error("prepare: bun build of the host list failed (is bun installed?)"); + process.exit(1); +} + +const font = join(here, "fonts", "OpenEmoji-CBDT.ttf"); +if (process.env.HQTERM_SKIP_FONT) { + console.log("prepare: skipping the OpenEmoji font (HQTERM_SKIP_FONT)"); +} else if (existsSync(font) && statSync(font).size > 1_000_000) { + console.log(`prepare: ${font} already here`); +} else { + mkdirSync(dirname(font), { recursive: true }); + console.log(`prepare: downloading ${FONT_URL}`); + const res = await fetch(FONT_URL); + if (!res.ok) { + console.error(`prepare: font download failed: HTTP ${res.status}`); + process.exit(1); + } + const buf = Buffer.from(await res.arrayBuffer()); + if (buf.length < 1_000_000 || buf.subarray(0, 4).toString("hex") !== "00010000") { + console.error(`prepare: that does not look like the font (${buf.length} bytes)`); + process.exit(1); + } + writeFileSync(`${font}.part`, buf); + renameSync(`${font}.part`, font); + console.log(`prepare: font ${buf.length} bytes`); +} diff --git a/desktop/src/app.js b/desktop/src/app.js new file mode 100644 index 0000000..9b44f97 --- /dev/null +++ b/desktop/src/app.js @@ -0,0 +1,567 @@ +// hqterm desktop renderer: tabs of split panes, each pane an xterm.js terminal +// on a PTY in the main process. Layout lives in src/layout.js (HqLayout). +"use strict"; +(async function () { + const L = window.HqLayout; + const hq = window.hq; + const $ = (sel) => document.querySelector(sel); + const INSTALL = "curl -fsSL https://hqterm.sh/install | sh"; + const MAX_WEBGL = 12; + + const init = await hq.init(); + const config = init.config; + let fontSize = config.fontSize; + const theme = Object.assign( + { + background: "#0f1115", + foreground: "#d8dee9", + cursor: "#5ea1ff", + cursorAccent: "#0f1115", + selectionBackground: "#5ea1ff55", + }, + config.theme || {}, + ); + const fontFamily = `${config.fontFamily}, "OpenEmoji"`; + // The colour emoji font must be loaded before xterm measures and draws glyphs. + await Promise.race([document.fonts.load(`${fontSize}px "OpenEmoji"`, "\u{1F600}").catch(() => {}), new Promise((r) => setTimeout(r, 3000))]); + + /** @type {{id:number, root:any, focus:string, view:HTMLElement}[]} */ + const tabs = []; + let active = 0; + let nextTab = 1; + const runtimes = new Map(); // pane id -> runtime + const byPty = new Map(); // pty id -> runtime + const early = new Map(); // pty id -> { data: string[], exit?: [code, signal] } + let webglCount = 0; + + // ---------- PTY plumbing ---------- + + hq.onData((id, data) => { + const rt = byPty.get(id); + if (rt) rt.term.write(data); + else (early.get(id) || early.set(id, { data: [] }).get(id)).data.push(data); + }); + hq.onExit((id, code, signal) => { + const rt = byPty.get(id); + if (rt) exited(rt, code, signal); + else (early.get(id) || early.set(id, { data: [] }).get(id)).exit = [code, signal]; + }); + + async function spawn(rt) { + rt.exited = undefined; + const id = await hq.spawn(rt.spec, rt.term.cols, rt.term.rows); + if (rt.disposed) return hq.kill(id); + rt.ptyId = id; + byPty.set(id, rt); + const pending = early.get(id); + if (pending) { + early.delete(id); + for (const d of pending.data) rt.term.write(d); + if (pending.exit) exited(rt, ...pending.exit); + } + } + + function exited(rt, code, signal) { + byPty.delete(rt.ptyId); + rt.ptyId = undefined; + if (rt.disposed) return; + if (code === 0 && !signal) return closePane(rt.pane.id); + rt.exited = { code, signal }; + let msg = `\r\n\x1b[2m[hqterm] ${rt.spec.kind === "remote" ? "hqsh" : "process"} exited (${signal ? `signal ${signal}` : code}).`; + if (code === 127) msg += ` ${rt.spec.kind === "remote" ? "hqsh" : "hqterm"} not found; install with: ${INSTALL}`; + msg += ` Enter ${rt.spec.kind === "remote" ? "reconnects" : "restarts"}, Esc closes.\x1b[0m\r\n`; + rt.term.write(msg); + } + + // ---------- panes ---------- + + function createRuntime(pane) { + const el = document.createElement("div"); + el.className = "pane"; + el.dataset.pane = pane.id; + const term = new Terminal({ + fontFamily, + fontSize, + theme, + allowProposedApi: true, + scrollback: 10000, + cursorBlink: true, + macOptionIsMeta: true, + allowTransparency: false, + }); + const rt = { pane, spec: pane.spec, el, term, fit: new FitAddon.FitAddon(), title: "", opened: false }; + term.loadAddon(rt.fit); + const unicode = new Unicode11Addon.Unicode11Addon(); + term.loadAddon(unicode); + term.unicode.activeVersion = "11"; + term.loadAddon(new WebLinksAddon.WebLinksAddon((_e, uri) => hq.openUrl(uri))); + term.onData((d) => { + if (rt.exited) { + if (d === "\r") { + term.write("\r\n"); + spawn(rt); + } else if (d === "\x1b") closePane(pane.id); + return; + } + if (rt.ptyId !== undefined) hq.write(rt.ptyId, d); + }); + term.onBinary((d) => rt.ptyId !== undefined && hq.write(rt.ptyId, d)); + term.onResize(({ cols, rows }) => rt.ptyId !== undefined && hq.resize(rt.ptyId, cols, rows)); + term.onTitleChange((t) => { + rt.title = t; + renderBar(); + }); + // One click focuses a pane. + el.addEventListener("mousedown", () => setFocus(pane.id), true); + el.addEventListener("focusin", () => setFocus(pane.id, false)); + rt.observer = new ResizeObserver(() => fitSoon(rt)); + rt.observer.observe(el); + runtimes.set(pane.id, rt); + return rt; + } + + function open(rt) { + if (rt.opened) return; + rt.opened = true; + rt.term.open(rt.el); + if (webglCount < MAX_WEBGL) { + try { + const gl = new WebglAddon.WebglAddon(); + gl.onContextLoss(() => { + gl.dispose(); + webglCount--; + }); + rt.term.loadAddon(gl); + webglCount++; + rt.webgl = gl; + } catch { + // DOM renderer it is. + } + } + // After the renderer, so images draw on top of it. + rt.term.loadAddon(new ImageAddon.ImageAddon({ iipSupport: true, sixelSupport: true, enableSizeReports: true })); + fit(rt); + spawn(rt); + } + + function fit(rt) { + if (!rt.opened || rt.disposed || !rt.el.isConnected) return; + if (rt.el.clientWidth < 10 || rt.el.clientHeight < 10) return; + try { + rt.fit.fit(); + } catch {} + } + + function fitSoon(rt) { + if (rt.fitQueued) return; + rt.fitQueued = true; + requestAnimationFrame(() => { + rt.fitQueued = false; + fit(rt); + }); + } + + function disposeRuntime(rt) { + rt.disposed = true; + if (rt.ptyId !== undefined) { + hq.kill(rt.ptyId); + byPty.delete(rt.ptyId); + } + rt.observer.disconnect(); + if (rt.webgl) webglCount--; + rt.term.dispose(); + rt.el.remove(); + runtimes.delete(rt.pane.id); + } + + // ---------- tabs and layout ---------- + + const tabOf = (paneId) => tabs.find((t) => L.find(t.root, paneId)); + const current = () => tabs[active]; + const focusedRt = () => current() && runtimes.get(current().focus); + const allSpecs = () => tabs.flatMap((t) => L.panes(t.root).map((p) => p.spec)); + + function addTab(root, focus) { + const view = document.createElement("div"); + view.className = "view"; + $("#views").appendChild(view); + const tab = { id: nextTab++, root, focus: focus || L.panes(root)[0].id, view }; + tabs.push(tab); + renderTab(tab); + return tab; + } + + function newTab(spec) { + const tab = addTab(L.pane(spec)); + activate(tabs.indexOf(tab)); + save(); + } + + /** Open a spec, or focus the pane already showing that host's session. */ + function openSpec(spec) { + const clean = L.cleanSpec(spec) || { kind: "shell" }; + if (clean.kind === "remote") { + for (const t of tabs) { + const p = L.panes(t.root).find((p) => p.spec.kind === "remote" && p.spec.host === clean.host && p.spec.session === clean.session); + if (p) { + activate(tabs.indexOf(t)); + return setFocus(p.id); + } + } + } + newTab(clean); + } + + function closeTab(i) { + const tab = tabs[i]; + if (!tab) return; + for (const p of L.panes(tab.root)) { + const rt = runtimes.get(p.id); + if (rt) disposeRuntime(rt); + } + tab.view.remove(); + tabs.splice(i, 1); + if (!tabs.length) { + save(true); + return window.close(); + } + activate(Math.min(i, tabs.length - 1)); + save(); + } + + function closePane(paneId) { + const tab = tabOf(paneId); + if (!tab) return; + const list = L.panes(tab.root); + const idx = list.findIndex((p) => p.id === paneId); + const rt = runtimes.get(paneId); + if (rt) disposeRuntime(rt); + tab.root = L.remove(tab.root, paneId); + if (!tab.root) return closeTab(tabs.indexOf(tab)); + if (tab.focus === paneId) { + const rest = L.panes(tab.root); + tab.focus = rest[Math.max(0, idx - 1)] ? rest[Math.max(0, idx - 1)].id : rest[0].id; + } + renderTab(tab); + if (tab === current()) focusTerm(); + renderBar(); + save(); + } + + function splitFocused(dir) { + const tab = current(); + const rt = focusedRt(); + if (!tab || !rt) return; + const added = L.pane(L.splitSpec(rt.spec, allSpecs())); + tab.root = L.split(tab.root, rt.pane.id, dir, added); + tab.focus = added.id; + renderTab(tab); + focusTerm(); + renderBar(); + save(); + } + + function moveFocus(dir) { + const tab = current(); + if (!tab) return; + const rects = {}; + for (const p of L.panes(tab.root)) { + const rt = runtimes.get(p.id); + if (rt) rects[p.id] = rt.el.getBoundingClientRect(); + } + const next = L.neighbor(rects, tab.focus, dir); + if (next) setFocus(next); + } + + function setFocus(paneId, focusTerminal = true) { + const tab = tabOf(paneId); + if (!tab) return; + if (tab.focus !== paneId) { + tab.focus = paneId; + save(); + } + for (const p of L.panes(tab.root)) { + const rt = runtimes.get(p.id); + if (rt) rt.el.classList.toggle("focused", p.id === paneId); + } + if (tab === current()) { + if (focusTerminal) focusTerm(); + renderBar(); + } + } + + function focusTerm() { + const rt = focusedRt(); + if (rt) requestAnimationFrame(() => rt.term.focus()); + } + + function activate(i) { + if (!tabs.length) return; + active = (i + tabs.length) % tabs.length; + tabs.forEach((t, j) => t.view.classList.toggle("inactive", j !== active)); + const tab = current(); + for (const p of L.panes(tab.root)) { + const rt = runtimes.get(p.id); + if (rt) fitSoon(rt); + } + setFocus(tab.focus); + renderBar(); + save(); + } + + /** (Re)build a tab's pane tree in its view; panes keep their terminals. */ + function renderTab(tab) { + const build = (node) => { + if (node.type === "pane") { + const rt = runtimes.get(node.id) || createRuntime(node); + rt.el.classList.toggle("focused", node.id === tab.focus); + return rt.el; + } + const box = document.createElement("div"); + box.className = `split ${node.dir}`; + const a = document.createElement("div"); + const b = document.createElement("div"); + a.className = b.className = "slot"; + const size = () => { + a.style.flex = `${node.ratio} 1 0`; + b.style.flex = `${1 - node.ratio} 1 0`; + }; + size(); + a.appendChild(build(node.a)); + b.appendChild(build(node.b)); + const div = document.createElement("div"); + div.className = "divider"; + div.addEventListener("mousedown", (e) => { + e.preventDefault(); + const r = box.getBoundingClientRect(); + div.classList.add("dragging"); + document.body.classList.add(`dragging-${node.dir}`); + const move = (ev) => { + const f = node.dir === "row" ? (ev.clientX - r.left) / r.width : (ev.clientY - r.top) / r.height; + node.ratio = Math.min(0.95, Math.max(0.05, f)); + size(); + }; + const up = () => { + window.removeEventListener("mousemove", move); + window.removeEventListener("mouseup", up); + div.classList.remove("dragging"); + document.body.classList.remove(`dragging-${node.dir}`); + save(); + focusTerm(); + }; + window.addEventListener("mousemove", move); + window.addEventListener("mouseup", up); + }); + box.append(a, div, b); + return box; + }; + tab.view.replaceChildren(build(tab.root)); + tab.view.classList.toggle("multi", L.panes(tab.root).length > 1); + for (const p of L.panes(tab.root)) { + const rt = runtimes.get(p.id); + if (rt) (rt.opened ? fitSoon(rt) : open(rt)); + } + } + + function tabLabel(tab) { + const rt = runtimes.get(tab.focus); + const spec = rt ? rt.spec : L.panes(tab.root)[0].spec; + const n = L.panes(tab.root).length; + const base = (rt && rt.title) || L.label(spec); + return n > 1 ? `${base} (${n})` : base; + } + + function renderBar() { + const bar = $("#tabs"); + bar.replaceChildren( + ...tabs.map((tab, i) => { + const el = document.createElement("div"); + el.className = `tab${i === active ? " active" : ""}`; + el.setAttribute("role", "tab"); + const rt = runtimes.get(tab.focus); + const icon = document.createElement("img"); + icon.className = "kind"; + icon.src = rt && rt.spec.kind === "remote" ? "icons/server.svg" : "icons/terminal.svg"; + icon.alt = ""; + const label = document.createElement("span"); + label.className = "label"; + label.textContent = tabLabel(tab); + el.title = label.textContent; + const close = document.createElement("button"); + close.className = "icon"; + close.title = "Close tab (Ctrl+Shift+W)"; + close.innerHTML = 'Close'; + close.addEventListener("click", (e) => { + e.stopPropagation(); + closeTab(tabs.indexOf(tab)); + }); + el.addEventListener("mousedown", (e) => { + if (e.button === 1) { + e.preventDefault(); + closeTab(tabs.indexOf(tab)); + } else if (e.button === 0 && e.target !== close && !close.contains(e.target)) activate(tabs.indexOf(tab)); + }); + el.append(icon, label, close); + return el; + }), + ); + const rt = focusedRt(); + const t = current(); + document.title = t ? `${(rt && rt.title) || L.label(rt ? rt.spec : undefined)} — hqterm` : "hqterm"; + } + + let saveTimer; + function save(now) { + clearTimeout(saveTimer); + const run = () => hq.saveLayout(L.serialize(tabs.map((t) => ({ root: t.root, focus: t.focus })), active)); + if (now) run(); + else saveTimer = setTimeout(run, 300); + } + + // ---------- menu ---------- + + const menu = $("#menu"); + function closeMenu() { + menu.hidden = true; + } + async function showMenu() { + if (!menu.hidden) return closeMenu(); + const item = (icon, text, key, fn) => { + const el = document.createElement("div"); + el.className = "item"; + el.setAttribute("role", "menuitem"); + el.innerHTML = `${key ? `${key}` : ""}`; + el.querySelector("span").textContent = text; + el.addEventListener("click", () => { + closeMenu(); + fn(); + }); + return el; + }; + const sep = () => Object.assign(document.createElement("div"), { className: "sep" }); + const items = [ + item("terminal", "New shell", "Ctrl+Shift+T", () => newTab({ kind: "shell" })), + item("terminal", "hqterm sessions", "", () => newTab({ kind: "hqterm" })), + ]; + if (focusedRt()) { + items.push(sep(), item("add", "Split right", "Ctrl+Shift+D", () => splitFocused("row")), item("add", "Split down", "Ctrl+Shift+E", () => splitFocused("column"))); + } + let hosts = []; + try { + hosts = await hq.hosts(); + } catch {} + if (hosts.length) { + const head = Object.assign(document.createElement("div"), { className: "head", textContent: "Connect host (hqsh, session main)" }); + items.push(sep(), head, ...hosts.map((h) => item("server", h, "", () => openSpec({ kind: "remote", host: h, session: "main" })))); + } + menu.replaceChildren(...items); + const r = $("#new").getBoundingClientRect(); + menu.style.left = `${Math.min(r.left, window.innerWidth - 240)}px`; + menu.style.top = `${r.bottom + 2}px`; + menu.hidden = false; + } + $("#new").addEventListener("click", (e) => { + e.stopPropagation(); + showMenu(); + }); + window.addEventListener("mousedown", (e) => { + if (!menu.hidden && !menu.contains(e.target) && e.target.closest("#new") === null) closeMenu(); + }); + + // ---------- keys ---------- + + function zoom(delta) { + fontSize = delta === 0 ? config.fontSize : Math.min(72, Math.max(6, fontSize + delta)); + for (const rt of runtimes.values()) { + rt.term.options.fontSize = fontSize; + fitSoon(rt); + } + } + + async function paste() { + const rt = focusedRt(); + const text = await hq.readClipboard(); + if (rt && text && rt.ptyId !== undefined) rt.term.paste(text); + } + + window.addEventListener( + "keydown", + (e) => { + if (e.key === "Escape" && !menu.hidden) return closeMenu(); + let handled = true; + if (e.ctrlKey && e.key === "Tab") activate(active + (e.shiftKey ? -1 : 1)); + else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.key === "PageDown") activate(active + 1); + else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.key === "PageUp") activate(active - 1); + else if (e.ctrlKey && e.shiftKey && !e.altKey) { + switch (e.code) { + case "KeyT": + newTab({ kind: "shell" }); + break; + case "KeyW": + closeTab(active); + break; + case "KeyC": { + const rt = focusedRt(); + const sel = rt && rt.term.getSelection(); + if (sel) hq.writeClipboard(sel); + break; + } + case "KeyV": + paste(); + break; + case "KeyD": + splitFocused("row"); + break; + case "KeyE": + splitFocused("column"); + break; + case "KeyX": { + const rt = focusedRt(); + if (rt) closePane(rt.pane.id); + break; + } + case "ArrowLeft": + moveFocus("left"); + break; + case "ArrowRight": + moveFocus("right"); + break; + case "ArrowUp": + moveFocus("up"); + break; + case "ArrowDown": + moveFocus("down"); + break; + case "Equal": + zoom(1); // Ctrl+Shift+= is Ctrl++ on US layouts + break; + default: + handled = false; + } + } else if (e.ctrlKey && !e.altKey && (e.key === "=" || e.key === "+")) zoom(1); + else if (e.ctrlKey && !e.altKey && !e.shiftKey && e.key === "-") zoom(-1); + else if (e.ctrlKey && !e.altKey && !e.shiftKey && e.key === "0") zoom(0); + else handled = false; + if (handled) { + e.preventDefault(); + e.stopPropagation(); + } + }, + true, + ); + + window.addEventListener("resize", () => { + for (const rt of runtimes.values()) fitSoon(rt); + }); + + // ---------- start ---------- + + const restored = L.restore(init.layout); + for (const t of restored.tabs) addTab(t.root, t.focus); + if (tabs.length) activate(restored.active); + if (init.open) openSpec(init.open); + if (!tabs.length) newTab({ kind: "shell" }); + hq.onOpen((spec) => openSpec(spec)); +})().catch((err) => { + document.body.textContent = `hqterm failed to start: ${err && err.stack ? err.stack : err}`; +}); diff --git a/desktop/src/icon.png b/desktop/src/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..126853897ab73a5a47f93cefd7e9baa169e7b437 GIT binary patch literal 56479 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 z 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 literal 0 HcmV?d00001 diff --git a/desktop/src/icons/add.svg b/desktop/src/icons/add.svg new file mode 100644 index 0000000..a31cd80 --- /dev/null +++ b/desktop/src/icons/add.svg @@ -0,0 +1 @@ +Add diff --git a/desktop/src/icons/server.svg b/desktop/src/icons/server.svg new file mode 100644 index 0000000..c2853df --- /dev/null +++ b/desktop/src/icons/server.svg @@ -0,0 +1 @@ +Server diff --git a/desktop/src/icons/terminal.svg b/desktop/src/icons/terminal.svg new file mode 100644 index 0000000..7776fcf --- /dev/null +++ b/desktop/src/icons/terminal.svg @@ -0,0 +1 @@ +Terminal diff --git a/desktop/src/icons/x.svg b/desktop/src/icons/x.svg new file mode 100644 index 0000000..31e23fc --- /dev/null +++ b/desktop/src/icons/x.svg @@ -0,0 +1 @@ +X diff --git a/desktop/src/index.html b/desktop/src/index.html new file mode 100644 index 0000000..7489fce --- /dev/null +++ b/desktop/src/index.html @@ -0,0 +1,31 @@ + + + + + + hqterm + + + + +
+
+ +
+ +
+ + + + + + + + + + diff --git a/desktop/src/layout.js b/desktop/src/layout.js new file mode 100644 index 0000000..e9e43dd --- /dev/null +++ b/desktop/src/layout.js @@ -0,0 +1,177 @@ +/* + * Pane trees for hqterm desktop, without DOM or PTYs so it can be tested. + * + * A tab's layout is a binary tree: + * { type: "pane", id, spec } spec: { kind: "shell" | "remote" | "hqterm", host?, session? } + * { type: "split", dir: "row" | "column", ratio, a, b } row = side by side, column = stacked + * + * Saved layouts (~/.config/hqterm/desktop-layout.json) keep the tree without + * ids: { version: 1, active, tabs: [{ root, focus }] }. + */ +(function (root, factory) { + const api = factory(); + if (typeof module === "object" && module.exports) module.exports = api; + else root.HqLayout = api; +})(typeof self !== "undefined" ? self : this, function () { + const HOST_RE = /^[A-Za-z0-9._@:\-\[\]]+$/; + const SESSION_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/; + + let nextId = 1; + const newId = () => `p${nextId++}`; + + function pane(spec) { + return { type: "pane", id: newId(), spec: cleanSpec(spec) || { kind: "shell" } }; + } + + /** A spec we are willing to run, or undefined. */ + function cleanSpec(spec) { + if (!spec || typeof spec !== "object") return undefined; + if (spec.kind === "shell" || spec.kind === "hqterm") return { kind: spec.kind }; + if (spec.kind === "remote" && typeof spec.host === "string" && HOST_RE.test(spec.host) && !spec.host.startsWith("-")) { + const session = typeof spec.session === "string" && SESSION_RE.test(spec.session) ? spec.session : "main"; + return { kind: "remote", host: spec.host, session }; + } + return undefined; + } + + function panes(node, out = []) { + if (!node) return out; + if (node.type === "pane") out.push(node); + else { + panes(node.a, out); + panes(node.b, out); + } + return out; + } + + function find(node, id) { + return panes(node).find((p) => p.id === id); + } + + /** Replace pane `id` with a split of it and `added`. Returns the new root. */ + function split(root, id, dir, added) { + const walk = (node) => { + if (node.type === "pane") return node.id === id ? { type: "split", dir, ratio: 0.5, a: node, b: added } : node; + return { ...node, a: walk(node.a), b: walk(node.b) }; + }; + return walk(root); + } + + /** Remove pane `id`; its sibling takes the parent's place. Returns the new root (null when empty). */ + function remove(root, id) { + const walk = (node) => { + if (node.type === "pane") return node.id === id ? null : node; + const a = walk(node.a); + const b = walk(node.b); + if (!a) return b; + if (!b) return a; + return a === node.a && b === node.b ? node : { ...node, a, b }; + }; + return root ? walk(root) : null; + } + + /** Session name for a new pane on `host`: main, main-2, main-3 … not used in `taken`. */ + function freeSession(host, taken) { + const used = new Set(taken.filter((s) => s.kind === "remote" && s.host === host).map((s) => s.session)); + if (!used.has("main")) return "main"; + for (let n = 2; ; n++) if (!used.has(`main-${n}`)) return `main-${n}`; + } + + /** What a split of `spec` opens: remote panes get a fresh session on the same host, the rest a shell. */ + function splitSpec(spec, taken) { + if (spec && spec.kind === "remote") return { kind: "remote", host: spec.host, session: freeSession(spec.host, taken) }; + return { kind: "shell" }; + } + + function clampRatio(r) { + return typeof r === "number" && isFinite(r) ? Math.min(0.95, Math.max(0.05, r)) : 0.5; + } + + function serializeNode(node) { + if (node.type === "pane") return { type: "pane", spec: node.spec }; + return { type: "split", dir: node.dir, ratio: clampRatio(node.ratio), a: serializeNode(node.a), b: serializeNode(node.b) }; + } + + /** tabs: [{ root, focus (pane id) }] */ + function serialize(tabs, active) { + return { + version: 1, + active: Math.max(0, Math.min(active | 0, tabs.length - 1)), + tabs: tabs.map((t) => ({ + root: serializeNode(t.root), + focus: Math.max(0, panes(t.root).findIndex((p) => p.id === t.focus)), + })), + }; + } + + function restoreNode(data, depth) { + if (!data || typeof data !== "object" || depth > 32) return null; + if (data.type === "pane") return pane(cleanSpec(data.spec) || { kind: "shell" }); + if (data.type === "split" && (data.dir === "row" || data.dir === "column")) { + const a = restoreNode(data.a, depth + 1); + const b = restoreNode(data.b, depth + 1); + if (!a) return b; + if (!b) return a; + return { type: "split", dir: data.dir, ratio: clampRatio(data.ratio), a, b }; + } + return null; + } + + /** From saved JSON (text or object) to { tabs: [{ root, focus }], active }; empty tabs when unusable. */ + function restore(saved) { + let data = saved; + if (typeof saved === "string") { + try { + data = JSON.parse(saved); + } catch { + data = null; + } + } + const tabs = []; + if (data && data.version === 1 && Array.isArray(data.tabs)) { + for (const t of data.tabs.slice(0, 64)) { + const root = restoreNode(t && t.root, 0); + if (!root) continue; + const list = panes(root); + const focus = (list[(t.focus | 0)] || list[0]).id; + tabs.push({ root, focus }); + } + } + const active = data && tabs.length ? Math.max(0, Math.min(data.active | 0, tabs.length - 1)) : 0; + return { tabs, active }; + } + + /** + * The pane next to `from` in direction dir ("left" | "right" | "up" | "down"), + * given rects { id: {left, top, right, bottom} }. Picks the closest pane that + * overlaps on the other axis, else the closest by centre. + */ + function neighbor(rects, from, dir) { + const r = rects[from]; + if (!r) return undefined; + const horiz = dir === "left" || dir === "right"; + let best; + let bestScore = Infinity; + for (const [id, o] of Object.entries(rects)) { + if (id === from) continue; + const ahead = + dir === "left" ? o.right <= r.left + 1 : dir === "right" ? o.left >= r.right - 1 : dir === "up" ? o.bottom <= r.top + 1 : o.top >= r.bottom - 1; + if (!ahead) continue; + const gap = dir === "left" ? r.left - o.right : dir === "right" ? o.left - r.right : dir === "up" ? r.top - o.bottom : o.top - r.bottom; + const overlap = horiz ? Math.min(r.bottom, o.bottom) - Math.max(r.top, o.top) : Math.min(r.right, o.right) - Math.max(r.left, o.left); + const cross = horiz ? Math.abs((o.top + o.bottom) / 2 - (r.top + r.bottom) / 2) : Math.abs((o.left + o.right) / 2 - (r.left + r.right) / 2); + const score = (overlap > 0 ? 0 : 1e6) + gap * 1000 + cross; + if (score < bestScore) (bestScore = score), (best = id); + } + return best; + } + + function label(spec) { + if (!spec) return "shell"; + if (spec.kind === "remote") return spec.session && spec.session !== "main" ? `${spec.host}:${spec.session}` : spec.host; + if (spec.kind === "hqterm") return "hqterm"; + return "shell"; + } + + return { pane, cleanSpec, panes, find, split, remove, freeSession, splitSpec, serialize, restore, neighbor, label }; +}); diff --git a/desktop/src/style.css b/desktop/src/style.css new file mode 100644 index 0000000..7e3665d --- /dev/null +++ b/desktop/src/style.css @@ -0,0 +1,248 @@ +@font-face { + font-family: "OpenEmoji"; + src: url("../fonts/OpenEmoji-CBDT.ttf") format("truetype"); + font-display: block; +} + +:root { + --bg: #0f1115; + --bar: #171a21; + --tab: #1e222b; + --tab-active: #2a2f3a; + --fg: #d8dee9; + --dim: #8a93a6; + --accent: #5ea1ff; +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + height: 100%; + background: var(--bg); + color: var(--fg); + font: 13px system-ui, sans-serif; + overflow: hidden; + user-select: none; +} + +body { + display: flex; + flex-direction: column; +} + +#bar { + display: flex; + align-items: stretch; + height: 32px; + background: var(--bar); + flex: none; +} + +#tabs { + display: flex; + overflow-x: auto; + min-width: 0; + scrollbar-width: none; +} + +.tab { + display: flex; + align-items: center; + gap: 6px; + padding: 0 6px 0 10px; + max-width: 220px; + min-width: 90px; + background: var(--tab); + border-right: 1px solid var(--bar); + color: var(--dim); + cursor: default; +} + +.tab.active { + background: var(--tab-active); + color: var(--fg); + box-shadow: inset 0 -2px var(--accent); +} + +.tab img.kind { + width: 14px; + height: 14px; + filter: invert(0.8); + flex: none; +} + +.tab .label { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +button.icon { + background: none; + border: 0; + padding: 0 8px; + cursor: pointer; + display: flex; + align-items: center; +} + +button.icon img { + width: 14px; + height: 14px; + filter: invert(0.7); +} + +button.icon:hover img { + filter: invert(1); +} + +.tab button.icon { + padding: 2px; + border-radius: 3px; +} + +.tab button.icon:hover { + background: #3a404d; +} + +#menu { + position: fixed; + z-index: 10; + min-width: 220px; + max-height: 70vh; + overflow-y: auto; + background: var(--tab); + border: 1px solid #333a47; + border-radius: 6px; + padding: 4px 0; + box-shadow: 0 8px 24px #0008; +} + +#menu .item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + cursor: pointer; +} + +#menu .item:hover { + background: var(--tab-active); +} + +#menu .item img { + width: 14px; + height: 14px; + filter: invert(0.8); +} + +#menu .item .key { + margin-left: auto; + color: var(--dim); + font-size: 11px; +} + +#menu .sep { + height: 1px; + margin: 4px 0; + background: #333a47; +} + +#menu .head { + padding: 4px 12px; + color: var(--dim); + font-size: 11px; + text-transform: uppercase; +} + +#views { + flex: 1; + position: relative; + min-height: 0; +} + +.view { + position: absolute; + inset: 0; + display: flex; +} + +/* Hidden tabs stay laid out, so their terminals keep a real size. */ +.view.inactive { + visibility: hidden; + pointer-events: none; +} + +.split { + display: flex; + flex: 1; + min-width: 0; + min-height: 0; +} + +.split.row { + flex-direction: row; +} + +.split.column { + flex-direction: column; +} + +.slot { + display: flex; + min-width: 0; + min-height: 0; +} + +.divider { + flex: none; + background: #2a2f3a; +} + +.split.row > .divider { + width: 4px; + cursor: col-resize; +} + +.split.column > .divider { + height: 4px; + cursor: row-resize; +} + +.divider:hover, +.divider.dragging { + background: var(--accent); +} + +.pane { + flex: 1; + min-width: 0; + min-height: 0; + padding: 4px 0 0 6px; + position: relative; + outline: 1px solid transparent; + outline-offset: -1px; +} + +.view.multi .pane.focused { + outline-color: #5ea1ff66; +} + +.pane .xterm { + height: 100%; +} + +body.dragging-row, +body.dragging-row * { + cursor: col-resize !important; +} + +body.dragging-column, +body.dragging-column * { + cursor: row-resize !important; +} diff --git a/desktop/test/layout-tests.js b/desktop/test/layout-tests.js new file mode 100644 index 0000000..0ab58e8 --- /dev/null +++ b/desktop/test/layout-tests.js @@ -0,0 +1,92 @@ +"use strict"; +const test = require("node:test"); +const assert = require("node:assert/strict"); +const L = require("../src/layout.js"); + +const shell = { kind: "shell" }; +const remote = (host, session = "main") => ({ kind: "remote", host, session }); + +test("split and remove keep pane identity and collapse to the sibling", () => { + const a = L.pane(shell); + const b = L.pane(remote("dev2")); + let root = L.split(a, a.id, "row", b); + assert.equal(root.type, "split"); + assert.equal(root.dir, "row"); + const c = L.pane(shell); + root = L.split(root, b.id, "column", c); + assert.deepEqual(L.panes(root).map((p) => p.id), [a.id, b.id, c.id]); + assert.equal(L.find(root, b.id), b); + root = L.remove(root, b.id); + assert.deepEqual(L.panes(root).map((p) => p.id), [a.id, c.id]); + assert.equal(root.dir, "row"); + root = L.remove(root, a.id); + assert.equal(root, c); + assert.equal(L.remove(root, c.id), null); +}); + +test("a split of a remote pane opens a fresh session on that host", () => { + const taken = [remote("dev2"), remote("dev2", "main-2"), remote("box")]; + assert.deepEqual(L.splitSpec(remote("dev2"), taken), remote("dev2", "main-3")); + assert.deepEqual(L.splitSpec(remote("new"), taken), remote("new", "main")); + assert.deepEqual(L.splitSpec(shell, taken), shell); + assert.deepEqual(L.splitSpec({ kind: "hqterm" }, taken), shell); +}); + +test("serialize then restore gives the same tree, specs and focus", () => { + const a = L.pane(remote("dev2", "work")); + const b = L.pane(shell); + const root = { ...L.split(a, a.id, "column", b), ratio: 0.3 }; + const saved = JSON.parse(JSON.stringify(L.serialize([{ root, focus: b.id }, { root: L.pane({ kind: "hqterm" }), focus: "x" }], 1))); + assert.equal(saved.version, 1); + assert.equal(saved.active, 1); + assert.equal(JSON.stringify(saved).includes('"id"'), false); + const { tabs, active } = L.restore(JSON.stringify(saved)); + assert.equal(active, 1); + assert.equal(tabs.length, 2); + const [t0] = tabs; + assert.equal(t0.root.dir, "column"); + assert.equal(t0.root.ratio, 0.3); + assert.deepEqual(L.panes(t0.root).map((p) => p.spec), [remote("dev2", "work"), shell]); + assert.equal(t0.focus, L.panes(t0.root)[1].id); + assert.deepEqual(L.panes(tabs[1].root)[0].spec, { kind: "hqterm" }); +}); + +test("restore rejects junk and unsafe specs", () => { + assert.deepEqual(L.restore("not json"), { tabs: [], active: 0 }); + assert.deepEqual(L.restore({ version: 2, tabs: [] }), { tabs: [], active: 0 }); + const { tabs } = L.restore({ + version: 1, + active: 99, + tabs: [ + { root: { type: "pane", spec: { kind: "remote", host: "-oProxyCommand=evil" } } }, + { root: { type: "pane", spec: { kind: "remote", host: "dev2", session: "$(rm -rf ~)" } } }, + { root: { type: "split", dir: "diagonal", a: {}, b: {} } }, + { root: { type: "split", dir: "row", ratio: 7, a: { type: "pane", spec: { kind: "exec", cmd: "x" } }, b: null } }, + ], + }); + assert.equal(tabs.length, 3); + assert.deepEqual(L.panes(tabs[0].root)[0].spec, shell); + assert.deepEqual(L.panes(tabs[1].root)[0].spec, remote("dev2", "main")); + assert.deepEqual(L.panes(tabs[2].root)[0].spec, shell); +}); + +test("neighbor picks the adjacent pane in each direction", () => { + // left | right-top / right-bottom + const rects = { + l: { left: 0, top: 0, right: 100, bottom: 100 }, + rt: { left: 100, top: 0, right: 200, bottom: 50 }, + rb: { left: 100, top: 50, right: 200, bottom: 100 }, + }; + assert.equal(L.neighbor(rects, "l", "right"), "rt"); + assert.equal(L.neighbor(rects, "rb", "left"), "l"); + assert.equal(L.neighbor(rects, "rt", "down"), "rb"); + assert.equal(L.neighbor(rects, "rb", "up"), "rt"); + assert.equal(L.neighbor(rects, "l", "left"), undefined); + assert.equal(L.neighbor(rects, "rt", "up"), undefined); +}); + +test("labels", () => { + assert.equal(L.label(remote("dev2")), "dev2"); + assert.equal(L.label(remote("dev2", "w")), "dev2:w"); + assert.equal(L.label(shell), "shell"); +}); diff --git a/install.sh b/install.sh index d5ceb20..9351719 100755 --- a/install.sh +++ b/install.sh @@ -1,29 +1,53 @@ #!/bin/sh # hqterm installer: https://hqterm.sh # -# curl -fsSL https://hqterm.sh/install | sh # hqterm + hqsh -# curl -fsSL https://hqterm.sh/install | sh -s -- --server # hqsh only (remote hosts) +# curl -fsSL https://hqterm.sh/install | sh # hqterm + hqsh (+ desktop app on a Linux desktop) +# curl -fsSL https://hqterm.sh/install | sh -s -- --desktop # also the desktop app, display or not +# curl -fsSL https://hqterm.sh/install | sh -s -- --server # hqsh only (remote hosts) # # Installs prebuilt binaries from the latest GitHub releases of # profullstack/hqterm and profullstack/hqsh into ~/.local/bin (or $HQTERM_BIN). -# No sudo, safe to run again. Pin versions with HQTERM_VERSION / HQSH_VERSION -# (e.g. v0.1.0). +# The desktop app (Linux x86_64/arm64 AppImage) goes to +# ~/.local/opt/hqterm-desktop with an app-launcher entry. No sudo, safe to run +# again. Pin versions with HQTERM_VERSION / HQSH_VERSION (e.g. v0.2.0). set -eu BIN="${HQTERM_BIN:-$HOME/.local/bin}" SERVER=0 +DESKTOP=auto for arg in "$@"; do case "$arg" in --server) SERVER=1 ;; + --desktop) DESKTOP=1 ;; + --no-desktop) DESKTOP=0 ;; --bin=*) BIN="${arg#--bin=}" ;; -h|--help) - echo "usage: install.sh [--server] [--bin=DIR]" - echo " --server install only hqsh (for hosts you connect to)" - echo " --bin=DIR install into DIR (default ~/.local/bin, or \$HQTERM_BIN)" + echo "usage: install.sh [--server] [--desktop|--no-desktop] [--bin=DIR]" + echo " --server install only hqsh (for hosts you connect to)" + echo " --desktop also install the hqterm desktop app (Linux AppImage)" + echo " --no-desktop never install the desktop app" + echo " (default: install it on Linux when a display is present)" + echo " --bin=DIR install into DIR (default ~/.local/bin, or \$HQTERM_BIN)" exit 0 ;; *) echo "hqterm install: unknown option $arg" >&2; exit 2 ;; esac done +if [ "$SERVER" = 1 ]; then + if [ "$DESKTOP" = 1 ]; then + echo "hqterm install: --server and --desktop do not mix" >&2 + exit 2 + fi + DESKTOP=0 +fi +DESKTOP_AUTO=0 +if [ "$DESKTOP" = auto ]; then + DESKTOP_AUTO=1 + if [ "$(uname -s)" = Linux ] && [ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ]; then DESKTOP=1; else DESKTOP=0; fi +fi +if [ -n "${HQTERM_INSTALL_PARSE_ONLY:-}" ]; then + echo "bin=$BIN server=$SERVER desktop=$DESKTOP auto=$DESKTOP_AUTO" + exit 0 +fi say() { printf '%s\n' "hqterm install: $*"; } die() { printf '%s\n' "hqterm install: error: $*" >&2; exit 1; } @@ -59,35 +83,45 @@ TMP="$(mktemp -d 2>/dev/null || mktemp -d -t hqterm)" trap 'rm -rf "$TMP"' EXIT INT TERM mkdir -p "$BIN" || die "cannot create $BIN" -# install_one REPO NAME VERSION: download NAME-OS-ARCH, verify, install as BIN/NAME. -# Returns 1 (without exiting) when the release or asset does not exist. -install_one() { - repo="$1"; name="$2"; version="$3" - asset="$name-$OS-$ARCH" - if [ -n "$version" ]; then - base="https://github.com/profullstack/$repo/releases/download/$version" +# release_base REPO VERSION: the download URL prefix of a release. +release_base() { + if [ -n "$2" ]; then + echo "https://github.com/profullstack/$1/releases/download/$2" else - base="https://github.com/profullstack/$repo/releases/latest/download" + echo "https://github.com/profullstack/$1/releases/latest/download" fi - say "downloading $asset from github.com/profullstack/$repo${version:+ ($version)}" - if ! fetch "$base/$asset" "$TMP/$asset"; then - return 1 - fi - if fetch "$base/SHA256SUMS" "$TMP/SHA256SUMS.$name" 2>/dev/null; then - want="$(grep " \*\{0,1\}$asset\$" "$TMP/SHA256SUMS.$name" | cut -d' ' -f1 | head -n1)" - got="$(sha256 "$TMP/$asset")" +} + +# verify BASE ASSET REPO: check $TMP/ASSET against the release's SHA256SUMS. +verify() { + if fetch "$1/SHA256SUMS" "$TMP/SHA256SUMS.$2" 2>/dev/null; then + want="$(grep " \*\{0,1\}$2\$" "$TMP/SHA256SUMS.$2" | cut -d' ' -f1 | head -n1)" + got="$(sha256 "$TMP/$2")" if [ -z "$want" ]; then - say "warning: $asset is not listed in SHA256SUMS; not verified" + say "warning: $2 is not listed in SHA256SUMS; not verified" elif [ -z "$got" ]; then - say "warning: no sha256sum/shasum here; $asset not verified" + say "warning: no sha256sum/shasum here; $2 not verified" elif [ "$want" != "$got" ]; then - die "checksum mismatch for $asset (expected $want, got $got)" + die "checksum mismatch for $2 (expected $want, got $got)" else - say "verified $asset (sha256 $got)" + say "verified $2 (sha256 $got)" fi else - say "warning: no SHA256SUMS in the $repo release; $asset not verified" + say "warning: no SHA256SUMS in the $3 release; $2 not verified" fi +} + +# install_one REPO NAME VERSION: download NAME-OS-ARCH, verify, install as BIN/NAME. +# Returns 1 (without exiting) when the release or asset does not exist. +install_one() { + repo="$1"; name="$2"; version="$3" + asset="$name-$OS-$ARCH" + base="$(release_base "$repo" "$version")" + say "downloading $asset from github.com/profullstack/$repo${version:+ ($version)}" + if ! fetch "$base/$asset" "$TMP/$asset"; then + return 1 + fi + verify "$base" "$asset" "$repo" chmod 755 "$TMP/$asset" # macOS on Apple silicon refuses unsigned binaries: ad-hoc sign if needed. if [ "$OS" = darwin ] && ! "$TMP/$asset" --version >/dev/null 2>&1 && command -v codesign >/dev/null 2>&1; then @@ -100,6 +134,63 @@ install_one() { return 0 } +# install_desktop: the AppImage, an icon and an app-launcher entry (Linux only). +install_desktop() { + if [ "$OS" != linux ]; then + say "the desktop app is Linux-only for now; skipping it" + return 1 + fi + case "$ARCH" in + amd64) aarch=x86_64 ;; + *) aarch=aarch64 ;; + esac + asset="hqterm-desktop-linux-$aarch.AppImage" + base="$(release_base hqterm "${HQTERM_VERSION:-}")" + say "downloading $asset" + fetch "$base/$asset" "$TMP/$asset" || return 1 + verify "$base" "$asset" hqterm + dir="$HOME/.local/opt/hqterm-desktop" + mkdir -p "$dir" + chmod 755 "$TMP/$asset" + mv -f "$TMP/$asset" "$dir/hqterm-desktop.AppImage.new.$$" + mv -f "$dir/hqterm-desktop.AppImage.new.$$" "$dir/hqterm-desktop.AppImage" + say "installed $dir/hqterm-desktop.AppImage" + + icons="$HOME/.local/share/icons" + apps="$HOME/.local/share/applications" + mkdir -p "$icons" "$apps" + if fetch "$base/hqterm-desktop.png" "$TMP/hqterm.png" 2>/dev/null \ + || fetch "https://raw.githubusercontent.com/profullstack/openemoji/main/png/256/1f5a5-fe0f.png" "$TMP/hqterm.png" 2>/dev/null; then + mv -f "$TMP/hqterm.png" "$icons/hqterm.png" + fi + # AppImages mount through FUSE; without it they can unpack and run instead. + extra="" + if ! command -v fusermount >/dev/null 2>&1 && ! command -v fusermount3 >/dev/null 2>&1; then + extra=" --appimage-extract-and-run" + say "note: no FUSE (fusermount) here, so the launcher uses --appimage-extract-and-run" + fi + cat > "$apps/hqterm.desktop" </dev/null 2>&1; then update-desktop-database "$apps" >/dev/null 2>&1 || true; fi + for k in kbuildsycoca6 kbuildsycoca5; do + if command -v "$k" >/dev/null 2>&1; then "$k" >/dev/null 2>&1 || true; break; fi + done + say "added app launcher entry $apps/hqterm.desktop" + return 0 +} + STATUS=0 if [ "$SERVER" = 0 ]; then install_one hqterm hqterm "${HQTERM_VERSION:-}" || die "could not download hqterm for $OS-$ARCH (no release asset yet?)" @@ -112,6 +203,17 @@ if ! install_one hqsh hqsh "${HQSH_VERSION:-}"; then STATUS=0 fi +DESKTOP_OK=0 +if [ "$DESKTOP" = 1 ]; then + if install_desktop; then + DESKTOP_OK=1 + elif [ "$DESKTOP_AUTO" = 1 ] || [ "$OS" != linux ]; then + say "warning: the desktop app was not installed; hqterm and hqsh are" + else + die "could not download the desktop app for $OS-$ARCH (no release asset yet?)" + fi +fi + case ":$PATH:" in *":$BIN:"*) ;; *) @@ -125,5 +227,6 @@ if [ "$SERVER" = 1 ]; then say "done: hqsh is ready on this host" else say "done. Try: hqterm doctor then: hqterm" + if [ "$DESKTOP_OK" = 1 ]; then say "desktop app: run hqterm desktop or pick hqterm in your app launcher"; fi fi exit "$STATUS" diff --git a/package.json b/package.json index 91a29aa..e477ab5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hqterm", - "version": "0.1.0", + "version": "0.2.0", "description": "Terminal session manager: persistent hqsh sessions with images and HD emoji, built on hqtui.", "license": "MIT", "private": true, diff --git a/site/index.html b/site/index.html index 4623436..73c4572 100644 --- a/site/index.html +++ b/site/index.html @@ -79,6 +79,20 @@

Just SSH

+

hqterm desktop

+

Our own terminal window for Linux (KDE, GNOME, anything with a display): tabs, split panes that come back where you left them, and every hqtui/qc image and HD emoji drawn in full, even where Konsole cannot.

+
curl -fsSL https://hqterm.sh/install | sh -s -- --desktop
+

Then run hqterm desktop (or hqterm desktop --host dev2 --session main), or pick hqterm in your app launcher. Linux x86_64 and arm64 AppImage; the plain install adds it automatically on a Linux desktop. Direct download: hqterm-desktop-linux-x86_64.AppImage.

+ + + + + + + + +
Ctrl+Shift+T / WNew tab / close tab (Ctrl+Tab switches)
Ctrl+Shift+D / ESplit right / split down (a split of a remote pane opens a new hqsh session on that host)
Ctrl+Shift+ArrowsMove between panes (or one click)
Ctrl+Shift+XClose the pane
Ctrl+Shift+C / VCopy / paste
Ctrl+= / Ctrl+-Zoom
+

Quick start

hqterm                      # hosts from ~/.ssh/config, their sessions, one click to attach
 hqterm connect dev2 main     # straight into session "main" on dev2
diff --git a/src/desktop.ts b/src/desktop.ts
new file mode 100644
index 0000000..53b4f5f
--- /dev/null
+++ b/src/desktop.ts
@@ -0,0 +1,117 @@
+/**
+ * `hqterm desktop`: find the hqterm desktop app (an Electron terminal that
+ * shows hqtui/qc images and HD emoji) and launch it, detached.
+ */
+import { spawn } from "node:child_process";
+import { existsSync } from "node:fs";
+import { homedir } from "node:os";
+import { join } from "node:path";
+import { validHostName } from "./hosts.ts";
+import { INSTALL_URL } from "./run.ts";
+
+export const DESKTOP_INSTALL = `curl -fsSL ${INSTALL_URL} | sh -s -- --desktop`;
+
+export interface DesktopOpts {
+  host?: string;
+  session?: string;
+}
+
+export type ParsedDesktopArgs = { ok: true; opts: DesktopOpts } | { ok: false; error: string };
+
+export const validSessionName = (s: string) => /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(s);
+
+/** `[--host H] [--session S]`, also `--host=H`. A session without a host is an error. */
+export function parseDesktopArgs(args: string[]): ParsedDesktopArgs {
+  const opts: DesktopOpts = {};
+  for (let i = 0; i < args.length; i++) {
+    const a = args[i]!;
+    const m = /^--(host|session)(?:=(.*))?$/.exec(a);
+    if (!m) return { ok: false, error: `unknown option ${a}` };
+    const value = m[2] ?? args[++i];
+    if (!value) return { ok: false, error: `--${m[1]} needs a value` };
+    if (m[1] === "host") {
+      if (!validHostName(value)) return { ok: false, error: `bad host name ${value}` };
+      opts.host = value;
+    } else {
+      if (!validSessionName(value)) return { ok: false, error: `bad session name ${value}` };
+      opts.session = value;
+    }
+  }
+  if (opts.session && !opts.host) return { ok: false, error: "--session needs --host" };
+  return { ok: true, opts };
+}
+
+/** Where the app may live, in the order we look. */
+export function desktopCandidates(env: NodeJS.ProcessEnv = process.env, platform = process.platform, home = homedir()): string[] {
+  const out: string[] = [];
+  if (env.HQTERM_DESKTOP) out.push(env.HQTERM_DESKTOP);
+  if (platform === "darwin") {
+    out.push("/Applications/hqterm.app", join(home, "Applications", "hqterm.app"));
+  } else {
+    const opt = join(home, ".local", "opt", "hqterm-desktop");
+    out.push(join(opt, "hqterm-desktop.AppImage"), join(opt, "hqterm-desktop"));
+  }
+  return out;
+}
+
+export function findDesktop(
+  env: NodeJS.ProcessEnv = process.env,
+  platform = process.platform,
+  home = homedir(),
+  exists: (p: string) => boolean = existsSync,
+): string | undefined {
+  return desktopCandidates(env, platform, home).find((p) => exists(p));
+}
+
+/** The argv that launches the app at `path`. */
+export function desktopLaunch(
+  path: string,
+  opts: DesktopOpts,
+  platform = process.platform,
+  hasFuse = true,
+): string[] {
+  const appArgs: string[] = [];
+  if (opts.host) appArgs.push("--host", opts.host);
+  if (opts.session) appArgs.push("--session", opts.session);
+  if (platform === "darwin" && path.endsWith(".app")) return ["open", "-n", "-a", path, "--args", ...appArgs];
+  const pre: string[] = [];
+  // AppImages mount with FUSE; without it they can still unpack and run.
+  if (path.endsWith(".AppImage") && !hasFuse) pre.push("--appimage-extract-and-run");
+  // Chromium's setuid sandbox is not set up inside an AppImage.
+  return [path, ...pre, ...(platform === "linux" ? ["--no-sandbox"] : []), ...appArgs];
+}
+
+export function hasDisplay(env: NodeJS.ProcessEnv = process.env, platform = process.platform): boolean {
+  return platform !== "linux" || Boolean(env.DISPLAY || env.WAYLAND_DISPLAY);
+}
+
+export async function desktop(args: string[]): Promise {
+  const parsed = parseDesktopArgs(args);
+  if (!parsed.ok) {
+    console.error(`hqterm desktop: ${parsed.error}\nusage: hqterm desktop [--host H] [--session S]`);
+    return 2;
+  }
+  const path = findDesktop();
+  if (!path) {
+    console.error(`hqterm desktop: the desktop app is not installed. Install it with:\n  ${DESKTOP_INSTALL}`);
+    return 127;
+  }
+  if (!hasDisplay()) {
+    console.error("hqterm desktop: no display ($DISPLAY / $WAYLAND_DISPLAY unset). Run it from your desktop session.");
+    return 2;
+  }
+  const hasFuse = Boolean(Bun.which("fusermount") || Bun.which("fusermount3"));
+  const [cmd, ...rest] = desktopLaunch(path, parsed.opts, process.platform, hasFuse);
+  const child = spawn(cmd!, rest, { detached: true, stdio: "ignore", env: { ...process.env, ELECTRON_DISABLE_SANDBOX: "1" } });
+  const failed = await new Promise((resolve) => {
+    child.once("error", resolve);
+    child.once("spawn", () => resolve(undefined));
+  });
+  if (failed) {
+    console.error(`hqterm desktop: could not start ${path}: ${failed.message}`);
+    return 1;
+  }
+  child.unref();
+  console.log(`hqterm desktop: started ${path}${parsed.opts.host ? ` (${parsed.opts.host}${parsed.opts.session ? `/${parsed.opts.session}` : ""})` : ""}`);
+  return 0;
+}
diff --git a/src/hostlist.ts b/src/hostlist.ts
new file mode 100644
index 0000000..d73ac2f
--- /dev/null
+++ b/src/hostlist.ts
@@ -0,0 +1,12 @@
+/**
+ * The host list the TUI shows, as plain names. Bundled for Node into
+ * desktop/lib/hosts.cjs so the desktop app's "+" menu lists the same hosts.
+ */
+import { loadUserHosts, mergeHosts, validHostName } from "./hosts.ts";
+import { sshConfigHosts } from "./sshconfig.ts";
+
+export function listHosts(): string[] {
+  return mergeHosts(sshConfigHosts(), loadUserHosts()).map((h) => h.name);
+}
+
+export { validHostName };
diff --git a/src/main.ts b/src/main.ts
index e786ea5..5db3a7c 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -5,6 +5,7 @@
  * can show them.
  */
 import { emojiFontStatus, installEmojiFont, removeEmojiFont } from "@profullstack/hqtui";
+import { desktop } from "./desktop.ts";
 import { doctor } from "./doctor.ts";
 import { addUserHost, loadUserHosts, mergeHosts, validHostName } from "./hosts.ts";
 import { connect, findHqsh, installArgs, INSTALL_URL, pressEnter, runInteractive, connectArgs } from "./run.ts";
@@ -17,6 +18,9 @@ const HELP = `hqterm ${VERSION}: persistent terminal sessions that survive disco
 Usage:
   hqterm                         full-screen session manager
   hqterm connect  [name]   attach to (or create) session  on  via hqsh
+  hqterm desktop [--host H] [--session S]
+                                 open the hqterm desktop app (tabs, split panes,
+                                 images and HD emoji); optionally attached to H/S
   hqterm doctor [--try kitty|iterm]
                                  what this terminal supports, with a test image
   hqterm fonts install|status|remove
@@ -31,6 +35,7 @@ Keys in the manager:
 
 Hosts come from ~/.ssh/config and ~/.config/hqterm/hosts.json.
 Install: curl -fsSL ${INSTALL_URL} | sh
+Desktop app: curl -fsSL ${INSTALL_URL} | sh -s -- --desktop
 `;
 
 async function manager(): Promise {
@@ -118,6 +123,8 @@ export async function main(argv: string[]): Promise {
       }
       return connect(host, session);
     }
+    case "desktop":
+      return desktop(rest);
     case "doctor":
       return doctor(rest);
     case "fonts":
diff --git a/src/sshconfig.ts b/src/sshconfig.ts
index 54de69c..34b288c 100644
--- a/src/sshconfig.ts
+++ b/src/sshconfig.ts
@@ -3,9 +3,9 @@
  * is a concrete host (no `*`, `?` or `!` pattern). Order is kept, duplicates
  * are dropped. `Include` is followed when a reader is given.
  */
-import { readFileSync } from "node:fs";
+import { readdirSync, readFileSync } from "node:fs";
 import { homedir } from "node:os";
-import { join, isAbsolute } from "node:path";
+import { basename, dirname, join, isAbsolute } from "node:path";
 
 export type ReadFile = (path: string) => string | undefined;
 
@@ -74,12 +74,31 @@ function expandInclude(pattern: string, home: string): string[] {
   if (!isAbsolute(p)) p = join(home, ".ssh", p);
   if (!/[*?[]/.test(p)) return [p];
   try {
-    return [...new Bun.Glob(p).scanSync({ absolute: true, onlyFiles: true })].sort();
+    if (typeof Bun !== "undefined") return [...new Bun.Glob(p).scanSync({ absolute: true, onlyFiles: true })].sort();
+    return globBasename(p);
   } catch {
     return [];
   }
 }
 
+/** Without Bun (the desktop app runs this under Node): wildcards in the last path segment only. */
+export function globBasename(p: string, list: (dir: string) => string[] = (d) => readdirSync(d)): string[] {
+  const dir = dirname(p);
+  if (/[*?[]/.test(dir)) return [];
+  const re = new RegExp(
+    "^" +
+      basename(p)
+        .replace(/[.+^${}()|\\]/g, "\\$&")
+        .replace(/\*/g, "[^/]*")
+        .replace(/\?/g, "[^/]") +
+      "$",
+  );
+  return list(dir)
+    .filter((n) => re.test(n))
+    .sort()
+    .map((n) => join(dir, n));
+}
+
 const readText: ReadFile = (path) => {
   try {
     return readFileSync(path, "utf8");
diff --git a/test/desktop.test.ts b/test/desktop.test.ts
new file mode 100644
index 0000000..32b8baf
--- /dev/null
+++ b/test/desktop.test.ts
@@ -0,0 +1,111 @@
+import { describe, expect, test } from "bun:test";
+import { join } from "node:path";
+import { desktopCandidates, desktopLaunch, findDesktop, hasDisplay, parseDesktopArgs } from "../src/desktop.ts";
+import { globBasename } from "../src/sshconfig.ts";
+import { main } from "../src/main.ts";
+
+describe("hqterm desktop", () => {
+  test("parses --host and --session", () => {
+    expect(parseDesktopArgs([])).toEqual({ ok: true, opts: {} });
+    expect(parseDesktopArgs(["--host", "dev2", "--session=work"])).toEqual({ ok: true, opts: { host: "dev2", session: "work" } });
+    expect(parseDesktopArgs(["--session", "x"]).ok).toBe(false);
+    expect(parseDesktopArgs(["--host"]).ok).toBe(false);
+    expect(parseDesktopArgs(["--host", "-oProxyCommand=x"]).ok).toBe(false);
+    expect(parseDesktopArgs(["--host", "dev2", "--session", "a;b"]).ok).toBe(false);
+    expect(parseDesktopArgs(["dev2"]).ok).toBe(false);
+  });
+
+  test("looks in $HQTERM_DESKTOP, then the install dir (Linux) or /Applications (macOS)", () => {
+    expect(desktopCandidates({}, "linux", "/h")).toEqual([
+      "/h/.local/opt/hqterm-desktop/hqterm-desktop.AppImage",
+      "/h/.local/opt/hqterm-desktop/hqterm-desktop",
+    ]);
+    expect(desktopCandidates({ HQTERM_DESKTOP: "/x/app" }, "linux", "/h")[0]).toBe("/x/app");
+    expect(desktopCandidates({}, "darwin", "/h")).toEqual(["/Applications/hqterm.app", "/h/Applications/hqterm.app"]);
+    const have = new Set(["/h/.local/opt/hqterm-desktop/hqterm-desktop"]);
+    expect(findDesktop({}, "linux", "/h", (p) => have.has(p))).toBe("/h/.local/opt/hqterm-desktop/hqterm-desktop");
+    expect(findDesktop({}, "linux", "/h", () => false)).toBeUndefined();
+  });
+
+  test("launch argv", () => {
+    const app = "/h/.local/opt/hqterm-desktop/hqterm-desktop.AppImage";
+    expect(desktopLaunch(app, {}, "linux", true)).toEqual([app, "--no-sandbox"]);
+    expect(desktopLaunch(app, { host: "dev2", session: "w" }, "linux", false)).toEqual([
+      app,
+      "--appimage-extract-and-run",
+      "--no-sandbox",
+      "--host",
+      "dev2",
+      "--session",
+      "w",
+    ]);
+    expect(desktopLaunch("/Applications/hqterm.app", { host: "dev2" }, "darwin")).toEqual([
+      "open",
+      "-n",
+      "-a",
+      "/Applications/hqterm.app",
+      "--args",
+      "--host",
+      "dev2",
+    ]);
+  });
+
+  test("display check", () => {
+    expect(hasDisplay({}, "linux")).toBe(false);
+    expect(hasDisplay({ WAYLAND_DISPLAY: "wayland-0" }, "linux")).toBe(true);
+    expect(hasDisplay({}, "darwin")).toBe(true);
+  });
+
+  test("is a known command", async () => {
+    const prev = process.env.HQTERM_DESKTOP;
+    process.env.HQTERM_DESKTOP = "/nonexistent/hqterm-desktop";
+    const home = process.env.HOME;
+    process.env.HOME = "/nonexistent-home";
+    try {
+      // Not installed here: exits 127 with the install hint, not "unknown command" (2).
+      expect(await main(["desktop"])).toBe(127);
+      expect(await main(["desktop", "--bogus"])).toBe(2);
+    } finally {
+      if (prev === undefined) delete process.env.HQTERM_DESKTOP;
+      else process.env.HQTERM_DESKTOP = prev;
+      process.env.HOME = home;
+    }
+  });
+});
+
+describe("ssh Include globs without Bun.Glob", () => {
+  test("matches wildcards in the last segment", () => {
+    const list = () => ["work", "work.conf", "home.conf", ".hidden.conf"];
+    expect(globBasename("/h/.ssh/conf.d/*.conf", list)).toEqual(["/h/.ssh/conf.d/.hidden.conf", "/h/.ssh/conf.d/home.conf", "/h/.ssh/conf.d/work.conf"]);
+    expect(globBasename("/h/.ssh/conf.d/w?rk", list)).toEqual(["/h/.ssh/conf.d/work"]);
+    expect(globBasename("/h/*/x", list)).toEqual([]);
+  });
+});
+
+describe("install.sh flags", () => {
+  const run = (args: string[], env: Record = {}) => {
+    const p = Bun.spawnSync(["sh", join(import.meta.dir, "..", "install.sh"), ...args], {
+      env: { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: "/h", HQTERM_INSTALL_PARSE_ONLY: "1", ...env },
+    });
+    return { code: p.exitCode, out: p.stdout.toString().trim(), err: p.stderr.toString() };
+  };
+  const linux = process.platform === "linux";
+
+  test("defaults: desktop only on a Linux display", () => {
+    expect(run([]).out).toBe("bin=/h/.local/bin server=0 desktop=0 auto=1");
+    expect(run([], { DISPLAY: ":0" }).out).toBe(`bin=/h/.local/bin server=0 desktop=${linux ? 1 : 0} auto=1`);
+    expect(run([], { WAYLAND_DISPLAY: "wayland-0" }).out).toBe(`bin=/h/.local/bin server=0 desktop=${linux ? 1 : 0} auto=1`);
+  });
+
+  test("--desktop, --no-desktop, --server, --bin", () => {
+    expect(run(["--desktop"]).out).toBe("bin=/h/.local/bin server=0 desktop=1 auto=0");
+    expect(run(["--no-desktop"], { DISPLAY: ":0" }).out).toBe("bin=/h/.local/bin server=0 desktop=0 auto=0");
+    expect(run(["--server"], { DISPLAY: ":0" }).out).toBe("bin=/h/.local/bin server=1 desktop=0 auto=0");
+    expect(run(["--bin=/opt/b"]).out).toBe("bin=/opt/b server=0 desktop=0 auto=1");
+  });
+
+  test("bad combinations fail", () => {
+    expect(run(["--server", "--desktop"]).code).toBe(2);
+    expect(run(["--nope"]).code).toBe(2);
+  });
+});

From 30f7049985de5a1f77e86d9c68aa69f34fbd91f0 Mon Sep 17 00:00:00 2001
From: Anthony Ettinger 
Date: Tue, 6 Oct 2026 15:25:18 +0000
Subject: [PATCH 2/7] desktop: carry the iTerm2-images hint to remote hqtui
 (LC_TERMINAL over ssh)

hqsh forwards only TERM; sshd accepts LC_* by default and hqtui reads
LC_TERMINAL=iTerm2 as iTerm2 inline images.

Co-Authored-By: Claude Opus 5.5 
---
 desktop/main.js | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/desktop/main.js b/desktop/main.js
index 33f539b..d52f67d 100644
--- a/desktop/main.js
+++ b/desktop/main.js
@@ -87,6 +87,8 @@ function paneEnv() {
     TERM_PROGRAM_VERSION: VERSION,
     HQTUI_IMAGES: "iterm",
     QC_HD: "iterm",
+    // The one hint that crosses ssh (SendEnv/AcceptEnv LC_*): iTerm2 inline images work here.
+    LC_TERMINAL: "iTerm2",
   });
   return env;
 }
@@ -102,7 +104,10 @@ function startDir() {
 function command(spec) {
   if (spec && spec.kind === "remote" && HOST_RE.test(spec.host || "") && !spec.host.startsWith("-")) {
     const session = SESSION_RE.test(spec.session || "") ? spec.session : "main";
-    return ["/bin/sh", ["-lc", 'exec hqsh "$0" --session "$1"', spec.host, session]];
+    // hqsh forwards only TERM. sshd accepts LC_* by default (AcceptEnv LANG LC_*),
+    // a new session's shell inherits it, and hqtui reads LC_TERMINAL=iTerm2 as
+    // "draw iTerm2 inline images": so remote hqtui/qc draw HD images here too.
+    return ["/bin/sh", ["-lc", 'exec hqsh "$0" --session "$1" -- -o SetEnv=LC_TERMINAL=iTerm2', spec.host, session]];
   }
   if (spec && spec.kind === "hqterm") return ["/bin/sh", ["-lc", "exec hqterm"]];
   return [loginShell(), ["-l"]];

From b64fe74f41159163805b735f26401ce16b787392 Mon Sep 17 00:00:00 2001
From: Anthony Ettinger 
Date: Tue, 6 Oct 2026 15:26:01 +0000
Subject: [PATCH 3/7] desktop: smoke test draws emoji + an inline image and
 screenshots it; desktopName for KDE window association

Co-Authored-By: Claude Opus 5.5 
---
 .github/workflows/ci.yml |  8 +++++++-
 desktop/main.js          | 24 ++++++++++++++++++++++--
 desktop/package.json     |  1 +
 3 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f4fb115..d3f0f55 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -40,8 +40,14 @@ jobs:
         run: |
           ls -la dist
           app="$(ls dist/*.AppImage | head -n1)"
-          HQTERM_DESKTOP_SMOKE=8000 XDG_CONFIG_HOME="$RUNNER_TEMP/cfg" xvfb-run -a "$app" --appimage-extract-and-run --no-sandbox 2>&1 | tee smoke.log
+          HQTERM_DESKTOP_SMOKE=10000 HQTERM_DESKTOP_SMOKE_SHOT="$PWD/smoke.png" XDG_CONFIG_HOME="$RUNNER_TEMP/cfg" \
+            xvfb-run -a -s "-screen 0 1280x800x24" "$app" --appimage-extract-and-run --no-sandbox 2>&1 | grep -v appimage_extracted | tee smoke.log
           grep -q "hqterm-desktop smoke: ok" smoke.log
+      - uses: actions/upload-artifact@v4
+        with:
+          name: hqterm-desktop-smoke-screenshot
+          path: desktop/smoke.png
+          retention-days: 7
       - uses: actions/upload-artifact@v4
         with:
           name: hqterm-desktop-linux-x86_64
diff --git a/desktop/main.js b/desktop/main.js
index d52f67d..89d0a28 100644
--- a/desktop/main.js
+++ b/desktop/main.js
@@ -248,11 +248,31 @@ if (!app.requestSingleInstanceLock()) {
     Menu.setApplicationMenu(null);
     createWindow();
     if (process.env.HQTERM_DESKTOP_SMOKE) {
-      // CI: prove the window and a PTY come up, then exit.
+      // CI: prove the window and a PTY come up, draw emoji and an iTerm2
+      // inline image, optionally screenshot it ($HQTERM_DESKTOP_SMOKE_SHOT), exit.
+      const wait = Number(process.env.HQTERM_DESKTOP_SMOKE) || 8000;
       setTimeout(() => {
+        const [id, p] = [...ptys.entries()][0] || [];
+        if (!p || !win) return;
+        p.write("printf '\\033[2J\\033[Hhqterm smoke: emoji \\360\\237\\230\\200 \\360\\237\\226\\245\\357\\270\\217 \\342\\234\\205 wide|\\n'\r");
+        const png = fs.readFileSync(path.join(__dirname, "src", "icon.png")).toString("base64");
+        setTimeout(() => {
+          if (win) win.webContents.send("pty:data", id, `\r\n\x1b]1337;File=inline=1;width=12;height=6;preserveAspectRatio=1:${png}\x07\r\nafter image\r\n`);
+        }, 1000);
+      }, wait / 2);
+      setTimeout(async () => {
+        const shot = process.env.HQTERM_DESKTOP_SMOKE_SHOT;
+        if (shot && win) {
+          try {
+            fs.writeFileSync(shot, (await win.webContents.capturePage()).toPNG());
+            console.log(`hqterm-desktop smoke: screenshot ${shot}`);
+          } catch (err) {
+            console.log(`hqterm-desktop smoke: screenshot failed: ${err && err.message}`);
+          }
+        }
         console.log(`hqterm-desktop smoke: ok (ptys=${ptys.size})`);
         app.exit(ptys.size > 0 ? 0 : 3);
-      }, Number(process.env.HQTERM_DESKTOP_SMOKE) || 8000);
+      }, wait);
     }
   });
   app.on("window-all-closed", () => app.quit());
diff --git a/desktop/package.json b/desktop/package.json
index c4d2d81..433bb64 100644
--- a/desktop/package.json
+++ b/desktop/package.json
@@ -8,6 +8,7 @@
   "homepage": "https://hqterm.sh",
   "private": true,
   "main": "main.js",
+  "desktopName": "hqterm.desktop",
   "scripts": {
     "prepare-assets": "node scripts/prepare.mjs",
     "start": "npm run prepare-assets && electron .",

From 90d61fb4353b9fb562a9984291f9b511ab5f806f Mon Sep 17 00:00:00 2001
From: Anthony Ettinger 
Date: Tue, 6 Oct 2026 15:27:51 +0000
Subject: [PATCH 4/7] desktop: OpenIcon close (not the X logo) on tabs; smoke
 test paints without a WM and logs the renderer console

Co-Authored-By: Claude Opus 5.5 
---
 desktop/main.js             | 5 +++++
 desktop/src/app.js          | 2 +-
 desktop/src/icons/close.svg | 1 +
 desktop/src/icons/x.svg     | 1 -
 4 files changed, 7 insertions(+), 2 deletions(-)
 create mode 100644 desktop/src/icons/close.svg
 delete mode 100644 desktop/src/icons/x.svg

diff --git a/desktop/main.js b/desktop/main.js
index 89d0a28..bd1f4fa 100644
--- a/desktop/main.js
+++ b/desktop/main.js
@@ -133,8 +133,13 @@ function createWindow() {
       nodeIntegration: false,
       sandbox: true,
       spellcheck: false,
+      // The smoke test runs without a window manager, where an unfocused window stops painting.
+      backgroundThrottling: !process.env.HQTERM_DESKTOP_SMOKE,
     },
   });
+  if (process.env.HQTERM_DESKTOP_SMOKE) {
+    win.webContents.on("console-message", (e) => console.log(`renderer ${e.level}: ${e.message}`));
+  }
   win.loadFile(path.join(__dirname, "src", "index.html"));
   // Nothing navigates away from the terminal; links open in the browser.
   win.webContents.on("will-navigate", (e) => e.preventDefault());
diff --git a/desktop/src/app.js b/desktop/src/app.js
index 9b44f97..152cf34 100644
--- a/desktop/src/app.js
+++ b/desktop/src/app.js
@@ -390,7 +390,7 @@
         const close = document.createElement("button");
         close.className = "icon";
         close.title = "Close tab (Ctrl+Shift+W)";
-        close.innerHTML = 'Close';
+        close.innerHTML = 'Close';
         close.addEventListener("click", (e) => {
           e.stopPropagation();
           closeTab(tabs.indexOf(tab));
diff --git a/desktop/src/icons/close.svg b/desktop/src/icons/close.svg
new file mode 100644
index 0000000..223d2e1
--- /dev/null
+++ b/desktop/src/icons/close.svg
@@ -0,0 +1 @@
+Close
diff --git a/desktop/src/icons/x.svg b/desktop/src/icons/x.svg
deleted file mode 100644
index 31e23fc..0000000
--- a/desktop/src/icons/x.svg
+++ /dev/null
@@ -1 +0,0 @@
-X

From dfb10cbc1af9912477da6b2137598e11c525d3e2 Mon Sep 17 00:00:00 2001
From: Anthony Ettinger 
Date: Tue, 6 Oct 2026 15:29:57 +0000
Subject: [PATCH 5/7] desktop: smoke test uses hqtui's exact iTerm2 image
 escape (with size=)

Co-Authored-By: Claude Opus 5.5 
---
 desktop/main.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/desktop/main.js b/desktop/main.js
index bd1f4fa..6124855 100644
--- a/desktop/main.js
+++ b/desktop/main.js
@@ -260,9 +260,11 @@ if (!app.requestSingleInstanceLock()) {
         const [id, p] = [...ptys.entries()][0] || [];
         if (!p || !win) return;
         p.write("printf '\\033[2J\\033[Hhqterm smoke: emoji \\360\\237\\230\\200 \\360\\237\\226\\245\\357\\270\\217 \\342\\234\\205 wide|\\n'\r");
-        const png = fs.readFileSync(path.join(__dirname, "src", "icon.png")).toString("base64");
+        // The same escape hqtui emits (emoji art: 2 cells; then a bigger one).
+        const png = fs.readFileSync(path.join(__dirname, "src", "icon.png"));
+        const iip = (w, h) => `\x1b]1337;File=inline=1;size=${png.length};width=${w};height=${h};preserveAspectRatio=1:${png.toString("base64")}\x07`;
         setTimeout(() => {
-          if (win) win.webContents.send("pty:data", id, `\r\n\x1b]1337;File=inline=1;width=12;height=6;preserveAspectRatio=1:${png}\x07\r\nafter image\r\n`);
+          if (win) win.webContents.send("pty:data", id, `\r\nemoji art: ${iip(2, 1)} | image:\r\n${iip(12, 6)}\r\nafter image\r\n`);
         }, 1000);
       }, wait / 2);
       setTimeout(async () => {

From 2ef94bdf6c0ba69f23e71f71680ce0b224346919 Mon Sep 17 00:00:00 2001
From: Anthony Ettinger 
Date: Tue, 6 Oct 2026 15:32:48 +0000
Subject: [PATCH 6/7] desktop: cursor moves past iTerm2 inline images like
 iTerm2/WezTerm (hqtui emoji art); smoke covers split + layout restore

Co-Authored-By: Claude Opus 5.5 
---
 .github/workflows/ci.yml | 19 ++++++++++++++-----
 desktop/main.js          |  6 ++++++
 desktop/src/app.js       | 32 +++++++++++++++++++++++++++++++-
 3 files changed, 51 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d3f0f55..7b79f4c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -36,17 +36,26 @@ jobs:
       - run: npm test
       - name: Build AppImage
         run: npm run dist
-      - name: Smoke test under Xvfb (window + a PTY come up)
+      - name: Smoke test under Xvfb (window, PTYs, images, split, layout restore)
         run: |
           ls -la dist
           app="$(ls dist/*.AppImage | head -n1)"
-          HQTERM_DESKTOP_SMOKE=10000 HQTERM_DESKTOP_SMOKE_SHOT="$PWD/smoke.png" XDG_CONFIG_HOME="$RUNNER_TEMP/cfg" \
-            xvfb-run -a -s "-screen 0 1280x800x24" "$app" --appimage-extract-and-run --no-sandbox 2>&1 | grep -v appimage_extracted | tee smoke.log
-          grep -q "hqterm-desktop smoke: ok" smoke.log
+          smoke() {
+            HQTERM_DESKTOP_SMOKE=10000 HQTERM_DESKTOP_SMOKE_SHOT="$PWD/$1" XDG_CONFIG_HOME="$RUNNER_TEMP/cfg" \
+              xvfb-run -a -s "-screen 0 1280x800x24" "$app" --appimage-extract-and-run --no-sandbox 2>&1 | grep -v appimage_extracted | tee "$1.log"
+          }
+          # First launch: one shell, then Ctrl+Shift+D splits it (2 PTYs).
+          smoke smoke-1.png
+          grep -q "hqterm-desktop smoke: ok (ptys=2)" smoke-1.png.log
+          cat "$RUNNER_TEMP/cfg/hqterm/desktop-layout.json"
+          # Second launch restores both panes, then splits again (3 PTYs).
+          smoke smoke-2.png
+          grep -q "hqterm-desktop smoke: ok (ptys=3)" smoke-2.png.log
       - uses: actions/upload-artifact@v4
+        if: always()
         with:
           name: hqterm-desktop-smoke-screenshot
-          path: desktop/smoke.png
+          path: desktop/smoke-*.png
           retention-days: 7
       - uses: actions/upload-artifact@v4
         with:
diff --git a/desktop/main.js b/desktop/main.js
index 6124855..c442b87 100644
--- a/desktop/main.js
+++ b/desktop/main.js
@@ -267,6 +267,12 @@ if (!app.requestSingleInstanceLock()) {
           if (win) win.webContents.send("pty:data", id, `\r\nemoji art: ${iip(2, 1)} | image:\r\n${iip(12, 6)}\r\nafter image\r\n`);
         }, 1000);
       }, wait / 2);
+      // Split right with the real key binding: one more pane, one more PTY.
+      setTimeout(() => {
+        if (!win) return;
+        win.webContents.sendInputEvent({ type: "keyDown", keyCode: "D", modifiers: ["control", "shift"] });
+        win.webContents.sendInputEvent({ type: "keyUp", keyCode: "D", modifiers: ["control", "shift"] });
+      }, wait * 0.7);
       setTimeout(async () => {
         const shot = process.env.HQTERM_DESKTOP_SMOKE_SHOT;
         if (shot && win) {
diff --git a/desktop/src/app.js b/desktop/src/app.js
index 152cf34..8d75ade 100644
--- a/desktop/src/app.js
+++ b/desktop/src/app.js
@@ -139,11 +139,41 @@
       }
     }
     // After the renderer, so images draw on top of it.
-    rt.term.loadAddon(new ImageAddon.ImageAddon({ iipSupport: true, sixelSupport: true, enableSizeReports: true }));
+    const images = new ImageAddon.ImageAddon({ iipSupport: true, sixelSupport: true, enableSizeReports: true });
+    rt.term.loadAddon(images);
+    iipCursorLikeITerm(rt.term, images);
     fit(rt);
     spawn(rt);
   }
 
+  /**
+   * iTerm2, WezTerm and kitty leave the cursor just right of an inline image,
+   * and hqtui's emoji art relies on it ("hi " + art + " there"). addon-image
+   * leaves it at the image's left edge, so the next text erases the art. After
+   * an iTerm2 image (not sixel), move the cursor past it.
+   */
+  function iipCursorLikeITerm(term, addon) {
+    const storage = addon._storage;
+    if (!storage || typeof storage.addImage !== "function" || !term._core) return;
+    let pending = false;
+    // Registered after the addon, so this runs first; false lets the addon draw it.
+    term.parser.registerOscHandler(1337, (data) => {
+      pending = data.startsWith("File=");
+      return false;
+    });
+    const addImage = storage.addImage.bind(storage);
+    storage.addImage = (img) => {
+      const buffer = term._core.buffer;
+      const x0 = buffer.x;
+      addImage(img);
+      if (!pending) return;
+      pending = false;
+      const cell = addon._renderer && addon._renderer.cellSize;
+      const w = cell && cell.width > 0 ? cell.width : 10;
+      buffer.x = Math.min(x0 + Math.ceil(img.width / w), term.cols);
+    };
+  }
+
   function fit(rt) {
     if (!rt.opened || rt.disposed || !rt.el.isConnected) return;
     if (rt.el.clientWidth < 10 || rt.el.clientHeight < 10) return;

From 0bb0a694d558336ca1729d8fc1fc51caa15e33a2 Mon Sep 17 00:00:00 2001
From: Anthony Ettinger 
Date: Tue, 6 Oct 2026 15:34:59 +0000
Subject: [PATCH 7/7] release: arm64 AppImage is best-effort; install xvfb if
 the runner lacks it

Co-Authored-By: Claude Opus 5.5 
---
 .github/workflows/release.yml | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 301b6f3..663bf43 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -17,6 +17,8 @@ jobs:
           - runner: ubuntu-24.04-arm
             arch: aarch64
     runs-on: ${{ matrix.runner }}
+    # arm64 is a bonus: its failure must not hold back the x86_64 release.
+    continue-on-error: ${{ matrix.arch == 'aarch64' }}
     defaults:
       run:
         working-directory: desktop
@@ -42,7 +44,8 @@ jobs:
           ls -la dist
       - name: Smoke test under Xvfb
         run: |
-          HQTERM_DESKTOP_SMOKE=8000 XDG_CONFIG_HOME="$RUNNER_TEMP/cfg" xvfb-run -a "dist/hqterm-desktop-linux-${{ matrix.arch }}.AppImage" --appimage-extract-and-run --no-sandbox 2>&1 | tee smoke.log
+          if ! command -v xvfb-run >/dev/null; then sudo apt-get update -qq && sudo apt-get install -y -qq xvfb; fi
+          HQTERM_DESKTOP_SMOKE=8000 XDG_CONFIG_HOME="$RUNNER_TEMP/cfg" xvfb-run -a "dist/hqterm-desktop-linux-${{ matrix.arch }}.AppImage" --appimage-extract-and-run --no-sandbox 2>&1 | grep -v appimage_extracted | tee smoke.log
           grep -q "hqterm-desktop smoke: ok" smoke.log
       - uses: actions/upload-artifact@v4
         with: