From a5eccc7764f2eef4c68d9b1d8f5b01f482b45a2b Mon Sep 17 00:00:00 2001 From: Louis Date: Fri, 25 Sep 2026 14:43:32 +0200 Subject: [PATCH] MrTerm: SSH/SFTP/RDP-Client im Termius-Stil (Electron) Co-Authored-By: Claude Opus 5.5 --- .gitignore | 2 + README.md | 27 + package-lock.json | 3712 +++++++++++++++++++++++++++++++++++++++ package.json | 57 + src/main/main.js | 266 +++ src/main/rdp.js | 87 + src/main/ssh.js | 324 ++++ src/main/store.js | 107 ++ src/preload.js | 30 + src/renderer/app.js | 1184 +++++++++++++ src/renderer/index.html | 61 + src/renderer/styles.css | 252 +++ src/renderer/themes.js | 24 + 13 files changed, 6133 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/main/main.js create mode 100644 src/main/rdp.js create mode 100644 src/main/ssh.js create mode 100644 src/main/store.js create mode 100644 src/preload.js create mode 100644 src/renderer/app.js create mode 100644 src/renderer/index.html create mode 100644 src/renderer/styles.css create mode 100644 src/renderer/themes.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e92bd34 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# MrTerm + +SSH-, SFTP- und RDP-Client im Stil von Termius – Electron + xterm.js + ssh2. Läuft auf Windows und Arch/CachyOS. + +## Funktionen +- **Hosts**: Gruppen (verschachtelt), Tags, Farben, Suche, Quick Connect (`user@host:port`, `rdp://host`) +- **SSH-Terminal** in Tabs: Passwort, Schlüssel, SSH-Agent (Windows OpenSSH / `SSH_AUTH_SOCK`), Keyboard-Interactive/2FA, Jump-Hosts (ProxyJump-Ketten), Startbefehl, Env-Variablen, Suche, Zoom, 7 Farbschemata +- **SFTP**: Dual-Pane (lokal ↔ remote), Drag & Drop, Ordner rekursiv, Umbenennen, Löschen, chmod, Fortschrittsanzeige +- **RDP**: Windows → `mstsc` (Anmeldedaten temporär via `cmdkey`), Linux → FreeRDP (`xfreerdp3`/`sdl-freerdp3`/`wlfreerdp3`, Passwort über stdin) +- **Keychain**: Ed25519/ECDSA/RSA generieren, importieren, Public Key kopieren +- **Port-Forwarding**: lokal (-L), remote (-R), dynamisch/SOCKS5 (-D) +- **Snippets**, **Known Hosts** (TOFU, Warnung bei geändertem Schlüssel), **Verlauf**, `~/.ssh/config`-Import, Backup Export/Import +- **Vault verschlüsselt** über das OS (Windows DPAPI, Linux libsecret/KWallet) +- Befehlspalette `Strg+K`, `Strg+W`, `Strg+Tab`, `Strg+1..9`, `Strg+Shift+C/V/F` + +## Entwicklung +```bash +npm install +npm start +``` +> In VS Code-Terminals ggf. vorher `ELECTRON_RUN_AS_NODE` entfernen. + +## Pakete bauen +- Windows (auf Windows): `npm run dist:win` → Installer + Portable in `dist/` +- Arch/CachyOS (auf Linux): `npm run dist:linux` → AppImage + `.pacman`-Paket; installieren mit `sudo pacman -U dist/mrterm-*.pacman` + +Abhängigkeiten unter Arch/CachyOS: `sudo pacman -S freerdp gnome-keyring` (oder `kwallet` unter KDE) für RDP bzw. Vault-Verschlüsselung. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..17695e2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3712 @@ +{ + "name": "mrterm", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mrterm", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@xterm/addon-fit": "^0.11.0", + "@xterm/addon-search": "^0.16.0", + "@xterm/addon-web-links": "^0.12.0", + "@xterm/xterm": "^6.0.0", + "ssh2": "^1.17.0" + }, + "devDependencies": { + "electron": "^44.4.5", + "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.9.5", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.5.tgz", + "integrity": "sha512-Ez3wLKVjaxdsLcgeWN4OE31QkM7oBOgKuuBJxldRYAkfYw2C+8zJPcSd/SThnbhszsEOlAmoaS5kIIkN29fGKQ==", + "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.13.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.6.tgz", + "integrity": "sha512-SGrw/h3KPFshy3OE6ZL53LMBG5vGQQ8/gIpiqz/kRZhPJ7HgwCEs8LBuNtWLa8dvGZVpSF7+Bf+c11HUrCb/yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "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-search": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0.tgz", + "integrity": "sha512-9OeuBFu0/uZJPu+9AHKY6g/w0Czyb/Ut0A5t79I4ULoU4IfU5BEpPFVGQxP4zTTMdfZEYkVIRYbHBX1xWwjeSA==", + "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/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/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.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/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "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/buildcheck": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz", + "integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==", + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, + "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/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "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.4.5", + "resolved": "https://registry.npmjs.org/electron/-/electron-44.4.5.tgz", + "integrity": "sha512-SjgoaeYsSWZfJzubgQU7juvuXMTvn6/e1gAHdGFA/yuMbpF+I+skYqIJ6DdXBXHeWbkbpNpmwZCTpiivtlWZSw==", + "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.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "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/nan": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.29.0.tgz", + "integrity": "sha512-GlGk3HIvitbvs+LT3g6XUP1kpirKNvmDFwF/bmo6XNWSb/eYEs/O4bfgIEIXCZ+lIOTS5xNwDvSGMw6FJdAhtA==", + "license": "MIT", + "optional": true + }, + "node_modules/node-abi": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.35.0.tgz", + "integrity": "sha512-ymk4aIzxdPopw2giv8Fs1Ec6vybGkjmyxUwVqhkI4MCy2tVfXdkOGGWieWVjL0THgH+7a8lRdevyupoYj3Js/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "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/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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/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/ssh2": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz", + "integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==", + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.10", + "nan": "^2.23.0" + } + }, + "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/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "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.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "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/package.json b/package.json new file mode 100644 index 0000000..33a0e2f --- /dev/null +++ b/package.json @@ -0,0 +1,57 @@ +{ + "name": "mrterm", + "productName": "MrTerm", + "version": "0.1.0", + "description": "Moderner SSH-, SFTP- und RDP-Client", + "main": "src/main/main.js", + "author": "MrBlake", + "license": "MIT", + "scripts": { + "start": "electron .", + "dist:win": "electron-builder --win", + "dist:linux": "electron-builder --linux" + }, + "build": { + "appId": "de.mrterm.app", + "productName": "MrTerm", + "files": [ + "src/**/*", + "node_modules/**/*", + "package.json" + ], + "win": { + "target": [ + "nsis", + "portable" + ] + }, + "linux": { + "target": [ + "AppImage", + "pacman" + ], + "category": "Network", + "maintainer": "Louis" + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true + } + }, + "dependencies": { + "@xterm/addon-fit": "^0.11.0", + "@xterm/addon-search": "^0.16.0", + "@xterm/addon-web-links": "^0.12.0", + "@xterm/xterm": "^6.0.0", + "ssh2": "^1.17.0" + }, + "devDependencies": { + "electron": "^44.4.5", + "electron-builder": "^26.15.3" + }, + "allowScripts": { + "electron@44.4.5": true, + "electron-winstaller@5.4.0": true, + "ssh2@1.17.0": true + } +} diff --git a/src/main/main.js b/src/main/main.js new file mode 100644 index 0000000..cd294be --- /dev/null +++ b/src/main/main.js @@ -0,0 +1,266 @@ +const { app, BrowserWindow, ipcMain, dialog, shell, clipboard, Menu } = require('electron'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); +const crypto = require('crypto'); +const { utils: sshUtils } = require('ssh2'); +const { Store } = require('./store'); +const { SshManager } = require('./ssh'); +const rdp = require('./rdp'); + +let win; +const store = new Store(); +const pendingSecrets = new Map(); + +function send(channel, ...args) { + if (win && !win.isDestroyed()) win.webContents.send(channel, ...args); +} + +async function confirmHostKey(target, fingerprint, known) { + if (process.env.MRTERM_SMOKE) return true; + const changed = !!known; + const r = await dialog.showMessageBox(win, { + type: changed ? 'warning' : 'question', + title: changed ? 'Host-Schlüssel hat sich geändert!' : 'Unbekannter Host', + message: changed + ? `WARNUNG: Der Host-Schlüssel von ${target.host}:${target.port} hat sich geändert. Das kann auf einen Man-in-the-Middle-Angriff hindeuten.` + : `Die Echtheit von ${target.host}:${target.port} kann nicht bestätigt werden.`, + detail: `Fingerprint:\n${fingerprint}${changed ? `\n\nBisher gespeichert:\n${known.fingerprint}` : ''}`, + buttons: [changed ? 'Trotzdem verbinden & ersetzen' : 'Vertrauen & verbinden', 'Abbrechen'], + defaultId: changed ? 1 : 0, + cancelId: 1, + }); + return r.response === 0; +} + +function askSecret(sessionId, req) { + return new Promise((resolve) => { + const reqId = crypto.randomUUID(); + pendingSecrets.set(reqId, resolve); + send('secret:request', { reqId, sessionId, ...req }); + }); +} + +const ssh = new SshManager(store, confirmHostKey, askSecret); + +function createWindow() { + win = new BrowserWindow({ + width: 1360, + height: 860, + minWidth: 900, + minHeight: 560, + backgroundColor: '#14161d', + frame: false, + titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'hidden', + webPreferences: { + preload: path.join(__dirname, '..', 'preload.js'), + contextIsolation: true, + nodeIntegration: false, + sandbox: true, + }, + }); + Menu.setApplicationMenu(null); + win.loadFile(path.join(__dirname, '..', 'renderer', 'index.html')); + win.on('maximize', () => send('win:state', true)); + win.on('unmaximize', () => send('win:state', false)); + win.webContents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); return { action: 'deny' }; }); +} + +function handle(channel, fn) { + ipcMain.handle(channel, async (_e, ...args) => { + try { return { ok: true, value: await fn(...args) }; } + catch (e) { return { ok: false, error: e.message || String(e) }; } + }); +} + +// ---------- Fenster ---------- +ipcMain.on('win:min', () => win.minimize()); +ipcMain.on('win:max', () => (win.isMaximized() ? win.unmaximize() : win.maximize())); +ipcMain.on('win:close', () => win.close()); + +// ---------- Vault ---------- +handle('vault:get', () => ({ ...store.get(), encrypted: store.encrypted, platform: process.platform })); +handle('vault:upsert', (col, item) => store.upsert(col, item)); +handle('vault:remove', (col, id) => store.remove(col, id)); +handle('vault:settings', (s) => store.setSettings(s)); +handle('vault:forgetHost', (id) => { delete store.get().knownHosts[id]; store.save(); }); +handle('vault:export', async () => { + const r = await dialog.showSaveDialog(win, { defaultPath: 'mrterm-backup.json', filters: [{ name: 'JSON', extensions: ['json'] }] }); + if (r.canceled) return false; + fs.writeFileSync(r.filePath, JSON.stringify(store.get(), null, 2), { mode: 0o600 }); + return r.filePath; +}); +handle('vault:import', async () => { + const r = await dialog.showOpenDialog(win, { filters: [{ name: 'JSON', extensions: ['json'] }], properties: ['openFile'] }); + if (r.canceled) return false; + const data = JSON.parse(fs.readFileSync(r.filePaths[0], 'utf8')); + for (const col of ['groups', 'hosts', 'keys', 'snippets', 'forwards']) + for (const item of data[col] || []) store.upsert(col, item); + return true; +}); + +// Import aus ~/.ssh/config +handle('vault:importSshConfig', () => { + const file = path.join(os.homedir(), '.ssh', 'config'); + if (!fs.existsSync(file)) throw new Error('~/.ssh/config nicht gefunden'); + const entries = []; + let cur = null; + for (const raw of fs.readFileSync(file, 'utf8').split(/\r?\n/)) { + const line = raw.trim(); + if (!line || line.startsWith('#')) continue; + const [k, ...rest] = line.split(/\s+/); + const v = rest.join(' '); + if (k.toLowerCase() === 'host') { + cur = v.includes('*') ? null : { label: v, address: v, port: 22, protocol: 'ssh' }; + if (cur) entries.push(cur); + } else if (cur) { + const key = k.toLowerCase(); + if (key === 'hostname') cur.address = v; + else if (key === 'user') cur.username = v; + else if (key === 'port') cur.port = Number(v); + else if (key === 'identityfile') cur._identity = v.replace(/^~/, os.homedir()); + } + } + let n = 0; + for (const e of entries) { + if (store.get().hosts.some((h) => h.address === e.address && h.username === e.username && Number(h.port) === e.port)) continue; + if (e._identity && fs.existsSync(e._identity)) { + const pk = fs.readFileSync(e._identity, 'utf8'); + let key = store.get().keys.find((k) => k.privateKey === pk); + if (!key) key = store.upsert('keys', { label: path.basename(e._identity), privateKey: pk, publicKey: readPub(e._identity) }); + e.keyId = key.id; + } + delete e._identity; + store.upsert('hosts', e); + n++; + } + return n; +}); + +function readPub(p) { + try { return fs.readFileSync(p + '.pub', 'utf8').trim(); } catch { return ''; } +} + +// ---------- Schlüssel ---------- +handle('key:generate', ({ type, bits, comment, passphrase }) => { + const opts = { comment: comment || `${os.userInfo().username}@mrterm` }; + if (type === 'rsa') opts.bits = Number(bits) || 4096; + if (passphrase) { opts.passphrase = passphrase; opts.cipher = 'aes256-cbc'; } + const k = sshUtils.generateKeyPairSync(type === 'rsa' ? 'rsa' : type === 'ecdsa' ? 'ecdsa' : 'ed25519', opts); + return { privateKey: k.private, publicKey: k.public }; +}); +handle('key:parse', ({ privateKey, passphrase }) => { + const k = sshUtils.parseKey(privateKey, passphrase || undefined); + if (k instanceof Error) throw k; + const key = Array.isArray(k) ? k[0] : k; + return { type: key.type, publicKey: `${key.type} ${key.getPublicSSH().toString('base64')} ${key.comment || ''}`.trim() }; +}); +handle('key:pickFile', async () => { + const r = await dialog.showOpenDialog(win, { defaultPath: path.join(os.homedir(), '.ssh'), properties: ['openFile', 'showHiddenFiles'] }); + if (r.canceled) return null; + return { name: path.basename(r.filePaths[0]), content: fs.readFileSync(r.filePaths[0], 'utf8'), publicKey: readPub(r.filePaths[0]) }; +}); + +// ---------- SSH-Terminal ---------- +function hostWithOverrides(hostOrId) { + if (typeof hostOrId === 'string') { + const h = store.resolveHost(hostOrId); + if (!h) throw new Error('Host nicht gefunden'); + return h; + } + return hostOrId; // Quick-Connect: temporärer Host +} + +handle('ssh:open', async (sessionId, hostRef, size) => { + const host = hostWithOverrides(hostRef); + if (host.id) store.addHistory({ hostId: host.id, at: Date.now() }); + await ssh.openShell(sessionId, host, size, (type, payload) => send('ssh:event', sessionId, type, payload)); + return true; +}); +ipcMain.on('ssh:write', (_e, id, data) => ssh.write(id, data)); +ipcMain.on('ssh:resize', (_e, id, cols, rows) => ssh.resize(id, cols, rows)); +ipcMain.on('ssh:close', (_e, id) => ssh.close(id)); +ipcMain.on('secret:reply', (_e, reqId, value) => { + pendingSecrets.get(reqId)?.(value); + pendingSecrets.delete(reqId); +}); + +// ---------- SFTP ---------- +handle('sftp:open', (sessionId, hostRef) => ssh.openSftp(sessionId, hostWithOverrides(hostRef))); +handle('sftp:list', (id, dir) => ssh.sftpList(id, dir)); +handle('sftp:op', (id, op, a, b) => ssh.sftpOp(id, op, a, b)); +handle('sftp:transfer', async (id, dir, localPath, remotePath, transferId) => { + let last = 0; + await ssh.transfer(id, dir, localPath, remotePath, (done, total) => { + const now = Date.now(); + if (now - last > 100 || done === total) { last = now; send('sftp:progress', transferId, done, total, dir === 'download' ? remotePath : localPath); } + }); + return true; +}); +handle('sftp:close', (id) => ssh.close(id)); + +// Lokales Dateisystem (für den Dual-Pane-SFTP-Browser) +handle('local:home', () => os.homedir()); +handle('local:list', (dir) => fs.readdirSync(dir, { withFileTypes: true }).map((d) => { + let st = {}; + try { st = fs.statSync(path.join(dir, d.name)); } catch {} + return { name: d.name, isDir: d.isDirectory() || (d.isSymbolicLink() && st.isDirectory?.()), size: st.size || 0, mtime: st.mtimeMs || 0 }; +})); +handle('local:join', (...p) => path.join(...p)); +handle('local:parent', (p) => path.dirname(p)); +handle('local:roots', () => { + if (process.platform !== 'win32') return ['/']; + return 'CDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((l) => `${l}:\\`).filter((d) => fs.existsSync(d)); +}); +handle('local:op', (op, a, b) => { + if (op === 'mkdir') return fs.mkdirSync(a, { recursive: true }); + if (op === 'rename') return fs.renameSync(a, b); + if (op === 'delete') return shell.trashItem(a); + if (op === 'open') return shell.openPath(a); +}); + +// ---------- Port-Forwarding ---------- +handle('fw:start', (id) => { + const fw = store.get().forwards.find((f) => f.id === id); + if (!fw) throw new Error('Regel nicht gefunden'); + return ssh.startForward(fw, (ev) => send('fw:event', id, ev)); +}); +handle('fw:stop', (id) => ssh.stopForward(id)); +handle('fw:active', () => ssh.activeForwards()); + +// ---------- RDP ---------- +handle('rdp:detect', () => rdp.detect(store.get().settings)); +handle('rdp:launch', (hostId) => { + const host = hostWithOverrides(hostId); + if (host.id) store.addHistory({ hostId: host.id, at: Date.now() }); + const r = rdp.launch(host, store.get().settings); + if (r.process) { + r.process.on('exit', (code) => { if (code && code !== 0 && code !== 12) send('toast', `RDP beendet (Code ${code}): ${r.getErr().split('\n').filter(Boolean).slice(-1)[0] || ''}`, 'error'); }); + r.process.on('error', (e) => send('toast', 'RDP-Start fehlgeschlagen: ' + e.message, 'error')); + } + return r.client; +}); + +handle('clipboard:write', (t) => clipboard.writeText(t)); +handle('clipboard:read', () => clipboard.readText()); +handle('shell:open', (url) => shell.openExternal(url)); + +app.whenReady().then(() => { + store.load(); + createWindow(); +}); +app.on('window-all-closed', () => { ssh.closeAll(); app.quit(); }); + +// Smoke-Test: MRTERM_SMOKE= startet, loggt Renderer-Meldungen, speichert einen Screenshot und beendet. +if (process.env.MRTERM_SMOKE) { + app.whenReady().then(() => { + win.webContents.on('console-message', (e) => console.log('[renderer]', e.level, e.message)); + setTimeout(async () => { + if (process.env.MRTERM_SMOKE_JS) await win.webContents.executeJavaScript(process.env.MRTERM_SMOKE_JS).catch((e) => console.log('js error', e)); + setTimeout(async () => { + fs.writeFileSync(process.env.MRTERM_SMOKE, (await win.webContents.capturePage()).toPNG()); + app.quit(); + }, 1500); + }, 2500); + }); +} diff --git a/src/main/rdp.js b/src/main/rdp.js new file mode 100644 index 0000000..b0bf22e --- /dev/null +++ b/src/main/rdp.js @@ -0,0 +1,87 @@ +// RDP: Windows nutzt das eingebaute mstsc.exe, Linux (Arch/CachyOS) FreeRDP (xfreerdp3 / xfreerdp / wlfreerdp). +const { spawn, execFile, execFileSync } = require('child_process'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +function which(bin) { + try { + execFileSync(process.platform === 'win32' ? 'where' : 'which', [bin], { stdio: 'ignore' }); + return true; + } catch { return false; } +} + +function linuxClient(pref) { + const candidates = pref && pref !== 'auto' + ? [pref] + : process.env.WAYLAND_DISPLAY + ? ['sdl-freerdp3', 'wlfreerdp3', 'xfreerdp3', 'wlfreerdp', 'xfreerdp'] + : ['xfreerdp3', 'sdl-freerdp3', 'xfreerdp']; + return candidates.find(which); +} + +function detect(settings) { + if (process.platform === 'win32') return { available: true, client: 'mstsc' }; + const c = linuxClient(settings.rdpClientLinux); + return c ? { available: true, client: c } : { available: false, hint: 'sudo pacman -S freerdp' }; +} + +function launch(host, settings) { + const port = Number(host.port) || 3389; + const user = host.domain ? `${host.domain}\\${host.username || ''}` : host.username || ''; + + if (process.platform === 'win32') { + const target = port === 3389 ? host.address : `${host.address}:${port}`; + const lines = [ + `full address:s:${target}`, + user && `username:s:${user}`, + `screen mode id:i:${host.rdpFullscreen ? 2 : 1}`, + host.rdpWidth && `desktopwidth:i:${host.rdpWidth}`, + host.rdpHeight && `desktopheight:i:${host.rdpHeight}`, + 'dynamic resolution:i:1', + 'smart sizing:i:1', + `redirectclipboard:i:${host.rdpClipboard === false ? 0 : 1}`, + `redirectdrives:i:${host.rdpDrives ? 1 : 0}`, + `audiomode:i:${host.rdpAudio === false ? 2 : 0}`, + 'prompt for credentials:i:0', + 'authentication level:i:2', + ].filter(Boolean); + const file = path.join(os.tmpdir(), `mrterm-${host.id}.rdp`); + fs.writeFileSync(file, lines.join('\r\n'), 'utf16le'); + const run = () => { + const p = spawn('mstsc.exe', [file], { detached: true, stdio: 'ignore' }); + p.unref(); + }; + if (host.password && user) { + // Anmeldedaten temporär im Windows-Anmeldeinformationsspeicher ablegen, danach wieder entfernen. + execFile('cmdkey', [`/generic:TERMSRV/${host.address}`, `/user:${user}`, `/pass:${host.password}`], () => { + run(); + setTimeout(() => execFile('cmdkey', [`/delete:TERMSRV/${host.address}`], () => {}), 20000); + }); + } else run(); + return { client: 'mstsc' }; + } + + const client = linuxClient(settings.rdpClientLinux); + if (!client) throw new Error('Kein FreeRDP gefunden. Installieren mit: sudo pacman -S freerdp'); + const args = [`/v:${host.address}:${port}`, '/cert:tofu', '+auto-reconnect']; + if (host.username) args.push(`/u:${host.username}`); + if (host.domain) args.push(`/d:${host.domain}`); + if (host.rdpFullscreen) args.push('/f'); + else if (host.rdpWidth && host.rdpHeight) args.push(`/size:${host.rdpWidth}x${host.rdpHeight}`); + else args.push('/dynamic-resolution'); + if (host.rdpClipboard !== false) args.push('+clipboard'); + if (host.rdpDrives) args.push(`/drive:home,${os.homedir()}`); + if (host.rdpAudio !== false) args.push('/sound'); + args.push(`/title:${host.label || host.address}`); + // Passwort über stdin statt Kommandozeile (nicht in `ps` sichtbar) + if (host.password) args.push('/from-stdin'); + const p = spawn(client, args, { detached: true, stdio: [host.password ? 'pipe' : 'ignore', 'ignore', 'pipe'] }); + if (host.password) { p.stdin.write(host.password + '\n'); p.stdin.end(); } + let err = ''; + p.stderr.on('data', (d) => { err += d; }); + p.unref(); + return { client, process: p, getErr: () => err }; +} + +module.exports = { detect, launch }; diff --git a/src/main/ssh.js b/src/main/ssh.js new file mode 100644 index 0000000..5afad30 --- /dev/null +++ b/src/main/ssh.js @@ -0,0 +1,324 @@ +// SSH-Verbindungen: Terminal-Shells, SFTP, Port-Forwarding (L/R/D), Jump-Hosts. +const { Client } = require('ssh2'); +const net = require('net'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const crypto = require('crypto'); + +function defaultAgent() { + if (process.env.SSH_AUTH_SOCK) return process.env.SSH_AUTH_SOCK; + if (process.platform === 'win32') return '\\\\.\\pipe\\openssh-ssh-agent'; + return undefined; +} + +class SshManager { + /** + * @param {import('./store').Store} store + * @param {(host, fingerprint, known) => Promise} confirmHostKey + * @param {(sessionId, prompt) => Promise} askSecret + */ + constructor(store, confirmHostKey, askSecret) { + this.store = store; + this.confirmHostKey = confirmHostKey; + this.askSecret = askSecret; + this.sessions = new Map(); // id -> { conn, stream, sftp, jumps[] } + this.forwards = new Map(); // forwardId -> { conn, server, jumps } + } + + buildAuth(host, sessionId) { + const cfg = { + host: host.address, + port: Number(host.port) || 22, + username: host.username || os.userInfo().username, + readyTimeout: 20000, + keepaliveInterval: (this.store.get().settings.keepAlive || 0) * 1000, + tryKeyboard: true, + }; + if (host.keyId) { + const key = this.store.resolveKey(host.keyId); + if (key) { + cfg.privateKey = key.privateKey; + if (key.passphrase) cfg.passphrase = key.passphrase; + } + } + if (host.password) cfg.password = host.password; + if (host.useAgent !== false) cfg.agent = defaultAgent(); + + cfg.hostVerifier = (keyBuf, verify) => { + const fp = 'SHA256:' + crypto.createHash('sha256').update(keyBuf).digest('base64').replace(/=+$/, ''); + const id = `[${cfg.host}]:${cfg.port}`; + const known = this.store.get().knownHosts[id]; + if (known && known.fingerprint === fp) return verify(true); + this.confirmHostKey({ id, host: cfg.host, port: cfg.port }, fp, known).then((ok) => { + if (ok) { + this.store.get().knownHosts[id] = { fingerprint: fp, addedAt: Date.now() }; + this.store.save(); + } + verify(ok); + }); + }; + return cfg; + } + + // Baut eine Verbindung auf, optional über eine Kette von Jump-Hosts. + async connect(host, sessionId, onStatus = () => {}) { + const chain = []; + let jumpId = host.jumpHostId; + const seen = new Set([host.id]); + while (jumpId && !seen.has(jumpId)) { + seen.add(jumpId); + const j = this.store.resolveHost(jumpId); + if (!j) break; + chain.unshift(j); + jumpId = j.jumpHostId; + } + const opened = []; + let sock; + try { + for (const hop of [...chain, host]) { + onStatus(`Verbinde mit ${hop.label || hop.address} (${hop.address}:${hop.port || 22}) …`); + const conn = await this.openClient(hop, sessionId, sock); + opened.push(conn); + if (hop !== host) { + const next = hop === chain[chain.length - 1] ? host : chain[chain.indexOf(hop) + 1]; + sock = await new Promise((res, rej) => + conn.forwardOut('127.0.0.1', 0, next.address, Number(next.port) || 22, (err, s) => (err ? rej(err) : res(s)))); + } + } + } catch (e) { + opened.forEach((c) => c.end()); + throw e; + } + const conn = opened.pop(); + return { conn, jumps: opened }; + } + + openClient(host, sessionId, sock) { + return new Promise((resolve, reject) => { + const conn = new Client(); + const cfg = this.buildAuth(host, sessionId); + if (sock) { cfg.sock = sock; delete cfg.host; } + let askedPassword = false; + conn.on('keyboard-interactive', async (name, instr, lang, prompts, finish) => { + const answers = []; + for (const p of prompts) { + if (!p.echo && host.password && !askedPassword) { answers.push(host.password); askedPassword = true; continue; } + const a = await this.askSecret(sessionId, { title: name || 'Anmeldung', prompt: p.prompt, echo: p.echo, host: host.label || host.address }); + if (a == null) return finish([]); + answers.push(a); + } + finish(answers); + }); + conn.once('ready', () => resolve(conn)); + conn.once('error', async (err) => { + // Kein Passwort hinterlegt und alle Methoden schlugen fehl -> nach Passwort fragen und neu versuchen + if (err.level === 'client-authentication' && !host.password && !host._retried) { + const pw = await this.askSecret(sessionId, { title: 'Passwort', prompt: `Passwort für ${cfg.username}@${host.address}`, echo: false, host: host.label || host.address }); + if (pw != null) { + this.openClient({ ...host, password: pw, _retried: true }, sessionId, sock).then(resolve, reject); + return; + } + } + reject(err); + }); + try { conn.connect(cfg); } catch (e) { reject(e); } + }); + } + + // ---------- Terminal ---------- + async openShell(sessionId, host, { cols, rows }, send) { + const { conn, jumps } = await this.connect(host, sessionId, (m) => send('status', m)); + const sess = { conn, jumps, host }; + this.sessions.set(sessionId, sess); + conn.on('close', () => { send('closed'); this.cleanup(sessionId); }); + conn.on('error', (e) => send('error', e.message)); + + const env = host.env ? Object.fromEntries(host.env.split('\n').filter(Boolean).map((l) => l.split('=').map((s) => s.trim()))) : undefined; + await new Promise((res, rej) => { + conn.shell({ term: 'xterm-256color', cols, rows }, { env }, (err, stream) => { + if (err) return rej(err); + sess.stream = stream; + stream.on('data', (d) => send('data', d.toString('utf8'))); + stream.stderr.on('data', (d) => send('data', d.toString('utf8'))); + stream.on('close', () => conn.end()); + if (host.startupCommand) stream.write(host.startupCommand + '\n'); + res(); + }); + }); + } + + write(id, data) { this.sessions.get(id)?.stream?.write(data); } + resize(id, cols, rows) { this.sessions.get(id)?.stream?.setWindow(rows, cols, 0, 0); } + + close(id) { + const s = this.sessions.get(id); + if (!s) return; + try { s.stream?.end(); } catch {} + try { s.conn.end(); } catch {} + this.cleanup(id); + } + + cleanup(id) { + const s = this.sessions.get(id); + if (!s) return; + s.jumps?.forEach((c) => { try { c.end(); } catch {} }); + this.sessions.delete(id); + } + + // ---------- SFTP ---------- + async openSftp(sessionId, host) { + const { conn, jumps } = await this.connect(host, sessionId); + const sftp = await new Promise((res, rej) => conn.sftp((e, s) => (e ? rej(e) : res(s)))); + this.sessions.set(sessionId, { conn, jumps, sftp, host }); + conn.on('close', () => this.cleanup(sessionId)); + const home = await new Promise((res) => sftp.realpath('.', (e, p) => res(e ? '/' : p))); + return { home }; + } + + sftpOf(id) { + const s = this.sessions.get(id); + if (!s?.sftp) throw new Error('SFTP-Sitzung nicht gefunden'); + return s.sftp; + } + + sftpList(id, dir) { + const sftp = this.sftpOf(id); + return new Promise((res, rej) => sftp.readdir(dir, (err, list) => { + if (err) return rej(err); + res(list.map((f) => ({ + name: f.filename, + size: f.attrs.size, + mtime: f.attrs.mtime * 1000, + isDir: (f.attrs.mode & 0o170000) === 0o040000, + isLink: (f.attrs.mode & 0o170000) === 0o120000, + mode: f.attrs.mode, + }))); + })); + } + + sftpOp(id, op, a, b) { + const sftp = this.sftpOf(id); + const cb = (res, rej) => (err, v) => (err ? rej(err) : res(v)); + return new Promise((res, rej) => { + switch (op) { + case 'mkdir': return sftp.mkdir(a, cb(res, rej)); + case 'rename': return sftp.rename(a, b, cb(res, rej)); + case 'unlink': return sftp.unlink(a, cb(res, rej)); + case 'rmdir': return this.sftpRmRf(sftp, a).then(res, rej); + case 'realpath': return sftp.realpath(a, cb(res, rej)); + case 'chmod': return sftp.chmod(a, b, cb(res, rej)); + default: rej(new Error('Unbekannte Operation ' + op)); + } + }); + } + + async sftpRmRf(sftp, dir) { + const list = await new Promise((res, rej) => sftp.readdir(dir, (e, l) => (e ? rej(e) : res(l)))); + for (const f of list) { + const p = path.posix.join(dir, f.filename); + if ((f.attrs.mode & 0o170000) === 0o040000) await this.sftpRmRf(sftp, p); + else await new Promise((res, rej) => sftp.unlink(p, (e) => (e ? rej(e) : res()))); + } + await new Promise((res, rej) => sftp.rmdir(dir, (e) => (e ? rej(e) : res()))); + } + + async transfer(id, direction, localPath, remotePath, progress) { + const sftp = this.sftpOf(id); + const step = (done, _chunk, total) => progress(done, total); + if (direction === 'download') { + const st = await new Promise((res, rej) => sftp.stat(remotePath, (e, s) => (e ? rej(e) : res(s)))); + if ((st.mode & 0o170000) === 0o040000) { + fs.mkdirSync(localPath, { recursive: true }); + for (const f of await this.sftpList(id, remotePath)) + await this.transfer(id, direction, path.join(localPath, f.name), path.posix.join(remotePath, f.name), progress); + return; + } + await new Promise((res, rej) => sftp.fastGet(remotePath, localPath, { step }, (e) => (e ? rej(e) : res()))); + } else { + const st = fs.statSync(localPath); + if (st.isDirectory()) { + await new Promise((res) => sftp.mkdir(remotePath, () => res())); + for (const name of fs.readdirSync(localPath)) + await this.transfer(id, direction, path.join(localPath, name), path.posix.join(remotePath, name), progress); + return; + } + await new Promise((res, rej) => sftp.fastPut(localPath, remotePath, { step }, (e) => (e ? rej(e) : res()))); + } + } + + // ---------- Port-Forwarding ---------- + async startForward(fw, onEvent) { + const host = this.store.resolveHost(fw.hostId); + if (!host) throw new Error('Host nicht gefunden'); + const { conn, jumps } = await this.connect(host, 'fw-' + fw.id); + const entry = { conn, jumps }; + this.forwards.set(fw.id, entry); + conn.on('close', () => { this.stopForward(fw.id); onEvent('stopped'); }); + const bindHost = fw.bindAddress || '127.0.0.1'; + + if (fw.type === 'remote') { + await new Promise((res, rej) => conn.forwardIn(bindHost, Number(fw.bindPort), (e) => (e ? rej(e) : res()))); + conn.on('tcp connection', (info, accept) => { + const ch = accept(); + const sock = net.connect(Number(fw.destPort), fw.destHost || '127.0.0.1'); + ch.pipe(sock).pipe(ch); + sock.on('error', () => ch.close()); + }); + return; + } + + const server = net.createServer((sock) => { + if (fw.type === 'dynamic') return this.socks5(conn, sock); + conn.forwardOut(sock.remoteAddress || '127.0.0.1', sock.remotePort || 0, fw.destHost, Number(fw.destPort), (err, ch) => { + if (err) return sock.destroy(); + sock.pipe(ch).pipe(sock); + sock.on('error', () => ch.close()); + }); + }); + entry.server = server; + await new Promise((res, rej) => { server.once('error', rej); server.listen(Number(fw.bindPort), bindHost, res); }); + } + + // Minimaler SOCKS5-Server (CONNECT, ohne Auth) für dynamisches Forwarding + socks5(conn, sock) { + sock.once('data', (hello) => { + if (hello[0] !== 5) return sock.destroy(); + sock.write(Buffer.from([5, 0])); + sock.once('data', (req) => { + if (req[1] !== 1) { sock.end(Buffer.from([5, 7, 0, 1, 0, 0, 0, 0, 0, 0])); return; } + let host, off; + if (req[3] === 1) { host = [...req.slice(4, 8)].join('.'); off = 8; } + else if (req[3] === 3) { const l = req[4]; host = req.slice(5, 5 + l).toString(); off = 5 + l; } + else if (req[3] === 4) { host = req.slice(4, 20).toString('hex').match(/.{4}/g).join(':'); off = 20; } + else return sock.destroy(); + const port = req.readUInt16BE(off); + conn.forwardOut('127.0.0.1', 0, host, port, (err, ch) => { + if (err) { sock.end(Buffer.from([5, 5, 0, 1, 0, 0, 0, 0, 0, 0])); return; } + sock.write(Buffer.from([5, 0, 0, 1, 0, 0, 0, 0, 0, 0])); + sock.pipe(ch).pipe(sock); + sock.on('error', () => ch.close()); + }); + }); + }); + sock.on('error', () => {}); + } + + stopForward(id) { + const f = this.forwards.get(id); + if (!f) return; + this.forwards.delete(id); + try { f.server?.close(); } catch {} + try { f.conn.end(); } catch {} + f.jumps?.forEach((c) => { try { c.end(); } catch {} }); + } + + activeForwards() { return [...this.forwards.keys()]; } + + closeAll() { + for (const id of [...this.sessions.keys()]) this.close(id); + for (const id of [...this.forwards.keys()]) this.stopForward(id); + } +} + +module.exports = { SshManager }; diff --git a/src/main/store.js b/src/main/store.js new file mode 100644 index 0000000..ca0dfe3 --- /dev/null +++ b/src/main/store.js @@ -0,0 +1,107 @@ +// Persistenter Vault: Hosts, Keys, Snippets, Forwards, Settings. +// Wird mit Electron safeStorage (DPAPI unter Windows, libsecret/kwallet unter Linux) verschlüsselt. +const { app, safeStorage } = require('electron'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const DEFAULTS = { + version: 1, + groups: [], + hosts: [], + keys: [], + snippets: [], + forwards: [], + knownHosts: {}, + history: [], + settings: { + terminalTheme: 'mrterm', + fontFamily: 'Cascadia Code, JetBrains Mono, Fira Code, Consolas, monospace', + fontSize: 14, + cursorStyle: 'block', + cursorBlink: true, + scrollback: 10000, + copyOnSelect: true, + keepAlive: 30, + rdpClientLinux: 'auto', + }, +}; + +class Store { + constructor() { + this.dir = app.getPath('userData'); + this.file = path.join(this.dir, 'vault.dat'); + this.data = structuredClone(DEFAULTS); + this.encrypted = false; + } + + canEncrypt() { + try { + if (!safeStorage.isEncryptionAvailable()) return false; + // Unter Linux ohne Keyring fällt Electron auf "basic_text" zurück – das ist keine echte Verschlüsselung. + if (process.platform === 'linux' && safeStorage.getSelectedStorageBackend?.() === 'basic_text') return false; + return true; + } catch { return false; } + } + + load() { + fs.mkdirSync(this.dir, { recursive: true }); + if (!fs.existsSync(this.file)) { this.save(); return this.data; } + const raw = fs.readFileSync(this.file); + let json; + if (raw.slice(0, 4).toString() === 'ENC1') { + json = safeStorage.decryptString(raw.slice(4)); + this.encrypted = true; + } else { + json = raw.toString('utf8'); + } + const parsed = JSON.parse(json); + this.data = { ...structuredClone(DEFAULTS), ...parsed, settings: { ...DEFAULTS.settings, ...(parsed.settings || {}) } }; + return this.data; + } + + save() { + const json = JSON.stringify(this.data, null, 2); + const tmp = this.file + '.tmp'; + if (this.canEncrypt()) { + fs.writeFileSync(tmp, Buffer.concat([Buffer.from('ENC1'), safeStorage.encryptString(json)])); + this.encrypted = true; + } else { + fs.writeFileSync(tmp, json, { mode: 0o600 }); + this.encrypted = false; + } + fs.renameSync(tmp, this.file); + } + + get() { return this.data; } + + // Generisches Upsert für Collections (hosts, groups, keys, snippets, forwards) + upsert(collection, item) { + const list = this.data[collection]; + if (!Array.isArray(list)) throw new Error('Unbekannte Collection: ' + collection); + if (!item.id) item.id = crypto.randomUUID(); + const i = list.findIndex((x) => x.id === item.id); + if (i >= 0) list[i] = { ...list[i], ...item, updatedAt: Date.now() }; + else list.push({ ...item, createdAt: Date.now(), updatedAt: Date.now() }); + this.save(); + return item; + } + + remove(collection, id) { + this.data[collection] = this.data[collection].filter((x) => x.id !== id); + if (collection === 'groups') this.data.hosts.forEach((h) => { if (h.groupId === id) h.groupId = null; }); + this.save(); + } + + setSettings(s) { this.data.settings = { ...this.data.settings, ...s }; this.save(); } + + addHistory(entry) { + this.data.history = [entry, ...this.data.history.filter((h) => h.hostId !== entry.hostId)].slice(0, 30); + this.save(); + } + + resolveHost(id) { return this.data.hosts.find((h) => h.id === id); } + resolveKey(id) { return this.data.keys.find((k) => k.id === id); } +} + +module.exports = { Store }; diff --git a/src/preload.js b/src/preload.js new file mode 100644 index 0000000..c18fa43 --- /dev/null +++ b/src/preload.js @@ -0,0 +1,30 @@ +const { contextBridge, ipcRenderer } = require('electron'); + +async function call(channel, ...args) { + const r = await ipcRenderer.invoke(channel, ...args); + if (!r.ok) throw new Error(r.error); + return r.value; +} + +function on(channel, fn) { + const h = (_e, ...a) => fn(...a); + ipcRenderer.on(channel, h); + return () => ipcRenderer.removeListener(channel, h); +} + +contextBridge.exposeInMainWorld('api', { + platform: process.platform, + call, + on, + win: { + min: () => ipcRenderer.send('win:min'), + max: () => ipcRenderer.send('win:max'), + close: () => ipcRenderer.send('win:close'), + }, + ssh: { + write: (id, d) => ipcRenderer.send('ssh:write', id, d), + resize: (id, c, r) => ipcRenderer.send('ssh:resize', id, c, r), + close: (id) => ipcRenderer.send('ssh:close', id), + }, + replySecret: (reqId, v) => ipcRenderer.send('secret:reply', reqId, v), +}); diff --git a/src/renderer/app.js b/src/renderer/app.js new file mode 100644 index 0000000..68b7606 --- /dev/null +++ b/src/renderer/app.js @@ -0,0 +1,1184 @@ +/* global Terminal, FitAddon, WebLinksAddon, SearchAddon, TERM_THEMES */ +'use strict'; + +// ============================================================ Helpers +const $ = (sel, root = document) => root.querySelector(sel); +const $$ = (sel, root = document) => [...root.querySelectorAll(sel)]; +const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); +const uid = () => crypto.randomUUID(); +function h(html) { const t = document.createElement('template'); t.innerHTML = html.trim(); return t.content.firstElementChild; } + +const ICONS = { + search: '', + plus: '', + edit: '', + trash: '', + more: '', + close: '', + folder: '', + file: '', + term: '', + screen: '', + key: '', + copy: '', + play: '', + stop: '', + up: '', + refresh: '', + home: '', + newdir: '', + arrowR: '', + arrowL: '', + code: '', + shield: '', + lock: '', + unlock: '', + eye: '', + fwd: '', + dup: '', + clock: '', + download: '', + upload: '', +}; +const COLORS = ['#6e7bff', '#3ecf8e', '#ff5f6d', '#ffb454', '#c792ea', '#56d6d6', '#ff79c6', '#8b91a5', '#4f9dff', '#e0a100']; +function colorFor(h) { + if (h.color) return h.color; + let n = 0; for (const c of h.label || h.address || '') n = (n * 31 + c.charCodeAt(0)) >>> 0; + return COLORS[n % COLORS.length]; +} +function avatar(hst, size) { + const letter = esc((hst.label || hst.address || '?').trim()[0]?.toUpperCase() || '?'); + const proto = hst.protocol === 'rdp' ? 'RDP' : 'SSH'; + return `
${hst.protocol === 'rdp' ? ICONS.screen : letter}${proto}
`; +} +function fmtSize(n) { + if (n == null) return ''; + const u = ['B', 'KB', 'MB', 'GB', 'TB']; let i = 0; + while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; } + return `${n.toFixed(i ? 1 : 0)} ${u[i]}`; +} +const fmtDate = (t) => (t ? new Date(t).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' }) : ''); +function relTime(t) { + const s = (Date.now() - t) / 1000; + if (s < 60) return 'gerade eben'; + if (s < 3600) return `vor ${Math.floor(s / 60)} Min.`; + if (s < 86400) return `vor ${Math.floor(s / 3600)} Std.`; + return `vor ${Math.floor(s / 86400)} Tagen`; +} + +function toast(msg, type = '') { + const el = h(`
${esc(msg)}
`); + $('#toasts').append(el); + setTimeout(() => el.remove(), type === 'error' ? 7000 : 3500); +} + +async function call(ch, ...a) { + try { return await api.call(ch, ...a); } + catch (e) { toast(e.message, 'error'); throw e; } +} + +// ---------- Modals +function modal({ title, text = '', body = '', buttons = [] }) { + return new Promise((resolve) => { + const bg = h(``); + const foot = $('.mfoot', bg); + const done = (v) => { bg.remove(); resolve(v); }; + buttons.forEach((b) => { + const btn = h(``); + btn.onclick = () => done(b.value === 'form' ? Object.fromEntries($$('[name]', bg).map((i) => [i.name, i.type === 'checkbox' ? i.checked : i.value])) : b.value); + foot.append(btn); + }); + bg.addEventListener('mousedown', (e) => { if (e.target === bg) done(null); }); + bg.addEventListener('keydown', (e) => { + if (e.key === 'Escape') done(null); + if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA') { e.preventDefault(); foot.lastElementChild.click(); } + }); + $('#modalRoot').append(bg); + ($('input,select,textarea', bg) || foot.lastElementChild).focus(); + }); +} +const confirmBox = (title, text, label = 'Löschen') => + modal({ title, text, buttons: [{ label: 'Abbrechen', value: false, cls: 'ghost' }, { label, value: true, cls: label === 'Löschen' ? 'danger' : 'primary' }] }); +async function promptBox(title, label, value = '', type = 'text') { + const r = await modal({ title, body: `
`, + buttons: [{ label: 'Abbrechen', value: null, cls: 'ghost' }, { label: 'OK', value: 'form', cls: 'primary' }] }); + return r ? r.v : null; +} + +// ---------- Kontextmenü +function ctxMenu(x, y, items) { + $$('.ctx').forEach((c) => c.remove()); + const m = h('
'); + for (const it of items) { + if (it === '-') { m.append(h('
')); continue; } + const b = h(``); + b.onclick = () => { m.remove(); it.run(); }; + m.append(b); + } + document.body.append(m); + const r = m.getBoundingClientRect(); + m.style.left = Math.min(x, innerWidth - r.width - 8) + 'px'; + m.style.top = Math.min(y, innerHeight - r.height - 8) + 'px'; + setTimeout(() => document.addEventListener('mousedown', function off(e) { if (!m.contains(e.target)) { m.remove(); document.removeEventListener('mousedown', off); } }), 0); +} + +// ============================================================ State +const S = { + vault: null, + view: 'hosts', + group: null, + search: '', + tag: null, + tabs: [], + active: 'home', + activeForwards: new Set(), +}; +const settings = () => S.vault.settings; + +async function reload() { + S.vault = await call('vault:get'); + const b = $('#vaultBadge'); + b.innerHTML = S.vault.encrypted ? `${ICONS.lock}Vault verschlüsselt` : `${ICONS.unlock}Vault unverschlüsselt`; + b.title = S.vault.encrypted ? 'Daten werden mit dem System-Schlüsselbund verschlüsselt.' : 'Kein System-Schlüsselbund verfügbar (z. B. gnome-keyring/kwallet installieren).'; + S.activeForwards = new Set(await call('fw:active')); + render(); +} + +// ============================================================ Navigation +$$('#sidebar .nav').forEach((n) => n.addEventListener('click', () => { + S.view = n.dataset.view; + $$('#sidebar .nav').forEach((x) => x.classList.toggle('active', x === n)); + closeDrawer(); + activateTab('home'); + render(); +})); + +function render() { + const page = $('#homeView'); + const views = { hosts: viewHosts, sftp: viewSftpPicker, keys: viewKeys, forwards: viewForwards, snippets: viewSnippets, known: viewKnown, history: viewHistory, settings: viewSettings }; + const scroll = page.scrollTop; + page.innerHTML = ''; + views[S.view](page); + page.scrollTop = scroll; +} + +function toolbar(placeholder, buttons = []) { + const t = h(`
`); + const inp = $('input', t); + inp.addEventListener('input', () => { S.search = inp.value; const pos = inp.selectionStart; render(); const n = $('#homeView .search input'); n.focus(); n.setSelectionRange(pos, pos); }); + buttons.forEach((b) => { const el = h(``); el.onclick = b.run; t.append(el); }); + return t; +} +const matches = (...fields) => { const q = S.search.trim().toLowerCase(); return !q || fields.some((f) => String(f || '').toLowerCase().includes(q)); }; +function emptyState(icon, title, text, btnLabel, run) { + const e = h(`
${ICONS[icon]}

${esc(title)}

${esc(text)}

`); + if (btnLabel) { const b = h(``); b.onclick = run; e.append(b); } + return e; +} + +// ============================================================ Hosts +function hostSub(hst) { + const u = hst.username ? `${hst.username}@` : ''; + const port = hst.port && Number(hst.port) !== (hst.protocol === 'rdp' ? 3389 : 22) ? `:${hst.port}` : ''; + return `${hst.protocol === 'rdp' ? 'rdp' : 'ssh'}, ${u}${hst.address}${port}`; +} + +function viewHosts(page) { + page.append(toolbar('Hosts suchen oder user@host eingeben …', [ + { label: 'Gruppe', icon: 'newdir', run: () => editGroup({ parentId: S.group }) }, + { label: 'Neuer Host', icon: 'plus', cls: 'primary', run: () => editHost({ groupId: S.group }) }, + ])); + const c = h('
'); + page.append(c); + const { groups, hosts } = S.vault; + const searching = !!S.search.trim(); + + if (S.group) { + const trail = []; let g = groups.find((x) => x.id === S.group); + while (g) { trail.unshift(g); g = groups.find((x) => x.id === g.parentId); } + const bc = h(''); + const root = h('Alle Hosts'); root.onclick = () => { S.group = null; render(); }; bc.append(root); + trail.forEach((t) => { bc.append(' / '); const a = h(`${esc(t.label)}`); a.onclick = () => { S.group = t.id; render(); }; bc.append(a); }); + c.append(bc); + } + + const allTags = [...new Set(hosts.flatMap((x) => x.tags || []))].sort(); + if (allTags.length) { + const ch = h('
'); + allTags.forEach((t) => { const b = h(``); b.onclick = () => { S.tag = S.tag === t ? null : t; render(); }; ch.append(b); }); + c.append(ch); + } + + const subGroups = searching ? groups.filter((g) => matches(g.label)) : groups.filter((g) => (g.parentId || null) === S.group); + if (subGroups.length && !S.tag) { + c.append(h(`
Gruppen ${subGroups.length}
`)); + const grid = h('
'); + for (const g of subGroups) { + const count = hosts.filter((x) => x.groupId === g.id).length; + const card = h(`
${ICONS.folder}
${esc(g.label)}
${count} Host${count === 1 ? '' : 's'}
`); + card.onclick = (e) => { if (e.target.closest('[data-a=edit]')) return editGroup(g); S.group = g.id; S.search = ''; render(); }; + card.oncontextmenu = (e) => ctxMenu(e.clientX, e.clientY, [ + { label: 'Bearbeiten', icon: 'edit', run: () => editGroup(g) }, + { label: 'Host hinzufügen', icon: 'plus', run: () => editHost({ groupId: g.id }) }, + '-', { label: 'Löschen', icon: 'trash', danger: true, run: async () => { if (await confirmBox('Gruppe löschen?', `„${g.label}“ wird gelöscht. Hosts bleiben erhalten.`)) { await call('vault:remove', 'groups', g.id); reload(); } } }, + ]); + grid.append(card); + } + c.append(grid); + } + + let list = hosts.filter((x) => (searching || S.tag ? true : (x.groupId || null) === S.group)); + list = list.filter((x) => matches(x.label, x.address, x.username, ...(x.tags || []))); + if (S.tag) list = list.filter((x) => (x.tags || []).includes(S.tag)); + list.sort((a, b) => (a.label || a.address).localeCompare(b.label || b.address)); + + if (list.length) { + c.append(h(`
Hosts ${list.length}
`)); + const grid = h('
'); + list.forEach((hst) => grid.append(hostCard(hst))); + c.append(grid); + } else if (!subGroups.length) { + const q = S.search.trim(); + if (q && /^[^\s@]+@[^\s]+$|^[\w.-]+(:\d+)?$/.test(q)) { + c.append(emptyState('term', `Mit ${q} verbinden?`, 'Quick Connect ohne den Host zu speichern.', 'Verbinden', () => quickConnect(q))); + } else if (!hosts.length) { + const e = emptyState('screen', 'Noch keine Hosts', 'Lege deinen ersten SSH- oder RDP-Host an oder importiere ~/.ssh/config.', 'Neuer Host', () => editHost({})); + const imp = h(''); + imp.onclick = importSshConfig; e.append(imp); + c.append(e); + } else c.append(emptyState('search', 'Keine Treffer', 'Passe die Suche oder Filter an.')); + } +} + +function hostCard(hst) { + const tags = (hst.tags || []).length ? `
${hst.tags.map((t) => `${esc(t)}`).join('')}
` : ''; + const card = h(`
${avatar(hst)}
${esc(hst.label || hst.address)}
${esc(hostSub(hst))}
${tags}
+
${hst.protocol !== 'rdp' ? `` : ''}
`); + card.addEventListener('click', (e) => { + const a = e.target.closest('[data-a]')?.dataset.a; + if (a === 'edit') return editHost(hst); + if (a === 'sftp') return openSftp(hst); + }); + card.addEventListener('dblclick', (e) => { if (!e.target.closest('[data-a]')) connectHost(hst); }); + card.oncontextmenu = (e) => ctxMenu(e.clientX, e.clientY, hostMenu(hst)); + return card; +} + +function hostMenu(hst) { + return [ + { label: hst.protocol === 'rdp' ? 'RDP-Verbindung starten' : 'Verbinden', icon: 'play', run: () => connectHost(hst) }, + ...(hst.protocol !== 'rdp' ? [{ label: 'SFTP öffnen', icon: 'folder', run: () => openSftp(hst) }] : []), + '-', + { label: 'Bearbeiten', icon: 'edit', run: () => editHost(hst) }, + { label: 'Duplizieren', icon: 'dup', run: async () => { const { id, createdAt, updatedAt, ...rest } = hst; await call('vault:upsert', 'hosts', { ...rest, label: (hst.label || hst.address) + ' (Kopie)' }); reload(); } }, + { label: 'Adresse kopieren', icon: 'copy', run: () => call('clipboard:write', `${hst.username ? hst.username + '@' : ''}${hst.address}`) }, + '-', + { label: 'Löschen', icon: 'trash', danger: true, run: async () => { if (await confirmBox('Host löschen?', `„${hst.label || hst.address}“ wird dauerhaft entfernt.`)) { await call('vault:remove', 'hosts', hst.id); reload(); } } }, + ]; +} + +function connectHost(hst) { + if (hst.protocol === 'rdp') return launchRdp(hst); + openTerminal(hst); +} + +async function launchRdp(hst) { + try { + const client = await call('rdp:launch', hst.id || hst); + toast(`RDP-Sitzung zu ${hst.label || hst.address} gestartet (${client})`, 'ok'); + } catch {} +} + +function parseQuick(q) { + const m = q.trim().match(/^(?:(rdp|ssh):\/\/)?(?:([^@\s]+)@)?([^\s:]+|\[[^\]]+\])(?::(\d+))?$/i); + if (!m) return null; + return { protocol: (m[1] || 'ssh').toLowerCase(), username: m[2], address: m[3].replace(/^\[|\]$/g, ''), port: m[4] ? Number(m[4]) : undefined, label: q.trim() }; +} +function quickConnect(q) { + const hst = parseQuick(q); + if (!hst) return toast('Ungültige Adresse. Format: user@host:port', 'error'); + connectHost(hst); +} + +async function importSshConfig() { + try { const n = await call('vault:importSshConfig'); toast(`${n} Host(s) importiert`, 'ok'); reload(); } catch {} +} + +// ============================================================ Drawer / Editoren +function openDrawer(title, fields, onSave, extraFooter) { + const d = $('#drawer'); + d.innerHTML = ''; + const form = h('
'); + const hd = h(`

${esc(title)}

`); + $('button', hd).onclick = closeDrawer; + const ft = h('
'); + if (extraFooter) ft.prepend(extraFooter); + const cancel = h(''); cancel.onclick = closeDrawer; + const save = h(''); + save.onclick = async () => { try { if ((await onSave()) !== false) closeDrawer(); } catch (e) { if (e.message) toast(e.message, 'error'); } }; + ft.append(cancel, save); + form.append(...[].concat(fields)); + d.append(hd, form, ft); + d.classList.add('open'); + d.onkeydown = (e) => { if (e.key === 'Escape') closeDrawer(); if (e.key === 'Enter' && e.ctrlKey) save.click(); }; + setTimeout(() => $('input,select,textarea', form)?.focus(), 50); + return form; +} +function closeDrawer() { $('#drawer').classList.remove('open'); } + +function field(label, name, value = '', opts = {}) { + const { type = 'text', placeholder = '', hint = '', cls = '', options } = opts; + let input; + if (type === 'select') input = ``; + else if (type === 'textarea') input = ``; + else if (type === 'password') input = `
`; + else input = ``; + const el = h(`
${input}${hint ? `
${esc(hint)}
` : ''}
`); + const eye = $('.pw-wrap button', el); + if (eye) eye.onclick = () => { const i = $('input', el); i.type = i.type === 'password' ? 'text' : 'password'; }; + return el; +} +const row = (...els) => { const r = h('
'); r.append(...els); return r; }; +const check = (label, name, checked) => h(``); +const heading = (t) => h(`

${esc(t)}

`); +function formValues(root) { + const o = {}; + $$('[name]', root).forEach((i) => { o[i.name] = i.type === 'checkbox' ? i.checked : i.value; }); + return o; +} +function colorPicker(current) { + const el = h(`
`); + const wrap = $('.colors', el); + ['', ...COLORS].forEach((c) => { + const b = h(``); + b.onclick = () => { $('input', wrap).value = c; $$('button', wrap).forEach((x) => x.classList.toggle('active', x === b)); }; + wrap.append(b); + }); + return el; +} + +function groupOptions() { + const out = [['', '— Keine Gruppe —']]; + const walk = (parent, depth) => S.vault.groups.filter((g) => (g.parentId || null) === parent).forEach((g) => { out.push([g.id, `${' '.repeat(depth)}${g.label}`]); walk(g.id, depth + 1); }); + walk(null, 0); + return out; +} + +function editHost(hst = {}) { + let proto = hst.protocol || 'ssh'; + const isNew = !hst.id; + const seg = h(`
`); + const sshBox = h('
'); + const rdpBox = h('
'); + const portField = field('Port', 'port', hst.port || '', { type: 'number', placeholder: proto === 'rdp' ? '3389' : '22', cls: 'small' }); + const setProto = (p) => { + proto = p; + $$('button', seg).forEach((b) => b.classList.toggle('active', b.dataset.p === p)); + sshBox.style.display = p === 'ssh' ? '' : 'none'; + rdpBox.style.display = p === 'rdp' ? '' : 'none'; + $('input', portField).placeholder = p === 'rdp' ? '3389' : '22'; + }; + $$('button', seg).forEach((b) => (b.onclick = () => setProto(b.dataset.p))); + + const keys = [['', '— Kein Schlüssel —'], ...S.vault.keys.map((k) => [k.id, k.label])]; + const jumps = [['', '— Direkt —'], ...S.vault.hosts.filter((x) => x.id !== hst.id && x.protocol !== 'rdp').map((x) => [x.id, x.label || x.address])]; + sshBox.append( + field('SSH-Schlüssel', 'keyId', hst.keyId, { type: 'select', options: keys }), + check('SSH-Agent verwenden', 'useAgent', hst.useAgent !== false), + heading('Erweitert'), + field('Jump-Host (ProxyJump)', 'jumpHostId', hst.jumpHostId, { type: 'select', options: jumps }), + field('Startbefehl', 'startupCommand', hst.startupCommand, { placeholder: 'z. B. tmux attach || tmux' }), + field('Umgebungsvariablen', 'env', hst.env, { type: 'textarea', placeholder: 'LANG=de_DE.UTF-8', hint: 'Eine pro Zeile, KEY=VALUE (Server muss AcceptEnv erlauben).' }), + ); + rdpBox.append( + field('Domäne', 'domain', hst.domain, { placeholder: 'optional' }), + row(field('Breite', 'rdpWidth', hst.rdpWidth, { type: 'number', placeholder: 'auto' }), field('Höhe', 'rdpHeight', hst.rdpHeight, { type: 'number', placeholder: 'auto' })), + check('Vollbild', 'rdpFullscreen', hst.rdpFullscreen), + check('Zwischenablage teilen', 'rdpClipboard', hst.rdpClipboard !== false), + check('Audio wiedergeben', 'rdpAudio', hst.rdpAudio !== false), + check('Lokale Laufwerke/Home freigeben', 'rdpDrives', hst.rdpDrives), + ); + + const form = openDrawer(isNew ? 'Neuer Host' : 'Host bearbeiten', [ + seg, + row(field('Adresse', 'address', hst.address, { placeholder: 'IP oder Hostname' }), portField), + field('Bezeichnung', 'label', hst.label, { placeholder: 'Mein Server' }), + field('Gruppe', 'groupId', hst.groupId, { type: 'select', options: groupOptions() }), + field('Tags', 'tags', (hst.tags || []).join(', '), { placeholder: 'prod, web, db' }), + heading('Anmeldung'), + field('Benutzername', 'username', hst.username, { placeholder: 'root' }), + field('Passwort', 'password', hst.password, { type: 'password', placeholder: 'wird verschlüsselt gespeichert' }), + sshBox, rdpBox, + heading('Darstellung'), + colorPicker(hst.color), + ], async () => { + const v = formValues(form); + if (!v.address.trim()) throw new Error('Bitte eine Adresse angeben.'); + const item = { + ...hst, ...v, protocol: proto, + address: v.address.trim(), + port: v.port ? Number(v.port) : undefined, + tags: v.tags.split(',').map((t) => t.trim()).filter(Boolean), + groupId: v.groupId || null, keyId: v.keyId || null, jumpHostId: v.jumpHostId || null, + }; + await call('vault:upsert', 'hosts', item); + reload(); + }, isNew ? null : (() => { const b = h(``); b.title = 'Löschen'; b.onclick = async () => { if (await confirmBox('Host löschen?', `„${hst.label || hst.address}“ wird dauerhaft entfernt.`)) { await call('vault:remove', 'hosts', hst.id); closeDrawer(); reload(); } }; return b; })()); + setProto(proto); + return form; +} + +function editGroup(g = {}) { + const parents = groupOptions().filter(([id]) => id !== g.id); + const form = openDrawer(g.id ? 'Gruppe bearbeiten' : 'Neue Gruppe', [ + field('Name', 'label', g.label, { placeholder: 'Produktion' }), + field('Übergeordnete Gruppe', 'parentId', g.parentId, { type: 'select', options: parents }), + colorPicker(g.color), + ], async () => { + const v = formValues(form); + if (!v.label.trim()) throw new Error('Name fehlt.'); + await call('vault:upsert', 'groups', { ...g, ...v, parentId: v.parentId || null }); + reload(); + }); +} + +// ============================================================ Keychain +function viewKeys(page) { + page.append(toolbar('Schlüssel suchen …', [ + { label: 'Importieren', icon: 'download', run: () => editKey({}, true) }, + { label: 'Generieren', icon: 'plus', cls: 'primary', run: generateKey }, + ])); + const c = h('
'); page.append(c); + const list = S.vault.keys.filter((k) => matches(k.label, k.publicKey)); + if (!list.length) return c.append(emptyState('key', 'Keine Schlüssel', 'Generiere einen neuen Ed25519-Schlüssel oder importiere einen bestehenden.', 'Schlüssel generieren', generateKey)); + c.append(h(`
Schlüssel ${list.length}
`)); + const grid = h('
'); + for (const k of list) { + const type = (k.publicKey || '').split(' ')[0] || 'key'; + const used = S.vault.hosts.filter((x) => x.keyId === k.id).length; + const card = h(`
${ICONS.key}
${esc(k.label)}
${esc(type)} · ${used} Host${used === 1 ? '' : 's'}
`); + card.onclick = (e) => { + const a = e.target.closest('[data-a]')?.dataset.a; + if (a === 'copy') { call('clipboard:write', k.publicKey || ''); toast('Public Key kopiert', 'ok'); } + else editKey(k); + }; + card.oncontextmenu = (e) => ctxMenu(e.clientX, e.clientY, [ + { label: 'Public Key kopieren', icon: 'copy', run: () => { call('clipboard:write', k.publicKey || ''); toast('Public Key kopiert', 'ok'); } }, + { label: 'Bearbeiten', icon: 'edit', run: () => editKey(k) }, + '-', { label: 'Löschen', icon: 'trash', danger: true, run: async () => { if (await confirmBox('Schlüssel löschen?', `„${k.label}“ wird aus dem Vault entfernt.`)) { await call('vault:remove', 'keys', k.id); reload(); } } }, + ]); + grid.append(card); + } + c.append(grid); +} + +async function generateKey() { + const r = await modal({ + title: 'SSH-Schlüssel generieren', + body: `
+
+
`, + buttons: [{ label: 'Abbrechen', value: null, cls: 'ghost' }, { label: 'Generieren', value: 'form', cls: 'primary' }], + }); + if (!r) return; + const k = await call('key:generate', { type: r.type, passphrase: r.passphrase, comment: r.label }); + await call('vault:upsert', 'keys', { label: r.label, privateKey: k.privateKey, publicKey: k.publicKey, passphrase: r.passphrase || undefined }); + await call('clipboard:write', k.publicKey); + toast('Schlüssel erzeugt – Public Key in Zwischenablage kopiert', 'ok'); + reload(); +} + +function editKey(k = {}, pick = false) { + const form = openDrawer(k.id ? 'Schlüssel bearbeiten' : 'Schlüssel importieren', [ + field('Name', 'label', k.label, { placeholder: 'id_ed25519' }), + field('Private Key', 'privateKey', k.privateKey, { type: 'textarea', placeholder: '-----BEGIN OPENSSH PRIVATE KEY-----' }), + field('Passphrase', 'passphrase', k.passphrase, { type: 'password' }), + field('Public Key', 'publicKey', k.publicKey, { type: 'textarea', hint: 'Wird bei Bedarf automatisch aus dem Private Key berechnet.' }), + ], async () => { + const v = formValues(form); + if (!v.privateKey.trim()) throw new Error('Private Key fehlt.'); + const parsed = await call('key:parse', { privateKey: v.privateKey, passphrase: v.passphrase }); + await call('vault:upsert', 'keys', { ...k, ...v, label: v.label || parsed.type, publicKey: v.publicKey.trim() || parsed.publicKey }); + reload(); + }, (() => { const b = h(``); b.onclick = loadFile; return b; })()); + async function loadFile() { + const f = await call('key:pickFile'); + if (!f) return; + $('[name=privateKey]', form).value = f.content; + if (!$('[name=label]', form).value) $('[name=label]', form).value = f.name; + if (f.publicKey) $('[name=publicKey]', form).value = f.publicKey; + } + if (pick) loadFile(); +} + +// ============================================================ Snippets +function viewSnippets(page) { + page.append(toolbar('Snippets suchen …', [{ label: 'Neues Snippet', icon: 'plus', cls: 'primary', run: () => editSnippet() }])); + const c = h('
'); page.append(c); + const list = S.vault.snippets.filter((s) => matches(s.label, s.command)); + if (!list.length) return c.append(emptyState('code', 'Keine Snippets', 'Speichere häufig genutzte Befehle und sende sie mit einem Klick an ein Terminal.', 'Neues Snippet', () => editSnippet())); + const l = h('
'); + for (const s of list) { + const card = h(`
${ICONS.code}
${esc(s.label)}
${esc(s.command.split('\n')[0])}${s.command.includes('\n') ? ' …' : ''}
`); + card.onclick = async (e) => { + const a = e.target.closest('[data-a]')?.dataset.a; + if (a === 'run') runSnippet(s); + else if (a === 'copy') { call('clipboard:write', s.command); toast('Kopiert', 'ok'); } + else if (a === 'del') { if (await confirmBox('Snippet löschen?', s.label)) { await call('vault:remove', 'snippets', s.id); reload(); } } + else editSnippet(s); + }; + l.append(card); + } + c.append(l); +} +function editSnippet(s = {}) { + const form = openDrawer(s.id ? 'Snippet bearbeiten' : 'Neues Snippet', [ + field('Name', 'label', s.label, { placeholder: 'Systemupdate' }), + field('Befehl(e)', 'command', s.command, { type: 'textarea', placeholder: 'sudo pacman -Syu' }), + check('Enter automatisch senden', 'autoRun', s.autoRun !== false), + ], async () => { + const v = formValues(form); + if (!v.command.trim()) throw new Error('Befehl fehlt.'); + await call('vault:upsert', 'snippets', { ...s, ...v, label: v.label || v.command.split('\n')[0].slice(0, 40) }); + reload(); + }); +} +function runSnippet(s, session) { + const t = session || S.tabs.find((x) => x.id === S.active && x.kind === 'ssh') || [...S.tabs].reverse().find((x) => x.kind === 'ssh'); + if (!t) return toast('Kein offenes Terminal', 'error'); + t.sendText(s.command.replace(/\r?\n/g, '\r') + (s.autoRun !== false ? '\r' : '')); + activateTab(t.id); + t.term.focus(); +} + +// ============================================================ Port Forwarding +function viewForwards(page) { + page.append(toolbar('Regeln suchen …', [{ label: 'Neue Regel', icon: 'plus', cls: 'primary', run: () => editForward() }])); + const c = h('
'); page.append(c); + const list = S.vault.forwards.filter((f) => matches(f.label, f.destHost)); + if (!list.length) return c.append(emptyState('fwd', 'Keine Port-Weiterleitungen', 'Lokal (-L), Remote (-R) oder dynamisch als SOCKS5-Proxy (-D).', 'Neue Regel', () => editForward())); + const l = h('
'); + for (const f of list) { + const hst = S.vault.hosts.find((x) => x.id === f.hostId); + const on = S.activeForwards.has(f.id); + const desc = f.type === 'dynamic' ? `SOCKS5 ${f.bindAddress || '127.0.0.1'}:${f.bindPort}` + : f.type === 'remote' ? `Remote ${f.bindPort} → ${f.destHost || '127.0.0.1'}:${f.destPort}` + : `Lokal ${f.bindAddress || '127.0.0.1'}:${f.bindPort} → ${f.destHost}:${f.destPort}`; + const card = h(`
${ICONS.fwd}
${esc(f.label || desc)} ${on ? 'aktiv' : 'inaktiv'}
${esc(desc)} · via ${esc(hst ? hst.label || hst.address : '??')}
+
`); + card.onclick = async (e) => { + const a = e.target.closest('[data-a]')?.dataset.a; + if (a === 'toggle') { + const b = e.target.closest('button'); b.disabled = true; + try { + if (on) await call('fw:stop', f.id); + else { await call('fw:start', f.id); toast('Weiterleitung aktiv', 'ok'); } + } catch {} + reload(); + } else if (a === 'del') { if (await confirmBox('Regel löschen?', f.label || desc)) { await call('fw:stop', f.id); await call('vault:remove', 'forwards', f.id); reload(); } } + else editForward(f); + }; + l.append(card); + } + c.append(l); +} +function editForward(f = {}) { + const hosts = S.vault.hosts.filter((x) => x.protocol !== 'rdp').map((x) => [x.id, x.label || x.address]); + if (!hosts.length) return toast('Lege zuerst einen SSH-Host an.', 'error'); + const form = openDrawer(f.id ? 'Regel bearbeiten' : 'Neue Weiterleitung', [ + field('Bezeichnung', 'label', f.label, { placeholder: 'Postgres Prod' }), + field('Typ', 'type', f.type || 'local', { type: 'select', options: [['local', 'Lokal (-L)'], ['remote', 'Remote (-R)'], ['dynamic', 'Dynamisch / SOCKS5 (-D)']] }), + field('Über Host', 'hostId', f.hostId, { type: 'select', options: hosts }), + row(field('Bind-Adresse', 'bindAddress', f.bindAddress, { placeholder: '127.0.0.1' }), field('Bind-Port', 'bindPort', f.bindPort, { type: 'number', placeholder: '8080', cls: 'small' })), + row(field('Ziel-Host', 'destHost', f.destHost, { placeholder: 'localhost' }), field('Ziel-Port', 'destPort', f.destPort, { type: 'number', placeholder: '5432', cls: 'small' })), + ], async () => { + const v = formValues(form); + if (!v.bindPort) throw new Error('Bind-Port fehlt.'); + if (v.type !== 'dynamic' && !v.destPort) throw new Error('Ziel-Port fehlt.'); + await call('vault:upsert', 'forwards', { ...f, ...v }); + reload(); + }); +} + +// ============================================================ Known Hosts & Verlauf +function viewKnown(page) { + page.append(toolbar('Known Hosts suchen …')); + const c = h('
'); page.append(c); + const entries = Object.entries(S.vault.knownHosts).filter(([id, v]) => matches(id, v.fingerprint)); + if (!entries.length) return c.append(emptyState('shield', 'Keine bekannten Hosts', 'Host-Schlüssel werden bei der ersten Verbindung hier gespeichert.')); + const l = h('
'); + for (const [id, v] of entries) { + const card = h(`
${ICONS.shield}
${esc(id)}
${esc(v.fingerprint)} · ${fmtDate(v.addedAt)}
`); + $('.actions button', card).onclick = async () => { await call('vault:forgetHost', id); reload(); }; + l.append(card); + } + c.append(l); +} +function viewHistory(page) { + page.append(toolbar('Verlauf durchsuchen …')); + const c = h('
'); page.append(c); + const items = S.vault.history.map((x) => ({ ...x, host: S.vault.hosts.find((y) => y.id === x.hostId) })).filter((x) => x.host && matches(x.host.label, x.host.address)); + if (!items.length) return c.append(emptyState('clock', 'Noch keine Verbindungen', 'Zuletzt genutzte Hosts erscheinen hier.')); + const l = h('
'); + for (const it of items) { + const card = h(`
${avatar(it.host)}
${esc(it.host.label || it.host.address)}
${esc(hostSub(it.host))} · ${relTime(it.at)}
`); + card.onclick = () => connectHost(it.host); + card.oncontextmenu = (e) => ctxMenu(e.clientX, e.clientY, hostMenu(it.host)); + l.append(card); + } + c.append(l); +} + +// ============================================================ Einstellungen +async function viewSettings(page) { + const st = settings(); + page.append(h('

Einstellungen

')); + const c = h('
'); page.append(c); + const save = async (patch) => { await call('vault:settings', patch); S.vault.settings = { ...st, ...patch }; S.tabs.forEach((t) => t.applySettings?.()); }; + + const themeCard = h('

Terminal-Farbschema

'); + for (const [id, t] of Object.entries(TERM_THEMES)) { + const p = h(`
${esc(t.name)}
user@host:~$ ls
docs run.sh err
`); + p.onclick = async () => { await save({ terminalTheme: id }); render(); }; + $('.theme-list', themeCard).append(p); + } + + const termCard = h('

Terminal

'); + termCard.append( + row(field('Schriftart', 'fontFamily', st.fontFamily), field('Größe', 'fontSize', st.fontSize, { type: 'number', cls: 'small' })), + row(field('Cursor', 'cursorStyle', st.cursorStyle, { type: 'select', options: [['block', 'Block'], ['bar', 'Balken'], ['underline', 'Unterstrich']] }), field('Scrollback-Zeilen', 'scrollback', st.scrollback, { type: 'number' })), + check('Cursor blinkt', 'cursorBlink', st.cursorBlink), + check('Auswahl automatisch kopieren', 'copyOnSelect', st.copyOnSelect), + field('SSH Keep-Alive (Sekunden, 0 = aus)', 'keepAlive', st.keepAlive, { type: 'number' }), + ); + termCard.addEventListener('change', () => { + const v = formValues(termCard); + save({ ...v, fontSize: Number(v.fontSize) || 14, scrollback: Number(v.scrollback) || 10000, keepAlive: Number(v.keepAlive) || 0 }); + }); + + const rdpCard = h('

RDP

Prüfe …
'); + if (api.platform !== 'win32') { + const f = field('FreeRDP-Client', 'rdpClientLinux', st.rdpClientLinux, { type: 'select', options: [['auto', 'Automatisch'], ['xfreerdp3', 'xfreerdp3 (X11)'], ['sdl-freerdp3', 'sdl-freerdp3 (Wayland/X11)'], ['wlfreerdp3', 'wlfreerdp3 (Wayland)'], ['xfreerdp', 'xfreerdp (FreeRDP 2)']] }); + f.onchange = () => save({ rdpClientLinux: $('select', f).value }).then(render); + rdpCard.append(f); + } + api.call('rdp:detect').then((d) => { + $('.rdp-info', rdpCard).textContent = d.available ? `Verwendeter Client: ${d.client}` : `Kein RDP-Client gefunden. Installation: ${d.hint}`; + if (!d.available) $('.rdp-info', rdpCard).style.color = 'var(--orange)'; + }).catch(() => {}); + + const dataCard = h(`

Daten

${S.vault.encrypted ? 'Der Vault ist mit dem Schlüsselbund des Betriebssystems verschlüsselt (Windows DPAPI / libsecret bzw. KWallet).' : 'Der Vault ist nicht verschlüsselt, da kein Schlüsselbund verfügbar ist. Unter Arch/CachyOS: gnome-keyring oder kwallet installieren.'}

`); + const btns = [ + ['~/.ssh/config importieren', importSshConfig], + ['Backup exportieren', async () => { const p = await call('vault:export'); if (p) toast('Exportiert nach ' + p, 'ok'); }], + ['Backup importieren', async () => { if (await call('vault:import')) { toast('Import abgeschlossen', 'ok'); reload(); } }], + ]; + btns.forEach(([l, fn]) => { const b = h(``); b.onclick = fn; $('.row', dataCard).append(b); }); + + const keysCard = h(`

Tastenkürzel

+ Strg+K / Strg+TQuick Connect / Befehlspalette + Strg+WTab schließen + Strg+TabNächster Tab + Strg+1..9Zu Tab wechseln + Strg+Shift+C/VKopieren / Einfügen im Terminal + Strg+Shift+FIm Terminal suchen + Strg++/−/0Schriftgröße
`); + c.append(themeCard, termCard, rdpCard, dataCard, keysCard); +} + +// ============================================================ Tabs +function addTab(session) { + S.tabs.push(session); + const tab = h(`
${esc(session.title)}
`); + tab.onclick = (e) => { if (e.target.closest('.x')) return closeTab(session.id); activateTab(session.id); }; + tab.onauxclick = (e) => { if (e.button === 1) closeTab(session.id); }; + tab.oncontextmenu = (e) => ctxMenu(e.clientX, e.clientY, [ + ...(session.host ? [{ label: 'Duplizieren', icon: 'dup', run: () => (session.kind === 'sftp' ? openSftp(session.host) : openTerminal(session.host)) }] : []), + ...(session.kind === 'ssh' ? [{ label: 'Neu verbinden', icon: 'refresh', run: () => session.reconnect() }] : []), + { label: 'Umbenennen', icon: 'edit', run: async () => { const n = await promptBox('Tab umbenennen', 'Titel', session.title); if (n) { session.title = n; $('span:nth-child(2)', tab).textContent = n; } } }, + '-', { label: 'Schließen', icon: 'close', run: () => closeTab(session.id) }, + { label: 'Andere schließen', run: () => S.tabs.filter((t) => t.id !== session.id).forEach((t) => closeTab(t.id)) }, + ]); + session.tabEl = tab; + $('#tabs').append(tab); + $('#sessions').append(session.el); + activateTab(session.id); +} +function setTabState(session, state) { const d = $('.dot', session.tabEl); if (d) d.className = 'dot ' + state; } + +function activateTab(id) { + S.active = id; + $$('#tabs .tab').forEach((t) => t.classList.toggle('active', t.dataset.tab === id)); + $('#homeView').classList.toggle('active', id === 'home'); + document.body.classList.toggle('in-session', id !== 'home'); + S.tabs.forEach((t) => { + t.el.classList.toggle('active', t.id === id); + if (t.id === id) requestAnimationFrame(() => t.onShow?.()); + }); + $$('.tab.active')[0]?.scrollIntoView({ block: 'nearest', inline: 'nearest' }); +} +function closeTab(id) { + const i = S.tabs.findIndex((t) => t.id === id); + if (i < 0) return; + const t = S.tabs[i]; + t.dispose(); + t.el.remove(); t.tabEl.remove(); + S.tabs.splice(i, 1); + if (S.active === id) activateTab(S.tabs[i]?.id || S.tabs[i - 1]?.id || 'home'); +} +$('.tab.home').onclick = () => activateTab('home'); + +// ============================================================ Terminal-Session +class TerminalSession { + constructor(host) { + this.kind = 'ssh'; + this.id = uid(); + this.host = host; + this.title = host.label || host.address; + this.el = h(`
+
${avatar(host, 22).replace('SSH', '')}${esc(hostSub(host))}
+ + + +
+
+
+
Snippets
+
`); + this.overlay = null; + this.alive = false; + this.initTerm(); + this.bindUi(); + this.unsub = api.on('ssh:event', (sid, type, payload) => { if (sid === this.id) this.onEvent(type, payload); }); + addTab(this); + this.connect(); + } + + initTerm() { + const st = settings(); + this.term = new Terminal({ + fontFamily: st.fontFamily, fontSize: st.fontSize, cursorStyle: st.cursorStyle, cursorBlink: st.cursorBlink, + scrollback: st.scrollback, theme: TERM_THEMES[st.terminalTheme] || TERM_THEMES.mrterm, allowProposedApi: true, macOptionIsMeta: true, + }); + this.fit = new FitAddon.FitAddon(); + this.search = new SearchAddon.SearchAddon(); + this.term.loadAddon(this.fit); + this.term.loadAddon(this.search); + this.term.loadAddon(new WebLinksAddon.WebLinksAddon((_e, url) => api.call('shell:open', url))); + this.term.open($('.term', this.el)); + this.term.onData((d) => { if (this.alive) api.ssh.write(this.id, d); else if (d === '\r' && this.ended) this.reconnect(); }); + this.term.onResize(({ cols, rows }) => { if (this.alive) api.ssh.resize(this.id, cols, rows); }); + this.term.onSelectionChange(() => { if (settings().copyOnSelect && this.term.hasSelection()) api.call('clipboard:write', this.term.getSelection()); }); + this.term.attachCustomKeyEventHandler((e) => { + if (e.type !== 'keydown') return true; + if (e.ctrlKey && e.shiftKey && e.code === 'KeyC') { if (this.term.hasSelection()) api.call('clipboard:write', this.term.getSelection()); return false; } + if (e.ctrlKey && e.shiftKey && e.code === 'KeyV') { this.paste(); return false; } + if (e.ctrlKey && e.shiftKey && e.code === 'KeyF') { this.toggleFind(true); return false; } + if (e.ctrlKey && !e.shiftKey && ['KeyK', 'KeyT', 'KeyW', 'Tab'].includes(e.code)) return false; + if (e.ctrlKey && /^Digit[1-9]$/.test(e.code)) return false; + if (e.ctrlKey && (e.key === '+' || e.key === '=' || e.key === '-' || e.key === '0')) { this.zoom(e.key); return false; } + return true; + }); + $('.term', this.el).addEventListener('contextmenu', (e) => { + e.preventDefault(); + if (this.term.hasSelection()) { api.call('clipboard:write', this.term.getSelection()); this.term.clearSelection(); toast('Kopiert'); } + else this.paste(); + }); + this.ro = new ResizeObserver(() => { if (this.el.classList.contains('active')) this.doFit(); }); + this.ro.observe($('.term', this.el)); + } + + bindUi() { + this.el.addEventListener('click', (e) => { + const a = e.target.closest('[data-a]')?.dataset.a; + if (a === 'sftp') openSftp(this.host); + if (a === 'snip') { $('.snip-panel', this.el).classList.toggle('open'); this.renderSnips(); this.doFit(); } + if (a === 'snipnew') editSnippet(); + if (a === 'find') this.toggleFind(); + const f = e.target.closest('[data-f]')?.dataset.f; + const inp = $('.findbar input', this.el); + if (f === 'next') this.search.findNext(inp.value); + if (f === 'prev') this.search.findPrevious(inp.value); + if (f === 'close') this.toggleFind(false); + }); + $('.findbar input', this.el).addEventListener('keydown', (e) => { + if (e.key === 'Enter') (e.shiftKey ? this.search.findPrevious : this.search.findNext).call(this.search, e.target.value); + if (e.key === 'Escape') this.toggleFind(false); + }); + } + + renderSnips() { + const box = $('.snip-panel .items', this.el); + box.innerHTML = ''; + if (!S.vault.snippets.length) box.append(h('
Noch keine Snippets.
')); + S.vault.snippets.forEach((s) => { + const it = h(`
${esc(s.label)}
${esc(s.command)}
`); + it.onclick = () => runSnippet(s, this); + box.append(it); + }); + } + + toggleFind(open) { + const fb = $('.findbar', this.el); + const o = open ?? !fb.classList.contains('open'); + fb.classList.toggle('open', o); + if (o) { $('input', fb).focus(); $('input', fb).select(); } else { this.search.clearDecorations?.(); this.term.focus(); } + } + + async paste() { const t = await api.call('clipboard:read'); if (t) this.term.paste(t); } + sendText(t) { if (this.alive) api.ssh.write(this.id, t); } + zoom(k) { + const s = this.term.options.fontSize; + this.term.options.fontSize = k === '0' ? settings().fontSize : Math.max(8, Math.min(32, s + (k === '-' ? -1 : 1))); + this.doFit(); + } + doFit() { try { this.fit.fit(); } catch {} } + onShow() { this.doFit(); this.term.focus(); } + applySettings() { + const st = settings(); + Object.assign(this.term.options, { fontFamily: st.fontFamily, fontSize: st.fontSize, cursorStyle: st.cursorStyle, cursorBlink: st.cursorBlink, scrollback: st.scrollback, theme: TERM_THEMES[st.terminalTheme] || TERM_THEMES.mrterm }); + this.doFit(); + } + + showOverlay(msg, err) { + if (!this.overlay) { this.overlay = h('
'); $('.term-wrap', this.el).append(this.overlay); } + this.overlay.innerHTML = err + ? `
${esc(msg)}
` + : `
${esc(msg)}
`; + this.overlay.onclick = (e) => { + const o = e.target.closest('[data-o]')?.dataset.o; + if (o === 'retry') this.reconnect(); + if (o === 'cancel') closeTab(this.id); + if (o === 'edit' && this.host.id) { activateTab('home'); editHost(S.vault.hosts.find((x) => x.id === this.host.id) || this.host); } + }; + } + hideOverlay() { this.overlay?.remove(); this.overlay = null; } + + async connect() { + this.ended = false; + setTabState(this, 'wait'); + this.showOverlay(`Verbinde mit ${this.host.address} …`); + this.doFit(); + try { + await api.call('ssh:open', this.id, this.host.id || this.host, { cols: this.term.cols, rows: this.term.rows }); + this.alive = true; + this.hideOverlay(); + setTabState(this, 'on'); + this.doFit(); + api.ssh.resize(this.id, this.term.cols, this.term.rows); + if (S.active === this.id) this.term.focus(); + } catch (e) { + setTabState(this, 'err'); + this.showOverlay(e.message, true); + } + } + reconnect() { + api.ssh.close(this.id); + this.alive = false; + this.term.write('\r\n\x1b[90m— Neu verbinden —\x1b[0m\r\n'); + this.connect(); + } + onEvent(type, payload) { + if (type === 'data') this.term.write(payload); + else if (type === 'status' && this.overlay) $('.msg', this.overlay).textContent = payload; + else if (type === 'error') this.term.write(`\r\n\x1b[31m${payload}\x1b[0m\r\n`); + else if (type === 'closed') { + if (!this.alive) return; + this.alive = false; this.ended = true; + setTabState(this, ''); + this.term.write('\r\n\x1b[90mVerbindung beendet. [Enter] zum erneuten Verbinden.\x1b[0m\r\n'); + } + } + dispose() { this.unsub(); this.ro.disconnect(); api.ssh.close(this.id); this.term.dispose(); } +} +function openTerminal(host) { return new TerminalSession(host); } + +// ============================================================ SFTP +function viewSftpPicker(page) { + page.append(toolbar('Host für SFTP wählen …')); + const c = h('
'); page.append(c); + const list = S.vault.hosts.filter((x) => x.protocol !== 'rdp' && matches(x.label, x.address, x.username)); + if (!list.length) return c.append(emptyState('folder', 'Keine SSH-Hosts', 'SFTP benötigt einen SSH-Host.', 'Neuer Host', () => editHost({}))); + c.append(h('
Host für SFTP-Browser öffnen
')); + const grid = h('
'); + list.forEach((hst) => { + const card = h(`
${avatar(hst)}
${esc(hst.label || hst.address)}
${esc(hostSub(hst))}
`); + card.onclick = () => openSftp(hst); + grid.append(card); + }); + c.append(grid); +} + +class FilePane { + constructor(sftp, side) { + this.sftp = sftp; this.side = side; this.cwd = ''; this.files = []; this.sel = new Set(); this.sort = { k: 'name', dir: 1 }; + this.el = h(`
${side === 'local' ? ICONS.screen + 'Lokal' : ICONS.folder + esc(sftp.host.label || sftp.host.address)} + ${side === 'local' && api.platform === 'win32' ? '' : ''} + + + + + +
+
`); + this.el.addEventListener('click', (e) => { + const p = e.target.closest('[data-p]')?.dataset.p; + if (p === 'up') this.up(); + if (p === 'home') this.go(this.home); + if (p === 'refresh') this.go(this.cwd); + if (p === 'mkdir') this.mkdir(); + if (p === 'xfer') this.transferSelected(); + }); + $('.path', this.el).addEventListener('keydown', (e) => { if (e.key === 'Enter') this.go(e.target.value); }); + const files = $('.files', this.el); + files.addEventListener('contextmenu', (e) => { if (!e.target.closest('tr[data-i]')) { e.preventDefault(); ctxMenu(e.clientX, e.clientY, [{ label: 'Neuer Ordner', icon: 'newdir', run: () => this.mkdir() }, { label: 'Aktualisieren', icon: 'refresh', run: () => this.go(this.cwd) }]); } }); + // Drag & Drop zwischen den Panes + this.el.addEventListener('dragover', (e) => { if (S.drag && S.drag.pane !== this) { e.preventDefault(); this.el.classList.add('dragover'); } }); + this.el.addEventListener('dragleave', (e) => { if (!this.el.contains(e.relatedTarget)) this.el.classList.remove('dragover'); }); + this.el.addEventListener('drop', (e) => { e.preventDefault(); this.el.classList.remove('dragover'); if (S.drag && S.drag.pane !== this) S.drag.pane.transferSelected(); S.drag = null; }); + } + get other() { return this.side === 'local' ? this.sftp.remote : this.sftp.local; } + async join(...p) { return this.side === 'local' ? api.call('local:join', ...p) : p.join('/').replace(/\/+/g, '/'); } + async parent(p) { return this.side === 'local' ? api.call('local:parent', p) : (p.replace(/\/[^/]+\/?$/, '') || '/'); } + async list(dir) { return this.side === 'local' ? api.call('local:list', dir) : api.call('sftp:list', this.sftp.id, dir); } + async init(home) { + this.home = home; + if (this.side === 'local' && api.platform === 'win32') { + const sel = $('.roots', this.el); + (await api.call('local:roots')).forEach((r) => sel.append(h(``))); + sel.onchange = () => this.go(sel.value); + } + return this.go(home); + } + async up() { this.go(await this.parent(this.cwd)); } + async go(dir) { + try { + const files = await this.list(dir); + this.cwd = dir; this.files = files; this.sel.clear(); + $('.path', this.el).value = dir; + const roots = $('.roots', this.el); if (roots) roots.value = dir.slice(0, 3).toUpperCase(); + this.draw(); + } catch (e) { toast(e.message, 'error'); } + } + sorted() { + const { k, dir } = this.sort; + return [...this.files].filter((f) => f.name !== '.' && f.name !== '..').sort((a, b) => (b.isDir - a.isDir) || (k === 'name' ? a.name.localeCompare(b.name, undefined, { numeric: true }) : (a[k] - b[k])) * dir); + } + draw() { + const box = $('.files', this.el); + const arrow = (k) => (this.sort.k === k ? (this.sort.dir > 0 ? ' ▲' : ' ▼') : ''); + const rows = this.view = this.sorted(); + box.innerHTML = ` + + ${rows.map((f, i) => ``).join('')}
Name${arrow('name')}Größe${arrow('size')}Geändert${arrow('mtime')}
${ICONS.folder}..
${f.isDir ? ICONS.folder : ICONS.file}${esc(f.name)}${f.isDir ? '' : fmtSize(f.size)}${fmtDate(f.mtime)}
`; + $$('th', box).forEach((th) => (th.onclick = () => { const k = th.dataset.s; this.sort = { k, dir: this.sort.k === k ? -this.sort.dir : 1 }; this.draw(); })); + $('tr[data-up]', box).ondblclick = () => this.up(); + $$('tr[data-i]', box).forEach((tr) => { + const f = rows[Number(tr.dataset.i)]; + tr.onclick = (e) => { + if (e.ctrlKey) this.sel.has(f.name) ? this.sel.delete(f.name) : this.sel.add(f.name); + else if (e.shiftKey && this.lastIdx != null) { const [a, b] = [this.lastIdx, Number(tr.dataset.i)].sort((x, y) => x - y); rows.slice(a, b + 1).forEach((x) => this.sel.add(x.name)); } + else { this.sel.clear(); this.sel.add(f.name); } + this.lastIdx = Number(tr.dataset.i); + $$('tr[data-i]', box).forEach((r) => r.classList.toggle('sel', this.sel.has(rows[Number(r.dataset.i)].name))); + }; + tr.ondblclick = async () => { + if (f.isDir) this.go(await this.join(this.cwd, f.name)); + else if (this.side === 'local') api.call('local:op', 'open', await this.join(this.cwd, f.name)); + else { this.sel = new Set([f.name]); this.transferSelected(); } + }; + tr.ondragstart = (e) => { if (!this.sel.has(f.name)) { this.sel = new Set([f.name]); } S.drag = { pane: this }; e.dataTransfer.effectAllowed = 'copy'; }; + tr.oncontextmenu = (e) => { + e.preventDefault(); + if (!this.sel.has(f.name)) { this.sel = new Set([f.name]); this.draw(); } + ctxMenu(e.clientX, e.clientY, [ + { label: this.side === 'local' ? 'Hochladen' : 'Herunterladen', icon: this.side === 'local' ? 'upload' : 'download', run: () => this.transferSelected() }, + ...(f.isDir ? [{ label: 'Öffnen', icon: 'folder', run: async () => this.go(await this.join(this.cwd, f.name)) }] : []), + { label: 'Umbenennen', icon: 'edit', run: () => this.rename(f) }, + { label: 'Pfad kopieren', icon: 'copy', run: async () => api.call('clipboard:write', await this.join(this.cwd, f.name)) }, + ...(this.side === 'remote' && !f.isDir ? [{ label: 'Rechte (chmod)…', icon: 'lock', run: () => this.chmod(f) }] : []), + '-', { label: this.side === 'local' ? 'In Papierkorb' : 'Löschen', icon: 'trash', danger: true, run: () => this.remove() }, + ]); + }; + }); + } + async mkdir() { + const n = await promptBox('Neuer Ordner', 'Name'); if (!n) return; + const p = await this.join(this.cwd, n); + try { this.side === 'local' ? await api.call('local:op', 'mkdir', p) : await api.call('sftp:op', this.sftp.id, 'mkdir', p); this.go(this.cwd); } catch (e) { toast(e.message, 'error'); } + } + async rename(f) { + const n = await promptBox('Umbenennen', 'Neuer Name', f.name); if (!n || n === f.name) return; + const a = await this.join(this.cwd, f.name), b = await this.join(this.cwd, n); + try { this.side === 'local' ? await api.call('local:op', 'rename', a, b) : await api.call('sftp:op', this.sftp.id, 'rename', a, b); this.go(this.cwd); } catch (e) { toast(e.message, 'error'); } + } + async chmod(f) { + const m = await promptBox('Rechte ändern', 'Oktal (z. B. 644)', (f.mode & 0o777).toString(8)); if (!m) return; + try { await api.call('sftp:op', this.sftp.id, 'chmod', await this.join(this.cwd, f.name), parseInt(m, 8)); this.go(this.cwd); } catch (e) { toast(e.message, 'error'); } + } + async remove() { + const names = [...this.sel]; if (!names.length) return; + if (!(await confirmBox('Löschen?', `${names.length} Element(e) ${this.side === 'local' ? 'in den Papierkorb verschieben' : 'endgültig vom Server löschen'}: ${names.slice(0, 5).join(', ')}${names.length > 5 ? ' …' : ''}`))) return; + for (const n of names) { + const f = this.files.find((x) => x.name === n); + const p = await this.join(this.cwd, n); + try { + if (this.side === 'local') await api.call('local:op', 'delete', p); + else await api.call('sftp:op', this.sftp.id, f.isDir ? 'rmdir' : 'unlink', p); + } catch (e) { toast(`${n}: ${e.message}`, 'error'); } + } + this.go(this.cwd); + } + async transferSelected() { + const names = [...this.sel]; + if (!names.length) return toast('Nichts ausgewählt'); + const target = this.other; + for (const n of names) { + const src = await this.join(this.cwd, n), dst = await target.join(target.cwd, n); + const dir = this.side === 'local' ? 'upload' : 'download'; + await this.sftp.transfer(dir, dir === 'upload' ? src : dst, dir === 'upload' ? dst : src, n); + } + target.go(target.cwd); + } +} + +class SftpSession { + constructor(host) { + this.kind = 'sftp'; this.id = uid(); this.host = host; + this.title = `SFTP · ${host.label || host.address}`; + this.el = h(`
`); + this.local = new FilePane(this, 'local'); + this.remote = new FilePane(this, 'remote'); + const wrap = $('.sftp', this.el); + wrap.append(this.local.el, h(`
Verbinde mit ${esc(host.address)} …
`)); + this.unsub = api.on('sftp:progress', (tid, done, total) => this.progress(tid, done, total)); + this.xfers = new Map(); + addTab(this); + this.open(); + } + async open() { + this.local.init(await api.call('local:home')); + try { + const { home } = await api.call('sftp:open', this.id, this.host.id || this.host); + $('.sftp', this.el).lastElementChild.replaceWith(this.remote.el); + await this.remote.init(home); + setTabState(this, 'on'); + } catch (e) { + setTabState(this, 'err'); + const pe = $('.pane-empty', this.el); + pe.innerHTML = `
${esc(e.message)}
`; + const b = h(``); + b.onclick = () => { closeTab(this.id); openSftp(this.host); }; + pe.append(b); + } + } + async transfer(dir, localPath, remotePath, name) { + const tid = uid(); + const row = h(`
${dir === 'upload' ? ICONS.upload : ICONS.download}${esc(name)}0%
`); + $('.transfers', this.el).prepend(row); + this.xfers.set(tid, row); + try { + await api.call('sftp:transfer', this.id, dir, localPath, remotePath, tid); + row.classList.add('done'); $('.bar i', row).style.width = '100%'; $('.pct', row).textContent = 'fertig'; + setTimeout(() => row.remove(), 5000); + } catch (e) { + row.classList.add('fail'); $('.pct', row).textContent = 'Fehler'; row.title = e.message; toast(`${name}: ${e.message}`, 'error'); + setTimeout(() => row.remove(), 10000); + } + } + progress(tid, done, total) { + const row = this.xfers.get(tid); if (!row) return; + const p = total ? Math.round((done / total) * 100) : 0; + $('.bar i', row).style.width = p + '%'; + $('.pct', row).textContent = `${p}% · ${fmtSize(done)} / ${fmtSize(total)}`; + } + dispose() { this.unsub(); api.call('sftp:close', this.id).catch(() => {}); } +} +function openSftp(host) { return new SftpSession(host); } + +// ============================================================ Command Palette / Quick Connect +function openPalette() { + if ($('.palette')) return; + const bg = h(``); + const inp = $('input', bg), res = $('.res', bg); + let items = [], idx = 0; + const close = () => bg.remove(); + const draw = () => { + const q = inp.value.trim().toLowerCase(); + items = []; + const quick = parseQuick(inp.value); + if (q && quick) items.push({ grp: 'Quick Connect', icon: avatar({ ...quick, label: quick.address }), label: `${quick.protocol.toUpperCase()} → ${inp.value.trim()}`, run: () => connectHost(quick) }); + const hosts = S.vault.hosts.filter((x) => !q || [x.label, x.address, x.username, ...(x.tags || [])].some((f) => String(f || '').toLowerCase().includes(q))); + const recent = S.vault.history.map((x) => x.hostId); + hosts.sort((a, b) => ((recent.indexOf(a.id) + 1 || 999) - (recent.indexOf(b.id) + 1 || 999))); + hosts.slice(0, 12).forEach((x) => { + items.push({ grp: 'Hosts', icon: avatar(x), label: x.label || x.address, sub: hostSub(x), run: () => connectHost(x) }); + if (x.protocol !== 'rdp' && q) items.push({ grp: 'Hosts', icon: `
${ICONS.folder}
`, label: `SFTP: ${x.label || x.address}`, run: () => openSftp(x) }); + }); + S.vault.snippets.filter((s) => q && s.label.toLowerCase().includes(q)).slice(0, 5).forEach((s) => items.push({ grp: 'Snippets', icon: `
${ICONS.code}
`, label: s.label, sub: s.command, run: () => runSnippet(s) })); + [['Neuer Host', () => { activateTab('home'); editHost({}); }], ['Schlüssel generieren', generateKey], ['Einstellungen', () => $('[data-view=settings]').click()]] + .filter(([l]) => !q || l.toLowerCase().includes(q)).forEach(([l, run]) => items.push({ grp: 'Aktionen', icon: `
${ICONS.plus}
`, label: l, run })); + idx = Math.min(idx, Math.max(0, items.length - 1)); + let last = ''; + res.innerHTML = ''; + items.forEach((it, i) => { + if (it.grp !== last) { res.append(h(`
${esc(it.grp)}
`)); last = it.grp; } + const el = h(`
${it.icon}
${esc(it.label)}
${it.sub ? `
${esc(it.sub.split('\n')[0])}
` : ''}
${i === idx ? '↵' : ''}
`); + el.onclick = () => { close(); it.run(); }; + el.onmousemove = () => { if (idx !== i) { idx = i; draw(); } }; + res.append(el); + }); + $('.item.active', res)?.scrollIntoView({ block: 'nearest' }); + }; + inp.oninput = () => { idx = 0; draw(); }; + inp.onkeydown = (e) => { + if (e.key === 'ArrowDown') { idx = Math.min(items.length - 1, idx + 1); draw(); e.preventDefault(); } + if (e.key === 'ArrowUp') { idx = Math.max(0, idx - 1); draw(); e.preventDefault(); } + if (e.key === 'Enter' && items[idx]) { close(); items[idx].run(); } + if (e.key === 'Escape') close(); + }; + bg.onmousedown = (e) => { if (e.target === bg) close(); }; + $('#modalRoot').append(bg); + draw(); + inp.focus(); +} + +// ============================================================ Globale Events +$('#newTabBtn').onclick = openPalette; +$('#winMin').onclick = api.win.min; +$('#winMax').onclick = api.win.max; +$('#winClose').onclick = api.win.close; +$('#titlebar').addEventListener('dblclick', (e) => { if (e.target.classList.contains('drag-fill') || e.target.id === 'titlebar') api.win.max(); }); +if (api.platform === 'darwin') $('.win-ctrls').style.display = 'none'; + +document.addEventListener('keydown', (e) => { + if (!e.ctrlKey) return; + if (!e.shiftKey && (e.code === 'KeyK' || e.code === 'KeyT')) { e.preventDefault(); openPalette(); } + else if (!e.shiftKey && e.code === 'KeyW') { e.preventDefault(); if (S.active !== 'home') closeTab(S.active); } + else if (e.code === 'Tab') { + e.preventDefault(); + const ids = ['home', ...S.tabs.map((t) => t.id)]; + const i = ids.indexOf(S.active); + activateTab(ids[(i + (e.shiftKey ? -1 : 1) + ids.length) % ids.length]); + } else if (/^Digit[1-9]$/.test(e.code)) { + e.preventDefault(); + const ids = ['home', ...S.tabs.map((t) => t.id)]; + const n = Number(e.code.slice(5)); + activateTab(n === 9 ? ids[ids.length - 1] : ids[n - 1] || ids[ids.length - 1]); + } +}); + +api.on('secret:request', async (req) => { + const r = await modal({ + title: req.title || 'Anmeldung', + text: req.host, + body: `
`, + buttons: [{ label: 'Abbrechen', value: null, cls: 'ghost' }, { label: 'Anmelden', value: 'form', cls: 'primary' }], + }); + api.replySecret(req.reqId, r ? r.v : null); +}); +api.on('fw:event', () => reload()); +api.on('toast', (m, t) => toast(m, t)); +api.on('win:state', (max) => { $('#winMax').innerHTML = max ? '' : ''; }); + +reload(); diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 0000000..b9e091f --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,61 @@ + + + + + + MrTerm + + + + +
+
+
MrTerm
+
+ +
+ +
+
+ + + +
+
+ +
+ + +
+
+
+
+ + +
+
+ +
+
+ + + + + + + + + diff --git a/src/renderer/styles.css b/src/renderer/styles.css new file mode 100644 index 0000000..76898e0 --- /dev/null +++ b/src/renderer/styles.css @@ -0,0 +1,252 @@ +:root { + --bg: #13151c; + --bg-2: #181b24; + --panel: #1c1f2a; + --card: #212533; + --card-hover: #282d3d; + --border: #2b3040; + --text: #e6e8ef; + --muted: #8b91a5; + --faint: #5d6377; + --accent: #6e7bff; + --accent-2: #8b95ff; + --green: #3ecf8e; + --red: #ff5f6d; + --orange: #ffb454; + --radius: 10px; + --titlebar: 42px; + font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, 'Noto Sans', sans-serif; + font-size: 13px; + color: var(--text); +} +* { box-sizing: border-box; } +html, body { margin: 0; height: 100%; background: var(--bg); overflow: hidden; } +button { font: inherit; color: inherit; cursor: pointer; } +input, select, textarea { font: inherit; color: var(--text); } +svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; flex: none; } +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-thumb { background: #2f3445; border-radius: 10px; border: 2px solid transparent; background-clip: padding-box; } +::-webkit-scrollbar-track { background: transparent; } + +#app { display: flex; flex-direction: column; height: 100vh; } + +/* ---------- Titlebar & Tabs ---------- */ +#titlebar { height: var(--titlebar); display: flex; align-items: center; background: #0f1117; -webkit-app-region: drag; border-bottom: 1px solid #1f2330; flex: none; } +.brand { width: 212px; padding-left: 16px; font-weight: 700; letter-spacing: .3px; display: flex; align-items: center; gap: 10px; flex: none; } +.logo { background: linear-gradient(135deg, var(--accent), #b36bff); color: white; font-family: monospace; font-size: 11px; padding: 3px 6px; border-radius: 6px; } +#tabs { display: flex; gap: 4px; height: 100%; align-items: flex-end; overflow-x: auto; min-width: 0; -webkit-app-region: no-drag; } +#tabs::-webkit-scrollbar { height: 0; } +.tab { -webkit-app-region: no-drag; display: flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px 0 12px; border: none; background: transparent; border-radius: 8px 8px 0 0; color: var(--muted); max-width: 220px; min-width: 110px; white-space: nowrap; position: relative; } +.tab.home { min-width: 0; } +.tab span { overflow: hidden; text-overflow: ellipsis; flex: 1; text-align: left; } +.tab:hover { background: #1a1d27; color: var(--text); } +.tab.active { background: var(--bg); color: var(--text); } +.tab .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--faint); flex: none; } +.tab .dot.on { background: var(--green); box-shadow: 0 0 6px var(--green); } +.tab .dot.err { background: var(--red); } +.tab .dot.wait { background: var(--orange); animation: pulse 1s infinite; } +.tab .x { opacity: 0; border: none; background: none; padding: 2px; border-radius: 4px; color: var(--muted); display: flex; } +.tab:hover .x, .tab.active .x { opacity: 1; } +.tab .x:hover { background: #ffffff18; color: var(--text); } +.tab .x svg { width: 12px; height: 12px; } +@keyframes pulse { 50% { opacity: .35; } } +.icon-btn { -webkit-app-region: no-drag; border: none; background: transparent; color: var(--muted); width: 30px; height: 30px; border-radius: 8px; font-size: 18px; margin-left: 4px; flex: none; } +.icon-btn:hover { background: #1f2330; color: var(--text); } +.drag-fill { flex: 1; min-width: 40px; height: 100%; } +.win-ctrls { display: flex; height: 100%; -webkit-app-region: no-drag; } +.win-ctrls button { width: 46px; border: none; background: transparent; color: var(--muted); display: flex; align-items: center; justify-content: center; } +.win-ctrls button svg { width: 12px; height: 12px; stroke-width: 1.2; } +.win-ctrls button:hover { background: #ffffff12; color: var(--text); } +.win-ctrls button.close:hover { background: #e81123; color: white; } + +/* ---------- Layout ---------- */ +#body { flex: 1; display: flex; min-height: 0; position: relative; } +#sidebar { width: 212px; background: #0f1117; padding: 10px 10px 12px; display: flex; flex-direction: column; gap: 2px; border-right: 1px solid #1f2330; flex: none; } +#sidebar .spacer { flex: 1; } +.nav { display: flex; align-items: center; gap: 12px; border: none; background: none; padding: 9px 12px; border-radius: 8px; color: var(--muted); text-align: left; font-weight: 500; } +.nav:hover { background: #1a1d27; color: var(--text); } +.nav.active { background: #1f2233; color: var(--text); } +.nav.active svg { color: var(--accent-2); } +.vault-badge { font-size: 11px; color: var(--faint); padding: 8px 12px; display: flex; gap: 6px; align-items: center; } +.vault-badge svg { width: 13px; height: 13px; } +body.in-session #sidebar { display: none; } +#main { flex: 1; min-width: 0; position: relative; background: var(--bg); } +.page { position: absolute; inset: 0; overflow: auto; display: none; } +.page.active { display: block; } +#sessions > .session { position: absolute; inset: 0; display: none; flex-direction: column; } +#sessions > .session.active { display: flex; } + +/* ---------- Header / Toolbar ---------- */ +.toolbar { position: sticky; top: 0; z-index: 5; background: var(--bg); padding: 18px 28px 14px; display: flex; gap: 10px; align-items: center; border-bottom: 1px solid transparent; } +.search { flex: 1; display: flex; align-items: center; gap: 10px; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 0 14px; height: 40px; } +.search:focus-within { border-color: var(--accent); } +.search input { flex: 1; background: none; border: none; outline: none; height: 100%; font-size: 14px; } +.search svg { color: var(--faint); } +.btn { display: inline-flex; align-items: center; gap: 8px; height: 36px; padding: 0 14px; border-radius: 8px; border: 1px solid var(--border); background: var(--panel); font-weight: 600; white-space: nowrap; } +.btn:hover { background: var(--card-hover); } +.btn.primary { background: var(--accent); border-color: var(--accent); color: white; } +.btn.primary:hover { background: var(--accent-2); } +.btn.danger { color: var(--red); } +.btn.danger:hover { background: #ff5f6d1a; } +.btn.ghost { background: transparent; border-color: transparent; color: var(--muted); } +.btn.ghost:hover { background: var(--card); color: var(--text); } +.btn.sm { height: 28px; padding: 0 10px; font-size: 12px; } +.btn svg { width: 16px; height: 16px; } +.content { padding: 4px 28px 40px; } +.section-title { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .6px; margin: 18px 0 10px; } +.section-title .count { background: var(--card); padding: 1px 7px; border-radius: 10px; font-size: 11px; } +.chips { display: flex; gap: 6px; flex-wrap: wrap; } +.chip { border: 1px solid var(--border); background: transparent; color: var(--muted); padding: 4px 10px; border-radius: 20px; font-size: 12px; } +.chip.active, .chip:hover { background: #6e7bff22; border-color: var(--accent); color: var(--text); } + +/* ---------- Cards ---------- */ +.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; } +.card { background: var(--card); border: 1px solid transparent; border-radius: var(--radius); padding: 14px; display: flex; gap: 12px; align-items: center; cursor: pointer; position: relative; transition: background .12s, border-color .12s, transform .12s; user-select: none; min-width: 0; } +.card:hover { background: var(--card-hover); border-color: var(--border); } +.card.selected { border-color: var(--accent); } +.card .meta { min-width: 0; flex: 1; } +.card .title { font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.card .sub { color: var(--muted); font-size: 12px; margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.card .actions { display: flex; gap: 2px; opacity: 0; transition: opacity .12s; } +.card:hover .actions { opacity: 1; } +.card .actions button { border: none; background: none; color: var(--muted); padding: 6px; border-radius: 6px; display: flex; } +.card .actions button:hover { background: #ffffff14; color: var(--text); } +.card .actions svg { width: 16px; height: 16px; } +.avatar { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 15px; color: white; flex: none; position: relative; } +.avatar svg { width: 20px; height: 20px; } +.avatar .proto { position: absolute; right: -4px; bottom: -4px; font-size: 8px; background: #0f1117; color: var(--muted); padding: 1px 3px; border-radius: 4px; border: 1px solid var(--border); font-weight: 700; } +.group-card .avatar { background: #2a2f45; color: var(--accent-2); } +.tags { display: flex; gap: 4px; margin-top: 6px; flex-wrap: wrap; } +.tag { font-size: 10px; background: #ffffff0f; color: var(--muted); padding: 1px 6px; border-radius: 4px; } +.empty { text-align: center; color: var(--muted); padding: 70px 20px; } +.empty .big { width: 64px; height: 64px; margin: 0 auto 16px; border-radius: 18px; background: var(--card); display: flex; align-items: center; justify-content: center; color: var(--accent-2); } +.empty .big svg { width: 30px; height: 30px; } +.empty h3 { color: var(--text); margin: 0 0 6px; font-size: 16px; } +.empty p { margin: 0 0 18px; } +.breadcrumb { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 13px; margin-top: 8px; } +.breadcrumb a { color: var(--accent-2); cursor: pointer; } +.list { display: flex; flex-direction: column; gap: 6px; } +.list .card { padding: 11px 14px; } +.mono { font-family: 'Cascadia Code', 'JetBrains Mono', Consolas, monospace; font-size: 12px; } +.status-pill { font-size: 11px; padding: 2px 8px; border-radius: 10px; background: #ffffff10; color: var(--muted); } +.status-pill.on { background: #3ecf8e22; color: var(--green); } + +/* ---------- Drawer (Editor) ---------- */ +.drawer { position: absolute; top: 0; right: 0; bottom: 0; width: 400px; background: var(--bg-2); border-left: 1px solid var(--border); transform: translateX(100%); transition: transform .18s ease; z-index: 20; display: flex; flex-direction: column; box-shadow: -20px 0 40px #0006; } +.drawer.open { transform: translateX(0); } +.drawer header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid var(--border); } +.drawer header h2 { margin: 0; font-size: 15px; } +.drawer .form { flex: 1; overflow: auto; padding: 16px 20px 20px; } +.drawer footer { display: flex; gap: 8px; padding: 14px 20px; border-top: 1px solid var(--border); } +.drawer footer .grow { flex: 1; } +.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; } +.field label { font-size: 11px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: .5px; } +.field input, .field select, .field textarea { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 9px 11px; outline: none; width: 100%; } +.field textarea { resize: vertical; min-height: 80px; font-family: 'Cascadia Code', Consolas, monospace; font-size: 12px; } +.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--accent); } +.field .hint { font-size: 11px; color: var(--faint); } +.row { display: flex; gap: 10px; } +.row > .field { flex: 1; } +.row > .field.small { flex: 0 0 90px; } +.form h4 { margin: 20px 0 12px; font-size: 11px; text-transform: uppercase; letter-spacing: .6px; color: var(--accent-2); } +.seg { display: flex; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 3px; gap: 3px; margin-bottom: 14px; } +.seg button { flex: 1; border: none; background: none; padding: 7px; border-radius: 6px; color: var(--muted); font-weight: 600; } +.seg button.active { background: var(--accent); color: white; } +.check { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; cursor: pointer; } +.check input { accent-color: var(--accent); width: 16px; height: 16px; } +.colors { display: flex; gap: 8px; flex-wrap: wrap; } +.colors button { width: 24px; height: 24px; border-radius: 50%; border: 2px solid transparent; } +.colors button.active { border-color: white; } +.pw-wrap { position: relative; } +.pw-wrap input { padding-right: 36px; } +.pw-wrap button { position: absolute; right: 6px; top: 50%; transform: translateY(-50%); border: none; background: none; color: var(--muted); display: flex; padding: 4px; } + +/* ---------- Terminal-Session ---------- */ +.session .sbar { height: 38px; display: flex; align-items: center; gap: 6px; padding: 0 10px; background: var(--bg-2); border-bottom: 1px solid var(--border); flex: none; } +.session .sbar .info { color: var(--muted); font-size: 12px; margin-right: auto; display: flex; gap: 8px; align-items: center; overflow: hidden; white-space: nowrap; } +.session .term-wrap { flex: 1; position: relative; min-height: 0; display: flex; } +.session .term { flex: 1; padding: 8px 0 4px 10px; min-width: 0; } +.session .term .xterm { height: 100%; } +.session .overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 16px; background: #13151ce6; z-index: 3; } +.session .overlay .spinner { width: 36px; height: 36px; border: 3px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.9s linear infinite; } +.session .overlay .msg { color: var(--muted); max-width: 520px; text-align: center; } +.session .overlay .msg.err { color: var(--red); } +@keyframes spin { to { transform: rotate(360deg); } } +.snip-panel { width: 260px; border-left: 1px solid var(--border); background: var(--bg-2); display: none; flex-direction: column; flex: none; } +.snip-panel.open { display: flex; } +.snip-panel .head { padding: 12px; font-weight: 600; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; } +.snip-panel .items { overflow: auto; padding: 8px; display: flex; flex-direction: column; gap: 6px; } +.snip-panel .it { background: var(--card); border-radius: 8px; padding: 9px 10px; cursor: pointer; } +.snip-panel .it:hover { background: var(--card-hover); } +.snip-panel .it .mono { color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 3px; } +.findbar { position: absolute; top: 8px; right: 24px; z-index: 4; display: none; gap: 4px; align-items: center; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 4px; box-shadow: 0 8px 24px #0008; } +.findbar.open { display: flex; } +.findbar input { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 5px 8px; outline: none; width: 200px; } + +/* ---------- SFTP ---------- */ +.sftp { display: flex; flex: 1; min-height: 0; gap: 1px; background: var(--border); } +.pane { flex: 1; display: flex; flex-direction: column; background: var(--bg); min-width: 0; } +.pane .phead { display: flex; gap: 6px; align-items: center; padding: 8px 10px; border-bottom: 1px solid var(--border); background: var(--bg-2); } +.pane .phead .title { font-weight: 600; margin-right: 6px; display: flex; align-items: center; gap: 8px; white-space: nowrap; } +.pane .phead input { flex: 1; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 5px 8px; outline: none; min-width: 60px; font-family: 'Cascadia Code', Consolas, monospace; font-size: 12px; } +.pane .phead select { background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 4px; } +.pane .files { flex: 1; overflow: auto; } +.pane.dragover .files { outline: 2px dashed var(--accent); outline-offset: -6px; background: #6e7bff0d; } +.pane table { width: 100%; border-collapse: collapse; } +.pane th { position: sticky; top: 0; background: var(--bg); text-align: left; font-size: 11px; color: var(--faint); font-weight: 600; padding: 7px 10px; text-transform: uppercase; letter-spacing: .4px; border-bottom: 1px solid var(--border); cursor: pointer; } +.pane td { padding: 6px 10px; white-space: nowrap; border-bottom: 1px solid #1c1f29; } +.pane td.name { display: flex; align-items: center; gap: 8px; max-width: 0; width: 100%; } +.pane td.name span { overflow: hidden; text-overflow: ellipsis; } +.pane td.num { color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; } +.pane td.date { color: var(--muted); } +.pane tr:hover td { background: #ffffff06; } +.pane tr.sel td { background: #6e7bff26; } +.pane .ico-dir { color: #6ea8ff; } +.pane .ico-file { color: var(--faint); } +.pane-empty { flex: 1; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 14px; color: var(--muted); } +.host-picker { width: min(520px, 90%); max-height: 60%; overflow: auto; display: flex; flex-direction: column; gap: 6px; } +.transfers { max-height: 150px; overflow: auto; border-top: 1px solid var(--border); background: var(--bg-2); flex: none; } +.transfers:empty { display: none; } +.transfer { display: flex; gap: 10px; align-items: center; padding: 6px 12px; font-size: 12px; } +.transfer .bar { flex: 0 0 180px; height: 5px; background: var(--card); border-radius: 4px; overflow: hidden; } +.transfer .bar i { display: block; height: 100%; background: var(--accent); width: 0; transition: width .1s; } +.transfer.done .bar i { background: var(--green); } +.transfer.fail .bar i { background: var(--red); } +.transfer .nm { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + +/* ---------- Modals, Palette, Menüs, Toasts ---------- */ +.modal-bg { position: fixed; inset: 0; background: #0009; display: flex; align-items: flex-start; justify-content: center; padding-top: 12vh; z-index: 100; animation: fade .12s; } +@keyframes fade { from { opacity: 0; } } +.modal { width: min(460px, 92vw); background: var(--bg-2); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 30px 80px #000a; overflow: hidden; } +.modal .mbody { padding: 20px; } +.modal h3 { margin: 0 0 6px; font-size: 16px; } +.modal p { color: var(--muted); margin: 0 0 16px; } +.modal .mfoot { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--border); background: var(--bg); } +.palette { width: min(620px, 92vw); } +.palette .pin { display: flex; align-items: center; gap: 10px; padding: 0 16px; border-bottom: 1px solid var(--border); } +.palette .pin input { flex: 1; background: none; border: none; outline: none; height: 54px; font-size: 16px; } +.palette .res { max-height: 50vh; overflow: auto; padding: 8px; } +.palette .item { display: flex; gap: 12px; align-items: center; padding: 8px 10px; border-radius: 8px; cursor: pointer; } +.palette .item .avatar { width: 32px; height: 32px; font-size: 13px; border-radius: 8px; } +.palette .item.active { background: #6e7bff26; } +.palette .item .k { margin-left: auto; color: var(--faint); font-size: 11px; } +.palette .grp { font-size: 11px; color: var(--faint); padding: 8px 10px 4px; text-transform: uppercase; letter-spacing: .5px; } +.ctx { position: fixed; z-index: 200; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 5px; min-width: 190px; box-shadow: 0 14px 40px #000a; } +.ctx button { display: flex; align-items: center; gap: 10px; width: 100%; border: none; background: none; padding: 7px 10px; border-radius: 6px; text-align: left; } +.ctx button:hover { background: #6e7bff33; } +.ctx button.danger { color: var(--red); } +.ctx hr { border: none; border-top: 1px solid var(--border); margin: 4px 0; } +.ctx svg { width: 15px; height: 15px; color: var(--muted); } +#toasts { position: fixed; bottom: 18px; right: 18px; display: flex; flex-direction: column; gap: 8px; z-index: 300; } +.toast { background: var(--panel); border: 1px solid var(--border); border-left: 3px solid var(--accent); padding: 10px 14px; border-radius: 8px; box-shadow: 0 10px 30px #0008; max-width: 380px; animation: slide .18s; } +.toast.error { border-left-color: var(--red); } +.toast.ok { border-left-color: var(--green); } +@keyframes slide { from { transform: translateY(10px); opacity: 0; } } +.settings-grid { max-width: 720px; } +.settings-card { background: var(--card); border-radius: var(--radius); padding: 18px 20px; margin-bottom: 14px; } +.settings-card h3 { margin: 0 0 14px; font-size: 14px; } +.theme-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; } +.theme-prev { border-radius: 8px; padding: 10px; font-family: 'Cascadia Code', Consolas, monospace; font-size: 11px; cursor: pointer; border: 2px solid transparent; line-height: 1.5; } +.theme-prev.active { border-color: var(--accent); } +.theme-prev .nm { font-family: 'Segoe UI', sans-serif; font-weight: 600; margin-bottom: 4px; } +kbd { background: var(--card); border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 4px; padding: 0 5px; font-size: 11px; font-family: inherit; } diff --git a/src/renderer/themes.js b/src/renderer/themes.js new file mode 100644 index 0000000..8d9d2e3 --- /dev/null +++ b/src/renderer/themes.js @@ -0,0 +1,24 @@ +// Terminal-Farbschemata +window.TERM_THEMES = { + mrterm: { name: 'MrTerm', background: '#13151c', foreground: '#e6e8ef', cursor: '#8b95ff', cursorAccent: '#13151c', selectionBackground: '#6e7bff55', + black: '#1c1f2a', red: '#ff5f6d', green: '#3ecf8e', yellow: '#ffcb6b', blue: '#6e9bff', magenta: '#c792ea', cyan: '#56d6d6', white: '#d0d4e0', + brightBlack: '#5d6377', brightRed: '#ff7a86', brightGreen: '#5fe0a5', brightYellow: '#ffd98a', brightBlue: '#8fb3ff', brightMagenta: '#d7aef2', brightCyan: '#7fe6e6', brightWhite: '#ffffff' }, + dracula: { name: 'Dracula', background: '#282a36', foreground: '#f8f8f2', cursor: '#f8f8f2', selectionBackground: '#44475a', + black: '#21222c', red: '#ff5555', green: '#50fa7b', yellow: '#f1fa8c', blue: '#bd93f9', magenta: '#ff79c6', cyan: '#8be9fd', white: '#f8f8f2', + brightBlack: '#6272a4', brightRed: '#ff6e6e', brightGreen: '#69ff94', brightYellow: '#ffffa5', brightBlue: '#d6acff', brightMagenta: '#ff92df', brightCyan: '#a4ffff', brightWhite: '#ffffff' }, + nord: { name: 'Nord', background: '#2e3440', foreground: '#d8dee9', cursor: '#d8dee9', selectionBackground: '#434c5e', + black: '#3b4252', red: '#bf616a', green: '#a3be8c', yellow: '#ebcb8b', blue: '#81a1c1', magenta: '#b48ead', cyan: '#88c0d0', white: '#e5e9f0', + brightBlack: '#4c566a', brightRed: '#bf616a', brightGreen: '#a3be8c', brightYellow: '#ebcb8b', brightBlue: '#81a1c1', brightMagenta: '#b48ead', brightCyan: '#8fbcbb', brightWhite: '#eceff4' }, + monokai: { name: 'Monokai', background: '#272822', foreground: '#f8f8f2', cursor: '#f8f8f0', selectionBackground: '#49483e', + black: '#272822', red: '#f92672', green: '#a6e22e', yellow: '#f4bf75', blue: '#66d9ef', magenta: '#ae81ff', cyan: '#a1efe4', white: '#f8f8f2', + brightBlack: '#75715e', brightRed: '#f92672', brightGreen: '#a6e22e', brightYellow: '#f4bf75', brightBlue: '#66d9ef', brightMagenta: '#ae81ff', brightCyan: '#a1efe4', brightWhite: '#f9f8f5' }, + solarized: { name: 'Solarized Dark', background: '#002b36', foreground: '#839496', cursor: '#93a1a1', selectionBackground: '#073642', + black: '#073642', red: '#dc322f', green: '#859900', yellow: '#b58900', blue: '#268bd2', magenta: '#d33682', cyan: '#2aa198', white: '#eee8d5', + brightBlack: '#586e75', brightRed: '#cb4b16', brightGreen: '#586e75', brightYellow: '#657b83', brightBlue: '#839496', brightMagenta: '#6c71c4', brightCyan: '#93a1a1', brightWhite: '#fdf6e3' }, + hacker: { name: 'Hacker Green', background: '#0a0f0a', foreground: '#33ff66', cursor: '#33ff66', selectionBackground: '#1f4d2a', + black: '#0a0f0a', red: '#ff4d4d', green: '#33ff66', yellow: '#e6ff4d', blue: '#4dd2ff', magenta: '#d24dff', cyan: '#4dffd2', white: '#ccffcc', + brightBlack: '#336633', brightRed: '#ff8080', brightGreen: '#80ff9f', brightYellow: '#f0ff80', brightBlue: '#80e0ff', brightMagenta: '#e080ff', brightCyan: '#80ffe0', brightWhite: '#ffffff' }, + light: { name: 'Paper Light', background: '#fbfbfd', foreground: '#2b2f3a', cursor: '#6e7bff', selectionBackground: '#6e7bff33', + black: '#2b2f3a', red: '#d63a4a', green: '#1f9d63', yellow: '#b68600', blue: '#3d63dd', magenta: '#9d45c9', cyan: '#138a9a', white: '#c9ccd6', + brightBlack: '#6b7080', brightRed: '#e8505f', brightGreen: '#27b474', brightYellow: '#cc9a00', brightBlue: '#5a7cf0', brightMagenta: '#b25bdd', brightCyan: '#1ca3b5', brightWhite: '#ffffff' }, +};