diff --git a/.github/workflows/botbrowser-control-build.yml b/.github/workflows/botbrowser-control-build.yml new file mode 100644 index 0000000..9df1922 --- /dev/null +++ b/.github/workflows/botbrowser-control-build.yml @@ -0,0 +1,135 @@ +name: BotBrowser Control — Build & Release + +on: + push: + tags: + - 'control-v*.*.*' + workflow_dispatch: + +jobs: + build-mac: + name: macOS + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + with: + ref: add-botbrowser-control + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm install + working-directory: botbrowser-control + + - name: Build macOS + run: npm run build:mac + working-directory: botbrowser-control + env: + CSC_IDENTITY_AUTO_DISCOVERY: false + + - uses: actions/upload-artifact@v4 + with: + name: mac-builds + path: | + botbrowser-control/dist/*.dmg + botbrowser-control/dist/*.zip + retention-days: 7 + + build-windows: + name: Windows + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + with: + ref: add-botbrowser-control + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm install + working-directory: botbrowser-control + + - name: Build Windows + run: npx electron-builder --win + working-directory: botbrowser-control + env: + CSC_IDENTITY_AUTO_DISCOVERY: "false" + DEBUG: electron-builder + + - uses: actions/upload-artifact@v4 + with: + name: windows-builds + path: | + botbrowser-control/dist/*.exe + botbrowser-control/dist/*.zip + retention-days: 7 + + build-linux: + name: Linux + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: add-botbrowser-control + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Linux build dependencies + run: | + sudo apt-get update + sudo apt-get install -y libnss3 libxss1 libxtst6 fakeroot + + - name: Install dependencies + run: npm install + working-directory: botbrowser-control + + - name: Build Linux + run: npm run build:linux + working-directory: botbrowser-control + + - uses: actions/upload-artifact@v4 + with: + name: linux-builds + path: | + botbrowser-control/dist/*.AppImage + botbrowser-control/dist/*.deb + botbrowser-control/dist/*.tar.gz + retention-days: 7 + + release: + name: Upload to Release + needs: [build-mac, build-windows, build-linux] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts/ + + - name: List artifacts + run: find artifacts/ -type f | sort + + - name: Upload to GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + draft: false + prerelease: false + files: | + artifacts/mac-builds/*.dmg + artifacts/mac-builds/*.zip + artifacts/windows-builds/*.exe + artifacts/windows-builds/*.zip + artifacts/linux-builds/*.AppImage + artifacts/linux-builds/*.deb + artifacts/linux-builds/*.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/botbrowser-control/.github/workflows/build.yml b/botbrowser-control/.github/workflows/build.yml new file mode 100644 index 0000000..59da6fd --- /dev/null +++ b/botbrowser-control/.github/workflows/build.yml @@ -0,0 +1,168 @@ +name: Build & Release + +on: + push: + tags: + - 'v*.*.*' + workflow_dispatch: + inputs: + version: + description: 'Version to build (e.g. 1.0.0)' + required: false + default: '' + +jobs: + # ───────────────────────────────────────── + # macOS — DMG + ZIP (x64 & arm64) + # ───────────────────────────────────────── + build-mac: + name: macOS + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build macOS (x64 + arm64) + run: npm run build:mac + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload macOS artifacts + uses: actions/upload-artifact@v4 + with: + name: mac-builds + path: | + dist/*.dmg + dist/*-mac.zip + dist/*-arm64-mac.zip + retention-days: 7 + + # ───────────────────────────────────────── + # Windows — NSIS installer + Portable + ZIP + # ───────────────────────────────────────── + build-windows: + name: Windows + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build Windows (x64 + arm64) + run: npm run build:win + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Windows artifacts + uses: actions/upload-artifact@v4 + with: + name: windows-builds + path: | + dist/*.exe + dist/*-win.zip + retention-days: 7 + + # ───────────────────────────────────────── + # Linux — AppImage + DEB + RPM + tar.gz + # ───────────────────────────────────────── + build-linux: + name: Linux + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install Linux build dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libgtk-3-dev \ + libnotify-dev \ + libgconf-2-4 \ + libnss3 \ + libxss1 \ + libxtst6 \ + xauth \ + xvfb \ + rpm \ + fakeroot + + - name: Install dependencies + run: npm ci + + - name: Build Linux (x64 + arm64) + run: npm run build:linux + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Linux artifacts + uses: actions/upload-artifact@v4 + with: + name: linux-builds + path: | + dist/*.AppImage + dist/*.deb + dist/*.rpm + dist/*.tar.gz + retention-days: 7 + + # ───────────────────────────────────────── + # Release — attach all artifacts to GitHub Release + # ───────────────────────────────────────── + release: + name: Create Release + needs: [build-mac, build-windows, build-linux] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + permissions: + contents: write + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts/ + + - name: List artifacts + run: find artifacts/ -type f | sort + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + name: BotBrowser Control ${{ github.ref_name }} + draft: false + prerelease: false + generate_release_notes: true + files: | + artifacts/mac-builds/*.dmg + artifacts/mac-builds/*.zip + artifacts/windows-builds/*.exe + artifacts/windows-builds/*.zip + artifacts/linux-builds/*.AppImage + artifacts/linux-builds/*.deb + artifacts/linux-builds/*.rpm + artifacts/linux-builds/*.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/botbrowser-control/.gitignore b/botbrowser-control/.gitignore new file mode 100644 index 0000000..5b48d8c --- /dev/null +++ b/botbrowser-control/.gitignore @@ -0,0 +1,54 @@ +# Dependencies +node_modules/ +.npm/ + +# Build output +dist/ +out/ +build/ + +# Electron builder cache +.electron-builder-cache/ + +# macOS +.DS_Store +*.DS_Store +__MACOSX/ + +# Windows +Thumbs.db +desktop.ini + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +*.pid +*.seed +*.pid.lock + +# Temp files +tmp/ +temp/ +*.tmp + +# Editor +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.env +.env.local + +# electron-store data (user data - never commit) +/data/ + +# Coverage +coverage/ +.nyc_output/ \ No newline at end of file diff --git a/botbrowser-control/BUILD.md b/botbrowser-control/BUILD.md new file mode 100644 index 0000000..9f15b7b --- /dev/null +++ b/botbrowser-control/BUILD.md @@ -0,0 +1,173 @@ +# Building BotBrowser Control + +Complete guide to building distributable packages for macOS, Windows, and Linux. + +--- + +## Quick Start + +```bash +npm install # install all dependencies first +npm run build # build for ALL platforms +``` + +All output goes to the `dist/` folder. + +--- + +## Prerequisites + +### Node.js 18+ +```bash +node --version # must be v18.0.0+ +npm --version # must be v9.0.0+ +``` + +Install via [nvm](https://github.com/nvm-sh/nvm) (macOS/Linux) or [winget](https://winget.run/) (Windows): +```bash +# macOS / Linux +nvm install 20 && nvm use 20 + +# Windows +winget install OpenJS.NodeJS +``` + +### Install project dependencies +```bash +cd botbrowser-control +npm install +``` + +--- + +## Build Commands + +| Command | Platforms | Output | +|---------|-----------|--------| +| `npm run build` | All | Mac DMG+ZIP, Win NSIS+Portable+ZIP, Linux AppImage+DEB+RPM+tar.gz | +| `npm run build:mac` | macOS | `.dmg` + `.zip` (x64 + arm64) | +| `npm run build:mac:x64` | macOS Intel | `.dmg` + `.zip` | +| `npm run build:mac:arm64` | macOS Apple Silicon | `.dmg` + `.zip` | +| `npm run build:win` | Windows | NSIS `.exe` + Portable + `.zip` (x64 + arm64) | +| `npm run build:win:x64` | Windows x64 | NSIS `.exe` + Portable + `.zip` | +| `npm run build:win:ia32` | Windows 32-bit | NSIS `.exe` | +| `npm run build:win:arm64` | Windows ARM64 | NSIS `.exe` | +| `npm run build:linux` | Linux | `.AppImage` + `.deb` + `.rpm` + `.tar.gz` (x64 + arm64) | +| `npm run build:linux:x64` | Linux x64 | All Linux formats | +| `npm run build:linux:arm64` | Linux ARM64 | `.AppImage` + `.deb` + `.tar.gz` | + +--- + +## Platform-specific Notes + +### Building on macOS + +No extra tools needed. Builds macOS and Linux targets natively. + +```bash +npm run build:mac # native, always works +npm run build:linux # also works on macOS (cross-compile) +npm run build:win # requires Wine for NSIS +``` + +Install Wine for Windows builds: +```bash +brew install --cask wine-stable +``` + +### Building on Windows + +```powershell +npm run build:win # native, always works +# Mac + Linux targets require WSL or GitHub Actions +``` + +### Building on Linux + +```bash +# Install build tools +sudo apt-get install -y libgtk-3-dev libnotify-dev libnss3 libxss1 libxtst6 rpm fakeroot + +npm run build:linux # native, always works +npm run build:win # requires Wine: sudo apt install wine64 +# macOS targets require GitHub Actions +``` + +--- + +## Output Directory + +After a full `npm run build`, the `dist/` folder contains: + +``` +dist/ +├── BotBrowser Control-1.0.0.dmg ← macOS Intel DMG +├── BotBrowser Control-1.0.0-arm64.dmg ← macOS Apple Silicon DMG +├── BotBrowser Control-1.0.0-mac.zip ← macOS Intel ZIP +├── BotBrowser Control-1.0.0-arm64-mac.zip ← macOS Apple Silicon ZIP +│ +├── BotBrowser Control Setup 1.0.0.exe ← Windows x64 NSIS installer +├── BotBrowser Control Setup 1.0.0-arm64.exe ← Windows ARM64 installer +├── BotBrowser Control 1.0.0.exe ← Windows portable +├── BotBrowser Control-1.0.0-win.zip ← Windows ZIP +│ +├── BotBrowser Control-1.0.0.AppImage ← Linux x64 AppImage +├── BotBrowser Control-1.0.0-arm64.AppImage ← Linux ARM64 AppImage +├── botbrowser-control_1.0.0_amd64.deb ← Debian/Ubuntu x64 +├── botbrowser-control_1.0.0_arm64.deb ← Debian/Ubuntu ARM64 +├── botbrowser-control-1.0.0.x86_64.rpm ← Red Hat/Fedora +└── botbrowser-control-1.0.0.tar.gz ← Generic Linux tarball +``` + +--- + +## Automated Builds with GitHub Actions + +The repository includes `.github/workflows/build.yml` which: + +1. Triggers on any tag push matching `v*.*.*` (e.g. `v1.0.0`) +2. Runs **3 parallel jobs**: `macos-latest`, `windows-latest`, `ubuntu-latest` +3. Uploads all artifacts to the GitHub Release automatically + +### How to trigger a release + +```bash +# Tag and push to trigger the workflow +git tag v1.0.0 +git push origin v1.0.0 +``` + +The Actions workflow will build all three platforms in parallel and attach every artifact to the GitHub Release. + +### Workflow file location +`.github/workflows/build.yml` + +--- + +## Troubleshooting + +| Problem | Solution | +|---------|----------| +| `electron-builder: command not found` | Run `npm install` first | +| `fpm not found` (RPM/DEB on Linux) | `sudo apt install ruby-dev && gem install fpm` or `sudo apt install rpm fakeroot` | +| Windows NSIS build fails on Linux/macOS | Install Wine: `brew install --cask wine-stable` or `sudo apt install wine64` | +| macOS DMG build fails on Linux | Use `macos-latest` GitHub Actions runner | +| Electron download hangs / slow | Set mirror: `ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm install` | +| Icon error during build | Ensure `src/assets/icon.png` exists and is at least 512×512 pixels | +| `ENOTFOUND` / network errors | Check internet connection; try with VPN off | +| `code signing` errors on macOS | Set `hardenedRuntime: false` and `gatekeeperAssess: false` in package.json (already set) | + +--- + +## Development Mode + +Run without building: +```bash +npm start # production mode +npm run dev # development mode (enables devtools, --dev flag) +``` + +Package without installer (just the unpacked app directory): +```bash +npm run pack # outputs to dist/linux-unpacked, dist/mac, or dist/win-unpacked +``` \ No newline at end of file diff --git a/botbrowser-control/INSTALL.md b/botbrowser-control/INSTALL.md new file mode 100644 index 0000000..726eab1 --- /dev/null +++ b/botbrowser-control/INSTALL.md @@ -0,0 +1,216 @@ +# Installing BotBrowser Control + +Step-by-step installation guide for macOS, Windows, and Linux. + +--- + +## macOS + +### From DMG (recommended) + +1. Download `BotBrowser.Control-*-arm64.dmg` (Apple Silicon) or `BotBrowser.Control-*-x64.dmg` (Intel) from [Releases](https://github.com/botswin/BotBrowser/releases) +2. Double-click the `.dmg` file to mount it +3. Drag **BotBrowser Control** into your `/Applications` folder +4. Eject the DMG +5. On first launch, right-click the app → **Open** (to bypass Gatekeeper on unsigned builds) + +### From source + +```bash +# Requires Node.js 18+ (https://nodejs.org) +git clone https://github.com/botswin/BotBrowser.git +cd BotBrowser/botbrowser-control +npm install +npm start +``` + +### Build your own DMG + +```bash +npm install +npm run build:mac # builds both x64 + arm64 DMG & ZIP into dist/ +``` + +--- + +## Windows + +### From NSIS Installer (recommended) + +1. Download `BotBrowser.Control.Setup-*.exe` from [Releases](https://github.com/botswin/BotBrowser/releases) +2. Double-click the installer +3. Follow the setup wizard (choose install directory, create shortcuts) +4. Launch from the Start Menu or Desktop shortcut + +### Portable (no install needed) + +1. Download `BotBrowser.Control-*-portable.exe` +2. Run it directly — no installation required + +### From source + +```powershell +# Requires Node.js 18+ (https://nodejs.org) +git clone https://github.com/botswin/BotBrowser.git +cd BotBrowser\botbrowser-control +npm install +npm start +``` + +### Build your own installer + +```powershell +npm install +npm run build:win # builds NSIS + portable + ZIP into dist\ +``` + +--- + +## Linux + +### AppImage (works on any distro) + +```bash +# Download +wget https://github.com/botswin/BotBrowser/releases/download/vX.X.X/BotBrowser.Control-X.X.X.AppImage + +# Make executable +chmod +x BotBrowser.Control-*.AppImage + +# Run +./BotBrowser.Control-*.AppImage +``` + +### Debian / Ubuntu (.deb) + +```bash +# Download and install +wget https://github.com/botswin/BotBrowser/releases/download/vX.X.X/botbrowser-control_X.X.X_amd64.deb +sudo dpkg -i botbrowser-control_*.deb + +# Fix any missing dependencies +sudo apt install -f + +# Launch +botbrowser-control +``` + +### Red Hat / Fedora / CentOS (.rpm) + +```bash +# Download and install +wget https://github.com/botswin/BotBrowser/releases/download/vX.X.X/botbrowser-control-X.X.X.x86_64.rpm +sudo rpm -i botbrowser-control-*.rpm + +# Or with dnf +sudo dnf install botbrowser-control-*.rpm + +# Launch +botbrowser-control +``` + +### From source + +```bash +# Requires Node.js 18+ — install via nvm or your package manager +curl -fsSL https://fnm.vercel.app/install | bash +fnm install 20 + +git clone https://github.com/botswin/BotBrowser.git +cd BotBrowser/botbrowser-control +npm install +npm start +``` + +### Build your own packages + +```bash +npm install +npm run build:linux # builds AppImage + .deb + .rpm + tar.gz into dist/ +``` + +--- + +## Verifying Your Node.js Version + +```bash +node --version # must be v18.0.0 or later +npm --version # must be v9.0.0 or later +``` + +If you need to upgrade Node.js: + +```bash +# Using nvm (macOS/Linux) +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash +nvm install 20 +nvm use 20 + +# Using fnm (faster, cross-platform) +curl -fsSL https://fnm.vercel.app/install | bash +fnm install 20 + +# Using winget (Windows) +winget install OpenJS.NodeJS +``` + +--- + +## First-time Configuration + +After installation, configure the path to your BotBrowser binary: + +1. Open BotBrowser Control +2. Click **Settings** in the left sidebar +3. Under **BotBrowser Executable**, click **Browse** and locate: + - **macOS:** `/Applications/Chromium.app/Contents/MacOS/Chromium` + - **Windows:** `C:\Program Files\BotBrowser\chrome.exe` + - **Linux:** `/usr/bin/botbrowser` or wherever you extracted BotBrowser +4. Click **Save Settings** + +> **Don't have BotBrowser yet?** Download it from [github.com/botswin/BotBrowser](https://github.com/botswin/BotBrowser) + +--- + +## Uninstalling + +### macOS +- Drag `BotBrowser Control.app` from `/Applications` to Trash +- Remove app data (optional): `rm -rf ~/Library/Application\ Support/BotBrowser\ Control` + +### Windows +- Go to **Settings → Apps → Installed Apps**, find **BotBrowser Control**, click **Uninstall** +- Or run `%LOCALAPPDATA%\Programs\BotBrowser Control\Uninstall BotBrowser Control.exe` +- Remove app data (optional): delete `%APPDATA%\BotBrowser Control` + +### Linux (AppImage) +- Delete the `.AppImage` file +- Remove app data (optional): `rm -rf ~/.config/BotBrowser\ Control` + +### Linux (.deb) +```bash +sudo apt remove botbrowser-control +``` + +### Linux (.rpm) +```bash +sudo rpm -e botbrowser-control +``` + +--- + +## Troubleshooting + +| Issue | Solution | +|-------|----------| +| App shows "Electron" in title bar | Update to latest version from source | +| "BotBrowser executable not found" | Set the correct path in Settings | +| App won't open on macOS (Gatekeeper) | Right-click → Open, then click Open in the dialog | +| White screen on startup | Run `npm start` in terminal to see error output | +| `ENOENT: node_modules` error | Run `npm install` in the project directory | +| Profiles stuck as "Running" after force-quit | Restart the app — stale status is reset automatically on launch | +| Can't install `.deb` — missing deps | Run `sudo apt install -f` after `dpkg -i` | + +--- + +For more help, open an issue at [github.com/botswin/BotBrowser/issues](https://github.com/botswin/BotBrowser/issues) \ No newline at end of file diff --git a/botbrowser-control/LICENSE b/botbrowser-control/LICENSE new file mode 100644 index 0000000..24cd588 --- /dev/null +++ b/botbrowser-control/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 BotBrowser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/botbrowser-control/README.md b/botbrowser-control/README.md new file mode 100644 index 0000000..16523b4 --- /dev/null +++ b/botbrowser-control/README.md @@ -0,0 +1,275 @@ +
+ +BotBrowser Control icon + +# BotBrowser Control + +**Professional desktop profile manager for [BotBrowser](https://github.com/botswin/BotBrowser)** + +[![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-blue)](#) +[![Electron](https://img.shields.io/badge/Electron-28-47848F?logo=electron)](#) +[![License](https://img.shields.io/badge/license-MIT-green)](#) + +[Features](#features) · [Install](#installation) · [Build](#building-from-source) · [Usage](#usage) · [Configuration](#profile-configuration) + +
+ +--- + +## Overview + +BotBrowser Control is an open-source Electron desktop application that provides a GoLogin-style GUI for managing and launching [BotBrowser](https://github.com/botswin/BotBrowser) profiles. It handles per-profile user data isolation, fingerprint configuration, proxy assignment, cookie persistence, and live session monitoring — all without touching the command line. + +
+ +### Profiles View +Profiles View + +### Settings +Settings View + +### Profile Editor +Profile Editor + +
+ +--- + +## Features + +- **Profile Manager** — Create, edit, duplicate, and delete browser profiles with a clean table UI +- **VS Code-style Profile Editor** — 7-tab sidebar editor (General, Network, Identity, Fingerprint, Behavior, Session, Advanced) +- **One-click Launch / Stop** — Spawn and kill BotBrowser instances per profile +- **Per-profile User Data** — Each profile gets an isolated `--user-data-dir` automatically +- **Proxy Support** — HTTP, HTTPS, SOCKS4, SOCKS5 with credentials, per-profile +- **Fingerprint Configuration** — Map to all BotBrowser `--bot-config-*` flags: locale, timezone, WebGL, WebRTC, canvas noise, seeds, etc. +- **`.enc` Profile File Support** — Load BotBrowser encrypted fingerprint profiles +- **Cookie Persistence** — Auto-save and reload cookies via CDP on stop/start +- **Session Monitor** — Live view of all running browser instances with PID and start time +- **Settings Page** — Configure BotBrowser executable path, default user data directory, default proxy +- **Dark / Light Theme** — Follows system theme; manual override available +- **macOS native** — Hidden titlebar, traffic lights, vibrancy effect +- **Cross-platform** — Works on macOS, Windows, and Linux + +--- + +## Requirements + +| Requirement | Version | +|-------------|---------| +| [BotBrowser](https://github.com/botswin/BotBrowser) | Any recent release | +| Node.js | 18 or later | +| npm | 9 or later | + +--- + +## Installation + +### Option 1 — Download a pre-built release + +Go to [Releases](https://github.com/botswin/BotBrowser/releases) and download the installer for your platform: + +| Platform | File | +|----------|------| +| macOS (Apple Silicon) | `BotBrowser.Control-*-arm64.dmg` | +| macOS (Intel) | `BotBrowser.Control-*-x64.dmg` | +| Windows 64-bit | `BotBrowser.Control.Setup-*.exe` | +| Linux (AppImage) | `BotBrowser.Control-*.AppImage` | +| Linux (Debian/Ubuntu) | `botbrowser-control_*_amd64.deb` | + +### Option 2 — Run from source + +```bash +# 1. Clone the repository +git clone https://github.com/botswin/BotBrowser.git +cd BotBrowser/botbrowser-control # adjust path if this is a standalone repo + +# 2. Install dependencies +npm install + +# 3. Launch in development mode +npm start +``` + +--- + +## Building from Source + +### Prerequisites + +```bash +node --version # v18+ +npm --version # v9+ +npm install # install all dependencies first +``` + +### Build commands + +```bash +# Build for ALL platforms (Mac + Windows + Linux) +npm run build + +# macOS only (produces .dmg and .zip for x64 + arm64) +npm run build:mac + +# Windows only (produces NSIS installer + portable + zip) +npm run build:win + +# Linux only (produces AppImage + .deb + .rpm + tar.gz) +npm run build:linux +``` + +All artifacts are written to the `dist/` directory. + +### Build a specific architecture + +```bash +npm run build:mac:x64 # macOS Intel +npm run build:mac:arm64 # macOS Apple Silicon +npm run build:win:x64 # Windows 64-bit +npm run build:win:ia32 # Windows 32-bit +npm run build:linux:x64 # Linux x64 +npm run build:linux:arm64 # Linux ARM64 +``` + +### Cross-platform builds + +You can build **macOS** and **Linux** targets from any Unix machine. Building **Windows** targets from macOS/Linux requires [Wine](https://www.winehq.org/): + +```bash +# macOS +brew install --cask wine-stable + +# Ubuntu/Debian +sudo apt install wine64 +``` + +> **Recommended:** Use GitHub Actions to produce official releases for all three platforms in parallel. See [BUILD.md](BUILD.md) for the full workflow YAML. + +--- + +## First-time Setup + +1. Launch the app (`npm start` or open the installed `.app` / `.exe`) +2. Click **Settings** in the left sidebar +3. Set the **BotBrowser Executable** path to your BotBrowser binary: + - macOS: `/Applications/Chromium.app/Contents/MacOS/Chromium` + - Windows: `C:\Program Files\BotBrowser\chrome.exe` + - Linux: `/usr/bin/botbrowser` +4. (Optional) Set a **Default User Data Directory** — defaults to `~/Library/Application Support/BotBrowser Control/browser-profiles` +5. Click **Save Settings** + +--- + +## Usage + +### Creating a Profile + +1. Click **+ New Profile** in the Profiles view +2. Fill in the **General** tab: name, browser brand, start URL, `.enc` profile file +3. Configure **Network** tab: proxy server, IP override +4. Optionally set fingerprint details in **Identity**, **Fingerprint**, **Behavior** tabs +5. Click **Save Changes** + +### Launching a Profile + +- Click the **▶ Launch** button on any profile row +- The profile opens as a standalone BotBrowser window +- Status changes to **Running** (green pill) +- Click **■ Stop** to close the browser and auto-save cookies + +### Managing Sessions + +- Click **Sessions** in the left sidebar to see all live instances +- Each entry shows: profile name, PID, start time, and URL +- Use **Stop All** to terminate every running instance at once + +### Keyboard Shortcuts + +| Action | macOS | Windows / Linux | +|--------|-------|-----------------| +| New Profile | `⌘N` | `Ctrl+N` | +| Go to Profiles | `⌘1` | `Ctrl+1` | +| Go to Sessions | `⌘2` | `Ctrl+2` | +| Go to Settings | `⌘3` | `Ctrl+3` | +| Reload UI | `⌘R` | `Ctrl+R` | +| DevTools | `⌘⌥I` | `Ctrl+Shift+I` | + +--- + +## Profile Configuration + +The profile editor maps directly to BotBrowser CLI flags. The most important fields: + +| Tab | Field | BotBrowser Flag | +|-----|-------|-----------------| +| General | Browser Brand | `--bot-config-browser-brand` | +| General | Profile File (.enc) | `--bot-profile` | +| General | Start URL | positional argument | +| Network | Proxy Server | `--proxy-server` | +| Network | Proxy IP Override | `--proxy-ip` | +| Identity | Locale | `--bot-config-locale` | +| Identity | Timezone | `--bot-config-timezone` | +| Identity | Color Scheme | `--bot-config-color-scheme` | +| Fingerprint | WebGL | `--bot-config-webgl` | +| Fingerprint | WebRTC | `--bot-config-webrtc` | +| Fingerprint | Canvas Noise | `--bot-config-noise-canvas` | +| Fingerprint | Noise Seed | `--bot-noise-seed` | +| Behavior | Disable Debugger | `--bot-disable-debugger` | +| Behavior | Always Active | `--bot-always-active` | +| Session | Remote Debug Port | `--remote-debugging-port` | +| Session | Bot Script | `--bot-script` | +| Advanced | Custom Headers | `--bot-custom-headers` | +| Advanced | Time Scale | `--bot-time-scale` | + +For the full list of supported flags see [BotBrowser CLI_FLAGS.md](https://github.com/botswin/BotBrowser). + +--- + +## Project Structure + +``` +botbrowser-control/ +├── src/ +│ ├── main/ +│ │ └── main.js # Electron main process (IPC, browser launch, store) +│ ├── preload/ +│ │ └── preload.js # contextBridge API exposed to renderer +│ ├── renderer/ +│ │ ├── index.html # App shell HTML +│ │ ├── styles/ +│ │ │ └── main.css # Full UI stylesheet (dark theme, GoLogin-inspired) +│ │ └── js/ +│ │ └── app.js # Renderer logic (renderProfiles, editor, settings) +│ └── assets/ +│ ├── icon.png # App icon (1024×1024, used for all platforms) +│ └── logo.svg # Sidebar SVG logo +├── preview/ +│ └── index.html # Standalone HTML UI preview (no Electron needed) +├── package.json # Electron + electron-builder config +├── BUILD.md # Detailed build instructions +└── README.md # This file +``` + +--- + +## Contributing + +1. Fork the repository +2. Create a feature branch: `git checkout -b feature/my-feature` +3. Make your changes +4. Run the app to verify: `npm start` +5. Commit and push: `git push origin feature/my-feature` +6. Open a Pull Request + +--- + +## License + +MIT — see [LICENSE](LICENSE) for details. + +--- + +
+Made with ♥ for the BotBrowser community · github.com/botswin/BotBrowser +
\ No newline at end of file diff --git a/botbrowser-control/package-lock.json b/botbrowser-control/package-lock.json new file mode 100644 index 0000000..a0c00c8 --- /dev/null +++ b/botbrowser-control/package-lock.json @@ -0,0 +1,4435 @@ +{ + "name": "botbrowser-control", + "version": "1.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "botbrowser-control", + "version": "1.2.0", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "electron-store": "^8.1.0", + "uuid": "^9.0.0" + }, + "devDependencies": { + "electron": "^28.0.0", + "electron-builder": "^24.0.0" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@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/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "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": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/notarize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.2.1.tgz", + "integrity": "sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==", + "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/notarize/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/notarize/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/@electron/osx-sign": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.0.5.tgz", + "integrity": "sha512-k9ZzUQtamSoweGQDV2jILiRIHUu7lYlJ3c6IEmjv1hC17rclE+eb9U+f6UFlOOETo0JzY1HNlXy4YOlCvl+Lww==", + "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/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/@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/osx-sign/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/osx-sign/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/@electron/universal": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.5.1.tgz", + "integrity": "sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.2.1", + "@malept/cross-spawn-promise": "^1.1.0", + "debug": "^4.3.1", + "dir-compare": "^3.0.0", + "fs-extra": "^9.0.1", + "minimatch": "^3.0.4", + "plist": "^3.0.4" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/universal/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/universal/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/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/universal/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/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "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": ">= 10" + } + }, + "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/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/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/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "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/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "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": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, + "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/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz", + "integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "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/ajv-formats/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==", + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "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-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz", + "integrity": "sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.13.3.tgz", + "integrity": "sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/notarize": "2.2.1", + "@electron/osx-sign": "1.0.5", + "@electron/universal": "1.5.1", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chromium-pickle-js": "^0.2.0", + "debug": "^4.3.4", + "ejs": "^3.1.8", + "electron-publish": "24.13.1", + "form-data": "^4.0.0", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "is-ci": "^3.0.0", + "isbinaryfile": "^5.0.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "minimatch": "^5.1.1", + "read-config-file": "6.3.2", + "sanitize-filename": "^1.6.3", + "semver": "^7.3.8", + "tar": "^6.1.12", + "temp-file": "^3.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "24.13.3", + "electron-builder-squirrel-windows": "24.13.3" + } + }, + "node_modules/app-builder-lib/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/app-builder-lib/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "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": "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/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/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", + "peer": true, + "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/archiver-utils/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", + "peer": true + }, + "node_modules/archiver-utils/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", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.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/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "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/atomically": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz", + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", + "license": "MIT", + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "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/bluebird-lst": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.5" + } + }, + "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": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", + "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.13.1.tgz", + "integrity": "sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "4.0.0", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-ci": "^3.0.0", + "js-yaml": "^4.1.0", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz", + "integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/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/builder-util/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/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/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": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "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": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/conf": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz", + "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==", + "license": "MIT", + "dependencies": { + "ajv": "^8.6.3", + "ajv-formats": "^2.1.1", + "atomically": "^1.7.0", + "debounce-fn": "^4.0.0", + "dot-prop": "^6.0.1", + "env-paths": "^2.2.1", + "json-schema-typed": "^7.0.3", + "onetime": "^5.1.2", + "pkg-up": "^3.1.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conf/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "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/conf/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==", + "license": "MIT" + }, + "node_modules/conf/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/config-file-ts": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.6.tgz", + "integrity": "sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.3.10", + "typescript": "^5.3.3" + } + }, + "node_modules/config-file-ts/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "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": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/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/config-file-ts/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/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "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/debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "3.3.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz", + "integrity": "sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal": "^1.0.0", + "minimatch": "^3.0.4" + } + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/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": "24.13.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz", + "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-builder/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/dmg-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/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/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", + "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "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/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "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": "28.3.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-28.3.3.tgz", + "integrity": "sha512-ObKMLSPNhomtCOBAxFS8P2DW/4umkh72ouZUlUKzXGtYuPzgr1SYhskhFWgzAsPtUzhL2CzyV2sfbHcEW4CXqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^18.11.18", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.13.3.tgz", + "integrity": "sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "dmg-builder": "24.13.3", + "fs-extra": "^10.1.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "read-config-file": "6.3.2", + "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": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-24.13.3.tgz", + "integrity": "sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "24.13.3", + "archiver": "^5.3.1", + "builder-util": "24.13.1", + "fs-extra": "^10.1.0" + } + }, + "node_modules/electron-builder-squirrel-windows/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", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder-squirrel-windows/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", + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-builder/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/electron-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/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/electron-publish": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz", + "integrity": "sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/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/electron-publish/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/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/electron-store": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-8.2.0.tgz", + "integrity": "sha512-ukLL5Bevdil6oieAOXz3CMy+OgaItMiVBg701MNlG6W5RaC0AHN7rvlqTCmeb6O7jP0Qa1KKYTE0xV0xbhF4Hw==", + "license": "MIT", + "dependencies": { + "conf": "^10.2.0", + "type-fest": "^2.17.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "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/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "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==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "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/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/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/global-agent/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", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "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": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "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-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "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/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "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", + "peer": true + }, + "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": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "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/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==", + "license": "BSD-2-Clause" + }, + "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": "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/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/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/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", + "peer": true, + "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/lazystream/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", + "peer": true + }, + "node_modules/lazystream/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", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "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-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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": "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/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": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.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/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "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/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/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", + "peer": true + }, + "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/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/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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-config-file": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", + "integrity": "sha512-M80lpCjnE6Wt6zb98DoW8WHR09nzMSpu8XHtPkiTHrJ5Az9CybfeQhTJ8D7saeBHpGhLPIVyA8lcL6ZmdKwY6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-file-ts": "^0.2.4", + "dotenv": "^9.0.2", + "dotenv-expand": "^5.1.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.0", + "lazy-val": "^1.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/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.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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", + "peer": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "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/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/serialize-error/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/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": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/simple-update-notifier/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/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.2.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/string-width-cjs": { + "name": "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/strip-ansi-cjs": { + "name": "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": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar 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": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "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/temp-file/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/temp-file/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/temp-file/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/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "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/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/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", + "peer": true + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "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/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/wrap-ansi-cjs": { + "name": "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.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "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/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/botbrowser-control/package.json b/botbrowser-control/package.json new file mode 100644 index 0000000..b4563d3 --- /dev/null +++ b/botbrowser-control/package.json @@ -0,0 +1,182 @@ +{ + "name": "botbrowser-control", + "version": "1.2.2", + "description": "BotBrowser Control Center - Professional Browser Profile Manager", + "main": "src/main/main.js", + "scripts": { + "start": "electron .", + "dev": "electron . --dev", + "build": "CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --mac --win --linux", + "build:mac": "CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --mac", + "build:mac:x64": "CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --mac --x64", + "build:mac:arm64": "CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --mac --arm64", + "build:win": "CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --win", + "build:win:x64": "CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --win --x64", + "build:win:ia32": "CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --win --ia32", + "build:win:arm64": "CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --win --arm64", + "build:linux": "electron-builder --linux", + "build:linux:x64": "electron-builder --linux --x64", + "build:linux:arm64": "electron-builder --linux --arm64", + "build:linux:rpm": "electron-builder --linux --x64 --config.linux.target=rpm", + "pack": "electron-builder --dir", + "postinstall": "electron-builder install-app-deps" + }, + "keywords": [ + "botbrowser", + "antidetect", + "fingerprint", + "privacy", + "browser" + ], + "author": { + "name": "BotBrowser Control", + "email": "support@botbrowser.io" + }, + "homepage": "https://github.com/botswin/BotBrowser", + "license": "MIT", + "devDependencies": { + "electron": "^28.0.0", + "electron-builder": "^24.0.0" + }, + "dependencies": { + "electron-store": "^8.1.0", + "uuid": "^9.0.0" + }, + "build": { + "appId": "io.botbrowser.control", + "productName": "BotBrowser Control", + "copyright": "Copyright \u00a9 2025 BotBrowser", + "directories": { + "output": "dist", + "buildResources": "build-resources" + }, + "files": [ + "src/**/*", + "node_modules/**/*", + "!node_modules/.cache", + "!**/*.map" + ], + "mac": { + "category": "public.app-category.developer-tools", + "icon": "src/assets/icon.png", + "target": [ + { + "target": "dmg", + "arch": [ + "x64", + "arm64" + ] + }, + { + "target": "zip", + "arch": [ + "x64", + "arm64" + ] + } + ], + "darkModeSupport": true, + "hardenedRuntime": false, + "gatekeeperAssess": false, + "identity": null, + "minimumSystemVersion": "11.0" + }, + "dmg": { + "title": "BotBrowser Control", + "contents": [ + { + "x": 130, + "y": 220 + }, + { + "x": 410, + "y": 220, + "type": "link", + "path": "/Applications" + } + ], + "window": { + "width": 540, + "height": 380 + } + }, + "win": { + "icon": "src/assets/icon.ico", + "target": [ + { + "target": "nsis", + "arch": [ + "x64", + "arm64" + ] + }, + { + "target": "portable", + "arch": [ + "x64" + ] + }, + { + "target": "zip", + "arch": [ + "x64", + "arm64" + ] + } + ], + "verifyUpdateCodeSignature": false, + "forceCodeSigning": false + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": true, + "createStartMenuShortcut": true, + "shortcutName": "BotBrowser Control" + }, + "linux": { + "icon": "src/assets/icon.png", + "category": "Development", + "target": [ + { + "target": "AppImage", + "arch": [ + "x64", + "arm64" + ] + }, + { + "target": "deb", + "arch": [ + "x64", + "arm64" + ] + }, + { + "target": "tar.gz", + "arch": [ + "x64", + "arm64" + ] + } + ], + "maintainer": "BotBrowser ", + "description": "Professional Browser Profile Manager for BotBrowser" + }, + "deb": { + "depends": [ + "libnotify4", + "libxtst6", + "libnss3" + ] + }, + "rpm": { + "depends": [ + "libnotify", + "libXtst", + "nss" + ] + }, + "publish": null + } +} \ No newline at end of file diff --git a/botbrowser-control/preview/screenshot_editor.png b/botbrowser-control/preview/screenshot_editor.png new file mode 100644 index 0000000..8efaa2c Binary files /dev/null and b/botbrowser-control/preview/screenshot_editor.png differ diff --git a/botbrowser-control/preview/screenshot_profiles.png b/botbrowser-control/preview/screenshot_profiles.png new file mode 100644 index 0000000..c54f5d8 Binary files /dev/null and b/botbrowser-control/preview/screenshot_profiles.png differ diff --git a/botbrowser-control/preview/screenshot_settings.png b/botbrowser-control/preview/screenshot_settings.png new file mode 100644 index 0000000..ee3b232 Binary files /dev/null and b/botbrowser-control/preview/screenshot_settings.png differ diff --git a/botbrowser-control/src/assets/icon.ico b/botbrowser-control/src/assets/icon.ico new file mode 100644 index 0000000..68bfedd Binary files /dev/null and b/botbrowser-control/src/assets/icon.ico differ diff --git a/botbrowser-control/src/assets/icon.png b/botbrowser-control/src/assets/icon.png new file mode 100644 index 0000000..f6a92c0 Binary files /dev/null and b/botbrowser-control/src/assets/icon.png differ diff --git a/botbrowser-control/src/assets/logo.svg b/botbrowser-control/src/assets/logo.svg new file mode 100644 index 0000000..ad926db --- /dev/null +++ b/botbrowser-control/src/assets/logo.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/botbrowser-control/src/main/main.js b/botbrowser-control/src/main/main.js new file mode 100644 index 0000000..c1444e2 --- /dev/null +++ b/botbrowser-control/src/main/main.js @@ -0,0 +1,1374 @@ +const { app, BrowserWindow, ipcMain, dialog, shell, Menu, nativeTheme, Notification } = require('electron'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); +const net = require('net'); +const { spawn, execFile } = require('child_process'); +const https = require('https'); +const http = require('http'); +const Store = require('electron-store'); +const { v4: uuidv4 } = require('uuid'); + +// ─── Fix app name BEFORE anything else ─── +app.setName('BotBrowser Control'); + +// ─── Platform-aware defaults ────────────────────────────────────────────────── +const IS_WIN = process.platform === 'win32'; +const IS_MAC = process.platform === 'darwin'; +const IS_LINUX = process.platform === 'linux'; + +function getDefaultBotBrowserPath() { + if (IS_MAC) return '/Applications/Chromium.app/Contents/MacOS/Chromium'; + if (IS_WIN) return 'C:\\Program Files\\BotBrowser\\chrome.exe'; + return '/usr/bin/botbrowser'; +} + +function getDefaultUserDataDir() { + return path.join(app.getPath('userData'), 'browser-profiles'); +} + +const DEFAULT_BOTBROWSER_PATH = getDefaultBotBrowserPath(); + +// ─── Persistent store ───────────────────────────────────────────────────────── +const store = new Store({ + name: 'botbrowser-control', + defaults: { + profiles: [], + settings: { + botBrowserPath: DEFAULT_BOTBROWSER_PATH, + defaultUserDataDir: getDefaultUserDataDir(), + theme: 'dark', + defaultProxy: '', + autoLaunch: false, + }, + windowBounds: { width: 1280, height: 800 }, + lastSeenKernelRelease: null, + lastSeenControlRelease: null, + cachedKernelReleases: null, + } +}); + +// ─── Runtime state ──────────────────────────────────────────────────────────── +const runningInstances = new Map(); +const tempFiles = new Map(); +let mainWindow = null; + +// ─── Window ─────────────────────────────────────────────────────────────────── +function createWindow() { + const bounds = store.get('windowBounds'); + + mainWindow = new BrowserWindow({ + width: bounds.width || 1280, + height: bounds.height || 800, + minWidth: 960, + minHeight: 600, + ...(IS_MAC ? { + titleBarStyle: 'hiddenInset', + trafficLightPosition: { x: 16, y: 18 }, + vibrancy: 'under-window', + visualEffectState: 'active', + } : { + frame: true, + }), + backgroundColor: '#2c3e50', + webPreferences: { + preload: path.join(__dirname, '../preload/preload.js'), + contextIsolation: true, + nodeIntegration: false, + sandbox: false, + devTools: false, // disable devtools + } + }); + + mainWindow.loadFile(path.join(__dirname, '../renderer/index.html')); + + // Prevent devtools from opening + mainWindow.webContents.on('devtools-opened', () => { + mainWindow.webContents.closeDevTools(); + }); + + mainWindow.on('resize', () => { + const [width, height] = mainWindow.getSize(); + store.set('windowBounds', { width, height }); + }); + + mainWindow.on('closed', () => { mainWindow = null; }); + + buildMenu(); +} + +function buildMenu() { + const template = [ + ...(IS_MAC ? [{ + label: app.getName(), + submenu: [ + { role: 'about' }, + { type: 'separator' }, + { role: 'services' }, + { type: 'separator' }, + { role: 'hide' }, + { role: 'hideOthers' }, + { role: 'unhide' }, + { type: 'separator' }, + { role: 'quit' } + ] + }] : []), + { + label: 'File', + submenu: [ + { label: 'New Profile', accelerator: IS_MAC ? 'Cmd+N' : 'Ctrl+N', click: () => mainWindow?.webContents.send('action', 'new-profile') }, + { type: 'separator' }, + IS_MAC ? { role: 'close' } : { role: 'quit' } + ] + }, + { + label: 'Edit', + submenu: [ + { role: 'undo' }, + { role: 'redo' }, + { type: 'separator' }, + { role: 'cut' }, + { role: 'copy' }, + { role: 'paste' }, + { role: 'selectAll' }, + ] + }, + { + label: 'View', + submenu: [ + { label: 'Profiles', accelerator: IS_MAC ? 'Cmd+1' : 'Ctrl+1', click: () => mainWindow?.webContents.send('navigate', 'profiles') }, + { label: 'Running Sessions', accelerator: IS_MAC ? 'Cmd+2' : 'Ctrl+2', click: () => mainWindow?.webContents.send('navigate', 'sessions') }, + { label: 'Settings', accelerator: IS_MAC ? 'Cmd+3' : 'Ctrl+3', click: () => mainWindow?.webContents.send('navigate', 'settings') }, + { type: 'separator' }, + { role: 'reload' }, + { type: 'separator' }, + { role: 'togglefullscreen' } + ] + }, + { + label: 'Window', + submenu: [ + { role: 'minimize' }, + { role: 'zoom' }, + ...(IS_MAC ? [{ type: 'separator' }, { role: 'front' }] : []) + ] + } + ]; + Menu.setApplicationMenu(Menu.buildFromTemplate(template)); +} + +// ─── IPC: Profile Management ────────────────────────────────────────────────── + +ipcMain.handle('profiles:getAll', () => store.get('profiles', [])); + +ipcMain.handle('profiles:create', (_, profileData) => { + const profiles = store.get('profiles', []); + const newProfile = { + id: uuidv4(), + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + status: 'stopped', + ...profileData + }; + profiles.push(newProfile); + store.set('profiles', profiles); + return newProfile; +}); + +ipcMain.handle('profiles:update', (_, { id, updates }) => { + const profiles = store.get('profiles', []); + const idx = profiles.findIndex(p => p.id === id); + if (idx === -1) throw new Error('Profile not found'); + profiles[idx] = { ...profiles[idx], ...updates, updatedAt: new Date().toISOString() }; + store.set('profiles', profiles); + return profiles[idx]; +}); + +ipcMain.handle('profiles:delete', (_, id) => { + const profiles = store.get('profiles', []); + store.set('profiles', profiles.filter(p => p.id !== id)); + if (runningInstances.has(id)) { + try { runningInstances.get(id).process.kill('SIGTERM'); } catch {} + runningInstances.delete(id); + } + return true; +}); + +ipcMain.handle('profiles:deleteMultiple', (_, ids) => { + const idSet = new Set(ids); + const profiles = store.get('profiles', []); + store.set('profiles', profiles.filter(p => !idSet.has(p.id))); + for (const id of ids) { + if (runningInstances.has(id)) { + try { runningInstances.get(id).process.kill('SIGTERM'); } catch {} + runningInstances.delete(id); + } + } + return true; +}); + +ipcMain.handle('profiles:duplicate', async (_, id) => { + const profiles = store.get('profiles', []); + const original = profiles.find(p => p.id === id); + if (!original) throw new Error('Profile not found'); + + const newId = uuidv4(); + const copy = { + ...original, + id: newId, + name: original.name + ' (Copy)', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + status: 'stopped', + cookieCount: 0, + cookiesSavedAt: null, + savedCookiesPath: null, + }; + + const settings = store.get('settings'); + const srcDir = path.join(settings.defaultUserDataDir, id); + const dstDir = path.join(settings.defaultUserDataDir, newId); + + if (fs.existsSync(srcDir)) { + try { + copyDirRecursive(srcDir, dstDir); + const newCookiesPath = path.join(dstDir, 'saved-cookies.json'); + if (fs.existsSync(newCookiesPath)) { + copy.savedCookiesPath = newCookiesPath; + try { + const cookies = JSON.parse(fs.readFileSync(newCookiesPath, 'utf8')); + copy.cookieCount = Array.isArray(cookies) ? cookies.length : 0; + copy.cookiesSavedAt = original.cookiesSavedAt; + } catch {} + } + } catch (e) {} + } + + profiles.push(copy); + store.set('profiles', profiles); + return copy; +}); + +function copyDirRecursive(src, dst) { + fs.mkdirSync(dst, { recursive: true }); + for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + const srcPath = path.join(src, entry.name); + const dstPath = path.join(dst, entry.name); + if (entry.isDirectory()) { + copyDirRecursive(srcPath, dstPath); + } else { + fs.copyFileSync(srcPath, dstPath); + } + } +} + +// ─── IPC: Browser Launch ────────────────────────────────────────────────────── + +ipcMain.handle('browser:launch', async (_, profileId) => { + let profiles = store.get('profiles', []); + let profile = profiles.find(p => p.id === profileId); + if (!profile) throw new Error('Profile not found'); + + if (runningInstances.has(profileId)) { + throw new Error('Profile is already running.'); + } + + if (profile.status === 'running') { + updateProfileStatus(profileId, 'stopped'); + profile = { ...profile, status: 'stopped' }; + } + + const settings = store.get('settings'); + const botBrowserPath = settings.botBrowserPath || DEFAULT_BOTBROWSER_PATH; + + if (!fs.existsSync(botBrowserPath)) { + throw new Error( + `BotBrowser executable not found at:\n${botBrowserPath}\n\nPlease install BotBrowser or update the path in Settings.` + ); + } + + const userDataDir = path.join(settings.defaultUserDataDir, profileId); + fs.mkdirSync(userDataDir, { recursive: true }); + + const savedCookiesPath = path.join(userDataDir, 'saved-cookies.json'); + if (fs.existsSync(savedCookiesPath) && !profile.cookies) { + profile = { ...profile, cookies: `@${savedCookiesPath}` }; + } + + let botProfileArg = ''; + if (profile.profileFilePath && fs.existsSync(profile.profileFilePath)) { + botProfileArg = injectConfigsIntoEncFile(profile.profileFilePath, profile, profileId); + } else if (profile.profileDirPath && fs.existsSync(profile.profileDirPath)) { + botProfileArg = null; + } else { + botProfileArg = writeStandaloneConfigFile(profile, profileId); + } + + const args = buildLaunchArgs(profile, userDataDir, botProfileArg); + + const proc = spawn(botBrowserPath, args, { + detached: false, + stdio: ['ignore', 'pipe', 'pipe'], + ...(IS_WIN ? { shell: false } : {}) + }); + + const rdpArg = args.find(a => a.startsWith('--remote-debugging-port=')); + const remoteDebuggingPort = rdpArg ? parseInt(rdpArg.split('=')[1], 10) : null; + + const instance = { + process: proc, + pid: proc.pid, + profileId, + profileName: profile.name, + startTime: new Date().toISOString(), + url: profile.startUrl || 'about:blank', + userDataDir, + remoteDebuggingPort, + args + }; + + runningInstances.set(profileId, instance); + updateProfileStatus(profileId, 'running'); + + proc.stdout.on('data', (_data) => {}); + proc.stderr.on('data', (_data) => {}); + + proc.on('close', (code) => { + runningInstances.delete(profileId); + updateProfileStatus(profileId, 'stopped'); + cleanupTempFile(profileId); + mainWindow?.webContents.send('instance:stopped', { profileId, code }); + }); + + proc.on('error', (err) => { + runningInstances.delete(profileId); + updateProfileStatus(profileId, 'stopped'); + cleanupTempFile(profileId); + mainWindow?.webContents.send('instance:error', { profileId, error: err.message }); + }); + + mainWindow?.webContents.send('instance:started', { profileId, pid: proc.pid }); + return { pid: proc.pid, args }; +}); + +ipcMain.handle('browser:stop', async (_, profileId) => { + if (!runningInstances.has(profileId)) { + updateProfileStatus(profileId, 'stopped'); + return false; + } + const inst = runningInstances.get(profileId); + + if (inst.remoteDebuggingPort) { + try { + await saveCookiesViaCDP(profileId, inst.remoteDebuggingPort, inst.userDataDir); + } catch (e) {} + } + + try { inst.process.kill(IS_WIN ? undefined : 'SIGTERM'); } catch {} + runningInstances.delete(profileId); + cleanupTempFile(profileId); + updateProfileStatus(profileId, 'stopped'); + return true; +}); + +ipcMain.handle('browser:stopAll', async () => { + const savePromises = []; + for (const [profileId, inst] of runningInstances) { + if (inst.remoteDebuggingPort) { + savePromises.push( + saveCookiesViaCDP(profileId, inst.remoteDebuggingPort, inst.userDataDir) + .catch(_e => {}) + ); + } + } + await Promise.allSettled(savePromises); + + for (const [profileId, inst] of runningInstances) { + try { inst.process.kill(IS_WIN ? undefined : 'SIGTERM'); } catch {} + cleanupTempFile(profileId); + updateProfileStatus(profileId, 'stopped'); + } + runningInstances.clear(); + return true; +}); + +ipcMain.handle('browser:getRunning', () => { + const result = []; + for (const [profileId, inst] of runningInstances) { + result.push({ + profileId, + pid: inst.pid, + profileName: inst.profileName, + startTime: inst.startTime, + url: inst.url + }); + } + return result; +}); + +// ─── IPC: Settings ──────────────────────────────────────────────────────────── + +ipcMain.handle('settings:get', () => store.get('settings')); +ipcMain.handle('settings:set', (_, newSettings) => { + store.set('settings', { ...store.get('settings'), ...newSettings }); + return true; +}); + +// ─── IPC: Dialogs ───────────────────────────────────────────────────────────── + +ipcMain.handle('dialog:openFile', async (_, options = {}) => { + const result = await dialog.showOpenDialog(mainWindow, { + properties: ['openFile'], + filters: options.filters || [{ name: 'All Files', extensions: ['*'] }], + ...options + }); + return result.canceled ? null : result.filePaths[0]; +}); + +ipcMain.handle('dialog:saveFile', async (_, options = {}) => { + const result = await dialog.showSaveDialog(mainWindow, options); + return result.canceled ? null : result.filePath; +}); + +ipcMain.handle('dialog:selectExecutable', async () => { + const filters = IS_WIN + ? [{ name: 'Executable', extensions: ['exe'] }] + : [{ name: 'All Files', extensions: ['*'] }]; + const result = await dialog.showOpenDialog(mainWindow, { + properties: ['openFile'], + filters + }); + return result.canceled ? null : result.filePaths[0]; +}); + +ipcMain.handle('dialog:selectDirectory', async () => { + const result = await dialog.showOpenDialog(mainWindow, { + properties: ['openDirectory'] + }); + return result.canceled ? null : result.filePaths[0]; +}); + +ipcMain.handle('shell:openPath', (_, p) => shell.openPath(p)); +ipcMain.handle('shell:showItemInFolder', (_, p) => shell.showItemInFolder(p)); + +// ─── IPC: Proxy / IP Check ──────────────────────────────────────────────────── + +/** + * Parse a proxy URL string into { protocol, host, port, username, password } + */ +function parseProxy(proxyStr) { + if (!proxyStr || !proxyStr.trim()) return null; + let s = proxyStr.trim(); + // Strip accidental double-scheme e.g. "socks5://socks5://host:port" + s = s.replace(/^(socks5?[ah]?|https?):\/\/(socks5?[ah]?|https?):\/\//i, '$1://'); + if (!/^[a-z][a-z0-9+\-.]*:\/\//i.test(s)) s = 'socks5://' + s; + try { + const u = new URL(s); + const proto = u.protocol.replace(':', '').toLowerCase(); + const host = u.hostname; + if (!host) return null; + return { + protocol: proto, + host, + port: parseInt(u.port) || (proto.startsWith('http') ? 8080 : 1080), + username: u.username ? decodeURIComponent(u.username) : '', + password: u.password ? decodeURIComponent(u.password) : '', + }; + } catch { return null; } +} + +/** + * Connect through SOCKS5 proxy using raw TCP (no external deps). + * Returns a socket connected to targetHost:targetPort via the proxy. + */ +function connectViaSocks5(proxyHost, proxyPort, targetHost, targetPort, username, password) { + return new Promise((resolve, reject) => { + const sock = net.connect(proxyPort, proxyHost, () => { + // SOCKS5 greeting + const authMethods = (username && password) ? [0x00, 0x02] : [0x00]; + const greeting = Buffer.from([0x05, authMethods.length, ...authMethods]); + sock.write(greeting); + }); + + sock.setTimeout(15000); + sock.on('timeout', () => { sock.destroy(); reject(new Error('SOCKS5 connect timeout')); }); + sock.on('error', reject); + + let state = 'greeting'; + let buf = Buffer.alloc(0); + + sock.on('data', (chunk) => { + buf = Buffer.concat([buf, chunk]); + + if (state === 'greeting') { + if (buf.length < 2) return; + if (buf[0] !== 0x05) { sock.destroy(); reject(new Error('Not a SOCKS5 server')); return; } + const method = buf[1]; + buf = buf.slice(2); + + if (method === 0xFF) { sock.destroy(); reject(new Error('SOCKS5: no acceptable auth method')); return; } + + if (method === 0x02) { + // Username/password auth + state = 'auth'; + const uBuf = Buffer.from(username || '', 'utf8'); + const pBuf = Buffer.from(password || '', 'utf8'); + const authPkt = Buffer.from([0x01, uBuf.length, ...uBuf, pBuf.length, ...pBuf]); + sock.write(authPkt); + } else { + // No auth — send CONNECT + state = 'connect'; + sendSocks5Connect(sock, targetHost, targetPort); + } + return; + } + + if (state === 'auth') { + if (buf.length < 2) return; + if (buf[1] !== 0x00) { sock.destroy(); reject(new Error('SOCKS5 auth failed')); return; } + buf = buf.slice(2); + state = 'connect'; + sendSocks5Connect(sock, targetHost, targetPort); + return; + } + + if (state === 'connect') { + if (buf.length < 10) return; // minimum response + if (buf[0] !== 0x05 || buf[1] !== 0x00) { + const errCodes = { 1: 'General failure', 2: 'Connection not allowed', 3: 'Network unreachable', 4: 'Host unreachable', 5: 'Connection refused' }; + sock.destroy(); + reject(new Error('SOCKS5 connect error: ' + (errCodes[buf[1]] || `code ${buf[1]}`))); + return; + } + // Success — socket is now connected to target + state = 'done'; + sock.removeAllListeners('data'); + resolve({ socket: sock, remaining: buf.slice(10) }); + } + }); + }); +} + +function sendSocks5Connect(sock, host, port) { + const hostBuf = Buffer.from(host, 'utf8'); + const pkt = Buffer.from([ + 0x05, 0x01, 0x00, 0x03, + hostBuf.length, ...hostBuf, + (port >> 8) & 0xFF, port & 0xFF + ]); + sock.write(pkt); +} + +/** + * Do an HTTP GET through a raw socket (used after SOCKS5 tunnel is established). + */ +function httpGetThroughSocket(socket, host, path, remainingData) { + return new Promise((resolve, reject) => { + let data = ''; + const req = `GET ${path} HTTP/1.1\r\nHost: ${host}\r\nConnection: close\r\nUser-Agent: BotBrowserControl/1.0\r\n\r\n`; + socket.write(req); + if (remainingData && remainingData.length > 0) { + data += remainingData.toString(); + } + socket.on('data', (chunk) => { data += chunk.toString(); }); + socket.on('end', () => { + const bodyStart = data.indexOf('\r\n\r\n'); + const body = bodyStart !== -1 ? data.slice(bodyStart + 4) : data; + try { resolve(JSON.parse(body)); } catch (e) { reject(new Error('Invalid JSON response from ip-api.com')); } + }); + socket.on('error', reject); + }); +} + +/** + * Check proxy exit IP via ip-api.com. + * Supports HTTP, HTTPS (CONNECT tunnel), SOCKS4, SOCKS5. + */ +ipcMain.handle('proxy:checkIp', async (_, proxyServer) => { + const API_HOST = 'ip-api.com'; + const API_PATH = '/json/?fields=66846719'; + const API_PORT = 80; + + // No proxy — direct request + if (!proxyServer || !proxyServer.trim()) { + return doDirectHttpGet(`http://${API_HOST}${API_PATH}`); + } + + const proxy = parseProxy(proxyServer); + if (!proxy) throw new Error('Invalid proxy URL'); + + const proto = proxy.protocol.toLowerCase(); + + // SOCKS5 / SOCKS5H + if (proto === 'socks5' || proto === 'socks5h' || proto === 'socks4' || proto === 'socks4a') { + const { socket, remaining } = await connectViaSocks5( + proxy.host, proxy.port, API_HOST, API_PORT, + proxy.username, proxy.password + ); + return httpGetThroughSocket(socket, API_HOST, API_PATH, remaining); + } + + // HTTP / HTTPS proxy — use CONNECT tunnel + return doHttpProxyIpCheck(proxy, API_HOST, API_PATH, API_PORT); +}); + +function doDirectHttpGet(url) { + return new Promise((resolve, reject) => { + const to = setTimeout(() => reject(new Error('Request timeout')), 15000); + const req = http.get(url, { headers: { 'User-Agent': 'BotBrowserControl/1.0' } }, (res) => { + let data = ''; + res.on('data', d => { data += d; }); + res.on('end', () => { + clearTimeout(to); + try { resolve(JSON.parse(data)); } catch { reject(new Error('Invalid JSON')); } + }); + }); + req.on('error', e => { clearTimeout(to); reject(e); }); + req.setTimeout(12000, () => { req.destroy(); clearTimeout(to); reject(new Error('Timeout')); }); + }); +} + +function doHttpProxyIpCheck(proxy, targetHost, targetPath, targetPort) { + return new Promise((resolve, reject) => { + const to = setTimeout(() => reject(new Error('Proxy timeout')), 15000); + + const connectTarget = `${targetHost}:${targetPort}`; + const headers = { 'User-Agent': 'BotBrowserControl/1.0' }; + if (proxy.username && proxy.password) { + headers['Proxy-Authorization'] = 'Basic ' + Buffer.from(`${proxy.username}:${proxy.password}`).toString('base64'); + } + + const connectReq = http.request({ + method: 'CONNECT', + hostname: proxy.host, + port: proxy.port, + path: connectTarget, + headers, + }); + + connectReq.on('connect', (res, socket) => { + if (res.statusCode !== 200) { + socket.destroy(); + clearTimeout(to); + reject(new Error(`Proxy CONNECT rejected: ${res.statusCode}`)); + return; + } + const req = `GET ${targetPath} HTTP/1.1\r\nHost: ${targetHost}\r\nConnection: close\r\nUser-Agent: BotBrowserControl/1.0\r\n\r\n`; + socket.write(req); + let data = ''; + socket.on('data', d => { data += d.toString(); }); + socket.on('end', () => { + clearTimeout(to); + const bodyStart = data.indexOf('\r\n\r\n'); + const body = bodyStart !== -1 ? data.slice(bodyStart + 4) : data; + try { resolve(JSON.parse(body)); } catch { reject(new Error('Invalid JSON')); } + }); + socket.on('error', e => { clearTimeout(to); reject(e); }); + }); + + connectReq.on('error', e => { clearTimeout(to); reject(e); }); + connectReq.setTimeout(12000, () => { connectReq.destroy(); clearTimeout(to); reject(new Error('Connect timeout')); }); + connectReq.end(); + }); +} + +// ─── IPC: Update Checker ────────────────────────────────────────────────────── + +const BOTBROWSER_RELEASES_API = 'https://api.github.com/repos/botswin/BotBrowser/releases/latest'; +const CONTROL_RELEASES_API = 'https://api.github.com/repos/tombaki/BotBrowser/releases/latest'; +const CONTROL_VERSION = '1.2.1'; + +ipcMain.handle('app:checkForUpdates', async () => { + const results = { kernel: null, control: null }; + + try { + const kernelRes = await httpsGet(BOTBROWSER_RELEASES_API); + if (kernelRes.statusCode === 200) { + const release = JSON.parse(kernelRes.body); + results.kernel = { + tagName: release.tag_name, + name: release.name || release.tag_name, + publishedAt: release.published_at, + url: release.html_url, + }; + } + } catch {} + + try { + const controlRes = await httpsGet(CONTROL_RELEASES_API); + if (controlRes.statusCode === 200) { + const release = JSON.parse(controlRes.body); + const tag = release.tag_name || ''; + const remoteVer = tag.replace(/^control-v/, ''); + results.control = { + tagName: tag, + version: remoteVer, + name: release.name || tag, + publishedAt: release.published_at, + url: release.html_url, + isNewer: remoteVer !== CONTROL_VERSION && remoteVer > CONTROL_VERSION, + }; + } + } catch {} + + // Persist last seen so we can detect "new since last check" + const lastKernel = store.get('lastSeenKernelRelease'); + const lastControl = store.get('lastSeenControlRelease'); + + const newKernel = results.kernel && results.kernel.tagName !== lastKernel; + const newControl = results.control && results.control.tagName !== lastControl && results.control.isNewer; + + if (results.kernel?.tagName) store.set('lastSeenKernelRelease', results.kernel.tagName); + if (results.control?.tagName) store.set('lastSeenControlRelease', results.control.tagName); + + results.newKernel = newKernel; + results.newControl = newControl; + + return results; +}); + +// ─── IPC: Kernel Manager ────────────────────────────────────────────────────── + +const KERNEL_GITHUB_API = 'https://api.github.com/repos/botswin/BotBrowser/releases'; + +function httpsGet(url, redirectCount = 0) { + return new Promise((resolve, reject) => { + if (redirectCount > 5) { reject(new Error('Too many redirects')); return; } + const req = https.get(url, { + headers: { + 'User-Agent': 'BotBrowserControl/1.0', + 'Accept': 'application/vnd.github+json', + } + }, (res) => { + if ([301, 302, 303, 307, 308].includes(res.statusCode)) { + const location = res.headers.location; + res.resume(); + if (!location) { reject(new Error('Redirect without location')); return; } + resolve(httpsGet(location, redirectCount + 1)); + return; + } + let data = ''; + res.on('data', d => { data += d; }); + res.on('end', () => resolve({ statusCode: res.statusCode, body: data, headers: res.headers })); + }); + req.on('error', reject); + req.setTimeout(20000, () => { req.destroy(); reject(new Error('Request timeout')); }); + }); +} + +ipcMain.handle('kernel:fetchReleases', async () => { + const res = await httpsGet(KERNEL_GITHUB_API); + if (res.statusCode !== 200) throw new Error(`GitHub API error: ${res.statusCode}`); + const releases = JSON.parse(res.body); + const mapped = releases.slice(0, 20).map(r => ({ + id: r.id, + tagName: r.tag_name, + name: r.name || r.tag_name, + publishedAt: r.published_at, + prerelease: r.prerelease, + body: (r.body || '').slice(0, 500), + assets: (r.assets || []).map(a => ({ + id: a.id, + name: a.name, + size: a.size, + downloadUrl: a.browser_download_url, + contentType: a.content_type, + })) + })); + // Cache releases to store so they persist across restarts + store.set('cachedKernelReleases', mapped); + return mapped; +}); + +// Return cached releases (fast, no network) +ipcMain.handle('kernel:getCachedReleases', () => { + return store.get('cachedKernelReleases', null); +}); + +function getKernelsDir() { + return path.join(app.getPath('userData'), 'kernels'); +} + +ipcMain.handle('kernel:getDir', () => getKernelsDir()); + +ipcMain.handle('kernel:listInstalled', () => { + const dir = getKernelsDir(); + if (!fs.existsSync(dir)) return []; + return fs.readdirSync(dir, { withFileTypes: true }) + .filter(e => e.isDirectory()) + .map(e => { + const vdir = path.join(dir, e.name); + const meta = path.join(vdir, '.meta.json'); + let info = { version: e.name, installedAt: null, platform: null, execPath: null }; + if (fs.existsSync(meta)) { + try { info = { ...info, ...JSON.parse(fs.readFileSync(meta, 'utf8')) }; } catch {} + } + return info; + }); +}); + +ipcMain.handle('kernel:delete', (_, version) => { + const dir = path.join(getKernelsDir(), version); + if (fs.existsSync(dir)) { + fs.rmSync(dir, { recursive: true, force: true }); + return true; + } + return false; +}); + +/** + * Download a kernel asset, then auto-install it. + * - macOS .dmg: mount with hdiutil, copy .app to /Applications (xattr -rd + codesign -f) + * - Linux .deb: install with dpkg -i (requires sudo) or just mark as ready + * - Linux .AppImage: chmod +x + * - Windows .7z/.zip: extract with built-in tools + */ +ipcMain.handle('kernel:download', async (_, { downloadUrl, fileName, version }) => { + const kernelsDir = getKernelsDir(); + const versionDir = path.join(kernelsDir, version); + fs.mkdirSync(versionDir, { recursive: true }); + + const destPath = path.join(versionDir, fileName); + + // Download first + await new Promise((resolve, reject) => { + function doDownload(url, redirectCount) { + if (redirectCount > 5) { reject(new Error('Too many redirects')); return; } + const parsedUrl = new URL(url); + const protocol = parsedUrl.protocol === 'https:' ? https : http; + const req = protocol.get(url, { headers: { 'User-Agent': 'BotBrowserControl/1.0' } }, (res) => { + if ([301, 302, 303, 307, 308].includes(res.statusCode)) { + const location = res.headers.location; + res.resume(); + if (!location) { reject(new Error('Redirect without location')); return; } + doDownload(location, redirectCount + 1); + return; + } + if (res.statusCode !== 200) { reject(new Error(`Download failed: HTTP ${res.statusCode}`)); return; } + + const total = parseInt(res.headers['content-length'] || '0', 10); + let downloaded = 0; + const fileStream = fs.createWriteStream(destPath); + + res.on('data', (chunk) => { + downloaded += chunk.length; + if (total > 0) { + const progress = Math.round((downloaded / total) * 100); + mainWindow?.webContents.send('kernel:downloadProgress', { version, progress, downloaded, total }); + } + }); + + res.pipe(fileStream); + fileStream.on('finish', () => { fileStream.close(resolve); }); + fileStream.on('error', reject); + }); + req.on('error', reject); + req.setTimeout(180000, () => { req.destroy(); reject(new Error('Download timeout')); }); + } + doDownload(downloadUrl, 0); + }); + + // Auto-install + let execPath = null; + let installStatus = 'downloaded'; + let installNote = ''; + + try { + if (IS_MAC && fileName.endsWith('.dmg')) { + // Mount DMG, copy .app to /Applications, unmount + const mountResult = await runCmd('hdiutil', ['attach', '-nobrowse', '-noverify', '-noautoopen', destPath]); + // Find mount point from output (last line with /Volumes/) + const mountPoint = (mountResult.stdout || '').split('\n') + .map(l => l.trim()) + .filter(l => l.includes('/Volumes/')) + .pop()?.split(/\s+/).pop(); + + if (mountPoint && fs.existsSync(mountPoint)) { + // Find .app bundle in mount + const apps = fs.readdirSync(mountPoint).filter(f => f.endsWith('.app')); + if (apps.length > 0) { + const appName = apps[0]; + const srcApp = path.join(mountPoint, appName); + const dstApp = path.join('/Applications', appName); + + // Remove old if exists + if (fs.existsSync(dstApp)) { + await runCmd('rm', ['-rf', dstApp]); + } + + // Copy .app to /Applications + await runCmd('cp', ['-R', srcApp, '/Applications/']); + + // Remove quarantine flag (xattr) so unsigned app can open + try { await runCmd('xattr', ['-rd', 'com.apple.quarantine', dstApp]); } catch {} + // Ad-hoc codesign to allow launch + try { await runCmd('codesign', ['--force', '--deep', '--sign', '-', dstApp]); } catch {} + + // Find the actual executable inside .app + const infoPlistPath = path.join(dstApp, 'Contents', 'Info.plist'); + let execName = 'Chromium'; + if (fs.existsSync(infoPlistPath)) { + const plistContent = fs.readFileSync(infoPlistPath, 'utf8'); + const match = plistContent.match(/CFBundleExecutable<\/key>\s*([^<]+)<\/string>/); + if (match) execName = match[1]; + } + execPath = path.join(dstApp, 'Contents', 'MacOS', execName); + installStatus = 'installed'; + installNote = `Installed to /Applications/${appName}`; + } + // Unmount + try { await runCmd('hdiutil', ['detach', mountPoint, '-quiet']); } catch {} + } + } else if (IS_LINUX && fileName.endsWith('.AppImage')) { + fs.chmodSync(destPath, 0o755); + execPath = destPath; + installStatus = 'ready'; + installNote = 'AppImage is ready to use'; + } else if (IS_LINUX && fileName.endsWith('.deb')) { + // Try to install with pkexec/sudo dpkg + try { + await runCmd('pkexec', ['dpkg', '-i', destPath]); + installStatus = 'installed'; + installNote = 'Installed via dpkg'; + execPath = '/usr/bin/botbrowser'; + } catch { + installStatus = 'downloaded'; + installNote = `Run: sudo dpkg -i ${destPath}`; + execPath = null; + } + } else if (IS_WIN && (fileName.endsWith('.zip') || fileName.endsWith('.7z'))) { + // Extract to versionDir + if (fileName.endsWith('.zip')) { + await runCmd('powershell', ['-Command', `Expand-Archive -Force -Path "${destPath}" -DestinationPath "${versionDir}"`]); + } + // Find .exe + const exes = findFilesRecursive(versionDir, '.exe').filter(f => !f.includes('Uninstall')); + execPath = exes[0] || null; + installStatus = 'extracted'; + installNote = 'Extracted successfully'; + } else if (IS_WIN && fileName.endsWith('.exe')) { + execPath = destPath; + installStatus = 'ready'; + installNote = 'Installer ready — run to install'; + } + } catch (installErr) { + installNote = `Install step failed: ${installErr.message}`; + } + + const meta = { + version, installedAt: new Date().toISOString(), + platform: process.platform, fileName, execPath, downloadUrl, + installStatus, installNote, + }; + fs.writeFileSync(path.join(versionDir, '.meta.json'), JSON.stringify(meta, null, 2), 'utf8'); + + mainWindow?.webContents.send('kernel:downloadComplete', { version, execPath, destPath, installStatus, installNote }); + return { version, execPath, destPath, installStatus, installNote }; +}); + +function runCmd(cmd, args) { + return new Promise((resolve, reject) => { + const proc = execFile(cmd, args, { timeout: 120000 }, (err, stdout, stderr) => { + if (err) reject(new Error(stderr || err.message)); + else resolve({ stdout, stderr }); + }); + }); +} + +function findFilesRecursive(dir, ext) { + const results = []; + if (!fs.existsSync(dir)) return results; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) results.push(...findFilesRecursive(full, ext)); + else if (entry.name.toLowerCase().endsWith(ext)) results.push(full); + } + return results; +} + +// ─── Config File Helpers ────────────────────────────────────────────────────── + +function writeStandaloneConfigFile(profile, profileId) { + const configs = buildConfigsBlock(profile); + const json = JSON.stringify({ configs }, null, 2); + const tmpPath = path.join(os.tmpdir(), `botbrowser-config-${profileId}.json`); + fs.writeFileSync(tmpPath, json, 'utf8'); + tempFiles.set(profileId, tmpPath); + return tmpPath; +} + +function injectConfigsIntoEncFile(encFilePath, profile, profileId) { + try { + const raw = fs.readFileSync(encFilePath, 'utf8'); + const enc = JSON.parse(raw); + enc.configs = buildConfigsBlock(profile); + const tmpPath = path.join(os.tmpdir(), `botbrowser-enc-${profileId}.json`); + fs.writeFileSync(tmpPath, JSON.stringify(enc, null, 2), 'utf8'); + tempFiles.set(profileId, tmpPath); + return tmpPath; + } catch (e) { + return writeStandaloneConfigFile(profile, profileId); + } +} + +function cleanupTempFile(profileId) { + const p = tempFiles.get(profileId); + if (p && fs.existsSync(p)) { + try { fs.unlinkSync(p); } catch {} + } + tempFiles.delete(profileId); +} + +function buildConfigsBlock(profile) { + const configs = {}; + + if (profile.locale) configs.locale = profile.locale; + if (profile.languages) configs.languages = profile.languages; + if (profile.timezone) configs.timezone = profile.timezone; + + if (profile.location) { + if (typeof profile.location === 'string' && /^-?\d+(\.\d+)?,-?\d+(\.\d+)?$/.test(profile.location.trim())) { + const [lat, lon] = profile.location.split(',').map(Number); + configs.location = { latitude: lat, longitude: lon }; + } else { + configs.location = profile.location; + } + } + + if (profile.colorScheme) configs.colorScheme = profile.colorScheme; + if (profile.browserBrand) configs.browserBrand = profile.browserBrand; + if (profile.brandFullVersion) configs.brandFullVersion = profile.brandFullVersion; + if (profile.uaFullVersion) configs.uaFullVersion = profile.uaFullVersion; + + if (profile.platform) configs.platform = profile.platform; + if (profile.platformVersion) configs.platformVersion = profile.platformVersion; + if (profile.model) configs.model = profile.model; + if (profile.architecture) configs.architecture = profile.architecture; + if (profile.bitness) configs.bitness = profile.bitness; + if (profile.mobile !== undefined && profile.mobile !== '') configs.mobile = !!profile.mobile; + + if (profile.proxyServer) { + configs.proxy = { server: profile.proxyServer }; + if (profile.proxyIp) configs.proxy.ip = profile.proxyIp; + } + + if (profile.customHeaders && typeof profile.customHeaders === 'object' && Object.keys(profile.customHeaders).length > 0) { + configs.customHeaders = profile.customHeaders; + } + + if (profile.windowSize) configs.window = profile.windowSize; + if (profile.screenSize) configs.screen = profile.screenSize; + if (profile.orientation) configs.orientation = profile.orientation; + if (profile.disableDeviceScaleFactorOnGUI) configs.disableDeviceScaleFactorOnGUI = true; + + if (profile.webgl) configs.webgl = profile.webgl; + if (profile.webgpu) configs.webgpu = profile.webgpu; + if (profile.webrtc) configs.webrtc = profile.webrtc; + if (profile.webrtcICE) configs.webrtcICE = profile.webrtcICE; + if (profile.speechVoices) configs.speechVoices = profile.speechVoices; + if (profile.mediaDevices) configs.mediaDevices = profile.mediaDevices; + if (profile.mediaTypes) configs.mediaTypes = profile.mediaTypes; + if (profile.fonts) configs.fonts = profile.fonts; + if (profile.keyboard) configs.keyboard = profile.keyboard; + + if (profile.noiseCanvas !== undefined && profile.noiseCanvas !== '') configs.noiseCanvas = !!profile.noiseCanvas; + if (profile.noiseWebglImage !== undefined && profile.noiseWebglImage !== '') configs.noiseWebglImage = !!profile.noiseWebglImage; + if (profile.noiseAudioContext !== undefined && profile.noiseAudioContext !== '') configs.noiseAudioContext = !!profile.noiseAudioContext; + if (profile.noiseClientRects !== undefined && profile.noiseClientRects !== '') configs.noiseClientRects = !!profile.noiseClientRects; + if (profile.noiseTextRects !== undefined && profile.noiseTextRects !== '') configs.noiseTextRects = !!profile.noiseTextRects; + + if (profile.alwaysActive !== undefined) configs.alwaysActive = !!profile.alwaysActive; + if (profile.disableDebugger !== undefined) configs.disableDebugger = !!profile.disableDebugger; + if (profile.disableConsoleMessage !== undefined) configs.disableConsoleMessage = !!profile.disableConsoleMessage; + if (profile.portProtection !== undefined) configs.portProtection = !!profile.portProtection; + if (profile.mobileForceTouch !== undefined) configs.mobileForceTouch = !!profile.mobileForceTouch; + if (profile.networkInfoOverride !== undefined) configs.networkInfoOverride = !!profile.networkInfoOverride; + if (profile.enableVariationsInContext !== undefined) configs.enableVariationsInContext = !!profile.enableVariationsInContext; + + if (profile.injectRandomHistory !== undefined && profile.injectRandomHistory !== '') { + const v = profile.injectRandomHistory; + if (v === true || v === 'true') configs.injectRandomHistory = true; + else if (v === false || v === 'false') configs.injectRandomHistory = false; + else if (!isNaN(Number(v))) configs.injectRandomHistory = Number(v); + } + + if (profile.fps !== undefined && profile.fps !== '' && profile.fps !== 'profile') configs.fps = isNaN(Number(profile.fps)) ? profile.fps : Number(profile.fps); + if (profile.timeScale !== undefined && profile.timeScale !== '') { const ts = parseFloat(profile.timeScale); if (!isNaN(ts)) configs.timeScale = ts; } + if (profile.noiseSeed !== undefined && profile.noiseSeed !== '') { const ns = parseInt(profile.noiseSeed); if (!isNaN(ns)) configs.noiseSeed = ns; } + if (profile.timeSeed !== undefined && profile.timeSeed !== '') { const ts = parseInt(profile.timeSeed); if (!isNaN(ts)) configs.timeSeed = ts; } + if (profile.stackSeed !== undefined && profile.stackSeed !== '') { + const ss = profile.stackSeed; + if (ss === 'profile' || ss === 'real') configs.stackSeed = ss; + else if (!isNaN(parseInt(ss))) configs.stackSeed = parseInt(ss); + } + + return configs; +} + +function buildLaunchArgs(profile, userDataDir, botProfileArg) { + const args = []; + + if (botProfileArg) args.push(`--bot-profile=${botProfileArg}`); + if (profile.profileDirPath && fs.existsSync(profile.profileDirPath)) { + args.push(`--bot-profile-dir=${profile.profileDirPath}`); + } + + args.push(`--user-data-dir=${userDataDir}`); + args.push('--restore-last-session'); + args.push(`--no-first-run`); + + if (profile.name) args.push(`--bot-title=${profile.name}`); + + if (profile.proxyServer && profile.proxyServer.trim()) args.push(`--proxy-server=${profile.proxyServer.trim()}`); + if (profile.proxyIp && profile.proxyIp.trim()) args.push(`--proxy-ip=${profile.proxyIp.trim()}`); + if (profile.proxyBypassRgx && profile.proxyBypassRgx.trim()) args.push(`--proxy-bypass-rgx=${profile.proxyBypassRgx.trim()}`); + + if (profile.browserBrand && profile.browserBrand !== '') args.push(`--bot-config-browser-brand=${profile.browserBrand}`); + if (profile.brandFullVersion && profile.brandFullVersion !== '') args.push(`--bot-config-brand-full-version=${profile.brandFullVersion}`); + if (profile.uaFullVersion && profile.uaFullVersion !== '') args.push(`--bot-config-ua-full-version=${profile.uaFullVersion}`); + if (profile.userAgent && profile.userAgent.trim()) args.push(`--user-agent=${profile.userAgent.trim()}`); + + if (profile.locale && profile.locale !== '') args.push(`--bot-config-locale=${profile.locale}`); + if (profile.timezone && profile.timezone !== '') args.push(`--bot-config-timezone=${profile.timezone}`); + if (profile.languages && profile.languages !== '') args.push(`--bot-config-languages=${profile.languages}`); + if (profile.location && profile.location !== '') args.push(`--bot-config-location=${profile.location}`); + if (profile.colorScheme) args.push(`--bot-config-color-scheme=${profile.colorScheme}`); + + if (profile.platform && profile.platform !== '') args.push(`--bot-config-platform=${profile.platform}`); + if (profile.platformVersion) args.push(`--bot-config-platform-version=${profile.platformVersion}`); + if (profile.model) args.push(`--bot-config-model=${profile.model}`); + if (profile.architecture) args.push(`--bot-config-architecture=${profile.architecture}`); + if (profile.bitness) args.push(`--bot-config-bitness=${profile.bitness}`); + if (profile.mobile !== undefined && profile.mobile !== '') args.push(`--bot-config-mobile=${!!profile.mobile}`); + + if (profile.windowSize) args.push(`--bot-config-window=${profile.windowSize}`); + if (profile.screenSize) args.push(`--bot-config-screen=${profile.screenSize}`); + if (profile.orientation) args.push(`--bot-config-orientation=${profile.orientation}`); + if (profile.keyboard) args.push(`--bot-config-keyboard=${profile.keyboard}`); + if (profile.fonts) args.push(`--bot-config-fonts=${profile.fonts}`); + if (profile.disableDeviceScaleFactorOnGUI) args.push('--bot-config-disable-device-scale-factor'); + + if (profile.webgl) args.push(`--bot-config-webgl=${profile.webgl}`); + if (profile.webgpu) args.push(`--bot-config-webgpu=${profile.webgpu}`); + if (profile.webrtc) args.push(`--bot-config-webrtc=${profile.webrtc}`); + if (profile.speechVoices) args.push(`--bot-config-speech-voices=${profile.speechVoices}`); + if (profile.mediaDevices) args.push(`--bot-config-media-devices=${profile.mediaDevices}`); + if (profile.mediaTypes) args.push(`--bot-config-media-types=${profile.mediaTypes}`); + + if (profile.noiseCanvas !== undefined && profile.noiseCanvas !== '') args.push(`--bot-config-noise-canvas=${!!profile.noiseCanvas}`); + if (profile.noiseWebglImage !== undefined && profile.noiseWebglImage !== '') args.push(`--bot-config-noise-webgl-image=${!!profile.noiseWebglImage}`); + if (profile.noiseAudioContext !== undefined && profile.noiseAudioContext !== '') args.push(`--bot-config-noise-audio-context=${!!profile.noiseAudioContext}`); + if (profile.noiseClientRects !== undefined && profile.noiseClientRects !== '') args.push(`--bot-config-noise-client-rects=${!!profile.noiseClientRects}`); + if (profile.noiseTextRects !== undefined && profile.noiseTextRects !== '') args.push(`--bot-config-noise-text-rects=${!!profile.noiseTextRects}`); + + if (profile.disableDebugger === true || profile.disableDebugger === 'true') args.push('--bot-disable-debugger'); + if (!(profile.disableConsoleMessage === false || profile.disableConsoleMessage === 'false')) args.push('--bot-disable-console-message'); + args.push('--bot-always-active'); + if (profile.portProtection === true || profile.portProtection === 'true') args.push('--bot-port-protection'); + if (profile.localDns === true || profile.localDns === 'true') args.push('--bot-local-dns'); + if (profile.mobileForceTouch === true || profile.mobileForceTouch === 'true') args.push('--bot-mobile-force-touch'); + if (profile.enableVariationsInContext === true || profile.enableVariationsInContext === 'true') args.push('--bot-enable-variations-in-context'); + if (profile.networkInfoOverride === true || profile.networkInfoOverride === 'true') args.push('--bot-network-info-override'); + + if (profile.injectRandomHistory !== undefined && profile.injectRandomHistory !== '') { + const v = profile.injectRandomHistory; + if (v === true || v === 'true') args.push('--bot-inject-random-history=true'); + else if (v !== false && v !== 'false' && !isNaN(Number(v))) args.push(`--bot-inject-random-history=${Number(v)}`); + } + + if (profile.webrtcICE && profile.webrtcICE !== 'profile' && profile.webrtcICE !== '') { + args.push(`--bot-webrtc-ice=${profile.webrtcICE}`); + } + + if (profile.noiseSeed !== undefined && profile.noiseSeed !== '') { const ns = parseInt(profile.noiseSeed); if (!isNaN(ns) && ns >= 0) args.push(`--bot-noise-seed=${ns}`); } + if (profile.timeSeed !== undefined && profile.timeSeed !== '') { const ts = parseInt(profile.timeSeed); if (!isNaN(ts) && ts >= 0) args.push(`--bot-time-seed=${ts}`); } + if (profile.stackSeed !== undefined && profile.stackSeed !== '') { + const ss = profile.stackSeed; + if (ss === 'profile' || ss === 'real') args.push(`--bot-stack-seed=${ss}`); + else if (!isNaN(parseInt(ss))) args.push(`--bot-stack-seed=${parseInt(ss)}`); + } + if (profile.timeScale !== undefined && profile.timeScale !== '') { const ts = parseFloat(profile.timeScale); if (!isNaN(ts) && ts > 0 && ts < 1) args.push(`--bot-time-scale=${ts}`); } + if (profile.fps !== undefined && profile.fps !== '' && profile.fps !== 'profile') args.push(`--bot-fps=${profile.fps}`); + + if (profile.gpuEmulation === false || profile.gpuEmulation === 'false') args.push('--bot-gpu-emulation=false'); + + if (profile.customHeaders && typeof profile.customHeaders === 'object' && Object.keys(profile.customHeaders).length > 0) { + args.push('--bot-custom-headers=' + JSON.stringify(profile.customHeaders)); + } + + if (profile.ipService) args.push(`--bot-ip-service=${profile.ipService}`); + + if (profile.mirrorController) args.push(`--bot-mirror-controller-endpoint=${profile.mirrorController}`); + if (profile.mirrorClient) args.push(`--bot-mirror-client-endpoint=${profile.mirrorClient}`); + + if (profile.canvasRecordFile) args.push(`--bot-canvas-record-file=${profile.canvasRecordFile}`); + if (profile.audioRecordFile) args.push(`--bot-audio-record-file=${profile.audioRecordFile}`); + + if (profile.botScript) args.push(`--bot-script=${profile.botScript}`); + + if (profile.remoteDebuggingPort) args.push(`--remote-debugging-port=${profile.remoteDebuggingPort}`); + + if (profile.cookies && profile.cookies.trim()) { + const cookieVal = profile.cookies.trim(); + if (cookieVal.startsWith('@')) { + args.push(`--bot-cookies=${cookieVal}`); + } else { + try { JSON.parse(cookieVal); args.push('--bot-cookies=' + cookieVal); } catch {; } + } + } + + if (profile.bookmarks && profile.bookmarks.trim()) { + try { JSON.parse(profile.bookmarks.trim()); args.push('--bot-bookmarks=' + profile.bookmarks.trim()); } catch {; } + } + + if (profile.startUrl && profile.startUrl.trim()) args.push(profile.startUrl.trim()); + + return args; +} + +// ─── CDP Cookie Save ────────────────────────────────────────────────────────── + +async function saveCookiesViaCDP(profileId, port, userDataDir) { + const cookies = await fetchCookiesViaCDP(port); + if (!Array.isArray(cookies)) return; + + const savePath = path.join(userDataDir, 'saved-cookies.json'); + fs.mkdirSync(userDataDir, { recursive: true }); + fs.writeFileSync(savePath, JSON.stringify(cookies, null, 2), 'utf8'); + + const profiles = store.get('profiles', []); + const idx = profiles.findIndex(p => p.id === profileId); + if (idx !== -1) { + profiles[idx].cookieCount = cookies.length; + profiles[idx].cookiesSavedAt = new Date().toISOString(); + profiles[idx].savedCookiesPath = savePath; + store.set('profiles', profiles); + } + + mainWindow?.webContents.send('profile:cookiesSaved', { profileId, count: cookies.length, path: savePath }); +} + +function fetchCookiesViaCDP(port) { + return new Promise((resolve, reject) => { + const crypto = require('crypto'); + const timeout = setTimeout(() => reject(new Error('CDP timeout')), 5000); + + const httpClient = net.createConnection({ port, host: '127.0.0.1' }, () => { + httpClient.write('GET /json/list HTTP/1.1\r\nHost: 127.0.0.1:' + port + '\r\nConnection: close\r\n\r\n'); + }); + let httpData = ''; + httpClient.on('data', d => { httpData += d.toString(); }); + httpClient.on('end', () => { + try { + const body = httpData.slice(httpData.indexOf('\r\n\r\n') + 4); + const tabs = JSON.parse(body); + const tab = tabs.find(t => t.type === 'page') || tabs[0]; + if (!tab?.webSocketDebuggerUrl) { clearTimeout(timeout); reject(new Error('No debuggable tab')); return; } + const wsPath = tab.webSocketDebuggerUrl.replace(/^ws:\/\/[^/]+/, ''); + + const wsClient = net.createConnection({ port, host: '127.0.0.1' }, () => { + const key = crypto.randomBytes(16).toString('base64'); + wsClient.write('GET ' + wsPath + ' HTTP/1.1\r\nHost: 127.0.0.1:' + port + '\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: ' + key + '\r\nSec-WebSocket-Version: 13\r\n\r\n'); + }); + + let wsHandshakeDone = false, wsBuffer = Buffer.alloc(0); + + function sendWsFrame(payload) { + const data = Buffer.from(JSON.stringify(payload)); + const maskKey = crypto.randomBytes(4); + const masked = Buffer.alloc(data.length); + for (let i = 0; i < data.length; i++) masked[i] = data[i] ^ maskKey[i % 4]; + let header; + if (data.length < 126) { header = Buffer.alloc(6); header[0] = 0x81; header[1] = 0x80 | data.length; maskKey.copy(header, 2); } + else if (data.length < 65536) { header = Buffer.alloc(8); header[0] = 0x81; header[1] = 0x80 | 126; header.writeUInt16BE(data.length, 2); maskKey.copy(header, 4); } + else { header = Buffer.alloc(14); header[0] = 0x81; header[1] = 0x80 | 127; header.writeBigUInt64BE(BigInt(data.length), 2); maskKey.copy(header, 10); } + wsClient.write(Buffer.concat([header, masked])); + } + + function parseWsFrames(buf) { + const messages = []; let offset = 0; + while (offset + 2 <= buf.length) { + const b1 = buf[offset + 1]; const isMasked = (b1 & 0x80) !== 0; + let payloadLen = b1 & 0x7f, headerLen = 2; + if (payloadLen === 126) { if (offset + 4 > buf.length) break; payloadLen = buf.readUInt16BE(offset + 2); headerLen = 4; } + else if (payloadLen === 127) { if (offset + 10 > buf.length) break; payloadLen = Number(buf.readBigUInt64BE(offset + 2)); headerLen = 10; } + const maskLen = isMasked ? 4 : 0; + const frameEnd = offset + headerLen + maskLen + payloadLen; + if (frameEnd > buf.length) break; + let payload = buf.slice(offset + headerLen + maskLen, frameEnd); + if (isMasked) { const mask = buf.slice(offset + headerLen, offset + headerLen + 4); payload = Buffer.from(payload); for (let i = 0; i < payload.length; i++) payload[i] ^= mask[i % 4]; } + messages.push(payload.toString('utf8')); offset = frameEnd; + } + return { messages, remaining: buf.slice(offset) }; + } + + wsClient.on('data', (chunk) => { + if (!wsHandshakeDone) { + if (chunk.indexOf('\r\n\r\n') !== -1) { + wsHandshakeDone = true; + const rest = chunk.slice(chunk.indexOf('\r\n\r\n') + 4); + if (rest.length > 0) wsBuffer = Buffer.concat([wsBuffer, rest]); + sendWsFrame({ id: 1, method: 'Network.getAllCookies', params: {} }); + } + return; + } + wsBuffer = Buffer.concat([wsBuffer, chunk]); + const { messages, remaining } = parseWsFrames(wsBuffer); + wsBuffer = remaining; + for (const msg of messages) { + try { + const parsed = JSON.parse(msg); + if (parsed.id === 1 && parsed.result?.cookies) { + clearTimeout(timeout); wsClient.destroy(); resolve(parsed.result.cookies); return; + } + } catch {} + } + }); + wsClient.on('error', e => { clearTimeout(timeout); reject(e); }); + } catch (e) { clearTimeout(timeout); reject(e); } + }); + httpClient.on('error', e => { clearTimeout(timeout); reject(e); }); + }); +} + +// ─── Profile Status ─────────────────────────────────────────────────────────── + +function updateProfileStatus(profileId, status) { + const profiles = store.get('profiles', []); + const idx = profiles.findIndex(p => p.id === profileId); + if (idx !== -1) { + profiles[idx].status = status; + store.set('profiles', profiles); + mainWindow?.webContents.send('profile:statusChanged', { profileId, status }); + } +} + +// ─── App Lifecycle ──────────────────────────────────────────────────────────── + +app.whenReady().then(() => { + nativeTheme.themeSource = 'dark'; + + const profiles = store.get('profiles', []); + const hadStale = profiles.some(p => p.status === 'running'); + if (hadStale) { + store.set('profiles', profiles.map(p => + p.status === 'running' ? { ...p, status: 'stopped' } : p + )); + } + + createWindow(); + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) createWindow(); + }); +}); + +app.on('window-all-closed', () => { + for (const [, inst] of runningInstances) { try { inst.process.kill(); } catch {} } + for (const [profileId] of tempFiles) { cleanupTempFile(profileId); } + if (!IS_MAC) app.quit(); +}); + +app.on('before-quit', () => { + for (const [, inst] of runningInstances) { try { inst.process.kill(); } catch {} } + for (const [profileId] of tempFiles) { cleanupTempFile(profileId); } +}); \ No newline at end of file diff --git a/botbrowser-control/src/preload/preload.js b/botbrowser-control/src/preload/preload.js new file mode 100644 index 0000000..91cc9c2 --- /dev/null +++ b/botbrowser-control/src/preload/preload.js @@ -0,0 +1,85 @@ +const { contextBridge, ipcRenderer } = require('electron'); + +contextBridge.exposeInMainWorld('api', { + // Profiles + profiles: { + getAll: () => ipcRenderer.invoke('profiles:getAll'), + create: (data) => ipcRenderer.invoke('profiles:create', data), + update: (id, updates) => ipcRenderer.invoke('profiles:update', { id, updates }), + delete: (id) => ipcRenderer.invoke('profiles:delete', id), + deleteMultiple: (ids) => ipcRenderer.invoke('profiles:deleteMultiple', ids), + duplicate: (id) => ipcRenderer.invoke('profiles:duplicate', id), + }, + + // Browser instances + browser: { + launch: (profileId) => ipcRenderer.invoke('browser:launch', profileId), + stop: (profileId) => ipcRenderer.invoke('browser:stop', profileId), + stopAll: () => ipcRenderer.invoke('browser:stopAll'), + getRunning: () => ipcRenderer.invoke('browser:getRunning'), + }, + + // Settings + settings: { + get: () => ipcRenderer.invoke('settings:get'), + set: (data) => ipcRenderer.invoke('settings:set', data), + }, + + // Dialogs + dialog: { + openFile: (options) => ipcRenderer.invoke('dialog:openFile', options), + saveFile: (options) => ipcRenderer.invoke('dialog:saveFile', options), + selectExecutable: () => ipcRenderer.invoke('dialog:selectExecutable'), + selectDirectory: () => ipcRenderer.invoke('dialog:selectDirectory'), + }, + + // Shell + shell: { + openPath: (p) => ipcRenderer.invoke('shell:openPath', p), + showItemInFolder: (p) => ipcRenderer.invoke('shell:showItemInFolder', p), + }, + + // Proxy / IP Check + proxy: { + checkIp: (proxyServer) => ipcRenderer.invoke('proxy:checkIp', proxyServer), + }, + + // Kernel Manager + kernel: { + fetchReleases: () => ipcRenderer.invoke('kernel:fetchReleases'), + getCachedReleases: () => ipcRenderer.invoke('kernel:getCachedReleases'), + getDir: () => ipcRenderer.invoke('kernel:getDir'), + listInstalled: () => ipcRenderer.invoke('kernel:listInstalled'), + delete: (version) => ipcRenderer.invoke('kernel:delete', version), + download: (opts) => ipcRenderer.invoke('kernel:download', opts), + }, + + // App / Update checker + app: { + checkForUpdates: () => ipcRenderer.invoke('app:checkForUpdates'), + }, + + // Platform info + platform: process.platform, + arch: process.arch, + + // Event listeners + on: (channel, callback) => { + const validChannels = [ + 'navigate', 'action', + 'instance:started', 'instance:stopped', 'instance:error', + 'profile:statusChanged', 'profile:cookiesSaved', + 'kernel:downloadProgress', 'kernel:downloadComplete', 'kernel:downloadError', + 'app:updateAvailable', + ]; + if (validChannels.includes(channel)) { + const sub = (_, ...args) => callback(...args); + ipcRenderer.on(channel, sub); + return () => ipcRenderer.removeListener(channel, sub); + } + }, + + once: (channel, callback) => { + ipcRenderer.once(channel, (_, ...args) => callback(...args)); + } +}); \ No newline at end of file diff --git a/botbrowser-control/src/renderer/index.html b/botbrowser-control/src/renderer/index.html new file mode 100644 index 0000000..ff6de56 --- /dev/null +++ b/botbrowser-control/src/renderer/index.html @@ -0,0 +1,104 @@ + + + + + + + BotBrowser Control + + + + + + +
+
+
+
+ + BotBrowser Control +
+
+ +
+
+ +
+ + +
+
+
+ +
+ + + + + + \ No newline at end of file diff --git a/botbrowser-control/src/renderer/js/app.js b/botbrowser-control/src/renderer/js/app.js new file mode 100644 index 0000000..1ff69ae --- /dev/null +++ b/botbrowser-control/src/renderer/js/app.js @@ -0,0 +1,1918 @@ +/* BotBrowser Control — Renderer */ +'use strict'; + +(function () { + // ─── State ──────────────────────────────────────────────────────────────────── + let profiles = []; + let runningSessions = []; + let currentView = 'profiles'; + let editingProfileId = null; + let searchQuery = ''; + let selectedProfileIds = new Set(); + let settings = {}; + const IS_WIN = window.api.platform === 'win32'; + const IS_MAC = window.api.platform === 'darwin'; + + // Kernel manager state + let kernelReleases = null; + let kernelInstalled = []; + let kernelDownloads = {}; + + // IP check state (profileId -> result) + let ipCheckResults = {}; + let ipCheckLoading = {}; + + // Inline proxy edit state (profileId being edited) + let inlineProxyEditId = null; + + // Update notification state + let updateInfo = null; + + // ─── Icons ──────────────────────────────────────────────────────────────────── + const I = { + play: '', + stop: '', + plus: '', + edit: '', + trash: '', + copy: '', + settings: '', + profile: '', + session: '', + shield: '', + network: '', + cpu: '', + zap: '', + user: '', + cookie: '', + check: '', + folder: '', + globe: '', + info: '', + download: '', + kernel: '', + proxy: '', + android: '', + refresh: '', + use: '', + bell: '', + close: '', + }; + + // ─── Helpers ────────────────────────────────────────────────────────────────── + function esc(s) { return String(s||'').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } + function el(id) { return document.getElementById(id); } + function val(id) { const e = el(id); return e ? e.value.trim() : ''; } + function chk(id) { const e = el(id); return e ? e.checked : false; } + function selVal(id) { const e = el(id); return e ? e.value : ''; } + + function timeAgo(iso) { + if (!iso) return ''; + const s = Math.floor((Date.now() - new Date(iso)) / 1000); + if (s < 60) return 'just now'; + if (s < 3600) return Math.floor(s/60) + 'm ago'; + if (s < 86400) return Math.floor(s/3600) + 'h ago'; + return Math.floor(s/86400) + 'd ago'; + } + + function showToast(msg, type = 'info', duration = 3500) { + const t = document.createElement('div'); + t.className = `toast toast-${type}`; + t.innerHTML = `${msg}`; + document.getElementById('toast-container').appendChild(t); + requestAnimationFrame(() => t.classList.add('show')); + setTimeout(() => { t.classList.remove('show'); setTimeout(() => t.remove(), 300); }, duration); + } + + function countryCodeToEmoji(cc) { + if (!cc || cc.length !== 2) return '🌐'; + return String.fromCodePoint(...[...cc.toUpperCase()].map(c => 0x1F1E6 - 65 + c.charCodeAt(0))); + } + + // Normalize proxy string: strip duplicate schemes, then default to socks5:// if no scheme + function normalizeProxy(s) { + if (!s || !s.trim()) return ''; + s = s.trim(); + // Strip duplicate scheme: e.g. socks5://socks5:// → socks5:// + s = s.replace(/^(socks5?[ah]?|https?):\/\/(socks5?[ah]?|https?):\/\//i, '$1://'); + if (!/^[a-z]+:\/\//i.test(s)) return 'socks5://' + s; + return s; + } + + // ─── Init ───────────────────────────────────────────────────────────────────── + async function init() { + settings = await window.api.settings.get(); + await loadProfiles(); + await refreshRunningSessions(); + // Load cached kernel releases so Kernel Manager shows immediately on open + await loadCachedKernelReleases(); + render(); + bindNav(); + bindEvents(); + // Check for updates in background after 2s + setTimeout(checkForUpdates, 2000); + } + + async function loadProfiles() { + profiles = await window.api.profiles.getAll(); + } + + async function refreshRunningSessions() { + runningSessions = await window.api.browser.getRunning(); + } + + async function checkForUpdates() { + try { + const info = await window.api.app.checkForUpdates(); + updateInfo = info; + if (info.newKernel || info.newControl) { + showUpdateBanner(info); + } + } catch {} + } + + function showUpdateBanner(info) { + const existing = document.getElementById('update-banner'); + if (existing) existing.remove(); + + const parts = []; + if (info.newKernel && info.kernel) { + parts.push(`🧠 New BotBrowser kernel: ${esc(info.kernel.tagName)}`); + } + if (info.newControl && info.control) { + parts.push(`🚀 New Control app: ${esc(info.control.tagName)}`); + } + if (!parts.length) return; + + const banner = document.createElement('div'); + banner.id = 'update-banner'; + banner.className = 'update-banner'; + banner.innerHTML = ` + ${I.bell} + ${parts.join('  ·  ')} + + + `; + // Insert after sidebar, before main content + const layout = document.querySelector('.app-layout') || document.body; + const main = document.getElementById('main-content'); + if (main && main.parentNode) { + main.parentNode.insertBefore(banner, main); + } else { + layout.prepend(banner); + } + } + + // ─── Navigation ─────────────────────────────────────────────────────────────── + function bindNav() { + document.querySelectorAll('[data-nav]').forEach(btn => { + btn.addEventListener('click', () => { + currentView = btn.dataset.nav; + selectedProfileIds.clear(); + render(); + }); + }); + window.api.on('navigate', (view) => { currentView = view; render(); }); + window.api.on('action', (action) => { if (action === 'new-profile') openProfileEditor(null); }); + } + + // ─── Event Delegation ───────────────────────────────────────────────────────── + function bindEvents() { + document.addEventListener('click', (e) => { + if (!e.target.closest('.context-menu')) { + document.querySelectorAll('.context-menu').forEach(m => m.remove()); + } + // Close inline proxy editor on outside click (not on the input itself or recheck button) + if (inlineProxyEditId && !e.target.closest('.proxy-inline-input') && !e.target.closest('[data-action="open-proxy-editor"]') && !e.target.closest('[data-action="check-ip"]')) { + closeInlineProxyEditor(); + } + const btn = e.target.closest('[data-action]'); + if (!btn) return; + const action = btn.dataset.action; + const data = { ...btn.dataset }; + handleAction(action, data, e); + }); + + document.addEventListener('change', (e) => { + if (e.target.id === 'search-input') { + searchQuery = e.target.value.toLowerCase(); + renderProfiles(); + } + if (e.target.dataset.action === 'select-profile') { + const id = e.target.dataset.id; + if (e.target.checked) selectedProfileIds.add(id); + else selectedProfileIds.delete(id); + updateBulkBar(); + } + if (e.target.id === 'select-all-profiles') { + const visible = getVisibleProfiles(); + if (e.target.checked) visible.forEach(p => selectedProfileIds.add(p.id)); + else selectedProfileIds.clear(); + renderProfiles(); + updateBulkBar(); + } + }); + + document.addEventListener('input', (e) => { + if (e.target.id === 'search-input') { + searchQuery = e.target.value.toLowerCase(); + renderProfiles(); + } + }); + + // IPC events + window.api.on('instance:started', ({ profileId }) => { + const p = profiles.find(x => x.id === profileId); + if (p) { p.status = 'running'; } + refreshRunningSessions().then(() => renderView()); + }); + window.api.on('instance:stopped', ({ profileId }) => { + const p = profiles.find(x => x.id === profileId); + if (p) { p.status = 'stopped'; } + refreshRunningSessions().then(() => renderView()); + }); + window.api.on('instance:error', ({ profileId, error }) => { + showToast(`Error: ${error}`, 'error', 5000); + const p = profiles.find(x => x.id === profileId); + if (p) p.status = 'stopped'; + refreshRunningSessions().then(() => renderView()); + }); + window.api.on('profile:statusChanged', ({ profileId, status }) => { + const p = profiles.find(x => x.id === profileId); + if (p) p.status = status; + renderView(); + }); + window.api.on('profile:cookiesSaved', ({ profileId, count, path: cookiePath }) => { + const p = profiles.find(x => x.id === profileId); + if (p) { p.savedCookiesPath = cookiePath; p.cookieCount = count; p.cookiesSavedAt = new Date().toISOString(); } + showToast(`${count} cookies saved for profile`, 'success'); + renderView(); + }); + + // Kernel download events + window.api.on('kernel:downloadProgress', ({ version, progress }) => { + kernelDownloads[version] = { ...(kernelDownloads[version] || {}), progress, status: 'downloading' }; + updateKernelProgressUI(version, progress); + }); + window.api.on('kernel:downloadComplete', ({ version, installStatus, installNote }) => { + kernelDownloads[version] = { progress: 100, status: 'done' }; + const msg = installStatus === 'installed' ? `Kernel ${version} installed! ✅` : + installStatus === 'ready' ? `Kernel ${version} ready to use.` : + `Kernel ${version} downloaded. ${installNote || ''}`; + showToast(msg, 'success', 6000); + window.api.kernel.listInstalled().then(list => { + kernelInstalled = list; + if (currentView === 'settings') renderSettings(); + }); + }); + } + + function handleAction(action, data, e) { + switch (action) { + case 'launch-profile': launchProfile(data.id); break; + case 'stop-profile': stopProfile(data.id); break; + case 'stop-all': stopAll(); break; + case 'edit-profile': openProfileEditor(data.id); break; + case 'duplicate-profile': duplicateProfile(data.id); break; + case 'delete-profile': deleteProfile(data.id); break; + case 'new-profile': openProfileEditor(null); break; + case 'save-profile': saveProfile(); break; + case 'cancel-edit': closeProfileEditor(); break; + case 'show-context': showContextMenu(data.id, e); break; + case 'delete-selected': deleteSelected(); break; + case 'clear-selection': clearSelection(); break; + case 'browse-file': browseFile(data.target, data.filter); break; + case 'browse-dir': browseDir(data.target); break; + case 'save-settings': saveSettings(); break; + case 'browse-exe': browseExe(); break; + case 'browse-userdata': browseUserData(); break; + case 'tab-switch': switchTab(data.tab); break; + case 'add-header': addCustomHeader(); break; + case 'remove-header': removeCustomHeader(data.key); break; + // IP check + case 'check-ip': checkProfileIp(data.id); break; + // Inline proxy editor + case 'open-proxy-editor': openInlineProxyEditor(data.id); break; + case 'save-inline-proxy': saveInlineProxy(data.id); break; + case 'cancel-inline-proxy': closeInlineProxyEditor(); break; + // Kernel manager + case 'kernel-refresh': fetchKernelReleases(true); break; + case 'kernel-download': downloadKernel(data.version, data.url, data.filename); break; + case 'kernel-delete': deleteKernel(data.version); break; + case 'kernel-use': useKernelPath(data.execpath); break; + case 'kernel-open-dir': window.api.shell.openPath(data.dir); break; + } + } + + // ─── Render ─────────────────────────────────────────────────────────────────── + function render() { + updateNavActive(); + renderView(); + } + + function renderView() { + const main = el('main-content'); + if (!main) return; + if (currentView === 'profiles') renderProfiles(); + else if (currentView === 'sessions') renderSessions(); + else if (currentView === 'settings') renderSettings(); + } + + function updateNavActive() { + document.querySelectorAll('[data-nav]').forEach(btn => { + btn.classList.toggle('active', btn.dataset.nav === currentView); + }); + } + + function getVisibleProfiles() { + if (!searchQuery) return profiles; + return profiles.filter(p => + (p.name||'').toLowerCase().includes(searchQuery) || + (p.proxyServer||'').toLowerCase().includes(searchQuery) || + (p.browserBrand||'').toLowerCase().includes(searchQuery) || + (p.startUrl||'').toLowerCase().includes(searchQuery) + ); + } + + function updateBulkBar() { + const bar = el('bulk-action-bar'); + if (!bar) return; + if (selectedProfileIds.size > 0) { + bar.style.display = 'flex'; + const countEl = bar.querySelector('.bulk-count'); + if (countEl) countEl.textContent = `${selectedProfileIds.size} selected`; + } else { + bar.style.display = 'none'; + } + } + + // ─── Profile List Rendering ─────────────────────────────────────────────────── + function renderProfiles() { + const main = el('main-content'); + if (!main) return; + const visible = getVisibleProfiles(); + const allSelected = visible.length > 0 && visible.every(p => selectedProfileIds.has(p.id)); + const runningSet = new Set(runningSessions.map(s => s.profileId)); + + const brandMeta = { + chrome: { bg: 'linear-gradient(135deg,#4285f4,#34a853)', label: 'Ch' }, + edge: { bg: 'linear-gradient(135deg,#0078d4,#00bcf2)', label: 'Ed' }, + brave: { bg: 'linear-gradient(135deg,#fb542b,#ff7f4d)', label: 'Br' }, + opera: { bg: 'linear-gradient(135deg,#cc0f16,#ff4444)', label: 'Op' }, + firefox: { bg: 'linear-gradient(135deg,#ff9500,#ff6611)', label: 'Fx' }, + chromium: { bg: 'linear-gradient(135deg,#6366f1,#8b5cf6)', label: 'Cr' }, + webview: { bg: 'linear-gradient(135deg,#10b981,#059669)', label: 'Wv' }, + }; + + const statP = el('stat-profiles'); if (statP) statP.textContent = profiles.length; + const statR = el('stat-running'); if (statR) statR.textContent = runningSessions.length; + const statInd = el('stat-indicator'); if (statInd) statInd.classList.toggle('running', runningSessions.length > 0); + const badgeP = el('badge-profiles'); if (badgeP) badgeP.textContent = profiles.length || ''; + const badgeS = el('badge-sessions'); if (badgeS) { badgeS.textContent = runningSessions.length || ''; badgeS.style.display = runningSessions.length ? '' : 'none'; } + + main.innerHTML = ` +
+
+
${I.profile}
+

Profiles

+ ${profiles.length} +
+
+
+ +
+ +
+
+ + ${selectedProfileIds.size > 0 ? ` +
+ ${selectedProfileIds.size} selected + + +
` : ``} + + ${visible.length === 0 ? ` +
+
${I.profile}
+ ${profiles.length === 0 + ? `

No profiles yet

Create your first browser profile to get started.

+ ` + : `

No results

No profiles match your search query.

` + } +
` : ` +
+
+
+ +
+
+
Profile
+
Proxy
+
Tags
+
Status
+
Actions
+
+ ${visible.map(profile => { + const isRunning = runningSet.has(profile.id) || profile.status === 'running'; + const isSelected = selectedProfileIds.has(profile.id); + const brandKey = (profile.browserBrand || '').toLowerCase(); + const bm = brandMeta[brandKey] || { bg: 'linear-gradient(135deg,#6366f1,#8b5cf6)', label: (profile.name || 'P').charAt(0).toUpperCase() }; + const letter = profile.browserBrand ? bm.label : (profile.name || 'P').charAt(0).toUpperCase(); + + // Proxy display + const ipResult = ipCheckResults[profile.id]; + const proxyDisplay = renderProxyCell(profile, ipResult); + + const brandTagClass = brandKey === 'firefox' ? 'tag-firefox' : 'tag-chrome'; + return ` +
+
+ +
+
+
${letter}
+
+
+
+
${esc(profile.name || 'Unnamed')}
+
+ ${profile.startUrl + ? `${I.globe}${esc(profile.startUrl)}` + : `No start URL` + } + ${profile.cookieCount + ? `${I.cookie} ${profile.cookieCount}${profile.cookiesSavedAt ? ' · ' + timeAgo(profile.cookiesSavedAt) : ''}` + : '' + } +
+
+
+
+ ${proxyDisplay} +
+
+ ${profile.browserBrand + ? `${profile.browserBrand.charAt(0).toUpperCase() + profile.browserBrand.slice(1)}` + : '' + } + ${profile.profileFilePath ? `.enc` : ''} + ${profile.remoteDebuggingPort ? `CDP` : ''} +
+
+ + + ${isRunning ? 'Running' : 'Idle'} + +
+
+ ${isRunning + ? `` + : `` + } + + + + +
+
`; + }).join('')} +
`} + `; + + // If inline proxy editor is open for a profile, re-mount the input + if (inlineProxyEditId) { + mountInlineProxyInput(inlineProxyEditId); + } + } + + // ─── Proxy Cell Rendering ───────────────────────────────────────────────────── + function renderProxyCell(profile, ipResult) { + const proxyStr = profile.proxyServer || ''; + const isLoading = ipCheckLoading[profile.id]; + + // Parse scheme for badge color + let schemeBadge = ''; + if (proxyStr) { + const m = proxyStr.match(/^([a-z0-9+\-]+):\/\//i); + const scheme = m ? m[1].toLowerCase() : 'socks5'; + const schemeColor = scheme.startsWith('socks5') ? '#9b59b6' : + scheme.startsWith('socks4') ? '#8e44ad' : + scheme.startsWith('http') ? '#27ae60' : '#7f8c8d'; + schemeBadge = `${scheme.toUpperCase()}`; + } + + // Host:port display (strip scheme+auth) + const proxyHost = proxyStr + ? proxyStr.replace(/^[a-z+\-]+:\/\/[^@]+@/i, '').replace(/^[a-z+\-]+:\/\//i, '') + : ''; + + // Status dot: green=ok, red=fail/error, yellow=loading, grey=unchecked + let statusDot = ''; + let flagDisplay = ''; + if (proxyStr) { + if (isLoading) { + statusDot = ``; + } else if (ipCheckResults[profile.id] === null) { + statusDot = ``; + } else if (ipResult && ipResult.status !== 'fail') { + const flag = ipResult.countryCode ? countryCodeToEmoji(ipResult.countryCode) : ''; + const country = ipResult.countryCode || ''; + const isHosting = ipResult.hosting || ipResult.proxy; + statusDot = ``; + flagDisplay = `${flag} ${esc(country)}${isHosting ? ' ⚠' : ''}`; + } else { + statusDot = ``; + } + } + + if (!proxyStr) { + return ` + ${I.proxy} Set proxy… + `; + } + + return `
+
+ ${statusDot} + ${schemeBadge} + ${esc(proxyHost)} +
+ ${flagDisplay ? `
${flagDisplay} + +
` : isLoading ? `
⟳ Checking…
` : ''} +
`; + } + + // ─── GoLogin-style Inline Proxy Editor ────────────────────────────────────────────── + // Clicking the proxy host text converts it directly into an in-place. + // No popup, no expanded editor — just a plain input field inline. + + function openInlineProxyEditor(profileId) { + if (inlineProxyEditId === profileId) return; // already editing + if (inlineProxyEditId) closeInlineProxyEditor(); + inlineProxyEditId = profileId; + mountInlineProxyInput(profileId); + } + + function mountInlineProxyInput(profileId) { + const profile = profiles.find(p => p.id === profileId); + if (!profile) return; + const current = profile.proxyServer || ''; + + // Find the proxy-host-text span and replace it with an input in-place + const cell = document.getElementById(`proxy-cell-${profileId}`); + if (!cell) return; + const hostSpan = cell.querySelector('.proxy-host-text'); + if (!hostSpan) return; + + const inp = document.createElement('input'); + inp.type = 'text'; + inp.className = 'proxy-inline-input'; + inp.id = `proxy-inline-input-${profileId}`; + inp.value = current; + inp.placeholder = 'socks5://host:port'; + inp.autocomplete = 'off'; + inp.spellcheck = false; + hostSpan.replaceWith(inp); + inp.focus(); + inp.select(); + + // Debounced auto-check as user types + let debounceTimer = null; + inp.addEventListener('input', () => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + const val = normalizeProxy(inp.value.trim()); + if (val) checkProxyValueQuick(profileId, val); + }, 900); + }); + + inp.addEventListener('keydown', (ev) => { + if (ev.key === 'Enter') { ev.preventDefault(); saveInlineProxy(profileId); } + if (ev.key === 'Escape') { ev.preventDefault(); closeInlineProxyEditor(); } + }); + + inp.addEventListener('blur', () => { + // Small delay so a click on the recheck button doesn't prematurely cancel + setTimeout(() => { + if (inlineProxyEditId === profileId) saveInlineProxy(profileId); + }, 150); + }); + } + + // Quick IP check while typing (updates dot + flag row without disturbing the input) + async function checkProxyValueQuick(profileId, proxyValue) { + if (ipCheckLoading[profileId]) return; + ipCheckLoading[profileId] = true; + const cell = document.getElementById(`proxy-cell-${profileId}`); + if (cell) { + const dot = cell.querySelector('.proxy-status-dot'); + if (dot) { dot.className = 'proxy-status-dot checking'; dot.title = 'Checking…'; } + } + try { + const result = await window.api.proxy.checkIp(proxyValue); + ipCheckResults[profileId] = result; + } catch (e) { + ipCheckResults[profileId] = null; + } finally { + ipCheckLoading[profileId] = false; + const cell2 = document.getElementById(`proxy-cell-${profileId}`); + if (cell2 && inlineProxyEditId === profileId) { + const ipResult = ipCheckResults[profileId]; + // Update dot + const dot = cell2.querySelector('.proxy-status-dot'); + if (dot) { + if (ipResult && ipResult.status !== 'fail') { + const isHosting = ipResult.hosting || ipResult.proxy; + dot.className = `proxy-status-dot ok${isHosting ? ' dc' : ''}`; + dot.title = `${ipResult.query} · ${ipResult.city||''}, ${ipResult.country||''}`; + } else { + dot.className = 'proxy-status-dot error'; + dot.title = 'Check failed'; + } + } + // Update or create the flag/ip row + const proxyContent = cell2.querySelector('.proxy-cell-content'); + let ipRow = cell2.querySelector('.proxy-ip-row'); + if (ipResult && ipResult.status !== 'fail') { + const flag = ipResult.countryCode ? countryCodeToEmoji(ipResult.countryCode) : ''; + const country = ipResult.countryCode || ''; + const isHosting = ipResult.hosting || ipResult.proxy; + const flagHtml = `${flag} ${esc(country)}${isHosting ? ' ⚠' : ''} + `; + if (!ipRow) { + ipRow = document.createElement('div'); + ipRow.className = 'proxy-ip-row'; + if (proxyContent) proxyContent.appendChild(ipRow); + } + if (ipRow) ipRow.innerHTML = flagHtml; + } else if (ipRow) { + ipRow.remove(); + } + } + } + } + + function closeInlineProxyEditor() { + const prevId = inlineProxyEditId; + inlineProxyEditId = null; + if (prevId) { + const cell = document.getElementById(`proxy-cell-${prevId}`); + const profile = profiles.find(p => p.id === prevId); + if (cell && profile) cell.innerHTML = renderProxyCell(profile, ipCheckResults[prevId]); + } + } + + async function saveInlineProxy(profileId) { + const inp = document.getElementById(`proxy-inline-input-${profileId}`); + const proxyRaw = inp ? inp.value.trim() : ''; + const proxyServer = normalizeProxy(proxyRaw); + + try { + const updated = await window.api.profiles.update(profileId, { proxyServer }); + const idx = profiles.findIndex(p => p.id === profileId); + if (idx !== -1) profiles[idx] = { ...profiles[idx], ...updated }; + showToast('Proxy saved.', 'success'); + inlineProxyEditId = null; + // Auto-check IP after saving + if (proxyServer) { + setTimeout(() => checkProfileIp(profileId), 300); + } + renderProfiles(); + } catch (err) { + showToast(`Failed: ${err.message}`, 'error'); + } + } + + // ─── IP Check ───────────────────────────────────────────────────────────────── + async function checkProfileIp(profileId) { + const profile = profiles.find(p => p.id === profileId); + if (!profile) return; + if (ipCheckLoading[profileId]) return; + + ipCheckLoading[profileId] = true; + // Update just the proxy cell + const cell = document.getElementById(`proxy-cell-${profileId}`); + if (cell && inlineProxyEditId !== profileId) { + cell.innerHTML = renderProxyCell(profile, null); + } + + try { + const result = await window.api.proxy.checkIp(profile.proxyServer || ''); + ipCheckResults[profileId] = result; + if (result.status === 'fail') { + showToast(`IP check failed: ${result.message || 'Unknown error'}`, 'error'); + } else { + const isHosting = result.hosting || result.proxy; + const loc = [result.city, result.regionName, result.country].filter(Boolean).join(', '); + const flag = countryCodeToEmoji(result.countryCode); + showToast( + `${flag} ${result.query} · ${loc}${isHosting ? ' · ⚠ Datacenter/Proxy' : ''}`, + isHosting ? 'warning' : 'success', + 6000 + ); + } + } catch (e) { + showToast(`IP check error: ${e.message}`, 'error'); + ipCheckResults[profileId] = null; + } finally { + ipCheckLoading[profileId] = false; + // Update proxy cell + const profile2 = profiles.find(p => p.id === profileId); + const cell2 = document.getElementById(`proxy-cell-${profileId}`); + if (cell2 && inlineProxyEditId !== profileId && profile2) { + cell2.innerHTML = renderProxyCell(profile2, ipCheckResults[profileId]); + } + // If inline editor open, re-mount the input field + if (inlineProxyEditId === profileId) { + mountInlineProxyInput(profileId); + } + } + } + + // ─── Sessions View ──────────────────────────────────────────────────────────── + function renderSessions() { + const main = el('main-content'); + if (!main) return; + + const statP = el('stat-profiles'); if (statP) statP.textContent = profiles.length; + const statR = el('stat-running'); if (statR) statR.textContent = runningSessions.length; + const statInd = el('stat-indicator'); if (statInd) statInd.classList.toggle('running', runningSessions.length > 0); + const badgeS = el('badge-sessions'); if (badgeS) { badgeS.textContent = runningSessions.length || ''; badgeS.style.display = runningSessions.length ? '' : 'none'; } + + main.innerHTML = ` +
+
+
${I.session}
+

Running Sessions

+ ${runningSessions.length} +
+
+ ${runningSessions.length > 0 ? `` : ''} +
+
+ ${runningSessions.length === 0 + ? `
+
${I.session}
+

No active sessions

+

Launch a profile from the Profiles tab to start a browser session.

+ +
` + : `
+ + + + + + + + + + + + ${runningSessions.map(s => ` + + + + + + + `).join('')} + +
ProfilePIDStartedURLAction
${esc(s.profileName)}${s.pid}${timeAgo(s.startTime)}${esc(s.url || 'about:blank')} + +
+
` + } + `; + } + + // ─── Settings View ──────────────────────────────────────────────────────────── + function renderSettings() { + const main = el('main-content'); + if (!main) return; + const s = settings; + const defaultPath = IS_WIN + ? 'C:\\Program Files\\BotBrowser\\chrome.exe' + : IS_MAC ? '/Applications/Chromium.app/Contents/MacOS/Chromium' : '/usr/bin/botbrowser'; + + main.innerHTML = ` +
+
+
${I.settings}
+

Settings

+
+
+ +
+
+ +
+
+ + +
+
+
+ +
+
+
BotBrowser Executable
+
Path to your BotBrowser (Chromium-based) binary
+
+
+
+
+ +
+ + +
+
${IS_WIN ? 'e.g. C:\\Program Files\\BotBrowser\\chrome.exe' : IS_MAC ? '/Applications/Chromium.app/Contents/MacOS/Chromium' : '/usr/bin/botbrowser'}
+
+
+ +
+ + +
+
Each profile gets its own sub-folder here.
+
+
+
+ + +
+
+
+ +
+
+
Default Proxy
+
Applied to all new profiles unless overridden
+
+
+
+
+ + +
Supports HTTP, HTTPS, SOCKS4, SOCKS5. Default scheme: socks5://
+
+
+
+ + +
+
+
+ +
+
+
Kernel Manager
+
Download and install BotBrowser kernels from GitHub releases
+
+
+ ${updateInfo && updateInfo.newKernel && updateInfo.kernel + ? `${I.bell} New: ${esc(updateInfo.kernel.tagName)}` + : ''} + +
+
+
+ ${renderKernelManager()} +
+
+ + +
+
+
+ +
+
+
About BotBrowser Control
+
Desktop profile manager for BotBrowser
+
+ ${updateInfo && updateInfo.newControl && updateInfo.control + ? `${I.bell} v${esc(updateInfo.control.version)} available` + : ''} +
+
+
+
+ Version + 1.1.0 +
+
+ Platform + ${IS_WIN ? 'Windows' : IS_MAC ? 'macOS' : 'Linux'} +
+
+ Support + github.com/botswin/BotBrowser +
+
+
+
+ +
+
+ `; + } + + // ─── Profile Editor ─────────────────────────────────────────────────────────── + function openProfileEditor(profileId) { + editingProfileId = profileId; + const profile = profileId ? profiles.find(p => p.id === profileId) : null; + const d = profile ? { ...getDefaultProfile(), ...profile } : getDefaultProfile(); + + const tabs = [ + { id: 'general', label: 'General', icon: '' }, + { id: 'network', label: 'Network', icon: '' }, + { id: 'identity', label: 'Identity', icon: '' }, + { id: 'fingerprint', label: 'Fingerprint', icon: '' }, + { id: 'behavior', label: 'Behavior', icon: '' }, + { id: 'session', label: 'Session', icon: '' }, + { id: 'advanced', label: 'Advanced', icon: '' }, + ]; + + const overlay = document.createElement('div'); + overlay.id = 'profile-editor-overlay'; + overlay.className = 'editor-overlay'; + overlay.innerHTML = ` +
+
+
+
+ +
+
+

${profileId ? 'Edit Profile' : 'New Profile'}

+
${profileId ? 'Update profile settings and flags' : 'Configure a new browser profile'}
+
+
+ +
+
+ +
+ ${renderTabGeneral(d)} + ${renderTabNetwork(d)} + ${renderTabIdentity(d)} + ${renderTabFingerprint(d)} + ${renderTabBehavior(d)} + ${renderTabSession(d)} + ${renderTabAdvanced(d)} +
+
+ +
+ `; + document.body.appendChild(overlay); + overlay.addEventListener('click', e => { if (e.target === overlay) closeProfileEditor(); }); + renderCustomHeadersUI(d.customHeaders || {}); + } + + function closeProfileEditor() { + const overlay = el('profile-editor-overlay'); + if (overlay) overlay.remove(); + editingProfileId = null; + } + + function switchTab(tab) { + document.querySelectorAll('.editor-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab)); + document.querySelectorAll('.editor-panel').forEach(p => p.classList.toggle('active', p.id === `tab-${tab}`)); + const body = document.querySelector('.editor-body'); + if (body) body.scrollTop = 0; + } + + function getDefaultProfile() { + return { + name: '', browserBrand: '', colorScheme: 'light', + locale: 'auto', timezone: 'auto', languages: 'auto', location: 'auto', + startUrl: '', proxyServer: '', proxyIp: '', proxyBypassRgx: '', + profileFilePath: '', profileDirPath: '', + windowSize: 'real', screenSize: 'real', orientation: 'profile', + disableDeviceScaleFactorOnGUI: false, + noiseCanvas: true, noiseWebglImage: true, noiseAudioContext: true, + noiseClientRects: false, noiseTextRects: true, + webrtc: 'profile', webrtcICE: 'google', webgl: 'profile', webgpu: 'profile', + fonts: 'profile', mediaDevices: 'profile', speechVoices: 'profile', mediaTypes: 'expand', + alwaysActive: true, disableDebugger: true, disableConsoleMessage: true, + portProtection: false, localDns: false, injectRandomHistory: '', + mobileForceTouch: false, keyboard: 'profile', + uaFullVersion: '', brandFullVersion: '', userAgent: '', + platform: '', platformVersion: '', model: '', architecture: '', bitness: '', mobile: false, + cookies: '', bookmarks: '', remoteDebuggingPort: '', + customHeaders: {}, networkInfoOverride: false, + noiseSeed: '', timeSeed: '', stackSeed: 'profile', timeScale: '', + fps: 'profile', mirrorController: '', mirrorClient: '', + canvasRecordFile: '', audioRecordFile: '', + botScript: '', ipService: '', enableVariationsInContext: false, + gpuEmulation: true, + }; + } + + // ─── Tab Renderers ──────────────────────────────────────────────────────────── + + function badge(tier) { + if (!tier) return ''; + if (tier === 'PRO') return 'PRO'; + return `${tier}`; + } + + function renderToggle(id, label, hint, checked, tier) { + return `
+
+ + ${hint ? `
${hint}
` : ''} +
+ +
`; + } + + function renderTabGeneral(d) { + return `
+
+
${I.user} Profile Identity
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
`; + } + + function renderTabNetwork(d) { + return `
+
+
${I.network} Proxy Configuration
+
+
+ + +
Default scheme: socks5://. Supports HTTP, HTTPS, SOCKS4, SOCKS5.
+
+
+ + +
Skip per-page IP lookups for better performance.
+
+
+ + +
+
+
+
+
${I.globe} Locale & Geo
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
${I.globe} Custom HTTP Headers ${badge('PRO')}
+
+ +
+
+
${I.network} IP & WebRTC
+
+
+ + +
+
+ + +
+
+ ${renderToggle('f-localDns', 'Local DNS Solver', 'Prevents DNS leaks.', d.localDns===true, 'ENT Tier1')} + ${renderToggle('f-portProtection', 'Port Protection', 'Protect local service ports.', d.portProtection===true, 'PRO')} + ${renderToggle('f-networkInfoOverride', 'Network Info Override', 'Use profile navigator.connection values.', d.networkInfoOverride===true, null)} +
+
`; + } + + function renderTabIdentity(d) { + return `
+
+
${I.shield} Browser Identity ${badge('ENT Tier2')}
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
${I.cpu} Custom User-Agent ${badge('ENT Tier3')}
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
`; + } + + function renderTabFingerprint(d) { + return `
+
+
${I.cpu} Display & Input
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ ${renderToggle('f-disableDeviceScaleFactorOnGUI', 'Disable Device Scale Factor', '', d.disableDeviceScaleFactorOnGUI===true, null)} +
+
+
${I.zap} Rendering & Media
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
${I.shield} Noise Toggles
+ ${renderToggle('f-noiseCanvas','Canvas Noise','',d.noiseCanvas!==false,null)} + ${renderToggle('f-noiseWebglImage','WebGL Image Noise','',d.noiseWebglImage!==false,null)} + ${renderToggle('f-noiseAudioContext','Audio Context Noise','',d.noiseAudioContext!==false,null)} + ${renderToggle('f-noiseClientRects','Client Rects Noise','',d.noiseClientRects===true,null)} + ${renderToggle('f-noiseTextRects','Text Rects Noise','',d.noiseTextRects!==false,null)} +
+
`; + } + + function renderTabBehavior(d) { + return `
+
+
${I.shield} Protection Toggles
+ ${renderToggle('f-disableDebugger','Disable Debugger','Ignore JS debugger statements.',d.disableDebugger!==false,null)} + ${renderToggle('f-disableConsoleMessage','Disable Console Messages','',d.disableConsoleMessage!==false,'ENT Tier1')} + ${renderToggle('f-alwaysActive','Always Active','Keep windows active even when unfocused.',d.alwaysActive!==false,'PRO')} + ${renderToggle('f-mobileForceTouch','Mobile Force Touch','Force touch events for mobile simulation.',d.mobileForceTouch===true,null)} + ${renderToggle('f-enableVariationsInContext','X-Client-Data in Incognito','',d.enableVariationsInContext===true,'ENT Tier2')} +
+
+
${I.zap} Timing & Seeds ${badge('ENT Tier2')}
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
${I.info} History
+
+ + +
+
+
`; + } + + function renderTabSession(d) { + return `
+
+
${I.cookie} Cookies & Bookmarks
+
+ + +
+
+ + +
+
+
+
${I.network} Mirror Mode ${badge('ENT Tier3')}
+
+
+ + +
+
+ + +
+
+
+
`; + } + + function renderTabAdvanced(d) { + return `
+
+
${I.cpu} Debug & Automation
+
+
+ + +
+
+ +
+ + +
+
+
+ ${renderToggle('f-gpuEmulation', 'GPU Emulation', '', d.gpuEmulation!==false, 'ENT Tier2')} +
+
+
${I.zap} Recording
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
`; + } + + // ─── Custom Headers UI ──────────────────────────────────────────────────────── + let customHeadersState = {}; + + function renderCustomHeadersUI(headers) { + customHeadersState = { ...headers }; + rebuildCustomHeadersDOM(); + } + + function rebuildCustomHeadersDOM() { + const container = el('custom-headers-container'); + if (!container) return; + const entries = Object.entries(customHeadersState); + if (entries.length === 0) { + container.innerHTML = '
No custom headers added.
'; + return; + } + container.innerHTML = entries.map(([key, value]) => ` +
+ + : + + +
+ `).join(''); + } + + function addCustomHeader() { + customHeadersState['X-Custom-Header'] = ''; + rebuildCustomHeadersDOM(); + } + + function removeCustomHeader(key) { + delete customHeadersState[key]; + rebuildCustomHeadersDOM(); + } + + function readCustomHeaders() { + const rows = document.querySelectorAll('.custom-header-row'); + const result = {}; + rows.forEach(row => { + const inputs = row.querySelectorAll('input'); + const k = inputs[0]?.value?.trim(); + const v = inputs[1]?.value?.trim(); + if (k) result[k] = v || ''; + }); + return result; + } + + // ─── Profile Actions ────────────────────────────────────────────────────────── + + async function launchProfile(id) { + try { + await window.api.browser.launch(id); + } catch (e) { + showToast(`Launch failed: ${e.message}`, 'error', 6000); + } + } + + async function stopProfile(id) { + await window.api.browser.stop(id); + await refreshRunningSessions(); + renderView(); + } + + async function stopAll() { + await window.api.browser.stopAll(); + await refreshRunningSessions(); + renderView(); + } + + async function duplicateProfile(id) { + try { + showToast('Duplicating profile…', 'info', 2000); + const copy = await window.api.profiles.duplicate(id); + profiles.push(copy); + showToast(`Profile "${copy.name}" created.`, 'success'); + renderView(); + } catch (e) { + showToast(`Duplicate failed: ${e.message}`, 'error'); + } + } + + async function deleteProfile(id) { + const p = profiles.find(x => x.id === id); + if (!confirm(`Delete profile "${p?.name || id}"? This cannot be undone.`)) return; + await window.api.profiles.delete(id); + profiles = profiles.filter(x => x.id !== id); + selectedProfileIds.delete(id); + renderView(); + } + + async function deleteSelected() { + if (selectedProfileIds.size === 0) return; + if (!confirm(`Delete ${selectedProfileIds.size} selected profile(s)?`)) return; + const ids = [...selectedProfileIds]; + await window.api.profiles.deleteMultiple(ids); + profiles = profiles.filter(p => !selectedProfileIds.has(p.id)); + selectedProfileIds.clear(); + showToast(`${ids.length} profile(s) deleted.`, 'success'); + renderView(); + } + + function clearSelection() { + selectedProfileIds.clear(); + renderView(); + } + + async function saveProfile() { + const name = val('f-name'); + if (!name) { showToast('Profile name is required.', 'error'); return; } + + const proxyRaw = val('f-proxyServer'); + const profileData = { + name, + browserBrand: val('f-browserBrand') || '', + colorScheme: selVal('f-colorScheme'), + startUrl: val('f-startUrl'), + profileFilePath: val('f-profileFilePath'), + profileDirPath: val('f-profileDirPath'), + proxyServer: normalizeProxy(proxyRaw), + proxyIp: val('f-proxyIp'), + proxyBypassRgx: val('f-proxyBypassRgx'), + timezone: val('f-timezone') || 'auto', + locale: val('f-locale') || 'auto', + languages: val('f-languages') || 'auto', + location: val('f-location') || 'auto', + ipService: val('f-ipService'), + webrtcICE: val('f-webrtcICE') || 'google', + localDns: chk('f-localDns'), + portProtection: chk('f-portProtection'), + networkInfoOverride: chk('f-networkInfoOverride'), + customHeaders: readCustomHeaders(), + userAgent: val('f-userAgent'), + uaFullVersion: val('f-uaFullVersion'), + brandFullVersion: val('f-brandFullVersion'), + platform: selVal('f-platform'), + platformVersion: val('f-platformVersion'), + model: val('f-model'), + architecture: selVal('f-architecture'), + bitness: selVal('f-bitness'), + mobile: selVal('f-mobile') === '' ? '' : selVal('f-mobile') === 'true', + windowSize: val('f-windowSize') || 'real', + screenSize: val('f-screenSize') || 'real', + orientation: selVal('f-orientation'), + keyboard: selVal('f-keyboard'), + fonts: selVal('f-fonts'), + disableDeviceScaleFactorOnGUI: chk('f-disableDeviceScaleFactorOnGUI'), + webgl: selVal('f-webgl'), + webgpu: selVal('f-webgpu'), + webrtc: selVal('f-webrtc'), + mediaDevices: selVal('f-mediaDevices'), + speechVoices: selVal('f-speechVoices'), + mediaTypes: selVal('f-mediaTypes'), + noiseCanvas: chk('f-noiseCanvas'), + noiseWebglImage: chk('f-noiseWebglImage'), + noiseAudioContext: chk('f-noiseAudioContext'), + noiseClientRects: chk('f-noiseClientRects'), + noiseTextRects: chk('f-noiseTextRects'), + disableDebugger: chk('f-disableDebugger'), + disableConsoleMessage: chk('f-disableConsoleMessage'), + alwaysActive: chk('f-alwaysActive'), + mobileForceTouch: chk('f-mobileForceTouch'), + enableVariationsInContext: chk('f-enableVariationsInContext'), + fps: val('f-fps') || 'profile', + timeScale: val('f-timeScale'), + noiseSeed: val('f-noiseSeed'), + timeSeed: val('f-timeSeed'), + stackSeed: val('f-stackSeed') || 'profile', + injectRandomHistory: val('f-injectRandomHistory'), + cookies: val('f-cookies'), + bookmarks: val('f-bookmarks'), + mirrorController: val('f-mirrorController'), + mirrorClient: val('f-mirrorClient'), + remoteDebuggingPort: val('f-remoteDebuggingPort'), + botScript: val('f-botScript'), + gpuEmulation: chk('f-gpuEmulation'), + canvasRecordFile: val('f-canvasRecordFile'), + audioRecordFile: val('f-audioRecordFile'), + }; + + // Auto-detect Android + const isAndroid = profileData.platform === 'Android'; + const hasMobileModel = profileData.model && /android|samsung|pixel|xiaomi|huawei|oneplus|oppo|vivo|lg|htc|sony|moto/i.test(profileData.model); + if (isAndroid || hasMobileModel) { + if (profileData.mobile === '' || profileData.mobile === false) profileData.mobile = true; + if (profileData.orientation === 'profile' || !profileData.orientation) profileData.orientation = 'portrait'; + if (!profileData.mobileForceTouch) profileData.mobileForceTouch = true; + if (!profileData.architecture) profileData.architecture = 'arm64'; + if (!profileData.bitness) profileData.bitness = '64'; + if (!editingProfileId) showToast('Android detected — mobile settings auto-applied.', 'info', 4000); + } + + try { + if (editingProfileId) { + const updated = await window.api.profiles.update(editingProfileId, profileData); + const idx = profiles.findIndex(p => p.id === editingProfileId); + if (idx !== -1) profiles[idx] = { ...profiles[idx], ...updated }; + showToast('Profile saved.', 'success'); + } else { + const created = await window.api.profiles.create(profileData); + profiles.push(created); + showToast(`Profile "${created.name}" created.`, 'success'); + } + closeProfileEditor(); + renderView(); + } catch (e) { + showToast(`Save failed: ${e.message}`, 'error'); + } + } + + // ─── Context Menu ───────────────────────────────────────────────────────────── + function showContextMenu(profileId, e) { + document.querySelectorAll('.context-menu').forEach(m => m.remove()); + const menu = document.createElement('div'); + menu.className = 'context-menu'; + const isRunning = runningSessions.some(s => s.profileId === profileId); + menu.innerHTML = ` +
${isRunning?I.stop+' Stop':I.play+' Launch'}
+
${I.edit} Edit
+
${I.copy} Duplicate (with session)
+
+
${I.trash} Delete
+ `; + document.body.appendChild(menu); + const rect = e.target.getBoundingClientRect(); + let x = rect.right, y = rect.bottom; + if (x + 200 > window.innerWidth) x = rect.left - 200; + if (y + 180 > window.innerHeight) y = rect.top - 180; + menu.style.left = x + 'px'; + menu.style.top = y + 'px'; + } + + // ─── File/Dir Browsing ──────────────────────────────────────────────────────── + async function browseFile(targetId, filter) { + const filters = filter === 'enc' ? [{ name: 'BotBrowser Profile', extensions: ['enc', 'json'] }] + : filter === 'js' ? [{ name: 'JavaScript', extensions: ['js'] }] + : filter === 'jsonl' ? [{ name: 'JSONL', extensions: ['jsonl', 'json'] }] + : [{ name: 'All Files', extensions: ['*'] }]; + const p = await window.api.dialog.openFile({ filters }); + if (p) { const inp = el(targetId); if (inp) inp.value = p; } + } + + async function browseDir(targetId) { + const p = await window.api.dialog.selectDirectory(); + if (p) { const inp = el(targetId); if (inp) inp.value = p; } + } + + async function browseExe() { + const p = await window.api.dialog.selectExecutable(); + if (p) { const inp = el('s-botBrowserPath'); if (inp) inp.value = p; } + } + + async function browseUserData() { + const p = await window.api.dialog.selectDirectory(); + if (p) { const inp = el('s-defaultUserDataDir'); if (inp) inp.value = p; } + } + + // ─── Settings ───────────────────────────────────────────────────────────────── + async function saveSettings() { + const proxyRaw = val('s-defaultProxy'); + const newSettings = { + botBrowserPath: val('s-botBrowserPath'), + defaultUserDataDir: val('s-defaultUserDataDir'), + defaultProxy: normalizeProxy(proxyRaw), + }; + await window.api.settings.set(newSettings); + settings = { ...settings, ...newSettings }; + showToast('Settings saved.', 'success'); + } + + // ─── Kernel Manager ─────────────────────────────────────────────────────────── + function renderKernelManager() { + const platform = window.api.platform; + const platformAssetExt = platform === 'win32' ? ['.7z', '-win', '.exe'] : + platform === 'darwin' ? ['.dmg', '-mac'] : + ['.deb', '.AppImage', '-linux']; + + if (!kernelReleases) { + return `
+
+ ${I.download} Click Refresh to fetch available releases from GitHub. +
+
`; + } + + if (kernelReleases.length === 0) { + return `
No releases found.
`; + } + + const installedVersions = new Set(kernelInstalled.map(k => k.version)); + + const installedSection = ` +
+ + ${kernelInstalled.length === 0 + ? `
No kernels installed yet.
` + : kernelInstalled.map(k => ` +
+
+ ${esc(k.version)} + ${k.installedAt ? timeAgo(k.installedAt) : ''} + ${k.installStatus ? `${esc(k.installStatus)}` : ''} + ${k.installNote ? `${esc(k.installNote.slice(0,40))}` : ''} +
+
+ ${k.execPath ? `` : ''} + +
+
+ `).join('') + } +
`; + + const releasesSection = ` + +
+ ${kernelReleases.map(release => { + const isInstalled = installedVersions.has(release.tagName); + const dl = kernelDownloads[release.tagName]; + // Pick the single best asset for this platform to avoid double-downloads + const platformAssets = release.assets.filter(a => + platformAssetExt.some(ext => a.name.toLowerCase().includes(ext.toLowerCase())) + ); + // Prefer arch-specific asset matching current platform arch + const arch = window.api.arch || ''; + const archKeywords = arch === 'arm64' ? ['arm64', 'aarch64'] : ['x64', 'amd64', 'x86_64']; + let bestAssets = platformAssets.filter(a => archKeywords.some(k => a.name.toLowerCase().includes(k))); + if (bestAssets.length === 0) bestAssets = platformAssets; + // On macOS prefer .dmg over .zip; on Windows prefer .exe installer over .zip + if (window.api.platform === 'darwin') { + const dmg = bestAssets.filter(a => a.name.toLowerCase().endsWith('.dmg')); + if (dmg.length > 0) bestAssets = dmg; + } else if (window.api.platform === 'win32') { + const exe = bestAssets.filter(a => a.name.toLowerCase().endsWith('.exe')); + if (exe.length > 0) bestAssets = exe; + } + // Fallback: show up to 3 assets if nothing matched + const displayAssets = (bestAssets.length > 0 ? bestAssets.slice(0, 1) : (platformAssets.length > 0 ? platformAssets.slice(0, 2) : release.assets.slice(0, 3))); + + return ` +
+
+
+ ${esc(release.tagName)} + ${release.prerelease ? 'PRE' : ''} + ${release.publishedAt ? new Date(release.publishedAt).toLocaleDateString() : ''} + ${isInstalled ? `${I.check} Installed` : ''} +
+
+ ${dl && dl.status === 'downloading' + ? `
+
+ ${dl.progress||0}% +
` + : '' + } +
+ ${displayAssets.map(asset => ` +
+ ${esc(asset.name)} + ${formatBytes(asset.size)} + ${(!dl || dl.status !== 'downloading') + ? `` + : `Downloading…` + } +
+ `).join('')} +
+
`; + }).join('')} +
`; + + return installedSection + releasesSection; + } + + function formatBytes(bytes) { + if (!bytes) return ''; + if (bytes < 1024) return bytes + ' B'; + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; + return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; + } + + async function fetchKernelReleases(force) { + if (kernelReleases && !force) return; + const btn = document.getElementById('kernel-refresh-btn'); + if (btn) { btn.disabled = true; btn.textContent = '⟳ Fetching…'; } + try { + // Always refresh available releases from GitHub + installed from disk + const [releases, installed] = await Promise.all([ + window.api.kernel.fetchReleases(), + window.api.kernel.listInstalled(), + ]); + kernelReleases = releases; + // Merge installed: keep existing entries, add/update from disk + const installedMap = {}; + kernelInstalled.forEach(k => { installedMap[k.version] = k; }); + installed.forEach(k => { installedMap[k.version] = k; }); + kernelInstalled = Object.values(installedMap); + } catch (e) { + showToast(`Failed to fetch releases: ${e.message}`, 'error', 5000); + if (!kernelReleases) kernelReleases = []; + } finally { + if (btn) { btn.disabled = false; btn.textContent = '⟳ Refresh'; } + if (currentView === 'settings') renderSettings(); + } + } + + // Load cached releases from store on startup (so kernel manager shows immediately) + async function loadCachedKernelReleases() { + try { + const [cached, installed] = await Promise.all([ + window.api.kernel.getCachedReleases(), + window.api.kernel.listInstalled(), + ]); + if (cached && cached.length > 0) kernelReleases = cached; + const installedMap = {}; + installed.forEach(k => { installedMap[k.version] = k; }); + kernelInstalled = Object.values(installedMap); + } catch {} + } + + async function downloadKernel(version, url, filename) { + if (kernelDownloads[version]?.status === 'downloading') return; + kernelDownloads[version] = { progress: 0, status: 'downloading' }; + if (currentView === 'settings') renderSettings(); + try { + await window.api.kernel.download({ downloadUrl: url, fileName: filename, version }); + } catch (e) { + kernelDownloads[version] = { status: 'error' }; + showToast(`Download failed: ${e.message}`, 'error', 5000); + if (currentView === 'settings') renderSettings(); + } + } + + function updateKernelProgressUI(version, progress) { + const safeV = version.replace(/[^a-zA-Z0-9]/g, '_'); + const wrap = document.getElementById(`kp-${safeV}`); + if (wrap) { + const fill = wrap.querySelector('.kernel-progress-bar'); + const label = wrap.querySelector('.kernel-progress-label'); + if (fill) fill.style.width = progress + '%'; + if (label) label.textContent = progress + '%'; + } + } + + async function deleteKernel(version) { + if (!confirm(`Delete kernel ${version}?`)) return; + await window.api.kernel.delete(version); + kernelInstalled = kernelInstalled.filter(k => k.version !== version); + showToast(`Kernel ${version} deleted.`, 'success'); + if (currentView === 'settings') renderSettings(); + } + + async function useKernelPath(execPath) { + if (!execPath) { showToast('No executable path for this kernel.', 'error'); return; } + await window.api.settings.set({ botBrowserPath: execPath }); + settings.botBrowserPath = execPath; + showToast('BotBrowser path updated to this kernel.', 'success'); + if (currentView === 'settings') renderSettings(); + } + + // ─── Bootstrap ──────────────────────────────────────────────────────────────── + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); \ No newline at end of file diff --git a/botbrowser-control/src/renderer/styles/main.css b/botbrowser-control/src/renderer/styles/main.css new file mode 100644 index 0000000..487edad --- /dev/null +++ b/botbrowser-control/src/renderer/styles/main.css @@ -0,0 +1,1696 @@ +/* ═══════════════════════════════════════════════════════════════ + BotBrowser Control — GoLogin-inspired Professional UI + Dark: #1B2436 base, #232F45 surface, #2980b9/#3498db accent + Light: #F4F6FA base, #FFFFFF surface, #2980b9 accent +═══════════════════════════════════════════════════════════════ */ + +/* ── Dark Theme ──────────────────────────────────────────────── */ +body.dark { + --bg-base: #161e2d; + --bg-surface: #1d2740; + --bg-elevated: #222f47; + --bg-card: #1d2740; + --bg-hover: #273350; + --bg-active: #2d3a58; + --bg-input: #151e30; + --bg-input-f: #1a2438; + + --accent: #3498db; + --accent-h: #5dade2; + --accent-dim: rgba(52,152,219,0.12); + --accent-border: rgba(52,152,219,0.28); + --accent-glow: 0 2px 12px rgba(52,152,219,0.3); + + --success: #27ae60; + --success-h: #2ecc71; + --success-dim: rgba(39,174,96,0.12); + --success-b: rgba(39,174,96,0.28); + + --warning: #f39c12; + --warning-dim: rgba(243,156,18,0.1); + --warning-b: rgba(243,156,18,0.25); + + --danger: #e74c3c; + --danger-dim: rgba(231,76,60,0.1); + --danger-b: rgba(231,76,60,0.25); + + --info: #2980b9; + --info-dim: rgba(41,128,185,0.1); + --info-b: rgba(41,128,185,0.25); + + --text-1: #e8edf5; + --text-2: #8899b4; + --text-3: #4a5c78; + --text-link: #5dade2; + + --border: rgba(255,255,255,0.055); + --border-s: rgba(255,255,255,0.09); + --border-a: rgba(52,152,219,0.28); + + --sidebar-bg: #111827; + --sidebar-text: #4a5c78; + --sidebar-hover: rgba(255,255,255,0.05); + --sidebar-active-bg: rgba(52,152,219,0.12); + --sidebar-active-color: #5dade2; + --sidebar-active-border: rgba(52,152,219,0.3); + + --shadow-sm: 0 1px 4px rgba(0,0,0,0.3); + --shadow: 0 4px 16px rgba(0,0,0,0.4); + --shadow-lg: 0 8px 32px rgba(0,0,0,0.55); + --shadow-modal: 0 20px 60px rgba(0,0,0,0.7); +} + +/* ── Light Theme ─────────────────────────────────────────────── */ +body.light { + --bg-base: #f0f3f9; + --bg-surface: #ffffff; + --bg-elevated: #f7f9fc; + --bg-card: #ffffff; + --bg-hover: #eaeff8; + --bg-active: #dde5f4; + --bg-input: #f0f3f9; + --bg-input-f: #ffffff; + + --accent: #2980b9; + --accent-h: #1a6fa3; + --accent-dim: rgba(41,128,185,0.08); + --accent-border: rgba(41,128,185,0.22); + --accent-glow: 0 2px 12px rgba(41,128,185,0.2); + + --success: #27ae60; + --success-h: #219a52; + --success-dim: rgba(39,174,96,0.08); + --success-b: rgba(39,174,96,0.2); + + --warning: #e67e22; + --warning-dim: rgba(230,126,34,0.08); + --warning-b: rgba(230,126,34,0.2); + + --danger: #c0392b; + --danger-dim: rgba(192,57,43,0.07); + --danger-b: rgba(192,57,43,0.18); + + --info: #2980b9; + --info-dim: rgba(41,128,185,0.07); + --info-b: rgba(41,128,185,0.18); + + --text-1: #0f1923; + --text-2: #4a5c78; + --text-3: #94a3b8; + --text-link: #2980b9; + + --border: rgba(0,0,0,0.06); + --border-s: rgba(0,0,0,0.1); + --border-a: rgba(41,128,185,0.2); + + --sidebar-bg: #111827; + --sidebar-text: #4a5c78; + --sidebar-hover: rgba(255,255,255,0.05); + --sidebar-active-bg: rgba(52,152,219,0.12); + --sidebar-active-color: #5dade2; + --sidebar-active-border: rgba(52,152,219,0.3); + + --shadow-sm: 0 1px 3px rgba(0,0,0,0.07); + --shadow: 0 4px 16px rgba(0,0,0,0.08); + --shadow-lg: 0 8px 32px rgba(0,0,0,0.12); + --shadow-modal: 0 20px 60px rgba(0,0,0,0.18); +} + +/* Light mode — main area overrides (sidebar stays dark always) */ +body.light .content, +body.light #main-content { background: var(--bg-base); } +body.light .view-header { background: var(--bg-surface); border-color: var(--border); } +body.light .profile-card { background: var(--bg-card); } +body.light .profile-card:hover { background: var(--bg-hover); } +body.light .toolbar { background: var(--bg-surface); border-color: var(--border); } +body.light .sessions-table th { background: var(--bg-surface); } +body.light .form-select option { background: #fff; color: #0f1923; } +body.light .editor-modal { background: var(--bg-surface); } +body.light .settings-card { background: var(--bg-card); } +body.light .settings-card-header { background: var(--bg-elevated); } +body.light .about-item { background: var(--bg-elevated); } +body.light .editor-footer { background: var(--bg-elevated); } + +/* ── Reset & Base ─────────────────────────────────────────────── */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +html, body { + height: 100%; + overflow: hidden; + background: var(--bg-base); + color: var(--text-1); + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-size: 13px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#app { display: flex; flex-direction: column; height: 100vh; overflow: hidden; } + +/* ── Scrollbar ────────────────────────────────────────────────── */ +::-webkit-scrollbar { width: 5px; height: 5px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--border-s); border-radius: 6px; } +::-webkit-scrollbar-thumb:hover { background: var(--text-3); } + +/* ── Titlebar ─────────────────────────────────────────────────── */ +.titlebar { + height: 44px; + background: var(--sidebar-bg); + border-bottom: 1px solid rgba(255,255,255,0.05); + display: flex; + align-items: center; + flex-shrink: 0; + -webkit-app-region: drag; + z-index: 200; +} + +.titlebar-drag { width: 76px; flex-shrink: 0; } + +.titlebar-title { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + font-weight: 600; + color: #ffffff; + flex: 1; + letter-spacing: 0.01em; + opacity: 0.9; +} + +.titlebar-title span { color: rgba(255,255,255,0.7); font-weight: 500; } + +.titlebar-right { + display: flex; + align-items: center; + gap: 4px; + padding-right: 14px; + -webkit-app-region: no-drag; +} + +.theme-btn { + width: 30px; height: 30px; + border-radius: 8px; + border: none; + background: transparent; + color: rgba(255,255,255,0.35); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.15s, color 0.15s; +} +.theme-btn:hover { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8); } + +/* ── Layout ───────────────────────────────────────────────────── */ +.layout { display: flex; flex: 1; overflow: hidden; } + +/* ── Sidebar ──────────────────────────────────────────────────── */ +.sidebar { + width: 216px; + background: var(--sidebar-bg); + border-right: 1px solid rgba(255,255,255,0.05); + display: flex; + flex-direction: column; + flex-shrink: 0; + overflow: hidden; +} + +.sidebar-nav { + flex: 1; + display: flex; + flex-direction: column; + padding: 12px 8px; + gap: 2px; + overflow-y: auto; +} + +.nav-section { display: flex; flex-direction: column; gap: 2px; } + +.nav-section-label { + font-size: 9px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.12em; + color: rgba(255,255,255,0.2); + padding: 8px 10px 4px; +} + +.nav-section-bottom { + margin-top: auto; + padding-top: 8px; + border-top: 1px solid rgba(255,255,255,0.05); +} + +.nav-item { + display: flex; + align-items: center; + gap: 10px; + padding: 9px 10px; + border-radius: 8px; + border: 1px solid transparent; + background: transparent; + color: var(--sidebar-text); + font-size: 13px; + font-weight: 500; + font-family: inherit; + cursor: pointer; + transition: all 0.14s; + text-align: left; + width: 100%; + position: relative; +} + +.nav-item:hover { + background: var(--sidebar-hover); + color: rgba(255,255,255,0.7); +} + +.nav-item.active { + background: var(--sidebar-active-bg); + color: var(--sidebar-active-color); + border-color: var(--sidebar-active-border); + font-weight: 600; +} + +.nav-icon { width: 15px; height: 15px; flex-shrink: 0; opacity: 0.85; } +.nav-item.active .nav-icon { opacity: 1; } + +.nav-badge { + margin-left: auto; + font-size: 10px; + font-weight: 700; + padding: 1px 6px; + border-radius: 20px; + min-width: 18px; + text-align: center; + background: rgba(255,255,255,0.07); + color: rgba(255,255,255,0.3); +} + +.nav-badge.live { + background: rgba(39,174,96,0.18); + color: #2ecc71; +} + +/* Sidebar footer stats */ +.sidebar-stats { + display: flex; + align-items: center; + padding: 12px 14px; + border-top: 1px solid rgba(255,255,255,0.05); + gap: 0; +} + +.stat-item { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + position: relative; +} + +.stat-value { + font-size: 16px; + font-weight: 700; + color: rgba(255,255,255,0.7); + line-height: 1; + font-variant-numeric: tabular-nums; +} + +.stat-label { + font-size: 9px; + color: rgba(255,255,255,0.2); + text-transform: uppercase; + letter-spacing: 0.1em; +} + +.stat-divider { width: 1px; height: 28px; background: rgba(255,255,255,0.07); margin: 0 8px; } + +.stat-dot { + width: 6px; height: 6px; + border-radius: 50%; + background: rgba(255,255,255,0.15); + position: absolute; + top: 0; right: 4px; +} + +.stat-dot.running { + background: var(--success); + box-shadow: 0 0 6px var(--success-h); + animation: pulse 2s infinite; +} + +@keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:0.5;transform:scale(0.85)} } + +/* ── Content Area ─────────────────────────────────────────────── */ +.content { + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; + background: var(--bg-base); +} + +#main-content { + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; +} + +/* ── View Header (GoLogin-style top bar) ─────────────────────── */ +.view-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 20px; + height: 52px; + border-bottom: 1px solid var(--border); + background: var(--bg-surface); + flex-shrink: 0; + position: sticky; + top: 0; + z-index: 30; + box-shadow: var(--shadow-sm); +} + +.view-header-left { display: flex; align-items: center; gap: 10px; } +.view-header-right { display: flex; align-items: center; gap: 6px; } + +.view-title { + font-size: 15px; + font-weight: 700; + color: var(--text-1); + display: flex; + align-items: center; + gap: 8px; +} + +.view-title-icon { + width: 30px; height: 30px; + border-radius: 8px; + background: var(--accent-dim); + border: 1px solid var(--accent-border); + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; +} +.view-title-icon svg { width: 15px; height: 15px; color: var(--accent); } + +.profile-count-pill { + font-size: 11px; + font-weight: 600; + padding: 2px 9px; + border-radius: 20px; + background: var(--bg-active); + color: var(--text-3); + border: 1px solid var(--border-s); +} + +/* ── Toolbar (below header) ───────────────────────────────────── */ +.toolbar { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + border-bottom: 1px solid var(--border); + background: var(--bg-surface); + flex-shrink: 0; +} + +.toolbar-sep { width: 1px; height: 20px; background: var(--border-s); margin: 0 2px; } +.toolbar-spacer { flex: 1; } + +/* ── Buttons ──────────────────────────────────────────────────── */ +.btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 7px 14px; + border-radius: 8px; + border: 1px solid transparent; + font-size: 12.5px; + font-weight: 500; + font-family: inherit; + cursor: pointer; + transition: all 0.14s; + white-space: nowrap; + line-height: 1.3; +} + +.btn svg { width: 13px; height: 13px; flex-shrink: 0; } + +.btn-primary { + background: var(--accent); + color: #fff; + border-color: var(--accent); + font-weight: 600; + box-shadow: 0 1px 4px rgba(52,152,219,0.25); +} +.btn-primary:hover { + background: var(--accent-h); + box-shadow: var(--accent-glow); + transform: translateY(-1px); +} +.btn-primary:active { transform: translateY(0); } + +.btn-secondary { + background: var(--bg-elevated); + color: var(--text-1); + border-color: var(--border-s); +} +.btn-secondary:hover { background: var(--bg-active); border-color: var(--text-3); } + +.btn-ghost { + background: transparent; + color: var(--text-2); + border-color: transparent; +} +.btn-ghost:hover { background: var(--bg-hover); color: var(--text-1); border-color: var(--border-s); } + +.btn-danger { + background: var(--danger-dim); + color: var(--danger); + border-color: var(--danger-b); +} +.btn-danger:hover { background: rgba(231,76,60,0.18); } + +.btn-success { + background: var(--success-dim); + color: var(--success); + border-color: var(--success-b); +} +.btn-success:hover { background: rgba(39,174,96,0.18); } + +.btn-sm { padding: 5px 10px; font-size: 12px; border-radius: 7px; } +.btn-xs { padding: 3px 8px; font-size: 11px; border-radius: 6px; } +.btn-icon { padding: 0; width: 30px; height: 30px; justify-content: center; border-radius: 8px; } +.btn-icon-sm { padding: 0; width: 26px; height: 26px; justify-content: center; border-radius: 7px; } +.btn:disabled { opacity: 0.38; cursor: not-allowed; pointer-events: none; transform: none !important; } + +/* ── Search ───────────────────────────────────────────────────── */ +.search-wrap { position: relative; display: flex; align-items: center; } + +.search-wrap .search-icon { + position: absolute; + left: 9px; + color: var(--text-3); + display: flex; align-items: center; + pointer-events: none; +} + +.search-input { + background: var(--bg-input); + border: 1px solid var(--border-s); + border-radius: 8px; + color: var(--text-1); + font-family: inherit; + font-size: 12.5px; + padding: 7px 12px 7px 32px; + width: 220px; + outline: none; + transition: border-color 0.14s, box-shadow 0.14s; +} +.search-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-dim); +} +.search-input::placeholder { color: var(--text-3); } + +/* ── Bulk Action Bar ──────────────────────────────────────────── */ +.bulk-action-bar { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 20px; + background: rgba(52,152,219,0.06); + border-bottom: 1px solid var(--accent-border); + flex-shrink: 0; + animation: slideDown 0.14s ease; +} +@keyframes slideDown { from{opacity:0;transform:translateY(-6px)} to{opacity:1;transform:none} } +.bulk-count { font-size: 12px; font-weight: 600; color: var(--accent); } + +/* ── Profile list table (GoLogin-style rows) ─────────────────── */ +.profiles-table-wrap { + flex: 1; + overflow-y: auto; +} + +/* Column header row */ +.profiles-col-header { + display: grid; + grid-template-columns: 28px 36px 1fr 150px 120px 110px 164px; + align-items: center; + gap: 0; + padding: 0 16px; + height: 34px; + background: var(--bg-surface); + border-bottom: 1px solid var(--border-s); + position: sticky; + top: 0; + z-index: 10; +} + +.col-hdr { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-3); + padding: 0 8px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.col-hdr:first-child { padding-left: 0; } + +/* Profile row */ +.profile-card { + display: grid; + grid-template-columns: 28px 36px 1fr 150px 120px 110px 164px; + align-items: center; + gap: 0; + padding: 0 16px; + height: 48px; + background: var(--bg-card); + border-bottom: 1px solid var(--border); + position: relative; + cursor: default; + transition: background 0.12s; +} + +.profile-card:last-child { border-bottom: none; } +.profile-card:hover { background: var(--bg-hover); } +.profile-card:hover .profile-actions { opacity: 1; } +.profile-card.selected { background: var(--accent-dim); } +.profile-card.selected:hover { background: rgba(52,152,219,0.15); } + +.profile-card.running { background: rgba(39,174,96,0.05); } +.profile-card.running:hover { background: rgba(39,174,96,0.09); } +.profile-card.running::before { + content: ''; + position: absolute; + left: 0; top: 0; bottom: 0; + width: 3px; + background: var(--success); + border-radius: 0 2px 2px 0; +} +.profile-card.running .profile-actions { opacity: 1; } + +/* Grid cells */ +.profile-cell { + padding: 0 8px; + overflow: hidden; + display: flex; + align-items: center; +} +.profile-cell:first-child { padding-left: 0; } + +/* Checkbox */ +.checkbox-wrap { + display: inline-flex; align-items: center; + cursor: pointer; position: relative; flex-shrink: 0; +} +.checkbox-wrap input[type="checkbox"] { position: absolute; opacity: 0; width: 0; height: 0; } +.checkmark { + width: 15px; height: 15px; + border: 1.5px solid var(--border-s); + border-radius: 4px; + background: var(--bg-input); + transition: all 0.13s; + position: relative; + flex-shrink: 0; +} +.checkbox-wrap input:checked + .checkmark { background: var(--accent); border-color: var(--accent); } +.checkbox-wrap input:checked + .checkmark::after { + content: ''; + position: absolute; + left: 3px; top: 1px; + width: 5px; height: 8px; + border: 2px solid #fff; + border-top: none; border-left: none; + transform: rotate(44deg); +} +.checkbox-wrap:hover .checkmark { border-color: var(--accent); } + +/* Profile identity cell */ +.profile-identity { + display: flex; align-items: center; gap: 10px; + overflow: hidden; + padding: 0 8px; +} + +.profile-avatar { + width: 30px; height: 30px; + border-radius: 8px; + display: flex; align-items: center; justify-content: center; + font-size: 11px; font-weight: 700; + flex-shrink: 0; + color: rgba(255,255,255,0.95); + letter-spacing: 0.02em; +} + +.profile-name-wrap { overflow: hidden; } + +.profile-name { + font-size: 13px; + font-weight: 600; + color: var(--text-1); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: 1.3; +} + +.profile-sub { + font-size: 11px; + color: var(--text-3); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: 1.3; + display: flex; + align-items: center; + gap: 4px; +} + +/* Status pill */ +.status-pill { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 11px; + font-weight: 600; + padding: 3px 9px; + border-radius: 20px; + letter-spacing: 0.02em; + white-space: nowrap; +} +.status-pill-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; } + +.status-pill.running { + background: rgba(39,174,96,0.12); + color: #2ecc71; + border: 1px solid rgba(39,174,96,0.25); +} +.status-pill.running .status-pill-dot { background: #2ecc71; box-shadow: 0 0 5px #2ecc71; animation: pulse 2s infinite; } + +.status-pill.idle { + background: var(--bg-active); + color: var(--text-3); + border: 1px solid var(--border); +} +.status-pill.idle .status-pill-dot { background: var(--text-3); } + +/* Tags in cells */ +.profile-tag { + font-size: 10px; + padding: 1px 7px; + border-radius: 20px; + font-weight: 500; + white-space: nowrap; + border: 1px solid transparent; +} +.profile-tag.tag-chrome { background: rgba(52,152,219,0.1); color: #5dade2; border-color: rgba(52,152,219,0.2); } +.profile-tag.tag-firefox { background: rgba(243,156,18,0.1); color: #f39c12; border-color: rgba(243,156,18,0.2); } +.profile-tag.tag-cookies { background: rgba(243,156,18,0.08); color: var(--warning); border-color: var(--warning-b); } +.profile-tag.tag-running { background: var(--success-dim); color: var(--success); border-color: var(--success-b); } +.profile-tag.tag-proxy { background: var(--bg-active); color: var(--text-3); border-color: var(--border-s); font-family: 'JetBrains Mono', monospace; font-size: 9.5px; max-width: 130px; overflow: hidden; text-overflow: ellipsis; display: inline-block; } + +/* Actions cell */ +.profile-actions { + display: flex; + align-items: center; + gap: 3px; + flex-shrink: 0; + opacity: 0; + transition: opacity 0.13s; + justify-content: flex-end; +} + +/* ── Empty State ─────────────────────────────────────────────── */ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 14px; + padding: 80px 20px; + text-align: center; + flex: 1; +} + +.empty-icon { + width: 60px; height: 60px; + border-radius: 18px; + background: var(--bg-elevated); + border: 1px solid var(--border-s); + display: flex; align-items: center; justify-content: center; +} +.empty-icon svg { width: 26px; height: 26px; color: var(--text-3); } +.empty-state h3 { font-size: 15px; font-weight: 700; color: var(--text-1); } +.empty-state p { font-size: 13px; color: var(--text-3); max-width: 300px; line-height: 1.65; } + +/* ── Sessions (table style) ───────────────────────────────────── */ +.sessions-wrap { flex: 1; overflow-y: auto; padding: 0 20px 20px; } + +.sessions-table { width: 100%; border-collapse: collapse; } +.sessions-table th { + text-align: left; + padding: 10px 14px; + font-size: 10px; + font-weight: 700; + color: var(--text-3); + text-transform: uppercase; + letter-spacing: 0.08em; + border-bottom: 1px solid var(--border-s); + background: var(--bg-surface); + position: sticky; top: 0; z-index: 5; + white-space: nowrap; +} +.sessions-table td { + padding: 11px 14px; + font-size: 12.5px; + border-bottom: 1px solid var(--border); + color: var(--text-2); + vertical-align: middle; +} +.sessions-table tbody tr { transition: background 0.1s; } +.sessions-table tbody tr:hover td { background: var(--bg-hover); } +.sessions-table code { + font-family: 'JetBrains Mono', monospace; + font-size: 11px; + background: var(--bg-active); + padding: 2px 7px; + border-radius: 5px; + color: var(--text-2); +} +.url-cell { + font-family: 'JetBrains Mono', monospace; + font-size: 11px; color: var(--text-3); + max-width: 200px; overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; + display: block; +} + +/* ── Profile Editor Modal ─────────────────────────────────────── */ +.editor-overlay { + position: fixed; inset: 0; + background: rgba(0,0,0,0.65); + backdrop-filter: blur(8px); + z-index: 1000; + display: flex; align-items: center; justify-content: center; + padding: 20px; + animation: oFade 0.15s ease; +} +@keyframes oFade { from{opacity:0} to{opacity:1} } + +.editor-modal { + background: var(--bg-card); + border: 1px solid var(--border-s); + border-radius: 14px; + width: 100%; + max-width: 900px; + max-height: 92vh; + display: flex; + flex-direction: column; + box-shadow: var(--shadow-modal); + overflow: hidden; + animation: mSlide 0.2s cubic-bezier(0.34,1.56,0.64,1); +} +@keyframes mSlide { from{transform:translateY(24px) scale(0.96);opacity:0} to{transform:none;opacity:1} } + +/* Editor Header */ +.editor-header { + display: flex; align-items: center; justify-content: space-between; + padding: 14px 20px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + background: var(--bg-surface); + gap: 10px; +} +.editor-header-left { display: flex; align-items: center; gap: 10px; } +.editor-header-icon { + width: 32px; height: 32px; border-radius: 9px; + background: var(--accent-dim); border: 1px solid var(--accent-border); + display: flex; align-items: center; justify-content: center; flex-shrink: 0; +} +.editor-header-icon svg { width: 15px; height: 15px; color: var(--accent); } +.editor-header h2 { font-size: 14px; font-weight: 700; color: var(--text-1); } +.editor-header-sub { font-size: 11px; color: var(--text-3); margin-top: 1px; } + +/* Editor Layout: sidebar + content */ +.editor-layout { + display: flex; + flex: 1; + overflow: hidden; +} + +/* Left sidebar nav */ +.editor-sidenav { + width: 168px; + flex-shrink: 0; + background: var(--sidebar-bg); + border-right: 1px solid rgba(255,255,255,0.06); + padding: 10px 8px; + display: flex; + flex-direction: column; + gap: 2px; + overflow-y: auto; +} + +.editor-nav-section { + padding: 6px 8px 3px; + font-size: 9.5px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.09em; + color: rgba(255,255,255,0.2); + margin-top: 4px; +} +.editor-nav-section:first-child { margin-top: 0; } + +.editor-tab { + display: flex; + align-items: center; + gap: 8px; + padding: 7px 10px; + font-size: 12px; + font-weight: 500; + color: rgba(255,255,255,0.35); + cursor: pointer; + border: none; + background: transparent; + border-radius: 7px; + transition: all 0.13s; + white-space: nowrap; + font-family: inherit; + width: 100%; + text-align: left; +} +.editor-tab svg { width: 14px; height: 14px; flex-shrink: 0; opacity: 0.7; } +.editor-tab:hover { color: rgba(255,255,255,0.7); background: rgba(255,255,255,0.06); } +.editor-tab.active { + color: #5dade2; + background: rgba(52,152,219,0.14); + font-weight: 600; +} +.editor-tab.active svg { opacity: 1; } + +/* Right content */ +.editor-body { flex: 1; overflow-y: auto; padding: 22px 24px 28px; background: var(--bg-card); } +.editor-panel { display: none; } +.editor-panel.active { display: block; } + +.editor-footer { + display: flex; align-items: center; justify-content: flex-end; gap: 8px; + padding: 12px 20px; + border-top: 1px solid var(--border); + flex-shrink: 0; + background: var(--bg-surface); +} + +/* ── Form Elements ─────────────────────────────────────────────── */ +.form-section { margin-bottom: 28px; } + +.form-section-title { + font-size: 11px; + font-weight: 700; + color: var(--text-2); + letter-spacing: 0.03em; + margin-bottom: 12px; + padding: 8px 12px; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-left: 3px solid var(--accent); + border-radius: 0 8px 8px 0; + display: flex; align-items: center; gap: 7px; +} +.form-section-title svg { width: 13px; height: 13px; color: var(--accent); flex-shrink: 0; } + +.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } +.form-grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; } +.form-group { display: flex; flex-direction: column; gap: 5px; } +.form-group.full { grid-column: 1 / -1; } + +.form-label { + font-size: 11.5px; + font-weight: 500; + color: var(--text-2); + display: flex; align-items: center; gap: 5px; flex-wrap: wrap; +} + +/* Tier badges */ +.badge-pro { + font-size: 8px; padding: 1px 5px; border-radius: 3px; + background: rgba(243,156,18,0.12); color: var(--warning); + font-weight: 700; letter-spacing: 0.04em; + border: 1px solid var(--warning-b); +} +.badge-ent { + font-size: 8px; padding: 1px 5px; border-radius: 3px; + background: rgba(52,152,219,0.1); color: var(--accent); + font-weight: 700; letter-spacing: 0.04em; + border: 1px solid var(--accent-border); +} + +.form-input, .form-select, .form-textarea { + background: var(--bg-input); + border: 1px solid var(--border-s); + border-radius: 8px; + color: var(--text-1); + font-family: inherit; + font-size: 13px; + padding: 8px 12px; + outline: none; + transition: border-color 0.14s, box-shadow 0.14s, background 0.14s; + width: 100%; +} +.form-input:focus, .form-select:focus, .form-textarea:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-dim); + background: var(--bg-input-f); +} +.form-input::placeholder, .form-textarea::placeholder { color: var(--text-3); } + +.form-select { + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%234a5c78' stroke-width='2.5'%3E%3Cpolyline points='6,9 12,15 18,9'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + padding-right: 30px; +} +.form-select option { background: #1d2740; color: var(--text-1); } + +.form-textarea { + resize: vertical; + min-height: 76px; + font-family: 'JetBrains Mono', monospace; + font-size: 11.5px; + line-height: 1.6; +} + +.form-hint { font-size: 11px; color: var(--text-3); line-height: 1.5; margin-top: 2px; } +.form-hint code { + font-family: 'JetBrains Mono', monospace; + background: var(--bg-active); + padding: 1px 5px; border-radius: 3px; + font-size: 10.5px; color: var(--text-2); +} +.form-hint-inline { + font-size: 10px; font-family: 'JetBrains Mono', monospace; + color: var(--text-3); background: var(--bg-active); + padding: 1px 5px; border-radius: 3px; +} +.form-hint-banner { + padding: 10px 13px; margin-bottom: 14px; + border-radius: 8px; + background: var(--info-dim); + border: 1px solid var(--info-b); + color: var(--text-2); + font-size: 12px; line-height: 1.55; +} + +/* ── Toggle Switch ─────────────────────────────────────────────── */ +.toggle-row { + display: flex; align-items: center; justify-content: space-between; + padding: 9px 12px; + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: 8px; + margin-bottom: 6px; + transition: background 0.12s, border-color 0.12s; +} +.toggle-row:hover { background: var(--bg-hover); border-color: var(--border-s); } +.toggle-info { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; } + +.toggle-switch { + position: relative; width: 36px; height: 20px; + flex-shrink: 0; margin-left: 14px; +} +.toggle-switch input { opacity: 0; width: 0; height: 0; position: absolute; } +.toggle-slider { + position: absolute; inset: 0; + background: var(--bg-active); + border-radius: 20px; + cursor: pointer; + border: 1.5px solid var(--border-s); + transition: background 0.18s, border-color 0.18s; +} +.toggle-slider::before { + content: ''; + position: absolute; + width: 13px; height: 13px; + border-radius: 50%; + background: var(--text-3); + top: 2px; left: 2px; + transition: all 0.2s cubic-bezier(0.34,1.56,0.64,1); + box-shadow: 0 1px 3px rgba(0,0,0,0.3); +} +.toggle-switch input:checked + .toggle-slider { background: var(--accent); border-color: var(--accent); } +.toggle-switch input:checked + .toggle-slider::before { background: #fff; left: 19px; } + +/* ── Input with Button ─────────────────────────────────────────── */ +.input-with-btn { display: flex; gap: 6px; } +.input-with-btn .form-input { flex: 1; } + +/* ── Custom Headers ────────────────────────────────────────────── */ +.custom-header-row { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; } +.custom-header-row .form-input { flex: 1; } +.custom-header-sep { color: var(--text-3); font-weight: 700; flex-shrink: 0; } + +/* ── Settings ──────────────────────────────────────────────────── */ +.settings-scroll-wrap { + flex: 1; + overflow-y: auto; + min-height: 0; +} +.settings-form { padding: 24px; max-width: 720px; display: flex; flex-direction: column; gap: 16px; } + +/* ── Settings Cards ─────────────────────────────────────────────────────────── */ +.settings-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 12px; + overflow: hidden; +} + +.settings-card-header { + display: flex; + align-items: center; + gap: 14px; + padding: 16px 20px; + border-bottom: 1px solid var(--border); + background: var(--bg-surface); +} + +.settings-card-icon { + width: 38px; height: 38px; + border-radius: 10px; + border: 1px solid transparent; + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; +} + +.settings-card-title { + font-size: 13px; + font-weight: 700; + color: var(--text-1); + line-height: 1.3; +} + +.settings-card-desc { + font-size: 11px; + color: var(--text-3); + margin-top: 2px; + line-height: 1.4; +} + +.settings-card-body { + padding: 18px 20px; +} + +/* About grid */ +.about-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} + +.about-item { + display: flex; + flex-direction: column; + gap: 3px; + padding: 10px 14px; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: 8px; +} + +.about-item-label { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.07em; + color: var(--text-3); +} + +.about-item-value { + font-size: 12px; + font-weight: 600; + color: var(--text-1); +} +.form-actions { margin-top: 20px; display: flex; gap: 8px; } + +/* ── Toast ─────────────────────────────────────────────────────── */ +.toast-container { + position: fixed; bottom: 20px; right: 20px; + z-index: 9000; + display: flex; flex-direction: column; gap: 7px; + pointer-events: none; +} +.toast { + display: flex; align-items: center; gap: 9px; + padding: 10px 14px; + background: var(--bg-elevated); + border: 1px solid var(--border-s); + border-left-width: 3px; + border-radius: 10px; + box-shadow: var(--shadow-lg); + font-size: 12.5px; + color: var(--text-1); + pointer-events: all; + max-width: 340px; + opacity: 0; transform: translateX(20px); + transition: opacity 0.2s ease, transform 0.2s ease; +} +.toast.show { opacity: 1; transform: translateX(0); } +.toast.toast-success { border-left-color: var(--success); } +.toast.toast-error { border-left-color: var(--danger); } +.toast.toast-warning { border-left-color: var(--warning); } +.toast.toast-info { border-left-color: var(--accent); } + +/* ── Spinner ───────────────────────────────────────────────────── */ +.spinner { + width: 16px; height: 16px; + border: 2px solid var(--border-s); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 0.65s linear infinite; + flex-shrink: 0; +} +@keyframes spin { to { transform: rotate(360deg); } } + +/* ── Context Menu ──────────────────────────────────────────────── */ +.context-menu { + position: fixed; + background: var(--bg-elevated); + border: 1px solid var(--border-s); + border-radius: 10px; + padding: 4px; + z-index: 5000; + min-width: 185px; + box-shadow: var(--shadow-lg); + animation: ctxIn 0.1s ease; +} +@keyframes ctxIn { from{opacity:0;transform:scale(0.94) translateY(-4px)} to{opacity:1;transform:none} } + +.context-menu-item { + display: flex; align-items: center; gap: 9px; + padding: 7px 10px; + border-radius: 7px; + font-size: 12.5px; + color: var(--text-2); + cursor: pointer; + transition: all 0.1s; +} +.context-menu-item:hover { background: var(--bg-hover); color: var(--text-1); } +.context-menu-item.danger { color: var(--danger); } +.context-menu-item.danger:hover { background: var(--danger-dim); } +.context-menu-item svg { width: 13px; height: 13px; flex-shrink: 0; } +.context-menu-sep, .context-menu-divider { height: 1px; background: var(--border); margin: 3px 4px; } + +/* ── Info box ──────────────────────────────────────────────────── */ +.info-box { + display: flex; align-items: flex-start; gap: 9px; + padding: 10px 13px; border-radius: 8px; + font-size: 12px; line-height: 1.5; +} +.info-box.info { background: var(--info-dim); border: 1px solid var(--info-b); color: var(--text-2); } +.info-box.warning { background: var(--warning-dim); border: 1px solid var(--warning-b); color: var(--warning); } +.info-box.success { background: var(--success-dim); border: 1px solid var(--success-b); color: var(--success); } +.info-box svg { width: 14px; height: 14px; flex-shrink: 0; margin-top: 1px; } + +/* ── Misc ──────────────────────────────────────────────────────── */ +.divider { height: 1px; background: var(--border); margin: 14px 0; } +.hidden { display: none !important; } +.font-mono { font-family: 'JetBrains Mono', monospace; font-size: 12px; } +.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.text-muted { color: var(--text-3); font-size: 12px; } +.text-sm { font-size: 12px; } + +/* ── Badge standalone ─────────────────────────────────────────── */ +.badge { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 20px; } +.badge-green { background: var(--success-dim); color: var(--success); border: 1px solid var(--success-b); } +.badge-orange { background: var(--warning-dim); color: var(--warning); border: 1px solid var(--warning-b); } +.badge-red { background: var(--danger-dim); color: var(--danger); border: 1px solid var(--danger-b); } +.badge-blue { background: var(--accent-dim); color: var(--accent); border: 1px solid var(--accent-border); } + +/* ── Modal legacy ─────────────────────────────────────────────── */ +.modal-overlay { + position: fixed; inset: 0; + background: rgba(0,0,0,0.65); + backdrop-filter: blur(6px); + z-index: 1000; + display: flex; align-items: center; justify-content: center; + padding: 20px; +} +.modal-overlay.hidden { display: none; } + +/* ── About box in settings ─────────────────────────────────────── */ +.about-box { + display: flex; align-items: center; gap: 14px; + padding: 16px; + background: var(--bg-input); + border: 1px solid var(--border-s); + border-radius: 10px; + margin-top: 24px; +} +.about-logo { width: 40px; height: 40px; border-radius: 10px; flex-shrink: 0; } +.about-info { flex: 1; min-width: 0; } +.about-name { font-size: 14px; font-weight: 700; color: var(--text-1); } +.about-desc { font-size: 11.5px; color: var(--text-3); margin-top: 2px; } +/* ── IP Check Result Bar ─────────────────────────────────────────────────── */ +.ip-result-bar { + display: flex; align-items: center; gap: 8px; flex-wrap: wrap; + padding: 5px 10px; + background: rgba(39,174,96,0.08); + border: 1px solid rgba(39,174,96,0.2); + border-radius: 6px; + margin-top: 5px; + font-size: 11.5px; + width: 100%; + box-sizing: border-box; +} +.ip-flag { font-size: 15px; line-height: 1; } +.ip-result-ip { font-weight: 700; color: var(--text-1); font-family: var(--font-mono); } +.ip-result-loc { color: var(--text-2); } +.ip-result-isp { color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 160px; } +.ip-badge-dc { + background: rgba(231,76,60,0.15); color: var(--danger); + border: 1px solid rgba(231,76,60,0.3); + border-radius: 4px; font-size: 10px; font-weight: 700; + padding: 1px 5px; +} +.ip-result-close { + margin-left: auto; background: none; border: none; + color: var(--text-3); cursor: pointer; font-size: 11px; padding: 2px 4px; + border-radius: 3px; +} +.ip-result-close:hover { color: var(--text-1); background: var(--hover-bg); } + +/* spinner */ +@keyframes spin { to { transform: rotate(360deg); } } +.spin { display: inline-block; animation: spin 0.8s linear infinite; } +.btn.loading { opacity: 0.7; pointer-events: none; } + +/* ── Quick Proxy Popover ─────────────────────────────────────────────────── */ +.quick-proxy-popover { + position: fixed; + z-index: 9999; + background: var(--bg-card); + border: 1px solid var(--border-m); + border-radius: 10px; + box-shadow: 0 8px 32px rgba(0,0,0,0.45); + width: 320px; + overflow: hidden; +} +.quick-proxy-header { + display: flex; align-items: center; justify-content: space-between; + padding: 10px 14px 8px; + border-bottom: 1px solid var(--border-s); + font-size: 12.5px; font-weight: 600; color: var(--text-1); + background: var(--bg-sidebar); +} +.quick-proxy-body { padding: 12px 14px 12px; } +.form-input-sm { + height: 28px; font-size: 12px; + padding: 0 8px; +} + +/* ── Kernel Manager ──────────────────────────────────────────────────────── */ +.kernel-loading { padding: 8px 0; } + +.kernel-section-label { + font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; + text-transform: uppercase; color: var(--text-3); + margin-bottom: 8px; +} + +.kernel-installed-section { margin-bottom: 4px; } + +.kernel-installed-row { + display: flex; align-items: center; justify-content: space-between; + padding: 6px 10px; margin-bottom: 4px; + background: var(--bg-input); + border: 1px solid var(--border-s); + border-radius: 6px; + gap: 8px; +} +.kernel-installed-info { display: flex; align-items: center; gap: 8px; } + +.kernel-version-tag { + display: inline-block; + font-family: var(--font-mono); font-size: 11px; font-weight: 600; + padding: 2px 8px; border-radius: 4px; + background: var(--bg-toolbar); color: var(--text-2); + border: 1px solid var(--border-s); +} +.kernel-version-tag.installed { + background: rgba(39,174,96,0.12); + color: var(--success); + border-color: rgba(39,174,96,0.3); +} + +.kernel-releases-list { + display: flex; flex-direction: column; gap: 8px; + max-height: 420px; overflow-y: auto; + padding-right: 2px; +} +.kernel-releases-list::-webkit-scrollbar { width: 4px; } +.kernel-releases-list::-webkit-scrollbar-thumb { background: var(--border-m); border-radius: 2px; } + +.kernel-release-row { + background: var(--bg-input); + border: 1px solid var(--border-s); + border-radius: 8px; + padding: 10px 12px; +} +.kernel-release-row.kernel-installed-row-highlight { + border-color: rgba(39,174,96,0.25); + background: rgba(39,174,96,0.04); +} + +.kernel-release-header { + display: flex; align-items: center; justify-content: space-between; + margin-bottom: 8px; + flex-wrap: wrap; gap: 4px; +} + +.kernel-progress-wrap { + position: relative; height: 6px; + background: var(--border-s); border-radius: 3px; + overflow: hidden; margin-bottom: 8px; +} +.kernel-progress-bar { + position: absolute; left: 0; top: 0; bottom: 0; + background: var(--accent); border-radius: 3px; + transition: width 0.3s ease; +} +.kernel-progress-label { + position: absolute; right: 0; top: -18px; + font-size: 10px; color: var(--text-3); +} + +.kernel-assets { display: flex; flex-direction: column; gap: 4px; } + +.kernel-asset-row { + display: flex; align-items: center; gap: 8px; + padding: 4px 6px; + background: var(--bg-card); + border: 1px solid var(--border-xs, var(--border-s)); + border-radius: 5px; +} +.kernel-asset-name { + flex: 1; min-width: 0; + font-family: var(--font-mono); font-size: 11px; + color: var(--text-2); + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; +} +.kernel-asset-size { + font-size: 10.5px; color: var(--text-3); + flex-shrink: 0; white-space: nowrap; +} + +/* ── Profile card: allow rows to expand vertically when inline editor open ── */ +.profile-card { + height: auto; + min-height: 48px; + align-items: flex-start; + padding-top: 0; + padding-bottom: 0; +} +.profile-card > .profile-cell, +.profile-card > .profile-identity, +.profile-card > .profile-cell.profile-actions { + min-height: 48px; + align-items: center; +} +/* Proxy cell stretches to content */ +.profile-cell.proxy-cell { + padding: 6px 8px; + align-items: flex-start; + min-height: 48px; + overflow: visible; +} + +/* ── Proxy Cell Content ─────────────────────────────────────────────────────── */ +.proxy-cell-content { + display: flex; + flex-direction: column; + gap: 3px; + width: 100%; + min-width: 0; +} + +.proxy-cell-row { + display: flex; + align-items: center; + gap: 4px; + min-width: 0; + overflow: hidden; +} + +.proxy-scheme-badge { + display: inline-flex; + align-items: center; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.04em; + padding: 1px 5px; + border-radius: 3px; + color: #fff; + flex-shrink: 0; + text-transform: uppercase; +} + +.proxy-host-text { + font-family: 'JetBrains Mono', monospace; + font-size: 11px; + color: var(--text-2); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + min-width: 0; + cursor: text; + border-bottom: 1px dashed transparent; + transition: border-color 0.13s, color 0.13s; +} +.proxy-host-text:hover { + color: var(--text-1); + border-bottom-color: var(--accent); +} + +/* GoLogin-style inline input — replaces proxy-host-text in-place */ +.proxy-inline-input { + flex: 1; + min-width: 0; + background: var(--bg-input); + border: 1px solid var(--accent); + border-radius: 5px; + color: var(--text-1); + font-family: 'JetBrains Mono', monospace; + font-size: 11px; + padding: 2px 6px; + outline: none; + box-shadow: 0 0 0 2px var(--accent-dim); + transition: border-color 0.13s, box-shadow 0.13s; + height: 22px; +} +.proxy-inline-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 2px var(--accent-dim); +} +.proxy-inline-input::placeholder { color: var(--text-3); } + +.proxy-edit-hint { + flex-shrink: 0; + color: var(--text-3); + opacity: 0; + transition: opacity 0.13s; + display: flex; + align-items: center; +} +.proxy-edit-hint svg { width: 11px; height: 11px; } +.proxy-cell-row:hover .proxy-edit-hint { opacity: 1; } + +/* No-proxy button */ +.proxy-none-btn { + font-size: 11px; + color: var(--text-3); + cursor: pointer; + padding: 2px 0; + border-bottom: 1px dashed var(--border-s); + transition: color 0.12s, border-color 0.12s; + display: inline-block; +} +.proxy-none-btn:hover { color: var(--accent); border-color: var(--accent); } + +/* IP result row */ +.proxy-ip-row { + display: flex; + align-items: center; + gap: 4px; + flex-wrap: nowrap; + overflow: hidden; +} + +.proxy-ip-result { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 10.5px; + color: var(--text-2); + font-family: 'JetBrains Mono', monospace; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 120px; +} +.proxy-ip-result.dc { color: var(--danger); } + +.proxy-country-code { + font-size: 10px; + font-weight: 700; + color: var(--text-3); + letter-spacing: 0.04em; + flex-shrink: 0; +} + +.proxy-ip-checking { + font-size: 10.5px; + color: var(--text-3); + font-style: italic; +} + +.proxy-check-btn { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 10.5px; + font-weight: 500; + font-family: inherit; + color: var(--accent); + background: var(--accent-dim); + border: 1px solid var(--accent-border); + border-radius: 4px; + padding: 1px 7px; + cursor: pointer; + transition: background 0.12s; + white-space: nowrap; +} +.proxy-check-btn:hover { background: rgba(52,152,219,0.2); } +.proxy-check-btn.loading { opacity: 0.5; pointer-events: none; } +.proxy-check-btn svg { width: 10px; height: 10px; } + +.proxy-recheck-btn { + background: none; + border: none; + font-size: 12px; + color: var(--text-3); + cursor: pointer; + padding: 0 2px; + line-height: 1; + transition: color 0.12s; + flex-shrink: 0; +} +.proxy-recheck-btn:hover { color: var(--accent); } +.proxy-recheck-btn.loading { animation: spin 0.8s linear infinite; pointer-events: none; } + +/* ── Inline Proxy Editor (PIE) ──────────────────────────────────────────────── */ +/* Legacy pie-* classes kept for safety — inline editor is now GoLogin-style (proxy-inline-input) */ + +/* ── Update Banner ──────────────────────────────────────────────────────────── */ +.update-banner { + display: flex; + align-items: center; + gap: 8px; + padding: 7px 16px; + background: linear-gradient(90deg, rgba(52,152,219,0.12), rgba(52,152,219,0.06)); + border-bottom: 1px solid var(--accent-border); + font-size: 12px; + color: var(--text-1); + flex-shrink: 0; + animation: slideDown 0.2s ease; +} + +.update-banner-icon { + display: flex; + align-items: center; + color: var(--accent); + flex-shrink: 0; +} +.update-banner-icon svg { width: 14px; height: 14px; } + +.update-banner-text { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--text-2); + font-size: 12px; +} + +/* ── Update pill in settings / kernel manager ───────────────────────────────── */ +.update-pill { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 10px; + font-weight: 700; + padding: 2px 7px; + border-radius: 20px; + background: rgba(52,152,219,0.12); + color: var(--accent); + border: 1px solid var(--accent-border); + white-space: nowrap; +} +.update-pill svg { width: 10px; height: 10px; } + +/* ── Light mode overrides for new elements ──────────────────────────────────── */ +body.light .proxy-inline-input { background: var(--bg-input); color: var(--text-1); } +body.light .update-banner { background: linear-gradient(90deg, rgba(41,128,185,0.08), rgba(41,128,185,0.03)); } + +/* ── Proxy Status Dot ───────────────────────────────────────────────────────── */ +.proxy-status-dot { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; + display: inline-block; + margin-right: 2px; +} +.proxy-status-dot.ok { background: var(--success); box-shadow: 0 0 4px var(--success); } +.proxy-status-dot.ok.dc { background: var(--warning); box-shadow: 0 0 4px var(--warning); } +.proxy-status-dot.error { background: var(--danger); box-shadow: 0 0 4px var(--danger); } +.proxy-status-dot.checking { background: var(--accent); animation: pulse 1s infinite; } +.proxy-status-dot.unchecked { background: var(--text-3); } + +/* Proxy flag + country inline */ +.proxy-flag-country { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 10.5px; + color: var(--text-2); + font-family: 'JetBrains Mono', monospace; +} + +/* ── Inline Proxy Editor status line ───────────────────────────────────────── */ +.pie-status { + font-size: 11px; + font-family: 'JetBrains Mono', monospace; + padding: 2px 6px; + border-radius: 4px; + flex: 1; +} +.pie-status.ok { color: var(--success); background: var(--success-dim); border: 1px solid var(--success-b); } +.pie-status.error { color: var(--danger); background: var(--danger-dim); border: 1px solid var(--danger-b); } +.pie-status.checking { color: var(--accent); background: var(--accent-dim); border: 1px solid var(--accent-border); }